How to Add new item to MVC ListView in a Popup?

1 Answer 36 Views
ListView
Bob
Top achievements
Rank 3
Iron
Iron
Veteran
Bob asked on 02 May 2025, 03:56 PM

I have a ListView that works very nicely with most of the default behavior as shown below.  However, I would like to create a new item in the ListView by using a Popup form.  This form could also be used by the edit event if that simplifies the code, but does not have to be the same, and the edit functionality could work as it does now.  I have looked at the popup editing feature as described here.  However, I could not figure out how to make this work for create as there is no create event on the ListView.  Does everything have to work outside the framework of the control, so that the add is using a separate Ajax call and then refreshing the view, or is there a better way to keep this within the structure of the ListView and its events?

Add Button and ListView:

            @(Html.Kendo().Button()
                .Name("AddNew")
                .Icon("plus")
                .FillMode(ButtonFillMode.Solid)
                .Rounded(Rounded.Medium)
                .HtmlAttributes(new { @class = "add-button" })
                .Events(e=>e.Click("addNewClick"))
            )

            @(Html.Kendo().ListView<MyNamespace.Models.ItemComment>()
                .Name("listView")
                .TagName("div")
                .ClientTemplateId("commentListTemplate")
                .Editable()
                .DataSource(dataSource => dataSource
                    .Model(model => model.Id(comment => comment.CommentID))
                    .Read(read => read.Action("Comments_Read", "Comment").Data("getCommentReadParameters"))
                    .Update(update => update.Action("Comments_Update", "Comment"))
                    .Destroy(destroy => destroy.Action("Comments_Delete", "Comment"))
                    .Events(e => e.Error("epic.error.kendoErrorHandler"))
                )
                .Events(e => e.DataBound("onCommentsDataBound"))
                .Events(e => e.Remove("deleteConfirmation"))

            )

Display Template:

<script type="text/x-kendo-tmpl" id="commentListTemplate">
     <div class="k-card">
            <div class="k-card-body k-card-horizontal k-vbox k-column" style="margin:0;padding:4px 4px 0">
                <img class='k-card-image' style="height:16px; margin-right:5px;" src="@Url.Content("~/Content/assets/images/blue-person.png")">
                <div class='commentHeader'>
                    <h6 class='k-card-subtitle'>#= UserID #</h6>
                    <div class="edit-buttons">
                        <a role="button" class="k-button k-button-solid-base k-button-solid k-button-md k-rounded-md k-edit-button" href="\\#">#= kendo.ui.icon({ icon: 'pencil' }) #</a>
                        <a role="button" class="k-button k-button-solid-base k-button-solid k-button-md k-rounded-md k-delete-button" href="\\#">#= kendo.ui.icon({ icon: 'x' }) #</a>
                    </div>
                    <p>
                        #: Comment #
                    </p>
                </div>
            </div>
    </div>
</script>

Supporting JavaScript functions:

<script type="text/javascript">

    function onCommentsDataBound() {
        if (this.dataSource.data().length == 0) {
            $("#listView").append("<h3 style='padding: 2px 4px 0;'>No comments</h3>");
        }
    }

    function addNewClick(e) {
        var listView = $("#listView").data("kendoListView");
        listView.add();
        e.preventDefault();
    }

    function deleteConfirmation(event) {

        if (!confirm("Are you sure you want to delete this comment?"))
            event.preventDefault();
    }

</script>

Thanks, Bob

 

1 Answer, 1 is accepted

Sort by
1
Accepted
Eyup
Telerik team
answered on 07 May 2025, 03:52 PM

Hi Bob,

 

Thank you for reaching out.

First, let me begin with the information that the Grid and TreeList components provide built-in PopUp editing feature:
- https://demos.telerik.com/aspnet-mvc/grid/editing-popup
- https://demos.telerik.com/aspnet-mvc/treelist/editing-popup

But if you want to achieve it with ListView, you can use the implementation from the mentioned KB article and add this button:

    <button onclick="addNewClick();">Add New Record</button>
    <script>
        function addNewClick() {
            var listView = $("#listView").data("kendoListView");
            listView.add();
        }
    </script>
    <div id="listView"></div>
And here is the updated dojo for your convenience:
https://dojo.telerik.com/CyuffNAg/2

And the result:

Let me know if you find this helpful and on point.

 

Regards,
Eyup
Progress Telerik

Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.

Bob
Top achievements
Rank 3
Iron
Iron
Veteran
commented on 07 May 2025, 04:16 PM

Hi Eyup, thank you for that information.  I can see now how this works integrated with a popup.

I have a separate question about the popup form.  One of the properties of my model is a file attachment as defined:

        public string AttachmentFileName { get; set; }
        public byte[] Attachment { get; set; }
Is it possible to create a Browse control on the popup form to browse the user's drive for a file to upload into the model? 

 

 

Bob
Top achievements
Rank 3
Iron
Iron
Veteran
commented on 09 May 2025, 02:56 PM | edited

I found one issue with the dojo example.  When adding a new record, the popup opens and a new entry is put in the listview behind.  When the popup is closed (not submitted), the empty entry remains in the listview.  How do I get rid of this empty entry on close?

Also, if the create action on the server adds data to the model that gets displayed, this data is not updated with datasource.sync(), only datasource.read().  I am calling datasource.read() after datasource.sync() in the submit.  Is that the proper way to handle the update of the model?

Mihaela
Telerik team
commented on 12 May 2025, 02:56 PM

Hi Bob, 

Here are my answers to your queries:

1) Upload editor in the Form - Indeed, you can integrate the Upload component in the Form, as described in the documentation:

In case you use a standrd HTML form, you can define the Upload component, as well

2) To remove the placeholder of the new record when the Widnow is closed without submittng the form:

  • Add a global variable that flags when the form is submitted;
  • Handle the "close" event of the Window, check the flag, and call the cancel() method to cancel any pending changes.
<script>
      var isFormSubmitted = false;
      $(document).ready(function () {
        $("#form-editor").kendoForm({
          ...
          submit: function (e) {
            isFormSubmitted = true;
            e.preventDefault();
            ...
          }
        });

        var formWindow = $("#form-editor-container").kendoWindow({
          ...
          close: function(e) {
            if(!isFormSubmitted) {
              $("#listView").data("kendoListView").cancel();
            }
            isFormSubmitted = false; // reset the flag each time the event triggers.
          }
        }).data("kendoWindow");
      });
</script>

3) Create Action - To ensure that the Create request triggers when calling the sync() method of the DataSource:

  • Specify the Create() action within the DataSource configuration of the ListView;
  • Set the "CommentID" to "0", so the item is recognized by the DataSource as a new one.

Best,
Mihaela

Bob
Top achievements
Rank 3
Iron
Iron
Veteran
commented on 12 May 2025, 03:55 PM

Hi Mihaela,

Thank you very much for these answers.  They are very helpful!

For #2 - canceling the edit, this works in one scenario, but not another.  Let me explain - I have a form that contains an entity, let's just call it the item.  Inside this form, I am rendering a partial control for comments that are associated with the item.  There are two states of the item, either existing or not yet created.  When the item is existing, the comments can be added/edited/removed from the database because the related item exists.  However, before the item exists, comments cannot be added or updated to the database because there is no relational entity.  Therefore, I am keeping the comments in the client datasource until the item form is submitted and then the comments can be added.  (This was working great when we were using a Grid for the comments, but we needed to convert the grid to a ListView for styling purposes and the ListView has not been as easy to get working.)  To sum up, when the item exists and the comments can be actioned using the CRUD methods of the ListView, everything is now working.  When the item does not exist and the client datasource holds the comments, trying to cancel the add/edit action throws exceptions.

Here is my ListView for the scenario where the relational item does not yet exist, along with the code to add/edit comments in the datasource.  Everything seems to work ok except for the cancel of the form window.

The ListView in the partial control:

@(Html.Kendo().ListView<EPICWeb.Models.ItemComment>() .Name("listView") .TagName("div") .ClientTemplateId("commentListTemplate") .Editable() .DataSource(dataSource => dataSource .Model(model => model.Id(comment => comment.CommentDate)) .Read(read => read.Action("Comments_Read", "Comment").Data("getCommentReadParameters")) .Events(e => e.Error("epic.error.kendoErrorHandler")) .ServerOperation(false) ) .Events(e => e.Edit("onCommentsListEdit")) .Events(e => e.DataBound("onCommentsListDataBound")) .Events(e => e.Remove("deleteConfirmation")) )

The code for form editing setup - notice I capture the uid to identify the correct selection because there is no entity ID yet.

    function onCommentsListEdit (e) {
        e.preventDefault();

        var cform = $("#cform-editor").getKendoForm();

        // get the selected item from the ListView
        var selectedItem = this.dataItem(e.item);
        // create the Form editor with the selected item's properties

        cform.setOptions({
            formData: {
                UUID: selectedItem.uid,
                CommentID: selectedItem.CommentID,
                Comment: selectedItem.Comment,
            }
        });
        // show the Form editor
        var fW = $("#cform-editor-container").getKendoWindow();
        fW.open();
        fW.center();
    }

And the code for form setup and submission:

<script>

    $(document).ready(function () {
        var isFormSubmitted = false;
        var dataSource = $("#listView").data("kendoListView").dataSource;
        var listView = $("#listView").getKendoListView();
        listView.editTemplate = kendo.template($("#commentListTemplate").html());

        $("#cform-editor").kendoForm({
            items: [
                {
                    field: "Comment", title: "Comment", width: 300,
                    hint: "Enter a Comment",
                    validation: { required: true },
                    editor: function (container, options) {
                        $("<textarea class='k-textarea' name='" + options.field + "' required data-bind='value: " + options.field + "'></textarea>")
                            .appendTo(container);
                    },
                }
            ],
            submit: function (e) {
                isFormSubmitted = true;
                e.preventDefault();
                // update the item in the DataSource with the form data
                if (e.model.CommentID == 0) {
                    // the comment has not been saved yet, so find the comment by uid
                    var dateNow = new Date();
                    var formatDate = kendo.toString(kendo.parseDate(dateNow, 'yyyy-MM-dd'), 'M/dd/yyyy');
                    var formatTime = kendo.toString(kendo.parseDate(dateNow, 'h:mm:ss tt'), 'h:mm:ss tt');

                    var commentListData = dataSource.data();
                    for (var i = 0; i < commentListData.length; i++) {
                        if (commentListData[i].uid == e.model.UUID) {
                            // this is our current comment
                        commentListData[i].set("Comment", e.model.Comment);
                        commentListData[i].set("UserID", '@Model.ItemWriterUserID');
                        commentListData[i].set("CommentDateAsString", formatDate);
                        commentListData[i].set("CommentTimeAsString", formatTime);
                            dataSource.sync();
                            break;
                        }
                    }
                } else {
                    // the comment exists with an ID
                    var item = dataSource.get(e.model.CommentID);
                    item.set("Comment", e.model.Comment);
                }
                formWindow.close();
            }
        });

        // create the Form editor as a Kendo Window
        var formWindow = $("#cform-editor-container").kendoWindow({
            title: "Comment",
            visible: false,
            modal: true,
            close: function (e) {
                if (!isFormSubmitted) {
                    try {
                        $("#listView").data("kendoListView").cancel();
                    } catch (error) {
                        console.log('canceling edit with error: ' + error);
                    }
                }
                isFormSubmitted = false; // reset the flag each time the event triggers.
            }
        }).data("kendoWindow");

    });

</script>

In the close function, the try/catch will throw this error when trying to cancel the popup window:

Error handling: message=Uncaught TypeError: Cannot read properties of undefined (reading 'uid') uri=http://localhost:53251/Scripts/kendo/2025.1.227/kendo.all.min.js lineNumber=9 columnNumber=138177

Thanks for any help,

Bob

 

 

 

Mihaela
Telerik team
commented on 15 May 2025, 11:25 AM

Hi Bob,

Thank you for the provided details and code snippets.

I examined them, and I noticed that the unique Model identifier that is specified in the DataSource of the ListView is "CommentDate". As a result, the DataSource uses this field as a Model identifier. 

Would you upadte the Model Id to "CommentID". This way, when adding a new item, the " if (e.model.CommentID == 0) { .. }" logic will execute within the "submit" event handler.

Best,
Mihaela

Bob
Top achievements
Rank 3
Iron
Iron
Veteran
commented on 15 May 2025, 12:14 PM

Hi Mihaela,

I apologize, the model id is set to CommentID.  I was trying different things to try to work around the exceptions, so when I grabbed the code snippet, it was changed to CommentDate as a test, but it made no difference.  I still get exceptions with either value when trying to dismiss the form and calling the cancel.

The exception is thrown in kendo code here (item is undefined):

        function indexOfPristineModel(data, model) {
            if (model) {
                return indexOf(data, function(item) {
                    return (item.uid && item.uid == model.uid) || (item[model.idField] === model.id && model.id !== model._defaultId);
                });
            }
            return -1;
        }

The stack trace is

  StackTrace:
  at http://localhost:53251/Scripts/kendo/2025.1.227/kendo.all.min.js:9:138177
    at $e (http://localhost:53251/Scripts/kendo/2025.1.227/kendo.all.min.js:9:138374)
    at Ve (http://localhost:53251/Scripts/kendo/2025.1.227/kendo.all.min.js:9:138150)
    at http://localhost:53251/Scripts/kendo/2025.1.227/kendo.all.min.js:9:148251
    at init._eachItem (http://localhost:53251/Scripts/kendo/2025.1.227/kendo.all.min.js:9:148157)
    at init._eachPristineItem (http://localhost:53251/Scripts/kendo/2025.1.227/kendo.all.min.js:9:147976)
    at init._pristineForModel (http://localhost:53251/Scripts/kendo/2025.1.227/kendo.all.min.js:9:148214)
    at init._cancelModel (http://localhost:53251/Scripts/kendo/2025.1.227/kendo.all.min.js:9:148329)
    at init.cancelChanges (http://localhost:53251/Scripts/kendo/2025.1.227/kendo.all.min.js:9:146188)
    at init.cancel (http://localhost:53251/Scripts/kendo/2025.1.227/kendo.all.min.js:9:2074445)

Once this exception is thrown, the control becomes unusable and the entire page must be refreshed to get it working again.

Mihaela
Telerik team
commented on 20 May 2025, 11:43 AM

Hi Bob,

Since I cannot replicate the issue you are experiencing, I have prepared a runnable ASP.NET MVC application based on your code snippets. It appears that the CRUD operations work as expected in the attached sample app.

Would you please modify the attached project to reproduce the issue and send it back in the thread for review?

Thank you for your cooperation.

Best,

Mihaela

Bob
Top achievements
Rank 3
Iron
Iron
Veteran
commented on 20 May 2025, 02:22 PM

Hi Mihaela,

Thank you for the application.  I have modified this application to produce the error.  When you run the application, please follow these steps:

  1. Click + to add a new comment.
  2. Enter text for the comment and Submit.
  3. Click + to add a second new comment.
  4. Close the popup with the X and do not submit.  The error should happen.

You may have to try a few times adding comments and cancelling the popup, but it should produce the error.

Let me know if I can provide any more information.

Thanks, Bob

Bob
Top achievements
Rank 3
Iron
Iron
Veteran
commented on 20 May 2025, 02:42 PM

Hi Mihaela,

After I submitted the previous comment, I believe that I have found the issue.  I was removing the Create, Update, and Destroy actions on the ListView because I did not want to post anything where the parent item reference was going to be null, but I see that these appear to be required even if not used to persist the comment to the database.  I believe I have to create some type of dummy actions that do nothing.  Does that sound correct?

Thanks, Bob

Mihaela
Telerik team
commented on 23 May 2025, 11:26 AM

Hi Bob,

Thank you for sharing the updated sample that replicates the issue. I have managed to resolve it by applying the following changes:

  • Instead of looping through the DataSource data in the "submit" event handler, when adding a new comment, use the insert() method of the DataSource and then call sync(). Also, do not save the data item with "CommentID" that equals "0". Otherwise, the DataSource will recognize the data item as a new one. Set a unique value to the "CommentID".
submit: function (e) {
    isFormSubmitted = true;
    e.preventDefault();
    if (e.model.CommentID == 0) { // Adding a new comment
        var dateNow = new Date();
        var formatDate = kendo.toString(kendo.parseDate(dateNow, 'yyyy-MM-dd'), 'M/dd/yyyy');
        var formatTime = kendo.toString(kendo.parseDate(dateNow, 'h:mm:ss tt'), 'h:mm:ss tt');

        var commentListData = dataSource.data();
        var dataItem = dataSource.get(e.model.CommentID); // find the data item using the "CommentID" field, which equals "0"
        if (dataItem) {
            dataSource.remove(dataItem); // remove it from the DataSource
        }
        dataSource.insert(0, { // Insert the new item manually
            CommentID: commentListData.length + 1, // add a unique value for the new data item
            Comment: e.model.Comment,
            UserID: "123",
            CommentDateAsString: formatDate,
            CommentTimeAsString: formatTime
        });
        dataSource.sync(); // sync the changes
    } else {
        // the comment exists with an ID
        var item = dataSource.get(e.model.CommentID);
        item.set("Comment", e.model.Comment);
        dataSource.sync();
    }
    formWindow.close();
}

Regarding the DataSource configuration, if you do not want to handle the Update/Create/Delete operations on the server, it is not needed to configure them at all:

@(Html.Kendo().ListView<TelerikMvcApp46.Models.ItemComment>()
                .Name("commentsListNewItem")
                .TagName("div")
                .ClientTemplateId("commentListTemplate")
                .Editable()
                .DataSource(dataSource => dataSource
                .Model(model => model.Id(comment => comment.CommentID))
                .Read(read => read.Action("Comments_Read", "Comment"))
                )
                .Events(e => e.Edit("onCommentsListEdit"))
                .Events(e => e.DataBound("onCommentsListDataBound"))
                .Events(e => e.Remove("deleteConfirmation"))
            )

Best,
Mihaela

Tags
ListView
Asked by
Bob
Top achievements
Rank 3
Iron
Iron
Veteran
Answers by
Eyup
Telerik team
Share this question
or