Kendo Grid - How to have different custom editor for Update and Create

15 posts, 0 answers
  1. Adrian
    Adrian avatar
    24 posts
    Member since:
    Jul 2012

    Posted 14 Aug 2012 Link to this post

    I have a KendoUI Grid set up and working pretty nicely. My team and I finally figured out how to get all the CRUD stuff working, but we're having an issue when confronted with the custom popup editor.

    We have a nice custom editor set up and it works perfectly for Edit mode. We only want certain fields to be editable for existing entries, and want different fields editable for new entries. For example, we have a database of team members on a project with the following schema model:

    model: {
        id: "empId",
        fields: {
            "empId": { editable: false, type: "string" },
            "empLastName": { editable: false, type: "string" },
            "empFirstName": { editable: false, type: "string" },
            "empRole": { editable: true, type: "string" },
            "empUserName": { editable: false, type: "string" },
            "isCore": { editable: true, type: "boolean" },
            "projTeamId": { editable: false, type: "string" },
            "hours": { editable: false, type: "number" },
            "empStatus": { editable: false, type: "string" }
        }
    }

    When editing an existing entry in the grid, only empRole and isCore should be editable.

    When adding a new entry to the grid, only empLastName, empFirstName, empRole, and isCore should be editable.

    The rest should appear as either standard text or greyed-out checkboxes. I am aware that I can simply make all fields inputs and the plugin will automatically filter out which fields accept input and which do not upon submission, but that is undesirable behaviour. We wish to make it visually clear that certain fields are not editable, while being able to have different fields be editable in different scenarios (Add vs Edit).

    Is this possible? We really need to find a way to make it possible if it isn't.

    Here's the editor template we have so far:

    <script id="teamEditorTemplate" type="text/x-kendo-template">
        <table>
            <colgroup>
                <col width="160" />
                <col width="100" />
                <col />
            </colgroup>
            <tr>
                <td rowspan="12"><img class="teamImage" width="128" height="128" data-src="/Content/images/anonymousUser.jpg" src="# if(empUserName === "null") { #/Content/images/anonymousUser.jpg# } else { #@System.Configuration.ConfigurationManager.AppSettings["EmployeePhoto"]#= empUserName #.jpg# } #" /></td>
                <td><div class="k-edit-label">
                    <label for="Id">Employee ID:</label>
                </div></td>
                <td>#= empId #</td>
            </tr>
            <tr>
                <td><div class="k-edit-label">
                    <label for="FirstName">First Name:</label>
                </div></td>
                <td>#= empFirstName #</td>
            </tr>
            <tr>
                <td><div class="k-edit-label">
                    <label for="LastName">Last Name:</label>
                </div></td>
                <td>#= empLastName #</td>
            </tr>
            <tr>
                <td><div class="k-edit-label">
                    <label for="Role">Role:</label>
                </div></td>
                <td><input type="text" id="Role" class="k-input k-textbox"
                    data-bind="value:empRole"
                    data-value-field="empRole"
                    data-text-field="empRole"
                    data-source="empRoleDropDownDataSource"
                    data-role="dropdownlist" /></td>
            </tr>
            <tr>
                <td><div class="k-edit-label">
                    <label for="Core">Core Member:</label>
                </div></td>
                <td><input type="checkbox" id="Core" class="k-input k-checkbox" data-bind="value:isCore" /></td>
            </tr>
            <tr>
                <td><div class="k-edit-label">
                    <label for="Active">Active Employee:</label>
                </div></td>
                <td><input id="Active" disabled # if(empStatus !== "T") { #checked="checked"# } # type="checkbox"  class="k-input k-checkbox" /></td>
            </tr>
            <tr>
                <td><div class="k-edit-label">
                    <label for="Hours">Hours:</label>
                </div></td>
                <td>#= hours #</td>
            </tr>
        </table>
    </script>

    Here's our DataSource declaration:

    var dataTeamRead = "/ProjectInfo/GetProjectTeam";
    var dataTeamUpdate = "/ProjectInfo/UpdateProjectTeam";
    var dataTeamDestroy = "/ProjectInfo/DestroyProjectTeam";
    var dataTeamCreate = "/ProjectInfo/CreateProjectTeam";
    var dataTeam = new kendo.data.DataSource({
        pageSize: 10,
        transport: {
            read: {
                url: dataTeamRead,
                data: { projectId: Querystring.Get("projectId") },
                type: "POST",
                dataType: "json"
            },
            update: {
                url: dataTeamUpdate,
                type: "POST",
                dataType: "json"
            },
            destroy: {
                url: dataTeamDestroy,
                type: "POST",
                dataType: "json"
            },
            create: {
                url: dataTeamCreate,
                type: "POST",
                dataType: "json"
            }
        },
        schema: {
            type: "json",
            model: {
                id: "empId",
                fields: {
                    "empId": { editable: false, type: "string" },
                    "empLastName": { editable: false, type: "string" },
                    "empFirstName": { editable: false, type: "string" },
                    "empRole": { editable: true, type: "string" },
                    "empUserName": { editable: false, type: "string" },
                    "isCore": { editable: true, type: "boolean" },
                    "projTeamId": { editable: false, type: "string" },
                    "hours": { editable: false, type: "number" },
                    "empStatus": { editable: false, type: "string" }
                }
            }
        }
    });

    Here's our kendoGrid declaration:

    var kendoGridPageable = {
        pageSize: 10,
        previousNext: true,
        numeric: false,
        info: true
    },
    kendoGridToolbar = [
        { name: "create", text: "Add" },
        { name: "edit", text: "Edit" },
        { name: "destroy", text: "Remove" }
    ];
     
    $("#gridTeam").kendoGrid({
        rowTemplate: kendo.template($("#teamRowTemplate").html()),
        columns: [
            { "title": " ", "filterable": false, "width": "48px" },
            { "field": "empLastName", "title": "Last", "filterable": true, "width": "25%" },
            { "field": "empFirstName", "title": "First", "filterable": true, "width": "25%" },
            { "field": "empRole", "title": "Role", "filterable": true, "width": "25%" },
            { "field": "hours", "title": "Hours", "filterable": true, "width": "25%" }
        ],
        dataSource: dataTeam,
        scrollable: false,
        sortable: {
            mode: "multiple",
            allowUnsort: true
        },
        filterable: true,
        pageable: kendoGridPageable,
        selectable: "row",
        nagivatable: true,
        toolbar: kendoGridToolbar,
        editable: {
            mode: "popup",
            template: $("#teamEditorTemplate").html(),
            update: true,
            destroy: true,
            confirmation: "Are you sure you want to remove this team member?"
        },
        columnMenu: true
    });

    Here's our rowTemplate:

    <script id="teamRowTemplate" type="text/x-kendo-template">
        <tr class="# if(isCore === true) { #coreMember# } # # if(empStatus === "T") { #terminatedEmployee# } #" data-uid="${uid}">
            <td><img class="teamImage" width="32" height="32" data-src="/Content/images/anonymousUser.jpg" src="# if(empUserName === "null") { #/Content/images/anonymousUser.jpg# } else { #@System.Configuration.ConfigurationManager.AppSettings["EmployeePhoto"]#= empUserName #.jpg# } #" /></td>
            <td>#= empLastName #</td>
            <td>#= empFirstName #</td>
            <td>#= empRole #</td>
            <td>#= hours #</td>
        </tr>
    </script>

    Any assistance that can be provided would be most appreciated.

    Thank you for your time and screen space. :)
  2. Adrian
    Adrian avatar
    24 posts
    Member since:
    Jul 2012

    Posted 16 Aug 2012 Link to this post

    I am attempting a workaround that modifies the disabled attributes of input fields which need to be either enabled during Add or disabled during Edit, but so far I have failed to get it working. The bug mentioned here involving using kendo.template with a custom editor popup template is preventing me from taking the easy way and simply setting a data-attribute on the grid's container with the last-clicked button (add or edit) as its value, and then enabling or disabling the input fields accordingly, because without kendo.template, I cannot include raw JavaScript in my template and have it evaluate. I want to do something like:

    # if ($("#teamGrid").data("mode") === "edit") { # disabled="disabled" # } #

    within my template, inside an <input /> tag, but I cannot because of the bug with kendo.template.
  3. Kendo UI is VS 2017 Ready
  4. Adrian
    Adrian avatar
    24 posts
    Member since:
    Jul 2012

    Posted 16 Aug 2012 Link to this post

    I figured out a hackish workaround for the lack of differentiation between Add (create) and Edit (update) mode in the Popup Editor template. I have the Add button store the value "add" in a custom "data-mode" attribute on the button's parent grid (and the edit button stores a value of "edit" in the same attribute). Then I have the buttons run a function which reads the value of the "data-mode" attribute for that specific grid and applies 'disabled="disabled"' to each element which needs to be disabled if the mode is "edit". I had already tried that, but unfortunately the Popup Editor doesn't just hide and show the same instance of the template within the DOM. Instead, it adds a new one to the DOM, and when the Add or Edit action is completed or canceled, simply hides it. This would be fine if it reused the same element next time, but if you perform a second Add or Edit operation, it creates an entirely new element in the DOM, thus making standard jQuery targeting useless. I had to take advantage of jQuery's ".last()" function to target the most-recently-created Popup Editor instance.

    It's an incredibly inelegant and hackish solution, and really shouldn't even be necessary, but oh well.

    Please consider this a feature request: add built-in differentiation between the initiating actions in the Popup Editor template and also fix the Popup Editor so it either reuses the same DOM node that it initially creates, or it removes it from the DOM when it's done using it (instead of leaving it hanging around wasting memory and confusing people).

    Thanks.

    If anyone can propose a more elegant solution to my problem, I would be more than happy to hear it.

    In fact, please don't let the fact that I've "solved" this issue stop you from proposing a solution. I really hate how hackish this is.
  5. Philipp
    Philipp avatar
    4 posts
    Member since:
    Sep 2012

    Posted 06 Sep 2012 Link to this post

    it would be very useful to be able to distinguish between these to states (without hacking it together ...).
    @adrian could you share your code - even if it is hackish? ;)
  6. Adrian
    Adrian avatar
    24 posts
    Member since:
    Jul 2012

    Posted 10 Sep 2012 Link to this post

    This function is called when the editor is spawned, to check its mode ("add" or "edit"), and disable or enable specific fields:

    function checkEditMode(item) {
        var test = item.attr("id");
        var editor = $("div.k-widget.k-window").last();
        if (test === "gridTeam") {
            if ($("#gridTeam").data("mode") === "edit") {
                editor.find("input#teamFirstName").attr("disabled", "disabled");
                editor.find("input#teamLastName").attr("disabled", "disabled");
                editor.find("span.k-window-title").text("Edit Existing Member");
                editor.find("a.k-button.k-button-icontext.k-grid-update").html('<span class="k-icon k-update"></span>Save');
                teamEditorOther();
            }
            if ($("#gridTeam").data("mode") === "add") {
                editor.find("input#teamFirstName").removeAttr("disabled");
                editor.find("input#teamLastName").removeAttr("disabled");
                editor.find("span.k-window-title").text("Add New Member");
                editor.find("a.k-button.k-button-icontext.k-grid-update").html('<span class="k-icon k-update"></span>Save');
                teamEditorCascade();
            }
        }
    }

    This code detects click events on the grid's buttons and sets the "data-mode" attribute of the grid's containing element appropriately. The toggleButtons functions are there to handle hiding and showing of the appropriate buttons in the toolbar for grids with inline editing -- annoying, but neccesary:

    $(document).on("click.gridButtons", ".k-grid.k-widget a.k-button", function (event) {
        var thisGrid = $(this).closest(".k-grid")
        var thisData = thisGrid.data("kendoGrid");
        /* ADD BUTTON */
        if ($(this).hasClass("k-grid-add") || $(this).hasClass("add")) {
            thisGrid.data("mode", "add");
            //console.log('data-mode changed to "add"');
            if (thisGrid.attr("id") !== "gridTeam"){
                toggleToolbarButtons(thisGrid, "hide");
                toggleSaveCancelButtons(thisGrid, "show");
            }
            checkEditMode(thisGrid);
        }
        /* EDIT BUTTON */
        if ($(this).hasClass("k-grid-edit") || $(this).hasClass("edit")) {
            thisData.editRow(thisData.select());
            thisGrid.data("mode", "edit");
            //console.log('data-mode changed to "edit"');
            if (thisGrid.attr("id") !== "gridTeam"){
                toggleToolbarButtons(thisGrid, "hide");
                toggleSaveCancelButtons(thisGrid, "show");
            }
            checkEditMode(thisGrid);
        }
        /* DELETE BUTTON */
        if ($(this).hasClass("k-grid-delete") || $(this).hasClass("delete")) {
            thisData.removeRow(thisData.select());
            toggleToolbarButtons(thisGrid, "show");
            toggleSaveCancelButtons(thisGrid, "hide");
        }
        /* SAVE BUTTON */
        if ($(this).hasClass("k-grid-save-changes") || $(this).hasClass("save")) {
        console.log("save button");
            toggleToolbarButtons(thisGrid, "show");
            toggleSaveCancelButtons(thisGrid, "hide");
            thisData.dataSource.read();
        }
        /* CANCEL BUTTON */
        if ($(this).hasClass("k-grid-cancel-changes") || $(this).hasClass("cancel")) {
            if (thisData.dataSource.options.batch === true) {
                thisData.cancelChanges();
            }
            if (thisData.dataSource.options.batch === false) {
                thisData.cancelRow();
            }
            toggleToolbarButtons(thisGrid, "show");
            toggleSaveCancelButtons(thisGrid, "hide");
        }
        event.preventDefault();
    });

    As I said, it's a bit hackish, but it does seem to work for me.

    Something else that may be helpful to note: when you close the editor, it is not removed from the DOM, so next time you edit, a new editor is added, resulting in two, then three, etc. It's annoying and makes traversing the DOM (and finding the correct, newly-created editor more difficult than it should be), so I added this function to handle removing the editor from the DOM once the edit/add operation has been completed:

    $(document).on("click.editorButtons", ".k-widget.k-window a.k-button, .k-widget.k-window a.k-window-action.k-link", function (event) {
        if ($(this).hasClass("k-grid-update") || $(this).hasClass("update")) { var thisWindow = $(this).closest("div.k-widget.k-window"); setTimeout(function () { thisWindow.remove(); }, 500); }
        if ($(this).hasClass("k-grid-cancel") || $(this).hasClass("cancel")) { var thisWindow = $(this).closest("div.k-widget.k-window"); setTimeout(function () { thisWindow.remove(); }, 500); }
        if ($(this).hasClass("k-window-action") || $(this).hasClass("k-link")) { var thisWindow = $(this).closest("div.k-widget.k-window"); setTimeout(function () { thisWindow.remove(); }, 500); }
        clearInterval(teamInterval);
        event.preventDefault();
    });

    Sorry for the word-wrapping going on here; it makes it a bit harder to read.

    Worthy of note: each consecutive add or edit operation takes (seemingly) twice the time of the previous add or edit operation to construct, append, and populate the editor window/widget. This is a bug I'm currently unable to provide a fix for, but we deemed it a low-priority issue on my project for the time being. I imagine it's probably caused by the fact that every time it's called, it has to rebuild the DOM element, rather than simply creating a reusable one and appending it to the DOM when the grid widget loads, then targeting it and populating it whenever an edit/add operation is called.

    I hope my code is helpful. I know it's a bit messy and possibly has a few appendices that should be sorted out, but I'm on a schedule here. :)
  7. Philipp
    Philipp avatar
    4 posts
    Member since:
    Sep 2012

    Posted 10 Sep 2012 Link to this post

    thanks for the snippets.
    i was trying different things myself and i think i found a "solution":

    var MYAPP MYAPP || {};

    // init grid
    $('#grid').kendoGrid({
        // ...
        editfunction({
            var $win $('.k-window');

            // set title
            if (MYAPP.editorType === 'add'{
                $win.find('.k-window-title').html("add new");
            }
            else {
                $win.find('.k-window-title').html("edit");
            }
        },
        dataBoundfunction({
            // add event listener to EDIT button
            $('#grid .k-grid-edit').on('click'function(evt{
                MYAPP.editorType 'edit';
            });
        }
        // ...
    });

    // add event listener to ADD button
    $('#grid .k-grid-toolbar .k-grid-add').on('click'function(evt{
        MYAPP.editorType 'add';
    });

    I'm just using a global variable inside my app namespace to save temporarily which button was clicked and set the title inside the window accordingly. seems to work so far ... 
  8. Just
    Just avatar
    24 posts
    Member since:
    Aug 2012

    Posted 09 Oct 2012 Link to this post

    I believe a simpler way would be to check for the selected row index. Like:


    edit: function(e) {
    
    selectedRowIndex = $("#myGrid").data("kendoGrid").select().index();        if (selectedRowIndex >= 0) {          //this is update } else{ //this is insert } }

    Anybody sees any pitfalls in this?
  9. Boon Kiat
    Boon Kiat avatar
    2 posts
    Member since:
    Oct 2012

    Posted 10 Oct 2012 Link to this post

    Well, I found a better solution, it took me quite some time to figure out. I used firebug to examine the e parameter in the edit event and I found out that from e.model you can actually access the model itself. Therefore, you just need to check the primary key(s) or non-null field(s) to see whether it's empty.


    Template:
    <script id="popup_editor" type="text/x-kendo-template">
        <p>Custom editor template</p>
     
        <div class="k-edit-label">
            <label for="SortOrder">Sort Order</label>
        </div>
        <input type="text" class="k-input k-textbox" name="SortOrder" data-bind="value:SortOrder">
     
         <div class="k-edit-label">
            <label for="SortOrder" >Sort Order</label>
        </div>
        <input type="text" class="k-input k-textbox" name="SortOrder" data-bind="value:SortOrder">
             
        <div class="k-edit-label">
            <label for="RatingName" >Rating Name</label>
        </div>
        <input type="text" class="k-input k-textbox" name="RatingName" data-bind="value:RatingName">
     
    </script>



    Javascript:
    var grid = $("#grid").data("kendoGrid");
    grid.bind("edit", function (e) {
        var title = $(e.container).parent().find(".k-window-title");
        var button = $(e.container).parent().find(".k-grid-update");
     
        if (e.model.SurveyId != '') {
            //Editing: If SurveyId is not empty, it must be an existing record
            $(title).text('Edit');
            $(button).html('<span class="k-icon k-update">Update</span>Update');
     
            $(e.container)
                .find("input[name='SurveyId']")
                    .attr("disabled", "disabled");
     
            $(e.container)
                .find("input[name='SortOrder']")
                    .attr("disabled", "disabled");
        } else {
            //Adding: Trying to enable the fields
           $(title).text('Add');
           $(button).html('<span class="k-icon k-update">Add</span>Add');
     
            $(e.container)
                .find("input[name='SurveyId']")
                    .removeAttr("disabled");
     
            $(e.container)
                .find("input[name='SortOrder']")
                    .removeAttr("disabled");
        }
    });
  10. TomW
    TomW avatar
    2 posts
    Member since:
    Oct 2012

    Posted 22 Oct 2012 Link to this post

    Just wanted to say thanks for this.  Just what I needed.
  11. Michal
    Michal avatar
    2 posts
    Member since:
    Oct 2012

    Posted 01 Nov 2012 Link to this post

    Well by putting together all posts here I found what i think is easiest solution.

    function EditItem(e) { 
        if (e.model.Id == 0) {
            //Add
        }
        else {
            //Edit
        }
    }

    This is based on the Id parameter if yours name of unique key is different you need to update this solution accordingly.

    New element doesnt have Id yet so this could help you. Could be also applied in template using Razor and If blocks.
  12. rooney
    rooney avatar
    17 posts
    Member since:
    Oct 2012

    Posted 01 Nov 2012 Link to this post

    Hii all
    i making custom popup editor with customize width, so my problem is a window not in center, but widen to right
    this my code :
    ...
    editable: { mode: "popup", template: $("#popup_brand").html() },
            edit: function(e) {
                $(e.container).parent().css({
                    width: 900
                });
            },

    so, how to make that template window into the center.

    Thank 
  13. jose
    jose avatar
    8 posts
    Member since:
    Apr 2012

    Posted 24 Dec 2012 Link to this post

     How about this...

    var grid = $("#Grid").data("kendoGrid");
     grid.bind("edit", function (e) {
    if (e.model.isNew()) {
    alert("new");
    }
    else {
    alert("edit");
    }
    })

  14. Andres
    Andres avatar
    31 posts
    Member since:
    May 2011

    Posted 07 Feb 2013 Link to this post

    I found a simple and clean solution to hide some fields in the editor popup when I'm creating a new element. I'm using a declarative data binding in the fields that I want to hide. For instance:

    <input type="checkbox" name="IsActive" id="IsActive" class="editor-checkbox" data-bind="checked: IsActive, visible: Id"  data-type="boolean" data-role="switch" />
    The  data-bind="visible: Id" allows me to hide the element at creation because Id is equal to 0 ... so it's false.

    Hope it helps
  15. Martin
    Martin avatar
    9 posts
    Member since:
    Nov 2014

    Posted 12 May 2015 Link to this post

    All these hacks are very misleading. The problem here is, that the differentiation of a NEW and an EXISTING data item in a Grid (and all other widgets) is NOT the job of the widget, but of the underlying MODEL (DataSource).

    If properly configured, your Grid is best served using a DataSource. For the DataSource you can specify a function

    " change: function (e) {

    if (e.action === "add") {

    // here do what you need to mark a new object } }".

    .items[0] is the first new item. Set some flag attribute on this object that you can read in the editor. I use this to set default values etc. Works fine.

    A comment to the Telerik Team:

    I come across a lot of forum discussions where people are trying impressive hacks just to simulate something that is already in the framework but is not documented. I would expect Telerik to support their customers in their own forum by giving at least some simple hints for best practices etc. Otherwise, working with Kendo UI becomes a very frustrating experience, though the product is quite good.

  16. Sharon
    Sharon avatar
    9 posts
    Member since:
    Dec 2013

    Posted 02 Dec 2015 in reply to jose Link to this post

    your answer looked the best, but... it fives me 'true' alse on edit...

    looked like this method say other something then you understood.

Back to Top
Kendo UI is VS 2017 Ready