AngularJS: RequireJS, dynamic loading and pluggable views

I’ve been using AngularJS for some time now and find declarative templates, the testability and dependency injection great.What I especially like is that the fact that AngularJS prescribes and dictates quite a lot, you are forced to bring some structure to the code of your application. This tends to reduce the velocity of the degeneration you often observe in larger JavaScript projects, where chaos slowly takes over.

But one of the things which was bothering me was that I always ended up adding some libraries/modules and forgetting to add them to my index.html. And sometimes after the list of loaded scripts grew quite a lot, figuring out where to insert this new script tag is really a pain.

Introducing RequireJS

So I decided some time ago to start using RequireJS to bring order in what was slowly becoming a mess. RequireJS does exactly what’s missing when you only rely on the AngularJS dependency injection: a file loader which supports dependencies and a way to define dependencies non-angular scripts (plain old JavaScript files).

Defining your AngularJS modules as RequireJS AMD modules with dependencies just means wrapping them in  a call to the define function e.g.:

define([
    'angular',
    'moment'
], function (angular, moment) {
    return angular.module('admin', [])
    	.controller('AdminController', ...);
});

Instead of loading all the scripts, you can replace all script tags by one of them loading RequireJS and referencing your main JavaScript file where RequireJS will be configured:

<script src="vendor/requirejs/require.js" data-main="scripts/main"></script>

main.js will contain the RequireJS configuration e.g.:

require.config({
    paths: {
        jquery: "../vendor/jquery/dist/jquery.min",
        jqueryui: "../vendor/jquery-ui/jquery-ui.min",
        angular: "../vendor/angular/angular.min",
        ngRoute: "../vendor/angular-route/angular-route.min",
    },
    shim: {
        angular: {exports: 'angular', deps: ['jquery']},
        jqueryui: {deps: ['jquery']},
        jquery: {exports: '$'},
        ngRoute: ["angular"],
    }
});

You also need to manually bootstrap your application to make sure that RequireJS loads all required dependency first. This is done by removing the ng-app attribute from your index.html and instead calling angular.bootstrap in main.js e.g.:

require.config({
	...
});

require([
        'angular',
        'scripts/app'
    ], function (angular, app) {
        angular.element(document).ready(function () {
            angular.bootstrap(document, ['cards']);
        });
    }
);

This will load AngularJS, then load your app and then bootstrap your application.

Introducing RequireJS is actually quite some work (if all your files and modules are already there) but not really complex. The main issue I have faced was that there is no explicit dependencies between AngularJS and JQuery, so they might be loaded in any random order. If AngularJS is loaded before jQuery, it will revert to using jqLite which is definitely not sufficient for jQuery UI. This will lead to strange errors occuring. So what you need to do is make sure that jQuery is loaded before AngularJS. This can either be done by setting priorities in your RequireJS configuration (only works with RequireJS 1.x) or by making jQuery a dependency of AngularJS (in the shim section of RequireJS 2.x – see example above).

OK, after all small problem were solved and my application was running again, I looked back at what I had done and realized that even though I didn’t ever had to add a script tag to my main HTML file, it wasn’t yet as great as I thought it would be before I started moving to RequireJS. First, I still have to manage dependencies on 3 levels:

  1. Dependencies between files i.e. file load order
  2. Dependencies between AngularJS modules
  3. Dependencies to individual controllers or providers

The first kind of dependencies is now managed by RequireJS instead of having me manage it in index.html. The other two have not changed.

Moreover, my application still looked like a big monolithic application at runtime since even though the file load order was now computed by RequireJS based on dependencies, I was still loading all files and interpreting them at application start. No matter whether some modules, controllers, directives… might only be needed much later or for specific users not at all.

Introducing Lazy Loading

So I started looking into lazy loading. Luckily I quickly realized that the step introducing RequireJS in my application hadn’t been useless. It is indeed possible to lazy load controllers, directives and such in AngularJS without resorting to using RequireJS. But you then need to manage the JavaScript files containing them and their dependencies to external libraries manually. With RequireJS, all you need to do to have all required files loaded before registering the lazy loaded controllers, views and such is to wrap it all in a require call.

There are different levels of lazy loading which can be achieved in AngularJS (having different levels of difficulty and restrictions).

Dynamically Loading Controllers and Views

The first level of lazy loading is dynamically loading controllers and views to an already loaded AngularJS module. To associate views with controllers, you would typically put your routing code in a module’s config function:

$routeProvider
     .when('/customers',
        {
            controller: 'CustomersController',
            templateUrl: 'views/customers.html'
        })
    .when('/orders',
        {
            controller: 'OrdersController',
            templateUrl: 'views/orders.html'
        })
    .otherwise({ redirectTo: '/customers' });

This means that everything will be loaded at once at runtime using the $routeProvider object. The referenced controller must have already been registered or you will get an error while defining the routing. Lazy loading the views (i.e. the referenced template URL) is supported out of the box but the JavaScript code for your controllers needs to be loading upfront.

To allow lazy loading, you will need to use the resolve property (additionally to the templateUrl) and also make sure that your controllers are properly registered once the JavaScript file is loaded. So using RequireJS to load the scripts and their dependencies, your code would look like this:

$routeProvider.when('/customers', {
    controller: 'CustomersController',
    templateUrl: 'views/customers.html',
    resolve: {
        resolver: ['$q','$rootScope', function($q, $rootScope)
        {
            var deferred = $q.defer();
            require(['views/customers'], function()
            {
                $rootScope.$apply(function()
                {
                    deferred.resolve();
                });
            });
            return deferred.promise;
        }]
    }
});

This makes sure that your controller doesn’t need to present immediately but that the resolver function will be called when this route is activated. The resolver function returns a promise we create using $q.defer. In order to load all required files and AMD modules, we wrap the logic in this function in a require block. Once the loading of the script and all dependencies is done, our callback is called which just resolves the deferred object.

Now, there is still a problem. All files will be loaded but you won’t be able to use the new controllers because they were created after startup. In order to have them properly registered, you will need to overwrite a few functions of your angular module to use compiler providers (such as $controllerProvider) instead (this needs to be done before using the $routeProvider). So you’re application js file would look like this:

define([
    'angular'
], function (angular) {
    var app = angular.module('app', []);

    app.config(['$routeProvider',
        '$controllerProvider',
        '$compileProvider',
        '$filterProvider',
        '$provide',
        function ($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) {
            app.controller = $controllerProvider.register;
            app.directive = $compileProvider.directive;
            app.filter = $filterProvider.register;
            app.factory = $provide.factory;
            app.service = $provide.service;

            $routeProvider.when(...);

            $routeProvider.otherwise(...);
        }]);

    return app;
});

Now you will see that when you activate this view, not only the HTML template file will be loaded on the fly, but also the JavaScript file containing your controller. And your controller will be accessible from the view.

This approach works well and doesn’t require much additional code but it only works if the controller you’re lazy loading is controller on an existing module. And even if it is the case, you’ll notice it doesn’t work that well, when the controllers you are lazy loading bring their own dependencies (other modules with controllers and directives).

Registering AngularJS modules dynamically

So to have a more robust and versatile solution, we need to be able to register and activate AngularJS modules dynamically (and the modules on which they are dependent).

In order to do it, we first need to understand what happens when you load a new module at startup. A module contains the following data which are relevant when you want to dynamically activate it:

  • A list of dependencies: module.requires
  • An invoke queue: module.invokeQueue
  • A list of config blocks to be executed on module load: module.configBlocks
  • A list of run blocks to be executed after injector creation: module.runBlocks

When a module is registered, AngularJS checks whether all referenced modules are available.

Whenever you call a function on your module (e.g. config, run, controller, directive, service…), it just pushes the provided function to a queue. In case of config, the queue is module.configBlocks. For run, it goes to module.runBlocks. For the others, it’s module.invokeQueue.

On module load, AngularJS will concatenate the run blocks (but without executing them yet), run the invoke queue and then run the config blocks. Once the modules are loaded, all run blocks will be executed.

When loading modules dynamically, the phase when modules are usually loaded is over, so even though the JavaScript files are loaded by RequireJS, the modules will not be properly initialized. So we just need to perform manually what’s usually done automatically by AngularJS.

Module Dependencies

So the first step is to make sure that modules on which this module is dependent are activated first. So the function to load a module would start like this:

this.registerModule = function (moduleName) {
    var module = angular.module(moduleName);

    if (module.requires) {
        for (var i = 0; i < module.requires.length; i++) {
            this.registerModule(module.requires[i]);
        }
    }

    ...
};

The Invoke Queue

Each entry in the invoke queue is an array with three entries:

  1. A provider
  2. A method
  3. Arguments

So in order to process it, we need to have a list of providers:

var providers = {
    $controllerProvider: $controllerProvider,
    $compileProvider: $compileProvider,
    $filterProvider: $filterProvider,
    $provide: $provide
};

And invoke the appropriate method with the provided arguments:

angular.forEach(module._invokeQueue, function(invokeArgs) {
    var provider = providers[invokeArgs[0]];
    provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
});

The Config and Run Blocks

To execute the config and run blocks, you can just rely on $injector:

angular.forEach(module._configBlocks, function (fn) {
    $injector.invoke(fn);
});
angular.forEach(module._runBlocks, function (fn) {
    $injector.invoke(fn);
});

The registerModule function

So the complete registerModule function would look like this:

this.registerModule = function (moduleName) {
    var module = angular.module(moduleName);

    if (module.requires) {
        for (var i = 0; i < module.requires.length; i++) {
            this.registerModule(module.requires[i]);
        }
    }

    angular.forEach(module._invokeQueue, function(invokeArgs) {
        var provider = providers[invokeArgs[0]];
        provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
    });
    angular.forEach(module._configBlocks, function (fn) {
        $injector.invoke(fn);
    });
    angular.forEach(module._runBlocks, function (fn) {
        $injector.invoke(fn);
    });
};

Registering pluggable views

Now that we can we can register modules dynamically, we’re only one step away from defining views which can be plugged into our application. Imagine you have an application, with multiple views showing different (possibly unrelated) data. If you want others to be able to extend your application, you need to provide a way to define a pluggable view and load all such views setting the appropriate routes and providing some model which can be used to display the corresponding navigation links.

In order to store all data required for configuring the routing and creating the navigation links, we’ll be using an object (viewConfig). The routing (assuming you’re using ngRoute) is then configured this way:

    $routeProvider.when(viewConfig.viewUrl, {
        templateUrl: viewConfig.templateUrl,
        controller: viewConfig.controller,
        resolve: {
            resolver: ['$q', '$timeout', function ($q, $timeout) {

                var deferred = $q.defer();
                if (angular.element("#"+viewConfig.cssId).length == 0) {
                    var link = document.createElement('link');
                    link.id = viewConfig.cssId;
                    link.rel = "stylesheet";
                    link.type = "text/css";
                    link.href = viewConfig.cssUrl;
                    angular.element('head').append(link);
                }
                if (viewConfig.requirejsConfig) {
                    require.config(viewConfig.requirejsConfig);
                }
                require([viewConfig.requirejsName], function () {
                    pluggableViews.registerModule(viewConfig.moduleName);
                    $timeout(function() {
                        deferred.resolve();
                    });
                });
                return deferred.promise;
            }]
        }
    });
};

It provides the following configuration possibilities:

viewConfig.viewUrl: it’s the relative routing URL e.g. “/admin”
viewConfig.templateUrl: it’s the relative URL for the HTML template of the view e.g. “views/admin/admin.html”
viewConfig.controller: it’s the name of the controller for the view e.g. “AdminController”
viewConfig.navigationText: it’s the text displayed on the navigation link e.g. “Administration”
viewConfig.requirejsName: it’s the name of the RequireJS AMD module e.g. “admin”
viewConfig.requirejsConfig: it’s the object to be added to the RequireJS configuration e.g. { paths: { ‘admin’: ‘views/admin/admin’ } }
viewConfig.moduleName: it’s the name of the module being loaded e.g. “app.admin”
viewConfig.cssId: it’s the ID of the link tag created to load the CSS stylesheet e.g. “admin-css”
viewConfig.cssUrl: it’s the relative URL of the CSS stylesheet file e.g. “views/admin/admin.css”

In order not to have to define all these parameters every time we call the function to register the view, we’ll first define some defaults to be set when some of these parameters are not explicitly set:

if (!viewConfig.viewUrl) {
    viewConfig.viewUrl = '/' + viewConfig.ID;
}
if (!viewConfig.templateUrl) {
    viewConfig.templateUrl = 'views/' + viewConfig.ID + '/' + viewConfig.ID + '.html';
}
if (!viewConfig.controller) {
    viewConfig.controller = this.toTitleCase(viewConfig.ID) + 'Controller';
}
if (!viewConfig.navigationText) {
    viewConfig.navigationText = this.toTitleCase(viewConfig.ID);
}
if (!viewConfig.requirejsName) {
    viewConfig.requirejsName = viewConfig.ID;
}
if (!viewConfig.moduleName) {
    viewConfig.moduleName = viewConfig.ID;
}
if (!viewConfig.cssId) {
    viewConfig.cssId = viewConfig.ID + "-css";
}
if (!viewConfig.cssUrl) {
    viewConfig.cssUrl = 'views/' + viewConfig.ID + '/' + viewConfig.ID + '.css';
}

Using this, it’s sufficient to call our provider function like this in order to have a link added for our administration view:

$pluggableViewsProvider.registerView({ ID: 'admin', moduleName: "admin", requirejsConfig: { paths: { 'admin': 'views/admin/admin' } } });

Since the way the navigation links are displayed pretty much depends on how you do it in your markup, our provider will not do it itself but just store the corresponding information and provide it through the provider:

this.views = [];
...
this.views.push(viewConfig);

So the complete module for our provider looks like this:

(function () {
    'use strict';

    define([
        'angular'
    ], function (angular) {
        return angular.module('pluggableViews', [])
            .provider('$pluggableViews', [
                '$controllerProvider',
                '$compileProvider',
                '$filterProvider',
                '$provide',
                '$injector',
                '$routeProvider',
                function ($controllerProvider, $compileProvider, $filterProvider, $provide, $injector, $routeProvider) {
                    var providers = {
                        $compileProvider: $compileProvider,
                        $controllerProvider: $controllerProvider,
                        $filterProvider: $filterProvider,
                        $provide: $provide
                    };
                    this.views = [];

                    var pluggableViews = this;

                    this.registerModule = function (moduleName) {
                        var module = angular.module(moduleName);

                        if (module.requires) {
                            for (var i = 0; i < module.requires.length; i++) {
                                this.registerModule(module.requires[i]);
                            }
                        }

                        angular.forEach(module._invokeQueue, function(invokeArgs) {
                            var provider = providers[invokeArgs[0]];
                            provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
                        });
                        angular.forEach(module._configBlocks, function (fn) {
                            $injector.invoke(fn);
                        });
                        angular.forEach(module._runBlocks, function (fn) {
                            $injector.invoke(fn);
                        });
                    };

                    this.toTitleCase = function (str)
                    {
                        return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
                    };

                    this.registerView = function (viewConfig) {
                        if (!viewConfig.viewUrl) {
                            viewConfig.viewUrl = '/' + viewConfig.ID;
                        }
                        if (!viewConfig.templateUrl) {
                            viewConfig.templateUrl = 'views/' + viewConfig.ID + '/' + viewConfig.ID + '.html';
                        }
                        if (!viewConfig.controller) {
                            viewConfig.controller = this.toTitleCase(viewConfig.ID) + 'Controller';
                        }
                        if (!viewConfig.navigationText) {
                            viewConfig.navigationText = this.toTitleCase(viewConfig.ID);
                        }
                        if (!viewConfig.requirejsName) {
                            viewConfig.requirejsName = viewConfig.ID;
                        }
                        if (!viewConfig.moduleName) {
                            viewConfig.moduleName = viewConfig.ID;
                        }
                        if (!viewConfig.cssId) {
                            viewConfig.cssId = viewConfig.ID + "-css";
                        }
                        if (!viewConfig.cssUrl) {
                            viewConfig.cssUrl = 'views/' + viewConfig.ID + '/' + viewConfig.ID + '.css';
                        }

                        this.views.push(viewConfig);

                        $routeProvider.when(viewConfig.viewUrl, {
                            templateUrl: viewConfig.templateUrl,
                            controller: viewConfig.controller,
                            resolve: {
                                resolver: ['$q', '$timeout', function ($q, $timeout) {

                                    var deferred = $q.defer();
                                    if (angular.element("#"+viewConfig.cssId).length == 0) {
                                        var link = document.createElement('link');
                                        link.id = viewConfig.cssId;
                                        link.rel = "stylesheet";
                                        link.type = "text/css";
                                        link.href = viewConfig.cssUrl;
                                        angular.element('head').append(link);
                                    }
                                    if (viewConfig.requirejsConfig) {
                                        require.config(viewConfig.requirejsConfig);
                                    }
                                    require([viewConfig.requirejsName], function () {
                                        pluggableViews.registerModule(viewConfig.moduleName);
                                        $timeout(function() {
                                            deferred.resolve();
                                        });
                                    });
                                    return deferred.promise;
                                }]
                            }
                        });
                    };
                    this.$get = function () {
                        return {
                            views: pluggableViews.views,
                            registerModule: pluggableViews.registerModule,
                            registerView: pluggableViews.registerView,
                            toTitleCase: pluggableViews.toTitleCase
                        };
                    }
                }]);
    });
}());

This is an example of how to use this provider in order to register pluggable views:

(function () {
    'use strict';

    define([
        'angular',
        'ngRoute',
        'pluggableViews',
        'views/nav/nav'
    ], function (angular) {
        var app = angular.module('cards', [
            'ngRoute',
            'pluggableViews',
            'cards.nav'
        ]);
        app.directive('navbar', function () {
            return {
                restrict: 'E',
                templateUrl: '../views/nav/nav.html'
            };
        });
        app.config(['$routeProvider',
            '$pluggableViewsProvider',
            function ($routeProvider, $pluggableViewsProvider) {
                $pluggableViewsProvider.registerView({
                    ID: 'walls',
                    moduleName: "cards.walls",
                    requirejsConfig: {paths: {'walls': 'views/walls/walls'}}
                });
                $pluggableViewsProvider.registerView({
                    ID: 'admin',
                    moduleName: "cards.admin",
                    requirejsConfig: {paths: {'admin': 'views/admin/admin'}}
                });
                $pluggableViewsProvider.registerView({
                    ID: 'reports',
                    moduleName: "cards.reports",
                    requirejsConfig: {paths: {'reports': 'views/reports/reports'}}
                });

                $routeProvider.otherwise({redirectTo: '/walls'});
            }]);

        return app;
    });
}());

In each view (which needs the navigation bar), I use the navbar directive which template contains the following:

<ul class="nav navbar-nav" ng-controller="NavigationController">
    <li ng-repeat="view in views track by $index" ng-class="navClass(view.ID)"><a href='#{{view.viewUrl}}'>{{view.navigationText}}</a></li>
</ul>

And the NavigationController gives us access to the views configured:

'use strict';
define([
    'angular',
    'pluggableViews'
], function(angular) {
    angular.module('cards.nav', [
        'pluggableViews'
    ])
        .controller('NavigationController', ['$scope', '$location', '$pluggableViews', function ($scope, $location, $pluggableViews) {
            $scope.navClass = function (page) {
                var currentRoute = $location.path().substring(1) || 'home';
                return page === currentRoute ? 'active' : '';
            };

            $scope.views = $pluggableViews.views;
        }]);
});

In this example, the pluggable views registered are hardcoded in my application but having a separate file containing the view configuration is fairly easy and would be a way to make your views truly work as plugins.

So that’s it for this post. We’ve build an provider which allows us to register views on the fly considering their dependencies and allowing the navigation bar or panel to be extended. Of course, you’ll still need to add some error handling, some logic to make sure that modules you depend on do not get activated multiple times… But this is all for a next post.

AngularJS: Running a directive after data has been fetched

In one of my AngularJS projects, I am using the angular-split-pane directive which is basically wrapping the split-pane JQuery plugin. This directive finds out how to split the panes and position the slider by reading the height and width attributes of the HTML tag.

I needed to add a functionality to my application so that the position of the slider is written to some session file on the server and reused next time the application is started to reposition the sliders at the same position, basically setting the height of the lower pane in percentage.

The previous position is fetched using $http. The problem is that it runs asynchronously and by the time the results are there, the directive has already been processed and changing the height tag has no effect anymore.

You can define the order in which directives are run by setting the priority property. But I needed a way to have the directive processed after the results from the server were available, not after or before another directive is processed.

After searching for a solution for quite some time, I finally find a way of implementing it late at night. This is actually pretty simple but for some reason it took me forever to think about such a solution.

When I fetch the data from the server I set some variable in my scope e.g.:

$http.get("../api/Config/").success(function(data) {
	sessionModel.ui = data;
});

Since I only want to execute the directive once the data is fetched, this means I want to have it executed once sessionModel.ui has a value. So all I need to do is add an ng-if attribute to my tag. Since the variable is initially not set, AngularJS will remove the element from the DOM and once the value is set, it will recreate it having the directive executed. So my tag would now look like this:

<split-pane ng-if="sessionModel.ui">

Of course the small disadvantage of this solution is that my UI is first shortly rendered without the split pane and when the data has been fetched, the split-pane is also rendered. In order not to have a partial rendering of the UI, you could of course move the ng-if attribute higher in your HTML code so that the whole UI is only rendered once the data from the server is available.

If your directive is not replacing the DOM element but just manipulating them, another solution would be to have the logic run asynchronously using $timeout and repeat until the data from the server is available. Or having it applied once the data is available by using $watch.

AngularJS: binding HTML code with ng-bind-html and $sce.trustAsHtml

I am working on a project in which based on some user interactions I am calling a service on the server and receive some pieces of HTML code which I need to display in the UI.

Of course I could just inject them in the DOM but to keep it clean I wanted to just store the HTML in the scope and bind it to an HTML tag. Basically doing something like this:

<!DOCTYPE html>
<html>

<head>
  <script src="https://code.angularjs.org/1.4.0-beta.6/angular.js" data-semver="1.4.0-beta.6" data-require="angular.js@*"></script>
  <link href="style.css" rel="stylesheet" />
  <script src="script.js"></script>
</head>

<body ng-app="myApp" ng-controller="myCtrl">
  <div ng-bind-html="myHtmlVar"></div>
</body>

</html>

The JavaScript code being:

var app = angular.module('myApp', []);

app.controller('myCtrl', function($scope) {
    $scope.myHtmlVar= "<a href='http://benohead.com'>benohead.com</a>";
});

This will unfortunately not work and you will get the following error message in the JavaScript console:

Error: [$sce:unsafe] Attempting to use an unsafe value in a safe context.

This basically means that the value we provided to ng-bind-html is not inherently safe and is also not marked as safe. So we need to mark it as trustworthy. This can be done by using $sce.trustAsHtml. An easy fix to the problem is to use the following:

app.controller('myCtrl', function($scope, $sce) {
    $scope.myHtmlVar= $sce.trustAsHtml("<a href='http://benohead.com'>benohead.com</a>");
});

If you do not want to add the calls to trustAsHtml everywhere in your code, you can also introduce a function in your scope and reference it in your page:

app.controller('myCtrl', function($scope, $sce) {
    $scope.myHtmlVar= "<a href='http://benohead.com'>benohead.com</a>";
    
    $scope.trustAsHtml = function(html) {
      return $sce.trustAsHtml(html);
    }
});

This function can then be called in the ng-bind-html attribute:

<div ng-bind-html="trustAsHtml(myHtmlVar)"></div>

Another way to do it is to define a filter which works in exactly the same way as the trustAsHtml function above:

app.filter('trustAsHtml', function($sce) {
  return function(html) {
    return $sce.trustAsHtml(html);
  };
});

app.controller('myCtrl', function($scope, $sce) {
  $scope.myHtmlVar = "<a href='http://benohead.com'>benohead.com</a>";
});

And use it like this:

<div ng-bind-html="myHtmlVar | trustAsHtml"></div>

Also note that if you want to set the HTML to the scope variable and then perform some DOM manipulation on this HTML code, you’ll run into a problem, because as long as your code is running, the data binding won’t be reflected until you return. Here is such an example:

app.controller('myCtrl', function($scope, $sce, $http) {
	$scope.myHtmlVar = "<a href='http://benohead.com'>benohead.com</a>";

	$scope.getHtmlCode = function (serviceParameter) {
		var responsePromise = $http.post("my-service-url", JSON.stringify(serviceParameter));
		responsePromise.success(function (data) {
			$scope.myHtmlVar = data;
			$scope.manipulateDom();
		});
	};
	
	$scope.manipulateDom = function () {
		//do some DOM manipulation on the retrieved HTML code...
	});
});

In order to execute some additional code, you’ll have to wrap it using $timeout:

app.controller('myCtrl', function($scope, $sce, $http, $timeout) {
	$scope.myHtmlVar = "<a href='http://benohead.com'>benohead.com</a>";

	$scope.getHtmlCode = function (serviceParameter) {
		var responsePromise = $http.post("my-service-url", JSON.stringify(serviceParameter));
		responsePromise.success(function (data) {
			$scope.myHtmlVar = data;
			$timeout(function() {
				$scope.manipulateDom();
			});			
		});
	};
	
	$scope.manipulateDom = function () {
		//do some DOM manipulation on the retrieved HTML code...
	});
});

 

Getting a file extension with JavaScript

For a current project, I needed to get the extension of a file being given it’s full path. In C#, you’d do something like this:

var extension = Path.GetExtension(path);

Unfortunately, we have no such possibility in JavaScript. Fortunately, writing a function returning the extension in JavaScript is not that difficult.

First you need to get the file name from the full path (basically getting rid of all parent directories in the path). In order to do this you have to also consider that you could either have a slash or a backslash as separator in the path.

The file name is basically, the last part of the path when you split it with the file path separator. This means you just need to use a split and a pop:

var filename = path.split('/').pop(); // when using slash as a file path separator

var filename = path.split('\\').pop(); // when using backslash as a file path separator

In order to handle both, you just need to do it twice:

var filename = path.split('\\').pop().split('/').pop();

It doesn’t really matter in which order you do it. If you use slashes, the first split/pop combination will not do anything. If you use backslashes, the second one will not do anything.

Getting the file name without path information is important because we’ll be searching for the extension based on dots in the path. If one of the parent directories does contain a dot, it will return a wrong results, so it’s safer to first get the file name without path information and then get the file extension.

Now we have the file name (without full path information), we need to extract the extension.

Let’s first look at which results we expect in different cases:

  • if the input name is an empty string, the returned extension should be an empty string as well
  • if the input name is does not contain any dots, the returned extension should be an empty string
  • if the input name contains only one dot and it’s the first character of the file name (e.g. .htpasswd), the returned extension should be an empty string
  • if the input name starts with a dot but contains another dot (e.g. .htpasswd.sav), the returned extension should be the part after the last dot
  • in general if the name contains any dots which are not at the beginning of the file, the returned extension should be the part after the last dot

You can get the index of the last dot in the file name using:

filename.lastIndexOf(".")

This will return -1 if no dot is present in the name. In this case, there is no extension and we can return an empty string:

var lastIndex = filename.lastIndexOf(".");
if (lastIndex < 0) return "";

Actually, even if it returns 0, it means that the file name starts with a dot and contains no further dots. In this case we also want to return an empty string:

var lastIndex = filename.lastIndexOf(".");
if (lastIndex < 1) return "";

In other cases (i.e. when we have at least one dot which is not at the beginning of the name), we want to exclude everything until the dot (including it) and return the rest:

var lastIndex = filename.lastIndexOf(".");
if (lastIndex < 1) return "";
return filename.substr(lastIndex + 1);

So the complete function to get the file extension from an arbitrary file path is:

function getFilePathExtension(path) {
	var filename = path.split('\\').pop().split('/').pop();
	var lastIndex = filename.lastIndexOf(".");
	if (lastIndex < 1) return "";
	return filename.substr(lastIndex + 1);
}

Now let’s test it a little bit and check that we do get the expected results:

console.log(getFilePathExtension(""));
console.log(getFilePathExtension("name"));
console.log(getFilePathExtension("name.ext"));
console.log(getFilePathExtension(".name"));
console.log(getFilePathExtension(".name.ext"));
console.log(getFilePathExtension("name.with.a.few.dots.ext"));
console.log(getFilePathExtension("/path/with/.a/few/dots"));
console.log(getFilePathExtension("/path/with/.a/few/.dots"));
console.log(getFilePathExtension("/path/with/.a/few/.dots/name.with.a.few.dots.ext"));

As expected the 4 tests where the name ends with “.ext” will return ext. The others will return an empty string.

You can make the function above even a little bit compacter (but less readable) by using Infinity as index in case the index returned is 0 or less. This is done by using:

  1. the Math.max function so that we get 0 whether lastIndexOf returns -1 or 0
  2. the fact that 0 is evaluated as false
  3. the fact that substr will return an empty string if the provided index is higher than the index of the last character
  4. the fact that Infinity + 1 is still higher than the index of the last character

So the compacter code looks like this:

function getFilePathExtension(path) {
	var filename = path.split('\\').pop().split('/').pop();
	return filename.substr(( Math.max(0, filename.lastIndexOf(".")) || Infinity) + 1);
}

 

AngularJS: Using Plupload in a dialog

Plupload is a very configurable JavaScript library you can use to allow your users to upload images. I’ve already used it in a few projects. I now wanted to use it in my current AngularJS project. Integrating Plupload in an AngularJS project is not especially difficult. There are already a few articles showing how this can be done. But it gets more difficult, when you try to integrate it into a dialog.

When initialized, Plupload puts an overlay above the browse button. This unfortunately only works if this element is displayed. If it is not displayed, the Plupload initialization will fail.

When working with dialogs, this means you need to execute the initialization of Plupload once the dialog is already displayed.

Let’s assume you have the use the following method to open a dialog using ngDialog:

$scope.uploadFile = function(){
	ngDialog.open({
		template: '/template/fileUploadDialog.html',
		controller: 'FileUploadController'
	});
};

and your controller looks like this:

app.controller('FileUploadController', function($scope){
	$scope.uploader = new plupload.Uploader({
		browse_button: 'browse',
		url: 'upload.php'
	});

	$scope.uploader.init();
});

Or if you are using angular-dialog-service:

$scope.uploadFile = function () {
	dialogs.create('/template/fileUploadDialog.html', 'FileUploadController', {}, {
		'backdrop': false,
		'size': 'sm',
		'copy': false,
		'windowClass': 'dialog-type-primary'
	});
};

and the same controller.

You will see that although you do not get any kind of error, nothing will happen when you click on the browse button. This is because no overlay was added by Plupload. If you put a breakpoint in the controller code, you will see that the dialog is not yet displayed when the code runs.

This is because Plupload has issues rendering inside elements which are hidden at initialization time You need to “refresh” Plupload after the dialog has been displayed:

$scope.uploader.refresh();

Now we know the root cause of the problem, all we need to do is either call the refresh method after displaying the dialog or just initialize Plupload after displaying the dialog. The problem is that I never found out how to provide a callback to get triggered after the dialog has been displayed.

After googling for it with no result, I decided to do it with a hack (won’t win a design price with this one but it works). Assuming the dialog will never take more than 300 milliseconds to display and the user will need at least 300 milliseconds to find the browse button, you can just solve it by executing some code asynchronously using setTimeout or $timeout (since we work with AngularJS):

app.controller('FileUploadController', function($scope, $timeout){
	$scope.uploader = new plupload.Uploader({
		browse_button: 'browse',
		url: 'upload.php'
	});

	$timeout(function () {
		$scope.uploader.init();
	}, 300);
});

Now your browse button should be working.

Plupload: previewing images and getting Data URI

I am working on a project where users can be created and assigned an image. The natural choice to upload images is to use Plupload. Plupload is a well-known JavaScript library to upload images using a variaty of uploader runtimes. It provides an HTML4 and an HTML5 runtime but also a Flash and a Silverlight runtime. You can configure a list of runtimes. Plupload will check for you which one is available on the particular client.

The usual way to use Plupload is to let the user select files with a native dialog or using drag&drop. You then upload the file to some server side business logic and display the progress. In my case, I wanted something different. First I want to show the selected image to the user and second I want to store the image not on the server but in a model on the client (which contains other data and will eventually be stored somewhere).

In this article, I will describe how to display a preview of the image in a canvas and how to convert the image to a Data URI.

First you need to deploy Plupload on your web server and reference it’s JavaScript file e.g.:

<script src="vendor/plupload/plupload.full.min.js"></script>

Then you need to have some HTML code to define a button which will be clicked by the user to open the file browser and a tag to display the image preview:

<a href="" type="button" id="selectfiles">Select</a>
<div id="preview"></div>

Now you’ll have to write some JavaScript code to initialize Plupload:

var uploader = new plupload.Uploader({
	runtimes: 'html5,flash,silverlight',
	browse_button: 'selectfiles',
	multi_selection: false,
	url: "about:blank",
	flash_swf_url: 'vendor/plupload/Moxie.swf',
	silverlight_xap_url: 'vendor/plupload/Moxie.xap',
	filters: [{title: "Image files", extensions: "jpg,gif,png"}]
});

uploader.init();

We’ve defined that Plupload should use the selectfiles element to let the user open the file browser. If you use the Flash and/or Silverlight runtimes, you have to point Plupload to the URL of those SWF and XAP files. And we define that a user can only choose one file and that it needs to be a GIF, JPEG or PNG file. This all pretty standard.

The only special trick is that we use “about:blank” as URL for the upload. Actually, you can specify whatever you want. It just needs to be a non-empty string. Otherwise Plupload will not initialize successfully. But since we won’t use Plupload to actually upload the file, we do not care about the value of this option, as long as it is defined.

Then we’ll want to be notified when the user as selected a file. So we need to register for an event:

uploader.bind("FilesAdded", filesAdded);

function filesAdded (uploader, files) {}

Now we’ll implement the filesAdded function. It will get the selected file and will need to embed that image in canvas in our preview element. Luckily, Plupload provides us with an Image class which provides us with all we need to do this:

function filesAdded (uploader, files) {
	$.each(files, function () {
		var img = new mOxie.Image();
		img.onload = function () {
			$('#preview').empty();
			this.embed($('#preview').get(0), {
				width: 100,
				height: 100
			});
		};
		img.onembedded = function () {
			this.destroy();
		};
		img.onerror = function () {
			this.destroy();
		};
		img.load(this.getSource());
	});
};

Now once the user select a file, it will be displayed as a preview. Now once the user decides to save, we can use this preview to save the image (note that this works in my case because I only want to store a 100×100 image and do not need the original image):

var dataURI = $("#preview canvas")[0].toDataURL();

Now you can store the data URI however you want.

Additionally, if you have a previously stored data URI and want to display it in the preview area, you can use the following code:

$('#preview').empty();
var $canvas = $("<canvas></canvas>");
$('#preview').append($canvas);
var context = $canvas.get(0).getContext('2d');
$canvas.attr('width', '100');
$canvas.attr('height', '100');
$('#preview').append($canvas);
var image = new Image();
image.onload = function () {
	context.drawImage(image, 0, 0, 100, 100);
};
image.src = $scope.data.user.image;

It just creates a canvas, loads the data URI in an Image object and has it drawn in the canvas’ 2D context.

Drag&Drop with AngularJS

I am working on a project where I have a board containing different lanes (a kind of grid) on which you can place cards and move them according to their completion status. In order to implement the drag&drop functionality, I am using the angular-dragdrop module.

It is a wrapper for jQueryUI draggable/droppable components which makes it easy to implement drag and drop functionality in AngularJS.

In order to install the module, just run the following in your terminal:

bower install angular-dragdrop

Then in order to load it, you need to reference its JavaScript file, just after the reference to angular.js (and also a reference to jQuery and jQuery UI which are used by this module):

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-dragdrop/src/angular-dragdrop.min.js"></script>

And add a dependency in your main module e.g.:

angular.module('myapp', [ 'ngDragDrop' ]);

Now, you’re ready to define draggable elements and droppable areas.

First, let’s make the card elements draggable. My elements looked like this before the change:

<div class="card">
	...
</div>

You need to add three custom attributes to this element:

<div class="card"
	 data-drag="true"
	 data-jqyoui-options="{revert: revertCard,
							helper: 'clone',
							appendTo: 'body',
							zIndex: 350}"
	 jqyoui-draggable="{index: 0,
						placeholder:true,
						animate:true,
						onStart: 'startCallback'}">
	...
</div>

jqyoui-draggable contain options passed to hte angular-dragdrop module. data-jqyoui-options contains the options passed to jQuery UI Draggable.

I’ve just copied the index, placeholder and animate options from some example, so you’ll have to use Google to find more info on them.

revertCard and startCallBack are functions on my controller which are used to control the way the draggable element react.

The zIndex is set so that the element is on top of all other elements while dragged. In my first try, I hadn’t set the appendTo and helper options. Since my draggable elements where contained in a container on which the overflow property was set, I could not move it out of its container. That’s because the element being dragged still had the same parent as the original element.

So you need to attach the element being dragged to the body of the page so that it can be moved anywhere. Unfortunately, attachTo with the “body” value only works if the helper option is set to “clone” i.e. it will not detach the element from its current container and attach it to the body but needs to work with a clone of the element which can be attached to the body.

The drawback is that the original element is still displayed. This is fine if you are writing an editor with some toolbox and want to use drag&drop to create new instances of this element. But if you actually want to move the element, it just doesn’t look good. So you need to additionally hide the original element while its clone is being dragged. That’s what is done in the startCallback of my controller:

$scope.startCallback = function (event, ui) {
	var $draggable = $(event.target);
	ui.helper.width($draggable.width());
	ui.helper.height($draggable.height());
	$draggable.css('opacity', '0');
};

This callback does two things:

  1. It makes sure that the clone element doesn’t grow to take up the whole screen (because its size is not limited by its parent anymore).
  2. It hides it by setting its opacity to 0.

In one of my early tries, I had hidden the original element by using display:none. The problem is that all other elements in this container would then move because this element was not displayed anymore. But setting its opacity to 0, it works as an empty placeholder.

Later on, once the dragging is stopped, I’ll make it visible again if required (only if it is not dropped at a valid location).

Usually, you will find that most example will set the revert option to “invalid”. This means that if you drop the component outside of a valid drop area, the helper will just come back to the original location and disappear. The problem is that since we’ve hidden the original element, you will end up with no element displayed at all. Thankfully, revert can also take a function as value. This is our second callback:

$scope.revertCard = function (valid) {
	if (!valid) {
		var that = this;
		setTimeout(function () {
			$(that).css('opacity', 'inherit');
		}, 500);
	}
	return !valid;
};

Returning false will cause the helper to return to the original location. The default duration of the animation is 500 milliseconds. Before returning true of false, we check whether the drop location was valid and if yes, we make the original element visible again. If we set the opacity to “inherit” immediately, the original element would be visible while the helper is moving back to the original location. That’s why we make the CSS change with a timeout of 500 milliseconds so that the original element is made visible as soon as the revert animation is finished.

Now we have a working draggable element and just need to define droppable areas where we can drop it:

<div class="lane"
	 data-drop="true"
	 jqyoui-droppable="{multiple: true,
						onDrop: 'dropCallback'}"
	 data-jqyoui-options="{hoverClass: 'ui-state-active',
							tolerance: 'pointer'}">
	...
</div>

The hoverClass is just used so that we can display the droppable area below the mouse pointer differently.

The tolerance option is used so that the element is dropped on the area below the mouse pointer and not on the area below the corner of the dragged element. This is useful when the dragged element is larger than the droppable areas.

We also define an additional callback in the controller:

$scope.dropCallback = function (event, ui) {
	var $lane = $(event.target);
	var $card = ui.draggable;
	if ($card.scope().card.lane != $lane.scope().lane.id) {
		$card.scope().card.lane = $lane.scope().lane.id;
	}
	else {
		$card.css('opacity', 'inherit');
		return false;
	}
};

This callback does two things:

  1. It updates the model so that the card is displayed on the lane it was dropped on.
  2. It prevents dropping the card on the lane it was on before dragging.

The first part is pretty straightforward. The second one just involves checking the original lane ID and the lane ID on which the drop operation is being performed. If they do not match we allow the drop. Otherwise we return false which is similar to a revert. Unfortunately, returning false doesn’t cause the revert callback to be called. So I have to change the opacity of the original element before returning false.

You’ll also notice that I do not set a timeout of 500 milliseconds. This is because returning false in the drop callback doesn’t cause a revert animation to be executed. I haven’t yet found out how to do this. So the helper just immediately disappears and I have to make the original element visible immediately.

That’s it ! It was actually pretty easy. Since you can set any draggable and droppable options without limitation, having this angular module between you and jQuery UI doesn’t seem to introduce any additional limitation. And since all references to event handler all point to callback in your controller, everything stays clean.

Update: I’ve also come accross an issue with a button on the card. I’m using the angular dialog service to display a dialog when a button on the card is clicked (using ng-click to trigger a function in my controller). At first, nothing happened when clicking on the button. This is because the mouse click didn’t go to the button. The solution was to increase the z-index of the button to 500 i.e. higher than the 350 configured for the draggable card.

Update: I’ve created a Plunker to show a working example.

WP Prism Syntax Highlighter not working on archive pages

Prism is a is a lightweight and extensible syntax highlighter written in JavaScript and CSS by Lea Verou. It’s fast and the rendered code looks great.

WP Prism Syntax Highlighter is one of the WordPress plugins integrating the Prism syntax highlighter. It is relatively new and I didn’t see much updating done but it works well.

Until now, I’ve only found one real problem with this plugin: althought it does work fine on individual posts, it just doesn’t work on archive pages and on the home page. So basically it fails to work on all pages showing lists of posts. It’s not an issue if you only display excerpts on those pages. But if you display full posts on them, all you see is the standard appearance of <pre> tags. This is because Prism will not be loaded on such pages.

The problem is that the plugin tries to limit the pages on which it loads Prism to those pages which actually do display some code. It does it with the following code:

private function decide_load_prism() {
	if ( strstr( get_post()->post_content, '<code class="language-' ) !== false ) {
		$this->load_pjsh = true;
	}
}

It basically gets the content of the current post and checks whether a Prism code tag is there. Only then will it load prism. This ensures that you do not load Prism on pages where you don’t need it in the first place e.g. posts without code, disclaimers, about me pages…

The problem is that this piece of code actually only works on single posts, not on archive pages or on the home page. Anyway for such pages, you’d need to check whether the whole post is displayed, if yes go through the posts being displayed and check whether code is present there. That’s kind of complex and probably the reason why it was implemented this way.

So to solve this you could delete the “if” line and the corresponding closing bracket. But then you’d load Prism on all pages including the ones where you definitely do not need it. After loading on all pages displaying code no matter whether a single post or a list of posts, the second best solution would be to make sure that single post pages only load Prism if required and on archives / on the home page, Prism is always loaded.

To implement it, open wp-prism-syntax-highlighter.php (http://YOURSITE/wp-admin/plugin-editor.php?file=wp-prism-syntax-highlighter/wp-prism-syntax-highlighter.php&a=te), search for “decide_load_prism” and replace this function by this one:

private function decide_load_prism() {
	if ( !is_single() || strstr( get_post()->post_content, '<code class="language-' ) !== false ) {
		$this->load_pjsh = true;
	}
}

This is almost the same as the previous one. The only difference is that it also loads Prism if is_single() is false i.e. we’re displaying a list of posts.

Now, the code displayed in my category archives and on my homepage also looks great !! Of course, if you don’t have a problem in the first place e.g. because you only display excerpts on these pages, then there is no point in implementing this since it will unnecessarily load Prism on these pages.

Please note that when the changes you make in a plugin will be overwritten after every update of the plugin. So until this issue is fixed in the plugin, you will need to reapply it after every update.

Update: There are no support requests on the plugin page on wordpress.org but I’ve just seen that there is a bug report on github.

JavaScript: variables in asynchronous callback functions

What’s the problem?

First let’s assume you have declared a variable called myVar:

var myVar;

If you immediately log the contents of the variable, it will return undefined.

Now if you define any of the following:

setTimeout(function() { myVar = "some value"; }, 0);

or:

$.ajax({
	url: '...',
	success: function(response) {
		myVar = response;
	}
});

or:

var script = document.createElement("script");
script.type = "text/javascript";
script.src = "myscript.js";
script.onload = function(){
	myVar = "some value";
};
document.body.appendChild(script);

and immediately afterwards display the contents of myVar, you will also get undefined as result.

Why is the variable not set?

In all examples above, the function defined are what’s called asynchronous callbacks. This means that they are immediately created but not immediately executed. setTimeout pushes it into the event queue, the AJAX calls will execute it once the call returns and onload will be executed when the DOM element is loaded.

In all three cases, the function does not execute right away but rather whenever it is triggered asynchronously. So when you log or display the contents of the variable, the function has not yet run and the content has not yet been set.

So even though JavaScript actually works with a single-thread model, you’ve landed in the asynchronous problem zone.

Synchronous vs. Asynchronous

Basically the difference between a synchronous function call and an asynchronous one can be shown with these 2 pieces of code:

var myVar;

function doSomething() {
	myVar = "some value";
}

//synchronous call
doSomething();

log(myVar);

And:

var myVar;

function doSomething() {
	myVar = "some value";
}

log(myVar);

//asynchronous call
doSomething();

In the asynchronous case, our function is also defined before logging the variable but is call some time later.

How to make it work?

You will need to rewrite your code in order to use callbacks which will be called when the processing (in this case setting the value of myVar) is done.

First example: Instead of using the following:

var myVar;

setTimeout(function() { 
    myVar = "some value"; 
}, 0);

alert(myVar);

You should rather do the following:

var myVar;

function callback() {
    alert(myVar);
}

setTimeout(function() { 
    myVar = "some value"; 
    callback();
}, 0);

Instead of this:

var myVar;

var script = document.createElement("script");
script.type = "text/javascript";
script.src = "d3.min.js";
script.onload = function(){
	myVar = "some value";
};
document.body.appendChild(script);

alert(myVar);

Use this:

function callback(response) {
    alert("loaded");
}

var script = document.createElement("script");
script.type = "text/javascript";
script.src = "d3.min.js";
script.onload = callback;
document.body.appendChild(script);

And instead of doing the following:

var myVar;

$.ajax({
	url: '...',
	success: function(response) {
		myVar = response;
	}
});

alert(myVar);

Do the following:

function callback(response) {
    alert(response);
}

$.ajax({
	url: '...',
	success: callback
});

Of course, here another solution is to make the AJAX call synchronous by adding the following to the options:

async: false

Also if you use $.get or $.post, you’ll need to rewrite it to use $.ajax instead.

But this is not a good idea. First if the call lasts longer than expected, you will block the UI making your browser window unresponsive. And at some point in the time the browser will ask your user whether you want to stop the unresponsive script. So, even though programing everything in an asynchronous way with callbacks is not always trivial, doing everything in a synchronous way will cause more sporadic and difficult to handle issues.

So to summarize, you should either use a callback or directly call a function after processing and not only rely on setting a variable in the asynchronous part.

JavaScript: Workaround for Object.defineProperty in IE8

Even though Internet Explorer 8 is now very old and outdated, there are unfortunately still many corporate users who are still stuck with this old browser. Many things are broken in IE8. One of them is the implementation of Object.defineProperty.

I noticed it when using the polyfill for String.startswith you can find on MDN looks. It looks like this:

if (!String.prototype.startsWith) {
  Object.defineProperty(String.prototype, 'startsWith', {
    enumerable: false,
    configurable: false,
    writable: false,
    value: function(searchString, position) {
      position = position || 0;
      return this.lastIndexOf(searchString, position) === position;
    }
  });
}

While Object.defineProperty is fully supported in IE9, it’s only partially supported in IE8. Internet Explorer 8 only supports defineProperty for DOM objects. If you use it for anything else, IE will complain that there is nothing like Object.defineProperty.

This is especially bad since calling defineProperty will cause and error which will prevent the rest of your JavaScript code to run.

So what can we do about it ?

The first thing I tried was:

        if (defineProperty){
            Object.defineProperty(String.prototype, 'startsWith', {
                enumerable: false,
                configurable: false,
                writable: false,
                value: function (searchString, position) {
                    position = position || 0;
                    return this.indexOf(searchString, position) === position;
                }
            });
        }

Unfortunately I then got an error in the if. So the easiest way to do this turned out to be to try to execute the defineProperty and ignore exceptions:

if (!String.prototype.startsWith) {
    try{ 
        if (defineProperty){
            Object.defineProperty(String.prototype, 'startsWith', {
                enumerable: false,
                configurable: false,
                writable: false,
                value: function (searchString, position) {
                    position = position || 0;
                    return this.indexOf(searchString, position) === position;
                }
            });
        }
    } catch (e) { };
}

After that, you can check again whether startsWith exists and add this function:

if (!String.prototype.startsWith) {
    String.prototype.startsWith = function(searchString, position) {
        position = position || 0;
        return this.indexOf(searchString, position) === position;
    };
}