MVC3 Unobtrusive Validation Not Working after Ajax Call

27,796

Solution 1

$.validator.unobtrusive.parse("#frmAddItem"); will work. Do note that it must be in the partial that you load through ajax (below the form in the partial)

<form id="frmAddItem" method="POST" action="...">
    <!-- all the items -->
</form>
<script type="text/javascript">
    $.validator.unobtrusive.parse("#frmAddItem");
</script>

Solution 2

I'm adding my experience as the above recommendations did not work for me. This solution did and may help others that get directed to this page from a search engine:

Add OnSuccess="$.validator.unobtrusive.parse('YourFormName');" to you AjaxOptions

An example using Ajax.ActionLink:

@Ajax.ActionLink("This is a test to get unobtrusive javascript working",
                 "Name_of_your_controller_action",
                 new AjaxOptions { HttpMethod = "POST", 
                                   InsertionMode = InsertionMode.Replace, 
                                   UpdateTargetId = "UserDiv", 
                                   OnSuccess="$.validator.unobtrusive.parse('UserDetailsForm');"  
                                 }
                )

This solution was found at: http://blog.janjonas.net/2011-07-24/asp_net-mvc_3-ajax-form-jquery-validate-supporting-unobtrusive-client-side-validation-and-server-side-validation

Solution 3

I wrote this little snippet that will you can place in your javascript file and it will handle all your forms that are ajax loaded.

//enable unobtrusive validation for ajax loaded forms
$(document).ajaxSuccess(function (event, xhr, settings) {
    //process only if html was returned
    if ($.inArray('html', settings.dataTypes) >= 0) {
        //will parse the element with given id for unobtrusive validation
        function parseUnobtrusive(elementId) {
            if (elementId) {
                $.validator.unobtrusive.parse('#' + elementId);
            }
        }

        //get the form objects that were loaded.  Search within divs
        //in case the form is the root element in the string
        var forms = $('form', '<div>' + xhr.responseText + '</div>');

        //process each form retrieved by the ajax call
        $(forms).each(function () {
            //get the form id and trigger the parsing.
            //timout necessary for first time form loads to settle in
            var formId = this.id;
            setTimeout(function () { parseUnobtrusive(formId); }, 100);
        });
    }
});

Solution 4

Another option, rather trick, which worked for me. Just add following line in the beginning of the partial view which is being returned by ajax call

this.ViewContext.FormContext = new FormContext(); 

Reference

Solution 5

I could only get it the validation to work inside of OnComplete instead of OnSuccess:

Here's the AJAX Code:

@using (Ajax.BeginForm("Index", null, 
                       new AjaxOptions { OnSuccess = "onSuccess", 
                                         OnComplete = "onComplete"}, 
                       new { id = "mainForm" }))

And here's my script:

function onComplete(result) {
    $.validator.unobtrusive.parse("#mainForm");
    alert("Complete");
};
Share:
27,796
David C
Author by

David C

Erudite and Scallywag, all rolled into one...

Updated on December 10, 2020

Comments

  • David C
    David C over 3 years

    Ok, here is the deal, I have seen a few posts on SO relating to this issue, but nothing is working for me.

    Basically, I have select drop downs that are being loaded from partial views, I am trying to filter contents of each subsequent drop down, based on the previously selected drop down.

    If I just put the call to the partial view in the div containers, and load the page, the validation from data annotations works fine, primarily Required attribute.

    However, if I try to load the same partial via AJAX as it is setup here, the Required validation does not work, anyone can post the form after that and KABOOM.

    I have found people saying that in the Success callback you need to have the client side validator reparse the form, and I am trying that, but it doesn't seem to be working.

    I have a view which looks like this...

      @model Area51.Models.Workflow.AddReportableItemToBatchActionModel
    @{
        ViewBag.Title = "Add Reportable Item to Batch";
        Layout = "~/Views/Shared/_Layout.cshtml";
    }
    
    <script type="text/javascript">
    
        $(function () {
            var fadeDelay = 150;
    
            $(".jqDatePicker").datepicker({
                dateFormat: 'm/d/yy',
                onSelect: function (date) {
                    $("#categoryContainer").show(fadeDelay);
                }
            });
    
            $('#Category').change(function () {
                RetrieveItemsForCategory();
                $("#itemContainer").show(100);
            });
    
            $('#Item').live('change', function () {
                RenderPartialForUOMByItem();           
            });
    
    
    
            function RetrieveItemsForCategory() {
    
                var category = $("#Category :selected").val();
    
                $.ajax({
                    type: "POST",
    
                    url: '@Url.Action("RenderPartialForLocationItemsByCategory","BatchWorkflow")',
    
                    data: 'category=' + category,
    
                    success: function (result) {
                        $("#itemContainer").html(result.toString());
                        $("#itemContainer").show(100);
                        RebindValidation();
                    },
    
                    error: function (req, status, error) {
                        alert("Sorry! Could not request items for your selection at this time.");
                    }
    
                });
    
    
            }
    
    
            function RenderPartialForUOMByItem() {
    
                var item = $("#Item :selected").val();
    
                $.ajax({
                    type: "POST",
    
                    url: '@Url.Action("RenderPartialForUOMByItem","BatchWorkflow")',
    
                    data: "item=" + item,
    
                    success: function (result) {
                        $("#quantityContainer").html(result.toString());
                        $("#quantityContainer").show(100);
                        RebindValidation();
                    },
    
                    error: function (req, status, error) {
                        alert("Sorry! Could not request items for your selection at this time.");
                    }
    
                });
            }
    
            function RebindValidation() {
                alert("Rebinding Validation");
                $.validator.unobtrusive.parse("#frmAddItem");
            }
    
        });      // End OnLoad Event
    </script>
    
    <h3 class="pageHeader">Batch : @Model.BatchName</h3>
    
    <div align="center">
    
    @{Html.BeginForm("AddItemToBatch", "BatchWorkflow", null, FormMethod.Post, new { id = "frmAddItem" });}
    
        @Html.ValidationSummary(true)
    
        <fieldset style="width:60%">
            <legend>Add an Item to the Batch</legend>     
    
         <div>       
              <h3>Select Date Item was Added</h3>
              @Html.EditorFor(x => x.EventDate,null)
              <br />
          </div>
    
          <div id="categoryContainer" style="display:none"> 
            <hr />
              <h3>Select an Inventory Category</h3>
              @Html.EditorFor(x => x.Category,null)
              <br />
          </div>
    
          <div id="itemContainer" style="display:none"> 
            @*   @{Html.RenderAction("RenderPartialForLocationItemsByCategory", "BatchWorkflow", new { category = Model.Category });}*@
          </div>
    
    
          <div id="quantityContainer" style="display:none"> 
            @*  @{Html.RenderAction("RenderPartialForUOMByItem", "BatchWorkflow", new { item = Model.Item });}*@
          </div>
    
          <div id="reportingDataContainer" style="display:none"> 
            <hr />
              <h3>What quantity of the batch was affected by this addition?</h3>
              @Html.EditorFor(x => x.ConsumedWineQuantity) (Gallons)
            <br />
            <hr />
              <h3>What was the increase in Batch Volume as a result of this addition?</h3>
              @Html.EditorFor(x => x.ProducedWineQuantity) (Gallons)
          </div>
    
            <div style="display:block">
            <div></div>        
                <span><button type="button" id="btnCancel" class="linkButton" value="Cancel" onclick="location.href='@Url.Action("Home","Home",null)';">Cancel</button></span>  
                <span><button type="submit" id="btnSubmit" class="linkButton" value="Add">Add Item</button></span>
            </div>
    
    
        </fieldset>
            @{ Html.EndForm(); }
    </div>
    

    The Partial Views are very simple, they basically look like this...

    @model Area51.Models.Workflow.AddReportableItemToBatchActionModel
    
          <hr />
              <h3>Select the Item to Add</h3>
              @Html.EditorFor(x => x.Item)
              <br />
    

    Again, if I just RenderPartial, the validation works fine, however when I try to do it via ajax, the validation goes away. The "Rebinding Validation" alert fires, but the $.validator.unobtrusive.parse("#frmAddItem"); doesn't seem to be doing a thing.

    Can anyone help with what I am missing? It would be greatly appreciated.

    <======================= UPDATE 1 =============================>

    OK, I tried adding the $.validator.unobtrusive.parse("#frmAddItem"); at the bottom of the the partial view in a document ready event and it didn't seem to work either, basically nothing changed, I could still submit the form.

    I did find a post here : http://xhalent.wordpress.com/2011/01/24/applying-unobtrusive-validation-to-dynamic-content/ that mentioned that when the MVC version of the jqvalidation sees a form already has validation rules bound to it, it just ignores the .validator call. I implemented the script extension that this gentleman used, and the validation is now rebinding to the form using the new extension. I can test this by appending html to the form and calling the new extension, and it is rebinding to the new text box.

    However, this still has not completely fixed the issue. I used Firebug to check out the actual content on the fields coming back from the ajax call, and noticed something very strange.

    When I use the RenderPartial to call the action, it writes out the following select :

    <select id="Item" name="Item" data-val-required="The Item field is required." data-val-number="The field Item must be a number." data-val="true">
    

    However, when I make the ajax call to the same exact controller action, it gives me this back :

    <select id="Item" name="Item">
    

    I tried adding the script tags to the partial view as well, but it didn't fix the issue. Is there some reason why the ajax call would be stripping the unobtrusive validation tags?

    <======================= UPDATE 2 =============================>

    Ok, so what was happening, is I had an editor template for the drop down that took a select list and converted it to an html select. I found a post that mentioned that in order to get data validation attributes to write out on an editor template, you have to have a form context. Since the Html.RenderPartial was being done within a form, then the editor template had a form context to work with. When I was just trying to call the partial via ajax, there was no form context to work with, and instead of complaining it just didn't write out the data validation attributes. Adding a new Form Context in the editor template for the SelectListDropDown fixed the issue.

    @{ // fix to stop stupid crappy brad wilson mvc3 code from stripping the jq data valdiation attributes
        if (ViewContext.FormContext == null)
        {
            ViewContext.FormContext = new FormContext();
        }
    }
    
  • David C
    David C over 12 years
    Ok, after adding just the script in the form at the end of the partial, aka : <script type="text/javascript"> $.validator.unobtrusive.parse("#frmAddItem"); </script> instead of trying the document ready event, it seemed to work as expected. I am marking this answer correct, as the issue with the validation not coming through on ajax is a separate issue. Thanks much jgauffin
  • David C
    David C about 12 years
    Ah, so you new'd up the form context in the partial instead of the editor template. Honestly the template seems more modular, as it would work for any call on any view. One thing you may want to do is check to see if the context is null before you create a new one and overwrite the old one...
  • bjan
    bjan about 12 years
    @DavidC Going for editor template or partial, it depends upon the scenario. For the first page load the context is not null but after all ajax calls the context is null. Is it really going to hurt if the context is set to new even if it is not null?
  • David C
    David C about 12 years
    true that it depends on the scenario, and your infrastructure, some people don't even use templates. But if you go the template route it IS more modular and dry. As to your second question, I guess it would depend upon what other validation data the form context had before you stomped on it.
  • Liam
    Liam over 11 years
    Just to note, I took a slightly different approach and added it into the success function of my ajax call $.ajax({success:function(){$.validator.unobtrusive.parse('fo‌​rm');}});
  • Andy
    Andy about 11 years
    The setTimeout helped me out. I hate adding stuff like that though - seems too brittle.
  • Andrei Dvoynos
    Andrei Dvoynos about 10 years
    Downvoted because of setTimeout. Such an ugly approach.