Gregs

helping me remember what I figure out

Building Your Assets Using Grunt

| Comments

So last time I posted I gave a quick update on the book. This time is no different, I just wanted to share that I finished drafting the chapter on building less and JavaScript assets as part of the deployment process. What follows is the chapter, hope you find it useful.

By the way you can read the http://gregstewart.gitbooks.io/modern-web-app-development/manuscript/building_our_assets.html here).


Building our assets

Now that we have a pipeline up and running it’s time to turn our attention to dealing with building our code and assets and figure out how are our app will consume these. We do not want to check in our generated assets, however Heroku deploys using a git mechanism.

Let’s start by creating some tasks to generate our css, then concatenate and ulgyfy our JS and we’ll finish by deploying these assets to Heroku as part of a successful build. We’ll also add some tasks to run these tasks on our local machine and have these re-generated when we save changes to the file.

git checkout -c generate-assets

Compile our less to css

Let’s tackle with our CSS files. The first thing we want to do is add the destination folder for our css content to the .gitignore list:

node_modules
.idea
bower_components
phantomjsdriver.log
app/css/

Under our source folder let’s create a less folder and create a main.less file in it. Here’s content of the file:

@import '../../bower_components/bootstrap/less/bootstrap';

Our build tasks will take this import directive and create us a nice main.css file that we can then use in our app.

npm install grunt-contrib-less --save-dev

And now let’s create a task to generate our css files by adding to our Gruntfile:

module.exports = function (grunt) {
    grunt.initConfig({
        express: {
            test: {
                options: {
                    script: './server.js'
                }
            }
        },
        cucumberjs: {
            src: 'tests/e2e/features/',
            options: {
                steps: 'tests/e2e/steps/'
            }
        },
        less: {
            production: {
                options: {
                    paths: ['app/css/'],
                    cleancss: true
                },
                files: {
                    'app/css/main.css': 'src/less/main.less'
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-express-server');
    grunt.loadNpmTasks('grunt-selenium-webdriver');
    grunt.loadNpmTasks('grunt-cucumber');
    grunt.loadNpmTasks('grunt-contrib-less');

    grunt.registerTask('generate', ['less:production']);

    grunt.registerTask('e2e', [
        'selenium_start',
        'express:test',
        'cucumberjs',
        'selenium_stop',
        'express:test:stop'
    ]);

};

When you run grunt generate, you should see the following output:

Running "less:production" (less) task
File app/css/main.css created: 131.45 kB → 108.43 kB

If you were to start up our server and browse to localhost:3000, our UI should have more of a Bootstrap feel to it!

Rendered HTML with generated css Now bootstrap also needs some fonts, so let’s move these across as part of the build.

npm install grunt-contrib-copy --save-dev

And add a simple task to copy our fonts across as well:

module.exports = function (grunt) {
    grunt.initConfig({
        express: {
            test: {
                options: {
                    script: './server.js'
                }
            }
        },
        cucumberjs: {
            src: 'tests/e2e/features/',
            options: {
                steps: 'tests/e2e/steps/'
            }
        },
        less: {
            production: {
                options: {
                    paths: ['app/css/'],
                    cleancss: true
                },
                files: {
                    'app/css/main.css': 'src/less/main.less'
                }
            }
        },
        copy: {
            fonts: {
                expand: true,
                src: ['bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/*'],
                dest: 'app/css/bootstrap/',
                filter: 'isFile',
                flatten: true
            }
        }
    });

    grunt.loadNpmTasks('grunt-express-server');
    grunt.loadNpmTasks('grunt-selenium-webdriver');
    grunt.loadNpmTasks('grunt-cucumber');
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-contrib-copy');

    grunt.registerTask('generate', ['less:production', 'copy:fonts']);

    grunt.registerTask('e2e', [
        'selenium_start',
        'express:test',
        'cucumberjs',
        'selenium_stop',
        'express:test:stop'
    ]);

};

Running grunt generate task should now also copy our fonts across.

Fonts now included

This is great, but how do we get this to run as part of our successful build?

Heroku build packs

Heroku has a way to run commands after a build, these come in the form of build packs. Luckily for us someone has already gone through the effort of creating one to run Grunt after an install.

I have to say it’s not ideal, however given Heroku’s git based deployment approach, we have little choice but to generate these as part of the deployement. Typicall you would rely on the build to generate a package with all of the generated assets ready for consumption. This works though and does not force us to commit our generated assets into our repository.

So here’s how you go about installing our Build Pack for Grunt (be sure to replace --app lit-meadow-5649 with your actual heroku app name):

heroku login

heroku config:add BUILDPACK_URL=https://github.com/mbuchetics/heroku-buildpack-nodejs-grunt.git --app lit-meadow-5649

heroku config:set NODE_ENV=production --app lit-meadow-5649

Then we modify our Gruntfile to include a new heroku:production task, which basically references our build task:

module.exports = function (grunt) {
    grunt.initConfig({
        express: {
            test: {
                options: {
                    script: './server.js'
                }
            }
        },
        cucumberjs: {
            src: 'tests/e2e/features/',
            options: {
                steps: 'tests/e2e/steps/'
            }
        },
        less: {
            production: {
                options: {
                    paths: ['app/css/'],
                    cleancss: true
                },
                files: {
                    'app/css/main.css': 'src/less/main.less'
                }
            }
        },
        copy: {
            fonts: {
                expand: true,
                src: ['bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/*'],
                dest: 'app/css/bootstrap/',
                filter: 'isFile',
                flatten: true
            }
        }
    });

    grunt.loadNpmTasks('grunt-express-server');
    grunt.loadNpmTasks('grunt-selenium-webdriver');
    grunt.loadNpmTasks('grunt-cucumber');
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-contrib-copy');

    grunt.registerTask('generate', ['less:production', 'copy:fonts']);

    grunt.registerTask('e2e', [
        'selenium_start',
        'express:test',
        'cucumberjs',
        'selenium_stop',
        'express:test:stop'
    ]);

    grunt.registerTask('unit', [
        'karma:unit'
    ]);

    grunt.registerTask('heroku:production', 'generate');
};

The final step involves re-jigging package.json to include those newly added grunt tasks as a general dependency:

    {
    "name": "weatherly",
    "version": "0.0.0",
    "description": "Building a web app guided by tests",
    "main": "index.js",
    "engines": {
        "node": "~0.10.28"
    },
    "scripts": {
        "test": "grunt test"
    },
    "repository": {
        "type": "git",
        "url": "https://github.com/gregstewart/weatherly.git"
    },
    "author": "Greg Stewart",
    "license": "MIT",
    "bugs": {
        "url": "https://github.com/gregstewart/weatherly/issues"
    },
    "homepage": "https://github.com/gregstewart/weatherly",
    "dependencies": {
        "express": "^4.4.5",
        "grunt-contrib-copy": "^0.5.0",
        "grunt-contrib-less": "^0.11.3"
    },
    "devDependencies": {
        "chai": "^1.9.1",
        "cucumber": "^0.4.0",
        "grunt": "^0.4.5",
        "grunt-contrib-copy": "^0.5.0",
        "grunt-contrib-less": "^0.11.3",
        "grunt-cucumber": "^0.2.3",
        "grunt-express-server": "^0.4.17",
        "grunt-selenium-webdriver": "^0.2.420",
        "webdriverjs": "^1.7.1"
    }
}

This is another thing about this approach that I am not a fan of, having to move what are essentially development dependencies into our production dependencies.

Now we are nearly ready to test this out, however there is one more task we need to add. Since we are using Bower for some of our front end components and we haven’t checked these into our repository, we’ll need to restore them from our bower.json file. Let’s first install a new grunt package to assist us:

npm install grunt-bower-task --save

And the edit our Gruntfile.js:

module.exports = function (grunt) {
    grunt.initConfig({
        express: {
            test: {
                options: {
                    script: './server.js'
                }
            }
        },
        cucumberjs: {
            src: 'tests/e2e/features/',
            options: {
                steps: 'tests/e2e/steps/'
            }
        },
        less: {
            production: {
                options: {
                    paths: ['app/css/'],
                    cleancss: true
                },
                files: {
                    'app/css/main.css': 'src/less/main.less'
                }
            }
        },
        copy: {
            fonts: {
                expand: true,
                src: ['bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/*'],
                dest: 'app/css/bootstrap/',
                filter: 'isFile',
                flatten: true
            }
        },
        bower: {
            install: {
                options: {
                    cleanTargetDir:false,
                    targetDir: './bower_components'
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-express-server');
    grunt.loadNpmTasks('grunt-selenium-webdriver');
    grunt.loadNpmTasks('grunt-cucumber');
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-bower-task');

    grunt.registerTask('generate', ['less:production', 'copy:fonts']);
    grunt.registerTask('build', ['bower:install', 'generate']);

    grunt.registerTask('e2e', [
        'selenium_start',
        'express:test',
        'cucumberjs',
        'selenium_stop',
        'express:test:stop'
    ]);

    grunt.registerTask('heroku:production', 'build');
};

With that let’s push these changes and see if we can’t have a more nicely styled page appear on our Heroku app!

git add .
git commit -m "Generate less as part of the build and copy fonts to app folder"
git checkout master
git merge code-build
git push origin master

Concatenate and minify our JavaScript

Having generated our CSS at build time, it’s time to turn our attention to concatenating and minifying our JavaScript.

If you recall in our getting started section we set up our project and used Bower to manage our front end dependencies. For our code we will be Browserify and adopting a CommonJS approach to dealing with modules and dependencies.

To get started let’s first create a our source directory for our JavaScript, we’ll store our source under src/js and let’s create a file to test our build process called TodaysWeather.js and let’s save it under a sub folder called models:

var TodaysWeather = function () {
    console.log('test');
};

module.exports = TodaysWeather;

With that done let’s install a grunt task for Browserify

npm install grunt-browserify --save

The reason we have chosen a grunt task is that we will use this to export our source so that browsers can understand module.exports and use it to concatenate our code.

We’ll skip through a few steps below and edit our Gruntfile.js to include the task we just installed, define the steps to build our JavaScript and include it into our build task:

module.exports = function (grunt) {
    grunt.initConfig({
        express: {
            test: {
                options: {
                    script: './server.js'
                }
            }
        },
        cucumberjs: {
            src: 'tests/e2e/features/',
            options: {
                steps: 'tests/e2e/steps/'
            }
        },
        less: {
            production: {
                options: {
                    paths: ['app/css/'],
                    cleancss: true
                },
                files: {
                    'app/css/main.css': 'src/less/main.less'
                }
            }
        },
        copy: {
            fonts: {
                expand: true,
                src: ['bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/*'],
                dest: 'app/css/bootstrap/',
                filter: 'isFile',
                flatten: true
            }
        },
        bower: {
            install: {
                options: {
                    cleanTargetDir:false,
                    targetDir: './bower_components'
                }
            }
        },
        browserify: {
            dist: {
                files: {
                    'app/js/main.min.js': ['src/js/**/*.js']
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-express-server');
    grunt.loadNpmTasks('grunt-selenium-webdriver');
    grunt.loadNpmTasks('grunt-cucumber');
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-bower-task');
    grunt.loadNpmTasks('grunt-browserify');

    grunt.registerTask('generate', ['less:production', 'copy:fonts', 'browserify']);
    grunt.registerTask('build', ['bower:install', 'generate']);

    grunt.registerTask('e2e', [
        'selenium_start',
        'express:test',
        'cucumberjs',
        'selenium_stop',
        'express:test:stop'
    ]);

    grunt.registerTask('heroku:production', 'build');
};

If we now run our generate task you should find a main.min.js file under app/js, which contains a bunch of Browserify and our test file. However you will notice that while it’s concatenated it’s not minified. Let’s fix this.

I chose to go with Uglifyify, as always let’s just install it:

npm install uglifyify --save

And then edit our Gruntfile.js by configuring our browserify task to use it is a transform:

module.exports = function (grunt) {
    grunt.initConfig({
        express: {
            test: {
                options: {
                    script: './server.js'
                }
            }
        },
        cucumberjs: {
            src: 'tests/e2e/features/',
            options: {
                steps: 'tests/e2e/steps/'
            }
        },
        less: {
            production: {
                options: {
                    paths: ['app/css/'],
                    cleancss: true
                },
                files: {
                    'app/css/main.css': 'src/less/main.less'
                }
            }
        },
        copy: {
            fonts: {
                expand: true,
                src: ['bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/*'],
                dest: 'app/css/bootstrap/',
                filter: 'isFile',
                flatten: true
            }
        },
        bower: {
            install: {
                options: {
                    cleanTargetDir:false,
                    targetDir: './bower_components'
                }
            }
        },
        browserify: {
            dist: {
                files: {
                    'app/js/main.min.js': ['src/js/**/*.js']
                }
            },
            options: {
                transform: ['uglifyify']
            }
        }
    });

    grunt.loadNpmTasks('grunt-express-server');
    grunt.loadNpmTasks('grunt-selenium-webdriver');
    grunt.loadNpmTasks('grunt-cucumber');
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-bower-task');
    grunt.loadNpmTasks('grunt-browserify');

    grunt.registerTask('generate', ['less:production', 'copy:fonts', 'browserify']);
    grunt.registerTask('build', ['bower:install', 'generate']);

    grunt.registerTask('e2e', [
        'selenium_start',
        'express:test',
        'cucumberjs',
        'selenium_stop',
        'express:test:stop'
    ]);

    grunt.registerTask('heroku:production', 'build');
};

If you now run our generate task, the contents of main should be nicely minified. Now all that’s left to do is edit our index.html file and add our generated JavaScript file:

<!DOCTYPE html>
<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>Weatherly - Forecast for London</title>
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->

        <link rel="stylesheet" href="css/main.css">
    </head>
    <body>
        <!--[if lt IE 7]>
            <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
        <![endif]-->

        <!-- Add your site or application content here -->
        <div class="container">
            <div class="header">
                <ul class="nav nav-pills pull-right">
                    <li class="active"><a href="#">Home</a></li>
                    <li><a href="#">About</a></li>
                    <li><a href="#">Contact</a></li>
                </ul>
            </div>

            <div class="jumbotron">
                <h1>London Right Now</h1>
                <p class="temperature">14 degrees</p>
                <p>Mostly cloudy - feels like 14 degrees</p>
            </div>

            <div class="row marketing">
                <div class="col-lg-6">
                    <h4>NEXT HOUR</h4>
                    <p>Mostly cloudy for the hour.</p>

                    <h4>NEXT 24 HOURS</h4>
                    <p>Mostly cloudy until tomorrow afternoon.</p>
                </div>
            </div>

            <div class="footer">
                <p><span class="glyphicon glyphicon-heart"></span> from Weatherly</p>
            </div>

        </div>
        <p></p>

        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
        <script>window.jQuery || document.write('<script src="js/vendor/jquery-1.10.2.min.js"><\/script>')</script>

        <!-- Google Analytics: change UA-XXXXX-X to be your site's ID. -->
        <script>
            (function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]=
                    function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date;
                e=o.createElement(i);r=o.getElementsByTagName(i)[0];
                e.src='//www.google-analytics.com/analytics.js';
                r.parentNode.insertBefore(e,r)}(window,document,'script','ga'));
            ga('create','UA-XXXXX-X');ga('send','pageview');
        </script>
        <script src="js/main.min.js"></script>
    </body>
</html>

Before we commit our changes let’s edit our .gitignore file one more time and tell it not to include our generated JavaScript:

node_modules
.idea
bower_components
phantomjsdriver.log
app/css/
app/fonts/
app/js/

Let’s commit, merge and push to our remote repository:

git add .
git commit -m "JavaScript browserified and uglyfied"
git checkout master
git merge generate-assets
git push

Book Update

| Comments

It’s been some time since I announced that I was going to write a book on JavaScript and testing, but I do have a small-ish update to share. I have finished drafting the first section of the book, which covers getting up and running with continuous delivery. It’s very wrinkly and very much a stream of thoughts, so apologies for typos, omissions and bad grammar. However I would still like to encourage feedback! So if there’s anything you want to let me know about feel free to leave a comment or open up a Github issue.

I have been experimenting both with Gitbook and LeanPub to help with bringing this to the masses. You can view the Gitbook version here and the LeanPub one here. I’d be interested in hearing feedback about the two versions and which one you prefer. I really like the web version that Gitbook offer, however I much prefer LeanPub’s e-reader generated format.

I have also created a basic page with links to the publishers, a Facebook page and other bits and bobs.

That’s it, time to get back to some more writing!

Design Pattern: Parallel Change

| Comments

I recently read about this pattern and was curious to see who one would implement this in JavaScript. Below is the base class that will be refactored:

function Grid() {
    this.cells = [[]];
}

Grid.prototype.addCell = function (x, y, cell) {
    this.cells[x][y] = cell;
};

Grid.prototype.fetchCell = function (x, y) {
    return this.cells[x][y];
};

Grid.prototype.isEmpty = function (x, y) {
    return this.cells[x][y] == null;
};

In the example on the blog post, the x and y parameters were moved into a Coordinate object and the methods in question simply overloaded. However we don’t have this luxury in JavaScript. The simplest thing to do would be to check the arguments being passed into the individual methods, so here’s what that might look like:

function Grid() {
    this.cells = [[]];
    this.newCells = [];

    this.findCell = function (coordinate) {
        var foundCell;
        this.newCells.forEach(function (cell) {
            if (cell.coordinate.x === coordinate.x && cell.coordinate.y === coordinate.y) {
                foundCell = cell.cell;
            }
        });
        return foundCell;
    };
}

Grid.prototype.addCell = function (x, y, cell) {
    if(arguments.length > 2 && !isNaN(x)) {
        this.cells[x][y] = cell;
    } else {
        this.newCells.push({coordinate: x, cell: y});
    }
};

Grid.prototype.fetchCell = function (x, y) {
    if(arguments.length > 1 && !isNaN(x)) {
        return this.cells[x][y];
    } else {
        return this.findCell(x);
    }
};

Grid.prototype.isEmpty = function (x, y) {
    if(arguments.length > 1 && !isNaN(x)) {
        return this.cells[x][y] == null;
    } else {
        return this.findCell(x) == null;
    }
};

Not exactly elegant, but it works. While looking into options for method overloading I found this post by John Resig. Using his approach here’s what the original Grid class would look like:

// addMethod - By John Resig (MIT Licensed)
function addMethod(object, name, fn){
    var old = object[ name ];
    object[ name ] = function(){
        if ( fn.length == arguments.length )
            return fn.apply( this, arguments );
        else if ( typeof old == 'function' )
            return old.apply( this, arguments );
    };
}

function Grid() {
    this.cells = [[]];
}

addMethod(Grid.prototype, "addCell", function(x, y, cell){
    this.cells[x][y] = cell;
});

addMethod(Grid.prototype, "fetchCell", function(x, y){
    return this.cells[x][y];
});

addMethod(Grid.prototype, "isEmpty", function(x, y){
    return this.cells[x][y] == null;
});

So let’s go ahead and refactor the object to expand the interface and also take an object:

// addMethod - By John Resig (MIT Licensed)
function addMethod(object, name, fn){
    var old = object[ name ];
    object[ name ] = function(){
        if ( fn.length == arguments.length )
            return fn.apply( this, arguments );
        else if ( typeof old == 'function' )
            return old.apply( this, arguments );
    };
}

function Grid() {
    this.cells = [[]];
    this.newCells = [];

    this.findCell = function (coordinate) {
        var foundCell;
        this.newCells.forEach(function (cell) {
            if (cell.coordinate.x === coordinate.x && cell.coordinate.y === coordinate.y) {
                foundCell = cell.cell;
            }
        });
        return foundCell;
    };
}

addMethod(Grid.prototype, "addCell", function(x, y, cell){
    this.cells[x][y] = cell;
});

addMethod(Grid.prototype, "addCell", function(coordinates, cell){
    this.newCells.push({coordinate: coordinates, cell: cell});

});

addMethod(Grid.prototype, "fetchCell", function(x, y){
    return this.cells[x][y];
});

addMethod(Grid.prototype, "fetchCell", function(coordinates){
    return this.findCell(coordinates);
});

addMethod(Grid.prototype, "isEmpty", function(x, y){
    return this.cells[x][y] == null;
});

addMethod(Grid.prototype, "isEmpty", function(coordinates){
    return this.findCell(coordinates) == null;
});

For what’s it’s worth you can find the code and tests on my Github Design Pattern project

TCIAS: Now With Continuous Delivery

| Comments

This time last year, I reworked TCIAS and my initial focus was to develop the site using a build pipeline. I opted to a few tools:

The workflow went as follows, commit to Github, let Travis CI pick up the changes, on successful build, push coverage results to Coveralls. At that point I did a little bit of manual work, reviewed Coveralls, refreshed Code Climate and reviewed the results. The final step was to run:

Cap deploy

And push the changes to my server. Life was pretty good, but I was still missing that final step, automating the deployment to my box. Fast forward a year and a colleague suggested I check out Codeship.io.

I think I spent possibly an hour getting up to speed and configuring things, basically building on the Travis CI setup and in no time I had everything continuously deploying to my server. A few gotchas:

  • figuring out coveralls integration – as sadly it’s missing from the documentation section
  • figuring out my coveralls repo token
  • rvm ruby 2.1.0 – when the deployment kicked off and my deploy script started doing it’s thing on my box, it aborted complaining about rvm not being able to use ruby 2.1.0 (for the record I use rvm and had version 2.1.1 of ruby installed). Once I installed 2.1.0 everything was fine.

I am really impressed with how simple Codeship have made things. Maybe it’s because I ironed out a lot of the kinks when setting up Travis CI, however being able to just add a cap deploy configuration to your build pipeline that can use your Capistrano configuration without any change was just delightful. I highly recommend everyone check them out. They are incredibly helpful and communicative, very impressive and I am a happy customer, with a complete continuous integration pipeline.

Backbone the TDD Way

| Comments

Or how I decided to give Leanpub a whirl and publish a book.

I am not a prolific writer or even a good writer, but I have always enjoyed writing. I have often entertained the idea of writing a book. but was put off by the slow nature of publishing, the long road to getting your content out in front of people.

I like sharing what I find and learn, and the result was this blog. A little like writing a book, without the barriers to entry and I can get my thoughts and writings out quickly.

I work in technology, love what I do and am reasonably good at what I do. I am a big proponent of Agile, I like the visibility you get from the process, the iterative approach, the fail fast nature and continuously improving your project.

Last year I read a book published by a an ex-colleague of mine, Patrick Kua. He used a Leanpub to do so and this gave me an idea.

Leanpub’s motto is “Publish Early, Publish Often”, very agile, very lean. The barrier to publishing has been lowered. Everything is coming together, so today I am happy to announce that I am writing a book.

I am going to base the book on the series of blog posts I wrote a last year on Backbone.js and Jasmine. The content for those posts is available over at Github and I intend to keep the content of this book freely available over there as well, but for those folks who find/found the content useful, I figured maybe they would also like a book version (and yes maybe make a few quid out of it). If you are interested, then why not head over the books page and sign up to get notified when it will be available.

The book aims to take you through creating a basic app, introducing you to Backbone.js and using TDD as the guiding principle. Here’s a rough list of the chapters:

  • Setup
  • Building a search interface
  • Populating a result page
  • Testing routes
  • Tying it all together
  • Migrating your code to Require.js
  • Writing functional tests

I am looking for feedback on the topic, so feel free to raise any issues over there. If you have any suggestions for other areas to be covered please let me know.

So You Want to Write Some User Journey Tests

| Comments

On past projects I have relied on WebDriver to automate and write User Journey tests. This involved using either .Net or Java, which is fine, however much to my delight I was informed that there is a JavaScript version! Here’s a brief outline on how to get started with WebDriverJS and let the good times roll!

If you have node installed then you are good to go. If not, off you go ahead and install it now (you can build it from source too if you are that way inclined).

  1. open up your favourite terminal window and install selenium webdriver using npm: npm install selenium-webdriver
  2. WebDriverJS uses Mocha as it’s test runner, so go ahead and install that next: npm install -g mocha
  3. You will also need the selenium standalone server (I suggest you put the selenium stand alone server jar in a vendor folder in your project).

That’s all the software you will need, but before we get stuck in, I like to to follow a folder structure that looks a little like this:

vendor
test
    functional
        helpers
    unit

Right let’s write some code! I’ll start off with a few helper node modules (these live in test/functional/helpers) that’ll make things a little bit more re-usable. Let’s start with a helper to start the selenium server:

var assert = require('assert'),
    fs = require('fs'),
    remote = require('selenium-webdriver/remote'),
    SELENIUM = '../vendor/selenium/selenium-server-standalone-2.32.0.jar',
    server;

exports.setUpServer = function () {
    var jar = process.env.SELENIUM;
    if(!jar) {
        jar = SELENIUM;
    }
    assert.ok(!!jar, 'SELENIUM variable not set');
    assert.ok(fs.existsSync(jar), 'The specified jar does not exist: ' + jar);
    server = new remote.SeleniumServer({ jar: jar, port: 4444 });

    server.start();

    return server;
}

And now for the WebDriver helper:

var webdriver = require('selenium-webdriver');

exports.setUpWebDriver = function(server) {
    return new webdriver.Builder().
        usingServer(server.address()).
        withCapabilities({ 'browserName': 'firefox' }).
        build();
};

exports.By = webdriver.By;

This setup assumes you have FireFox installed, as it’s the simplest browser to get started with, but you can use a bunch of different ones. Now for our test, which will go to a page and assert the value of a title is correct.

var assert = require('assert'),
    test = require('selenium-webdriver/testing'),
    serverHelper = require('./helpers/server.helper'),
    webDriverHelper = require('./helpers/webdriver.helper'),
    driver,
    server;

test.before(function () {
    server = serverHelper.setUpServer();
    driver = webDriverHelper.setUpWebDriver(server);

    driver.get('http://www.google.co.uk/');
});


test.describe('Homepage view', function () {

    test.it('should have the correct title', function () {
        driver.getTitle().then(function (title) {
            assert.equal(title, 'Google');
        });
    });
});

test.after(function () {
    driver.quit();
    server.stop();
});

This file lives in your functional test folder and let’s call it test-journey.js. It basically sets up the server and driver (i.e. starts our browser and navigates to Google), before running the simple title assertion and when done, closes the browser and server. To run this test, in your terminal window inside test/functional, type:

mocha -R list test-journey.js

Keep your fingers crossed and after a few moments FireFox should fire up and run the test. If the test passed, then your console should display something like this:

. Homepage view should have the correct title: 407ms

1 test complete (13 seconds)

Pretty straightforward, no?

jQuery Dial

| Comments

A long with some of my work colleagues we recently built a jQuery Dial/Knob UI control that we decided to open-source and share with others. We tested it against the following browsers:

  • Chrome 23
  • Opera 12.11
  • Safari 6.0.2
  • Firefox 17
  • IE6+

Check out the project page, for instructions on how to use it and a working example. Let me know about your thoughts or indeed any issues.

What Is a Closure in JavaScript?

| Comments

If we turn to Google, invariably you are led to Stackoverflow. There’s a ton of information in that post, but I am going to try and put it into my on words so that the information sticks. A closure is a function with an inner function keyword AND you return that inner function. The inner function has access to the private member variables of the outer function. Here’s an example

 function foo(x) {
   var tmp = 3;
   return function (y) {
     alert(x + y + (++tmp));
   }
 }
 var bar = foo(2); // bar is now a closure.      
 bar(10);

But what is happening here? For starters we are creating our closure by calling bar = foo(2). So bar holds a reference to our closure, i.e. we are assigning bar a reference to our inner function. Of note as well is that the inner function is being returned from the outer function before being executed.

Now we invoke bar(10) which alerts 16, because bar() can see tmp and x. When you run bar(10) again and you get a slightly different result (i.e. 17) and that is because both x and tmp are still alive and well, and since tmp was incremented by 1 we know get 17 instead of 16. Also we have effectively we have closed over the internal variables, i.e. we can’t access tmp.

In this case our inner function here is an anonymous function, it could just as easily have been written as follows:

 function foo(x) {
   var tmp = 3;
   alertSum = function (y) {
     alert(x + y + (++tmp));
   };       
   return alertSum;
 }
 var bar = foo(2); // bar is now a closure.
 bar(10);

A closure is a special kind of object that combines both a function and the environment it was created in. The ‘environment’ refers to the local variables that are in scope, when the closure was created. In the previous example from Stackoverflow, that would be tmp and x. When the closure was created the value of tmp was 3, but then you invoked bar(10) the first time it’s value was incremented to 4. Now as described we invoked bar(10) again and got 17, because tmp now had a value of 5.

This combination of data and function, resembled key Object Oriented design construct, where it differs, is that we only work with one method here, where in OO an object has data and one or more methods that interact with the objects data.

Sometimes closures are also called function factories, because you can create new functions based on the initial value passed into the closure when you created it. Consider this example from the Mozilla article:

 function makeAdder(x) {
   return function(y) {
     return x + y;
   };
 }

 var add5 = makeAdder(5);
 var add10 = makeAdder(10);

 print(add5(2));  // 7
 print(add10(2)); // 12

It’s always helpful to come up with your own example, so how about using a closure to create a dice maker? If you have ever played Dungeon and Dragons, you need a bunch of different dice to play. Now we could use a closure as a function factory to create x number of sided dice and return a roll method that we could invoke on that die:

 function dieMaker(x) {
     var sides = x;

     function getRandomInt(min, max) {
       return Math.floor(Math.random() * (max - min + 1)) + min;
     }

     roll = function() {
         return getRandomInt(1, sides);                        
     }

     return roll;
 }​;

 var sixSidedDie = dieMaker(6);
 alert(sixSidedDie());

Backbone-Jasmine - Part 4: Display Results

| Comments

Display Results

Time to display the response back to the user, to this end we’ll leverage Underscore.js built-in templating language. Our template to represent the Feed model will end up looking something like this:

<dl>
    <dt>itemId</dt>
    <dd><%= itemId %></dd>
    <dt>timestamp</dt>
    <dd><%= timestamp %></dd>
    <dt>type</dt>
    <dd><%= type %></dd>
    <dt>featOfStrength</dt>
    <dd><%= featOfStrength %></dd>
    <dt>name</dt>
    <dd><%= name %></dd>
    <dt>quantity</dt>
    <dd><%= quantity %></dd>
</dl>

We’ll use a FeedView to populate the template, so given what we know about the FeedModel and the JS template, let’s go ahead and write some tests to populate the template from the model.

describe('Feed View', function () {
    beforeEach(function() {
        loadFixtures('search-results.html');
    });

    it('should render a view item based on model values', function () {
        var feedModel = new BackboneJasmine.Feed({
                'itemId':'1',
                'timestamp':'1',
                'type': 'LOOT',
                'featOfStrength': 'Feat of Strength',
                'name': 'Name',
                'quantity': '1'
            }),
            view = new BackboneJasmine.FeedView({model:feedModel}),
            el = '';

        view.render();

        el = $(view.el).find('dl dd');

        expect($(el[0]).text()).toBe(view.model.get('itemId'));
        expect($(el[1]).text()).toBe(view.model.get('timestamp'));
        expect($(el[2]).text()).toBe(view.model.get('type'));
        expect($(el[3]).text()).toBe(view.model.get('featOfStrength'));
        expect($(el[4]).text()).toBe(view.model.get('name'));
        expect($(el[5]).text()).toBe(view.model.get('quantity'));
    });
});

To get the tests to pass we need to first create a FeedView object and it will look as follows:

var BackboneJasmine = BackboneJasmine || {};

BackboneJasmine.FeedView = Backbone.View.extend({

    tagName: 'li',
    className: 'feed',
    model: BackboneJasmine.Feed,

    render: function() {
        var variables = {
            itemId: this.model.get('itemId'),
            timestamp: this.model.get('timestamp'),
            type: this.model.get('type'),
            featOfStrength: this.model.get('featOfStrength'),
            name: this.model.get('name'),
            quantity: this.model.get('quantity')
        };

        var template = _.template($('#result-item').html(), variables);
        this.$el.html(template);
    }

});

The next step involves building out the result view, which will be bound to our collection and display multiple FeedViews. Let’s start by fleshing out the test a little to get us started

describe('Result View', function() {
    beforeEach(function() {

    });

    it('should load a fixture', function () {
        expect($('section.search-results')).toExist();
    });

    it('should display a result data', function() {

        var els = $('.search-results > ul li');
        expect($('.search-results')).not.toBeHidden();
        expect(els.length).not.toBe(0);
        expect(els.find('dl > dd').first().text()).toBe('77022');
    });
});

We can get the first test to pass easily by creating our fixture and adding it to the spec, but we’ll also need to start start pulling our result view, which will be populated with our response fixture:

beforeEach(function() {
    loadFixtures('search-results.html');
    this.response = readFixtures('feed.json');

    this.view = new BackboneJasmine.ResultView();
    this.view.collection.add(JSON.parse(this.response).feed);
    this.view.render();
});

With this in place, we can build out the ResultView object.

var BackboneJasmine = BackboneJasmine || {};

BackboneJasmine.ResultView = Backbone.View.extend({
    el: 'section.search-results',

    initialize: function() {
        _.bindAll(this, 'addFeed');

        this.collection = new BackboneJasmine.SearchCollection();
        this.$el.hide();
        this.render();
    },

    render: function() {
        this.$el.show();
        this.collection.each(this.addFeed);
    },

    addFeed: function(feed) {
        var view = new BackboneJasmine.FeedView({model: feed}),
            feedItem = view.render().el;
        this.$el.find('ul').append(feedItem);
    }

});

‘This is an idealistic view’ of syncing data between your services and your UI. Next up I’ll look at customising the collection fetch method and later on extend this to make a JSONP call.

Backbone-Jasmine: Part 3 - Search Results

| Comments

In the following we will leverage Backbone Collections and the fetch feature in order to make a call to the service, receive some JSON and build up an array of models from that response. Let’s start with creating the collection tests:

describe('Search Collection', function() {

    beforeEach(function() {
        this.collection = new BackboneJasmine.SearchCollection();
    });

    it('should initialise with an empty collection', function() {
        expect(this.collection.length).toBe(0);
    });
});

Next populating the collection with some dummy data:

describe('fetch', function() {
    beforeEach(function() {
        this.server = sinon.fakeServer.create();
        this.server.respondWith('GET', '/search', [
            200,
            {"Content-Type": "application/json"},
            this.response
        ]);
    });

    afterEach(function() {
        this.server.restore();
        this.collection.reset();
    });

    it('should populate the collection', function() {

        this.collection.fetch();
        this.server.respond();

        expect(this.server.requests.length)
            .toEqual(1);
        expect(this.server.requests[0].method)
            .toEqual("GET");
        expect(this.server.requests[0].url)
            .toEqual("/search");

        expect(this.collection.length).toBe(JSON.parse(this.response).feed.length);
    })
});

Since the API is for a character’s feed, let’s call our model Feed. The call to fetch() will go away get the JSON and magically take the data and create one feed model for each feed entry returned and store it in the collection. However because the response is wrapped within a feed object, the collection object also has a parse method to handle the response and let Backbone to work the magic described previously.

var BackboneJasmine = BackboneJasmine || {};

BackboneJasmine.SearchCollection = Backbone.Collection.extend({
    model: BackboneJasmine.Feed,
    url:'search',

    parse:function (response) {
        return response.feed;
    }
});

All the code is in the ‘Search Results branch’.