Laravel User Authentication with Ajax Validation

This post documents how to add Ajax form validation to Laravel User Authentication that is generated by the Artisan console.

Requirements

An instance of Laravel 5.2 setup for local development is required. Head over to the Laravel 5.2 docs to get setup as needed. For my setup on Windows 10, I am using XAMPP 5.6.12 which meets all of the requirements. Note that your document root will need to be the laravel/public directory. Here is the virtual hosts configuration I am using for Laravel.

httpd-vhosts.conf
<VirtualHost *:8080>
    DocumentRoot "C:/xampp/htdocs/laravel/public"
    ServerName laravel.dev
    ServerAlias www.laravel.dev
    SetEnv APPLICATION_ENV development
    <Directory "C:/xampp/htdocs/laravel">
        Options Indexes MultiViews FollowSymLinks
        AllowOverride All
        Order allow,deny
        Allow from all
    </Directory>
</VirtualHost>

More info on setting up XAMPP for Windows

New Laravel Site

Using Composer, install Laravel 5.2 into htdocs/laravel

cd c:/xampp/htdocs

composer create-project laravel/laravel laravel "5.2.*"
Generate Authentication Scaffolding
cd laravel

php artisan make:auth

Now the Laravel site has routes and views to allow a user to register, login, logout, and reset their password. A HomeController has also been added to allow authenticated users to enter its view.

User Model

I prefer to organize models within their own directory. Therfore, let’s create a folder named Models in the app directory and move the User model into it.

mkdir app/Models

mv app/User.php app/Models

Edit app/Models/User.php, update the App namespace to App\Models.

User.php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    ...

  • Throughout this tutorial you will encounter an ellipsis … in the code examples. These are not a part of the code and are there only to denote code that is being skipped and not applicable to the example. To view the entire file, examine the source code.

Edit app/Http/Controllers/Auth/AuthController.php, update use App\User to use App\Models\User.

AuthController.php
<?php

namespace App\Http\Controllers\Auth;

use App\Models\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;

class AuthController extends Controller
{
    ...

Edit, config/auth.php, update the authentication drivers user provider.

'users' => [
    'driver' => 'eloquent',
    'model' => App\User::class,
],
REPLACE
'model' => App\User::class,
WITH
'model' => App\Models\User::class,

Database

Edit the .env configuration file to access the database. I have already created a database named laravel on the MySQL Server that is part of XAMPP.

.env
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

The settings above are the only ones I needed to update for my setup, I do not have a password set for MySQL Server and will just use the root account for local development. Now run php artisan migrate to create the tables for users.

Run Migration
php artisan migrate
Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table

Validation Rules

The validation rules are currently located in the AuthController, I would like to be able to share them with the new controller we will create later for ajax validation. Since these rules apply to the User, and that model is already being used by the AuthController, it seeems to be the best place for them.

Edit app/Models/User.php, adding the public function rules() to the class.

User.php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    /**
     * Get the validation rules that apply to the user.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|max:255',
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|min:6|confirmed',
        ];
    }

    ...

Edit app/Http/Controllers/Auth/AuthController.php. Locate the validator function and replace the array of rules being passed to the Validator make method with a call to the newly created rules method in our User model.

REPLACE
return Validator::make($data, [
    'name' => 'required|max:255',
    'email' => 'required|email|max:255|unique:users',
    'password' => 'required|min:6|confirmed',
]);
WITH
return Validator::make($data, (new User)->rules());

Before going further, now would be a good time to test registering a new user, login, logout etc.. to make sure everyting works. All we have done so far aside from the code that artisan generated is move the User model and move the validation rules from the AuthController into it.

The next page covers using the api middleware in routes, creating the Validation\UserController for handling the XHR request and finally updating the templates and adding the JavaScript for Ajax.


Design Patterns

Design patterns are reusable solutions to commonly occurring problems in software design.
Addy Osmani

Constructor Pattern

All methods and properties of the object are added in the constructor.

function Track(artist, title, year) {
    this.artist = artist;
    this.title = title;
    this.year = year;

    this.trackInfo = function() {
        return this.title + ", by " + this.artist + ", Year released: " + this.year;
    };
}

var theFixer = new Track("Pearl Jam", "The Fixer", 2009);
var lazaretto =  new Track("Jack White", "Lazaretto", 2014);

console.log(theFixer.trackInfo()); // The Fixer, by Pearl Jam, Year released: 2009

Constructor Pattern with Prototype

function Track(artist, title, year) {
    this.artist = artist;
    this.title = title;
    this.year = year;
}

Track.prototype.trackInfo = function() {
    return this.title + ", by " + this.artist + ", Year released: " + this.year;
};

var theFixer = new Track("Pearl Jam", "The Fixer", 2009);
var lazaretto =  new Track("Jack White", "Lazaretto", 2014);

console.log(theFixer.trackInfo()); // The Fixer, by Pearl Jam, Year released: 2009

Prototype Pattern

var trackPrototype = {
    init: function(artist, title, year) {
        this.artist = artist;
        this.title = title;
        this.year = year;
    },
    trackInfo: function() {
        return this.title + ", by " + this.artist + ", Year released: " + this.year;
    }
};

function track(artist, title, year) {
    
    function F() {}
    F.prototype = trackPrototype;
    
    var f = new F();
    
    f.init(artist, title, year);
    return f;
}
 
var theFixer = track("Pearl Jam", "The Fixer", 2009);
var lazaretto = track("Jack White", "Lazaretto", 2014);

console.log(theFixer.trackInfo()); // The Fixer, by Pearl Jam, Year released: 2009

Object Literal Module Pattern

var playlist = {
    
    tracksCollection: [],
    
    lastTrack: function() {
        return tracksCollection[tracksCollection.length -1];
    },
    
    addTrack: function(track) {
        tracksCollection.push(track);
    },
    
    init: function(tracks) {
        tracks.forEach( function(track) {
           this.addTrack(track); 
        });
    }
}

playlist.init([new Track("Pearl Jam", "The Fixer", 2009)]);
playlist.lastTrack().trackInfo(); // The Fixer, by Pearl Jam, Year released: 2009
playlist.addTrack(new Track("Jack White", "Lazaretto", 2014));
playlist.lastTrack().trackInfo(); // Lazaretto, by Jack White, Year released: 2014

IIFE Module Pattern

var playlist = (function() {

    var tracksCollection = [];

    return {

        lastTrack: function() {
            return tracksCollection[tracksCollection.length -1];
        },

        addTrack: function(track) {
            tracksCollection.push(track);
        },

        init: function(tracks) {
            tracks.forEach( function(track) {
            this.addTrack(track);
            });
        }
    }

})();

playlist.init([new Track("Pearl Jam", "The Fixer", 2009)]);
playlist.lastTrack().trackInfo(); // The Fixer, by Pearl Jam, Year released: 2009
playlist.addTrack(new Track("Jack White", "Lazaretto", 2014));
playlist.lastTrack().trackInfo(); // Lazaretto, by Jack White, Year released: 2014

Resource

Learning JavaScript Design Patterns
A book by Addy Osmani

Google Maps API with Browserify

This post documents how to use local JavaScript modules with Browserify and the Google Maps JavaScript API. In this example, the Google Map contains a marker that can be dragged to reset the browser form with the marker position location data. Additionally, Browserify shim is used to require jQuery since it is already being loaded from a CDN.

Requirements

Install Browserify

After installing Node.js, using a Command shell, install Browserify globally. *

# global browserify install
npm install -g browserify

* If you have an older version of global Browserify, run npm rm –global browserify to remove the old version before installing the newer one.

Project Folders and Files

Create these folders and files. Browse, clone or download the source code if you prefer.

    • css
      • style.css
    • js
    • src
      • js
        • modules
          • gapi.js
          • gmap.js
          • jsonp.js
          • location.js
        • index.js
    • gulpfile.js
    • index.html

Web Page

This web page contains the map container, address and geocode location inputs. jQuery is being loaded from the Google Hosted Libraries CDN.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
	<title>Page Title</title>
	<link rel="stylesheet" href="/css/style.css">
</head>
<body>
	
	<p>Drag the marker to a location on the map and set input values.</p>
	
	<div id="map-canvas"></div>
	<label>Street
		<input type="text" id="street" name="street" />		
	</label>
	<label>Locality
		<input type="text" id="locality" name="locality" />
	</label>
	<label>Region
		<input type="text" id="region" name="region" />
	</label>
	<label>Country
		<input type="text" id="country" name="country" />
	</label>
	<label>Latitude
		<input type="text" id="latitude" name="latitude" />
	</label>
	<label>Longitude
		<input type="text" id="longitude" name="longitude" />
	</label>
	
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>

        <!-- Prod / built version -->
	<!--script src="/js/bundle.js"></script-->

</body>
</html>

Application Entry Point

The index module is the starting point for the map application. The module dependency requirements are defined followed by a call to the load function exported by the gapi module. gapi.load uses a callback function before the location.init.

index.js
var $ = require('jQuery');
var gapi = require('./modules/gapi');
var location = require('./modules/location');

gapi.load( function () {
    location.init();
});

Google Map JavaScript API

Loading the Google Maps JavaScript API requires a global callback. For this I have created a jsonp module which accepts three parameters, a URL (url), a global callback function name (callbackname), and a callback function (done).

jsonp.js
module.exports = function (url, callbackname, done) {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(script, s);

    window[callbackname] = done;
};

The gapi module makes use of the jsonp module to load the API:

gapi.js
var jsonp = require('./jsonp');

var gapiurl = 'http://maps.googleapis.com/maps/api/js?callback=__googleMapsApiOnLoadCallback';

exports.load = function (done) {
    jsonp(gapiurl, '__googleMapsApiOnLoadCallback', done);
};

Location Module

The location module initiates the google map using the gmap.init function exported by the gmap module. The gmap.init callback results data is handled by the location module to populate the address inputs.

location.js
var gmap = require('./gmap');

var latitude = 39.084014922903;
var longitude = -77.51372591791;

exports.init = function () {
    gmap.init(
        latitude,
        longitude,
        function(result){
            console.log('RESULT',result);

            if (result.address) {
                _setAddress(result.address);
            }

            $('#latitude').val(result.latitude);
            $('#longitude').val(result.longitude);
        }
    );
};

_setAddress = function(address) {
    var street = [];
    if (address.street_number) {
        street.push(address.street_number);
    }
    if (address.route) {
        street.push(address.route);
    }
    if (street.length > 0) {
        $('#street').val(street.join(' '));
    }
    if (address.locality) {
        $('#locality').val(address.locality);
    }
    if (address.administrative_area_level_1) {
        $('#region').val(address.administrative_area_level_1);
    }
    if (address.country) {
        $('#country').val(address.country);
    }
};

Google Map

The gmap module uses the Google Maps JavaScript API.

gmap.js
exports.init = function (lat, lng, callback) {
    var geocoder = new google.maps.Geocoder();
    var marker = new google.maps.Marker({
        position: new google.maps.LatLng(lat, lng),
        anchorPoint: new google.maps.Point(0, -29),
        draggable: true
    });
    map = new google.maps.Map(document.getElementById('map-canvas'), {zoom: 10});
    map.setCenter(marker.position);
    marker.setMap(map);

    google.maps.event.addListener(marker, 'dragend', function (evt) {

        var latlng = {lat: evt.latLng.lat(), lng: evt.latLng.lng()};
        var addrComponents = {
            street_number: 'short_name',
            route: 'long_name',
            establishment: 'long_name',
            locality: 'long_name',
            administrative_area_level_1: 'short_name',
            country: 'short_name',
            postal_code: 'short_name'
        };
        result = {
            address: {},
            latitude: 0,
            longitude: 0
        };

        geocoder.geocode({'location': latlng}, function(results, status) {
            if (status === google.maps.GeocoderStatus.OK) {
                if (results[0]) {

                    var i; var type = null;
                    for (i = 0; i < results[0].address_components.length; i++) {
                        type = results[0].address_components[i].types[0];
                        if (addrComponents[type]) {
                            result.address[type] = results[0].address_components[i][addrComponents[type]];
                        }
                    }
                    result.latitude = latlng.lat;
                    result.longitude = latlng.lng;
                    if(typeof callback == "function"){
                        callback(result);
                    }

                } else {
                    window.alert('No results found');
                }
            } else {
                window.alert('Geocoder failed due to: ' + status);
            }
        });
    });
}
style.css

Add this file into the css folder to declare some basic styles.

label {
	display: block;
	width: 300px;
	margin-bottom: 1em;
}
input[type='text'] {
	display: block;
	width: 100%;
}
#map-canvas {
	width: 300px;
	height: 300px;
	margin-bottom: 1em;
}

Build

Use the Gulp task and build runner to configure and run the build process. Install Gulp and the Node modules locally that are needed to properly bundle, minify and sourcemap the Browserify modules.

Install Gulp

Install gulp globally with the npm install -g command as follows: *

# global gulp install
npm install -g gulp

* If you have an older version of global Gulp, run npm rm –global gulp to remove the old version before installing the newer one.

Create package.json

The package.json file contains project metadata and lists dependencies that are available on npm.

# create package.json
npm init

Install node modules

# install dependencies and save to package.json
npm install browserify --save-dev
npm install browserify-shim --save-dev
npm install gulp --save-dev
npm install gulp-sourcemaps --save-dev
npm install gulp-uglify --save-dev
npm install gulp-util --save-dev
npm install gulp-webserver --save-dev
npm install vinyl-buffer --save-dev
npm install vinyl-source-stream --save-dev

Browserify Shim

Browserify shim is used so global jQuery can be required by modules. Near the bottom of the package.json file, add these browserify and browserify-shim properties. View the source code if needed.

"browserify": {
  "transform": ["browserify-shim"]
},
"browserify-shim": {
  "jQuery": "global:jQuery"
}

Create a gulpfile.js in the project root to define and configure the Gulp tasks. The default task is simply gulp. When gulp is executed, it will run the bundle task, start a local web server with livereload and open the app in a web browser.

gulpfile.js
'use strict';

var gulp        = require('gulp'),
    browserify  = require('browserify'),
    buffer      = require('vinyl-buffer'),
    gutil       = require('gulp-util'),
    source      = require('vinyl-source-stream'),
    sourcemaps  = require('gulp-sourcemaps'),
    uglify      = require('gulp-uglify'),
    webserver   = require('gulp-webserver');

gulp.task('bundle', function () {
  var b = browserify({
    entries: './src/js/index.js',
    debug: true
  });

  return b.bundle()
    .pipe(source('bundle.js'))
    .pipe(buffer())
    .pipe(sourcemaps.init({loadMaps: true}))
        .pipe(uglify())
        .on('error', gutil.log)
    .pipe(sourcemaps.write('./'))
    .pipe(gulp.dest('./js/'));
});

// static web server w/ livereload
gulp.task('server', function() {
    gulp.src('./')
        .pipe(webserver({
            livereload: true,
            directoryListing: false,
            open: true
        }));
});

gulp.task('default', ['bundle','server']);

Add watchify to the gulp workflow on the next page.

Source Code

Javascript Frameworks

After discovering yet another interesting Javascript framework for the web application development recently (Vue.js), I decided to put together this short list of some the emerging and more popular frameworks out there today for building user interfaces.

Emerging

Mithril is a light-weight robust MVC framework with no dependencies, a small API and small learning curve. Mithril implements a virtual DOM tree similar to React.js, however Mithril’s uncompiled templates run natively in the browser and compile into static Javascript data structures. Mithril.js was initially released in March, 2014 by Leo Horie. Source available at https://github.com/lhorie/mithril.

File Version Size
mithril.min.js 0.2.2-rc.1 19 kb

Vue.js is a library for building interactive web interfaces and its focus is on the view layer only. Vue.js extensions such as vue-router and vue-resource are available for inclusion into the application as needed. The Vue.js API is simple and easy to adapt compared to other frameworks. Vue.js was initially released in July, 2014 by Evan You. Source available at https://github.com/vuejs/vue.

File Version Size
vue.min.js 1.0.13 70 kb

Riot.js – A React-like user interface micro-library. Compare Riot.js with React and Polymer. Source available at https://github.com/riot/riot.

File Version Size
riot.min.js 2.3.12 19.2 kb

Polymer is a framework for creating and rendering custom web components. Web Components are the future of the web and Google has created the Polymer library set of polyfills for developers to dive into. Source available at https://github.com/Polymer/polymer.

Popular

AngularJS a framework by Google that binds HTML (views) to Javascript objects (models). AngularJS usage rivals that of BackboneJS, according to JavaScript analytics service Libscore, AngularJS is used on over 10,000 websites including The Weather Channel, Lego, Intel, Sprint and ABC News. AngularJS was initially released in September 2009 by Misko Hevery. Source available at https://github.com/angular/angular.js.

File Version Size
angular.min.js 1.4.8 145 kb

Ember.js is a framework based on the model-view-controller (MVC) pattern and follows Convention over Configuration (CoC). Ember has a large API that covers traditional MVC patterns and a broad range of helper utilities. Ember 2.0 has a brand new high performance rendering engine named Glimmer, and server-side rendering is provided with FastBoot. After Handlebars in 2010, on December 8, 2011, Yehuda Katz released Ember.js. Source available at https://github.com/emberjs/ember.js.

File Version Size
ember.min.js 2.2.0 435 kb

React.js is the framework that powers Facebook and Instagram’s user interface and is one of the fastest growing JavaScript frameworks today. Features include one-way data flow and a virtual-DOM using an in-memory data structure cache process so only applicable nodes on the client browser’s DOM are updated for high performance complex user interfaces. React.js was created by Jordan Walke, a ‎Software Engineer at Facebook and was initially released in May, 2013 by Facebook and Instagram. Source available at https://github.com/facebook/react.

File Version Size
react.min.js 0.14.6 133 kb
react-dom.min.js 0.14.6 1 kb
Total 134 kb

Backbone.js is a light weight framework that provides a RESTful JSON interface for data models, views, collections, and events. Backbone.js is used on over 12,000 websites according to Libscore including Linkedin, Pinterest, ESPN, Walmart, Target and Best Buy. Backbone.js was initially released on October 13, 2010 by Jeremy Ashkenas. Source available at https://github.com/jashkenas/backbone/.

File Version Size
backbone-min.js 1.2.3 22.5 kb
underscore-min.js 1.8.3 16.0 kb
Total 38.5 kb

Google Maps API RequireJS Module

This post documents how to create a RequireJS module that loads the Google Maps JavaScript API and is used by a web page to populate form inputs with location data. The Google Map contains a marker that can be dragged to reset the form with the marker position location data.

source code

Development Environment

In order to use the package managers, Gulp task runner, BrowserSync and RequireJS optimizer, you will need to have the following installed.

  1. Node.js
  2. Bower

Using a Command shell in the project root, follow these steps to get the local development environment setup.

Create package.json

The package.json file contains project metadata and lists dependencies that are available on npm. This is useful to have when you want to re-install or update the dependencies with the npm install command.

# create package.json
npm init

Install node modules

# install dependencies and save to package.json
npm install browser-sync --save-dev
npm install gulp --save-dev
npm install requirejs --save-dev

Create bower.json

The bower.json file contains project metadata and lists dependencies that are available on bower. Re-install / update bower components saved to this file with the bower install command.

# create bower.json
bower init

Install bower components

Only using the async plugin from this requirejs-plugins bundle for async dependency loading of the Google Maps JavaScript API.

# install bower components and save to bower.json
bower install --save requirejs-plugins

Project Folders and Files

Create these folders and files. Browse, clone or download the source code if you prefer.

  • css
  • js
  • src
    • js
      • modules
        • gmap.js
        • location.js
      • require
      • main.js
    • gulpfile.js
    • index.html
    • require.build.js

Web Page

This web page contains the map container, address and geocode location inputs. jQuery is loaded separately before the require.js module loader to demonstrate how to define jQuery when it is external.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
	<title>Page Title</title>
	<link rel="stylesheet" href="/css/style.css">
</head>
<body>
	
	<p>Drag the marker to a location on the map and set input values.</p>
	
	<div id="map-canvas"></div>
	<label>Street
		<input type="text" id="street" name="street" />		
	</label>
	<label>Locality
		<input type="text" id="locality" name="locality" />
	</label>
	<label>Region
		<input type="text" id="region" name="region" />
	</label>
	<label>Country
		<input type="text" id="country" name="country" />
	</label>
	<label>Latitude
		<input type="text" id="latitude" name="latitude" />
	</label>
	<label>Longitude
		<input type="text" id="longitude" name="longitude" />
	</label>
	
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
	
	<!-- Prod / built version -->
	<!--script data-main="/js/main" src="/js/require/require.js"></script-->
	<!-- Dev version -->
	<script data-main="/src/js/main" src="/src/js/require/require.js"></script>

</body>
</html>

Google Map JavaScript API

This module is dependent upon the RequireJS async plugin to load the google maps api. The gmap function accepts three parameters: the default latitude, default longitude and a callback function to handle the result data.

gmap.js
define(['async!//maps.googleapis.com/maps/api/js'],
    function(async){
    var gmap = {
        map: {},
        init: function gmap(lat, lng, callback) {
            var geocoder = new google.maps.Geocoder();
            var marker = new google.maps.Marker({
                position: new google.maps.LatLng(lat, lng),
                anchorPoint: new google.maps.Point(0, -29),
                draggable: true
            });
            map = new google.maps.Map(document.getElementById('map-canvas'), {zoom: 10});
            map.setCenter(marker.position);
            marker.setMap(map);

            google.maps.event.addListener(marker, 'dragend', function (evt) {

                var latlng = {lat: evt.latLng.lat(), lng: evt.latLng.lng()};
                var addrComponents = {
                    street_number: 'short_name',
                    route: 'long_name',
                    establishment: 'long_name',
                    locality: 'long_name',
                    administrative_area_level_1: 'short_name',
                    country: 'short_name',
                    postal_code: 'short_name'
                };
                result = {
                    address: {},
                    latitude: 0,
                    longitude: 0
                };

                geocoder.geocode({'location': latlng}, function(results, status) {
                    if (status === google.maps.GeocoderStatus.OK) {
                        if (results[0]) {

                            var i; var type = null;
                            for (i = 0; i < results[0].address_components.length; i++) {
                                type = results[0].address_components[i].types[0];
                                if (addrComponents[type]) {
                                    result.address[type] = results[0].address_components[i][addrComponents[type]];
                                }
                            }
                            result.latitude = latlng.lat;
                            result.longitude = latlng.lng;
                            if(typeof callback == "function"){
                                callback(result);
                            }

                        } else {
                            window.alert('No results found');
                        }
                    } else {
                        window.alert('Geocoder failed due to: ' + status);
                    }
                });
            });
        }
    };
    return gmap;
});

Location module

This module handles the result data from the gmap.js module and sets the input values.

location.js
define(['jquery','modules/gmap'],
    function($, gmap){
    var location = {
        // default map coordinates
        latitude: 39.084014922903,
        longitude: -77.51372591791,
        init: function(){
            gmap.init(
                location.latitude,
                location.longitude,
                function(result){
                    console.log('RESULT',result);

                    if (result.address) {
                        location._setAddress(result.address);
                    }

                    $('#latitude').val(result.latitude);
                    $('#longitude').val(result.longitude);
                }
            );
        },
        _setAddress: function(address) {
            var street = [];
            if (address.street_number) {
                street.push(address.street_number);
            }
            if (address.route) {
                street.push(address.route);
            }
            if (street.length > 0) {
                $('#street').val(street.join(' '));
            }
            if (address.locality) {
                $('#locality').val(address.locality);
            }
            if (address.administrative_area_level_1) {
                $('#region').val(address.administrative_area_level_1);
            }
            if (address.country) {
                $('#country').val(address.country);
            }
        }
    };
    location.init();
    return location;
});
main.js

JavaScript application entry point.

'use strict';

// since jQuery is already loaded from google cdn
define('jquery', [], function() {
    return jQuery;
});

require.config({
    paths : {
        //create alias to plugins (not needed if plugins are on the baseUrl)
        async: 'require/async'
    }
});

require(['modules/location'], function(location) {
     location.init();
});
gulpfile.js

This gulp file defines two tasks. 1. gulp copy copies node modules and bower components into the application. 2. gulp server runs the application in a static BrowserSync web server.

'use strict';
var gulp = require('gulp');
var browserSync = require('browser-sync').create();

// copy vendor libraries into app
gulp.task('copy', function() {
    gulp.src([
        './bower_components/requirejs-plugins/src/async.js',
        './node_modules/requirejs/require.js'
        ])
        .pipe(gulp.dest('./src/js/require/'));
});

// BrowserSync static server
gulp.task('server', function() {
    browserSync.init({
        server: {
            baseDir: './',
            directory: false,
            index: 'index.html'
        }
    });
});
require.build.js

This is the RequireJS Optimizer build configuration file. Once the entry point and modules are created, run node node_modules/requirejs/bin/r.js -o require.build.js to compile the modules into a single js/main.js file.

({
    baseUrl: './src/js',
    dir: './js',
    modules: [
        {
            name: 'main'
        }
    ],
    fileExclusionRegExp: /^(r|require.build)\.js$/,
    removeCombined: true,
    paths: {
        async: 'require/async'
    },
    preserveLicenseComments: false
})
style.css

Add this file into the css folder to declare some basic styles.

label {
	display: block;
	width: 300px;
	margin-bottom: 1em;
}
input[type='text'] {
	display: block;
	width: 100%;
}
#map-canvas {
	width: 300px;
	height: 300px;
	margin-bottom: 1em;
}

Overview

  1. Create package.json and bower.json
  2. Install node modules and bower components
  3. Create /index.html
  4. Create /src/js/modules/gmap.js
  5. Create /src/js/modules/location.js
  6. Create /src/js/main.js
  7. Create /gulpfile.js
  8. Create /require.build.js
  9. Create /css/style.css
  10. Copy the node modules and bower components into the application. In a command shell,
    run gulp copy
  11. Load the application in the web browser. In a command shell,
    run gulp server
  12. Optimize the application. In a command shell,
    run node node_modules/requirejs/bin/r.js -o require.build.js

Reference

Browserify with Sourcemaps

Browserify lets you write modular JavaScript in node.js style. At the beginning of each module you write, the respective dependencies are added using require statements. Then Browserify compiles the modules along with all of the dependencies into an optimized JavaScript file for use in the browser.

If jQuery has already been loaded by a CDN or otherwise, a Browserify Shim can be used to include global jQuery as a module.

Requirements

Install Browserify

Using a Command shell, enter npm commands as follows: *

# global browserify install
npm install -g browserify

* If you have an older version of global Browserify, run npm rm –global browserify to remove the old version before installing the newer one.

Create package.json

The package.json file contains project metadata and lists dependencies that are available on npm.

# create package.json
npm init

Install both jQuery and Lo-Dash from npm

# install dependencies
npm install jquery lodash --save
Updated on October 24, 2015

Local Modules

In the /src/js/modules folder, create a local application module. In this example, the service.js local module uses jQuery ajax to get data from a rest endpoint that returns json data. Then Lo-Dash is used to output the json data in the browser with a simple template.

service.js
var $ = require('jquery');
var _ = require('lodash');
var setElementHtml = require('./element');
module.exports = function() {

    var module = {

        init: function() {

            var tmplSource = $('#tmpl-artist').html(),
                template;

            $.getJSON('http://responsiveresourcesgroup.com/chinook/api/artists').done(function(data) {
                if (data) {

                    template = _.template(tmplSource)({
                        data: data
                    });

                    $(".artist-list").html(template);

                    setElementHtml('#some-id', 'Done');
                }
            });
        }
    };

    return module.init();

};

In the /src/js/modules folder, create a second local application module. This module depends on jQuery and contains a small function to update element HTML. Here we are simply using it to change the page heading as various steps are completed in this small application. It also shows how multiple module dependencies are defined and how they interact with each other.

element.js
var $ = require('jquery');
module.exports = function(selector, data) {

    return $(selector).html(data);

};

Application Entry Point

In the /src/js folder, create an index.js JavaScript file.

index.js
var setElementHtml = require('./modules/element');
var getServiceData = require('./modules/service');

setElementHtml('#some-id', 'Step 2...');

getServiceData();

Build

Use the Gulp task and build runner to configure and run the build process. Install Gulp and the Node modules locally that are needed to properly minify and sourcemap the Browserify JavaScript bundle.

Install Gulp

Install gulp globally with the npm install -g command as follows: *

# global gulp install
npm install -g gulp

* If you have an older version of global Gulp, run npm rm –global gulp to remove the old version before installing the newer one.

Install Remaining Node Modules

$ npm install gulp --save
$ npm install browserify --save
$ npm install vinyl-source-stream --save
$ npm install vinyl-buffer --save
$ npm install gulp-uglify --save
$ npm install gulp-sourcemaps --save
$ npm install gulp-util --save

Create a gulpfile.js in the project root for instructing gulp to perform the tasks.

gulpfile.js
'use strict';

var browserify = require('browserify');
var browserSync = require('browser-sync').create();
var gulp = require('gulp');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var uglify = require('gulp-uglify');
var sourcemaps = require('gulp-sourcemaps');
var gutil = require('gulp-util');

gulp.task('javascript', function () {
  // set up the browserify instance on a task basis
  var b = browserify({
    entries: './src/js/index.js',
    debug: true
  });

  return b.bundle()
    .pipe(source('app.js'))
    .pipe(buffer())
    .pipe(sourcemaps.init({loadMaps: true}))
        // Add transformation tasks to the pipeline here.
        .pipe(uglify())
        .on('error', gutil.log)
    .pipe(sourcemaps.write('./'))
    .pipe(gulp.dest('./js/'));
});

// Static server
gulp.task('server', function() {
    browserSync.init({
        server: {
            baseDir: "./"
        }
    });

    gulp.watch('./js/*.js').on('change', browserSync.reload);

});
index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>Browserify App</title>
</head>
<body>
	<h1 id="some-id">Step 1 ...</h1>

	<!-- template wrapper -->
	<ul class="artist-list"></ul>

	<!-- template -->
	<script id="tmpl-artist" type="text/tmpl">
		<% _.forEach(data,function(artist) { %>
		<li data-artist-id="<%= artist.ArtistId %>"><%= artist.Name %></li>
		<% }); %>
	</script>
	<script type="text/javascript" src="/js/app.js"></script>
</body>
</html>

Run

Using a Command shell, run the gulp javascript task to Browserify the modules into a single JavaScript file, /js/app.js, with respective source map file, /js/app.js.map.

# run the task
$ gulp javascript

Server

Run the gulp server task to load the app in the browser using a BrowseSync static server. The gulp.watch event handler provides live reloading when the /js/app.js file is regenerated by the gulp javascript task.

# run the task
$ gulp server
Available for browsing and download at GitHub: https://github.com/jimfrenette/BrowserifyGulp

UPDATE: March 26, 2016

Browserify Shim jQuery

Browserify shim to use external / global jQuery loaded from a CDN. Source code is available for browsing and download at GitHub.

Source Code

Resources

AngularJS v2: Angular.io

At ng-conf 2015 last month it was announced that AngularJS 1.X will continue to reside at angularjs.org and Angular 2.0 will be hosted at angular.io. The new version of Angular is not a major update, it is a complete rewrite. In February, Brad Green announced that Angular 2 was officially Alpha in this First look at App Development in Angular2 video.

During the ng-conf 2015 keynote, Brad and Igor talked about some of the big work that is being done on Angular 1 is work that will be shared in Angular 2, including:

  • New Router that focuses on mobile and large complex apps.
  • New Internationalization (i18n) module
  • Better Performance
Angular 2
github.com/angular/angular

Best Practices

Here are a few Best Practices to follow when coding in JavaScript.

White Space

Follow these recommendations so commits and diffs will be easier to read.

  • Do not mix spaces and tabs.
  • Configure the editor to “show invisibles”. For example, in Sublime Text, the setting is "draw_white_space": "all".
  • Configure the editor to remove end of line whitespace.
  • Choose one, spaces or tab indentation for all JavaScript the team is writing for the project. Consider this, a space is always just one column. The code editor setup pages linked below have info on how to configure for consistent formatting of tabs to spaces and vice versa.
  • Use a consistent value throughout the project for the indent size, e.g., 4 columns.

Code Editor Setup

Comparison Operators

Use === and !==

The equality operator == implicitly tries to convert the values before comparing. The identity or “strict equals” operator === does not convert the values when comparing. Use the equality operator == and != only as needed.

Semicolons

Include all necessary semicolons. Configure your editor to use JSHint, a fork of JSLint, used to detect errors and potential problems in JavaScript.

Global Scope

Any variable that is defined outside of a function body is global in scope. Global variables can be accessed and altered in any scope. Global variables should be declared only as needed. For more information, read this YUI blog post.

Namespace

Using a namespace is a good practice to keep your code out of the global scope.

// global scope without a namespace
var foo = 'bar';

// namespace example
var app = {};
app.foo = 'bar';

// even better,
// namespace check before creation example
var app = app || {};

Line Length

80 characters per line should be sufficient enough to work with. When a statement will not fit on a single line, it may be necessary to break it. Place the break after an operator, ideally after a comma.

Eval

As Douglas Crockford states in his Code Conventions for the JavaScript Programming Language, “eval is Evil”. Improper use of eval makes your code unsafe and difficult to debug.

Curly Braces

For consistency and potential error prevention if additional statements are ever needed in the future, wrap them in curly braces. Minification will remove curly braces as needed for production.

// valid but not recommended
if (condition)
    alert('Condition true');
else
    alert('Condition false');

// recommended
if (condition) {
    alert('Condition true');
}
else {
    alert('Condition false');
}

Quotes

Use single quotes so strings containing double quotes don’t need to be escaped.

Arrays

Creating arrays should be done using the shorthand [] constructor instead of using new Array() notation.

// valid but not recommended
var city = new Array('Atlanta', 'Boston', 'Cleveland');
 
// recommended
var city = ['Atlanta', 'Boston', 'Cleveland'];

Objects

Object literal notation is recommended unless the object requires a specific prototype, then create the object by calling a constructor function with new.

// object literal notation
var config = {};

// constructor function with new
var cityGallery = new Gallery();

Object properties should be accessed using dot notation, unless the key is a variable, a reserved word, or a string that would not be a valid identifier.

prop = obj.propertyName;
prop = obj[variableKey];
prop = obj['default'];
prop = obj['key-with-hyphens'];

Script Element

The <script src="filename.js"></script> element or script tag as it is often referred to should be placed as far down in the body as possible. Ideally, just before the closing </body> tag. Some browsers try to load script files when they hit the script tag and the rest of the page is not parsed until script loading has finished. Additionally, async and defer attributes can be used to control when requested scripts are loaded.

async downloads the file during HTML parsing. The HTML parser will pause to execute the script when the download is completed.

defer downloads the file during HTML parsing. The script will execute after the HTML parser has completed. Multiple defer scripts execute in the order they appear in the document.

More info is available from this developers.google.com resource, Parser blocking versus asynchronous JavaScript

Miscellaneous

// object functions
app = {
  square: function( number ) {
    return number * number;
  },
  double: function( number ) {
    return number + number;
  }
};

Resources

JavaScript Templating Options

Javascript templating is a technique to render JSON data in HTML markup with JavaScript.

Handlebars is a semantic web template system, started by Yehuda Katz in 2010. Handlebars.js is a superset of Mustache, and can render Mustache templates in addition to Handlebars templates.

The document body will need an element that the template will be injected into. Since the template in this example will be a list item for each artist, an unordered list element is the logical choice.

Template Target
<!-- template wrapper -->
<ul class="artist-list"></ul>
Template Source
<!-- template -->
<script id="tmpl-artist" type="text/tmpl">
    {{#each this}}
    <li data-artist-id="{{ArtistId}}">{{Name}}</li>
    {{/each}}
</script>
JavaScript
//jQuery and Handlebars
$(function() {
    var tmplSource = $('#tmpl-artist').html(),
        template = Handlebars.compile(tmplSource);

    $.getJSON('http://responsiveresourcesgroup.com/chinook/api/artists').done(function(data) {
        if (data) {

            $(".artist-list").html(template(data));
        }
    });
});

A utility library delivering consistency, customization, performance, and extras.

Template Target (same as above)
Template Source
<!-- template -->
<script id="tmpl-artist" type="text/tmpl">
    <% _.forEach(data,function(artist) { %>
    <li data-artist-id="<%= artist.ArtistId %>"><%= artist.Name %></li>
    <% }); %>
</script>
JavaScript
//jQuery and Lo-Dash
$(function() {
    var tmplSource = $('#tmpl-artist').html(),
        template;

    $.getJSON('http://responsiveresourcesgroup.com/chinook/api/artists').done(function(data) {
        if (data) {

            template = _.template(tmplSource)({
                data: data
            });

            $(".artist-list").html(template);
        }
    });
});

jQuery only templates

A jQuery only templating solution may be all that you need if none of the other libraries that provide templating such as lodash, underscore or handlebars is present.

Template Target (same as above)
Template Source
<!-- template -->
<script id="tmpl-artist" type="text/tmpl">
    <li id="artist_0"></li>
</script>
JavaScript
//jQuery
$(function() {
    var tmplSource = $('#tmpl-artist').html(),
        artistTmplItem;

    $.getJSON('http://responsiveresourcesgroup.com/chinook/api/artists').done(function(data) {
        if (data) {

            for(var i = 0, len = data.length; i < len; i++) {

                artistTmpl = $(tmplSource);

                artistTmplItem = artistTmpl.attr('id','artist_'+data[i].ArtistId);

                artistTmplItem.text(data[i].Name);

                $('ul.artist-list').append(artistTmplItem);

            }
        }
    });
});

Just JavaScript

A JavaScript only solution that does not use jQuery or any other libraries.

Target element (same as Template Target above)
<ul class="artist-list"></ul>
JavaScript
var getJSON = function(url) {
    return new Promise(function(resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.open('get', url, true);
        xhr.responseType = 'json';
        xhr.onload = function() {
            var status = xhr.status;
            if (status == 200) {
                resolve(xhr.response);
            } else {
                reject(status);
            }
        };
        xhr.send();
    });
};

getJSON('http://responsiveresourcesgroup.com/chinook/api/artists').then(function(data) {
    if (data) {
        // getElementsByClassName returns an array,
        // use the index [0] to get the first element match
        var artistList = document.getElementsByClassName('artist-list')[0],
            li = null,
            da = null;

        for(var i = 0, len = data.length; i < len; i++) {

            li = document.createElement('li');
            da = document.createAttribute('data-artist-id');
            da.value = data[i].ArtistId;
            li.setAttributeNode(da);
            li.appendChild(document.createTextNode(data[i].Name));
            artistList.appendChild(li);
        }
    }
});

Source

https://github.com/jimfrenette/jsTemplates

Benchmarks

Others ...

Resources

WordPress Theme Javascript Optimization

This post shows how to combine and minify multiple javascript files in a WordPress theme into one javascript file. The benefit is a single request to a javascript file that has been compressed by minification instead of multiple request to larger javascript files.

package.json

The package.json file contains meta data about your app or module and it includes the list of dependencies to install from Node Package Manager (NPM) when running npm install. NPM is bundled with Node.js; if you have not done so already, install Node.js so you have it. Then create and save this file in your themes root directory, for example /wp-content/my-theme/package.json. Then when you run npm install, the package.json file is read and the respective node modules are installed. More information is available here.

/* package.json */
{
  "name": "my-theme",
  "version": "0.1.0",
  "devDependencies": {
    "grunt": "~0.4.5",
    "grunt-contrib-jshint": "~0.10.0",
    "grunt-contrib-uglify": "~0.6.0"
  }
}
# install node_modules
$ npm install
gruntfile.js

The Grunt Javascript Task Runner will be used to configure and run the minification of /js/src/*.js into /js/main.js using UglifyJs.

/* gruntfile.js */
module.exports = function(grunt) {
  'use strict';
  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    uglify: {
      build: {
        files: {
          'js/main.js': ['js/src/*.js']
        }
      }
    }
  });
  // Load the plugin
  grunt.loadNpmTasks('grunt-contrib-uglify');
  // Default task(s).
  grunt.registerTask('default', ['uglify']);
};
file structure

Here is what the themes javascript file structure looks like for this example. The /js/src folder contains all of the individual javascript files for your theme. When you run grunt, these are all combined and minified into /js/main.js

javascript file location
Partial view of themes files showing location of javascript
functions.php

The functions.php file controls the loading of the themes javascript resources. In the functions.php file, the wp_enqueue_script() function links the script to the page. The pre-existing wp_enqueue_script() function calls are no longer valid since the javascript files have been moved to the /js/src/ folder. Here is an example of the code from the pre-existing functions.php that was linking the scripts:

function my-theme_scripts() {
  wp_enqueue_style( 'my-theme-style', get_stylesheet_uri() );

  wp_enqueue_script( 'my-theme-deflist', get_template_directory_uri() . '/js/deflist.js', array('jquery'), '20141011', true );

  wp_enqueue_script( 'my-theme-navigation', get_template_directory_uri() . '/js/navigation.js', array(), '20120206', true );
  
  wp_enqueue_script( 'my-theme-skip-link-focus-fix', get_template_directory_uri() . '/js/skip-link-focus-fix.js', array(), '20130115', true );

  if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
	wp_enqueue_script( 'comment-reply' );
  }
}
add_action( 'wp_enqueue_scripts', 'my-theme_scripts' );

Here is an example of the code from functions.php after making the changes to link /js/main.js instead. Since /js/src/deflist.js depends on jQuery, so does /js/main.js and the $deps array parameter is set to ‘jquery’. For more information, refer to the wp_enqueue_script function reference

function my-theme_scripts() {
  wp_enqueue_style( 'my-theme-style', get_stylesheet_uri() );

  wp_enqueue_script( 'my-theme-main', get_template_directory_uri() . '/js/main.js', array('jquery'), '20141011', true );

  if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
	wp_enqueue_script( 'comment-reply' );
  }
}
add_action( 'wp_enqueue_scripts', 'my-theme_scripts' );

Time to Grunt

Run Grunt when you are ready to create or overwrite /js/main.js

# run the default task(s)
$ grunt

Resources

Helper Functions

These are some JavaScript functions I saved here because I couldn’t find an equivalent in Underscore.js or lodash or didn’t need the overhead of the whole library.

find index by key value
// find index of an object in an array by key and value
var _findIndexOf = function(array, key, value) {
    var i;
    for(i = 0; i < array.length; i++) {
        if(array[i].hasOwnProperty(key) && array[i][key] === value) {
            return i;
        }
    }
    return -1;
}
filename from path
// get the filename from a path without using regex
var _filename = function(path) {
    return path.split('/').pop();
}
path only, remove filename
// get the path only without using regex
var _path = function(path) {
    return path.substring(0, path.lastIndexOf('/') +1);
}

Validation

// validate external URL syntax 
var _isExternalURL = function(str) {
    var a  = document.createElement('a');
    a.href = str;

    if (!a.host || location.host == a.host)
    {
        return false;
    }
    return true;
}
source

 

Math

/**
 * round to decimal place
 *
 * @param num number to round
 * @param dec number of decimal places to round to
 */ 
function _round(num, dec) {
    return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
}

Misc

//breakout of iframe / frame
function iframed() {
    try {
        return window.self !== window.top;
    } catch (e) {
        return true;
    }
}
if(iframed()) window.top.location.reload(false);
Resize Handler
window.addEventListener("resize", resizeThrottler, false);

var resizeTimeout;
function resizeThrottler() {
    // wait for resizeHandler execution to complete
    if ( !resizeTimeout ) {
        resizeTimeout = setTimeout(function next() {
            resizeTimeout = null;
            resizeHandler();

           // resizeHandler executes at 15fps
        }, 66);
    }
}

function resizeHandler() {
    // handle the resize event
    console.log(window.innerWidth);
}

Grunt JavaScript Task Runner

This tutorial describes how to setup and use the Grunt JavaScript task runner to automate repetitive tasks such as minification and compilation. Grunt is installed using npm, the Node.js package manager. You will also need Git to work with the tagged source code. This makes it easy to reset and compare your working copy of the code at each step. I discovered commit tags while using the AngularJS tutorial.

If you have not done so already, make sure that you have Node.js, Grunt CLI and Git installed. For Windows systems, after installing Git, you may decide to use the Git bash shell for your CLI (command line interface) instead of the Command Prompt.

Install Grunt’s command line interface (CLI) globally.

#install grunt CLI
$ npm install -g grunt-cli

Source Code

Using Git, clone the GruntTutorial repository. This contains all the source code for the tutorial. As you work through the tutorial, you will be instructed to use git to reset the source code so it matches the step at that point. This will revert the source code to it’s original state for the respective tag and thus overwrite any changes you have made to it.

# git clone creates the GruntTutorial directory in your current directory
$ git clone https://github.com/jimfrenette/GruntTutorial.git

# reset to step 0
$ git checkout -f step-00

Step 1: Node.js Package

# reset to step 1
$ git checkout -f step-01

A npm (nodejs package manager) package.json file is added to the project root for npm to read its structure and know what to do when installing it.

Update: 12-20-2014 — Interactively create a package.json file with the npm init command. More information available at the npm cli commands doc.

# create package.json
$ npm init

Grunt and node modules are installed per the package.json dependencies.

package.json
{
  "name": "grunt-tutorial",
  "version": "0.1.0",
  "devDependencies": {
    "grunt": "~0.4.1",
    "grunt-contrib-jshint": "~0.6.3",
    "grunt-contrib-nodeunit": "~0.2.0",
    "grunt-contrib-uglify": "~0.2.2"
  }
}

These node_modules are already included in the step-01 source. To verify the process on your own, reset the tutorial source code to step-00 and run these npm install commands:

GruntTutorial – bash
# install the latest Grunt in your project folder
$ npm install grunt --save-dev

# install dependencies per package.json
$ npm install

Step 2: Combine and Minify Javascript files

# reset to step 2
$ git checkout -f step-02

Gruntfile.js is added to root of the project to specify the modules configuration, define tasks and load plugins. This Gruntfile.js specifies an uglify plugin to perform JavaScript minification. The banner option creates a comment on the first line of the minified file that is output. The JavaScript source (src) and destination (dest) paths are set in the build properties. Since our build source path has a wildcard * before the js filename extension, all of the js files in that directory will be minified into a single JavaScript file named grunt-tutorial.min.js as specified in the build destination property.

module.exports = function(grunt) {
  'use strict';
  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    uglify: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
      },
      build: {
        src: 'src/js/*.js',
        dest: 'build/<%= pkg.name %>.min.js'
      }
    }
  });
  // Load the plugin that provides the "uglify" task.
  grunt.loadNpmTasks('grunt-contrib-uglify');
  // Default task(s).
  grunt.registerTask('default', ['uglify']);
};

Time to test our Gruntfile.

# run the default task
$ grunt

You should recieve a message like this

Running "uglify:build" (uglify) task
File "build/js/grunt-tutorial.min.js" created.

Done, without errors. 

Step 3: Compass and Watch Plugin Install

# reset to step 3
$ git checkout -f step-03
OS X / Linux

For the Sass compile with the Compass & Watch Grunt plugins, you will need to have Ruby, Sass, and Compass version 0.12.2 or greater installed. Ruby comes pre-installed on OS X.

Windows

For the Sass compile with the Compass & Watch Grunt plugins, you will need to have the RubyInstaller for Windows. As of this writing, use Ruby 1.9.3 installers. These provide a stable language and a extensive list of packages (gems) that are compatible and updated. During the setup, check the option to Add Ruby executables to your PATH. After installing Ruby, install Sass and Compass using the Start Command Prompt with Ruby.

Start Command Prompt with Ruby
Start Command Prompt with Ruby

Install compass and watch Grunt plugins

By running these npm install commands with –save-dev, the package.json file will automatically be updated to include these two new dependencies.

GruntTutorial – bash
# install plugins and update package.json 
$ npm install grunt-contrib-compass --save-dev

$ npm install grunt-contrib-watch --save-dev

Compass and watch Grunt plugins added to the package.json devDependencies.

package.json
{
  "name": "grunt-tutorial",
  "version": "0.1.0",
  "devDependencies": {
    "grunt": "~0.4.2",
    "grunt-contrib-jshint": "~0.6.3",
    "grunt-contrib-nodeunit": "~0.2.0",
    "grunt-contrib-uglify": "~0.2.2",
    "grunt-contrib-compass": "~0.6.0",
    "grunt-contrib-watch": "~0.4.4"
  }
}

Step 4: Sass

# reset to step 4
$ git checkout -f step-04

Sass .scss files added to the source (src) directory and the Gruntfile.

Gruntfile.js
module.exports = function(grunt) {
  'use strict';
  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    compass: {
      dev: {
        // http://compass-style.org/help/tutorials/configuration-reference/#configuration-properties
        // these options will override (or extend) config.rb settings.
        options: {  
          cssDir: 'build/css/',
          sassDir: 'src/sass/'
        }
      }
    },
    uglify: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
      },
      build: {
        src: 'src/js/*.js',
        dest: 'build/js/<%= pkg.name %>.min.js'
      }
    },
    watch: {
      css: {
        files: '**/*.scss',
        tasks: ['compass']
      }
    }
  });
  // Load the plugins.
  grunt.loadNpmTasks('grunt-contrib-compass');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-watch');
  // Default task(s).
  grunt.registerTask('default', ['uglify', 'watch']);
};

Time to test our Gruntfile.

# run the default task(s)
$ grunt

Step 5: Compile Sass

# reset to step 5
$ git checkout -f step-05

The CLI / Terminal shows the status of the Grunt tasks that are run.

# run the default task(s)
$ grunt

Running "uglify:build" (uglify) task
File "build/js/grunt-tutorial.min.js" created.

Running "watch" task
Waiting...OK

Edit and save one of the Sass (.scss) files. The watch task waits for changes to files and then fires a task. In our Gruntfile, the watch is configured to run the the compass task whenever a .scss file is updated. The compass task is configured to compile the Sass files specified in the sassDir property (src/sass/) and output to the cssDir specified (build/css/).

CLI / Terminal output from changed Sass file.

>> File "src/sass/reset.scss" changed.

Running "compass:dev" (compass) task
overwrite build/css/reset.css (0.02s)
unchanged src/sass/style.scss
Compilation took 0.053s

Done, without errors.
Completed in 0.717s at Sat Nov 23 2013 17:14:57 GMT-0500 (EST) - Waiting...
Source Code
December 2014 — New Page added to cover using a static web server with live browser reloading.

Browser

The JavaScript Browser Object Model (BOM) represents the objects exposed by the web browser.

History

The history object represents the browsers URL history. Methods are back(), forward() and go().

// go back twice
history.go(-2);

Location

The location object represents the document URL.

// reload the current document
location.reload();

// current location properties

Navigator

The navigator object contains several properties with info about the browser.

// information about the browser
navigator.userAgent;

// current navigator.userAgent properties

Screen

The screen object contains properties with info about the display.

// show information about screen size
alert(screen.width + 'x' + screen.height);

// current screen properties

JavaScript

Window

The window object represents the browser window or frame and also inherits all of the global JavaScript core properties

History

The history object represents the browsers URL history. Methods are back(), forward() and go().

// go back twice
history.go(-2);

Location

The location object represents the document URL.

// reload the current document
location.reload();

// current location properties

Navigator

The navigator object contains several properties with info about the browser.

// information about the browser
navigator.userAgent;

// current navigator.userAgent properties

Screen

The screen object contains properties with info about the display.

// show information about screen size
alert(screen.width + 'x' + screen.height);

// current screen properties

Document

The Document object represents the root node of HTML, XHTML and XML documents. The nodes of every document are organized in a tree structure, called the DOM tree. Objects in the DOM tree may be addressed and manipulated by using methods on the objects.

Document object reference page


Global Objects

Array
An array is an ordered collection of elements that can be selected by indices. JS Bin
var ballsArray = ["Titleist", "Callaway", "Srixon"];
var scoresArray = [80, 82, 79];
Boolean
A boolean object is an object wrapper around the fundamental boolean datatype value (true or false).
var bool = false;
alert('bool is ' + bool);
Date
A date object is used to work with dates and times. JS Bin
// new Date object
var d = new Date();
d.getFullYear();

// date string
var s = Date();
console.log(s);
Error
An error object contains information to describe JavaScript errors. JS Bin
try {
    throw new Error("Oh Snap");
} catch (e) {
    console.log(e.name + ": " + e.message);
}
JSON
A JSON (JavaScript Object Notation) object is used for converting values to JSON and values from JSON.
var json = '{"result":true,"count":1}',
obj = JSON.parse(json);
console.log(obj.count);
console.log(JSON.stringify(obj));
Math
A math object is used for mathematical functions and constants.
// shows 10
alert(Math.floor(10.6));
Number
A number object is used for numeric functions and constants.
// create number object
var num = new Number(10);
RegExp
A RegExp object is to create regular expressions for pattern matching. JS Bin
// regular expression defining 5 digits
var fiveDigitPattern=/^\d{5}$/;
var digits = "12345";
alert(digits.search(fiveDigitPattern)== -1? false:true );
String
A string object is used for string functions and constants.
// create string object
var str = new String("Hello world");
//or
var str = "Hello world";
Functions & Properties
decodeURI
A function that returns a decoded URI by replacing each UTF-8 escape sequence with it’s respective characters. JS Bin
console.log(decodeURI('~!@#$%25%5E&*()=+%5B%5D%7B%7D%5C;:\'%22,/?'));
decodeURIComponent
A function that returns a decoded URI component by replacing each UTF-8 escape sequence with it’s respective characters. JS Bin
console.log(decodeURIComponent('~!%40%23%24%25%5E%26*()%3D%2B%5B%5D%7B%7D%5C%3B%3A\'%22%2C%2F%3F'));
encodeURI
A function that returns an encoded URI by replacing all special characters with their respective UTF-8 escape sequences. JS Bin
console.log(encodeURI('~!@#$%^&*()=+[]{}\\;:\'",/?'));
encodeURIComponent
A function that returns an encoded URI component by replacing all special characters with their respective UTF-8 escape sequences. JS Bin
console.log(encodeURIComponent('~!@#$%^&*()=+[]{}\\;:\'",/?'));
eval
A function that evaluates a JavaScript expression.
var x = 5;
var str = "if (x == 5) {console.log('z is 42'); z = 42;} else z = 0; ";
eval(str);
infinity
A property that represents a number that is greater than any other number, including itself.
var x = Math.pow(2, 1024);
console.log(x);
    
isFinite
A function that returns true if the value exceeds the lower limit 5e-324 (Number.MIN_VALUE) or the upper limit 1.7976931348623157e+308 (Number.MAX_VALUE), false otherwise.
var x = isFinite(Math.sqrt(-4));
console.log(x);
var y = isFinite(50);
console.log(y);
isNaN
A function that returns true if the value is a number, false otherwise.
console.log(isNaN(50));
console.log(isNaN('foo'));
NaN
A property that represents a NaN (Not a Number) value.
console.log(Number('foo'));
parseFloat
A function that returns the float value of a variable.
// both return 1.505
console.log(parseFloat('1.5050'));
console.log(parseFloat('0.015050E+2'));
parseInt
A function that returns the integer value of a variable.
// return 50
console.log(parseInt('50');
undefined
A property that represents a variable that has not been declared or initialised. JS Bin
foo; // throws an error, foo is not defined
var foo; // foo is uninitialised

if (typeof foo === 'undefined') {
    // foo is undefined
}

Resources

Document

The JavaScript Document object is the root node of the document tree in HTML, XHTML and XML documents.

Properties

Methods

Resources

DotNetNuke CSS and JS Module Development – Part One

During module development, to see all of the js and css files in firebug or your favorite client debugging tool of choice, turn off the file combination provider. In this section at or near the bottom of your web.config, set the provider enableCompositeFiles attributes from true to false as shown in the example below.

<clientDependency version="2" fileDependencyExtensions=".js,.css">
  <fileRegistration defaultProvider="LoaderControlProvider">
    <providers>
      <add name="DnnBodyProvider" type="DotNetNuke.Web.Client.Providers.DnnBodyProvider, DotNetNuke.Web.Client" enableCompositeFiles="false" />
      <add name="DnnFormBottomProvider" type="DotNetNuke.Web.Client.Providers.DnnFormBottomProvider, DotNetNuke.Web.Client" enableCompositeFiles="false" />
      <add name="PageHeaderProvider" type="ClientDependency.Core.FileRegistration.Providers.PageHeaderProvider, ClientDependency.Core" enableCompositeFiles="false" />
      <add name="LazyLoadProvider" type="ClientDependency.Core.FileRegistration.Providers.LazyLoadProvider, ClientDependency.Core" enableCompositeFiles="false" />
      <add name="LoaderControlProvider" type="ClientDependency.Core.FileRegistration.Providers.LoaderControlProvider, ClientDependency.Core" enableCompositeFiles="false" />
      <add name="DnnPageHeaderProvider" type="DotNetNuke.Web.Client.Providers.DnnPageHeaderProvider, DotNetNuke.Web.Client" enableCompositeFiles="false" />
    </providers>
  </fileRegistration>
  <compositeFiles defaultFileProcessingProvider="CompositeFileProcessor" compositeFileHandlerPath="~/DependencyHandler.axd">
    <fileProcessingProviders>
      <add name="CompositeFileProcessor" type="ClientDependency.Core.CompositeFiles.Providers.CompositeFileProcessingProvider, ClientDependency.Core" enableCssMinify="false" enableJsMinify="true" persistFiles="true" compositeFilePath="~/App_Data/ClientDependency" bundleDomains="" urlType="MappedId" />
    </fileProcessingProviders>
  </compositeFiles>
</clientDependency>

Here is another web.config change to speed up the page load after a new build.

Change
<compilation debug="true" strict="false">
To
<compilation debug="true" strict="false" optimizeCompilations="true">

Resources

ISO Date Time

Useful javascript for converting an iso DateTime string into a javacript Date object. The Date.prototype.setISO8601 function is from dansnetwork.com/javascript-iso8601rfc3339-date-parser/

 
Converted Date Time:

Fiddle with this

function isoToDateTime(value) {
	var output = '';
	if (value != '0001-01-01T00:00:00') {
		var d = new Date();
		d.setISO8601(value);
		//desired output format: 5/17/2011 8:34:56 AM - below does not return local time
		output = d.getMonth() + 1 + "/" + d.getDate() + "/" + d.getFullYear() + " " + setClockTime(d)
	}
	return output;
}

function setClockTime(d)
{
	var h = d.getHours();
	var m = d.getMinutes();
	var s = d.getSeconds();
	var suffix = "AM";
	if (h > 11) {suffix = "PM";}
	if (h > 12) {h = h - 12;}
	if (h == 0) {h = 12;}
	if (h < 10) {h = "0" + h;}
	if (m < 10) {m = "0" + m;}
	if (s < 10) {s = "0" + s;}
	return h + ":" + m + ":" + s + " " + suffix;
}

//convert an ISO8601 date string into a js date object
Date.prototype.setISO8601 = function(dString){
var regexp = /(\d\d\d\d)(-)?(\d\d)(-)?(\d\d)(T)?(\d\d)(:)?(\d\d)(:)?(\d\d)(\.\d+)?(Z|([+-])(\d\d)(:)?(\d\d))/;
if (dString.toString().match(new RegExp(regexp))) {
	var regexp = /(\d\d\d\d)(-)?(\d\d)(-)?(\d\d)(T)?(\d\d)(:)?(\d\d)(:)?(\d\d)(\.\d+)?(Z|([+-])(\d\d)(:)?(\d\d))/;
	if (dString.toString().match(new RegExp(regexp))) {
		var d = dString.match(new RegExp(regexp));
		var offset = 0;
		this.setUTCDate(1);
		this.setUTCFullYear(parseInt(d[1],10));
		this.setUTCMonth(parseInt(d[3],10) - 1);
		this.setUTCDate(parseInt(d[5],10));
		this.setUTCHours(parseInt(d[7],10));
		this.setUTCMinutes(parseInt(d[9],10));
		this.setUTCSeconds(parseInt(d[11],10));
		if (d[12]) {
			this.setUTCMilliseconds(parseFloat(d[12]) * 1000);
		}
		else {
			this.setUTCMilliseconds(0);
		}
		if (d[13] != 'Z') {
			offset = (d[15] * 60) + parseInt(d[17],10);
			offset *= ((d[14] == '-') ? -1 : 1);
			this.setTime(this.getTime() - offset * 60 * 1000);
		}
	}
	else {
		this.setTime(Date.parse(dString));
	}
		return this;
	}
}