SharePoint Rich Content Editor with CKEditor Widgets

0

In this article, we build a CKEditor widget representing a SharePoint document link. This introduces a SharePoint Rich Content Editor alternative, which provides features to build a variety custom widgets. At the same time, we demonstrate the ability to leverage SharePoint data and the 2013 REST API.

The following image displays a standard CKEditor control, which includes a toolbar containing various features to deliver rich content without requiring the author to understand HTML or CSS.

The first step is create the CKEditor widget, which is the JavaScript plugin.js located in the widget folder. 

 Javascript |  copy code |? 
01
CKEDITOR.plugins.add('documentlinkwidget', {
02
    requires: 'widget',
03
    icons: 'documentlinkwidget',
04
    init: function(editor) {
05
       editor.widgets.add('documentlinkwidget', {
06
           button: 'Add document link',
07
           template:
08
               '<div class="documentlinkwidget" title="">' +
09
               '<a href="" class="doclinkwidget" style="width: 200px;"></a>' +
10
               '</div>',
11
           requiredContent: 'div(documentlinkwidget); a(doclinkwidget)',
12

In the init, we add the widget to the CKEditor. The template defines the widget markup structure. The requiredContent represents the mandatory widget content, which is a div and a element.

 Javascript |  copy code |? 
01
            init: function() {
02
                var width = this.element.getStyle('width');
03
                if (width)
04
                    this.setData('width', width);
05
                else
06
                    this.setData('width', '200px'); //default
07
 
08
                var id = this.element.getAttribute('title');
09
                if (id)
10
                    this.setData('docid', id);
11
            },
12

Next, add an init function to set the data. The width value is retrieved from the div element style attribute. If the style does exist then width is set to “200px” as the default. The id is retrieved from the title attribute, which is the docid value.

 Javascript |  copy code |? 
1
           data: function() {
2
                var width = this.data.width;
3
                if (width == '')
4
                    this.element.removeStyle('width');
5
                else
6
                    this.element.setStyle('width', width);
7

In the data function above, we build the widget markup. The div element style attribute is set to the appropriate width value. Next, we will retrieve the SharePoint document information using a REST API call.

 Javascript |  copy code |? 
01
               var spQuery = {
02
                    element: '',
03
                    url: '',
04
                    init: function(element) {
05
                        spQuery.element = element;
06
                        spQuery.url = _spPageContextInfo.webAbsoluteUrl + "/_api/lists/getByTitle('" + encodeURIComponent(spQuery.element.data.sourcelistname) + "')/items(" + spQuery.element.data.docid + ")/File?$select=Title,Name,Exists,Length,Level,ServerRelativeUrl";
07
                    },
08
                    load: function() {
09
                        $.ajax(
10
                            {
11
                                url: spQuery.url,
12
                                method: "GET",
13
                                headers: { "accept": "application/json;odata=verbose" },
14
                                success: spQuery.onSuccess,
15
                                error: spQuery.onError
16
                            }
17
                        );
18
                    },
19
                    onSuccess: function(data) {
20
                        spQuery.element.element.setAttribute('title', spQuery.element.data.docid);
21
 
22
                        var link = spQuery.element.element.findOne('a');
23
                        if (link) {
24
                            link.setText(data.d.Title || data.d.Name); //title is not required so set to name if missing
25
                            link.setAttribute('href', data.d.ServerRelativeUrl);
26
                            link.setAttribute('class', 'doclinkwidget');
27
 }
28
                    },
29
                   onError: function(err) {
30
                        api.openMsgDialog('', JSON.stringify(err));
31
                    }
32
                };
33
 
34
                //get data if docid is set
35
                if (this.data.docid) {
36
                    spQuery.init(this);
37
                    spQuery.load();
38
                }
39
            }
40

SPQuery contains our initialize and JQuery logic, so we can retrieve the SharePoint document file information to build the widget markup. The init sets the SPQuery.element and SPQuery.url values, which will be used in the load function. The SPQuery.onSuccess and SPQuery.onError are the callbacks to handle the logic based on the response. The link.setText will set the value to Title or Name from the JSON result. The ServerRelativeUrl  result value is the href attribute. You can retrieve the icon and file size, which can also be included in the markup.

The final section will conditionally make the SPQuery call when a docid value is present. This is accomplished with SPQuery.init(this) and SPQuery.load() calls.

We also add logic to support a widget configuration dialog, which  allows the user to select a document and set the width. The following is the additional plugin.js code required to define a dialog.

 Javascript |  copy code |? 
1
init: function(editor) {
2
        CKEDITOR.dialog.add('documentlinkwidget', this.path + 'dialogs/documentlinkwidget.js');
3
 
4

We will cover the CKEditor widget dialog in the next post. The following is the complete widget plugin.js.

 Javascript |  copy code |? 
01
CKEDITOR.plugins.add('documentlinkwidget', {
02
    requires: 'widget',
03
 
04
    icons: 'documentlinkwidget',
05
 
06
    init: function(editor) {
07
        CKEDITOR.dialog.add('documentlinkwidget', this.path + 'dialogs/documentlinkwidget.js');
08
 
09
        editor.widgets.add('documentlinkwidget', {
10
            button: 'Add document link',
11
 
12
            template:
13
                '<div class="documentlinkwidget" title="">' +
14
                '<a href="" class="doclinkwidget" style="width: 200px;"></a>' +
15
                '</div>',
16
 
17
            allowedContent:
18
                'div(!documentlinkwidget); a(!doclinkwidget)',
19
 
20
            requiredContent: 'div(documentlinkwidget); a(doclinkwidget)',
21
 
22
            dialog: 'documentlinkwidget',
23
 
24
            upcast: function(element) {
25
                return element.name == 'div' && element.hasClass('documentlinkwidget');
26
            },
27
 
28
            init: function() {
29
                var width = this.element.getStyle('width');
30
 
31
                if (width)
32
                    this.setData('width', width);
33
                else
34
                    this.setData('width', '200px'); //default
35
 
36
                var id = this.element.getAttribute('title');
37
                if (id)
38
                    this.setData('docid', id);
39
            },
40
 
41
            data: function() {
42
                var width = this.data.width;
43
 
44
                if (width == '')
45
                    this.element.removeStyle('width');
46
                else
47
                    this.element.setStyle('width', width);
48
 
49
                var SPQuery = {
50
                    element: '',
51
                    url: '',
52
                    init: function(element) {
53
                        SPQuery.element = element;
54
                        SPQuery.url = _spPageContextInfo.webAbsoluteUrl + "/_api/lists/getByTitle('" + encodeURIComponent(SPQuery.element.data.sourcelistname) + "')/items(" + SPQuery.element.data.docid + ")/File?$select=Title,Name,Exists,Length,Level,ServerRelativeUrl";
55
                    },
56
                    load: function() {
57
                        $.ajax(
58
                            {
59
                                url: SPQuery.url,
60
                                method: "GET",
61
                                headers: { "accept": "application/json;odata=verbose" },
62
                                success: SPQuery.onSuccess,
63
                                error: SPQuery.onError
64
                            }
65
                        );
66
                    },
67
                    onSuccess: function(data) {
68
                        SPQuery.element.element.setAttribute('title', SPQuery.element.data.docid);
69
                        var link = SPQuery.element.element.findOne('a');
70
                        if (link) {
71
                            link.setText(data.d.Title || data.d.Name); //title is not required so set to name if missing
72
                            link.setAttribute('href', data.d.ServerRelativeUrl);
73
                            link.setAttribute('class','doclinkwidget');
74
                        }
75
                    },
76
                   onError: function(err) {
77
                        api.openMsgDialog('', JSON.stringify(err));
78
                    }
79
                };
80
 
81
                //get data if docid is set
82
                if (this.data.docid) {
83
                    SPQuery.init(this);
84
                    SPQuery.load();
85
                }
86
            }
87
        });
88
    }
89
});
90
 
91

TFS Work Item Templates Filtering User Selector

0

If you are managing multiple Team Foundation Server (TFS) project teams then you probably would prefer to filter the user/people selector based on project or roles. The standard list includes all users, which contains more users than necessary.

Visual Studio provides an option to modify the work item templates. You can access this option at Tools-Process Editor-Work Item Types-Open WIT From Server. Select the Team Foundation Server and Project Collection before clicking Connect. The available work item templates will appear. Next, select the appropriate template to apply the filter. In this example, the User Story was selected.

 On the Fields tab, select the Assigned To field.

After selecting the field, the Fields Definition will be presented.

Click the Rules tab, which provides the filtering options for the Assigned To field.

Select ALLOWEDVALUES and click Edit. You can assign a project group with the [project] placeholder and the name. In my case, assigning the TFS project team.

Once you save the work item template, visiting a User Story and selecting the Assigned To field will display the filtered user list.

You can also export the work item template and add to TFS source control. In this case, modifying the following XML will also update the filter.

 XML |  copy code |? 
1
<FieldDefinition name="Assigned To" refname="System.AssignedTo" type="String" syncnamechanges="true" reportable="dimension">
2
  <ALLOWEXISTINGVALUE />
3
  <ALLOWEDVALUES expanditems="true" filteritems="excludegroups">
4
    <LISTITEM value="[project]\My Team" />
5
  </ALLOWEDVALUES>
6
  <HELPTEXT>The person currently working on this story</HELPTEXT>
7
</FieldDefinition>
8

Hopefully, this tip can save you time applying a filter to a user/people selector.

Improve UX with SharePoint Client-side Rendering

0

The standard SharePoint rendered views generally score low for originality with limited points for user-experience. The following is an example of a standard list view, which is a table structure with text-based field values.

What if we add a little style to represent the Approval Status column? We can override the standard field control rendering and inject our own client-side logic. This can be accomplished using HTML and JavaScript technologies with the SharePoint 2013 client-side rendering feature. So…let’s look at a simple style enhancement to the above list view and Approval Status column.

This is a very simple change, which utilizes color (red, yellow and green) and a minor value change (Approved is now Live) to improve the view. At a glance, the user can associate the color with the status using the common traffic stop light palette. So…let’s look at the changes required to implement the rendering logic.

 First, we can create the rendering logic.

 Javascript |  copy code |? 
01
(function () {
02
    var fieldContext = {}; 
03
    fieldContext.Templates = {}; 
04
    fieldContext.Templates.Fields = { 
05
        "_ModerationStatus": { "View": approvalStatusFieldTemplate, "ViewForm": approvalStatusFieldTemplate&nbsp;}
06
    };
07
 
08
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(fieldContext); 
09
 
10
})(); 
11

This function contains the field context and registers the templates. The key is fields and _ModerationStatus, which is the internal field name for Approval Status. You can include templates for View, DisplayForm, EditForm and NewForm. In this example, the View and ViewForm templates are defined to call the approvalStatusFieldTemplate function.

 Javascript |  copy code |? 
01
function approvalStatusFieldTemplate(ctx) { 
02
 var approvalStatus = ctx.CurrentItem[ctx.CurrentFieldSchema.Name];
03
 
04
    switch (approvalStatus) { 
05
        case "Draft": 
06
            return "<span style='color:red'><strong>" + approvalStatus + "</strong></span>"; 
07
            break; 
08
        case "Scheduled": 
09
            return "<span style='color:orange'><strong>" + approvalStatus + "</strong></span>"; 
10
            break; 
11
        case "Approved": 
12
            return "<span style='color:green'><strong>Live</strong></span>";
13
 }
14
 
15
 return approvalStatus; 
16
}
17

This function will return the injected mark-up for the target field. The first statement assigns the current field value to approvalStatus. Next, a switch statement returns a span element with the appropriate style color. The result is a colorized value based on the approval status.

The next example replaces the text value with an image, which also represents the approval status. The result is mark-up to render an image with the approval status value as a tooltip.

The following is the modified approvalStatusFieldTemplate function. In this example, we are borrowing a few SharePoint provided images.

 Javascript |  copy code |? 
01
function approvalStatusFieldTemplate(ctx) { 
02
 var approvalStatus = ctx.CurrentItem[ctx.CurrentFieldSchema.Name];
03
 var icon;
04
 
05
    switch (approvalStatus) { 
06
        case "Draft": 
07
            icon = "hlthwrn.png"; 
08
            break; 
09
        case "Scheduled": 
10
            icon =  "calendar.gif"; 
11
            break; 
12
        case "Approved":
13
            icon = "hlthsucc.png";
14
            break;
15
        default:
16
            icon = "hltherr.png";
17
    }
18
 
19
    return "<img src='/_layouts/15/images/" + icon + "' title='" + approvalStatus + "'/>";
20
}
21

The client-side rendering is also referred to as JSLink, since this is the new property that instructs the field rendering process to replace the result using the JavaScript contained in the JSLink resource. The following PowerShell script will assign the JSLink value to the JavaScript file containing our rendering logic for the Approval Status field.

 DOS |  copy code |? 
1
$web = Get-SPWeb http://myserver
2
$field = $web.Fields["Approval Status"]
3
$field.JSLink = "~layouts/15/ApprovalStatusViewDecorate.js"
4
$field.Update($true)
5

If you would like to explore client-side rendering for your sites then I would recommend additional tutorials. This should include handling multiple instances of the field on a page, which would cause issues without additional configuration. The following are a few helpful resources:

Good Examples

Good Overview

This new powerful feature will provide another tool to improve the user experience with endless possibilities. I hope this information provides a quick start for SharePoint 2013 Client-side rendering.

TFS Build Process Packaging

0

In previous posts, we discussed various techniques to tailor your Team Foundation Server (TFS) Build Process. The TFS Versioning article introduced a library, which enables you to set the assembly versions during the build. The TFS Merge post demonstrates the steps necessary to provide a merge process within the TFS Build Process Template. In this article, we explore the Build Process Parameters of the TFS Build Process Definition.

After you successfully compile and test, the final step is packaging and/or publishing the product for deployment. If your build process definition includes multiple source solutions then collecting the assets required to deploy is tricky. In most scenarios, you will move the packages and artifacts to a share or drop location. The versioning process provides a unique identifier, so you can properly label or tag the deployment release. If you create a share folder name with the generated version then you can catalog and track the deployment releases.

The MSBuild command-line provides a series of parameters instructing the build to perform one or more defined operations. The parameters will override any solution or project specific values, which is helpful to ensure the results are consistent for packaging and publishing.

The MSBuild Parameters is an Advanced section option, which provides the override instructions to the build process. The web projects are packaged to include the required assets to deploy, which appear as a “_PublishedWebsites” folder. A SharePoint Solution Package (.wsp) and SQL Server DAC Package (.dacpac) are other examples of deployment packages.

The following MSBuild parameters will ensure the above packages are created during the build process.

TFS Build Definition MSBuild Arguments

The OutputPath parameter is optional, but will move results to the bin folder. This includes the web, SharePoint and SQL Server deployment packages discussed previously. The WarningLevel parameter is set to zero, so all warning messages are suppressed.

I hope this information is helpful, so you can customize your build process and capture the deployment packages without writing significant custom logic.

ASP.NET MVC and Kendo UI Grid

0

A grid provides a common presentation to represent a collection of data, which is a table containing rows and columns. Although creating this experience is not difficult, the features to deliver a quality user experience will require a significant investment. The alternative is a widget library, which provides advanced features requiring minimal effort.

In this post, I will demonstrate a few features of the Kendo UI Grid, which is one of many widgets included in the Kendo UI products. After several years of experience with Telerik products, the next generation Kendo UI is HTML5 and CSS3. I also elected to include the Kendo UI Complete, which packages convenient server extensions for the JavaScript framework. You can learn more about the products and better understand the licensing, which is a huge obstacle for many budget conscious organization. And yes…many alternatives exist including the JQuery.UI library.

Kendo UI provides a handy Visual Studio plugin, which can convert and prepare projects for Kendo UI. I converted an existing ASP.NET MVC4 project, which saves several manual steps. I was also able to select one of the Kendo themes, which will be applied to all Kendo widgets.

You can read several of my previous posts, which discuss design patterns to build enterprise applications. In this article, my goal is to provide a quick start for the Kendo UI Grid implementation. So…I will not create a fully functioning repository, but instead a static memory based to provide the basics for the demonstration.

I will modify the standard ASP.NET MVC4 starter project and add a new menu option called Contacts. This will require a controller, view model and service - all the basics. We’ll start with a simple read-only grid, which will display the current contacts with paging, filtering and sorting features. The following is the read-only Contacts grid.

Contacts Read-Only Grid

The grid is a table containing the Name, Mode of Contact, Phone and Email columns. The sort, filter and paging features are enabled, so the user can interact with the grid and data using several helpful options. So…let’s look at the view containing the grid configuration.

 C# |  copy code |? 
01
@using MvcKendoUIGrid.Models
02
 
03
@(Html.Kendo().Grid<Contact>()
04
      .Name("contactGrid")
05
      .Columns(columns =>
06
 
07
          {
08
               columns.Bound(c => c.Name).Title("Name").Width(150);
09
              columns.Bound(c => c.PreferredContactMode).Title("Mode of Contact").Width(150);
10
              columns.Bound(c => c.Phone).Title("Phone").Width(120);
11
              columns.Bound(c => c.Email).Title("Email").Width(120);
12
          })
13
      .Pageable()
14
      .Filterable()
15
      .Sortable()
16
      .Scrollable()
17
      .AutoBind(true)
18
      .HtmlAttributes(new {style = "height:250px;"})
19
      .DataSource(dataSource => dataSource
20
                                    .Ajax()
21
                                    .ServerOperation(false)
22
                                    .Model(model =>
23
                                        {
24
                                            model.Id(c => c.Id);
25
                                            model.Field(c => c.PreferredContactMode).Editable(true).DefaultValue(new ContactMode {Id = 0, Name = string.Empty});
26
                                        })
27
                                    .Read(read => read.Action("Read", "Contact"))
28
)      )

The Pageable, Sortable, Filterable and Scrollable provide the advanced grid features with just one line of code. The DataSource defines the data and actions to read. The model is the Contact, so the fields and id are defined. The Read action is an ajax call to the Contact controller and Read method, which will return a collection of Contact objects. The Columns define the layout of columns, headers and binding to the model.

The Contact model contains the Id, NamePhone and Email properties. It also includes the ContactMode, which is a complex object including an Id and Name. We’ll see more on the complex object in the next section.

 C# |  copy code |? 
01
using System.Collections.Generic;
02
using System.Web.Mvc;
03
using Kendo.Mvc.Extensions;
04
using Kendo.Mvc.UI;
05
using MvcKendoUIGrid.Models;
06
using MvcKendoUIGrid.Services;
07
 
08
namespace MvcKendoUIGrid.Controllers
09
{
10
    /// <summary>
11
    /// Contact controller providing support for views
12
    /// </summary>
13
    public class ContactController : Controller
14
    {
15
        private readonly IContactService _service;
16
 
17
        /// <summary>
18
        /// Default Constructor
19
        /// </summary>
20
        public ContactController()
21
        {
22
            _service = new ContactService();
23
        }
24
 
25
        /// <summary>
26
        /// Default action to render Contact grid
27
        /// </summary>
28
        /// <returns></returns>
29
        public ActionResult Index()
30
        {
31
            TempData["ContactMode"] = new List<ContactMode>
32
                {
33
                    new ContactMode {Id = 1, Name = "Phone"},
34
                    new ContactMode {Id = 2, Name = "Email"}
35
                };
36
 
37
            return View();
38
        }
39
 
40
        /// <summary>
41
        ///     Read action to populate grid
42
        /// </summary>
43
        /// <param name="dsRequest">Kendo Datasource Request</param>
44
        /// <returns>ActionResult</returns>
45
        [HttpPost]
46
        public ActionResult Read([DataSourceRequest] DataSourceRequest dsRequest)
47
        {
48
            try
49
            {
50
                return Json(_service.GetAllContacts().ToDataSourceResult(dsRequest));
51
            }
52
            catch
53
            {
54
                ModelState.AddModelError("Contacts", "Unable to load contacts.");
55
                return Json(ModelState.ToDataSourceResult());
56
            }
57
        }
58

The Index action is called on the initially, which will save the possible ContactMode selections as TempData. The Read action invokes the service GetAllContacts method to return a collection of Contacts.

The next step is providing the ability to edit the contacts. The following is the revised view, which includes the changes to enable editing.

 C# |  copy code |? 
01
@using MvcKendoUIGrid.Models
02
 
03
@(Html.Kendo().Grid<Contact>()
04
      .Name("contactGrid")
05
      .Columns(columns =>
06
 
07
          {
08
              columns.Command(command =>
09
                  {
10
                      command.Edit();
11
                   }).Width(180);
12
              columns.Bound(c => c.Name).Title("Name").Width(150);
13
              columns.Bound(c => c.PreferredContactMode).Title("Mode of Contact").Width(150);
14
              columns.Bound(c => c.Phone).Title("Phone").Width(120);
15
              columns.Bound(c => c.Email).Title("Email").Width(120);
16
          })
17
      .Pageable()
18
      .Filterable()
19
      .Sortable()
20
      .Scrollable()
21
      .AutoBind(true)
22
      .HtmlAttributes(new {style = "height:250px;"})
23
      .Editable(ed => ed.Mode(GridEditMode.InLine))
24
      .DataSource(dataSource => dataSource
25
                                    .Ajax()
26
                                    .ServerOperation(false)
27
                                    .Model(model =>
28
                                        {
29
                                            model.Id(c => c.Id);
30
                                            model.Field(c => c.PreferredContactMode).Editable(true).DefaultValue(new ContactMode {Id = 0, Name = string.Empty});
31
                                        })
32
                                    .Read(read => read.Action("Read", "Contact"))
33
                                    .Update(update => update.Action("Edit", "Contact"))
34
      )
35
      )

The DataSource now includes the Update, which calls the Contact controller Edit method. The Editable supports inline and popup edit modes, so we will select inline to allow editing within the grid. We also add a column command, which injects the edit button in the first column. We also add the Edit method to the controller, which is responsible for calling the service to update the contract.

 C# |  copy code |? 
01
        /// <summary>
02
        /// Edit action to update existing contact
03
        /// </summary>
04
        /// <param name="dsRequest">Kendo Datasource Request</param>
05
        /// <param name="viewmodel">Contact model containing values to update</param>
06
        /// <returns>ActionResult</returns>
07
        [HttpPost]
08
        public ActionResult Edit([DataSourceRequest] DataSourceRequest dsRequest, Contact viewmodel)
09
        {
10
            try
11
            {
12
                if (ModelState.IsValid && viewmodel != null)
13
                    _service.Update(viewmodel);
14
                else //model error
15
                    ModelState.AddModelError("Contacts", "Unable to edit contact - not a valid contact.");
16
            }
17
            catch
18
            {
19
                ModelState.AddModelError("Contacts", "Unable to edit contact.");
20
            }
21
 
22
            return Json(ModelState.ToDataSourceResult());
23
        }
24

If we test the changes then we see the addition of the edit button. Clicking the edit button will allow the editing of the selected row.

Contact Grid Edit

You will notice the Mode of Contact column displays the Id and Name textbox, which is not the best experience for selecting the contact mode.

Contact Grid Edit Complex Object

Since ContactMode is a complex object, the editor includes the Id and Name. We would like to provide a dropdown list of available contact modes, so the user must select a valid option. We previously saved a collection of ContactMode objects representing the valid options in the TempData, so this will be the dropdown list source. The solution is creating a new editor for the ContactMode type, so when the correct editor is presented. The following is the ContactMode editor, which should be added to the Shared Editor Templates folder.

 C# |  copy code |? 
1
@model MvcKendoUIGrid.Models.ContactMode
2
@(Html.Kendo().DropDownList()
3
      .Name("PreferredContactMode")
4
      .BindTo((System.Collections.IEnumerable)TempData["ContactMode"])
5
      .OptionLabel("Select a Mode")
6
      .DataTextField("Name")
7
      .DataValueField("Id")
8
      .HtmlAttributes(new { required = true })
9
      )

We are using the Kendo UI DropDownList widget, which is a dropdown list. The BindTo is the list data source, which is referencing the TempData we populated in our initial Index method. The DataTextField and DataValueField are the value and text property names. We also set the required attribute, which ensures the contact mode is required. It is important to note the Name must be the name of the grid model property, so the two-way binding is correct. Let’s see the results of the changes to the edit mode.

Contacts Grid Edit Dropdown

The final features are Add and Delete. The following is the view containing the updates for Add and Delete. You will also notice the ClientTemplate applied to the ContactMode, which forces the view to include the Name property in the display.

 C# |  copy code |? 
01
@using MvcKendoUIGrid.Models
02
 
03
@(Html.Kendo().Grid<Contact>()
04
      .Name("contactGrid")
05
      .Columns(columns =>
06
 
07
          {
08
              columns.Command(command =>
09
                  {
10
                      command.Edit();
11
                      command.Destroy();
12
                  }).Width(180);
13
              columns.Bound(c => c.Name).Title("Name").Width(150);
14
              columns.Bound(c => c.PreferredContactMode).Title("Mode of Contact").ClientTemplate("#=PreferredContactMode.Name#").Width(150);
15
              columns.Bound(c => c.Phone).Title("Phone").Width(120);
16
              columns.Bound(c => c.Email).Title("Email").Width(120);
17
          })
18
      .ToolBar(toolbar => toolbar.Create().Text("Add new contact"))
19
      .Pageable()
20
      .Filterable()
21
      .Sortable()
22
      .Scrollable()
23
      .AutoBind(true)
24
      .HtmlAttributes(new {style = "height:250px;"})
25
      .Editable(ed => ed.Mode(GridEditMode.InLine))
26
      .DataSource(dataSource => dataSource
27
                                    .Ajax()
28
                                    .ServerOperation(false)
29
                                    .Model(model =>
30
                                        {
31
                                            model.Id(c => c.Id);
32
                                            model.Field(c => c.PreferredContactMode).Editable(true).DefaultValue(new ContactMode {Id = 0, Name = string.Empty});
33
                                        })
34
                                    .Read(read => read.Action("Read", "Contact"))
35
                                    .Update(update => update.Action("Edit", "Contact"))
36
                                    .Destroy(delete => delete.Action("Delete", "Contact"))
37
                                    .Create(create => create.Action("Create", "Contact"))
38
      )
39
      )

We add the Destroy to the command, which provides the Delete button. The DataSource Destroy defines the Contact controller delete action, which will be responsible for deleting the contact. We add the create command to the Toolbar, which is positioned above the grid. The DataSource Create is the action invoked when the “Add new contact” button is clicked.

The Create and Delete controller methods appear below.

 C# |  copy code |? 
01
        /// <summary>
02
        /// Create action to add new contact
03
        /// </summary>
04
        /// <param name="dsRequest">Kendo Datasource Request</param>
05
        /// <param name="viewmodel">Contact model containing values to add</param>
06
        /// <returns>ActionResult</returns>
07
        [HttpPost]
08
        public ActionResult Create([DataSourceRequest] DataSourceRequest dsRequest, Contact viewmodel)
09
        {
10
            try
11
            {
12
                if (ModelState.IsValid && viewmodel != null)
13
                {
14
                    _service.Add(viewmodel);
15
                    return Json(new[] {viewmodel}.ToDataSourceResult(dsRequest, ModelState));
16
                }
17
                ModelState.AddModelError("Contacts", "Unable to add contact - not a valid contact.");
18
            }
19
            catch
20
            {
21
                ModelState.AddModelError("Contacts", "Unable to add contact.");
22
            }
23
 
24
            return Json(ModelState.ToDataSourceResult());
25
        }
26
 
27
        /// <summary>
28
        /// Delete action to remove existing contact
29
        /// </summary>
30
        /// <param name="dsRequest">Kendo Datasource Request</param>
31
        /// <param name="viewmodel">Contact model containing values to delete</param>
32
        /// <returns>ActionResult</returns>
33
        [HttpPost]
34
        public ActionResult Delete([DataSourceRequest] DataSourceRequest dsRequest, Contact viewmodel)
35
        {
36
            try
37
            {
38
                if (viewmodel != null)
39
                    _service.Delete(viewmodel);
40
                else
41
                    ModelState.AddModelError("Contacts", "Unable to delete contact - not a valid contact.");
42
            }
43
            catch
44
            {
45
                ModelState.AddModelError("Contacts", "Unable to delete contact.");
46
            }
47
 
48
            return Json(ModelState.ToDataSourceResult());
49
        }
50

The final version with the Edit, Add and Delete actions enabled.

Contacts Grid Complete

Contacts Grid AddContacts Grid Delete

I hope this provides a quick overview of the Kendo UI Grid widget and Kendo UI MVC server extensions, which together provide powerful features with minimal effort.

Go to Top