Category Archives: Bootstrap

Styled cross-platform number input (in Angular but applicable to any HTML/CSS app)

Native elements in HTML 5 such as the number input are great, but unfortunately our designers often want them to render the same between different browsers and operating systems. For example on linux/chrome the number input has an up/down spinner on the right hand side which is always visible. On Mac the spinner is only visible on mouse over, on firefox it is rendered differently and on mobile the spinner is not there at all usually. In this case, my designer wanted number input with up/down buttons always available. This was for an order quantity input, so it should be integers from 1 upwards. With bootstrap you can easily add buttons etc to the left or right of an input, however I couldn’t see an easy way in ‘native’ bootstrap to have two buttons stacked as one of the addons, so I created my own html/less. This is for Angular 1, bootstrap 3 and fontawesome but it should be very easy to change for different platforms.

Here’s the HTML

<div class="number-input-group">
    <input class="noscroll" type="number" min=1 max=100 step=1 ng-model="extra.quantity"/>
    <div class="buttons">
        <div ng-click="extra.quantity = extra.quantity + 1"><i class="fa fa-caret-up"></i></div>
        <div ng-click="extra.quantity = extra.quantity > 1 ? extra.quantity - 1 : extra.quantity"><i class="fa fa-caret-down"></i></div>
    </div>
</div>

And the LESS:

.number-input-group {
    display: table; 
    width: 100%;    // fill container - remove if you want it as effectively an inline-block
    position: relative;
    border-collapse: separate;
    border-spacing: 0px;    
                                
    > input[type=number] {  
        display: table-cell;
        width: 100%; // keep biggest
        -moz-appearance:textfield;
        border-right: none;         
        &::-webkit-inner-spin-button, &::-webkit-outer-spin-button {
            -webkit-appearance: none;   
            margin: 0;                  
        }                           
    }                           
    > .buttons {            
        display: table-cell;
        width: 1%;      // shrink to smallest size
        vertical-align: top;
        border: @input-transparent-border-width solid @bespoke-light-black;
        //color: @bespoke-light-black;
        > div { 
            @number-input-group-arrow-box-size: (@input-padding-top * 2 + @input-line-height - @input-transparent-border-width - 1) / 2;
            line-height: @number-input-group-arrow-box-size * 0.8;  // make a bit smaller because ff and chrome mobile add a few px for some reason
            font-size: @number-input-group-arrow-box-size * 1;
            padding: 0 7px;
                            
            &:hover {
                background-color: lighten(@background-color, 15%);
            }

            &:last-child {
                border-top: @input-transparent-border-width solid @bespoke-light-black;
            }
        }
    }
}

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.

Easy ticks and crosses using FontAwesome

I spent a few minutes knocking up a comparison table for a project today and wanted an easy way to have ticks and crosses. After a bit of experimenting I found out that the excellent FontAwesome project makes this quite easy:

<span class="fa-stack">
    <i class="fa fa-circle fa-stack-2x"></i>
    <i class="fa fa-times fa-stack-1x fa-inverse"></i>
</span>
<span class="fa-stack">
    <i class="fa fa-circle fa-stack-2x"></i>
    <i class="fa fa-check fa-stack-1x fa-inverse"></i>
</span>

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%;
    }
}

Upgrading bootstrap LESS files stored in a git repo

Since starting a project several months ago with bootstrap 3.2.0 I noticed that there’s a newer version. I’ve been editing the LESS files quite a bit and adding various bits of my own in and then recompiling into a single CSS file for the project. As this is all stored in a git repo it’s a bit difficult (and I wouldnt want all of the extra stuff and history) to just pull and update from the bootstrap repo. So, the easy way to upgrade:

cd bootstrap-repo
git pull
git diff v3.2.0..v3.3.1 less/ > ../less.patch
cd .. # (go to main project)
git apply --reject --directory=bootstrap/ less.patch

You’ll probably have a handful of rejects (check for *.rej files) – fix those as with any patch apply, recompile your LESS and you’re done plus you don’t have any of the bootstrap history left around.

Replacing glyphicons with font-awesome in bootstrap

So, I wanted to use the much wider range of icons available with Font Awesome compared to the glyphicons in bootstrap. As most of them are in this icon set and as I’m already compiling bootstrap straight from LESS, it didn’t seem worth it to keep the glyphicons in there. However because I’m using Angular Bootstrap they already had a number of glyphicon sections embedded in the templates that I didn’t want to have to remember to change whenever I updated.

Anyway, to replace them first you download the less for bootstrap and font awesome, then you open up bootstrap/less/bootstrap.less, comment out the

@import "glyphicons.less";
line and add the following import:

@import "../../font-awesome/less/font-awesome.less";

You then need to edit font-awesome/less/variables.less and change the @fa-css-prefix: to be glyphicon rather than fa. Recompile and just include the general output in your html, no need for fa to be included as well any more. Then you have a drop-in replacement with many more icons available. Anything you can do with font-awesome can also be done with bootstrap you just have to remember to use glyphicon* rather than fa* in any CSS. So far I’ve only noticed that glyphicon-log-out and glyphicon-floppy-disk classes need to be changed to their fa equivalents.