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.

Answers:

Answer

None of these approaches worked for me. A very simple implentation looks like this

edit: function (e) {
        e.container.find("input[name='Name']").each(function () { $(this).attr("disabled", "disabled") });       
    }

Where edit is part of the kendo grid declaration and Name is the actual name of the field.

Answer

Please try with the below code snippet.

VIEW

<script type="text/javascript">  

function errorHandler(e) {  
    if (e.errors) {  
        var message = "Errors:\n";  
        $.each(e.errors, function (key, value) {  
            if ('errors' in value) {  
                $.each(value.errors, function () {  
                    message += this + "\n";  
                });  
            }  
        });  
        alert(message);  
    }  
}  

function onGridEdit(arg) {  
    if (arg.container.find("input[name=IsFkEnabled]").length > 0) {
        arg.container.find("input[name=IsFkEnabled]").click(function () {
            if ($(this).is(":checked") == false) {  

            }  
            else {  
                arg.model.IsFkEnabled = true;
                $("#Grid").data("kendoGrid").closeCell(arg.container);  
                $("#Grid").data("kendoGrid").editCell(arg.container.next());  
            }  
        });  
    }  
    if (arg.container.find("input[name=FID]").length > 0) {  
        if (arg.model.IsFkEnabled == false) {
            $("#Grid").data("kendoGrid").closeCell(arg.container)  
        }  
    }  
}  
</script>  

<div>
@(Html.Kendo().Grid<MvcApplication1.Models.TestModels>()
    .Name("Grid")
    .Columns(columns =>
    {
        columns.Bound(p => p.ID);
        columns.Bound(p => p.Name);
        columns.Bound(p => p.IsFkEnabled);
        columns.ForeignKey(p => p.FID,   (System.Collections.IEnumerable)ViewData["TestList"], "Value", "Text");

    })
    .ToolBar(toolBar => toolBar.Save())
    .Editable(editable => editable.Mode(GridEditMode.InCell))
    .Pageable()
    .Sortable()
    .Scrollable()
    .Filterable()
    .Events(e => e.Edit("onGridEdit"))
    .DataSource(dataSource => dataSource
        .Ajax()
        .Batch(true)
        .ServerOperation(false)
        .Events(events => events.Error("errorHandler"))
        .Model(model =>
        {
            model.Id(p => p.ID);
            model.Field(p => p.ID).Editable(false);
        })
    .Read(read => read.Action("ForeignKeyColumn_Read", "Home"))
    .Update(update => update.Action("ForeignKeyColumn_Update", "Home"))
    )
)
</div>

MODEL

namespace MvcApplication1.Models
{
    public class TestModels
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public bool IsFkEnabled { get; set; }
        public int FID { get; set; }
    }
}

CONTROLLER

public class HomeController : Controller
{
    public ActionResult Index()
    {

        List<SelectListItem> items = new List<SelectListItem>();

        for (int i = 1; i < 6; i++)
        {
            SelectListItem item = new SelectListItem();
            item.Text = "text" + i.ToString();
            item.Value = i.ToString();
            items.Add(item);
        }

        ViewData["TestList"] = items;

        return View();
    }

    public ActionResult ForeignKeyColumn_Read([DataSourceRequest] DataSourceRequest request)
    {
        List<TestModels> models = new List<TestModels>();

        for (int i = 1; i < 6; i++)
        {
            TestModels model = new TestModels();
            model.ID = i;
            model.Name = "Name" + i;

            if (i % 2 == 0)
            {
                model.IsFkEnabled = true;

            }

            model.FID = i;


            models.Add(model);
        }

        return Json(models.ToDataSourceResult(request));
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult ForeignKeyColumn_Update([DataSourceRequest] DataSourceRequest request, [Bind(Prefix = "models")]IEnumerable<TestModels> tests)
    {
        if (tests != null && ModelState.IsValid)
        {
            // Save/Update logic comes here  
        }

        return Json(ModelState.ToDataSourceResult());
    }
}

If you want to download demo then click here.

Answer

The simplest way is to use the dataBound event to conditionally apply one of the special CSS classes to the cells that the grid ignore for editing:

  • http://dojo.telerik.com/izOka

       dataBound: function(e) {
          var colIndex = 1;
    
          var rows = this.table.find("tr:not(.k-grouping-row)");
          for (var i = 0; i < rows.length; i++) {
            var row = rows[i];
            var model = this.dataItem(row);
    
            if (!model.Discontinued) {
              var cell = $($(row).find("td")[colIndex]);
              cell.addClass("k-group-cell");
            }
          }
    
        },
    
Answer

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));
});
Answer

Another approach is to use your own "editor" function for column definition that provides either an input element or a plain div depending on your condition.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.