Introducing Sparker: A Codebase Library Management Tool Showcasing AngularJS, Protractor, and Grunt Techniques

AngularJS , Grunt , JavaScript , Web development No Comments »

Sometimes projects take on a life of their own, and you end up with something unexpected.

I set out to create an template for CRUD-focused single page AngularJS web applications, something I and perhaps my colleagues could use as a foundation for writing new applications.  But under the momentum of self-applied scope creep, what I ended up creating was a Grunt-powered codebase library management tool, with my original template concept as the first codebase of potentially multiple foundational codebases.

After sitting back and looking at what I ended up with, I decided to name the project Sparker for two reasons:

  • I didn't want to use terms like "templates", "scaffolds", or "foundations" for the codebases housed in the project (partly because the project encourages creating demo codebases to accompany the "clean slate" template codebases).  So the codebases are organized into "sparks" and Sparker is the tool for managing them.

  • I wanted to focus on the inspirational aspect of the project:  that, at worst, the techniques in the sparks and in Sparker could "spark" ideas in other developers for how to approach certain problems.

At the end of the day, I see Sparker as a functional proof-of-concept, something individuals or particular teams can play around with or use in their shops to maintain and build off of their own spark libraries, but not something adopted for widespread use by the developer community.  It's not designed to compete with things like Yeoman or Express.js because, like I said, it didn't come out of a design.  But it was fun to develop, and I think there's value in sharing it.

Some of the things you'll find in Sparker today:

  • An example of how to organize Grunt tasks and task options into separate files, and how to execute two different sets of tasks from the same directory/Gruntfile.

  • A Grunt build task that determines which resource files to concatenate based on the <link> and <script> tags in your index.html file.

  • A technique for executing the login process and running certain sets of Protractor tests based on the user role being tested.

  • Convention-based Angular routes for restricting user access based on authentication state/user roles.

  • Example of mocking REST calls within a demo Angular application using the ngMockE2E module.

  • Examples of Angular unit tests and Protractor tests that utilize page objects.

Sparker is available on GitHub at https://github.com/bcswartz/Sparker.

Lightning Talk Presentations from the Recent AngularJS DC Meetup

AngularJS 1 Comment »

Last week I participated in a series of lightning talks at the AngularJS DC Meetup, hosted by Difference Engine, and I thought I'd share the links to the slide decks and demos presented (unfortunately, the equipment recording the entire event failed, otherwise I would just share that).

Not all of the presenters have posted their material, but here what's been shared so far:

Using Grunt to Concatenate Only the JavaScript/CSS Files Used in Index.html

Grunt , JavaScript , Web development 1 Comment »

One of the most common uses of the Grunt task runner is to build a deployment package out of your development code for your website or web application, and part of that build process is usually a task that concatenates the CSS and JavaScript files into singular (or at least fewer) files for optimal download.

The grunt-contrib-concat Grunt plugin allows you to configure a concatenation task to target individual files or entire directories, like so:

concat: {
            js: {
                src: [ 'dev/jquery/jquery.js', 'dev/angular/services/*.js', 'dev/angular/directives/*.js' ],
                dest: '../build/combined.js',
                options: {
                    separator: ';'
                }
            },
        }

The only drawback is that you have to update the task's "src" property as you add or remove CSS and JavaScript assets from your web application.

As I was playing around with Grunt on a personal project, I came to wonder: could I create a Grunt task or set of tasks that could figure out which files to concatenate based on the <link> and <script> tags in my code?  Here's what I came up with.

My project is a single page web application powered by AngularJS.  It has an index.html file that serves as the "single page" that displays the appropriate view fragment (HTML files stored in a "views" directory) for a given page/route in the application.  None of those view fragments require additional CSS or JavaScript resources, so my index.html page pulls down all of the necessary CSS and JavaScript files.

In my index.html file, I wrapped my blocks of <link> and <script> tags with special HTML "build" comments, a set for my CSS block and a set for my JavaScript block:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>My HTML Page</title>

    <!--build-css-start-->
    <link rel="stylesheet" media="screen" href="assets/app.css" />
    <link rel="stylesheet" media="screen" href="packages/bootstrap/dist/css/bootstrap.css" />
    <!--build-css-end-->

</head>
<body ng-app="demoApp">

    <!--Div where my Angular views will be displayed-->
    <div ng-view></div>

    <!--build-js-start-->
    <script type="text/javascript" src="packages/angular/angular.js" ></script>
    <script type="text/javascript" src="common/app.js" ></script>
    <script type="text/javascript" src="controllers/loginController.js" ></script>
    <script type="text/javascript" src="controllers/logoutController.js" ></script>
    <script type="text/javascript" src="services/authService.js" ></script>
    <script type="text/javascript" src="services/session.js" ></script>
    <!--build-js-end-->

</body>
</html>

I then created a Grunt task powered by the grunt-replace plugin that finds and parses those blocks via regex, extracting and modifying the file path within each "src" and "href" attribute and appending them to arrays stored as Grunt configuration properities. Each block is replaced by a single <link> and <script> tag pointed at the combined CSS and JavaScript file. The overall build task then executes the concat task, which concatenates all of the files in the respective configuration arrays into the combined files.

So when I run the master build task against my original set of development files:

Image of development directory

...I end up with a build directory containing the index.html file, the views folders with the view HTML files, and a single CSS and JavaScript file: 

Image of build directory

...and the index.html file itself looks like this:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>My HTML Page</title>

    <link rel="stylesheet" media="screen" href="combined.css"/>

</head>
<body ng-app="demoApp">

    <!--Div where my Angular views will be displayed-->
    <div ng-view></div>

    <script type="text/javascript" src="combined.js"></script>

</body>
</html>

Cool, right?

If you want to try it out for yourself, the entire Gruntfile used to power the build task is below, with comments:

module.exports = function( grunt )
{
    grunt.initConfig({
        //Create jsResources and cssResources configuration properties
        jsResources: [],
        cssResources: [],

        clean: {
            build: {
                dot: true,
                src: [ '../build/**/*' ]
            }

        },

        copy: {
            build: {
                files: [
                    {
                        cwd: '../app/',
                        dest: '../build/',
                        expand: true,
                        //Copy over the index.html file and all the files in the "views" directory
                        src: [ 'index.html', 'views/**' ]
                    }
                ]
            }
        },

        replace: {
            gather: {
                files: [
                    {
                        cwd: '../app/',
                        dest: '../build/',
                        expand: true,
                        src: [ 'index.html' ]
                    }
                ],
                options: {
                    patterns: [
                        {
                            //Grab the <!--build-js-start--> and <!--build-js-end--> comments and everything in-between
                            match: /\<\!\-\-build\-js\-start[\s\S]*build\-js\-end\-\-\>/,
                            replacement: function ( matchedString ) {
                                //Grab all of the src attributes from the <script> tags
                                var jsArray = matchedString.match( /(src\s?\=\s?[\'\"])([^\'\"]*)([\'\"])/g );
                                jsArray.forEach( function( element ) {
                                    //Get just the value of the src attribute (the file path to the JS file)
                                    var resourceTarget = element.match( /(src\s?\=\s?[\'\"])([^\'\"]*)([\'\"])/ )[ 2 ];
                                    targetConfig = grunt.config( 'jsResources' );
                                    //Alter the path for use with the concat task
                                    targetConfig.push( '../app/' + resourceTarget );
                                    //Add the path to the JS file to the jsResources configuration property
                                    grunt.config( 'jsResources', targetConfig );
                                });

                                //Replace the entire build-js-start to build-js-end block with this <script> tag
                                return '<script type="text/javascript" src="combined.js"></script>';
                            }
                        },
                        {
                            //Grab the <!--build-js-start--> and <!--build-js-end--> comments and everything in-between
                            match: /\<\!\-\-build\-css\-start[\s\S]*build\-css\-end\-\-\>/,
                            replacement: function ( matchedString ) {
                                //Grab all of the href attributes from the <href> tags
                                var cssArray = matchedString.match( /(href\s?\=\s?[\'\"])([^\'\"]*)([\'\"])/g );
                                cssArray.forEach( function( element ) {
                                    var resourceTarget = element.match( /(href\s?\=\s?[\'\"])([^\'\"]*)([\'\"])/ )[ 2 ];
                                    targetConfig = grunt.config( 'cssResources' );
                                    //Alter the path for use with the concat task
                                    targetConfig.push( '../app/' + resourceTarget );
                                    //Add the path to the CSS file to the cssResources configuration property
                                    grunt.config( 'cssResources', targetConfig );
                                });

                                //Replace the entire build-css-start to build-css-end block with this <link> tag
                                return '<link rel="stylesheet" media="screen" href="combined.css"/>';
                            }
                        }
                    ]
                }
            }
        },

        concat: {
            js: {
                //Concatenate all of the files in the jsResources configuration property
                src: [ '<%= jsResources %>' ],
                dest: '../build/combined.js',
                options: {
                    separator: ';'
                }
            },

            css: {
                //Concatenate all of the files in the cssResources configuration property
                src: [ '<%= cssResources %>' ],
                dest: '../build/combined.css'
            }

        }

    });

    grunt.loadNpmTasks( 'grunt-contrib-clean' );
    grunt.loadNpmTasks( 'grunt-contrib-concat' );
    grunt.loadNpmTasks( 'grunt-contrib-copy' );
    grunt.loadNpmTasks( 'grunt-replace' );

    grunt.registerTask( 'build', 'Creates a build from the files in the app directory', function() {
        //So the user doesn't have to add '--force' to the command to clean the build directory
        grunt.option( 'force', true );

        grunt.task.run ( [
            'clean:build',
            'copy:build',
            'replace:gather',
            'concat:css',
            'concat:js'
        ]);

    });

};

 

Angular 1.3x Makes ARIA Enhancement Simple With ngAria Module

AngularJS No Comments »

One of the JavaScript podcasts I listen to mentioned that the one of the new features in Angular 1.3x was the ngAria module.  I knew just from the name that the module had something to do with ARIA (Accessible Rich Internet Applications) but I wasn't sure what it did or how to implement it, so I decided to check it out.

Turns out using this new module is drop-dead simple.  You don't have to add any code to your markup in order to use it:  once you include the ngAria module in your Angular application, it'll automatically add and manage "aria-" attributes on your DOM elements, attributes that help screen readers understand what's going on in your application.

The folks over at Egghead.io have an excellent five minute video that demonstrates ngAria in action:  https://egghead.io/lessons/angularjs-using-ng-aria-to-automatically-improve-your-angularjs-accessibility

You can also read more about ngAria on the AngularJS documentation page on accessibility:  https://docs.angularjs.org/guide/accessibility

Frankly, I can't see a reason for not including this module in your AngularJS application if you're using Angular 1.3x.  It won't solve every accessibility issue, but it's a good starting point.

 

 

Using the "Controller As" Syntax With AngularJS Controllers

AngularJS , JavaScript No Comments »

I recently had a chance to try out the "controller as" alternative to storing model data and controller functions on the $scope of an Angular controller, and I was rather taken with the elegance of it.

An example of the familar method of attaching a controller to a view via the regular Angular router and exposing data and methods via the $scope:

Route:

...
when('/auth/sales/salesPage', {
  templateUrl: 'views/sales.html',
  controller: 'salesController'
})
...

 

Controller:

.controller( 'salesController', [ '$scope', 'demoService',  function( $scope, demoService ) {
   $scope.uiState = { pageLoaded: false };

   $scope.performAction = function() {
     console.log( "Action!" );
   }
 }]);

 

View:

<h1>Sales Prospects</h1>
<div ng-show="uiState.pageLoaded"></div>

 

...now the same code but using the "controller as" methodology:

 

Route:

...
when('/auth/sales/salesPage', {
  templateUrl: 'views/sales.html',
  controller: 'salesController as vm' //'vm' is now a reference to the controller object
})
...

 

Controller:

.controller( 'salesController', [ 'demoService',  function( demoService ) {
   var vm = this; //The controller object takes the place of the scope.
   vm.uiState = { pageLoaded: false };

   vm.performAction = function() {
     console.log( "Action!" );
   }
 }]);

 

View:

<h1>Sales Prospects</h1>
<-- The route provides the controller object to the view as 'vm' -->
<div ng-show="vm.uiState.pageLoaded"></div>

 

Some of the benefits of this "controller as" methodology:

  • You don't have to inject the $scope into the controller.
  • If you have multiple views with multiple controllers on the same page, you can now have unique references/namespaces for each controller and not worry about name collisions between each controller's $scope.
  • It looks cleaner.
  • Less typing of "$scope" means you won't wear out the 4/$ key on your keyboard as quickly. :)