Question

I'm using EditorTemplates for things like DateTime, Colors, etc. My forms, which may contain quite a bit of these templates, are primarily loaded via Ajax as a partial view.

Rather than have a bunch of jQuery initialization scripts at the bottom of each editor template, is there a conceptual way to do it only once per response? Lets say the you have 10 date time pickers, on one form, it would be really silly to pass down the same initialization code 10 times.

Placing the initialization script on the main form with the 10 date pickers would be efficient (certainly doesn't do much to keep code isolated to the datetime editor template), but then there's other cases where you may just want to have one date time picker on a different form or two or three, and now you're duplicating those scripts across multiple views in your code.

So how can I still leverage editor templates the right way here? I think I'm looking for something like --

EnsureThisScriptIsOutputOncePerThisResponse(<script>$('.datepicker').datepicker("insert lengthy config here");</script>)

to add to the bottom of the editor template that works with partial views rendered via AJAX.

Was it helpful?

Solution

One thing we did on our project was to have a startup.js file that extends the unobtrusive javascript pattern by utilizing data attributes for configuring things like that. An example for ours is buttons. Let's say I had the following html:

<input type='submit' data-button=true data-button-icon='ui-icon-check' value='approve' />

the startup file looks for buttons something like this:

function initializeButtons() {
    $("*[data-button=true]").each(function () {
        var $this = $(this);
        var initialized = $this.data('button-init');
        if (!initialized) {
            var options = {
                disabled: $this.data('button-disabled'),
                text: $this.data('button-text'),
                icons: {
                    primary: $this.data('button-icon-primary') || $this.data('button-icon'),
                    secondary: $this.data('button-icon-secondary')
                }
            };
            $this.button(options);
            $this.data('button-init', true);
        }
    });
}

$(document).ready(initializeButtons).ajaxSuccess(initializeButtons)

This makes common jQuery UI initialization code much easier to manage, and we created HtmlHelper extensions to easily fill in the data attributes for the stuff we need/use.

OTHER TIPS

So how can I still leverage editor templates the right way here? I think I'm looking for something like -- EnsureThisScriptIsOutputOncePerThisResponse() to add to the bottom of the editor template.

Using Cassette. This is the library that allows you to reference scripts and stylesheets from anywhere - even from partial views. Then it ensures script reference order and renders scripts ensuring this script and its references output only once and generally in single place in response.

Update:

Referencing scripts for ajax can be done as follows

In datepicker editor template - EditorTemplates/DateTime.cshtml

@model DateTime?
@{
    Bundles.ReferenceScript("path to datepicker script. Script may reference jquery ui in its turn");
} 
//datepicker

And in datepicker container form, directly render scripts. If not ajax, this would have been done in Layout page

@model ViewModelModelWithTenDates
@Bundles.RenderScripts() //will output distinc script references in order

//10 datepickers
@Html.EditorFor(model => model.FirstDate)

You can use livequery plugin to put add the initialization code into an external .js file and include the file in your _Layout.cshtml.

$(".datepicker").livequery(function(){
    $(this).datepicker("insert lengthy config here");
});

I use an init attribute om the stuff that needs some script run:

<input type="text" id="id" init="datepicker makereadonly" />

This indicates that two functions have to be run. Running them is done in the Ajax-complete which is defined in an external file, notice that once an item is initialized the initialized attribute is set. This way no script exists in your partial views.

var initFunctions = initFunctions || {};

initFunctions.datepicker = function(item) {
    $(item).datepicker();
};

initFunctions.readonly = function(item) {
    $(item).attr("disabled", "disabled");
};

$('body').ajaxComplete(function (e, xhr, settings) {
  var itemFunction = function (item) {
     var $this = $(item);
     var attr = $this.attr("init");
     var functions = attr.split(" ");
     for (var i = 0; i < functions.length; i++) {
        var funcName = functions[i];
        var func = initFunctions[funcName];
        if (func != undefined)
           func($this);
     }
     $this.attr("initialized", "");
  };

  items = $("[init]:not([initialized])");
  items.each(function (index, item) { itemFunction(item); });
});
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top