Search code examples
c#ajaxasp.net-mvckendo-asp.net-mvc

PartialView not rendering properly on ajax reload


Im having an issue to reload a partial view using ajax.

I have my country form in a partial view, and initially when i load the main page everything is rendered properly.

As demonstrated in the image below, I can search for country using my autocomplete and i can select a countryfrom my combobox (both are kendo-mvc controntrols)

enter image description here

The issue emerges when i select a from my autocomplete and try to load the info of the selected country via ajax. The form is reloaded, the info is displayed but the controls are not being rendered properly. The autocomplete stops working and the combobox is rendered as a normal textbox, displaying the CountryID instead of the Country name.

enter image description here

Code in my view

@using (Html.BeginForm("Index", "CountryManagement", FormMethod.Post, new { id = "country-form" }))
{  
    @Html.ValidationSummary(true);
    <div id="form-content">
         @Html.Partial("_CountryForm", Model)
    </div>

}

Code in my partialview

<div class="form-group">
        @Html.Label("Search Country", htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-3">
            @(Html.Kendo().AutoCompleteFor(m => m.Search)
                          .DataTextField("Designation")
                          //.DataValueField("CountryID")
                          .Filter("contains")
                          .MinLength(2)
                          .DataSource(d => { d.Read(r => r.Action("SearchCountry", "Common")); })
                          .Events(e => e.Change("onChange")).Deferred()
            )
        </div>
    </div>

Code in my controller

[HttpPost]
public PartialViewResult Edit(int id)
{
    //HTTP GET: api/Country/CountryDetails?id={id}&lang={lang}
    dynamic model =  //code to get selcted country data
    return PartialView("_CountryForm", model);
}

Code in my .js file

function onChange() {
    
    if ($("#Search").data("handler")) {
        var data = $("#Search").data("handler").dataSource.data();
        var country = data.find(x => x.Designation == $("#Search").val());
        console.log("Country")
        if (country) {
            var request = $.post('/CountryManagement/Edit', { id: country.CountryID });
            request.success(function (data) {
                $("#form-content").html(data);
            });
        }
    }
}

HTML code generated on page load (autocomplete and dropdown only)

<div class="form-group">
    <label class="control-label col-md-2" for="Search_Country">Search Country</label>
    <div class="col-md-3">
        <span tabindex="-1" role="presentation" class="k-widget k-autocomplete k-header k-state-default k-state-hover"><input data-val="true" data-val-length="Description  must have between 1  and 150 characters" data-val-length-max="150" data-val-length-min="1" id="Search" name="Search" type="text" data-role="autocomplete" class="k-input" autocomplete="off" role="textbox" aria-haspopup="true" aria-disabled="false" aria-readonly="false" aria-owns="Search_listbox" aria-autocomplete="list" has-focus="false" style="width: 100%;"><span class="k-icon k-loading" style="display:none"></span></span>
    </div>
</div>
<br>
<br>
<div class="form-group">
    <label class="control-label col-md-2" for="Country">Country</label>
    <div class="col-md-3">
       <span class="k-widget k-combobox k-header"><span tabindex="-1" unselectable="on" class="k-dropdown-wrap k-state-default"><input name="CountryID_input" class="k-input" type="text" autocomplete="off" title="" role="combobox" aria-expanded="false" tabindex="0" aria-disabled="false" aria-readonly="false" aria-autocomplete="list" aria-owns="CountryID_listbox" has-focus="false" style="width: 100%;"><span tabindex="-1" unselectable="on" class="k-select"><span unselectable="on" class="k-icon k-i-arrow-s" role="button" tabindex="-1" aria-controls="CountryID_listbox">select</span></span></span><input data-val="true" data-val-number="The field CountryID must be a number." id="CountryID" name="CountryID" type="text" value="0" data-role="combobox" aria-disabled="false" aria-readonly="false" has-focus="false" style="display: none;"></span>
        <span class="field-validation-valid text-danger" data-valmsg-for="CountryID" data-valmsg-replace="true"></span>
    </div>
</div>

HTML code generated on partialview reload (autocomplete and dropdown only)

<div class="form-group">
    <label class="control-label col-md-2" for="Search_Country">Search Country</label>
    <div class="col-md-3">
        <input data-val="true" data-val-length="Description  must have between 1  and 150 characters" data-val-length-max="150" data-val-length-min="1" id="Search" name="Search" type="text" value="South Africa">
    </div>
</div>
<br>
<br>
<div class="form-group">
    <label class="control-label col-md-2" for="Country">Country</label>
    <div class="col-md-3">
       <input data-val="true" data-val-number="The field CountryID must be a number." id="CountryID" name="CountryID" type="text" value="1003">
        <span class="field-validation-valid text-danger" data-valmsg-for="CountryID" data-valmsg-replace="true"></span>
    </div>
</div>

I'm not sure if this is an issue with ASP.Net MVC partialviews, Kendo controls or some scripts that should be re-run when the partialview reloads. Can someone please help me?


Solution

  • After numerous failed attempts and fruitsless digging in the deep end of Telerik forums, i finally found out what the problem is.

    Kendo widgets in partial views should not use deferred initialization.

    @(Html.Kendo().AutoCompleteFor(m => m.Search)
                          ///.. code removed to reduce complexity
                          .Events(e => e.Change("onChange")).Deferred()
     )
    

    When the view is loaded through as server request, it renders properly because in the end of the layout file, all the widgets with deferred initialization are initialized using @Html.Kendo().DeferredScripts(). However when the partial view is reloaded via ajax, the deferred scripts initialization never happens because the scripts are not re-run.

    Adding @Html.Kendo().DeferredScripts() to the partial view solves the problem of ajax reload but becomes and issue on server loading because: 1 - The deferred initialization would be running twice, and 2 - Because kendo widgets have jquery dependencies and would be running before the jquery scripts are included in the page.

    The solution i found was to not deffer the initialization of the widgets, however this would take us back to the point above regarding the jquery dependencies. And as much as i tried to avoid it, the only thing that really worked was to include jquery in the page header. All of the other scripts (validation, kendo, custom, etc) are still at the bottom but the main jquery script is at the top. And now my partial views load perfectly on server or ajax request.

    TL;DR;

    1. Do not use deferred initialization in partial views
    2. Move jquery.min.js to the top of the page.

    Hopefully there is a better way to do it but for now it does the trick.