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. :)

 

Task-based web browsing: Grunt and the grunt-open plugin

JavaScript , Miscellaneous No Comments »

Lately I've been playing around with Grunt, which is a JavaScript-based task runner similar to Ant and Gradle. Generally, these tools are used to automate software builds, but they can be utilized in other ways.

While browsing through the large collection of Grunt plugins, I came across one called grunt-open. The grunt-open plugin lets you create a task that opens a URL in your web browser. It looks like it was created with the idea of opening a web page at the end of a chain of Grunt tasks to check the result of a software build. But I thought it might be useful in automating everyday web browsing tasks.

I like to start off my work day with a bit of Internet humor and I have certains sites I visit for that. Dilbert has a new strip every day, while JoyOfTech and XKCD get published every Monday, Wednesday, and Friday, and the XKCD "What-If" article is published once a week, usually by Thursday. Rather than manually opening the appropriate bookmarks for the appropriate day of the week, I figured I'd program a task to take care of opening the appropriate sites.

I won't reinvent the wheel here trying to explain the basics of Grunt: the Grunt "Getting Started" guide does a good job of explaining how to install Grunt and generate the files needed (the package.json and the Gruntfile.js files). Here's what my Gruntfile looks like:

 
module.exports = function( grunt )
{
    grunt.initConfig({
        open: {
            joyoftech: {
                path: 'http://www.geekculture.com/joyoftech/',
                app: 'Chrome'
            },
            dilbert: {
                path: 'http://www.dilbert.com/',
                app: 'Chrome'
            },
            xkcd: {
                path: 'http://www.xkcd.com/',
                app: 'Chrome'
            },
            xkcdwhatif: {
                path: 'http://what-if.xkcd.com/',
                app: 'Chrome'
           }
        }
    });

    grunt.loadNpmTasks( 'grunt-open' );

    grunt.registerTask( 'webhumor', 'Load the funnies!', function() {
        var today = new Date();
        var dayOfWeek = today.getDay();

        switch( dayOfWeek ) {
            case 1: //Monday
            case 3:
            case 5:
                grunt.task.run( [ 'open:joyoftech', 'open:dilbert', 'open:xkcd' ] );
                break;
            case 0:
            case 6:
            case 2:
                grunt.task.run( [ 'open:dilbert' ] );
                break;
            case 4:
                grunt.task.run( [ 'open:dilbert', 'open:xkcdwhatif' ] );
                break;
        }
    })
};

So in the directory where this Gruntfile.js lives, I just have to type "grunt webhumor' in the command, and Grunt will open the appropriate sites. Not a huge timesaver, but kinda cool.