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
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
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
Hi Petar,
do you have any idea what I am doing incorrectly?
cheers
Ursus
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
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
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
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);
}
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
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