tools | tech | trends HTML, JavaScript, RequireJS
uilding websites can often lead to head tags stuff ed with tens of script tags, some of which may depend on each other, others of which may be standalone. Managing these files and the order in which they’re loaded can be time consuming, and browsers stop rendering when parsing these files. This can, at worst, cause a highly unresponsive UI.
Like a knight in shining armour, RequireJS comes to the rescue. Unlike other programming languages, JavaScript doesn’t have a package management system but Asynchronous Module Definitions (AMD) are part of a solution to this. RequireJS is a module loader, which, as the name might suggests, loads in modules that you specify asynchronously (ie without blocking the page from rendering).
The RequireJS project also has a few helpful plug-ins and tools: domReady, text, a Coff eeScript adapter, i18n, and r.js. In particular, i18n makes converting
international strings a doddle and r.js is a separate tool to help minify files or projects, concatenating dependencies into a single file to reduce HTTP requests. We will be using domReady, text, and r.js.
In this tutorial you’ll learn how to use RequireJS to manage library dependencies by building a simple application that’ll pull in photos from Flickr and display them in a slideshow.
B
005
app.js006
slides.html007
jquery.js008
main.js009
styles010
main.css011
index.html03 Require configuration
The first thing we’ll do in our main.js file is set up some custom configuration options. The baseUrl defaults to the folder that data-main points to in our HTML, but in this case most libraries are housed under components. We can also set up paths for each dependency separately Now RequireJS will look for domReady at scripts/components/requirejs/ domReady.js.
001
require.config({002
baseUrl: ‘scripts/components’,003
paths: {004
domReady: ‘requirejs/domReady’005
}006
});04 Shim configuration
Many scripts will need to be ‘shimmed’ so that RequireJS can work with them. Scripts that declare themselves in the global scope are ‘legacy’ and RequireJS needs to be told what they call themselves (eg Underscore.js is _). We can also set dependencies so if we define a module which requires Backbone, then it’ll automatically grab jQuery and Underscore too.
001
shim: {002
handlebar: {003
exports: ‘Handlebars’004
},005
‘jquery.slideshow’: [‘jquery’],006
backbone: {007
deps: [‘underscore’, ‘jquery’],008
exports: ‘Backbone’009
},010
underscore: {011
exports: ‘_’012
}013
}05 Define a module
Defining a module is pretty simple but there is actually a lot going on here. First we call define and pass it three things: a module ID (‘main’), its dependencies (if any) and a function that’ll contain the rest of the code. The dependencies get passed to the function in the order that you specify them so you could write function(require, app).
001
define(‘main’, [‘require’, ‘app’], function(require) {});002
data-main attribute. This sets the base URL to the scripts folder and runs the code found in scripts/main.js – with most RequireJS definitions you can omit the file extension.
001
<!doctype html>002
<html>003
<head>004
<meta charset=”utf-8”>005
<title>Flickr Slideshow</title>006
<link rel=”stylesheet” href=”styles/ main.css”>007
</head>008
<body>009
<div class=”slideshow”></div>010
<script data-main=”scripts/main” src=”scripts/components/ require.js”></ script>011
</body>012
</html>02 Directory structure
Our directory structure will look like the following code, as RequireJS encourages a shallow structure. However, it can be customised when it is being configured to match your preferences. Instead of thinking of your scripts by filename, think of them like IDs. For example, if we require jQuery as an ID it’ll request scripts/components/jQuery.js.
001
.002
scripts003
app.build.js004
components01 Data-main
We’ll start with our HTML page. The most interesting part here is the script tag. We load Require.js as we would any other JavaScript file – but this one has a
Source files
available
sIUUQXXX ŢJMFTJMPDPVL CLT
Get faster, smarter code with RequireJS
06 Requiring dependencies
Within our module we’ll require domReady, a RequireJS plug-in. domReady is a cross-browser solution to waiting for the DOMContentLoaded event without having to wait for jQuery to be loaded. It’s especially important waiting for domReady with asynchronous scripts because they could attempt to change the DOM before it’s finished parsing.
001
‘use strict’;002
require([‘domReady’, ‘http://api.flickr.com/ services/ rest/?method=flickr.interestingness. getList&api_key=dfe82aea164aa1 83a555938 136493c82&format=json&extras=url_l&jsoncallback= define’, ‘app’], function (domReady, data, app) {003
domReady(function() {004
app.setSlides(data);005
});006
});07 External dependencies
RequireJS can also make AJAX calls to diff erent domains, making it very useful for API calls. In this case we’ll get a JSONP feed containing Flickr’s most
interesting photos and images; note that the callback method is called define. Importantly, this means that we can depend on external services alongside other dependencies, such as libraries from content delivery networks.
001
‘use strict’;002
require([‘http://api.flickr. com/services/rest/?method=flickr. interestingness.getList&api_key=API_ KEY&format=json&extras=url_l&jso ncallback=define’]);08 Custom dependencies
Requiring ‘app’ will make a request to scripts/ components/app.js where we’ll define our own module. Again, we pass through the arguments in order so: domReady, the Flickr response we’ll call ‘data’, and the ‘app’ will be an object with diff erent methods attached to it. If you get script errors it could be because the file cannot be found.
001
‘use strict’;require([‘app’], function (domReady, data, app) {
002
console.log(domReady, data, app);003
});09 Define another module
Now we’ll write app.js. Along with domReady, another RequireJS plug-in is text.js. This is used to load simple text files, we’ll use it in conjunction with Handlebars, a JavaScript templating engine, to load in some HTML. It’s simple to use, just ‘text!’ suff ixed with the filename. We’ve already told RequireJS that jquery.slideshow is dependent on jQuery, so it’ll load both.
001
/*global define */002
define(‘app’, [‘text!slides.html’, ‘handlebar’, ‘jquery. slideshow’], function (slides) {003
});10 Text.js and Handlebars
slides.html contains this morsel of HTML. The curly brackets are where Handlebars will replace the text with the value of the data that we’ll pass to it. The great thing about Handlebars is that it’s highly readable. It goes through each photo if there’s a length greater than 0, else it displays the message.
001
<ul class=”slides”>002
{{#each photos.photo}}003
<li><img src=”{{url_l}}” alt=”{{title}}” title=”{{title}}”></ li>004
{{else}}005
<li><p>There were no results!</p></li>006
{{/each}}007
</ul>008
11
setSlides method
Within our app module definition we can set up a method that will take the Flickr data (although because of the implementation abstracting the source away it could be any service). We then return the method so that our service will be able to be used by other parts of the application.
001
var setSlides = function(data){/* next step */};002
return {003
setSlides: setSlides004
};12
Compile Handlebars template
We’ve made a request to get slides.html with the Handlebars brackets and passed it in as a variable called slides. To interpolate our data with the template we need to compile the HTML (Handlebars.compile) and then pass the data to the template, appending it all to our slideshow element.
001
var setSlides = function(data) {<Right> s)BOEMFCBSTIBTVTFGVMIFMQFST JOUIJTDBTFXFSFEJTQMBZJOHB NFTTBHFJGOPSFTVMUTXFSFSFUVSOFEVTJOHJUT\\FMTF^^IFMQFS <Below> s/PUFUIBUUIFTMJEFTIPXQMVHJOJTOUDBMMFEVOUJMUIFK2VFSZ EFQFOEFODZIBTCFFOTBUJTŢJFE <Left> s5IF3FRVJSFJSTJUFJTBO JOWBMVBCMFSFTPVSDFUIBOLTUPJUT FYIBVTUJWFEPDVNFOUBUJPOBOE MJOLTUPJUTIFMQGVMDPNNVOJUZ <Below> s5BEB8IFOBMMUIF EFQFOEFODJFTBSFNFU PVSCBTJD TMJEFSJTEJTQMBZFEBOECSPVHIU UPMJGF
17
Listen for submit
We’ll use jQuery to listen for the form to be submitted. When it is submitted we’ll get the text of what they typed in (by reading .value) and pass it through encodeURI, this converts special characters e.g. ‘&’ becomes ‘%26’, so that the search term can be passed to a URL parameter.
001
$(‘.search-flickr’).on(‘submit’, function (e) {002
e.preventDefault();003
var term = encodeURIComponent(e. target[0].value);004
// next step005
return false;006
});18
Get images
This time we’ll use jQuery to make the request using $.getJSON and adding the term in as the tags value. When there’s a response we call the setSlides method exactly as we did before. As the data format is the same we don’t need to change anything else, and you should see the images update.
001
$.getJSON(‘http://api.flickr.com/services/rest/?method=flickr. photos.search&tags=’ + term + ‘&api_ key=API_KEY&format=json&extras=u rl_l&jsoncallback=?’, function (data) {
002
app.setSlides(data);003
});19
RequireJS command line
RequireJS isn’t limited to just handling
dependencies, a spinoff project of RequireJS is r.js, which can be loaded either from Node or Java (Node is recommended as it’s much faster). Note that to install it you’ll need to have Node installed and you may have to use sudo if you get permission errors.
001
$ npm install -g requirejs15
Form HTML
Now we’ll add search functionality to our page. We’ll build a simple form with a search input and a button so that people can type in a search term and we’ll ask Flickr for all of the images that are tagged with that term.
001
<form>
002
<input type="search"
placeholder="Search for
something" required>
003
<button>Find</button>
004
</form>
16
Flexibility of asynchronicity
In our original main.js file we’ll require domReady, app.js and jQuery. Newer versions of jQuery register themselves as a named AMD module called ‘jquery’. Again we’ll wait for domReady but here’s where the asynchronicity of RequireJS comes in to play – because we’re not waiting for a response from Flickr, this function will fire before the one above it.
001
require([‘domReady’, ‘app’, ‘jquery’], function (domReady, app) {002
domReady(function() {003
// next step004
});005
});002
var template = Handlebars.compile(slides);003
$(‘.slideshow’).html( template(data) );004
};13
Responsive slides plug-in
We’ll use Viljami Salminen’s ResponsiveSlides.js to easily convert our list of images into a slideshow that simply fades between each one. Again, because of our implementation, it’s trivial to swap this out with another library if needs change. Because we call this after the Handlebars template has compiled and been appended, we can trust that .slides exists.
001
$(‘.slides’).responsiveSlides();14
Use app module
That’s all there is to it! At this point the most interesting Flickr images will populate the slideshow. By arranging individual pieces of functionality into separate files, RequireJS encourages a more modular approach to your front-end software architecture. This is
something that other languages have had for some time and comes into its own in large-scale projects.