This file contains all the configurations and dependency rules for all the JavaScript files. Here is what it looks like:
//DETERMINE BASE URL FROM CURRENT SCRIPT PATH
var scripts = document.getElementsByTagName("script");
var src = scripts[scripts.length-1].src;
var baseUrl = src.substring(src.indexOf(document.location.pathname), src.lastIndexOf('/'));
//CONFIGURE LIBRARIES AND DEPENDENCIES VIA REQUIREJS
require.config({
baseUrl: baseUrl,
//ASSIGN SHORTCUTS FOR EASY LOADING AND VERSION ABSTRACTION
paths: {
jquery: [
//LOAD FROM THIS LOCATION IF THE CDN LOCATION FAILS
'libs/jquery-1.8.1.min'
],
underscore: 'libs/underscore-min',
underscorestring: 'libs/underscore.string.min',
can: 'libs/canjs/can.jquery.min',
canfixture: 'libs/canjs/can.fixture',
bootstrap: 'libs/bootstrap/js/bootstrap.min',
kendoall: 'libs/kendoui/js/kendo.all.min',
kendoweb: 'libs/kendoui/js/kendo.web.min',
kendomobile: 'libs/kendoui/js/kendo.mobile.min',
kendodataviz: 'libs/kendoui/js/kendo.dataviz.min',
jquerymobile: 'libs/jquerymobile/jquery.mobile-1.2.0-beta.1.min',
qtip: 'libs/qtip/jquery.qtip.min',
blockUI: 'libs/jquery.blockUI',
ajaxForm: 'libs/jquery.form',
moment: 'libs/moment.min',
notifier: 'libs/notifier.mod',
html5placeholder: 'libs/html5placeholder.mod',
nailthumb: 'libs/nailthumb/jquery.nailthumb.1.1.min',
onebyone: 'libs/onebyone/jquery.onebyone',
touchwipe: 'libs/jquery.touchwipe.min',
browserselector: 'libs/css_browser_selector'
},
//DECLARE NON-AMD COMPLIANT JS AND DEPENDENCIES
shim: {
underscore: {
deps: ['jquery'],
exports: '_'
},
can: {
deps: ['jquery'],
exports: 'can'
},
kendoall: {
deps: ['jquery'],
exports: 'kendo'
},
kendoweb: {
deps: ['jquery'],
exports: 'kendo'
},
kendomobile: {
deps: ['jquery'],
exports: 'kendo'
},
kendodataviz: {
deps: ['jquery'],
exports: 'kendo'
},
moment: {
deps: ['jquery'],
exports: 'moment'
},
notifier: {
deps: ['jquery'],
exports: 'Notifier'
},
underscorestring: ['underscore'],
canfixture: ['can'],
qtip: ['jquery'],
bootstrap: ['jquery'],
blockUI: ['jquery'],
ajaxForm: ['jquery'],
html5placeholder: ['jquery'],
onebyone: ['jquery'],
touchwipe: ['jquery'],
nailthumb: ['jquery']
}
});
//INITIALIZE APP
require([ 'App' ], function (App) {
App.init();
});
Now let's look at App.js:
define([
'require',
'jquery',
'notifier',
'bootstrap',
'blockUI',
'browserselector',
'html5placeholder'
], function (require, $, Notifier) {
//PRIVATE PROPERTIES
var baseUrl = require.toUrl('./');
var baseServiceUrl = '~/api';
//PUBLIC PROPERTIES
return {
init: function () {
console.log('App initialized!');
},
toUrl: function (url) {
if (url.indexOf('~/') === 0) {
url = baseUrl + url.substring(2);
}
return url;
},
toRestUrl: function (restUrl) {
return this.toUrl(baseServiceUrl + restUrl);
},
notifySuccess: function (message, title) {
Notifier.success(message, title);
},
notifyError: function (message, title) {
Notifier.error(message, title);
},
notify: function (message, title, icon, timeout) {
Notifier.notify(message, title, icon, timeout);
},
convertToBoolean: function (value) {
//VALIDATE INPUT
if (!this.isDefined(value)) return false;
//DETERMINE BOOLEAN VALUE FROM STRING
if (typeof value === 'string') {
switch (value.toLowerCase()) {
case 'true':
case 'yes':
case '1':
return true;
case 'false':
case 'no':
case '0':
return false;
}
}
//RETURN DEFAULT HANDLER
return Boolean(value);
}
};
});
This is a JavaScript module! It takes two parameters. The first parameter is an array of dependencies (it is using the shortcut names to the JavaScript files declared in main.js). This is telling RequireJS to load up jQuery, Notifier, Bootstrap, BlockUI and some other libraries when my site loads. Remember, this App.js file was being called by main.js as a dependency, so it will load App.js, then it will load App.js's dependencies, then load up the dependencies for each of those dependencies, and so forth. Do you see what is happening here? It is recursively loading dependencies up the tree. This handles dependencies but also makes each module self contained!
The second parameter of
define is a function that returns a list of properties and methods... a JavaScript class in other words. You can put your own goodies in here. What is important here is the parameters of the function. It matches up to the dependencies array. That is how you use the dependencies! And they are scoped within your functions so you do not have to worry about conflicting with another JavaScript class. Note that some dependencies return a class, such as jQuery returns $ or App.js return App. Other times, you just need to load the dependencies without needing a class to use. For example, I am loading HtmlPlaceHolder which adds placeholder support for form inputs for browsers that do not support placeholders. Another example is
Bootstrap, which is an awesome library by the way. I just need the JavaScript loaded up but do not need to use it as a utility in my function logic.
There you have it... the client-side architecture is in place. Pheww, all that hard work for what? Well, now in pages all you have to do is ask RequireJS for the dependencies and do what you like in the callback functions. Gone are the days of adding tons of JavaScript files onto the Sitefinity page, or juggaling the order of the files, or thinking of putting them on the template or the page level, or worrying about JavaScript conflicts. This is what you do from your Sitefinity pages:
This piece of JavaScript code is telling RequireJS to load up: jQuery, my App.js file and the OneByOne jQuery plugin. Then I list the dependencies as parameters to the function, then start my logic. Very elegant huh? Welcome to JavaScript AMD and Sitefinity.
Happy Coding!!