Category Archives: Javascript

A Facebook Share component for AngularJS

There are several facebook share components available for AngularJS however I was needing something that could:

  • Show a count of the number of shares
  • Work within the translation infrastructure of my app (ie a custom template)
  • Handle different URL mappings for pages – whilst a typical angular url might be http://app.com/#/path/… facebook can’t scrape this unless you use the #! method or somesuch. Because of this typically on the server we map to different links such as http://app.com/landing/path/…

The below code snippets work together to do all of this in a few lines:

angularApp.directive("ngFbShare", function($http, $rootScope) {
    return {
        restrict: 'A',
        templateUrl: 'views/components/fbshare.html',
        scope: {
            ngFbShare: "@",
        },
        link: function(scope, elem, attrs) {
            var link; // always the latest link relative to the base of the server. Use this for click tracking etc
            var update_share_link = function() {
                link = '/landing/' + scope.ngFbShare;
                scope.share_link = MY_FULL_ADDRESS + link;
            };
            update_share_link();
            scope.$watch('ngFbShare', update_share_link);

            scope.fb_total_count = 0;
            $http.get('http://api.facebook.com/method/links.getStats?format=json&urls=' + scope.share_link)
                .then(function(res) {
                    scope.fb_total_count = res.data[0].total_count;
                });                
        }       
    };      
});             

<a href="http://www.facebook.com/sharer/sharer.php?u={{ share_link }}" target="fb_share_window" class="btn btn-primary btn-fb">
    <i class="fa fa-fw fa-facebook"></i> Share
    <span class="count">{{ fb_total_count }}</span>
</a>

Background Slideshow with AngularJS and Bootstrap

As part of a project we wanted to have the front page with a nice rotating background for the jumbotron. There are a number of carousel components and scripts that can be easily found online but mostly they use the img tag and/or require a root absolute div which means it won’t automatically resize to the jumbotron content. I wanted a jumbotron that would resize to the content and also provide a nice seamless transition for the images. So, I sat down and rolled my own.

Firstly you need to set up a jumbotron component:

.jumbotron-slideshow {
    position: relative;
    background-color: transparent;  // replace the standard bootstrap background color

    .slideshow {
        background-size: cover;
        background-repeat: no-repeat;
        background-position: 50% 50%;
        position: absolute;
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
        
        /* Layer the images so that the visible one is below all the others,
         * but the previously active one fades out to reveal the visible one
         * below */
        transition: opacity 1s;
        opacity: 0;
        
        &.visible {
            transition: none;
            opacity: 1;
            z-index: -1;
        }
    }   
}       

And then the HTML:

<div class="jumbotron jumbotron-slideshow">
    <div ng-bg-slideshow="[ 'images/bg1.jpg', 'images/bg2.jpg', ... ]" interval=5000></div>

    ... content that you want ...

Create the angular template to generate the image divs:

<div ng-repeat="img in images"
        class="slideshow" ng-class="{ visible: active_image == $index }" ng-style="{ 'background-image': 'url(' + img + ')' }">
    </div>  

And finally the Angular component:

app.directive("ngBgSlideshow", function($interval) {
    return {
        restrict: 'A',
        scope: {
            ngBgSlideshow: '&',
            interval: '=',
        },
        templateUrl: 'views/components/slideshow.html',
        link: function( scope, elem, attrs ) {
            scope.$watch( 'ngBgSlideshow', function(val) {
                scope.images = val();
                scope.active_image = 0;
            });

            var change = $interval(function() {
                scope.active_image++;
                if( scope.active_image >= scope.images.length )
                    scope.active_image = 0;
            }, scope.interval || 1000 );
        
            scope.$on('$destroy', function() {
                $interval.cancel( change );
            });
        }
    };  
});         

Note: If you want to be able to programatically change the interval you’ll need to add a watch that recreates the interval when the interval attribute changes.

Per-component loading spinner for AngularJS

One of the first things that people want to do with AngularJS is to have a loading spinner on their page to prevent the unseemly appearance of a page with no content loaded because you’re waiting on an ajax xhr request. There are quite a lot of these spinner plugins available, or you can relatively easily roll your own.

However most of these are whole-page ie if any infly request is happening, the whole page appears blocked to the user. This can be quite annoying and give the impression of your site being pretty slow. What other sites heavily dependent on ajax (eg Facebook and LinkedIn) typically do is have each individual block/component on the page display a loading graphic so that perhaps your friends list is marked as loading but your news feed had already loaded.

Fortunately with AngularJS’s awesome scope, factory and component design it’s very easy to bolt this on to an existing app in just a few minutes. Let’s look at some code.

Firstly, (as you should be doing already) you need to have your ajax request going through a single point in your code such as the skeletal factory below. I’d typically do something like this:

angularApp.factory('api', function( $http ) {
    var fns = {};
    var req = function( path, args, opts ) {
        var promise = $http.post( fns.get_url(path), args );

        return promise.then(function(res) {
            return res.data;
        });
    };

    // Two calls - nonblocked which doesnt show the spinner and req which does
    fns.nonblocked = req;
    fns.req = req
    return fns;
});

Then we extend this so that the req function can have a scope passed in which will have a variable called infly_http_request which contains the number of outstanding ajax requests under that scope. We now add this in to the api service replacing the req function with something that will check the requests:

    ...
    function setup_spinner( scope ) { 
        if( scope.hasOwnProperty('infly_http_request') )
            return;

        scope.infly_http_request = 0;
        
        var cur_timeout;
        scope.stop_blocked_request = function( ) { 
            if( cur_timeout )
                $timeout.cancel(cur_timeout);
                
            scope.infly_http_request--;
     
            if( scope.infly_http_request < 0 ) 
                scope.infly_http_request = 0;
        };  
        scope.start_blocked_request = function( ) {
            if( cur_timeout )
                $timeout.cancel(cur_timeout);

            cur_timeout = $timeout(function() {
                scope.stop_blocked_request( );
                // XXX raise error
            }, 10000);

            scope.infly_http_request++;
        };
    }
    fns.req = function( path, args, opts ) {
        if( !opts )
            opts = {};

        var scope = opts.scope || $rootScope;
        setup_spinner( scope );

        scope.start_blocked_request();
        return req( path, args, opts )
            ['finally'](function() {
                scope.stop_blocked_request( );
            });
     };

Basically if a scope option is passed in this will scope the spinner to that block, otherwise it will use the global scope so you can still do a whole-page lock.

Finally here’s a quick directive to apply to a nice and easy spinner using fontawesome:

// XXX has to be a subdirective to an ngController - can't be on the same level as it.
window.angularApp.directive('showSpinner', function() {
    return {
        transclude: true,
        template: '<div><ng-transclude ng-show="infly_http_request == 0"></ng-transclude><div ng-hide="infly_http_request == 0" class="subspinner-container"><i class="fa fa-cog fa-spin"></i></div></div>',
    }
});

And the LESS (CSS) to go with it:

@subspinner-size: 3em;
.subspinner-container {
    text-align: center;
    .fa-spin {
        font-size: @subspinner-size;
    }
}

You can then write your Angular component and HTML as:

angularApp.controller('Product.List', function( $scope, api ) {
    api.req( '/api/path', { data... }, { scope: $scope } )
        .then(...)
});

<div ng-controller="Product.List">
  <div show-spinner>
    ...
  </div>
</div>

Anything within the show-spinner container under the controller and the scope attribute passed in the req() call will be replaced by a spinner while the request is in progress. If not you can have something in the main body of your page to show a spinner like:

<div ng-if="infly_http_request" class="spinner-container">
    <div id="spinner">
        <i class="fa fa-cog fa-spin"></i>
    </div>
</div>

@spinner-size: 5em;
.spinner-container {
    position: fixed;
    top:0;
    left:0;
    right:0;
    bottom:0;
    z-index:10000;
    background-color:gray;
    background-color:rgba(70,70,70,0.2);
    #spinner {
        position: absolute;
        font-size: @spinner-size;
    
        margin-left: -0.5em;
        margin-top: -0.5em;

        z-index: 20000;
        left: 50%;
        top: 50%;
    }
}

Stop Grunt minifying libraries all the time

Recently I’ve been playing around with using a proper build system for my latest Angular project. I chose to start with grunt which seems very powerful if quite difficult to set up (mostly because yeoman did most of the initial config with it). However I find it very strange that by default the grunt-usemin plugin tries to minify and then concat all of the libraries even those such as jquery or angular. This is both not very efficient (as there are already .min.js files distributed with them), also it probably can’t do it as well as they can. So, I started doing a bit of research as to how this could be avoided and came up with the following.

Firstly install the grunt-usemin-uglifynew module:

npm install --save-dev grunt-usemin-uglifynew

Then, change your Gruntfile to look like this:

  var uglifyNew = require('grunt-usemin-uglifynew');
  grunt.initConfig({
    ...
    useminPrepare: {
    ...
      options: {
        flow: {
          html: {
            steps: {
              js_min: [uglifyNew, 'concat'],
              js: ['concat', 'uglifyjs'],
    ...
    usemin: {
      ...
      options: {
        blockReplacements: {
            // copy of js block replacement fn from fileprocessor.js
            js_min: function (block) {
              var defer = block.defer ? 'defer ' : '';
              var async = block.async ? 'async ' : '';
              return '<script ' + defer + async + 'src="' + block.dest + '"><\/script>';
            }
        },

and your html file(s) to look like:

    <!-- build:js_min(.) scripts/vendor.js -->
    <!-- bower:js -->
    <script src="bower_components/jquery/dist/jquery.js"></script>
    <script src="bower_components/angular/angular.js"></script>
    <script src="bower_components/angular-route/angular-route.js"></script>
    <!-- endbower -->
    <!-- endbuild -->

        <!-- build:js({.tmp,app}) scripts/scripts.js -->
        <script src="scripts/app.js"></script>
        <script src="scripts/controllers/main.js"></script>
        <script src="scripts/controllers/about.js"></script>
        <!-- endbuild -->

Basically this splits the js processor into two – js (the normal one) remains unchanged and continues to concat all the files in the block and then minify them. The other one, js_min just tries to find the .min.js file and then concats into a single file (I wish there was an easy way to avoid having to concat them – I think it should really just copy the .min.js to the build directory and update the links to point to them there)

Some observations of the GoogleBot

Recently rather than using cookies to gather data on people using some of my sites I’ve started using the newish html5 localStorage and generating a unique code on the first visit. This has a few advantages, mainly that it’s a bit more persistent than cookies, I already use localStorage to store customization data for the user client-side, it works seamlessly with PhoneGap/Cordova mobile apps and also I don’t have to worry about anything on the server side (ie setting, sending and tracking cookies). I use the roughly the following code (assuming localStorage is available for the browser, which in 99% of cases it is):

    if( localStorage['uuid'] == null) {
        first_use = 1;
        // 64-bit random number persistent between sessions
        localStorage['uuid'] = EDITION + Math.floor( Math.random() * 0x10000 ).toString(16)
                                + Math.floor( Math.random() * 0x10000 ).toString(16)
                                + Math.floor( Math.random() * 0x10000 ).toString(16)
                                + Math.floor( Math.random() * 0x10000 ).toString(16);
    }

I noticed that over several of my sites the GoogleBot was generating exactly the same uuid (over multiple access IPs) but it seemed that other localStorage preferences etc were not being saved. From this it seems like the GoogleBot doesn’t support saving stuff in localStorage (not a surprise given there are probably 10k computers running the GoogleBot scraper and it’s easier for them not to share site state). However it also appears that they are using the random number generator with a fixed seed so that any random numbers generated by the site are the same over all their scraper servers.

Conclusions? Don’t expect bots (or even some clients eg incognito mode) to actually save localStorage between sessions even if they support it as an interface (the modernizr test for localStorage is as follows:

try {
    var mod = 'modernizr';
    localStorage.setItem(mod, mod);
    localStorage.removeItem(mod);
    has_localstorage = 1;
} catch(e) {
}

which basically tests that the interface works, not that it is persistent between sessions). Also if you want truly random output when run in a bot, it looks like you’ll have to write your own pseudo-random number generator function with some changing seed perhaps based on Date.now() output. It doesn’t look like Javascript’s Math object supports a seed for the .random() function which, whilst I can understand this design means that you basically have to code your own random generator stack if you want to get truly random output for bots.

The little-known toJSON javascript function

I say little-known, perhaps it is not but at least I struggled for a while to figure out how to do a really simple thing – change the format that dates are sent out via a JSON call. Typically a date will be sent in ISO string format eg “2015-01-19T10:30:33.732Z” which is not ideal for my very thin backend service to dump into a database – I usually use a unix timestamp. Rather than having to add a whole load of parsing code and figuring out which was meant to be a date field and which is just a normal string I really wanted any dates to automatically serialize to a unix timestamp (or javascript timestamp which is unixtime*1000). The solution?

// All Dates want to get sent as timestamps...
Date.prototype.toJSON = Date.prototype.getTime;

Thanks to Javascript’s great prototype concept (this must be a good day – usually it winds me up quite a bit!) this means that any date objects will now get serialized into the format that I wanted. To send them all as unix-times you can just do:

Date.prototype.toJSON = function() { return Math.floor( this.getTime() / 1000 ) }

Having both a require and a controller in a directive

I’ve been playing around with AngularJS directives a lot recently trying to modularize out some commonly used bits of code – particularly form validation stuff. As part of this I often want a controller in the directive (so I have child scopes reach in and add elements or trigger events), but I also want to be able to access some parent angular things such as the ‘form’ directive’s controller.

Typically if you have a controller in a directive it gets passed in as the 4th argument to the link function eg:

angularApp.directive('ngValidSubmit', function() {
    return {
        controller: function($scope, $element, $attrs) {
        },
        link: function(scope, elem, attr, ngValidSubmitCtrl) {
        },
    }
});

However if you have a require argument it also gets passed in as the 4th element in link which unfortunately trumps our controller:

angularApp.directive('ngValidSubmit', function() {
    return {
        require: 'form',
        controller: function($scope, $element, $attrs) {
        },
        link: function(scope, elem, attr, formCtrl) {
        },
    }
});

I couldn’t find anything else referencing this problem on the internet, so as a hunch I tried (what turned out to be correct) multiple require arguments including requiring the directive itself:

angularApp.directive('ngValidSubmit', function() {
    return {
        require: [ 'form', 'ngValidSubmit' ],
        controller: function($scope, $element, $attrs) {
        },
        link: function(scope, elem, attr, ctrls) {
            var formCtrl = ctrls[0];
            var ngValidSubmitCtrl = ctrls[1];
        },
    }
});

Hopefully this helps someone!

Formatting time inputs nicely with AngularJS

Unfortunately the AngularJS code uses a fixed formatter for the time when a browser doesn’t support the time input natively (eg firefox). The code specifies ‘HH:mm:ss.sss’ which is pretty pointless for most users I would imagine (I’m building a calendering app). Fortunately we can hook into this formatter and fix it, although the ideal would be to override it unfortunately there are local variables that we can’t override particularly easily. So I took the easy route of just modifying the string after it had already been parsed:

/* Attach to input time elements and switch their formatting to be HH:MM
 */             
angularApp.directive('ngModel', function( $filter ) {
    return {
        require: '?ngModel',
        link: function(scope, elem, attr, ngModel) {
            if( !ngModel )
                return;
            if( attr.type !== 'time' )
                return;
                    
            ngModel.$formatters.unshift(function(value) {
                return value.replace(/:00\.000$/, '')
            });
        }
    }   
});         

Extending Array functionality safely in Javascript

So I’m using AngularJS to create some tables of data and want to have a simple way to sum the columns at the bottom. I could write a function and attach to $scope however I’d like to be able to use this with a minimum of typing and have it the same in the templates as in Javascript. A good situation to extend Array.prototype.

Array.prototype.sum_col = function(col) {
        var t = 0; 
        for( var i = 0; i < this.length; i++ )
            t += parseFloat( this[i][col] );
            
        return t;
    };

Nice and easy. However then I noticed that certain pages of my Angular app didn't load with a very random error. After some debugging in the chrome JS browser it came down to the fact that whilst the above 3-statement for loop works fine, if I now do for( var i in array ) it also includes the sum_col function. D'oh. So after some research on stackoverflow I figured out the correct way to add stuff to Array.prototype which should probably be done by default whenever you want to:

Object.defineProperty( Array.prototype, "sum_col", {
    enumerable: false,
    value: function(col) {
        var t = 0; 
        for( var i = 0; i < this.length; i++ )
            t += parseFloat( this[i][col] );
            
        return t;
    }       
});