I've looked at a number of questions similar to How to add a script in a partial view in MVC4? and MVC4 partial view javascript bundling Issue and am still struggling to understand ASP.NET MVC architecture when it comes to view-specific script. It seems the answer to others who have tried to include script in their MVC4 partial views is to put the script at a higher level. But some script can't be moved to a higher level where it will run more globally. For example, I don't want to run script that applies knockout.js data bindings for a view model whose controls aren't loaded. And I don't want to run a whole bunch of script for a whole bunch of views that aren't active every time I load a page.
So I started using the view-specific @Section Script
blocks in my .vbhtml
views to include script specific to a view. However, as pointed out by others, this does not work in a partial view. I am prototyping our architecture to see what we can and can't do here. I'd like to think that I might be able, in some cases, to use a view as a partial view and vice versa. But when you pull in a view to use as a partial view the @Section Script
block does not render. I have managed to get all my viewmodel script defined globally in a way such that I need only run one line of code to create and bind a view model, but I still need that one line of code to run only when a particular view is active. Where can I appropriately add this line of code in a partial view?
ko.applyBindings(window.webui.inventoryDetailViewModel(ko, webui.inventorycontext));
Am I going down the right path here? Is this a proper way to architect an MVC application?
Edit Found this question very closely related to my problem, and includes a significant part of my answer: Can you call ko.applyBindings to bind a partial view?
This is the best you can do, but there can be still problems:
So, I also recommend don't doing using this hacky trick. (Well, Darin Dimitrov's solution is great, but using it it not a good idea).
The best solution is to have all the scripts available when the partial is rednered:
If you do this, you can run the scripts when they are needed. But then, how do you only run the desired scripts on the desireds parts of your partials? The easier way is to mark them with custom
data-
attributes. Then you can "parse" the page, looking for your customdata-
attributes, and running the scripts that apply: that's unobtrusive javascript.For example, you can include an script that "parses" the page on jQuery's
$(document).ready
(when all the page, and all the scripts have finished loading). This script can look for the elements with the customdata-
attributes($('[data-my-custom-attr]').each( MyCustomSccript(this));
You can also take into account that the
data-
attributes can be used to configure your script, i.e. you can use an attribute to indicate that some kind of script must be run, and extra attributes to configure how the script runs.And, what about partial views loaded with ajax? No problem. I told you could use
$(document).ready
, but you also havesuccess
callbacks in the functions used to load partial views with ajax, and you can make exactly the same on this callbacks. An you can register a global handler forjQuery.Ajax
success, so your scripts will be applied to all your ajax loaded partials.And you can even use more powerful techniques, like loading dynamically the scripts needed for your partials, as required for the attributes.
Usually, the problem, is that we think that JavaScript should be supplied from the server, but the truth is that JavaScript lives on the browser, and the browser should have more control on it
Description of architecture with dynamic loading of scripts:
main page: include a "parser script": this parser script is responsible for:
partials
data-
attributes on DOM elements so that the parser knows which scripts are requireddata-
attributes to pass extra data to the scriptsObviously, it's very important to follow a good convention to name the scripts and the
data-
attributes, so that the code is easier to use and debug.A good place to see how the scripts can be dynamically downloaded is: On-demand JavaScript
There are many solutions. Other option: How can I dynamically download and run a javascript script from a javascript console?
Your script should attach itself to the singleton, just like you do when you define a jQUery plugin. the content of a .js would be like this:
A little clue on how to implement the parser:
Following the right conventions is absolutely neccessary so that the parser can run the necessary script.
The name of the function to run could be another
data-
attributes, or be always the same likeinit
. As this function can acces the DOM element, it can find there other parameters and options using otherdata-
attributes.This can seem hard to implement, but once you have set up a working skeleton you can complete and improve it easily.
The existing answers weren't quite detailed enough, so allow me to provide a detailed answer with code. I mostly followed the suggestion of JotaBe's answer, and here's exactly how.
First I devised a scheme for what custom ("data") attribute I would use and created a helper function to apply it in a way that would help me be compatible with ASP.Net bundling. The attribute needs to provide the necessary information to download a single bundle file when bundling optimizations are turned on (
BundleTable.EnableOptimizations = True
) and several independent files otherwise. You can see the format I settled on for adata-model
attribute in the comments on the code below. This code went into a file calledHelpers.vbhtml
which was added to a new folderApp_Code
in my main project.App_Code/Helpers.vbhtml
Then I can apply that attribute on a node like this to have it indicate how it wants knockout bindings applied to itself and its descendants and what scripts are needed before doing so. Notice how my intention is to be able to refer to the same script bundle and model from multiple nodes without duplicating the download or having duplicate instances of the model unless I specifically request separate instances of the model with
forceNew
. It would probably be better to add a container to house this attribute in a single place, but I want to demonstrate that it's not necessary.Views/Inventory/Details.html
Finally I create a javascript file referenced in an existing bundle that's always pulled in in
_Layout.vbhtml
. It has the client side code necessary for processing the new "data-model" attribute. The idea is to callko.applyBindings
on these specific nodes, and to only instantiate the view model once unless distinct instances of the model are explicitly requested on multiple nodes.Scripts/app/webui.main.js
With this solution, I can rely on the existing ASP.NET MVC4 bundling framework (I don't need r.js) to optimize and combine javascript files, but also implement download on demand and an unobstrusive mechanism for defining the scripts and view models related to knockout bindings.
Here's how I've been composing view models and views:
In my Views, I do have a Script section in my master template. So my view looks like this:
In fact, the more I write these MVVM apps, the more inclined I am use ajax for loading data and not pass model data into the
init
function. This enables me to move theinit
call into the factory. So then you get something like:Which reduces my view script to a simple script tag:
Lastly, I like to create script templates for vm components inside of partial views like so:
Partial view at ~/Views/Shared/ScriptTemplates/_secondaryViewModelTemplates.cshtml
A couple of things going on here. First, the associated script is imported. This ensures that the necessary view model factory script is included when the partial is rendered. This allows the master view to remain ignorant to the script needs of the sub-component (of which it may have multiple). Also, by defining the templates in a partial rather than in a script file, we're also able to utilize the wildly helpful HtmlHelper and UrlHelper as well as any other server-side utilities you so chose.
Finally, we render the template in the main view:
That's a lot of code and it was all written in SO so there could be some errors. I've been evolving this style of MVVM+MVC architecture for the past couple of years and it's really made an improvement in my development cycles. Hopefully this will be beneficial to you as well. I'd be happy to answer any questions.