4

For some context, the DOM Hierarchy:

 Layout.cshtml
 > View
   > Partial View

The Layout file contains:

<head>
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/jqueryui")
</head>
<body>
     <div>
          @RenderBody()
     </div>
     @RenderSection("scripts", required: false)
</body>

The View contains a form. After submitting the form, an AJAX call returns the partial view which is inserted into the View using $('selector').html(PartialViewResult).

The Partial View contains:

// @Scripts.Render("~/bundles/jquery") // [†]

@using(Ajax.BeginForm(...)
{
   // MinRate has the appropriate "lessthanproperty" data-val HTML properties
   @Html.EditorFor(x => x.MinRate)
   // MaxRate has the appropriate "greaterthanproperty" data-val HTML properties
   @Html.EditorFor(x => x.MaxRate)
   @Html.ValidationSummary()
   <button type="submit">Submit</button>
}

@Scripts.Render("~/bundles/jqueryval")
@Scripts.Render("~/bundles/MapRates")

<script>
    $(document).ready(function () {
        console.log("CSHTML, ON READY");
    });

    (function() {
        console.log("CSHTML, IIFE");

        $.validator.addMethod("lessthanproperty", function (value, element, params) {
            return Number(value) < Number($(params).val());
        });

        $.validator.addMethod("greaterthanproperty", function (value, element, params) {
            return Number(value) > Number($(params).val());
        });
     })();
</script>

MapRates Javascript file contains:

$(document).ready(function () {
    console.log("JS, ON READY");
});

(function () {
    console.log("JS, IIFE")

    $.validator.unobtrusive.adapters.add("lessthanproperty", ["comparisonpropertyname"], function (options) {
        options.rules["lessthanproperty"] = "#" + options.params.comparisonpropertyname;
        options.messages["lessthanproperty"] = options.message;
    });

    $.validator.unobtrusive.adapters.add("greaterthanproperty", ["comparisonpropertyname"], function (options) {
        options.rules["greaterthanproperty"] = "#" + options.params.comparisonpropertyname;
        options.messages["greaterthanproperty"] = options.message;
    });
})();

Now...from what I can gather, the above sourcecode should work. When the user interacts with the MinRate or MaxRate fields, client-side validation should cause the ValidationSummary to be updated according to any validation errors encountered. However, the above does not work unless I uncomment the jquery library reference, @Scripts.Render("~/bundles/jquery") at the top of the Partial View, above the Ajax.BeginForm line.

But the jquery script is already included in Layout making this the second time it is loaded and so naturally it breaks some things in another, previously unmentioned partial view. For this reason and for good coding practice, I need to get this validation working without the crutch of referencing the jquery library a second time.

When the unobtrusive validation works, with jquery referenced, the printout statements appear as:

JS, IIFE
CSHTML, IIFE
JS, ON READY
CSHTML, ON READY

When the unobtrusive validation breaks, without jquery referenced, the printout statements appear as:

JS, ON READY
JS, IIFE
CSHTML, ON READY
CSHTML, IIFE

Including the jquery library reference causes the script contents to be loaded in the correct order. Without the library, the validation adapters and methods are not incorporated until after the document is ready which apparently makes the unobtrusive js validation non-functional.

How can I get the validation components to load in the correct order and be functional on the page? Also, can anyone explain why including the jquery reference on the view causes it to work at all? Many thanks in advance for your help!

edit: It occurs to me that it may be pertinent to say that the Partial View is a bootstrap modal. I'm not sure if its being a modal with visibility triggered by a data-toggle could cause any odd behavior with validation binders or not.

1
  • Why not put your IIFE in the $(document).ready? Commented Apr 28, 2016 at 15:37

2 Answers 2

2

However, the above does not work UNLESS I uncomment the jquery library reference, @Scripts.Render("~/bundles/jquery") at the top of the Partial View,

You shouldn't store scripts in partial view's, Place all your script and script references in Your main view.(references on top) at @section scripts {} after placing @RenderSection("scripts", required: false) in Your _Layout,which will insert a section named "scripts" found in the view.

Please note that the @Scripts.Render("~/bundles/jquery") in _Layout must be included after @RenderBody() method to be seen on your views, like so:

 <!DOCTYPE html>
<html>
<head>
</head>
<body class="body">
    <div class="container-fluid">
        @RenderBody()
        <footer class="footer">
            <p>&copy; @DateTime.Now.Year  company name</p>
        </footer>
    </div>
    @Scripts.Render("~/bundles/modernizr")
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
</body>
</html>

and Your rendered view

 @model someViewModel
@{
    ViewBag.Title = "some title";
}
<div class="row">
 //some chtml
</div>
    @section Scripts {
       <script>your scripts</script>   
    }
Sign up to request clarification or add additional context in comments.

8 Comments

Okay, so I've moved the jqueryval and MapRates JS files into the View within a .@section Scripts {} region. I'm unclear about your second suggestion to move the Layout jquery/jqueryui [email protected] out from the <head> tags to below the .@RenderBody() call. Some of the body views have a dependency on jQuery, so I get a "$ is not defined" reference error. How is the MapRates JS file supposed to run if jQuery has not been loaded yet?
what do You mean by saying "body views"? is it layout body or your view which is rendered in layout ?
Sorry, I could have been more clear: In my Layout, .@RenderBody() loads the page content which includes my view and its partial views; some of these are depedent on jQuery. So when I put my @Scripts.Render("~bundles/jquery") below this, my body content is not able to execute jQuery. It could be that I'm using @section Scripts incorrectly - I'm looking into this now although if you have more feedback I'm happy to hear it!
Partial views don't have @section support so he can't put all of the script logic for his partial view there. Therefore there's no way for his partial view to have an associated set of JS that references scripts not yet registered, right? See: stackoverflow.com/questions/7556400/…
Yes , as I said , scripts should be placed in main View , not in partials
|
2

Scripts should not be in partial views. Remove all duplicates and include them only in the main view or the layout.

Your issue is that your dynamically loading a form (the partial) after the page has initially be rendered (and therefore after the the $.validator has parsed the form). When loading dynamic content, you need to re-parse the validator in the ajax success callback, for example

$.ajax({
    ....
    success: function(PartialViewResult) {
         $('selector').html(PartialViewResult); // append to the DOM
         var form = $('form'); // better to give the form an id attribute for selection
         // re-parse the validator
         form.data('validator', null);
         $.validator.unobtrusive.parse(form);
    }
});

3 Comments

Hey thanks for the response, Stephen. I've seen this suggestion a few times in other SO posts on this topic but it doesn't seem to work for me. I think it didn't matter when the @Scripts.Render("~/bundles/jqueryval") was in the Partial View b/c the validation would not run until the Partial View was loaded anyway. But I've since moved this Render statement to the main view so I can't explain why your solution seems to still have no impact.
What is not working? If you re-parse the validator after loading the form, you will get validation on that form. If its not, then there is something else wrong in your code (or you have not implemented it correctly)
I'd agree except I've seen the validation run successfully as per the problem description. What I can't explain is why it works only when the jquery reference is made in the view. And when I pull out the jquery reference, even if I add the parse statement you suggested, it still doesn't work.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.