Friday, April 19, 2013

Kick Ass Cross-Platform Workflow With Grunt

My apologies for the vulgar title, but Grunt has me pretty excited these Days.  I work on a Mac at home and on a Windows machine at work, this means that cross platform tools are extremely important to my workflow. I hate having to use CodeKit at home and Scout at work to compile my .scss files.  Additionally tying a project to a platform specific tool means that its that much harder for others to get involved. Lately I've become a bit of a GruntJS fanboy, not only does it
meet my cross platform needs, but it cuts down the number of tools I need to use because it does so many things.
The watch plugin has made my workflow even better by allowing me to run Grunt tasks when files in my project are added, changed, or deleted. With watch whenever a .less or .scss file gets updated Grunt will see the change and automatically compile the file for me.  This post will walk you through installing watch and a compiler plugin and addresses a few of the gotcha's I ran into along the way. 

Install Grunt

Duh. Installing Grunt is out of the scope of this post but  the Grunt Homepage does an excellent job of walking you through the intstall (here's a hint you'll need to install Node too).  It's important to note here that version 0.4.0 and newer of Grunt works much differently than prior versions, and that the examples in this post will only work with version 0.4.0 or newer of Grunt.

Install Compiler Plugin

Once you have Grunt installed the next step is to get a compiler installed (since my current project is using Twitter Bootstrap I'm going to walk through setting up the LESS compiler in this post, however the process is the same for any other compiler).
To find the plugin you need the Grunt plugin page has a list of plugin's available for Grunt.  Clicking on a plugin will send you to the plugin's NPM page where you'll find documentation on the plugin and the command needed to install the plugin.

A quick not on plugins.  There are two type's of plugins "contrib" and "non-contrib".  The "contrib" plugins are official plugins and are maintained by the Grunt team.  I always prefer to use the "contrib" plugins whenever I can since they are generally better maintained and supported.
To install the Grunt LESS plugin:
  • open the terminal and navigate to the project's root directory in a terminal
  • type in npm install grunt-contrib-less --save-dev in the command line
  • hit enter
It really is that simple. The --save-dev command will ensure that the plugin, and its version, are added to your package.json file.
If you are not using a package.json file I strongly suggest you start. Using a package.json file means that you don't have to add your node_modules code to your distributions, users simply have to type in npm install in a terminal to install the dependencies.

Install The Watch Plugin

The next step is simple, install the watch plugin.  While still in your project's root directory in the terminal type in:
npm install grunt-contrib-watch --save-dev

Configure your Gruntfile

Before you go any further read the "Configuring Tasks" section of the Grunt website. This will save you a lot of time in the long run, I promise. Many plugins will assume that you know how to configure tasks and will leave these details out of their documentation.
A few points of frustration that I've run into that are covered in "Configuring Tasks" are:
  • Specifying the task name to configure. Typically the task name is the same as your plugin name.
  • Specifying source and destination files.  This is almost always left out of documentation and is almost always extremely important since a lot of plugins deal with manipulating files. 
  • Targets and how to use them (some plugins require targets and some do not, the bad news is that it's usually poorly documented whether a plugin uses them or not).
Configure the Compiler
Now that I've gotten that out of the way, lets walk through setting up the LESS compiler in the Gruntfile. In order to add a LESS task to the Gruntfile add a property named less to the object that is passed into the grunt.initConfig() function.  Since the LESS plugin requires targets (it will fail without them) I am going to specify a development target in the configuration.
A quick note on targets: if no target is specified when register or running a task ALL targets for a task will be executed.
The development target in this example only uses properties: src and dest. These properties are used to tell the LESS task where to find the .less files and where to put the compiled .css file. The syntax /**.*.less tells Grunt to get all files with a .less extension in the less directory and all of its sub-directories.

grunt.initConfig({

    less: {

        development : {

            src : [ 'less/**/*.less' ],

            dest : 'css/compiled-better.css'

        }

    }

)};

Configure Watch
The watch plugin does exactly what it's name implies, watches a directory for changes.  Since this plugin does not require the use of targets I'll skip them in this example and will put properties directly under the watch task. At a minimum watch needs two properties: files and tasks. The files property tells watch what files to look for changes for, and tasks property tells watch what tasks to run when a change is detected.
The example below is telling watch to listen for any changes to files with a.less extension in the less folder and any of its sub-folders and to run the less task when a change is detected.
watch: {

    files : [ 'less/**/*.less' ],

    tasks : [ 'less' ]

}
Register Tasks
The last step in configuring your Gruntfile is to register the less and watch tasks. To do this the following lines are added to the Gruntfile immediately after the grunt.initConfig() call:


grunt.initConfig({
    ...
});

grunt.loadNpmTasks('grunt-contrib-less');

grunt.loadNpmTasks('grunt-contrib-watch');

Run the Watch Task

The last step is to run the watch task. In a terminal window once again navigate to the root directory of the project and enter:
grunt watch
Grunt will run the watch task which will then tell you that its "Waiting...".  When a change is detected Grunt will spring into action and you'll see the normal Grunt messages start to appear in the console. When the tasks are complete watch will go back to the Waiting... status.



Monday, April 15, 2013

A Quick Look at e.target and e.currentTarget. And Bubbling.

Since jQuery makes such easy work of event listeners it's sometimes easy to ignore some of the finer points of JavaScripts events.  Two things that I often forget is the difference between the e.target and e.currentTarget properties of the event object that gets passed into a event callback. Hopefully writing this post will help the concepts stick in my head.

The reason we need both of these properties is because of event bubbling.  Event bubbling is when a event travels up the DOM, until it reaches the document node.   Lets say we have a DOM tree that looks like this:

document
|
|--div
|  |
|  |--span
|  |  |
|  |  |--a

Here we have a document node with a child of a div with a child of a span with a child of an anchor.  When the anchor is clicked the any event listeners for the anchor are triggered, then the event bubbles up to it's parent, the span and any of it's event listeners are triggered, then the event bubbles up to the div where its event listeners are triggered, and finally the event bubbles up to the document element where any  of it's event listeners are triggered.

Assuming that the span and div have a margin, padding, and border of 0 whenever a user clicks on the link the event the target property of the event that is triggered on the anchorspan,  div  and document will always be the anchor, since this is where the event originated. The currentTarget property will always be the element that is listening for the event.

The following code shows event listeners on 4 different objects, however when a user clicks on the anchor link the target will be the same for each event listener.



Open up your console and run the following example, paying close attention to the order the events fire as they bubble up the dom:

JS Bin
Fork me on GitHub