Search code examples
javascriptkendo-uikendo-gridkendo-asp.net-mvc

Make cell readonly in Kendo Grid if condition is met


Let's say I have a data like this:

[
    {ID: 1, SomeForeignKeyID: 4, IsFkEnabled: true},
    {ID: 2, SomeForeignKeyID: 9, IsFkEnabled: false}
]

Kendo Grid is using this data:

columns.Bound(m => m.ID);
columns.ForeignKey(p => p.SomeForeignKeyID, ViewBag.ForeignKeys as IEnumerable<object>, "Value", "Name");

Here's the problem: how to make ForeignKey column editable, but only in rows, where IsFkEnabled == true? Edit mode is InCell.


Solution

  • Notes:

    • this solution works for in-cell editing only (inline or popup editing require a different approach)
    • the first approach can lead to unwanted visual effects (grid jumping) under certain circumstances; if you experience that, I recommend approach #2
    • approach #2 may not work if you want to use the MVC wrappers (although it may be possible to extend Kendo.Mvc.UI.Fluent.GridEventBuilder); in that case, you'll need to bind the edit handler in JS

    Approach #1

    Use the grid's edit event and then do something like this:

    $("#grid").kendoGrid({
        dataSource: dataSource,
        height: "300px",
        columns: columns,
        editable: true,
        edit: function (e) {
            var fieldName = e.container.find("input").attr("name");
            // alternative (if you don't have the name attribute in your editable):
            // var columnIndex = this.cellIndex(e.container);
            // var fieldName = this.thead.find("th").eq(columnIndex).data("field");
    
            if (!isEditable(fieldName, e.model)) {
                this.closeCell(); // prevent editing
            }
        }
    });
    
    /**
     * @returns {boolean} True if the column with the given field name is editable 
     */
    function isEditable(fieldName, model)  {
        if (fieldName === "SomeForeignKeyID") {
            // condition for the field "SomeForeignKeyID" 
            // (default to true if defining property doesn't exist)
            return model.hasOwnProperty("IsFkEnabled") && model.IsFkEnabled;
        }
        // additional checks, e.g. to only allow editing unsaved rows:
        // if (!model.isNew()) { return false; }       
    
        return true; // default to editable
    }
    

    Demo here (updated for Q1 2014)

    To use this via the MVC fluent syntax, simply give the anonymous edit function above a name (e.g. onEdit):

    function onEdit(e) {
        var fieldName = e.container.find("input").attr("name");
        // alternative (if you don't have the name attribute in your editable):
        // var columnIndex = this.cellIndex(e.container);
        // var fieldName = this.thead.find("th").eq(columnIndex).data("field");
    
        if (!isEditable(fieldName, e.model)) {
            this.closeCell(); // prevent editing
        }
    }
    

    and reference it like this:

    @(Html.Kendo().Grid()
        .Name("Grid")
        .Events(events => events.Edit("onEdit"))
    )
    

    The disadvantage to this is that the editor gets created first before the edit event is triggered, which can sometimes have undesirable visual effects.

    Approach #2

    Extend the grid by overriding its editCell method with a variation that triggers a beforeEdit event; for that to work with grid options, you'll also need to override the init method:

    var oEditCell = kendo.ui.Grid.fn.editCell;
    var oInit = kendo.ui.Grid.fn.init;
    kendo.ui.Grid = kendo.ui.Grid.extend({
        init: function () {
            oInit.apply(this, arguments);
            if (typeof this.options.beforeEdit === "function") {
                this.bind("beforeEdit", this.options.beforeEdit.bind(this));
            }
        },
        editCell: function (cell) {
            var that = this,
                cell = $(cell),
                column = that.columns[that.cellIndex(cell)],
                model = that._modelForContainer(cell),
                event = {
                    container: cell,
                    model: model,
                    field: column.field
                };
    
            if (model && this.trigger("beforeEdit", event)) {
                // don't edit if prevented in beforeEdit
                if (event.isDefaultPrevented()) return;
            }
    
            oEditCell.call(this, cell);
        }
    });
    kendo.ui.plugin(kendo.ui.Grid);
    

    then use it similar to #1:

    $("#grid").kendoGrid({
        dataSource: dataSource,
        height: "300px",
        columns: columns,
        editable: true,
        beforeEdit: function(e) {
            var columnIndex = this.cellIndex(e.container);
            var fieldName = this.thead.find("th").eq(columnIndex).data("field");
    
            if (!isEditable(fieldName, e.model)) {
                e.preventDefault();
            }
        }
    });
    

    The difference of this approach is that the editor won't get created (and focused) first. The beforeEdit method is using the same isEditable method from #1. See a demo for this approach here.

    If you want to use this approach with MVC wrappers but don't want / can't extend GridEventBuilder, you can still bind your event handler in JavaScript (place below the grid MVC initializer):

    $(function() {
        var grid = $("#grid").data("kendoGrid");
        grid.bind("beforeEdit", onEdit.bind(grid));
    });