Ammount of JS code in applications across the globe increases fast. This fact has been wiedly illustrated by professionals working in industry, for instance
here and
here. This fact raises requirement to build and maintain modular js code with dependencies management and testing capabilities without loss of integration with common best practices, one of wich is Maven usage in Java world. Solution to that problem set is going to be a subject of this post description.
Task outline and framewor set
Task: create Maven project with dependency management, containing structured JS code and htm markup with templating and testing support.
The main reason behid creation of such project is the desire to deliver static content, like js files and html markup, with Apache HTTP or Nginx servers without usage of Java web container or full EE server. And provide REST service in separate modules deployed in appropriate environment.
After some research I've came up with following set of required components:
Description of each framework is out of scope for this topic, but feel free to read more about each of them if you see some new names here.
Maven project
Maven is providing us clean and strict way of project description, with fixed directory layout, and files locations. We start with creation of pom.xml file describing the project, with name, group and version filled out. Packaging is chosen to be .war as it's a web module eventually. Once basic information is present, we have to add some plugins:
-
maven-jasmine-plugin - for test launches with appropriate configuration to force RequireJS usage
com.github.searls
jasmine-maven-plugin
1.2.0.0
true
test
${project.basedir}/src/main/webapp/js
${project.basedir}/src/test/js
FIREFOX_3
REQUIRE_JS
<source>libs/jasmine/jasmine-jquery-1.3.1.js</source>
libs/require/require.js
-
maven-war-plugin to make sure that project is going to be successfully built even if web.xml, which is a standard java web module descriptor, not present.
org.apache.maven.plugins
maven-war-plugin
2.1
false
- maven-resources-plugin to provide Jasmine specs (tests) with all required resources
maven-resources-plugin
copy-js-files
generate-test-resources
copy-resources
${project.build.directory}/jasmine
src/main/webapp
false
once we're finished we have to create appropriate folder structure:
.
├── README.md
├── aqua-jasmine-web.iml
├── pom.xml
└── src //root folder (maven structure)
├── main //main sources (maven structure)
│ └── webapp //web application resources (maven structure)
│ ├── css
│ │ ├── application.css
│ │ ├── bootstrap.css
│ │ ├── chosen.css
│ │ ├── docs.css
│ │ ├── expressmon.css
│ │ ├── graphs.css
│ │ ├── jquery.ui.all.min.css
│ │ ├── style.css
│ │ └── styles.css
│ ├── imgs
│ │ └── 334.gif
│ ├── index.html //one-pager index
│ ├── js //all js files root
│ │ ├── app.js
│ │ ├── collections //Backbone collections folder
│ │ │ └── projects //projects pages collections
│ │ │ └── TestProjectsCollection.js
│ │ ├── libs //all js libs
│ │ │ ├── backbone
│ │ │ │ └── backbone-min.js
│ │ │ ├── handlebars
│ │ │ │ └── handlebars.js
│ │ │ ├── jasmine
│ │ │ │ └── jasmine-jquery-1.3.1.js
│ │ │ ├── jquery
│ │ │ │ ├── jquery-min.js
│ │ │ │ └── jquery-serialize.js
│ │ │ ├── require
│ │ │ │ ├── require.js
│ │ │ │ └── text.js
│ │ │ └── underscore
│ │ │ └── underscore-min.js
│ │ ├── main.js //js entry point file (RequireJS enforced)
│ │ ├── models //Backbone models
│ │ │ └── projects //models for projects pages
│ │ │ └── ProjectModel.js
│ │ ├── router.js
│ │ └── views //Backbone views
│ │ ├── layout //layout views
│ │ │ ├── EmptyContent.js
│ │ │ ├── EmptyFooter.js
│ │ │ ├── NavigationHeader.js
│ │ │ └── PageLayoutView.js
│ │ └── projects //projects pages views
│ │ ├── ProjectView.js
│ │ └── TestProjectsView.js
│ └── templates //static html templates
│ ├── layout //layout htmls (header,footer,etc...)
│ │ ├── emptyContentTemplate.html
│ │ ├── footerTemplate.html
│ │ ├── navigationTemplate.html
│ │ └── simpleTemplate.html
│ └── projects //projects pages templates
│ ├── projectTemplate.html
│ └── testProjectsTemplate.html
└── test //tests sources (maven structure)
└── js //jasmine tests
├── layout
│ └── AboutLayout.js
└── projects
The whole project starts at
main.js file, this is forced by RequireJs.
It contains declarations of shortcuts, which are reused in dependencies declarations of other modules
require.config({
paths: {
jquery: 'libs/jquery/jquery-min',
underscore: 'libs/underscore/underscore-min',
backbone: 'libs/backbone/backbone-min',
handlebars: 'libs/handlebars/handlebars',
templates: '../templates',
text: 'libs/require/text'
},
shim: {
handlebars: {
exports: 'Handlebars'
}
}
});
and invocation of application initialization
require([
// Load our app module and pass it to our definition function
'app'
], function(App){
// The "app" dependency is passed in as "App"
// Again, the other dependencies passed in are not "AMD" therefore don't pass a parameter to this function
App.initialize();
});
Application initialization is pretty straight forward and simply invokes Backbone router initialization which declares actions
that should be taken once certain url of application is reached.
Templates and Layouts
Static .html markup is loaded throug RequireJs text plugin and passed as a template field value to requesting Backbone View component, which gets compiled into Handlebars template during View instantiation. Markup itself looks as follows:
This template will be converted to valid markup during rendering, which will require you to provide handlebars template wrapping object id and title fields values.
As none of listed frameworks provides us with layout principle, we are going to introduce one by hand. We add Page View which is a simple composition of header, footer and content views, we put default initialization code into page object, which can be overriden through options during object instantiation.
define([
'jquery',
'underscore',
'backbone',
'views/layout/NavigationHeader',
'views/layout/EmptyContent',
'views/layout/EmptyFooter',
'text!templates/layout/simpleTemplate.html' ,
'handlebars'
], function($, _, Backbone,NavigationHeader,EmptyContent,EmptyFooter,simpleTemplate){
var PageLayoutView = Backbone.View.extend({
template : Handlebars.compile(simpleTemplate),
//defaults to NavigationHeader view function
headerContent : NavigationHeader,
//defaults to EmptyContent view function
mainContent : EmptyContent,
//defaults to EmptyFooter view function
footerContent : EmptyFooter,
initialize : function(options) {
//instantiate appropriate views based on component functions
if (options.mainContent != undefined && options.mainContent != null) {
this.mainContent = options.mainContent;
}
if (options.headerContent != undefined && options.headerContent != null) {
this.headerContent = options.headerContent;
}
if (options.footerContent != undefined && options.footerContent != null) {
this.footerContent = options.footerContent;
}
},
render: function(){
//compile handlebars template with appropriate markup of components
var html = this.template();
//append appropriate content to root element right away after compilation
$(this.el).html(html);
this.headerView = new this.headerContent({el : '#header'});
this.mainView = new this.mainContent({el : '#mian'});
this.footerView = new this.footerContent({el : '#footer'});
this.headerView.render();
this.mainView.render();
this.footerView.render();
return this;
}
});
return PageLayoutView;
});
Testing
Testing is suggested to be performed throug the Jasmine framework, which provides fairly rich capabilities. Set up of tests relies on the same RequireJS style. The only thing that I had to add is logging capabilities, as HtmlUnit sandbox, which will be running specs code doesn't have one provided. One extra thing to notice for Mavn users is that jasmine-maven-plugin has a bdd goal which will allow you to open jasmine test report in browser and rerun your jasmine tests once you refresh browser tab\window. This approach is really a time saver for those who believe TDD.
URL's
You can grab sources and boilerplate
here.
Russian translation
References:
- http://searls.github.com/jasmine-maven-plugin/
- http://pseudobry.com/jasminemavenrequirejscoverage/
- http://backbonetutorials.com/organizing-backbone-using-modules/