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

Cloning fields but script not being cloned

9 Answers 689 Views
DropDownList
This is a migrated thread and some comments may be shown as answers.
Ursus
Top achievements
Rank 1
Iron
Ursus asked on 04 Nov 2019, 05:58 PM

I have a MVC .NET application. I have a dropdown list that does backend calls to populate it. I want to copy the field and everything seems to work properly... except the searching, the search value that is searched for is from the original field and not the field I am entering the search into. I am assuming that I have missed something when copying the field.  

First I copy the field:

var fieldToCopy = currentRow.find("#" + oldFieldId);
fieldToCopy.clone()
       .attr("id", newFieldId)
       .attr("name", newFieldName)
       .appendTo('#' + fieldId);

 

then I get the field I created:

var newField = $(`#${newFieldId}`);
if (!newField.length) return;

 

I then init the new field:

var originalWidgetType = "kendo" + window.kendo.widgetInstance(fieldToCopy).options.name;
var originalWidget = fieldToCopy.data(originalWidgetType);
newField[originalWidgetType](originalWidget.options);

 

This gets me 90% of the way. I have a working dropdown. The only problem is that although I have a cloned field, searching does not work. I always get the search value from the field I copied. 

When I check the HTML I see that the <script> which is in the original code but missing in the cloned code. How do I properly initialise a cloned field?

This is the stuff that is missing after the clone.

kendo.syncReady(function(){jQuery("#Fields_21__Values_0_").kendoDropDownList({"minLength":3,"value":"100015026","filter":"contains","dataSource":{"transport":{"read":{"url":"/d3Search/Shared/Document/TypeAhead?docId=GD00000148\u0026docType=DEPSU\u0026fieldNumber=61\u0026row=0\u0026currentValue=100015026","data":function() { return kendo.ui.DropDownList.requestData(jQuery("#Fields_21__Values_0_")); }},"prefix":""},"serverFiltering":true,"filter":[],"schema":{"errors":"Errors"}}});});

 

Hopefully somebody can help.

 

 

9 Answers, 1 is accepted

Sort by
0
Petar
Telerik team
answered on 06 Nov 2019, 01:03 PM

Hi Ursus,

Based on the provided information, I would assume that the reason for the reported behavior is that the cloned DropDownList is not initialized using .kendoDropDownList() and this is why the jQuery script related to the cloned component is missing.

The jQuery clone method makes a deep clone of the DropDownList and its configuration but this doesn't initialize a new component. 

Check this Dojo example. It demonstrates how we clone a DropDownList but to have it fully operational the cloned component is being initialized.

Regards,
Petar
Progress Telerik

Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
0
Ursus
Top achievements
Rank 1
Iron
answered on 06 Nov 2019, 01:25 PM

Hi

thank you for answering. I thought that I was doing exactly that though. As I do not know what type of field is being copied I need to create the method name interactively. 

1. get the element type:

var originalWidgetType = "kendo" + window.kendo.widgetInstance(fieldToCopy).options.name;

 

2. Then get the data for that data for the current element:

var originalWidget = fieldToCopy.data(originalWidgetType);

 

3. call the initialise function:

newField[originalWidgetType](originalWidget.options);

 

as I was wondering if perhaps the problem is that the .options have links back to the original element? I was thinking that maybe the name of the field that is being used to pass the value to the search is in the options, could that be? Is there a description of the options for the initialise function somewhere?

 

Cheers 

Ursus

0
Ursus
Top achievements
Rank 1
Iron
answered on 08 Nov 2019, 09:45 AM

Hi Petar,

do you have any idea what I am doing incorrectly?

cheers 

Ursus

0
Petar
Telerik team
answered on 08 Nov 2019, 12:40 PM

Hi Ursus,

I've discussed the case internally and we would recommend using the demonstrated in this Dojo example approach. As the number of components you are going to clone is a known number you can create an if-else construction or use a switch statement to execute the needed method based on the type of the original component. Here is the function that realizes the target functionality:

 function getNewField(widgetType, widget, elementId){ 
                    
                    if(widgetType === "kendoDropDownList") {
                      $(elementId).kendoDropDownList(widget.options);
                    }else {
                      //..................
                    }
                                         
                  }

I hope the above implementation resolves your issue. If you have questions regarding the proposed solution, please let me know.

Regards,
Petar
Progress Telerik

Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
0
Ursus
Top achievements
Rank 1
Iron
answered on 08 Nov 2019, 01:14 PM

Hi Petar

I have tried to do that but I still have the same problem... Here is my new code

 

// copy the field
  var newField = fieldToCopy
      .clone()
      .attr("id", newFieldId)
      .attr("name", newFieldName);
 
  // add the field to the row
  newRow.find("#" + fieldId).append(newField);
 
  // get the original field type
  var originalWidgetType = window.kendo.widgetInstance(fieldToCopy).options.name;
 
  switch (originalWidgetType) {
      case "DropDownList":
          var clonedDropDownOptions = fieldToCopy.data("kendoDropDownList").options;
          newField.kendoDropDownList(clonedDropDownOptions);
          break;
      case "DatePicker":
          var clonedDateOptions = fieldToCopy.data("kendoDatePicker").options;
          newField.kendoDatePicker(clonedDateOptions);
          break;
      case "DateTimePicker":
          var clonedDateTimeOptions = fieldToCopy.data("kendoDateTimePicker").options;
          newField.kendoDateTimePicker(clonedDateTimeOptions);
          break;
      default:
          break;
  }

 

Any other ideas?

Cheers

Ursus

0
Petar
Telerik team
answered on 08 Nov 2019, 01:24 PM

Hi Urus,

Can you isolate the problem in a Dojo project and send it back to me? G

Having such an example I will be able to directly test the discussed case in an environment where it is reproduced and provide a relevant feedback.  

Looking forward to your reply.

Regards,
Petar
Progress Telerik

Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
0
Ursus
Top achievements
Rank 1
Iron
answered on 10 Nov 2019, 05:03 PM

Hi Petar

sorry but I cannot upload the code as it is an MVC project that relies on a DMS backend. This is also my first big project with KendoUI and JavaScript so am not quite sure how to create a usable DoJo example. I will upload the relevant code snippets here (maybe you can see my error) or I can offer a TeamViewer session?

 

Just a quick recap - the UI is like in the attached UI.PNG file. I press the + button besides line 2, and get a new field, everything seems perfect. I then search for "GmbH" in field 2, and when I search for TUBESHEET in field 3, I get GmbH, just like I searched for in field 2.

If I check what gets passed to the TypeAhead method it is the values for field 2 - I must be doing something wrong when I copy...

 

Thank you for your time

Ursus

 

DropDownList in MVC:

@(Html.Kendo().DropDownList()
      .Name(kendoFieldName)
      .MinLength(3)
      .Value(Model.Fields[i].Values[j])
      .HtmlAttributes(new {style = "width: 100%"})
      .Filter(FilterType.Contains)
      .DataSource(source =>
      {
          source.Read(read =>
          {
              read.Action("TypeAhead", "Document", new
              {
                  docId = Model.DocId,
                  docType = Model.DocType,
                  fieldNumber = Model.Fields[i].FieldIndex,
                  row = j,
                  currentValue = Model.Fields[i].Values[j]
              });
          })
          .ServerFiltering(true);
      })
      )

 

Here the MVC called for the search:

public JsonResult TypeAhead(string docId, string docType, int fieldNumber, int row, string currentValue, string text)
{
    // get the list of predefined values
    var predefinedValues = GetPredefinedValuesForField(docId, docType, fieldNumber, row, text);
 
    // Add the current value to the list of values should it not be there - this may happen if the d.3 hooks run
    if (!predefinedValues.Contains(currentValue))
    {
        predefinedValues.Insert(0, currentValue);
    }
 
    return Json(predefinedValues);
}

 

And here the entire JS code for this page:

//
// Add a new div to the page
//
var rowsDictionary = {};
 
//
//  Add a new row to the multivalue field
//
function addRow(fieldNo, rowNo, maxRowNo) {
 
    // get the current row
    var currentRowId = `row_field_${fieldNo}_value_${rowNo}`;
    const currentRow = $(`#${currentRowId}`);
    if (!currentRow.length) return;
 
    // get the next row number
    var rowsAdded;
    var key = fieldNo;
    if (key in rowsDictionary) {
        rowsAdded = rowsDictionary[key];
    } else {
        rowsAdded = 0;
    }
 
    // create a new, empty row, and add it below the current row
    var newRowNo = maxRowNo + rowsAdded;
    var newRowId = `row_field_${fieldNo}_value_${newRowNo}`;
    $(currentRow).after("<div id=" + newRowId + " class=\"form-group row\"></div>");
 
    // get the row you just created
    var newRow = $(`#${newRowId}`);
    if (!newRow.length) return;
 
    // just some vars to make the code easier to read
    var labelId = `label_${fieldNo}_value_${newRowNo}`;
    var fieldId = `field_${fieldNo}_value_${newRowNo}`;;
    var buttonsId = `buttons_${fieldNo}_value_${newRowNo}`;
    var labelColumn = "<div id=\"" + labelId + "\" class=\"col-sm-3\">";
    var fieldColumn = "<div id=\"" + fieldId + "\" class=\"col-sm-7\">";
    var buttonsColumn = "<div id=\"" + buttonsId + "\" class=\"col-sm-2\">";
 
    // append your columns
    newRow.append(labelColumn);
    newRow.append(fieldColumn);
    newRow.append(buttonsColumn);
 
    // add the new plus and minus button
    var addFunction = `addRow(${fieldNo}, ${newRowNo}, ${maxRowNo})`;
    var removeFunction = `removeRow(${fieldNo}, ${newRowNo})`;
    var plusButton = `<button id="plusButton" type="button" class="btn" onclick="${addFunction};"><i class="fa fa-plus fa-lg"></i></button>`;
    var minusButton = `<button id="minusButton" type="button" class="btn" onclick="${removeFunction};"><i class="fa fa-minus fa-lg"></i></button>`;
    newRow.find("#" + buttonsId).append(plusButton);
    newRow.find("#" + buttonsId).append(minusButton);
 
    // clone the field
    var newFieldId = `Fields_${fieldNo}__Values_${newRowNo}_`;
    var newFieldName = `Fields[${fieldNo}].Values[${newRowNo}]`;
    var oldFieldId = `Fields_${fieldNo}__Values_${rowNo}_`;
    var fieldToCopy = currentRow.find("#" + oldFieldId);
 
    // this is a text field with no Kendo components
    if (fieldToCopy.length === 0) {
        // recreate the input field
        var inputField =
            $("<input>")
                .addClass("form-control")
                .attr(
                    {
                        id: newFieldId,
                        name: newFieldName,
                        type: "text"
                    }
                );
        newRow.find(`#${fieldId}`).append(inputField);
 
        rowsDictionary[key] = rowsAdded + 1;
        return;
    }
 
    // copy the field
    var newField = fieldToCopy
        .clone()
        .attr("id", newFieldId)
        .attr("name", newFieldName);
 
    // attach the field to the row
    newRow.find("#" + fieldId).append(newField);
     
    // get the original field type
    var originalWidgetType = window.kendo.widgetInstance(fieldToCopy).options.name;
 
    switch (originalWidgetType) {
        case "DropDownList":
            var clonedDropDownOptions = fieldToCopy.data("kendoDropDownList").options;
            newField.kendoDropDownList(clonedDropDownOptions);
            break;
        case "DatePicker":
            var clonedDateOptions = fieldToCopy.data("kendoDatePicker").options;
            newField.kendoDatePicker(clonedDateOptions);
            break;
        case "DateTimePicker":
            var clonedDateTimeOptions = fieldToCopy.data("kendoDateTimePicker").options;
            newField.kendoDateTimePicker(clonedDateTimeOptions);
            break;
        default:
            break;
    }
 
    // finished
    rowsDictionary[key] = rowsAdded + 1;
    return;
}
//
// remove a div on the page
//
function removeRow(fieldNo, rowNo) {
 
    // remove the selected row
    var currentRowId = `row_field_${fieldNo}_value_${rowNo}`;
    const currentRow = $(`#${currentRowId}`);
    if (!currentRow.length) return;
 
    // add a hidden input field otherwise the Razor passing does not work
    var fieldId = `Fields_${fieldNo}__Values_${rowNo}_`;
    var fieldName = `Fields[${fieldNo}].Values[${rowNo}]`;
    var hiddenField = `<input type="hidden" id="${fieldId}" name="${fieldName}" value=" " />`;
 
    // ok, find the place to add the hidden field
    const hiddenFieldsDiv = $(`#hiddenFields`);
    if (!hiddenFieldsDiv.length) return;
 
    // remove the current row
    currentRow.remove();
 
    // and append you hidden field
    hiddenFieldsDiv.append(hiddenField);
}

 

 

 

 

0
Petar
Telerik team
answered on 12 Nov 2019, 11:44 AM

Hi Ursus,

Based on the provided code, I would assume that the reason for the reported behavior is that the TypeAhead controller method doesn't have access to the actual "current value" of the component that is calling it. To make my words more clear, please check this Server filtering demo from our demo page. 

As you can see from the two code tabs under the component demo(ServerFilteringController.cs tab), the way we take the current DropDownList value in the controller is by getting the string variable "text" server-side. The way the different variables are passed to the server in the provided code can be kept as it is if it is needed for the application logic. The real current value should be taken from the "'text" parameter, not from the "currentValue". Having my previous words in mind, the modified controller code should look something like:

public JsonResult TypeAhead(string text, string docId, string docType, int fieldNumber, int row, string text)
{
    // get the list of predefined values
    var predefinedValues = GetPredefinedValuesForField(docId, docType, fieldNumber, row, text);
 
    // Add the current value to the list of values should it not be there - this may happen if the d.3 hooks run
    if (!predefinedValues.Contains(text))
    {
        predefinedValues.Insert(0, text);
    }
 
    return Json(predefinedValues);
}

One more thing, I would like to mention is that the DropDownList should have DataTextField and DataValueField fields set in its definition. Even if it looks like it is working correctly without these fields, there is a big chance that some of its functionalities will break.  

Let me know if the proposed above resolves the issue.

Regards,
Petar
Progress Telerik

Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
0
Ursus
Top achievements
Rank 1
Iron
answered on 12 Nov 2019, 12:45 PM

Hi Petar

thank you for your answer. I cannot use the DataTextField and DataValueField fields as I cannot tell the two values apart (long story but sometimes the values have been swapped by the previous programmers) - that is why I did not fill these fields.

 

I have reengineered the code to do a full server round-trip with every update - that solves all my problems.

 

Thank you again and keep up the good work

Cheers

Ursus

Tags
DropDownList
Asked by
Ursus
Top achievements
Rank 1
Iron
Answers by
Petar
Telerik team
Ursus
Top achievements
Rank 1
Iron
Share this question
or