Today I will show you how to organize and load JavaScript on demand in ASP.Net MVC4 Views with KnockoutJS using RequireJS.
Here is my last article that explains the use of RequireJS with KnockoutJS in ASP.Net MVC4. You can have a look at them since it will help you to understand this article much better.
Organizing JavaScript in Views
What will happen if you fail to organize the JavaScript code in an ASP.Net MVC view? Of course, you will end up with the loading order of JavaScript and if you referenced all the scripts in the same page then it will lead you to have a collision on the JavaScript function's name. So it is very important to organize the JavaScript in MVC views.
How are we going to organize the JavaScript on views? Well, as we will use RequireJS, it will be much easier to organize them. We have a two-step process to organize the JavaScript in ASP.Net MVC4.
- Use RequireJS or StealJS to load JavaScript on demand asynchronously.
- Have an "Initializer" section in each page that will load only the required JavaScript models and viewmodels.
- Render the "Initializer" section from the master page, probably _Layout.cshtml in ASP.Net MVC4 that is defined in each page, only when the specific page is loaded into the master page.
Let us explain this with an example project.
Setup the Project
Since we will use the same example project that we created for my last article, please follow the steps provided there.
If you are done with the project setup then please ensure that the necessary JavaScript files are added. Cross-check with the figure shown below.
In this above example, we have two new files added.
The config.js
Here the config.js holds the configuration settings required for RequireJS.
var require = {
baseUrl: "/Js",
paths: {
ko: "/Scripts/knockout-2.3.0",
jquery: "/Scripts/jquery-1.8.2.min"
}
};
It is always good to have a separate config file for RequireJS. The syntax for the separate config is that the config object should be assigned to the variable that should be named as "require" as shown in the preceding example.
The home-content.js
The home-content.js is nothing but a JavaScript file for the Content.cshtml view's knockout viewmodel.
define('home-content', ['ko'], function (ko) {
var contentViewModel = function (options) {
var self = this;
self.welcomeMessage = ko.observable('This is ' + options.title + ' page');
self.onmousein = function (data, event) {
$(event.target).animate({ fontSize: "24px" }, 1500);
},
self.onmouseout = function (data, event) {
$(event.target).animate({ fontSize: "12px" }, 1500);
};
};
return contentViewModel;
});
This file has the knockout viewmodel defined by RequireJS for the Content view. RequireJS defines the knockout viewmodel with the moduleId 'home-content'. When the dependency module (for KonckoutJS) is loaded, the given callback function will be loaded with the ko object as a parameter.
Note: Each define()'s callback function should return the viewmodel created else you won't be getting the viewmodel object when you access the defined viewmodel from require().
The Content.cshtml
In this view, as usual we have simple welcome text. But this time, we have a link for another view.
@{
ViewBag.Title = "Content";
}
<section id="content-page">
<div id="welcome-msg"
data-bind="text: welcomeMessage,
event: { mouseover: onmousein, mouseout: onmouseout }">
</div>
@Html.ActionLink("Return Back", "Index");
</section>
@section Initializer
{
require(['app', 'home-content'], function (app, contentModule) {
app.render(new contentModule({title: '@ViewBag.Title'}));
});
}
Since we are using HTML5, we have a section here as the root node. If you see, the yellow highlighted section "Initializer" has the require() call to load the Content.cshtml's knockout viewmodel home-content.
We have centralized the ko.applyBindings call in the app.js file that is the starter file for RequireJS to bind the home-content knockout viewmodel to the Content.cshtml view. In this require() call, we are loading an app module and home-content module to render the view.
Let us explain the app.js in detail.
The app.js
The app.js file defines the "app" module that has the render function to apply the binding to the view with the provided viewmodel.
define('app', ['require'], function (localRequire) {
var app = {
render: function (module) {
localRequire(['ko', 'jquery'], function (ko, $) {
$(document).ready(function () {
var viewModel = {
mainViewModel: {
layout: {
userName: 'Jagan'
},
pageViewModel: module
}
};
ko.cleanNode($('.container')[0]);
ko.applyBindings(viewModel, $('.container')[0]);
});
});
}
}
return app;
});
Here the render function accepts the module/viewmodel for the view and binds that to the view. If you are going to use require() inside the define(), pass the "require" as a module as shown above.
The _Layout.cshtml
The _Layout.cshtml file is changed substantially from my last article's example project.
In the preceding image, the first point shows that we are loading the config.js file before loading the require.js file. As I said, it is always better to keep the configuration for require.js in a separate file and load it before loading require.js.
In point two, we are checking whether the "Initializer" section is defined in the loaded page or not. If defined, the "Initializer" section is rendered within the script tag. We know that the Initializer section has the require() call to load the page's knockout viewmodel and calls the render() function of app module to bind the viewmodel to the view.
The remaining part is as usual for the markup with knockout binding.
Finally we are done with organizing JavaScript in ASP.Net MVC4 view with KnockoutJS using RequireJS.
If you find this article helpful then please drop a comment. Hope you liked it.
Happy Coding.