Drag n Drop Queue Ordering with a Kendo UI Grid

There are a bunch of examples for drag and drop Kendo grids floating around the web, but this is the only reordering solution that has worked for me with an in-cell editable grid. It features an automatically updating priority queue column with the row index number.

First let’s look at the main reordering functionality, and then complete the user experience with some extra bits for updating the queue.

// Enable drag n drop reordering on this grid - can be done on document.ready
$("#my_grid").data("kendoGrid").table.kendoSortable({
    filter: ">tbody >tr",
    hint: $.noop,
    cursor: "move",
    ignore: "TD, input",
    placeholder: function (element) {
        return element.clone().addClass("k-state-hover").css("opacity", 0.65);
    },
    container: "#my_grid tbody",
    change: grid_reorder
});


// Reordering event triggered by user releasing drag n drop element
function grid_reorder(e) {

    // Get data from drag n drop html element
    var grid = $("#my_grid").data("kendoGrid");
    var dataItem = grid.dataSource.getByUid(e.item.data("uid"));

    // Copy data into new grid row location
    grid.dataSource.remove(dataItem);
    grid.dataSource.insert(e.newIndex, dataItem);

    // Get rid of delete request caused by dataSource.remove
    for (var i = 0; i < grid.dataSource._destroyed.length; i++) {
        if (grid.dataSource._destroyed[i].uid == dataItem.uid) {
            grid.dataSource._destroyed.splice(i, 1);
        }
    }

    // Update ordering of all grid rows
    for (var i = 0; i < grid.dataSource.data().length; i++) {
        grid.dataSource.data()[i].set("PriorityOrder", i + 1);
    }
}

Setting up drag n drop on the grid is accomplished above using kendoSortable. Beyond that, Kendo leaves reordering the associated grid data up to the user from what I could gather. My solution removes and inserts the row in its new location, and then removes the subsequent delete request from the grid’s private _destroyed array to avoid false deletes. I then update the PriorityOrder column for all rows, as this is the queue that is shown to the user.

Next we need to take care of updating the priority queue when the user inserts a new row. The grid’s Edit event is triggered by Kendo when the add button is clicked, giving focus to the first cell and opening it for editing.

function grid_editcell(e) {
    // Do not allow user to set priority, but keep it editable so it can be updated by drag n drop
    if (e.container.find("input[name=PriorityOrder]").length > 0) {
        e.sender.closeCell(e.container);

        // If user is adding a new row update entire queue order
        if (e.model.isNew()) {
            for (var i = 1; i < e.sender.dataSource.data().length; i++) {
                e.sender.dataSource.data()[i].set("PriorityOrder", i + 1);
            }
        }
    }
}

Don’t forget about the user removing a row…

function grid_removerow(e) {
    // Remove row then update entire queue order
    this.removeRow($(e.target).closest("tr"));
    var grid = $("#my_grid").data("kendoGrid");

    for (var i = 0; i < grid.dataSource.data().length; i++) {
        grid.dataSource.data()[i].set("PriorityOrder", i + 1);
    }
}

Now let’s take a look at a (simplified) grid definition in Razor code, as I’m using ASP MVC wrappers to generate the grid. If you’re not using wrappers you should be able to make out the important bits.

@(Html.Kendo().Grid<MyProj.ExampleClass>()
    .Name("my_grid")
    .Columns(columns =>
    {
        columns.Bound(p => p.PriorityOrder).Width("75px")
                .ClientTemplate("<span class='fa fa-arrows drag-handle'></span> &nbsp; #=PriorityOrder#");
        columns.Bound(p => p.Amount).Format("{0:C2}");
        columns.Bound(p => p.UpdateDate).Format("{0:MM/dd/yyyy}");
        columns.Command(command => command.Custom("Delete").Click("grid_removerow"))
                .Title("Action").Width("60px");
    })
    .Editable(editable => editable.Enabled(true).Mode(GridEditMode.InCell))
    .Events(events => events
        .Edit("grid_editcell")
    )
    .DataSource(dataSource => dataSource.Ajax().Batch(true)
        .Model(model =>
        {
            model.Id(key => key.ExampleKey);
            model.Field(f => f.PriorityOrder).DefaultValue(1);
            model.Field(f => f.UpdateDate).Editable(false);
        })
        .Create(create => create.Action("Grid_Create", "Home"))
        .Read(read => read.Action("Grid_Read", "Home"))
        .Update(update => update.Action("Grid_Update", "Home"))
        .Destroy(destroy => destroy.Action("Grid_Delete", "Home"))
    )
)

I have Font Awesome displaying the classic Windows “move” arrow icon as a drag handle in the PriorityOrder queue column as a client template.

When a grid row is inserted, the grid_editcell event is triggered, which causes the queue to refresh. I found that it was necessary to keep the PriorityOrder column editable, so the values could be visually refreshed. To prevent the user from manually changing the queue, any cell in the PriorityOrder column is closed immediately upon click.

Lastly each row has a custom delete button, which triggers the queue refresh when the row is removed.

Kendo grid drag 'n' drop reordering
Kendo Grid with Queue
Kendo Drag 'n' Drop Reordering
Kendo Drag ‘n’ Drop Reordering

Get ID when a custom command is clicked in Kendo grids

Quick code tip for getting the row data from a custom command button in Kendo UI grids. This is useful for intercepting a delete event or any custom actions you have set up on a Kendo grid.

    function grid_deleterow(e) {
        var row = $(e.currentTarget).closest("tr");
        var data = $("#grid").data("kendoGrid").dataItem(row);
        var id = data.EntityId;
    }

In the JavaScript function above, id will contain the value of the EntityId column for the row of the custom button clicked. This is great because you don’t have to make your grid selectable for this to work.

Here is how the event would be triggered when using the Kendo MVC wrappers:

        @(Html.Kendo().Grid<EntityModel>()
             .Name("test_grid")
             .Columns(columns =>
             {
                 columns.Bound(p => p.Field1);
                 columns.Command(command => command.Custom("Delete").Click("grid_deleterow"));
             })
           )

Displaying inactive foreign keys in Kendo UI grids with In-Cell editing

Foreign key columns in Kendo UI are pretty great. With in-grid editing, you can use them to display a choice of categories, people, and so on. At some point you may need to mark a category as inactive or disabled, so that the user can no longer select that foreign key as a valid choice. So how do you display old deactivated foreign keys in your Kendo grid for record keeping purposes?

By selecting the foreign key text value into our grid as a hidden column, we can use that to resolve any deactivated foreign keys. Here is the JavaScript function that will display the text equivalent of the foreign key, and use the hidden column as a backup source for inactive foreign keys.

    var collection;
    function getforeignkeytext(rowData) {

        if (!collection) {
            var grid = $("#grid").data("kendoGrid");
            var foreignKeys = grid.options.columns[2].values; // Set the FK column index
            collection = {};
            for (var i in foreignKeys) {
                collection[foreignKeys[i].value] = foreignKeys[i].text;
            }
        }

        var result = collection[rowData.CategoryId];
        if (result == undefined) {
            result = rowData.CategoryName;
        }
        return result;
    }

For our grid to use this, use a client template for the foreign key column set to use the function above. I am using the MVC server wrappers in this example.

columns.ForeignKey(p => p.CategoryId, Model.CategoryDropdown, "Value", "Text")
       .ClientTemplate("#=getforeignkeytext(data)#");

Why go through all that trouble when something simple like .ClientTemplate("#=CategoryName#") would display the hidden column value? That solution won’t correctly display an edited value, before the user saves the grid when using GridEditMode.InCell.

So what about when editing the cell? It would be super nice if the dropdown also magically contained the inactive foreign key as a choice only for that cell. That would really complete the user experience. With a second JavaScript function we can take care of that.

    function editforeignkey(e) {
        var valueToAddToDropdown = e.model.CategoryId;
        var textToAddToDropdown = e.model.CategoryName;

        var dropdown = e.container.find('[data-role=combobox]').data();

        if (typeof dropdown != "undefined") {
            var valueExists = false;
            for (var i = 0; i < dropdown.kendoComboBox.dataSource._data.length; i++) {
                if (dropdown.kendoComboBox.dataSource._data[i].Value == valueToAddToDropdown) {
                    valueExists = true;
                }
            }
            if (!valueExists) {
                dropdown.kendoComboBox.dataSource.add({ Text: textToAddToDropdown, Value: valueToAddToDropdown });
            }
        }
    }

The function above is triggered on the Kendo grid’s edit event. It attempts to find the foreign key value in the cell’s dropdown, and adds it if necessary. The value will be automatically selected.

There does appear to be one limitation. If the user changes an inactive foreign key value to an active one, and the cell loses focus, then the inactive foreign key can no longer be reselected, unless all grid changes are cancelled.

With this technique you can provide users with a cohesive experience for Kendo grids containing historical foreign key data.

Sorting Kendo foreign key columns by text in MVC

The Kendo UI MVC wrappers make it very easy to have a dropdown in a grid column as a foreign key. You may notice that the column sorts by the id though, and not the text value that would make sense to the user.

The Kendo documentation has a great example of custom binding in MVC for custom sorting, filtering, and paging.

I wanted something lightweight to use that didn’t require rewriting all of a grid’s sorting functionality. Here is how I translate the sort type in a grid’s Read method in the controller.

        public JsonResult Coco_Read([DataSourceRequest]DataSourceRequest request)
        {   
            // Sort foreign key columns by text not id
            foreach (SortDescriptor sortDescriptor in request.Sorts)
            {
                switch (sortDescriptor.Member)
                {
                    case "CocoTypeId":
                        request.Sorts[request.Sorts.IndexOf(sortDescriptor)].Member = "CocoTypeName";
                        break;
                }
            }

            return Json(Model.GetCoco().ToDataSourceResult(request), JsonRequestBehavior.AllowGet);
        }

My solution replaces any sort for the foreign key column CocoTypeId with the hidden text equivalent column CocoTypeName. This is the easiest technique I could come up with for sorting from what Telerik recommends.

Single click checkbox editing for Kendo grids

This is possibly the most anti-climatic code ever. You click a checkbox in a column, and click the save button. It just works. Except that’s not a default behavior supported in Kendo UI. Depending on how you do it, you either have to click into the cell, then the click the checkbox, or maybe your checkbox value won’t save at all.

The goal here is to have a grid column displayed as checkboxes for boolean values. The problem is the checkbox displayed isn’t connected to the data structure behind the Kendo Grid.

Telerik has an example of checkbox column editing, but it’s hard coded with a grid id, column name, and checkbox class. We need something that can be applied to all grids in an application!

Behold…


// On jQuery document ready
jQuery(function ($) {

    // Synchronize the kendo grid data to user clicked checkbox value
    // This is necessary for single-click grid editing on checkboxes for the checkbox state to be saved properly
    // This delegate event handler applies to all kendo grids
    $("body").on("click", "td", function(e) {

        // Get the parent grid from the cell clicked
        var grid = $(this).closest(".k-grid").data().kendoGrid;

        // Get the selected row and column from the cell clicked
        var row = $(this).closest("tr");
        var colId = $("td", row).index(this);

        // Get the column binding name from the grid properties
        var colBinding = grid.options.columns[colId].field;

        // Look for an enabled checkbox input inside the cell clicked
        var chk = $(this).children("input[type=checkbox]:enabled");
        if (chk.length) {

            // Get the Kendo data structure which keeps track of grid edits for the chosen row
            var dataItem = grid.dataItem(row);

            // FINALLY: Set the boolean data value to the checkbox value in the cell clicked
            dataItem.set(colBinding, chk[0].checked);

        }
    });
});

Now any grid in your application will allow the user to click into the checkbox directly, and save the grid.

Checkboxes in a Kendo grid

If you’re still stuck on how to get a checkbox showing in a Kendo grid column in the first place, here’s how to do it with ASP.NET MVC wrappers:

columns.Bound(p => p.IsActive).ClientTemplate("<input type='checkbox' #= IsActive ? checked='checked' : '' # />");