Autonomous Machine

Posts tagged with JavaScript

Apply jQuery event handlers once with $.fn.safe()

Today I was in this situation:

  • I had a page that could mutate in a lot of different ways (AJAX, user interaction, etc)
  • I needed a way to execute a block of code against only the new DOM elements on the page (to avoid duplicating the event handlers on the already existing elements)
  • I didn't want to use livequery (delegation would have created its own set of problems)

This is what I came up with:

$('ul.choices a').safe('choices').click(function(event) {
  // code to only execute once for any DOM element
};

$.fn.safe works similarly to jQuery's filter. It returns a jQuery object containing only DOM elements that haven't already been returned for the specified key ('choices' in the above example). So it's safe to execute the code above multiple times without repercussions. Obviously, looping through a large set of elements can be costly, but for smaller sets this simple solution works great.

Here's what's going on behind the scenes:

var $$ = function(param) {
    var node = jQuery(param)[0];
    var id = jQuery.data(node);
    jQuery.cache[id] = jQuery.cache[id] || {};
    jQuery.cache[id].node = node;
    return jQuery.cache[id];
};

$.fn.safe = function(name) {
  return this.filter(function(i, element) {
    $$(this).unsafe = $$(this).unsafe || [];
    if ($.inArray(name, $$(this).unsafe) == -1) {
      $$(this).unsafe.push(name);
      return true;
    } else {
      return false;
    }
  });
};

I'm using the $$ helper I first saw on Yahuda Katz's blog- you should too. It makes working with jQuery's data cache much easier.

See Also

LowPro for Prototype will only apply a given behavior to a DOM element once, but I haven't used it since moving to jQuery for all new projects.

  • November 20, 2009
  • Article
  • Code, JavaScript, jQuery

Dollar Spec is a tiny JavaScript spec library

Dollar spec is a tiny JavaScript spec framework that gets out of the way and lets you get things done. It supports nested describes and before/after blocks, but that is about it. The standard set of expectations is small right now, but they are easy to add.

Why another JavaScript spec framework?

I wanted a small, pure-JS framework that supported a syntax I didn't have to think too much to use. I wanted to only add as much syntax as was neccessary, and leave the rest to JavaScript. I wanted to run JavaScript tests from the console. I wanted a really extensible way to add new expectations. And I wanted minimal namespace usage. DollarSpec creates a single global entity, $spec.

Also, it seemed like a good way to stretch my brain. I've been doing a lot of JavaScript development recently, and it was fun to apply some of the new techniques I've picked up.

OK Let's See What You've Got

Here's a sample spec for an awesome addition function:

$spec.describe('my awesome addition function', function(spec) {

    var add = function() {
       var total = 0;
       for (var i = 0, l = arguments.length; i < l; i++) {
           total += arguments[i];
       }
       return total;
    };
    
    spec.before(function() {
        // code in this block is run before each spec
        // and yes, describe blocks can be nested
    });
    
    spec.it('adds two numbers', function(expect) {
        expect(add(2,2)).to.equal(4);
    });

    spec.it('adds three numbers', function(expect) {
        expect(add(1,1,2)).to.equal(4);
    });

    // to show a failing spec
    spec.it('adds two numbers', function(expect) {
        expect(add(1,1,2)).to.equal(5);
    });

    // to show a pending spec
    spec.it('casts and adds strings');
});

$spec.run();

If you run this inside Firefox, you will get some messages in the Firebug console:

Filter results

These message can be adjusted to your liking:

// To disable console output (defaults to true)
$spec.opts.console = false;

// Print a line to the console for each test (defaults to false)
$spec.opts.verbose = true;

If you don't want to use the console reporting, the results of all specs are collected and returned by $spec.stats(). This makes it easy to do whatever you wish with the results.

Supported expectations

Keep in mind that any expectation can be negated using not(). For example:

expect(3).to.not().equal(8);

expect(actual).to.be(object)

Compares the expected and actual values using ===.

expect(actual).to.beA(Object)

Verifies that actual is an instance of Object.

expect(actual).to.equal(expected)

Compares actual and expected using ==.

expect(affectorFunction).to.change(affectedFunction)

Compares the value returned from calling affectedFunction before and after calling affectorFunction.

expect(affectorFunction).to.change(affectedFunction).by(amount)

Compares the difference between the value returned from calling affectedFunction before and after calling affectorFunction to amount.

expect(callbackFunction).to.raiseError([verifyFunction])

Verifies that callbackFunction raises an exception when called.

Optionally will call verifyFunction and pass in a raised exception for further inspection. verifyFunction must return true or false.

Custom expectations

DollarSpec's expectations are built from two pieces- verbs and matchers.

Verbs

A verb is a function that can be used in an expectation to define something about the parameters of what is expected. The most basic verbs simply define which matcher to use. Verbs must always return this in order to allow chaining to work. Here is the verb definition for equal:

$spec.verb('equal', function(expected) {
    this.set('matcher', 'equal');
    this.set('expected', expected);
    return this;
});

This verb will cause the expectation to use the equal matcher, and stores the value of expected so it can be examined in the matcher later.

Matchers

Matchers are functions that determine if an expectation passes or fails. They can also define messages to be displayed to the user when a test fails. Here's the beA matcher:

$spec.matcher('beA', function(result) {
  result.failure = "Expected an instance of " + this.klass.toString() + ", but it was " + typeof(this.actual);
  result.negatedFailure = "Expected instance of a class other than " + this.klass.toString() + ", but it was one";

  return this.actual instanceof(this.klass);
});

result.negatedFailure is the message to be displayed when the tests passes, but was used in a negated expectation using not().

You can see how the built-in verbs and matchers are implemented in the Github repository.

Dogfood for Dinner

Dollar Spec has a suite of specs, written using Dollar Spec and powered by diligence, a tiny remote JavaScript test runner library. It requires Node.js, which is awesome. Go check it out if you haven't yet.

To run the specs:

cd spec
node suite.js

Open a browser and go to localhost:5678. You should see something like the following:

Dollar Spec Results

Diligence is still under development, but its working well so far on a few JavaScript projects I'm working on. I'll post more about it as it matures.

More information

There is a bit more info in the README on Github. Feedback is always welcome!

Alternatives

You should probably take a look at ScrewUnit if you're looking for a more complete/mature BDD framework.

Now go write some specs.

  • September 04, 2009
  • Article
  • Code, JavaScript

List Ordering with a JavaScript State Machine

I've been thinking a lot about state machines recently- specifically, if the use of an event-driven state machine could be a useful pattern when creating JavaScript interfaces. I did a little poking around online, and the best resource I could find was a series of articles on IBM's developerWorks. The articles are actually quite good, and I'd recommend reading them. They include some nice background information on the hows and whys of state machine usage.

The one valid criticism I have of the articles is they spend a lot of time developing special code to deal with browser idiosyncrasies and event handling although the articles date from early 2007, well after the rise of JavaScript frameworks. I decided to see what I could to do build a lightweight state machine model using mostly framework code, in this case Prototype and Low Pro.

A Somewhat Contrived Example

Since I want to mostly show the syntax I'm using, here is a basic example for a simple widget: a web radio. Here are the states and transitions this example will be using:

State machine diagram for radio widget

States

Here are the states shown in that diagram, converted to JavaScript (I've stripped most of the functionality out for brevity, a link to the full code is below). I've stolen the idea of using functions as event handlers like this from the developerWorks articles. I like the flexibility it offers, and also that in order to transition to a new state, one of these functions simply needs to return the name of the new state. I've also added a shortcut- if a event handling function is only going to return a state name, that string can just be used in the definition in place of the function.

The state 'start' and its event 'init' are simply there so we can define a place for the machine to start.

Radio.definition = {
    start: {
        init: 'Stopped'
    },

    Stopped: {
        play: 'Playing'
    },
    
    Paused: {
        play: 'Playing',
        stop: function(event) {
            return 'Stopped';
        }
    },
    
    Playing: {
        enter: function() {
            // called when we enter this state
        },
        pause: 'Paused',
        stop: 'Stopped',
        exit: function() {
            // called when we leave this state
        }
    }
};

Events

The next thing to consider is how to map DOM events to the events defined above. I decided to define them for each state using a simple CSS selector to event name mapping. Just as with the state definitions, each CSS selector is either mapped to a string (this time an event name) or a function that will return an event name.

Radio.events = {
    Stopped: {
        'button.play:click': 'play'
    },
    Playing: {
        'button.stop:click': 'stop',
        'button.pause:click': function(event) {
            // ...
            return 'pause';
        }
    },
    Paused: {
        'button.stop:click': 'stop',
        'button.play:click': 'play'
    }
};

All Together Now

The state and event mapping definitions are brought together in a fairly standard Low Pro behavior, inheriting from another behavior containing the state machine logic. Any functions that are used as a part of the state and event definitions above are evaluated in the context of a behavior instance, so this is a convenient place for utility and helper functions.

Radio.machine = Behavior.create(State.behavior, {
    definition: Radio.definition,
    events: Radio.events,
    incrementCounter: function() {
        // ...
    },
    updateCounter: function() {
        // ...
    }
});

The complete behavior is applied using the standard Low Pro Event.addBehavior().

Event.addBehavior({
    'div.radio': Radio.machine
});

Here's a working example of the radio using the complete code.

Sort, Sort, Sort

Obviously, a state machine could be used to model a much more complicated interaction. A client application I'm working on had a need for some customizable sorting, and I decided to write the code from the ground up in order to have complete control over the sort lifecycle. After some thought, I decided to try to model the behavior of many desktop UI systems: a clone of the element being dragged would move with the mouse, and an insertion point would appear when and where a legal insertion could occur. I thought this was the best interface for most use cases as it allows the user to see both an object's new and old positions throughout the drag process. I also like the clear indication of when a valid drop can occur.

The states and events used by the ordering widget are shown in the following diagram:

State machine diagram for ordering widget

I won't paste all of the code to the sorter here, but I've posted it on GitHub as well as a working example. It worked out rather well, and I found the use of a state machine helped in keeping the code well organized and extensible, despite the fairly complicated interaction being modeled.

  • December 30, 2008
  • Article
  • JavaScript, Prototype

Emulating :hover in IE6 with Prototype and Low Pro

A common technique for building UI items that respond to a hover, such as drop downs, is to write CSS rules that make a child UL appear when an enclosing element is moused over:

<ul>
    <li>
         <a href="/item1">Item 1</a>
         <ul>
                <li><a href="subitem1">Subitem1</a></li>
                <li><a href="subitem2">Subitem2</a></li>
                <li><a href="subitem3">Subitem3</a></li>
         </ul>
    </li>
    <li><a href="/item2">Item 2</a></li>
    <li><a href="/item3">Item 3</a></li>
</ul>
ul li ul {
  display: none;
}

ul li:hover ul {
  display: block;
}

Which works everywhere except under IE6. I've seen some posts on the web that suggest adding event listeners to the elements that need to detect hovering, and then having an event handler add a CSS class to the elements. Unfortunately, most of these posts recommend attaching listeners to the the mouseover and mouseout events, which is problematic if the elements have any children. When the child elements fire their own events, the result will be an annoying flicker.

One option is to add conditionals the event handler functions to make sure only the desired events are being responded to. But a simpler solution in this case is to use IE's non-standard mouseenter and mouseleave events. Here's an example in LowPro:

var ElementWithManualHover = Behavior.create({
    onmouseenter: function() {
        this.element.addClassName('hover');
    },
    onmouseleave: function() {
        this.element.removeClassName('hover');
    }
});

if (Prototype.Browser.IE6) {
    Event.addBehavior({
       'ul li': ElementWithManualHover 
    });
}

I'm using some extended browser detection to apply these behaviors only to IE6. You could do the same thing on your own in a few more lines of code.

This, of course, is only a good technique if you're already using Prototype. If you aren't, take a look at jQuery's hover.

  • November 24, 2008
  • Article
  • JavaScript, Prototype

Use valid markup in your jQuery DOM creation

I just spent almost an hour trying to figure out why some really basic DOM creation/insertion and JavaScript-initiated form submission wasn't working in IE7 (everything was OK in FireFox). I could have saved a lot of time by checking everything from the beginning, but I noticed a form element wasn't getting appended to the DOM and so I tried debugging that for a while before checking if I even had a valid DOM element to append. I didn't, and after a few minutes of troubleshooting I found the root cause of all my troubles- I had been using invalid markup to create the form element. I changed:

f = $('<form>');

to

f = $('<form />');

and everything started working. I was using the (admittedly invalid) lone opening tags without a thought after seeing that form in several jQuery tutorials and, not being that familiar with jQuery, thinking it was a supported syntax.

So, always use valid markup everywhere and avoid this kid of bug.

  • October 08, 2008
  • Article
  • JavaScript, jQuery

On Your Best Behavior: Prototype, LowPro, and Responsible Scripting

The Prototype JavaScript library was born out of the aura that was the early days of Ruby on Rails, and it has grown into a respectable framework used on many high profile sites. ProtoType is a general purpose framework, adding convenience methods to many of JavaScript's built in object types. This approach makes it easier to write more concise, explanatory code, but it has also generated legions of Prototype dissenters. I think it's safe to say Prototype is the right library for some developers and situations, and other libraries may be a better fit for others. Plenty of sites are built with Prototype, and plenty more will be; my goal here is to show how to use Prototype the right way.

If you're working on a jQuery-based project, there's a version of LowPro for you too. The rest of this article will be solid theory, but the syntax used in the following examples uses Prototype.

Problems with Prototype

The biggest problem that I've personally had with Prototype is it tends to be slow to adopt best practices emerging in other libraries. For example, it had an anemic event model before version 1.5, and it didn't natively support any kind of DOM-ready event until 1.6. Luckily, there has been another small JavaScript library that has filled in these functionality gaps for quite some time- LowPro. It you are using Prototype to write your client side code, you should install LowPro before you install Script.aculo.us. If you're using Rails, add installing LowPro to your list of new project setup tasks.

In my opinion, LowPro should have been merged into Prototype long ago. And while its code has never made it into Prototype's codebase, over time, LowPro's intentions have been gradually assimilated. As a result, LowPro's role has grown smaller with each release of Prototype, and today it serves one primary purpose: binding behaviors to page elements. It also has some convenience methods for constructing DOM elements, but I won't be dealing with those in this article.

An introduction to twenty-first century scripting

It's 2008. There is a short list of things that should never be seen in markup from this point forward:

  • inline event handlers
  • script elements outside of your document's head

Yes, this includes your Google Analytics tracking scripts. Put those script tags in your document's head where they belong, and use DOM ready events to fire your tracking.

The key to modern scripting is in your markup. Make it as semantic as you can. Use class names that makes sense, more than one if necessary. Use microformats if you can. Once you do this, you will find id attributes become much less important. I tend to only use them for database objects these days- and even then it can get you into trouble.

There are a lot of third party libraries that expect to be passed an element with an id attribute to function properly. I believe parts of Script.aculo.us are like this. Do not allow these libraries seduce you into adding id attributes where you do not otherwise need them. Behavior-based JavaScript is based around the relationship between elements in the DOM. In most cases, none of the elements need id attributes. From inside behaviors, use DOM traversal methods to find other nodes you need to work with.

With that covered, let's move on to some code examples.

LowPro Basics

The core of LowPro revolves around two functions: Event.addBehavior and Behavior.create. Event.addBehavior can map DOM elements to anonymous functions based on CSS selectors:

Event.addBehavior({
  'div.main': function() {
    // 'this' is a reference to a matching DOM node
  },
  'div.secondary,div.tertiary': function() {
    // multiple CSS selectors can be separated by a comma
  }
});

Event.addBehavior also supports attaching event listeners to DOM elements. Note how the CSS selector has a pseudo-class appended to it:

Event.addBehavior({
  'div.main:click': function(event) {
    // 'this' is a reference to a matching DOM node
    // 'event' it the standard Prototype-extended event object
  }
});

Now when a user clicks on a page element that matches the selector 'div.main', the anonymous function is passed the standard Prototype-extended event object and executed, which is cool. This is a good place to start when writing behaviors, since a good portion of the time, a single event handler will do the job. But if you start to see a lot of duplication of selectors or you need to maintain a state information for your various event handlers to use, it's best to move to creating Behavior classes.

Oh, Behave!

Here's what a simple Behavior looks like- it's generally a good idea to define your behaviors first, and then reference them inside an Event.addBehavior block:

var SimpleBehavior = Behavior.create({
  initialize: function() {
    // Called when the behavior is created
    // 'this' is a reference to the Behavior instance
    // 'this.element' is a reference to the matching DOM element
  },
  onclick: function(event) {
    // 'this' and 'this.element' work the same way as in initialize
    // All 'on...' methods are automatically bound as event listeners
    // to their respective events
  },
  otherMethod: function(arg) {
    // You can add other methods to the behavior object
  }
});

Event.addBehavior({
  'div.main': SimpleBehavior
});

Behavior.create works a lot like Prototype's Class.create- both return class objects that support pseudo-inheritance. You can use these classes and the new keyword to instantiate a behavior. Doing so requires the first argument to the constructor be the DOM element to bind the behavior to. This is what Event.addBehavior does behind the scenes.

new SimpleBehavior(element);

Remember that if you want to store data in properties on the Behavior object instance, the correct way to do so is initialize those properties in an initialize function:

var BehaviorWithProperties = Behavior.create({
  initialize: function() {
    this.property = 'value';
  }
});

A common point of confusion is how behaviors and DOM elements interact. LowPro Behavior classes (and the rest of the examples in this article) involve the interaction of two parts: an instance of the Behavior class, and the DOM element this instance is bound to. I find that this is a stumbling point, but it's simple once the you grasp the way these parts interact: the behavior instance has a reference to the DOM element, but the DOM element knows nothing of the behavior. This is in part to get around some circular-reference memory leaks in certain JavaScript interpreters, and partly because this pattern has emerged as a good design pattern to follow when implementing user interfaces.

Pseudo-inheritance, Prototype style.

Behavior.create handles subclassing behavior in the same way that Prototype's Class.create does. By writing basic behaviors and then extending them for your application-specific purposes, you can build up a library of general behaviors that solve common problems. I usually try to identify a generic behavior component when I'm writing a new behavior, and finish that before building any application specific code.

var Parent = Behavior.create({
  initialize: function() {
    this.element.addClassName('crazy');
  }
});

var Child = Behavior.create(Parent, {
  initialize: function($super) {
    $super();
    this.element.hide();
  }
});

DOM Ready and You

Old-school JavaScript taught developers to use inline onload event handlers on a document's body element to call page initialization methods. The trouble with this is we don't really want the page load event, because it is fired once the page is finished loading. For scripting purposes, all we care about is the DOM being fully parsed and assembled. Starting in version 1.6, Prototype has supported custom events, the most vital of which is 'dom:loaded'. This event is fired right when we need it to: once the DOM is ready.

Event.addBehavior hooks into Prototype's 'dom:loaded' event, and waits until then to look for elements to initialize. Remember that if you instantiate behaviors using JavaScript's new keyword, you need to make sure to do it after the DOM has been loaded.

var MyBehavior = Behavior.create({
  initialize: function() {
    this.element.addClassName(Cycle.thru('odd', 'even'));
  }
});

document.observe('dom:loaded', function() {
  $$('ul.products li').each(function(element) {
    new MyBehavior(element);
  }); 
});

Now Go Out and Make The World a Better Place

I've found LowPro to be an essential tool for writing user interfaces over the last year. It's greatest downside has always been its somewhat sparse documentation, which I hope to have somewhat rectified with this article. Be sure to check out Dan Webb's website for more LowPro and other web goodness. And the LowPro Google Group is a great place to go for help and discussion.

  • August 24, 2008
  • Article
  • JavaScript, Prototype

Cycle.js: A JavaScript implementation of Rail's text helper

On a recent project I had to zebra-stripe a table on the client side in JavaScript, and set out to port Rail's cycle text helper to JavaScript. I rewrote it a couple weeks later using what Douglas Crockford calls the module pattern.

var Cycle = function(){
    var cycles, that, thru, reset;
    
    cycles = {};
    that = {};
 
    thru = function(){
        var values, i, cycle, options, name, nextValue;
 
        values = Array.prototype.slice.call(arguments);
        options = typeof(values.slice(-1)[0]) === 'object' ? values.pop() : {};
 
        name = options['name'] || 'default';
 
        if(!cycles[name]) {
            cycles[name] = {
              values: values,
              index: 0
            };
        };
        cycle = cycles[name];
        nextValue = cycle.values[cycle.index];
        cycle.index = (cycle.index === cycle.values.length -1) ? 0 : (cycle.index +1);
        return nextValue;
    };
    that.thru = thru;
 
    reset = function(name) {
        if(cycles[name]) {
            cycles[name].index = 0;
        }
    };
    that.reset = reset;
    
    return that;
 
}();

Usage is fairly simple:

>>> Cycle.thru('one', 'two');
"one"
>>> Cycle.thru('one', 'two');
"two"
>>> Cycle.thru('one', 'two');
"one"

Passing an object literal containing a name property allows for reset functionality:

>>> Cycle.thru('one', 'two', 'three', {name: 'numbers'});
"one"
>>> Cycle.thru('one', 'two', 'three', {name: 'numbers'});
"two"
>>> Cycle.reset('numbers');
>>> Cycle.thru('one', 'two', 'three', {name: 'numbers'});
"one"

I've started a GitHub repository for this and other small Javascript utility functions.

  • August 03, 2008
  • Article
  • JavaScript