Dynamically add and remove items with Form, can it be done?

1 Answer 3251 Views
Form
TheDev
Top achievements
Rank 1
TheDev asked on 25 Apr 2022, 03:54 AM
I wonder if the Form items can be added and removed dynamically in razor pages. Please take a look at the demo of Form from DevExpress at https://demos.devexpress.com/ASPNetCore/Demo/Form/UpdateItemsDynamically/. As you can see, the phone entry can be added and removed dynamically. I would like to leverage Telerik's Form similar to that demo. Looking at the DevExpress demo source, I don't think it would not be difficult with Telerik's Form, but I can't find any example for that. I truly appreciate any advice you can provide.

1 Answer, 1 is accepted

Sort by
1
Accepted
Mihaela
Telerik team
answered on 27 Apr 2022, 04:29 PM

Hello Tu Tran,

Indeed, this functionality can be achieved by using the Telerik UI Form as per the following REPL example:

https://netcorerepl.telerik.com/cmkewrbA19zwm9hG43

In addition, this feature is already logged in our Feedback Portal. You can follow the progress here (feel free to cast your vote and follow it to receive status updates):

https://feedback.telerik.com/kendo-jquery-ui/1509812-add-the-ability-to-dynamically-insert-delete-fields-in-the-kendoform-via-buttons

If you have any questions, don't hesitate to let me know.

 

Regards, Mihaela Progress Telerik

Virtual Classroom, the free self-paced technical training that gets you up to speed with Telerik and Kendo UI products quickly just got a fresh new look + new and improved content including a brand new Blazor course! Check it out at https://learn.telerik.com/.

TheDev
Top achievements
Rank 1
commented on 27 Apr 2022, 05:33 PM | edited

Hi Mihaela,

Thank you very much for sharing your demonstration.

I see the concept of implementing a dynamic field with Telerik Form. I will try to see if I can extend your code to have multiple fields can be added/removed at the same time instead of one field. For instance, I want to dynamically add the fields: First Name, Last Name, phone, and address (group together) simultaneously.

In fact, Telerik should have made a knowledge base for dynamically (add/remove) generating form fields available. This is not only showing how powerful Telerik UI controls can be leveraged, but it also helps developers find the actual implementation faster. This should be standard for most web applications. I know it is logic and how to implement it, but it does save the developers some time to research and familiar all Telerik APIs.

I truly appreciate your time and assistance.

Mihaela
Telerik team
commented on 29 Apr 2022, 01:39 PM

Hi Tu Tran,

Thank you for your feedback. I am glad that the example is helpful.

I completely agree that this example is suitable for a KB article. I will create an article and share it here once it is published.

If you need further assistance with the dynamic form fields, feel free to let me know.

 

TheDev
Top achievements
Rank 1
commented on 03 May 2022, 04:14 PM | edited

Hi Mihaela,

Your demonstration is great. I hope you can extend it to add/remove multiple form fields at once for the knowledge. I have been working on the expansion for adding and removing multiples fields. I was able to add three fields at the same time using "maskedtextbox", but I have not been able to get remove three fields at the same time. I wonder if there is a way to inject a div tag with unique ID as a wrapper to wrap three fields (e.g. first name, last name, address). This is probably easier to keep reference of the row without depending on "maskedtextbox". Any suggestion for me to accomplish multiple entries? Also, I want to implement one field as a Combobox (custom editor), I have a difficulty to convert the format of the code below to the inside of Push( ..... ) function. 


i.Add()
                        .Field(f => f.ComboBox)
                        .Label(l => l.Text("ComboBox:").Optional(true))
                        .Editor(e =>
                        {
                            e.ComboBox()
                                .HtmlAttributes(new { })
                                .Placeholder("Select...")
                                .DataTextField("ProductName")
                                .DataValueField("ProductID")
                                .HtmlAttributes(new { style = "width:100%" })
                                .Height(520)
                                .DataSource(source =>
                                {
                                    source.Read(read =>
                                    {
                                        read.Action("Items_GetProducts", "Form");
                                    })
                                    .ServerFiltering(true);
                                });
                        });

AND a DropDownList format inside Push(........)


i.Add()
                            .Field(f => f.AppoinmentType)
                            .Label(l => l.Text("Appointment Type"))
                            .ColSpan(1)
                            .Editor(e =>
                            {
                                e.DropDownList()
                                .DataTextField("Text")
                                .DataValueField("Value")
                                .OptionLabel("Select Appointment Type")
                                .BindTo(new List<SelectListItem>() 
                                    {
                                        new SelectListItem() {
                                            Text = "Annual", Value = "1"
                                        },
                                        new SelectListItem() {
                                            Text = "Fall/Spring", Value = "2"
                                        },
                                        new SelectListItem() {
                                            Text = "Summer", Value = "3"
                                        },
                                        new SelectListItem() {
                                            Text = "May", Value = "4"
                                        }
                                });
                            });

Are there any examples that I can follow? I can reply on a JavaScript or JQuery to generate a dynamically add/remove form entries. However, I want to take potential/advanced built-in features with Telerik's form.

Again, I truly appreciate your insights and time.
Mihaela
Telerik team
commented on 06 May 2022, 11:59 AM

Hello Tu Tran,

Basically, to add/remove multiple Form fields simultaneously you can follow the same approach from illustrated in the REPL example. For example, to remove multiple fields when a button is clicked:

  • Handle the "click" event of the button;
  • Get the "formData" and "items" objects through the Form options ($("#exampleForm").data("kendoForm").options);
  • Update the "formData" and "items" objects by removing the required fields:
var formComponent = $("#exampleForm").data("kendoForm");
var formData = formComponent.options.formData;
for(var i = 2; i < formData.Phones.length; i++) { //loop through the available Phones fields and remove all accept the Phone 1 and Phone 2
  formData.Phones.splice(i, 1);
}
  • Populate the new Form items by adding the appropriate editor and its options (i.e., insert "TextBox" for the FirstName, "MaskedTextBox" for the Phone and so on).  You can review the available Form editors here;
  • Use the setOptions() method to apply the Form changes.

Alternatively, you could add/remove a group of multiple fields by adding/removing an object in the "items" object:

var formComponent = $("#exampleForm").data("kendoForm");
var formItems = formComponent.options.items; //returns an array of objects
var newItem = {
    "type": "group",
    "label": {
        "text": "New Group of Items"
    },
    "items": [
        {
            "field": "FirstName2",
            "label": {
                "text": "First Name 2"
            },
            "validation": {},
            "shouldRenderHidden": true
        },
        {
            "field": "LastName2",
            "label": {
                "text": "Last Name 2"
            },
            "validation": {},
            "shouldRenderHidden": true
        },
        {
            "field": "Address2",
            "editor": "TextBox",
            "shouldRenderHidden": true
        }
    ]
};

formComponent.options.items.push(newItem); //add a new group of fields. Ensure that the names of the Model properties are unique
formComponent.setOptions({ items: formComponent.options.items }); //Update the Form items options

Here you can review the format of the Form items object:

Regarding the ComboBox editor, it can be inserted in the Form items as follows:

var newItem = {
  "field": "Country1",
  "editor": "ComboBox",
  "editorOptions": { 
    placeholder: "Select...",
    dataTextField: "ProductName",
    dataValueField: "ProductID",
    height: 520,
    dataSource: dataSource
  },
  "label": {"text": "Country"},
 "shouldRenderHidden": false
};

var dataSource = new kendo.data.DataSource({
  serverFiltering: true,
  transport: {
    read: {
      url: '@Url.Action("Items_GetProducts", "Form")',
      dataType: "json"
    }
  }
});

$("#exampleForm").data("kendoForm").options.items.push(newItem);

In terms of the DropDownList, check out this example: https://docs.telerik.com/kendo-ui/api/javascript/ui/form/configuration/items#itemseditoroptions

I hope these suggestions will be helpful to your scenario.

TheDev
Top achievements
Rank 1
commented on 08 May 2022, 03:57 AM | edited

Hi Mihaela,

Thank you very much your patience, suggestions, and time.

I have followed your example from
REPL, and I think I am almost getting close to what I need. I have not tried your alternative approach to add an object in "items" object.

As I follow the first approach by adding individual field. Adding the fields is not an issue for me, but when I try click on Remove, multiple rows are removed. I understand that I have to remove each individual field using splice and push functions. The issue I may have to keep track the index of row of the items. As you see in the screenshot (attached in this comment), if I click one of remove buttons (red arrow), all rows will be removed except the first row. It is supposed to remove the row when I click on the remove button. I will include my remove function here. Hopefully, you can point out what I am doing wrong.

The second issue is that every time I add a new field, the data of previous fields are refreshed and clear (empty). In other words, the data are not persisted (stay) when adding new fields. I have done extensive research and reading on Telerik's forum, I can't find a solution for it. The example from REPL does not persist data as well. In a traditional Web forms, it looks like post back and loss the data and need to rebind. How would I prevent this issue?

Your suggestions and example are very helpful, and I am learning Telerik's components. When you have a moment, could you please give me your advice? Thank you very much for your continued support.

 


function onRemoveBtnClick(e) {
        
        var formComponent = $("#exampleForm").data("kendoForm");
        var formData = formComponent.options.formData;

        var speedTypeItems = formComponent.options.items[1].items;
        AccountSpeedType.splice(speedTypeId, 1);
        speedTypeItems.splice(speedTypeId, 1);
        var speedTypeLength =  formData.AccountSpeedType.length;
        var newSpeedTypeItems = [];        
        for(var i = 0; i < formData.AccountSpeedType.length; i++)
        {
            
            newSpeedTypeItems.push({
            "field": `AccountSpeedType${i}`,
            "editor": "ComboBox",
            "editorOptions": {
                                "autoBind":false,
                                "dataTextField":"AccountSpeedType",
                                "dataValueField":"AccountSpeedType",
                                "height":520,
                                "placeholder":"Select Speedtype",
                                "filter":"contains",
                                "dataSource":
                                {
                                    "transport":
                                    {
                                        "read":
                                        {
                                            "url":"/request/?handler=Read",
                                            "data": dataFunction
                                        },
                                            "prefix":""
                                    },
                                    "serverFiltering":true,
                                    "filter":[],
                                    "schema":
                                    {
                                        "errors":"Errors"
                                    }
                               }
                            }, // END OF COMBOBOX EDITOR OPTION
                
            "label": {
                "text": `Select Speedtype ${i + 1}`
            },
            "shouldRenderHidden": false
           });

            newSpeedTypeItems.push({
                "field": `ProjectStartDate[${i}]`,
                "editor": "DatePicker",
                "label": {
                    "text": `Funding Start Date ${i + 1}`
                },
                "shouldRenderHidden": false
            });

            newSpeedTypeItems.push({
                "field": `ProjectEndDate[${i}]`,
                "editor": "DatePicker",
                "label": {
                    "text": `Funding End Date ${i + 1}`
                },
                "shouldRenderHidden": false
            });
       

            newSpeedTypeItems.push({
                "field": `PayAmountPerSpeedtype[${i}]`,
                "editor": "NumericTextBox",
                "editorOptions": {
                    "Format": "c",
                    "spinners": false,
                },
                "label": {
                    "text": `Pay Amount ${i + 1}`
                },
                "shouldRenderHidden": false
            });
        } // FOR Loop

        formComponent.options.items[1].items = newSpeedTypeItems;
        formComponent.setOptions({
            formData: formData,
            items: formComponent.options.items
        });
         
        insertRemovePhoneButton($('[data-role="numerictextbox"]'));

    }

Mihaela
Telerik team
commented on 11 May 2022, 07:36 PM

Hi Tu Tran,

Your feedback is much appreciated!

1) When creating the "remove" button for each input, assign an id to each button (i.e., "removeSpeedTypeBtn_{index}"). Then you can get the index of the element that should be removed in the "click" event handler of the button:

function onRemoveBtnClick(e) {
       var speedTypeId = $(e.sender.element).attr("id").split("_")[1]; //get the id of the clicked icon
        var formComponent = $("#exampleForm").data("kendoForm");
        var formData = formComponent.options.formData;
        var speedTypeItems = formComponent.options.items[1].items;
        formData.speedType.splice(speedTypeId, 1); //ensure that the removed item is removed from the formData object
        speedTypeItems.splice(speedTypeId, 1);
       ....
}

    function insertRemovePhoneButton(fields) {
        $.each($(fields), function(i,v){
            var deletePhoneBtn = `<em id="removePhoneBtn_${i}"></em>`; //set an id attribute to each remove button
            $(deletePhoneBtn).insertAfter($(this));
            $(`#removePhoneBtn_${i}`).kendoButton({
                icon: "trash",
                click: onRemoveBtnClick
            });
        });
    }

 

2) To persist the form data, you can access the data through the form model and update the formData object when adding/removing items. For example:

 

function onRemoveBtnClick(e) {
        var phoneId = $(e.sender.element).attr("id").split("_")[1];
        var formComponent = $("#exampleForm").data("kendoForm");
        var formData = formComponent.options.formData;
        var phoneItems = formComponent.options.items[1].items;
        var currnetPhonesData = formComponent._model.Phones; //get the current entered phones
        formData.Phones.splice(phoneId, 1);
        phoneItems.splice(phoneId, 1);
        currnetPhonesData.splice(phoneId, 1);   
        ...
        for(var i = 0; i < formData.Phones.length; i++) { //loop through the formData Phones and set their values
            formData.Phones[i] = currnetPhonesData[i];
        }
        ...
    }

    function onAddPhone(e) {
        var formComponent = $("#exampleForm").data("kendoForm");
        var formData = formComponent.options.formData;
        var phoneItems = formComponent.options.items[1].items;
        var currnetPhonesData = formComponent._model.Phones;
        var totalPhones = phoneItems.length;
        formData.Phones.push("");
        ...

        for(var i = 0; i < formData.Phones.length; i++) {
            if(currnetPhonesData[i] != formData.Phones[i]) {
                formData.Phones[i] = currnetPhonesData[i];
            }
        }
        ....
    }

Here is the revised REPL example for your reference: 

https://netcorerepl.telerik.com/cmkpPvlX28qLVQka29

I hope these suggestions will be helpful.

Dan
Top achievements
Rank 1
Veteran
commented on 11 Aug 2022, 05:51 PM

In the example you show/hide an entire group using "slice(-1)".  Can you provide a link to explain this method?  it appears you are just showing/hiding the "last" group on the form, but I don't know if that is just a coincidence.  Can it be used to show/hide any group on the form?
Mihaela
Telerik team
commented on 16 Aug 2022, 09:06 AM

Your assumption is correct - $(".k-form-fieldset").slice(-1) selects the last fieldset element (class "k-form-fieldset").

For more information about the jQuery slice() method, refer to the jQuery API:

https://api.jquery.com/slice/

For example, if you would like to show the second Form group:

$(".k-form-fieldset").slice(1,2).show();

 

Brian
Top achievements
Rank 2
Iron
commented on 23 Sep 2022, 11:30 AM

Hi,

Apologies for the necropost, but I'm trying to get this exact solution to work using TypeScript and the Kendo TS definitions.

var currentAnalystData = formComponent._model.SmeIds;

The TypeScript definitions show `_model` as undefined and won't render to JavaScript.

Any ideas on what needs to be done to correct this?

Thanks!

Mihaela
Telerik team
commented on 27 Sep 2022, 06:37 AM

Hi Brian,

You could get the "_model" information from the "formData" object:

var currnetPhonesData = formComponent?.options.formData?.Phones;

Brian
Top achievements
Rank 2
Iron
commented on 28 Sep 2022, 02:49 PM

Oh yeah, that's true!  Thanks much!
Tags
Form
Asked by
TheDev
Top achievements
Rank 1
Answers by
Mihaela
Telerik team
Share this question
or