JavaScript: Detect click outside of an element

If you want to implement some logic when a user clicks outside of an element (e.g. closing a dropdown), the most use solution is to add a click listener on both the document and the element and prevent propagation in the click handler of the element e.g.:

<html>
<head>
	<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
	<script>
	$(document).on('click', function(event) {
		alert("outside");
	});
	$(document).ready(function() {
		$('#div3').on('click', function() {
			return false;
		});
	});
	</script>
</head>
<body>
	<div style="background-color:blue;width:100px;height:100px;" id="div1"></div>
	<div style="background-color:red;width:100px;height:100px;" id="div2"></div>
	<div style="background-color:green;width:100px;height:100px;" id="div3"></div>
	<div style="background-color:yellow;width:100px;height:100px;" id="div4"></div>
	<div style="background-color:grey;width:100px;height:100px;" id="div5"></div>
</body>
</html>

If you use other libraries with add their own click handlers, it might break some of the functionality when stopping event propagation (see this article for more info). In order to implement this functionality in a way which doesn’t mess with the event propagation, you only need a click handler on the document and check whether the click target is the element or one of its children e.g. using the jQuery function closest:

<html>
<head>
	<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
	<script>
	$(document).on('click', function(event) {
		if (!$(event.target).closest('#div3').length) {
			alert("outside");
		}
	});
	</script>
</head>
<body>
	<div style="background-color:blue;width:100px;height:100px;" id="div1"></div>
	<div style="background-color:red;width:100px;height:100px;" id="div2"></div>
	<div style="background-color:green;width:100px;height:100px;" id="div3"></div>
	<div style="background-color:yellow;width:100px;height:100px;" id="div4"></div>
	<div style="background-color:grey;width:100px;height:100px;" id="div5"></div>
</body>
</html>

If you want to avoid jQuery and implement it in a pure JavaScript way with no additional dependencies, you can use addEventListener to had an event handler on the document and implement your own function to replace closest e.g.:

<html>
<head>
	<script>
	function findClosest (element, fn) {
		if (!element) return undefined;
		return fn(element) ? element : findClosest(element.parentElement, fn);
	}
	document.addEventListener("click", function(event) {
		var target = findClosest(event.target, function(el) {
			return el.id == 'div3'
		});
		if (!target) {
			alert("outside");
		}
	}, false);
	</script>
</head>
<body>
	<div style="background-color:blue;width:100px;height:100px;" id="div1"></div>
	<div style="background-color:red;width:100px;height:100px;" id="div2"></div>
	<div style="background-color:green;width:100px;height:100px;" id="div3">
		<div style="background-color:pink;width:50px;height:50px;" id="div6"></div>
	</div>
	<div style="background-color:yellow;width:100px;height:100px;" id="div4"></div>
	<div style="background-color:grey;width:100px;height:100px;" id="div5"></div>
</body>
</html>

The findClosest function just checks whether the provided function returns true when applied to the element and if not recursively calls itself with the parent of the element as parameter until there no parent.

If instead of using the element id, you want to apply this to all elements having a given class, you can use this function as second argument when calling findClosest:

function(el) {
	return (" " + el.className + " ").replace(/[\n\t\r]/g, " ").indexOf(" someClass ") > -1
}

 

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.

HTML5: Displaying the battery level

Note that this currently only works with Firefox. Support for Chrome and Internet Explorer is not yet available and it’s not yet known when it will be available.

The new Battery Status API allows you from a web page to access information about the battery status of the device rendering it. It is not only useful in order to show the current status but to implement battery status dependent logic e.g. increasing polling interval using Ajax or disabling animations in order to preserve the battery, or auto-saving in case the device goes out of power, prevent the user from performing long running operation which might not be able to finish because of the low remaining battery level.

First we’ll create the HTML and CSS in order to get an empty battery display looking like this:

The HTML code couldn’t be simpler. We just need a battery div:

<div id="battery"></div>

Now we need to display a border around the div and a smaller rectangle after it:

.battery:after {
    background-color: #fff;
    border: 2px solid #000;
    content: "";
    display: block;
    height: 16px;
    position: absolute;
    right: -6px;
    top: 6px;
    width: 6px;
}
.battery {
    background-color: #fff;
    border: 2px solid #000;
    height: 32px;
    margin-left: auto;
    margin-right: auto;
    position: relative;
    width: 100px;
}

Then we need to display a battery level within the battery display:

<div id="battery"><div id="battery-level"></div></div>

And we just need to add some CSS:

.battery .battery-level {
    background-color: #666;
    height: 100%;
}

By setting the width of .battery-level in pixels (or in percent which is the same since the element is 100 pixels wide) to the battery level in percents, we’ll get the right display:

Of course, one would expect our battery level display to show the level in a different color depending whether the battery is almost empty or not. So let’s introduce three CSS classes which will show the level in green, yellow or red:

Using the following CSS code:

.battery .battery-level.high {
    background-color: #66CD00;
}
.battery .battery-level.medium {
    background-color: #FCD116;
}
.battery .battery-level.low {
    background-color: #FF3333;
}

Before we move to the HTML5 part, let’s also display the battery level as text, by setting it as text on the battery-level element and by adding the following css rule:

.battery .battery-level {
    text-align: center;
}

It then looks like this:

10%
75%
95%

Now we’re almost done… All we need is what’s actually the main topic of this post: to get the battery level data using HTML5. For this we need to use the new Battery Status API.

The Battery Status API provides access the battery information through navigator.battery which implement the BatteryManager interface. This interface provides the following information:

  • whether the battery is currently charging
  • the charging time
  • the discharging time
  • the battery level

Additionally, it also provides events you can listen to:

  • onchargingchange
  • onchargingtimechange
  • ondischargingtimechange
  • onlevelchange

We will not use the events for now and just show the battery level when the page was loaded:

The JavaScript code to work with this API is pretty straight forward:

var battery = navigator.battery;
var level    = battery.level * 100;
var batteryLevel = jQuery('.battery .battery-level');
batteryLevel.css('width', level + '%');
batteryLevel.text(level + '%');
if (battery.charging) {
    batteryLevel.addClass('charging');
} else if (level > 50) {  
    batteryLevel.addClass('high');  
} else if (level >= 25 ) {  
    batteryLevel.addClass('medium');  
} else {  
    batteryLevel.addClass('low');  
}

If you want to update the battery level display as the battery charges and discharges:

Here’s the JavaScript code:

function updateBatteryDisplay(battery) {
	var level = battery.level * 100;
	var batteryLevel = jQuery('.battery .battery-level');
	batteryLevel.css('width', level + '%');
	batteryLevel.text(level + '%');
	if (battery.charging) {
	    batteryLevel.addClass('charging');
	    batteryLevel.removeClass('high');  
	    batteryLevel.removeClass('medium');  
	    batteryLevel.removeClass('low');  
	} else if (level > 50) {  
	    batteryLevel.addClass('high');  
	    batteryLevel.removeClass('charging');
	    batteryLevel.removeClass('medium');  
	    batteryLevel.removeClass('low');  
	} else if (level >= 25 ) {  
	    batteryLevel.addClass('medium');  
	    batteryLevel.removeClass('charging');
	    batteryLevel.removeClass('high');  
	    batteryLevel.removeClass('low');  
	} else {  
	    batteryLevel.addClass('low');  
	    batteryLevel.removeClass('charging');
	    batteryLevel.removeClass('high');  
	    batteryLevel.removeClass('medium');  
	}
}

var battery = navigator.battery;
updateBatteryDisplay(battery);
battery.onchargingchange = function () {
    updateBatteryDisplay(battery);
};
battery.onlevelchange = function () {
    updateBatteryDisplay(battery);
};

So the Battery Status API is a very powerful and useful API which is very easy to use. Now let’s hope it will soon be available in all major browsers !

First and last matching child in CSS and with jQuery

I recently noticed a few problems in some code I had written a few months ago:

  1. I am displaying a grid containing an empty first column and an empty last column and want to remove them. This only happened for the first row and not for the others
  2. I display a left border on all columns and additionally a right border on the last column. This also didn’t work anymore, the right border was missing.

These two problems were due to using the selector (once in CSS and once with jQuery) which do not do what I would expect.

First let’s assume we have a table-like div structure:

<div class="table">
    <div class="tr">
        <div class="th"></div>
        ...
        <div class="th"></div>
    </div>
    <div class="tr">
        <div class="td"></div>
        ...
        <div class="td"></div>
    </div>
    ...
    <div class="tr">
        <div class="td"></div>
        ...
        <div class="td"></div>
    </div>
</div>

In CSS, I can set the style of the first cell and of the last cell of each row like this:

div.tr div:first-child { }

div.tr div:last-child { }

To select them with jQuery:

jQuery("div.tr div:first-child")

jQuery("div.tr div:last-child")

Easy, right ? So where’s the problem ?

Well the problem is that is that it only worked by chance and as soon as I extended the generated html code, it broke. I needed to add some additional divs at the end of every line in the grid but have them be invisible. So basically, since the new divs where invisbile, the current last div would still have to be styled in a special way although it wasn’t the last div child anymore. So I just modified my CSS and jQuery selectors like this:

div.tr div.td:last-child { }
jQuery("div.tr div.d:last-child")

And the same for th as well. But it doesn’t work. What happens is that div.tr div.td:last-child doesn’t mean select the last child of div.tr being a div.td. It means select the last child of div.tr if it is a div.td. So since the last child of div.tr was not a div.td anymore, it failed.

So what I needed was a selector like :last-child-of-type. Well actually there is a selector :last-of-type. Unfortunately, it also failed using it since this selector doesn’t work with classes i.e. you can use div:last-of-type as selector but not div.class:last-of-type. So :last-of-type is useful to select the second div in this case:

<article>
    <p></p>
    <div></div>
    <div></div>
    <div></div>
    <p></p>
</article>

But not in our case. So what can be done ? Well, the way I went for (which just involved replacing an append by a prepend) was to move the hidden divs to the beginning of the row. So now I could use :last-child again. And since I was selecting the first line only with jQuery, I used the following:

$(".tr.cell").each(function() {
    $(this).children(".td").first().remove();
    $(this).children(".td").last().remove();
});

So it’d be great if :last-of-type could support classes or if there was a :last-of-class. But until there is, you can work around it with jQuery using a loop. In CSS, unfortunately, there seems to be no way to do it. The only way to select this last element of a type using CSS selectors is to assign it a class with javascript or while generating the HTML code and then selecting this class using CSS.

jQuery: Check whether a checkbox is checked

If you have never done it before when you need to react on a checkbox being checked on unchecked, you might try using the jQuery attr function e.g.:

<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
        $(function() {
                $('#mycheckbox').click(function () {
                      if ($(this).attr('checked')) {
                                $("#mydiv").html("checked");
                        }
                        else {
                                $("#mydiv").html("unchecked");
                        }
                });
        });
</script>
</head>
<body>
<input type="checkbox" id="mycheckbox"/>
<div id="mydiv"></div>
</body>

Well, this will not work. The condition will always return false and you’ll never see that the checkbox is checked.

There are a few different ways to get this piece of information. Instead of “$(this).attr(‘checked’)”, you can use any of the following conditions.

First “checked” is not an attribute of our checkbox but a property. In some versions of jQuery, calling attr(‘checked’) will return true/false, in others it will return checked or not and in others it will return the checked attribute set when creating the checkbox. Here a table from the jQuery documentation:

$( elem ).attr( "checked" ) (1.6) "checked" (String) Initial state of the checkbox; does not change
$( elem ).attr( "checked" ) (1.6.1+) "checked" (String) Will change with checkbox state
$( elem ).attr( "checked" ) (pre-1.6) true (Boolean) Changed with checkbox state

Now, in jQuery 1.10, it doesn’t seem to ever return anything else than undefined.

So since it’s not actually an attribute but a property, the obvious solution is to use the prop function:

if ($(this).prop('checked')) {...} else {...}

Since checked is a property, you can also get the first object returned by $(this) and evaluate the checked property:

if ($(this).get(0).checked) {...} else {...}

While at it, you can switch back to a pure JavaScript solution:

if (document.getElementById('mycheckbox').checked) {...} else {...}

Another option is to use the :checked selector:

if ($(this).is(':checked')) {...} else {...}

Or if it looks to simple and you like it more complex:

if ($('#mycheckbox:checked').val() !== undefined) {...} else {...}

Well, I guess I’ve shown you enough different ways to do this. You just have to choose which one you like best. My personal favourite is using the prop function. But I guess the more pure JavaScript, the faster but I doubt your users will ever notice the difference (except if they have to work with millions of checkbox but in this case I’d sue your UI designer).

jQuery: Keypressed and keydown event propagation in IE and Firefox

I’m working on a project where I have a button which starts some processing when pressed and wanted to add a keyboard shortcut (Ctrl-E) to also trigger this processing.

The processing on click is handled like this:

$("#evaluate").click(function () {
    ...
});

Now I first implemented the keyboard shortcut using a keypressed event:

$(document).bind('keypressed', function (event) {
	if (event.ctrlKey || event.metaKey) {
		switch (String.fromCharCode(event.which).toLowerCase()) {
			case 'e':
				$("#evaluate").click();
				return false;
		}
	}
});

Unfortunately, this does work in Firefox but not in Internet Explorer (the click is not triggered). For it to work in Internet Explorer, you need to use keydown instead of keypressed. So I changed it to look like this:

$(document).bind('keydown', function (event) {
	if (event.ctrlKey || event.metaKey) {
		switch (String.fromCharCode(event.which).toLowerCase()) {
			case 'e':
				$("#evaluate").click();
				return false;
		}
	}
});

Now it works in IE and Firefox. The only problem left was that in Firefox, the event is further propagated. In this case, the search box is activated.

I first thought I needed to add an event.preventDefault() and/or event.stopPropagation() but it didn’t help. The trick is actually to add a keypressed event handler which will return false if Ctrl-E is pressed:

$(document).bind('keydown', function (event) {
	if (event.ctrlKey || event.metaKey) {
		switch (String.fromCharCode(event.which).toLowerCase()) {
			case 'e':
				$("#evaluate").click();
				return false;
		}
	}
});
$(document).bind('keypressed', function (event) {
	if (event.ctrlKey || event.metaKey) {
		switch (String.fromCharCode(event.which).toLowerCase()) {
			case 'e':
				return false;
		}
	}
});

Now it works in both IE and Firefox !

CSS: Display checkboxes as switches

Checkboxes are kind of the most boring elements on a web page since the browsers always display them the same. Another problem is that the checkbox itself is pretty small. Of course it’s not a real problem for such checkboxes:

You can click on the checkbox itself or on the label to toggle it. So the small size of the checkbox itself is not an issue. But when you have something like this, it’s more of a problem:

Identification code:
Display name:
Activated:

Even if you wrap the checkbox in a label for it, you can still click anywhere near the checkbox to toggle it but it’s not visible to the user that it’s possible.

A better solution would be to have some alternative representation of the checkbox e.g. as a switch. It’s also important to be able to adapt the size of the switch. Here’s how it should look like in the end (the first one is the normal size and the second one is twice as large):

switches

Since you cannot style the checkbox itself, we’ll have to add a new element and hide the checkbox. Additionally, since some scripts will read the state of the checkbox, we need to keep the checkbox in there (even if not displayed) and we need to make sure that the state of the switch and the state of the check are in sync.

The easiest way to do it is to use a label. As can be see above if a label is created for a checkbox, clicking on the label toggles the checkbox. So the basic idea is to:

  • add a label for the checkbox
  • hide the checkbox
  • style the label to look like a switch
  • have a styling of the label based on the state of the checkbox

For the last point, we’ll leverage a special kind of CSS selector which selects elements that are placed immediately after a given element. This is done with a plus sign between the two elements:

input[type="checkbox"] + label

This would select the labels immediately following a checkbox. Using this we can style the labels depending on the state of the checkbox.

So first we need some HTML to have a checkbox:

<input class="switch" id="check1" type="checkbox" />

Note that I’ve used the class “switch” in case you do not want to turn all checkboxes into switches but only some of them. If you do want to turn them all into switches, just remove this class from all selector in the CSS and jQuery code in the rest of this post.

Your checkbox should also need to have an ID to be referenced by the label. Now let’s add the label dynamically using jQuery:

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript">
    $( document ).ready(function() {
        $('input[type="checkbox"].switch').each(function() {
            if ($(this).attr("id")) {
                $('<label for="'+$(this).attr("id")+'"></label>').insertAfter($(this));
            }
        });
    });
</script>

It does the following:

  • Search for all checkboxes to be turned into switches
  • Only change the ones with an ID
  • Add a label immediately after the checkbox referencing the ID of the checkbox

Note that we do not need a special class for the label as we need to make the appearance dependent on the checkbox state anyway so no extra selector for the label is required.

If you want to achieve a solution without JavaScript, you’ll have to add the label after the checkbox manually in the HTML code e.g.:

<input id="check1" class="switch" type="checkbox">
<label for="check1"></label>

I’d rather use JavaScript and CSS pseudo-classes than polute my HTML code with elements which are only needed for styling. But I guess others may see it in a different way and rather use pure CSS and HTML than introduce some JavaScript.

Now we have this all in place, we just need to style the checkboxes and labels. An easier way to do it would be to use a background image. But I tend to avoid using images except for icons. And also if the element has to have a variable size, I definitely try to avoid images. The goal in this post is anyway no do show you how to make nice looking backgrounds with CSS only so we’ll stick to a plain two color background.

The first step is to hide the checkboxes we’ll turn into switches:

input[type="checkbox"].switch {
	display: none;
}

Now we want the label to look like a switch, so have a border, be rounded on the left and right and have a background color depending on its state (e.g. red for not checked and green for checked):

input[type="checkbox"].switch + label {
	background-color: #EE9090;
	border: 1px solid black;
	border-radius: 999px 999px 999px 999px;
	display: inline-block;
	width: 1.6em;
}

For the border radius, you’ll need to add the following to make it work in older browsers (i.e. Firefox < 4.0, Chrome < 5.0, Safari < 5.0, iOS Safari < 4.0 and Android Browser < 2.2):

-moz-border-radius: 999px 999px 999px 999px;
-webkit-border-radius: 999px 999px 999px 999px;

Please also note that IE only supports it starting with version 9.0.

The inline-block is a great invention. For those who still don’t know it, it’s a kind of float on steroids. It means it handles width and height just like a block element. But is displayed inlined with the surrounding elements just like inline elements. So this means that the label will have a width and height although it doesn’t have a text and that the round element we’ll add afterwards will be displayed in it.

Note that we’ve defined the width in ems. This allows us to make the element scale when we increase the font size.

Now, we’ll use pseudo-classes to dynamically add a round element which will be moved from left to right when the checkbox is checked:

input[type="checkbox"].switch + label:before {
	background-color: silver;
	border: 1px solid black;
	border-radius: inherit;
	box-sizing: border-box;
	content: "";
	display: block;
	height: 0.8em;
	width: 0.8em;
}

So the content is empty because we do not want to display some text but only have a background and borders displayed.

box-sizing is used so that the width and height defined do already contain the borders. We want this element to be half the width of the containing label and to be round (so same height and width). Since the width of the label is defined in ems, the height and width of this element also have to be defined in ems. But since we need the border to always be 1 pixel (defining it also in ems makes it look terrible when the size is increased), we should define the width and height to be 0.8em – 1px. This is not possible. So we use box-sizing to say that the 0.8em already contains the border.

Note that box-sizing, requires a prefix for all versions of Firefox (both mobile and desktop) and required a prefix for Safari until version 5.0, for iOS Safari until version 4.3, for Blackberry Browser version 7.0, Android Browser until version 3.0. Also not that Internet Explorer version 7.0.

So you may want to add the following:

-moz-border-radius: inherit;
-web-kit-border-radius: inherit;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;

When the checkbox is checked, we want to change the background color and move the round element to the right. Changing the background color is easy. The trick to move it to the right is to add a padding to the label. But adding the padding will make the label larger so at the same time we have to reduce the width of the label:

input[type="checkbox"].switch:checked + label {
	background-color: #90EE90;
	padding-left: 0.8em;
	width: 0.8em;
}

So now the switch is working ! But we want the transition to be smoother when the checkbox is checked or unchecked. This is done with the transition-duration and transition-property CSS properties:

input[type="checkbox"].switch + label {
	transition-duration: 0.3s;
	transition-property: padding, width;
}

This means that when changing the padding and width the transition will be distributed over 300 milliseconds. You could also add the change of background color but I felt it looks kind of strange because it looks like it changing to some kind of brownish color between red and green. Also, for this CSS property there are also browser specific prefixes:

-webkit-transition-duration: 0.3s;
-moz-transition-duration: 0.3s;
-o-transition-duration: 0.3s;
-webkit-transition-property: padding, width;
-moz-transition-property: padding, width;
-o-transition-property: padding, width;

Now we’re done !

To get a larger switch use the following:

<div style="font-size:200%"><input id="check2" type="checkbox" /></div>

You can find the whole code below:

<html>
        <head>
                <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
                <script type="text/javascript">
                        $( document ).ready(function() {
                                $('input[type="checkbox"].switch').each(function() {
                                        if ($(this).attr("id")) {
                                                $('<label for="'+$(this).attr("id")+'"></label>').insertAfter($(this));
                                        }
                                });
                        });
                </script>
                <style type="text/css">
                        input[type="checkbox"].switch {
                                display: none;
                        }

                        input[type="checkbox"].switch + label:before {
                            background-color: silver;
                            border: 1px solid black;
                            border-radius: inherit;
                            -moz-border-radius: inherit;
                            -web-kit-border-radius: inherit;
                            box-sizing: border-box;
                            -moz-box-sizing: border-box;
                            -webkit-box-sizing: border-box;
                            content: "";
                            display: block;
                            height: 0.8em;
                            width: 0.8em;
                        }

                        input[type="checkbox"].switch:checked + label {
                            background-color: #90EE90;
                            padding-left: 0.8em;
                            width: 0.8em;
                        }

                        input[type="checkbox"].switch + label {
                            background-color: #EE9090;
                            border: 1px solid black;
                            border-radius: 999px 999px 999px 999px;
                            -moz-border-radius: 999px 999px 999px 999px;
                            -webkit-border-radius: 999px 999px 999px 999px;
                            display: inline-block;
                            transition-duration: 0.3s;
                            -webkit-transition-duration: 0.3s;
                            -moz-transition-duration: 0.3s;
                            -o-transition-duration: 0.3s;
                            transition-property: padding, width;
                            -webkit-transition-property: padding, width;
                            -moz-transition-property: padding, width;
                            -o-transition-property: padding, width;
                            width: 1.6em;
                        }
                </style>
        </head>
        <body>
                <input class="switch" id="check1" type="checkbox" />
                <br/><br/>
                <div style="font-size:200%"><input class="switch" id="check2" type="checkbox" /></div>
        </body>
</html>

A simple login dialog with HTML, CSS and jQuery

I’m currently working on a new software and I am working on both an administration page and a home page for the software. I also made a login page. Currently the login page looks kind of strange because it basically only has a few elements and takes up almost the whole screen (except a header, a navigation bar and a footer). So the screen is much too big for the few elements I have in there. So I decided to move the login page to a floating login dialog which is much smaller and thus looks better. Here is a short tutorial how to build such a dialog with HTML, CSS and jQuery.

First, you need to understand what dialogs in this context are. Dialogs are usually implemented in such a way that you have an HTML element floating on top of the page. In order to have it not displayed in the page or below the page, you need to define a z-index which defines where the element is displayed on the Z axis. This is done in CSS with the following:

        .dialog-popup {
            display: none;
            left: 50%;
            position: fixed;
            top: 50%;
            z-index: 99999; 
        }

Here a short explanation about the different CSS properties set above:

  • display: none => the dialog is not displayed when you open the page containing it but only when you click on some login link.
  • left: 50% / top: 50% => we want to have the dialog centered on the screen. This defines that the top left corner of the dialog will be in the middle of the screen. This means we’ll have to add some negative left and top margins when we display the dialog in order to have the middle of the dialog centered (the exact offset of course depends on the height and width of the displayed dialog.
  • position: fixed => this means that the position of the dialog doesn’t depend on the position of other elements but has a fixed position on the screen. Also note that the default value for position is static and with this set, the z-index will be ignored. So it’s important to set it to fixed.
  • z-index: 99999 => All elements with a z-index smaller than 99999 will be displayed below the dialog. If elements have no z-index defined, they inherit the z-index of their parent and if none is defined for any element, they all have a z-index of 0.

To make it look nicer, we’ll also had the following to the CSS for .dialog_popup:

            background: none repeat scroll 0 0 #CCCCCC;
            border: 2px solid #222222;
            border-radius: 3px 3px 3px 3px;
            font-family: Verdana,Geneva,sans-serif;
            padding: 10px;

Note that you may want to add the corresponding browser specific properties for border-radius.

Now let’s first move to the HTML code before we get back to the CSS. We need a link which will display the dialog:

<a href="#" data-selector="#login-dialog" class="dialog-link">Login</a>

The data-selector attribute is a custom attribute which is used to indicate which div ID is to be opened in the dialog when the link is clicked.

Now we’ll define the div for the dialog itself:

<div id="login-dialog" class="dialog-popup">
	<form method="post" action="#">
		<fieldset>
		</fieldset>
		<button class="submit" type="button">Login</button>
	</form>
</div>

To actually open the dialog, we’ll use jQuery. First we’ll load jQuery from Google:

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>

Then we’ll define that the dialog with the id defined in the data-selector attribute will be made visible with a fade in effect and its margins will be adapted so that it’s properly centered:

    <script type="text/javascript">
        $(document).ready(function() {
            $('a.dialog-link').click(function() {
                var dialog_id = $(this).attr('data-selector');
                $(dialog_id).fadeIn(200);
                $(dialog_id).css({ 
                    'margin-top' : -($(dialog_id).height() + 4) / 2,
                    'margin-left' : -($(dialog_id).width() + 4) / 2
                });
                return false;
            });
        });
    </script>

To go from the top left corner to the middle of the dialog, we’re defining a negative top and left margin corresponding to half the height and width of the dialog. We also add 4 pixels to the height and width because of the 2 pixels border on all 4 sides of the dialog.

If you want the dialog to be displayed faster, you should reduce the 200 parameter in the fadeIn call. If it’s too fast for you, increase it.

Now we’ll want to add some content to our login dialog:

  • A close button.
  • A username field.
  • A password field.
  • A link for users who forgot their password.

For the close button, I’ll this nice little icon:

close

With these additions, the HTML code for the dialog looks like this:

<div id="login-dialog" class="dialog-popup">
	<a href="#" class="close"><img src="close.png" class="close-button" title="Close this dalog" alt="Close" /></a>
	<form method="post" action="#">
		<fieldset>
			<label for="username">
				<span>Username</span>
				<input type="text" id="username" name="username" placeholder="Username" value=""></input>
			</label>
			<label for="password">
				<span>Password</span>
				<input type="password" id="password" name="password" placeholder="Password" value=""></input>
			</label>
			</fieldset>
		<button class="submit" type="button">Login</button>
		<a href="#" id="forgot-password-link">Forgot your password? Click here.</a>
	</form>
</div>

We’ll need to style it a little bit. First place the close button on the top right corner:

.dialog-popup .close-button {
	float: right;
	margin: -26px -26px 0 0;
}

Then we’ll remove the border and padding from the fieldset:

.dialog-popup form fieldset {
	border: medium none;
	padding: 0;
}

Then we’ll make the labels and input elements look nicer:

.dialog-popup form fieldset label {
	display: block;
	font-size: 1.2em;
}

.dialog-popup form fieldset label span,
.dialog-popup form fieldset label input,
.dialog-popup form button,
.dialog-popup form a {
	display: block;
	margin-bottom: 10px;
}

.dialog-popup form fieldset label input {
	background: none repeat scroll 0 0 #EEEEEE;
	border-color: #FFFFFF #DDDDDD #DDDDDD #FFFFFF;
	border-radius: 3px 3px 3px 3px;
	border-style: solid;
	border-width: 1px;
	color: #000000;
	padding: 6px 6px 4px;
	width: 200px;
}

Style the submit button:

.dialog-popup form button.submit {
	border-color: #000000;
	border-radius: 3px 3px 3px 3px;
	border-width: 1px;
	cursor: pointer;
	margin-top: 10px;
	padding: 6px 6px 4px;
	width: 200px;
}

.dialog-popup form button.submit:hover {
	background-color: #E0E0E0;
}

And the link for those who forgot their password:

#forgot-password-link {
	color: #00AE00;
	font-size: 0.7em;
	text-align: center;
	text-decoration: none;
	width: 200px;
}

Now that everything looks nice, we’ll also add an overlay below the dialog, so that we can make the page under the dialog darker. This way it is clear to the user, that he/she has to focus on the dialog and not the page in the background anymore. So first we need to add a div for the overlay e.g. after the definition of the dialog:

<div id="dialog-overlay" title="Click here to close the dialog">

This overlay is also hidden when the page is first displayed (just like the dialog itself) and will be displayed when the link is clicked. The overlay should cover the whole page, be dark and semi-transparent.

Being hidden by default means:

display: none;

Below the dialog but over of the page:

z-index: 99998;

Covering the whole page:

position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
height: 100%;
width: 100%;
margin-left: auto;
margin-right: auto;

Being dark and semi-transparent:

background: #000;
opacity: 0.75;

This makes the page in the background still visible but not really readable. If it’s too dark for you, just reduce the opacity: 0 means transparent, 1 means opaque (i.e. the page is not visible anymore).

Now that we’ve defined the overlay below the dialog, we need to have it display at the same time as the dialog itself. This is done by adding the following line just above the similar line making the dialog appear:

$('#dialog-overlay').fadeIn(200);

And we want both to disappear when the close button is clicked. Actually we also want both to disappear when you click on the overlay (so outside of the dialog). It is easy to do it with jQuery:

$('.dialog-popup a.close, #dialog-overlay').click(function() { 
	$('.dialog-popup').fadeOut(200);
	$('#dialog-overlay').fadeOut(200);
	return false;
});

And we’re done. The dialog looks like this when it is displayed:

login

In case you do not like puzzle games and would rather see the whole code at once, here it is:

<html>
    <head>
    <style>
        #dialog-overlay {
            background: #000;
            bottom: 0;
            display: none;
            height: 100%;
            left: 0;
            margin-left: auto;
            margin-right: auto;
            opacity: 0.75;
            position: fixed;
            right: 0;
            top: 0;
            width: 100%;
            z-index: 99998;
        }

        .dialog-popup {
            background: none repeat scroll 0 0 #CCCCCC;
            border: 2px solid #222222;
            border-radius: 3px 3px 3px 3px;
            font-family: Verdana,Geneva,sans-serif;
            padding: 10px;
            display: none;
            left: 50%;
            position: fixed;
            top: 50%;
            z-index: 99999; 
        }

        .dialog-popup .close-button {
            float: right;
            margin: -26px -26px 0 0;
        }

        .dialog-popup form fieldset {
            border: medium none;
            padding: 0;
        }

        .dialog-popup form fieldset label {
            display: block;
            font-size: 1.2em;
        }

        .dialog-popup form fieldset label span,
        .dialog-popup form fieldset label input,
        .dialog-popup form button,
        .dialog-popup form a {
            display: block;
            margin-bottom: 10px;
        }

        .dialog-popup form fieldset label input {
            background: none repeat scroll 0 0 #EEEEEE;
            border-color: #FFFFFF #DDDDDD #DDDDDD #FFFFFF;
            border-radius: 3px 3px 3px 3px;
            border-style: solid;
            border-width: 1px;
            color: #000000;
            padding: 6px 6px 4px;
            width: 200px;
        }

        .dialog-popup form button.submit {
            border-color: #000000;
            border-radius: 3px 3px 3px 3px;
            border-width: 1px;
            cursor: pointer;
            margin-top: 10px;
            padding: 6px 6px 4px;
            width: 200px;
        }

        .dialog-popup form button.submit:hover {
            background-color: #E0E0E0;
        }    

        #forgot-password-link {
            color: #00AE00;
            font-size: 0.7em;
            text-align: center;
            text-decoration: none;
            width: 200px;
        }
    </style>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script type="text/javascript">
        $(document).ready(function() {
            $('a.dialog-link').click(function() {
                var dialog_id = $(this).attr('data-selector');
                $('#dialog-overlay').fadeIn(200);
                $(dialog_id).fadeIn(200);
                $(dialog_id).css({ 
                    'margin-top' : -($(dialog_id).height() + 4) / 2,
                    'margin-left' : -($(dialog_id).width() + 4) / 2
                });
                return false;
            });

            $('.dialog-popup a.close, #dialog-overlay').click(function() { 
                $('.dialog-popup').fadeOut(200);
                $('#dialog-overlay').fadeOut(200);
                return false;
            });
        });
    </script>
    </head>
    <body>
        <a href="#" data-selector="#login-dialog" class="dialog-link">Login</a>
        <div id="login-dialog" class="dialog-popup">
            <a href="#" class="close"><img src="close.png" class="close-button" title="Close this dalog" alt="Close" /></a>
            <form method="post" action="#">
                <fieldset>
                    <label for="username">
                        <span>Username</span>
                        <input type="text" id="username" name="username" placeholder="Username" value=""></input>
                    </label>
                    <label for="password">
                        <span>Password</span>
                        <input type="password" id="password" name="password" placeholder="Password" value=""></input>
                    </label>
                    </fieldset>
                <button class="submit" type="button">Login</button>
                <a href="#" id="forgot-password-link">Forgot your password? Click here.</a>
            </form>
        </div>
        <div id="dialog-overlay" title="Click here to close the dialog">
    </body>
</html>

jQuery: style current page link in navigation

I’m currently working on a web page which has a navigation area in the header and looks like this:

<nav>
    <ol class="main_menu">
        <li><a href="http://localhost/demo/index.php/home" class="active">home</a></li>
        <li><a href="http://localhost/demo/index.php/features">Features</a></li>
        <li><a href="http://localhost/demo/index.php/pricing">Pricing</a></li>
        <li><a href="http://localhost/demo/index.php/support">Support</a></li>
        <li><a href="http://localhost/demo/index.php/blog">Blog</a></li>
        <li><a href="http://localhost/demo/index.php/register">Sign up</a></li>
        <li><a href="http://localhost/demo/index.php/login">Login</a></li>
    </ol>
</nav>

Now I wanted to have the active page somehow highlighted so that the user can clearly see where he is. So first I defined some styles for this highlighting (basically just making it bold and underlined):

<style type="text/css">
    nav .active {
        font-weight: bold;
        text-decoration: underline;
    }
</style>

So what I needed now was some JavaScript to check the currently loaded URL and check which of the navigation links should be highlighted. All URLs start with http://localhost/demo/index.php. So I just needed to check the 4th part of the location path, find the navigation link related to that (so basically the link which ends with the same 4th part). Additionally if you go to http://localhost/demo/index.php you are shown the Home page even though the URL doesn’t end with /home. So I also needed to handle this special case.

So first I needed a JavaScript function to get the last part of the URL. Here I just needed to split the path using a slash as a separator and get the last one. There is just one exception: if the URL ends with a slash, I do not get the last one but the second to last (so that I do not get an empty result):

function getLastPart(url) {
    var parts = url.split("/");
    return (url.lastIndexOf('/') !== url.length - 1 ? parts[parts.length - 1] : parts[parts.length - 2]);
}

Now that we have it, we’ll get the last part of the URL and activate the link which ends with this last part. Additionally if the last part is index.php, we’ll highlight the Home link:

$(function() {
    var lastpart = getLastPart(window.location.href);
    $('nav a[href$="/' + lastpart + '"]').addClass('active');
    if (lastpart == "index.php") {
        $('nav a[href$="/home"]').addClass('active');
}

About the jQuery selector for the link selection:

nav a[href$="/home"]

It basically says to find a link in the navigation (i.e. nav a) having a href attribute (i.e. nav a[href…]) ending with (i.e. href$=) “/home”.

Of course if your URLs look different e.g. if you have query parameters or it is not the last part of the URL which is relevant for the link selection, you’ll need to adapt the getLastPart function. If it is not the last part of the URL which is relevant, you’ll also need to use a different selector.

Here the complete JavaScript:

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js" type="text/javascript">
<script type="text/javascript">
<!--
$(function() {
    var lastpart = getLastPart(window.location.href);
    $('nav a[href$="/' + lastpart + '"]').addClass('active');
    if (lastpart == "index.php") {
        $('nav a[href$="/home"]').addClass('active');
}
  });
function getLastPart(url) {
    var parts = url.split("/");
    return (url.lastIndexOf('/') !== url.length - 1 ? parts[parts.length - 1] : parts[parts.length - 2]);
}
//-->
</script>

How to build a simple table filter with jQuery

I needed to have a very lightweight and easy to use javascript table filter. The idea was to add a second row to the table header (below the column names) and have there a text input in each cell of this new row. When you write something in there and press Return, it will filter out all rows not containing this string in this particular column. You should also be able to combine filters for different columns.

Let’s implement it as a jQuery plugin. First we need a name… We’ll call the file jquery.filtertable.js and the function we define in the plugin will be filtertable:

(function($) {
    $.fn.filterTable = function(filter, columnname) {
        return this;
    };
})(jQuery);

We have two parameters:

  1. First, the string used to filter the table
  2. Second, the name of the column where the filter string will be searched for

Of course using the column name is not always a good idea. It’s be better to use an ID or an index. An index is kind of difficult to use especially if columns are moved around. And since the column might not have an ID but will most probably have a name/header, I went for the name.

So the first thing we need to do is to map the column name to an internal index:

var index = null;
this.find("thead > tr:first > th").each(function(i) {
    if ($.trim($(this).text()) == columnname) {
        index = i;
        return false;
    }
});

So here I make a few assumptions:

  • The table has a theader
  • The first row in the this header is the one we’re interested in

If we do not find the column, we just throw an exception:

if (index == null)
    throw ("filter columnname: " + columnname + " not found");

Then we loop through the rows, get the text of the corresponding cell and compare it with the filter. If it contains the filter, the row is shown otherwise hidden:

this.find("tbody:first > tr").each(function() {
    var row = $(this);
    var cellText = $(row.find(("td:eq(" + index + ")"))).text();
    if (cellText.indexOf(filter) == -1) {
        row.hide();
    }
    else {
        row.show();
    }
});

Note that I here also assume you have a <tbody> tag. If not, you’ll have to modify the plugin.

There are 2 additional pieces of functionality we’ll want to add:

  • If the filter string is empty, we show everything
  • If the cell contains a select box, we want to use the text of the selected entry and cell text

First the empty filter showing all rows:

this.find("tbody:first > tr").each(function() {
    var row = $(this);
    if (filter == "") {
        row.show();
    }
    else {
        var cellText = $(row.find(("td:eq(" + index + ")"))).text();
        if (cellText.indexOf(filter) == -1) {
            row.hide();
        }
        else {
            row.show();
        }
    }
});

Now handle select box:

this.find("tbody:first > tr").each(function() {
    var row = $(this);
    if (filter == "") {
        row.show();
    }
    else {
        var cellText = row.find("td:eq(" + index + ")").find('option:selected').text();
        if (cellText == "") {
            cellText = $(row.find(("td:eq(" + index + ")"))).text();
        }
        if (cellText.indexOf(filter) == -1) {
            row.hide();
        }
        else {
            row.show();
        }
    }
});

Now the whole code:

(function($) {
	$.fn.filterTable = function(filter, columnname) {
		var index = null;
		this.find("thead > tr:first > th").each(function(i) {
			if ($.trim($(this).text()) == columnname) {
				index = i;
				return false;
			}
		});
		if (index == null)
			throw ("filter columnname: " + columnname + " not found");

		this.find("tbody:first > tr").each(function() {
		    var row = $(this);
		    if (filter == "") {
		        row.show();
		    }
		    else {
		        var cellText = row.find("td:eq(" + index + ")").find('option:selected').text();
		        if (cellText == "") {
		            cellText = $(row.find(("td:eq(" + index + ")"))).text();
		        }
		        if (cellText.indexOf(filter) == -1) {
		            row.hide();
		        }
		        else {
		            row.show();
		        }
		    }
		});
		return this;
	};
})(jQuery);

You can download the file here.

To use it, simply add the following to your <head> section:

<script type='text/javascript' src='jquery.tablefilter.js'></script>

Then when you want to filter the table, just do the following:

$("#your-table-id").filterTable('filter_text', 'column_name');