Make cell readonly in Kendo Grid if condition is met
Asked Answered
C

6

27

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.

Centerpiece answered 2/1, 2014 at 11:2 Comment(1)
This is not supported by Kendo UI out-of-the-box but you can implement it but a clean/easy implementation is dependent on the type of edition that you are using. Is it inline, popup or incell?Cabernet
B
45

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));
});
Brambling answered 2/1, 2014 at 11:31 Comment(6)
Telerik, please implement approach 2 out of the box!Cunctation
I was wondering if anyone has an example of how to implement this fo inline editing?Carraway
@Azzi one option here: #24723393Danita
I had problems hooking up the event (I am creating the grid using MVC bindings). In the end I just implemented my entire event handler in the editCell implementation. Should be OK for my case since I only have on grid on the page.Alissaalistair
right, for the MVC fluent builders, you'd probably need to extend Kendo.Mvc.UI.Fluent.GridEventBuilder if you want to use beforeEdit; I should have mentioned thatDanita
@Alissaalistair I added a paragraph at the end - if you have time to try this, I'd be interested if it works for youDanita
C
6

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.

Cleanse answered 27/5, 2015 at 18:44 Comment(0)
H
2

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.

Hypercritical answered 2/1, 2014 at 11:26 Comment(0)
P
1

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");
            }
          }
    
        },
    
Paver answered 18/10, 2015 at 9:17 Comment(1)
If you are using a version before 2017 R2 which added an official BeforeEdit event, and you are using the MVC wrappers ("Kendo MVC"), this hack is a neat way to solve it. If you use it, I recommend adding a comment in your code as to why you are hi-jacking the k-group-cell class, so that those who read your code in the future understand.Turki
U
0

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.

Unsegregated answered 24/11, 2015 at 5:35 Comment(0)
L
0

Use Template column instead of Bound column

columns.Template(@<text></text>).ClientTemplate("#=Id#").Title("Id");
Lamebrain answered 14/9, 2021 at 16:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.