MVC AutoComplete Editor To use Html.BeginCollectionItem

I am trying to create an autocomplete text box using EditorTemplate . The problem I am facing is that using the extension solution Html.BeginCollectionItem() ( https://www.nuget.org/packages/BeginCollectionItem/ ) the EditorFor() identifier and TextBoxFor() methods are set dynamically, and this breaks my javascript. Next to this, I don’t know for sure whether this is possible (and if so, how. Below you will find how far I have come).

In the main view, I have a loop to create a partial view for each item in the collection

 for (int i = 0; i < Model.VoedingCollection.Count; i++) { @Html.EditorFor(x => x.VoedingCollection[i], "CreateVoedingTemplate") } 

The partial view of CreateVoedingTemplate.cshtml uses the Html.BeginCollectionItem() method

 @using (Html.BeginCollectionItem("VoedingCollection")) { string uniqueId = ViewData.TemplateInfo.HtmlFieldPrefix.Replace('[', '_').Replace(']', '_').ToString(); string searchId = "Search_"; string standaardVoedingId = "StandaardVoeding_"; foreach (KeyValuePair<string, object> item in ViewData) { if (item.Key == "Count") { searchId = searchId + item.Value.ToString(); standaardVoedingId = standaardVoedingId + item.Value.ToString(); } } <div class="form-horizontal"> <div class="form-group" id=@standaardVoedingId > @Html.LabelFor(model => model.fk_standaardVoedingId, "Voeding naam", htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.HiddenFor(model => model.fk_standaardVoedingId) <input type="text" id='@searchId' placeholder="Search for a product"/> </div> </div> </div> <script type="text/javascript"> var id = '@uniqueId' + '_fk_standaardVoedingId'.toString(); var search = '@searchId'.toString(); var url = '@Url.RouteUrl("DefaultApi", new { httproute = "", controller = "AgendaApi" })'; $(document.getElementById(search)).autocomplete({ source: function (request, response) { $.ajax({ url: url, data: { query: request.term }, dataType: 'json', type: 'GET', success: function (data) { response($.map(data, function (item) { return { label: item.standaardVoedingNaam, value: item.standaardVoedingId } })); } }) }, select: function (event, ui) { $(document.getElementById(search)).val(ui.item.label); //$('#id').val(ui.item.value); document.getElementById(id).value = ui.item.value; return false; }, minLength: 1 }); </script> } <link href="~/Content/SearchBox/jquery-ui.css" rel="stylesheet" /> <script src="~/Scripts/SearchBox/jquery-1.9.1.js"></script> <script src="~/Scripts/SearchBox/jquery-ui.js"></script> 

In the script above, I am trying to create a function in which the user can enter the name of the standaardVoeding element, and then get the results, where after the user standaardVoeding standaardVoedingId element standaardVoeding standaardVoedingId property gets set. Then, after submitting the entire form, the controller receives standaardVoedingId (with all other information)

So, I think that Javascript cannot somehow handle Razor View @ code, and next to it Html.BeginCollectionItem does something suspicious because you cannot set the value of your text fields through the code at runtime. Next to this, I tried making alert(document.getElementById(*html.begincollectionitemId*)) and it finds the fields perfectly. But apparently all other methods do not work?

Perhaps the best solution for this?

+5
source share
1 answer

The BeginCollectionItem() method changes the id and name html attributes generated by the built-in helpers, in your case for hidden input, instead

 <input ... name="fk_standaardVoedingId" .... /> 

he will generate

 <input ... name="VoedingCollection[xxxx].fk_standaardVoedingId" .... /> 

where xxxx is a Guid .

Although one could use javascript to extract the Guid value from the text field (provided that it was correctly created by usind @Html.TextBoxFor() ) and build the identifier of the associated hidden input for use as a selector, it is far simpler to use class names and relative selectors.

You also need to remove your scripts and css from the partial and place them in the main view (or its layout). In addition to the built-in scripts, their duplication for each element in your collection.

Your part should be

 @using (Html.BeginCollectionItem("VoedingCollection")) { <div class="form-horizontal"> <div class="form-group"> @Html.LabelFor(model => model.fk_standaardVoedingId, "Voeding naam", htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10 item"> // add class name @Html.HiddenFor(model => model.fk_standaardVoedingId) <input type="text" class="search" placeholder="Search for a product"/> </div> </div> </div> } 

Notice the class name for the text field and its container, which also includes hidden input. Then in the main view the script will be

 <script type="text/javascript"> var url = '@Url.RouteUrl("DefaultApi", new { httproute = "", controller = "AgendaApi" })'; // Attach the script to all textboxes $('.search').autocomplete({ source: function (request, response) { $.ajax({ url: url, data: { query: request.term }, dataType: 'json', type: 'GET', success: function (data) { response($.map(data, function (item) { return { label: item.standaardVoedingNaam, value: item.standaardVoedingNaam, // this needs to be the name id: item.standaardVoedingId // add property for the id } })); } }) }, select: function (event, ui) { // Get the associated hidden input var input = $(this).closest('.item').find('input[type="hidden"]'); // Set the value of the id property input.val(ui.item.id); }, minLength: 1 }); </script> 

Based on your comments that you are not dynamically adding or removing elements in the view, then there is no unnecessary overhead or using the BeginCollectionItem() method. Change the partial name to standaardvoeding.cshtml (assuming the class name) and move it to the /Views/Shared/EditorTemplates .

Then in the main view, replace the for loop with

 @Html.EditorFor(m => m.VoedingCollection) 

which will generate the correct html for each item in the collection. Finally, remove the BeginCollectionItem() method from the template so that it is just

 <div class="form-horizontal"> <div class="form-group"> @Html.LabelFor(m => m.fk_standaardVoedingId, "Voeding naam", htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10 item"> // add class name @Html.HiddenFor(m => m.fk_standaardVoedingId) <input type="text" class="search" placeholder="Search for a product"/> </div> </div> </div> 
+1
source

All Articles