RequireJS Setup in SilverStripe

It is common to have all JS scripts in one file, however as you add more and more functions and handlers you realise that you need more structure. RequireJS is a very popular script loader that helps us achieve this, by allowing us to define modules and dependencies which can be loaded on the fly, which means that instead of loading all files one after the other, e.g. jQuery lib, then our slider plugin which requires jQuery etc, we can load them asynchronously only when needed after the page has loaded and that is done through defining dependencies when declaring your modules.  Read more AMD.

Code Structure

Assuming you're familiar with the MVC module, we'll be applying this structure to our code.  Let's assume that the following structure will live in your themes javascript folder (/themes/mytheme/javascript/).

  • [app]
    • [controller]
      • BaseController.js
      • HomePageController.js
      • PageController.js
      • ParkHomePageController.js
      • ParkPageController.js
    • [model]
      • BaseModel.js
      • HomePageModel.js
      • PageModel.js
      • ParkHomePageModel.js
      • ParkPageModel.js
    • lib.js
    • Page.js
    • ParkHomePage.js
    • ParkPage.js
  • [lib]
    • [jquery-2.1.4]
      • jquery.min.js
    • require.js

Create Base Modules and Libs

  1. Create BaseController.js inside controller:

    define(function() {
    	function BaseController(id) {
    		this.id = id;
    	}
    	BaseController.prototype = {
    		setModel: function(model) {
    			this.model = model;
    		},
    		render: function(bodyDom) {
    		}
    	};
    	return BaseController;
    });
  2. Create BaseModel.js inside model:

    define(function () {
    	function BaseModel(title) {
    		this.title = title;
    	}
    	BaseModel.prototype = {};
    	return BaseModel;
    });
    
  3. Create lib.js inside app folder:
    define(['jquery'], function ($) {
    	return {
    		/* commonFunction
    		 * ==============
    		 * Blah blah. */
    		commonFunction: function(x) {
    		...
    		}
    	}
    });
    

Load Modules

1. Load Page class in your template (/themes/mytheme/templates/Page.ss).

<!-- Load App -->
<script src="/$ThemeDir/javascript/lib/require.js"></script>
<script>
	requirejs(['$ThemeDir/javascript/common'], function (common) {
		requirejs(['app/Page']);
	});
</script>

Initialise RequireJS config file with following settings in (/themes/mytheme/javascript/common.js):
Note: It is better to use the SilverStripe core jquery include to avoid jQuery version conflicts.

requirejs.config({
	baseUrl: '/themes/gwrc/javascript/lib',
	paths: {
		app: '../app',
		jquery: 'jquery-2.1.4/jquery-2.1.4.min',
		jqueryui: 'jquery-ui-1.11.4/jquery-ui.min',
		mmenu: 'mmenu/js/jquery.mmenu.min.all',
		validate: 'jquery-validate-1.14.0/jquery.validate.min',
		owlcarousel: 'owl-carousel-2.0.0/owl.carousel.min'
	},
	shim: {
		jqueryui: {
			deps :['jquery']
		},         
		mmenu: {
			deps :['jquery']
		}, 
		owlcarousel: {
			deps :['jquery']
		},
		validate: {
			deps :['jquery']
		}              
	}
});

HomePage app Example

In SilverStripe context, we can assume that every page will require the Page app, in situations where you need to extend Page app for example in the case of the Home Page as you may wish to load certain plugins such sliders etc, then this example explains how this can be achieved.

Load both Page and HomePage apps in (themes/mytheme/templates/Layout/HomePage.ss):

<!-- Load App -->
<script>
requirejs(['$ThemeDir/javascript/common'], function (common) {
requirejs(['app/HomePage']);
});
</script>

Then add the following:

HomePage.js

define(function (require) {
	var $ = require('jquery'),
		model = require('./model/HomePageModel'),
		controller = require('./controller/HomePageController'),
		owlcarousel = require('owlcarousel');
	controller.setModel(model);
	$(document).ready(function($) {
		controller.render(model);
	});
});

HomePageModel.js

define(['./BaseModel'], function (BaseModel) {
	var model = new BaseModel('HomePageModel');
	model = {

        loadCarousel: function (carouselHolderID, carouselOptions) {
		// ...
        },
	shuffleCarouselVideos: function(carouselHolderID) {
		// ...
	}	        
	};
	return model;
});

HomePageController.js

define(['./BaseController'], function (BaseController) {
	var controller = new BaseController('HomePageController');
	var carouselOptionsNews = { ... };
	var carouselOptionsVideos = { ... };
	controller = {
		setModel: function(model) {
			this.model = model;
		},
		render: function(model) {

			// Load tabbed content
			try {
				$('#tabsWebcams').tabs().removeClass('hidden');
				$('#tabsMediaReleases').tabs().removeClass('hidden');
			} catch(e) {}

			// Load carousels
			model.shuffleCarouselVideos( $('.gwrc-videos') ); // carouselHolderID
			model.loadCarousel('.gwrc-news', carouselOptionsNews);  // carouselHolderID, carouselOptions		
			model.loadCarousel('.gwrc-videos', carouselOptionsVideos);  // carouselHolderID, carouselOptions
		}
	};
	return controller;
});

References
http://requirejs.org/docs/download.html
https://github.com/requirejs/example-multipage-shim
http://verekia.com/requirejs/build-simple-client-side-mvc-app-require-js