This is a migrated thread and some comments may be shown as answers.

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

14 Answers 4573 Views
Grid
This is a migrated thread and some comments may be shown as answers.
Adrian
Top achievements
Rank 1
Adrian asked on 15 Aug 2012, 12:11 AM
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. :)

14 Answers, 1 is accepted

Sort by
0
Adrian
Top achievements
Rank 1
answered on 16 Aug 2012, 04:58 PM
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.
0
Adrian
Top achievements
Rank 1
answered on 16 Aug 2012, 11:38 PM
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.
0
Philipp
Top achievements
Rank 1
answered on 06 Sep 2012, 10:23 AM
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? ;)
0
Adrian
Top achievements
Rank 1
answered on 10 Sep 2012, 05:20 PM
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. :)
1
Philipp
Top achievements
Rank 1
answered on 10 Sep 2012, 05:48 PM
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 ... 
0
Just
Top achievements
Rank 1
answered on 09 Oct 2012, 07:39 PM
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?
0
Boon-Kiat
Top achievements
Rank 1
answered on 10 Oct 2012, 05:28 PM
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");
    }
});
0
TomW
Top achievements
Rank 2
answered on 22 Oct 2012, 08:54 AM
Just wanted to say thanks for this.  Just what I needed.
0
Michal
Top achievements
Rank 1
answered on 01 Nov 2012, 02:40 PM
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.
0
rooney
Top achievements
Rank 1
answered on 02 Nov 2012, 02:40 AM
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 
0
jose
Top achievements
Rank 1
answered on 24 Dec 2012, 11:19 PM

 How about this...

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

0
Andres
Top achievements
Rank 1
answered on 08 Feb 2013, 12:03 AM
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
0
Martin
Top achievements
Rank 1
answered on 12 May 2015, 07:55 AM

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.

0
Yuri
Top achievements
Rank 1
Iron
answered on 02 Dec 2015, 06:03 AM

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

looked like this method say other something then you understood.

Tags
Grid
Asked by
Adrian
Top achievements
Rank 1
Answers by
Adrian
Top achievements
Rank 1
Philipp
Top achievements
Rank 1
Just
Top achievements
Rank 1
Boon-Kiat
Top achievements
Rank 1
TomW
Top achievements
Rank 2
Michal
Top achievements
Rank 1
rooney
Top achievements
Rank 1
jose
Top achievements
Rank 1
Andres
Top achievements
Rank 1
Martin
Top achievements
Rank 1
Yuri
Top achievements
Rank 1
Iron
Share this question
or