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.

OneManBand
  • 528
  • 5
  • 24

2 Answers2

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>   
    }
joint_ops
  • 312
  • 1
  • 5
  • 20
  • 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 .@Script.Renders out from the 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? – OneManBand Apr 28 '16 at 18:01
  • what do You mean by saying "body views"? is it layout body or your view which is rendered in layout ? – joint_ops Apr 28 '16 at 18:43
  • 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! – OneManBand Apr 28 '16 at 18:59
  • 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: http://stackoverflow.com/questions/7556400/injecting-content-into-specific-sections-from-a-partial-view-asp-net-mvc-3-with – stephen.vakil Apr 28 '16 at 19:59
  • Yes , as I said , scripts should be placed in main View , not in partials – joint_ops Apr 28 '16 at 20:01
  • Was just elaborating on / clarifying what I suspect was your reasoning, since you didn't specify. IMO, this is a shortcoming of partial views. – stephen.vakil Apr 28 '16 at 20:53
  • Just to be clear - this solution would mean that any partial view usage of the – OneManBand Apr 28 '16 at 22:08
  • If You place scripts in head section browser will load scripts before it loads Your html, which is lowering your page efficiency., when You have it on the bottom of page You forcing the scripts to load last . – joint_ops Apr 29 '16 at 02:07
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);
    }
});
  • 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. – OneManBand Apr 29 '16 at 06:02
  • 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) –  Apr 29 '16 at 06:04
  • 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. – OneManBand Apr 29 '16 at 06:09