Patterns for DRY-er JavaScript

Posted

I came across a little code the other day that reminded me I've been meaning to write about JavaScript patterns I take for granted. The code in question was intended to set the value of some fields in a form when a checkbox was selected; when it was deselected, the same fields were to be emptied. It looked not unlike this:

// config is defined outside of this snippet,
// and may contain more than the properties
// we care about 
$('#myCheckbox').click(function() {         
  if (this.checked) {                 
    $('#field_foo').val(config.foo);                 
    $('#field_bar').val(config.bar);                 
    $('#field_baz').val(config.baz);         
  } else {                 
    $('#field_foo').val('');                 
    $('#field_bar').val('');                 
    $('#field_baz').val('');         
  } 
});

This is a wholly readable bit of code -- there's almost no question what's going on here. On the other hand, it's pretty easy to see the rampant repetition; this code isn't interested in "don't repeat yourself" (DRY). We're calling the same method on every selection we make, and our selections are repeated in both the if and else block. When I saw this code, I had an immediate inclination to rewrite it. Here's what I came up with first:

// config is defined outside of this snippet,
// and may contain more than the properties
// we care about

$('#myCheckbox').click(function() {
        // note whether the checkbox is checked
        var checked = this.checked;

        // iterate over the keys we care about
        $.each(['foo', 'bar', 'baz'], function(i,v) {
                // find the field for the given key
                $('#field_' + v)
                        // and set its value either to the string
                        // stored for the key, or to an empty string,
                        // depending on whether the checkbox was checked
                        .val(checked ? config[v] : '');
        });
});

This looks approximately nothing like the initial code, and without the comments, the code itself would be substantially less readable than the original. The idealistic part of me -- the part that believes people who write JavaScript should understand JavaScript -- says this is an acceptable price to pay. And besides, there's something to be said for explaining the code in a comment that can be stripped by a minifier, rather than explaining the code via the code. In this iteration, we've introduced two patterns for DRY-er code: iterating over an array literal (or, alternately, an object) to achieve repetition without repeating ourselves, and using the ternary operator in place of an if/else statement when the simplicity of our logic allows it. The array literal serves as a list of the fields we care about. When our checkbox is clicked, we iterate over this list, build up a selector for each item in the list, make our selection, and then set the field value using a ternary operator. We've gone from 11 lines of code to six, with the added bonus that we have to do a lot less typing if we need our checkbox to affect more fields. (A side note: Is this premature optimization? I'd argue no, if you've learn to see these patterns before you start writing code. Once you learn how to spot these patterns in a requirement, writing code that embraces them can actually be easier than writing code that takes a more "literal" approach to the problem. For example, imagine if the checkbox affected 20 other fields instead of one? You'd undoubtedly find yourself copying and pasting code if you took the more "literal" approach to the problem, and that would be your first clue that you were doing something inefficiently.) The great thing about using a pattern like this is that it rapidly exposes the actual meat of what you're doing, and makes refactoring far less painful. I also find that it helps me see opportunities for reuse that I might not have spotted in the more literal version of the code. Let's say we're feeling all proud of ourselves for DRYing out our code using clever JavaScript that only super-smart people can read. Now there's another checkbox that needs similar behavior, but it's going to use a different config object and a different list of fields. No problem! You've already written this code, so you can just copy and paste it and then change what's different. Sweet. Er ... suddenly you're not looking so DRY after all. This is when another pattern comes into play: creating a function that returns another function with certain variables already baked in (that is, creating a closure). We'll execute this creator function and then use the function it returns in place of the anonymous function we were using previously when we bound to the click event.

// handleClick accepts a config object
// and a makeSelector function; it returns
// a function that can be bound to 
// a click event, using the config object
// and the makeSelector function to react
// appropriately to the click
var handleClick = function(fields, config, makeSelector) {
        return function() {
                var checked = this.checked;

                fields && $.each(fields, function(i, v) {
                        // build the selector using the provided
                        // makeSelector function
                        $(makeSelector(v))
                                // set the value using the
                                // config object, depending
                                // on whether the checkbox
                                // is checked
                                .val(checked ? config[v]: '');
                });
        };
};

$('#myCheckbox').click(
        // use handleClick to create a function
        // that has these variables baked in;
        // pass the created function as the
        // click handling function
        handleClick(
                ['foo','bar','baz'],
                myCheckboxConfig, 
                function(field) {
                        return '#field_' + field;
                }
        )
);

$('#myOtherCheckbox').click(
        handleClick(
                ['bim','bar','bop'],
                myOtherCheckboxConfig, 
                function(field) {
                        return 'input[name="' + field + '"]';
                }
        )
);

By creating a function that returns a function, we can isolate what's different about the two different handlers while centralizing the pieces that are the same. If your event handling function was slightly less trivial than this one, or if you were binding to five different checkboxes instead of two, the benefit of consolidating the code would be even more substantial. JavaScript offers plenty of patterns for writing DRY-er code; it's important to learn to both recognize and use them. It's also important to recognize when you're writing un-DRY code in the first place -- copying and pasting code is one crystal-clear indicator, but others are more subtle and you may not identify them on the first go-round. For example, take these two functions; each receives a list item as its only argument and returns either the next or previous list item, returning to the beginning or end of the list if there is no next or previous item.

var getNextItem = function($item) {
        return $item.next().length ? 
                $item.next() : $items.first();
};

var getPrevItem = function($item) {
        return $item.prev().length ?
                $item.prev() : $items.last();
};

This felt repetitive when I first wrote it, but I couldn't quickly come up with a single function that would work. A little thinking about it, though, led me to this single function which gets a second argument: direction. That argument is used to decide whether to run the item's next or previous method, and then whether to run the item's first or last method. Besides combining two functions into one, it also eliminates calling next or prev twice inside each function.

var getItem = function($item, direction) {
        var $returnItem = $item[direction]();
        return $returnItem.length ? 
                $returnItem : 
                $items[(direction == 'next') ? 'first' : 'last']();
};

Learning about patterns and then discovering opportunities to use them is one of the more pleasing parts of my job. I hope this helps you identify some of those opportunities for yourself :)

Filed under  //  front-end development   howto   javascript  
Comments (24)
Posted

Announcing jQuery Fundamentals: An Open-Source jQuery Training Curriculum

Posted

I've been leading jQuery trainings for more than a year now, from tiny gatherings that I organized myself at the local coworking space, to intensive two-day sessions at local web companies, to whirlwind one-day classes at governmental agencies. Over the course of those trainings, I've developed what I'd like to think is a decent curriculum -- training material that's the size of a small book, exercises that demonstrate core concepts, and solutions to those exercises that students can peek at later or when they get stuck. I decided recently that it was time for all of this material to see the light of day, so I spent the last several days converting it all to DocBook files that allow for easy publication to HTML and PDF (and other formats, if I'm later so inclined). I also fleshed out some topics that I'd given short shrift, and started planning sections covering advanced topics such as plugin authoring, code organization, best practices, and more. There's more to come in the next few days, but I think what I've done so far is worth sharing. I hope you'll agree.


Media_httpgyazocoma21_qfifz


My goals in releasing this are several. First and foremost, I want to see people writing better jQuery. The free resources for learning jQuery are scattered across the internets, and my personal experience of learning the library was haphazard — it was a long time before I learned some things I wish I'd known from the get-go. In addition, I want people who are writing jQuery to understand JavaScript. To that end, the book begins with a survey of JavaScript itself before jumping into jQuery. Finally, I want to enlist the bright minds of the jQuery community to help developing a robust, authoritative, in-depth jQuery curriculum, and in exchange it only seemed fair to make it available to everyone. I should mention that the goal of this material is to serve as a companion to a human instructor. That said, individuals may find it useful for self-study, especially if they're diligent about doing the exercises at the end of each chapter. If you're inclined to help -- by adding a chapter, a section, a paragraph, an exercise, or even just a correction -- fork the repo and send me a pull request. I look forward to seeing how this project might evolve with the community's help. Note: If you comment on this post pointing out an issue with the material, I will do my best to tend to the issue, but I probably won't publish your comment, as this post isn't the right place for reporting issues in the code. You can report issues at the repository, but if it's important to you, please fork the repository, make the change, and send me a pull request.

Filed under  //  front-end development   howto   javascript   jquery   jquery fundamentals   training  
Comments (37)
Posted

jQuery: Storing and retrieving data related to elements

Posted

It’s very common to need to get information about a DOM element when a user interacts with it — for example, perhaps you have an unordered list of names, and when a user clicks on a name, you want to show a picture of the person above the list. In order to do this, you need to figure out which person the clicked list item represents. Many beginning jQuery users will attempt to achieve this by putting ID attributes on each list item, such as id="rebecca". Then, they’ll read the ID attribute off the clicked element and use it to build a URL for the related image.

<ul class="people">
<li id="paul">Paul</li>
<li id="rebecca">Rebecca</li>
<li id="alex">Alex</li>
<li id="adam">Adam</li>
</ul>


var portrait = $('#portrait');

$('ul.people li').click(function() { 
    var name = $(this).attr('id');
    portrait.html('<img class="posterous_download_image" src="/images/people/'%20+%20name%20+%20'.jpg" alt="" />');
});

Strictly speaking, this will work. But is the ID really the right place to store this information? What if you need this behavior on other elements on the page too? You can’t have more than one element with the same ID on the page, so you might find yourself using funny prefixes in your IDs, like person_rebecca, and then stripping out the prefix. You could do it with classes, but then you’d have the opposite problem: you’re using (generally) unique classes like rebecca, but really classes are meant to indicate similarities among a set of elements. And what if you need to store more than one piece of information about an element on the element? Next thing you know you’ve got id="person_alex_red" and you’re jumping through all sorts of hoops to parse out the data you need.

Custom Data Attributes

HTML5 makes available [custom data attributes](http://dev.w3.org/html5/spec/Overview.html#custom-data-attribute, and they prove to be a much more elegant and robust solution to this problem. They’re custom, so they can contain pretty much anything you want, and each element can have as many of them as you want.

<ul class="people">
  <li>Paul</li>
  <li>Rebecca</li>
  <li>Alex</li>
  <li>Adam</li>
</ul>


var $portrait = $('#portrait');

$('ul.people li').click(function() { 
    var $li = $(this),
        name = $li.attr('data-name'),       
        color = $li.attr('data-hairColor'),
        $img = $('<img class="posterous_download_image" src="/images/people/'%20+%20name%20+%20'.jpg" alt="" />');

    $portrait.append($img).css('border', '5px solid ' + color);
});

$.fn.data

When you want to embed information about an element in the HTML you send down from your server, custom data attributes offer a clear and easy solution. But what if you want to attach data to elements that you’ve added to the page using JavaScript? For example, you might have some data you fetched from your server via an Ajax request:

{
    "items" : [
        { "name" : "Paul", "image" : "paul", "hairColor" : "black" },
        { "name" : "Rebecca", "image" : "rebecca", "hairColor" : "brown" },
        { "name" : "Alex", "image" : "alex", "hairColor" : "red" },
        { "name" : "Adam", "image" : "adam", "hairColor" : "red" }
    ]
}

You’re going to iterate over the data to produce a structure much like the one above, but in this case, it doesn’t make sense to store the related data in markup, because you’ll just have to extract it again later. Instead, you can use the $.fn.data() method in jQuery to store the data using JavaScript instead of markup:

var $target = $('ul.people');

$.each(response.items, function(i, data) {
    $('<li/>', { html : data.name })        
      .data({  
        name : data.image,              
        hairColor: data.hairColor       
      })        
      .appendTo($target); 
});

Later, you can read the data off the element using the $.fn.data() method again, this time passing just the name of the key you’re after:

var $portrait = $('#portrait');

$('ul.people li').click(function() { 
    var $li = $(this),
        name = $li.data('name'),
        color = $li.data('hairColor'),
        $img = $('<img class="posterous_download_image" src="/images/people/'%20+%20name%20+%20'.jpg" alt="" />');

    $portrait.append($img).css('border', '5px solid ' + color);
});

Mixing the two methods

This is all well and good, but what if you have a list of people that was sent from the server using HTML and custom data attributes, and then you add elements to it later using JavaScript and store data on them using $.fn.data()? Now your data is stored on elements in two different ways, so how do you extract it reliably? One option is to handle both cases. First, you’ll switch to using jQuery’s delegate method for the event binding, so you don’t have to keep binding click handlers as you add list items to your list. Then, inside of your click handler, you’ll figure out where you can get your data from:

var $portrait = $('#portrait');

$('ul.people').delegate('li', 'click', function() { 
    var $li = $(this), 
        name = $li.attr('data-name'), 
        color, $img;

    if (!name) { // did the li have custom data attributes?
        name = $li.data('name');
        color = $li.data('hairColor');
    } else {
        color = $li.attr('data-hairColor');
    }

    $img = $('<img class="posterous_download_image" src="/images/people/'%20+%20name%20+%20'.jpg" alt="" />');
    $portrait.append($img).css('border', '5px solid ' + color);
});

Another option is to iterate over the original elements, and store the data from the custom data attributes using the $.fn.data() method:

var $portrait = $('#portrait'), 
      $ul = $('ul.people');

$ul.find('li').each(function() {
    var $li = $(this);
    $li.data({
        name : $li.attr('data-name'),
        hairColor : $li.attr('data-hairColor')
    });
});

$('ul.people').delegate('li', 'click', function() { 
    var $li = $(this), 
        name = $li.data('name'),
        color = $li.data('hairColor'),
        $img = $('<img class="posterous_download_image" src="/images/people/'%20+%20name%20+%20'.jpg" alt="" />');

    $portrait.append($img).css('border', '5px solid ' + color);
});

// load more list items via ajax at some point
// to make that whole delegate thing worthwhile

Which option you use will depend on how large your original list is (and thus how long it will take to iterate over it), and how likely people are to click on a lot of items in the list (and thus whether the initial iteration is worth the time). I leave it as an exercise for the reader to decide which approach makes sense for you.

In Conclusion

When you need to attach data to elements and then extract that data later, there are options beyond classes and IDs, and in fact classes and IDs may be an especially poor way to approach the problem. Taking advantage of custom data attributes and the $.fn.data() method in jQuery can make it painless to store and retrieve data related to elements, and the metadata plugin can streamline the process for you even further.

Further Reading

Filed under  //  data   howto   html5   javascript   jquery  
Comments (8)
Posted

Building a standalone JavaScriptMVC controller.js

Posted

I've been doing a lot of work lately with a client who's using just the controller portion of JavaScriptMVC. I plan to write a more in-depth post about this while I'm traveling this week, but in the meantime, I wanted to jot down the steps to create the standalone controller.js file for my future reference. These instructions work for me on OSX 10.6.

  1. Go to the JavaScriptMVC downloads page on Github
  2. Download the latest version (3.0.0a0 as of this writing) of the framework and unzip it
  3. Open a terminal window and cd to the directory created when you unzipped the file
  4. Run steal/js steal/compress/plugin.js jquery/controller
  5. You should now have a new controller.js file in the directory; you're all set!

Filed under  //  howto   javascript   javascriptmvc   jquery  
Comment (1)
Posted

Demystifying custom events in jQuery

Posted

This article originally appeared in the May 2009 issue of JSMag. We’re all familiar with the basic events — click, mouseover, focus, blur, submit, etc. — that we can latch on to as a user interacts with the browser. Custom events open up a whole new world of event-driven programming. In this article, we’ll use jQuery’s custom events system to make a simple Twitter search application, but the general concepts should apply to any framework that supports custom events. I confess: it took me a long time to decide to learn about custom events. The built-in events seemed to suit my needs just fine, and it was difficult to understand why I’d want to start adding my own. Boy, was I missing out. It turns out that custom events offer a whole new way of thinking about event-driven JavaScript. Instead of focusing on the element that triggers an action, custom events put the spotlight on the element being acted upon. This brings a bevy of benefits, including:

  • behaviors of the target element can easily be triggered by different elements using the same code;
  • behaviors can be triggered across multiple, similar, target elements at once; and
  • behaviors are more clearly associated with the target element in code, making code easier to read and maintain
Why should you care? An example is probably the best way to explain. Suppose you have a lightbulb in a room in a house. The lightbulb is currently turned on, and it’s controlled by two three-way switches and a clapper, as shown here:
Triggering the clapper or either of the switches will change the state of the lightbulb. The switches and the clapper don’t care what state the lightbulb is in; they just want to change the state. Without custom events, you might write some code like this:
$('.switch, .clapper').click(function() {
        var $light = $(this).parent().find('.lightbulb');
        if ($light.hasClass('on')) {
                $light.removeClass('on').addClass('off');
        } else {
                $light.removeClass('off').addClass('on');
        }
});
With custom events, your code might look more like this:
$('.lightbulb').bind('changeState', function(e) {
        var $light = $(this);
        if ($light.hasClass('on')) {
                $light.removeClass('on').addClass('off');
        } else {
                $light.removeClass('off').addClass('on');
        }
});
$('.switch, .clapper').click(function() { 
        $(this).parent().find('.lightbulb').trigger('changeState');
});
This last bit of code is not that exciting, but something important has happened: we’ve moved the behavior of the lightbulb to the lightbulb, and away from the switches and the clapper. Let’s make our example a little more interesting. We’ll add another room to our house, along with a master switch, as shown here:
If there are any lights on in the house, we want the master switch to turn all the lights off; otherwise, we want it to turn all lights on. To accomplish this, we’ll add two more custom events to the lightbulbs: turnOn and turnOff. We’ll make use of them in the changeState custom event, and use some logic to decide which one the master switch should trigger:
$('.lightbulb').
bind('changeState', function(e) {
        var $light = $(this);
        if ($light.hasClass('on')) {
                $light.trigger('turnOff');
        } else {
                $light.trigger('turnOn');
        }
}).
bind('turnOn', function(e) {
        $(this).removeClass('off').addClass('on');
}).
bind('turnOff', function(e) {
        $(this).removeClass('off').addClass('on');
});

$('.switch, .clapper').click(function() { 
        $(this).parent().find('.lightbulb').trigger('changeState');
});

$('#master_switch').click(function() {
        if ($('.lightbulb.on').length) {
                $('.lightbulb').trigger('turnOff');
        } else {
                $('.lightbulb').trigger('turnOn');
        }
});
A bit more interesting, huh? Note how the rules about what the master switch does belongs to the master switch; the rules about how a lightbulb turns on and off belong to the lightbulb. It’s also worth noting that the master switch is able to affect all of the lightbulbs without having to iterate over them — it just triggers an event on all elements that have the class “lightbulb”. This isn’t critical to the example, but if we were to perform more elaborate manipulations of each lightbulb than adding and removing a class, this would be an important benefit of custom events, as you’ll see in the rest of the article. (If you’re accustomed to object-oriented programming, you may find it useful to think of custom events as methods of objects. Loosely speaking, the object to which the method belongs is created via the jQuery selector. Binding the changeState custom event to all $('.light') elements is akin to having a class called Light with a method of changeState(), and then instantiating new Light objects for each element with a classname of light.)

jQuery Event Primer

Before we dive in, a couple of things we need to recap. In the world of custom events, there are two important jQuery methods: .bind() and .trigger(). I encourage you to read the jQuery docs for details, but basically:
  • The .bind() method takes an event type and an event handling function as arguments. Optionally, it can also receive data, which will be available to the event handling function in the data property of the event object. The event handling function always receives the event object as its first argument.
  • The .trigger() method takes an event type as its argument. Optionally, it can also take an array of values. The first item in the array will be the second argument passed to the event handling function (after the event object).
Confused? I don’t blame you. Read on and I’ll try to clear some things up.

Our Mission

To demonstrate the power of custom events, we’re going to create a simple tool for searching Twitter. The tool will offer several ways for a user to add search terms to the display: by entering a search term in a text box, by entering multiple search terms in the URL, and by querying Twitter for trending terms. The results for each term will be shown in a results container; these containers will be able to be expanded, collapsed, refreshed, and removed, either individually or all at once. When we’re done, it will look like this:

The Setup

We’ll start with some basic HTML:
Twitter Search

Search Results for

This gives us a container (#twitter) for our widget, a template for our results containers (hidden via CSS), and a simple form where users can input a search term. (For the sake of simplicity, we’re going to assume that our application is JavaScript-only and that our users will always have CSS.) First, the setup. We’ll build an $actions object that we’ll use later to create the buttons in each results container, and we’ll also create a global search_terms object so we can store a list of search terms that are being displayed on the page. Next, we’ll do our custom event binding. There are two types of objects we’ll want to act on: the results containers, and the Twitter container.

The Results Containers

The results containers are the heart of the application. We’ll create a setupResults() plugin that will prepare each results container once it’s added to the Twitter container. Among other things, it will bind the custom events for each container and add the action buttons at the top right of each container. Each results container will have the following custom events:
  • The refresh event will mark the container as being in the “refreshing” state, and fire the $.getJSON() request to fetch the data for the search term.
  • The populate event will receive the returned JSON data and use it to populate the container.
  • The remove event will remove the container from the page after the user verifies the request to do so. Verification can be bypassed by passing true as the second argument to the event handler. The remove event also removes the term associated with the results container from the global search_terms object.
  • The collapse event will add a class of collapsed to the container, which will hide the results via CSS. It will also turn the container’s “Collapse” button into an “Expand” button.
  • The expand event will remove the collapsed class from the container. It will also turn the container’s “Expand” button into a “Collapse” button.
The plugin is also responsible for adding the action buttons to the container by cloning the $actions object that was created earlier. It binds a click event to each action’s list item, and uses the list item’s class to determine which custom event will be triggered on the corresponding results container.
// we'll use this every time we add a new results panel,
// so let's build it once and cache it in $actions
var $actions = $('
    '); $('
  • Refresh
  • ').appendTo($actions); $('
  • Remove
  • ').appendTo($actions); $('
  • Collapse
  • ').appendTo($actions); // this is where we'll keep track of which search terms // are shown on the page already var search_terms = {}; Here's the setupResults plugin:
    $.fn.setupResults = function(settings) {
            return $(this).each(function() {
                    
                    var $results = $(this);
                    var $actions = settings.actions;
                    var term = settings.term;
    
                    // change the "Search results for" text
                    $results.find('span.search_term').text(term);
    
                    // bind custom events for results box
                    $results. 
            
                    // the "refresh" event fetches 
                    // the latest content for the term
                    bind('refresh', function(e) {
                            // indicate that the results are refreshing
                            var $this = $(this).addClass('refreshing');
    
                            $this.find('p.tweet').remove();
                            $results.append('Loading ...');
    
                            // get the twitter data using jsonp
                            $.getJSON(
                                    'http://search.twitter.com/search.json?q=' +                                         escape(term) + '&rpp=5&callback=?', 
                                    function(json) { 
                                            $this.trigger('populate', [ json ]); 
                                    }
                            );
                    }).
                    
                    // the "populate" event takes results 
                    // in json format 
                    // and populates the results container
                    bind('populate', function(e, json) {
                            var results = json.results;
                            var $this = $(this);
                            
                            $this.find('p.loading').remove();
    
                            $.each(results, function(i,result) {
                                    var tweet = '' + 
                                            '' +
                                            result.from_user + 
                                            ': ' +
                                            result.text + 
                                            ' ' + 
                                            result.created_at + 
                                            '' +
                                    '';
                                    $this.append(tweet);
                                            });
    
                                            // indicate that the results 
                                            // are done refreshing
                                            $this.removeClass('refreshing');
                                    }).
                                                    // the remove event removes 
                                                    // the results from the page
                                                    // after the user confirms the action
                                                    bind('remove', function(e, force) {
                                                            // allow forced removal without confirmation
                                                            if (
                                                                    !force &&         
                                                                    !confirm('Remove panel for term ' + term + '?')
                                                            ) {
                                                                    return;
                                                            }
                                                            $(this).remove();
    
                                                            // indicate that we no longer 
                                                            // have a panel for the term
                                                            search_terms[term] = 0;
                                                    }).
    
                                                    // the collapse event collapses the results so only the
                                                    // header of the results section is showing
                                                    bind('collapse', function(e) {
                                                            $(this).find('li.collapse').removeClass('collapse')
                                                                    .addClass('expand').text('Expand');
    
                                                            $(this).addClass('collapsed');
                                                    }).
    
                                                    // the expand event
                                                    bind('expand', function(e) {
                                                            $(this).find('li.expand').removeClass('expand')
                                                                    .addClass('collapse').text('Collapse');
    
                                                            $(this).removeClass('collapsed');
                                                    });
    
                                                    if ($actions && $actions.length) {
                                                            // add a clone of $actions to the results panel
                                                            var $a = $actions.clone().prependTo($results);
    
                                                            // use the class of each action to figure out 
                                                            // which event it will trigger on the results panel
                                                            $a.find('li').click(function() {
                                                                    // pass the li that was clicked to the function
                                                                    // so it can be manipulated if needed
                                                                    $results.trigger(
                                                                            $(this).attr('class'), [ $(this) ]
                                                                    );
                                                            });
                                                    }
                                            });
                                    };

    The Twitter Container

    The Twitter container itself will have just two custom events:
    • The getResults event will receive a search term. It will check the global search_terms object to determine whether there’s already a results container for the term; if not, it will add a results container using the results template (div.template), set up the results container using the setupResults() plugin discussed above, and then trigger the refresh event on the results container in order to actually load the results. Finally, it will store the search term in the global search_terms object, so the application knows not to re-fetch the term.
    • The getTrends event will query Twitter for the top 10 trending terms. It will iterate over them and trigger the widget’s getResults event for each of them, thereby adding a results container for each term. Here you can see how we go about passing data to a triggered event.
    The Twitter container bindings are shown here:
    $('#twitter').
    bind('getResults', function(e, term) {
            // make sure we don't have a box for this term already
            if (!search_terms[term]) { 
                    var $this = $(this);
                    var $template = $this.find('div.template');
    
                    // make a copy of the template div
                    // and insert it as the first results box
                    $results = $template.clone().
                            removeClass('template').
                            insertBefore($this.find('div:first')).
                            setupResults({
                                    'term' : term,
                                    'actions' : $actions
                            });
    
                    // load the content using the "refresh" 
                    // custom event that we bound to the results container
                    $results.trigger('refresh');
                    search_terms[term] = 1;
            }
    }).
    
    bind('getTrends', function(e) {
            var $this = $(this);
            $.getJSON('http://search.twitter.com/trends.json?callback=?',                 function(json) {
                            var trends = json.trends; 
                            $.each(trends, function(i, trend) {
                                    $this.trigger('getResults', [ trend.name ]);
                            });
                    });
    });
    So far, we’ve written a lot of code that does approximately nothing, but that’s OK. By specifying all the behaviors that we want our core objects to have, we’ve created a solid framework for rapidly building out the interface. Let’s start by hooking up our text input and the “Load Trending Terms” button. For the text input, we’ll make the form submission stop in its tracks using e.preventDefault(), then capture the term that was entered in the input and pass it to the Twitter widget’s getResults event. (Again, you can see how we go about passing data to a triggered event.) Clicking the “Load Trending Terms” will simply trigger the Twitter widget’s getTrends event:
    $(document).ready(function() {
    
    $('form').submit(function(e) {
            e.preventDefault();
            var term = $('#search_term').val();
            $('#twitter').trigger('getResults', [ term ]);
    });
    
    $('#get_trends').click(function() {
            $('#twitter').trigger('getTrends'); 
    });
    
    });
    Just entering a search term into a text box is boring, of course — the following code shows how we can capture search terms from the URL’s hash (e.g., http://foo.com/index.html#foo,bar):
    $(document).ready(function() {
    
    // pass search terms via URL hash 
    if (document.location.hash) {
            var terms = document.location.hash.split(',').reverse();
            $.each(terms, function(i,term) {
                    $('#twitter').trigger('getResults', [ term ]);
            });
    }
    
    });
    By adding a few buttons with the appropriate IDs, we can make it possible to remove, collapse, expand, and refresh all results containers at once, as shown below. For the remove button, note how we’re passing a value of true to the event handler as its second argument, telling the event handler that we don’t want to verify the removal of individual containers.
    $(document).ready(function() {
    
    $('#refresh').click(function(e) {
            $('#twitter div.results').trigger('refresh'); 
    });
    
    $('#expand').click(function(e) {
            $('#twitter div.results').trigger('expand'); 
    });
    
    $('#collapse').click(function(e) {
            $('#twitter div.results').trigger('collapse'); 
    });
    
    $('#remove').click(function(e) {
            if (confirm('Remove all results?')) {
                    $('#twitter div.results').
                            trigger('remove', [ true ]);
            }
    });
    });
    It should be noted that you don’t have to choose one or all results containers — really, you can choose any results containers you want to affect. For example, if for some (strange) reason you wanted to refresh only the first and last results containers, you could do:
    $('div.results:first, div.results:last').
            trigger('refresh');
    You can see the entire application, including the full HTML and CSS, at http://www.rebeccamurphey.com/jsmag/custom-events/.

    Conclusion

    Custom events offer a new way of thinking about your code: they put the emphasis on the target of a behavior, not on the element that triggers it. If you take the time at the outset to spell out the pieces of your application, as well as the behaviors those pieces need to exhibit, custom events can provide a powerful way for you to “talk” to those pieces, either one at a time or en masse. Once the behaviors of a piece have been described, it becomes trivial to trigger those behaviors from anywhere, allowing for rapid creation of and experimentation with interface options. Finally, custom events can enhance code readability and maintainability, by making clear the relationship between an element and its behaviors.

    Learn More

    Filed under  //  howto   javascript   jquery  
    Comments (7)
    Posted

    Using Objects to Organize Your Code

    Posted

    This is a reprint of an article that originally appeared in the March 2009 issue of JSMag.

    When you move beyond simple snippets of jQuery and start developing more complex user interactions, your code can quickly become unwieldy and difficult to debug. This article shows you how to start thinking about these interactions in terms of the bits of behavior the feature comprises, using the object literal pattern.

    In the past few years, JavaScript libraries have given beginning developers the ability to add elaborate interactions to their sites. Some, like jQuery, have a syntax so simple that people with zero programming experience can quickly add bells and whistles to their pages. Adding all those bells and whistles, even some pretty elaborate ones, seems to be just a few Google searches away. A copy here, a paste there, a plugin or a few dozen lines of custom code — the client is duly impressed, and you’re adding jQuery to your resume.

    But wait. Now the requirements have changed. Now the thing that needed to work for three elements needs to work for ten. Now your code needs to be reused for a slightly different application where all the IDs are different. We’ve all seen the snippets that make jQuery (and other libraries) look dead-simple. What those snippets leave out — and hey, they’re just snippets, right? — is how to design your code when your needs go beyond dropping in a plugin or doing some show() and hide().

    Introducing the Object Literal pattern

    The object literal pattern offers a way to organize code by the behaviors it comprises. It’s also a means to keep your code from “polluting the global namespace,” which is a good practice for all projects and imperative for larger ones. It forces you to think at the outset about what your code will do and what pieces need to be in place in order for you to do it. An object literal is a way to encapsulate related behaviors, as shown here:

    var myObjectLiteral = {
        myBehavior1 : function() {
            /* do something */
        },
    
        myBehavior2 : function() {
            /* do something else */
        }
    };

    As an artificially simplistic example, suppose you had the jQuery shown in Listing 2 for showing and hiding content when a list item was clicked.

    $(document).ready(function() {
        $('#myFeature li')
            .append('<div/>')
            .each(function() {
                $(this).find('div')
                    .load('foo.php?item=' + $(this).attr('id'));
            })
        .click(function() {
            $(this).find('div').show();
            $(this).siblings().find('div').hide();
        });
    });

    Simple enough, and yet even in this example there are several things you might want to change later — for example, the way you determine the URL for loading the content, the destination of the loaded content, or the show and hide behavior. An object literal representation of the feature cleanly separates these aspects. It might look like this:

    var myFeature = {
        config : {
            wrapper : '#myFeature',
            container : 'div',
            urlBase : 'foo.php?item='
        },
    
        init : function(config) {
            $.extend(myFeature.config, config);
            $(myFeature.config.wrapper).find('li').
                each(function() {
                    myFeature.getContent($(this));
                }).
                click(function() {
                    myFeature.showContent($(this));
                });
        },
    
        buildUrl : function($li) {
            return myFeature.config.urlBase + $li.attr('id');
        }, 
    
        getContent : function($li) {
            $li.append(myFeature.config.container);
            var url = myFeature.buildUrl($li);
            $li.find(myFeature.config.container).load(url);
        },
    
        showContent : function($li) {
            $li.find('div').show();
            myFeature.hideContent($li.siblings());
        },
    
        hideContent : function($elements) {
            $elements.find('div').hide();
        }
    };
    
    $(document).ready(function() { myFeature.init(); });

    Because the initial example was incredibly simplistic, the object literal incarnation is longer. Truth be told, the object literal method generally won’t save you lines of code. What it will save is headaches. By using an object literal, we’ve broken our code into its logical parts, making it easy to locate the things we might want to change down the road. We’ve made our feature extendable, by providing the ability to pass in overrides to the default configuration. And, we’ve done some limited self-documentation — it’s easy to see at a glance what the feature does. As your needs grow beyond the simplicity of this example the benefits of the structure will become clearer, as you’ll see below.

    _Note: For an excellent primer on objects, properties, and methods, check out Object-Oriented JavaScript: Create scalable, reusable high-quality JavaScript applications and libraries by Stoyan Stefanov. You may also want to read up on JSON (JavaScript Object Notation).

    An in-depth example

    Our mission will be to create a UI element that features multiple pieces of content divided into several sections. Clicking on a section will show a list of items in the section; clicking on an item in the left nav will show the item in the content area. Whenever a section is shown, the first item in the section should be shown. The first section should be shown when the page loads.

    Step 1: Crafting the HTML

    Writing good semantic HTML is a crucial prerequisite to writing good JavaScript, so let’s start by thinking about what the HTML for something like this might look like. The HTML should:

    • Make sense (and work) when JavaScript isn’t available.
    • Provide a predictable DOM to which we can attach JavaScript.
    • Avoid unnecessary IDs and classes (and you might be surprised by how few are necessary).

    With those guidelines in mind, we’ll start with this html. Note that we haven’t included any markup to display the section navigation or the item navigation; those pieces will be added by jQuery since they will only work with jQuery; non-JavaScript users will get nice semantic markup. (If there’s anything surprising or confusing in that HTML, now would be a good time to read up on POSH (plain-old semantic HTML) and progressive enhancement.)

    Step 2: Scaffolding the Object

    My first step in creating an object for a feature is to create “stubs” within the object. Stubs are basically placeholders; they’re the outline for the feature we’re going to build. Our object will have the following methods:

    • myFeature.init() will run on $(document).ready(). It will turn the semantic HTML we start with into a JavaScript-enabled user interface.
    • myFeature.buildSectionNav() will be called by myFeature.init(). It will take a jQuery object that contains all of the sections from the semantic HTML and use those sections to build the top navigation. It will bind the click handlers to the top navigation items so that clicking on them will show the appropriate section.
    • myFeature.buildItemNav() will be called by myFeature.showSection(). It will ake a jQuery object that contains all of the items associated with the section from the semantic HTML, and use them to build the side navigation. It will bind the click handlers to the side navigation items so that clicking on them will show the appropriate content.
    • myFeature.showSection() will be called when the user clicks on an item in the top navigation. It will use the navigation item that’s clicked on to figure out which section to show from the semantic HTML.
    • myFeature.showContentItem() will be called when the user clicks on an item in the side navigation. It will use the navigation item that’s clicked on to figure out which content item to show from the semantic HTML.

    We’ll also make room for a configuration property, myFeature.config, which will be a single location for setting default values rather than scattering them throughout the code. We’ll include the ability to override the defaults when we define the myFeature.init() method.

    var myFeature = { 
        'config' : { },
        'init' : function() { },
        'buildSectionNav' : function() { },
        'buildItemNav' : function() { },
        'showSection' : function() { },
        'showContentItem' : function() { }
    };

    Step 3: The Code

    Once we’ve built this skeleton, it’s time to start coding. Let’s start by setting up a simple myFeature.config object and writing the myFeature.init() method:

    'config' : { 
        // default container is #myFeature
        'container' : $('#myFeature')
    },
    
    'init' : function(config) { 
        // provide for custom configuration via init()
        if (config && typeof(config) == 'object') {
            $.extend(myFeature.config, config);
        }
    
        // create and/or cache some DOM elements 
        // we'll want to use throughout the code
        myFeature.$container = myFeature.config.container;
    
        myFeature.$sections = myFeature.$container.
            // only select immediate children!
            find('ul.sections > li'); 
    
        myFeature.$section_nav = $('<p/>')
          .attr('id','section_nav')
          .prependTo(myFeature.$container);     
    
        myFeature.$item_nav = $('<p/>')
          .attr('id','item_nav')
          .insertAfter(myFeature.$section_nav);     
    
        myFeature.$content = $('<p/>')
          .attr('id','content')
          . insertAfter(myFeature.$item_nav);   
    
      // build the section-level nav and    
      // "click" the first item
      myFeature.buildSectionNav(myFeature.$sections);   
      myFeature.$section_nav.find('li:first').click();      
    
      // hide the plain HTML from sight
      myFeature.$container.find('ul.sections').hide();      
    
      // make a note that the initialization    
      // is complete; we don't strictly need this   
      // for this iteration, but it can come in handy   
      myFeature.initialized = true;  
    }

    Next we’ll create the myFeature.buildSectionNav() method:

    'buildSectionNav' : function($sections) {
    
        // iterate over the provided list of sections
        $sections.each(function() {
    
            // get the section
            var $section = $(this);     
    
            // create a list item for the section navigation
            $('<li/>')
              // use the text of the first h2
              // in the section as the text for
              // the section navigation
              .text($section.find('h2:first').text())
    
              // add the list item to the section navigation
              .appendTo(myFeature.$section_nav)
    
              // use data() to store a reference
              // to the original section on the
              // newly-created list item
              .data('section', $section)
    
              // bind the click behavior
              // to the newly created list itme
              // so it will show the section
              .click(myFeature.showSection);
    });

    Next we’ll create the myFeature.buildItemNav() method:

    ‘buildItemNav’ : function($items) {

    // iterate over the provided list of items
    $items.each(function() {
    
        // get the item
        var $item = $(this);
    
        // create a list item element for the
        // item navigation
        $('<li>')
    
          // use the text of the first h3
          // in the item as the text for the
          // item navigation
          .text($item.find('h3:first').text())
    
          // add the list item to the item navigation
          .appendTo(myFeature.$item.nav)
    
          // use data to store a reference 
          // to the original item on the 
          // newly created list item
          .data('item', $item)
    
          // bind the click behavior to the
          // newly created list item so it will
          // show the content item
          .click(myFeature.showContentItem);
    
    });

    Finally, we’ll write the methods for showing sections and content items:

    'showSection' : function() { 
        // capture the list item that was clicked on
      var $li = $(this);
    
      // clear out the left nav and content area
      myFeature.$item_nav.empty();
      myFeature.$content.empty();
    
      // get the jQuery section object from the orginal HTML,
      // which we stored using data() during buildSectionNav
      var $section = $li.data('section');
    
      // mark the clicked list item as current
      // and remove the current marker from its siblings
      $li.addClass('current')
        .siblings().removeClass('current');
    
      // find all of the items related to the section
      var $items = $section.find('ul li');
    
      // build the item nav for the section
      myFeature.buildItemNav($items);
    
      // "click" on the first list item in the section's item nav
      myFeature.$item_nav.find('li:first').click();
    
    }, 
    
    'showContentItem' : function() {
      var $li = $(this);
    
      // mark the clicked list item as current
      // and revmoe the current marker from its siblings
      $li.addClass('current')
        .siblings().removeClass('current');
    
      // get the jQuery item object from the original HTML,
      // which we stored using data during buildContentNav
      var $item = $li.data('item');
    
      // use the item's HTML to populate the content area
      myFeature.$content.html($item.html());
    }

    All that’s left to do is to call the myFeature.init() method:

    $(document).ready(myFeature.init);

    You can see the whole thing (including a little CSS to make it presentable) here.

    Step 4: Changing Requirements

    No project is complete without some last-minute change in the requirements, right? Here’s where the object literal approach really shines by making it quick and fairly painless to implement last-minute changes. What if we need to get the content item excerpts via AJAX instead of from the HTML? Assuming the backend is set up to handle it, try this:

    var myFeature = {
    
        'config' : { 
            'container' : $('#myFeature'),
    
            // configurable function for getting
            // a URL for loading item content
            'getItemURL' : function($item) {
                return $item.find('a:first').attr('href');
            }
    
        },
    
        'init' : function(config) { 
            // stays the same   
        },
    
        'buildSectionNav' : function($sections) {
            // stays the same   
        },
    
        'buildItemNav' : function($items) {
            // stays the same   
        },
    
        'showSection' : function() { 
            // stays the same
        },
    
        'showContentItem' : function() {
    
            var $li = $(this);
    
            $li.addClass('current').
                siblings().removeClass('current');
    
            var $item = $li.data('item');
            var url = myFeature.config.getItemURL($item);
    
            // myFeature.$content.html($item.html());
            myFeature.$content.load(url);
    
        }
    
    };

    Do you need more flexibility? There’s a lot more you can configure (and therefore override) if you really want to make this flexible. For example, you can use myFeature.config to specify how to find and process the title text for each item in the left nav.

    var myFeature = { 
        'config' : { 
            'container' : $('#myFeature'),
    
            // specify the default selector
            // for finding the text to use
            // for each item in the item nav
            'itemNavSelector' : 'h3',
    
            // specify a default callback 
            // for "processing" the jQuery object
            // returned by the itemNavText selector
            'itemNavProcessor' : function($selection) {
                return 'Preview of ' + 
                    $selection.eq(0).text();
            }
        },
    
        'init' : function(config) {
            // stays the same
        },
    
        'buildSectionNav' : function($sections) {
            // stays the same
        },
    
        'buildItemNav' : function($items) {
    
            $items.each(function() {
                var $item = $(this);
    
                // use the selector and processor 
                // from the config
                // to get the text for each item nav
                var myText = myFeature.config.itemNavProcessor(
                    $item.find(myFeature.config.itemNavSelector)
                );
    
                $('<li/>')
                // use the new variable                 
                // as the text for the nav item                 
                  .text(myText)
                  .appendTo(myFeature.$item_nav)
                  .data('item', $item)
                  .click(myFeature.showContentItem);        
          });   
      },    
    
      'showSection' : function() {      
        // stays the same   
      },    
    
      'showContentItem' : function() {      
        // stays the same   
      }  
    
    };

    Once you’ve added defaults to the config object, you can override them when you call myFeature.init():

    $(document).ready(function() { 
        myFeature.init({ 'itemNavSelector' : 'h2' });
    });

    Beyond the scope of this article (but also interesting to contemplate and much easier with the object literal pattern) is this: making the back button retrace your path through the tabs using the jQuery history plugin. I leave it as an exercise for the reader.

    Conclusion

    If you’ve stepped through the code examples in this column, you should have a basic understanding of the object literal pattern and how it might prove useful to you as you develop more complex features and interactions. You also have access to some code that you can use to build on this basic foundation.

    I encourage you to give this pattern a try the next time you find yourself writing more than a few lines of JavaScript — it forces you to think through the elements and behaviors that make up a complex feature or interaction. Once you become proficient, it provides a sturdy foundation for extending and reusing your code.

    Learn More

    Filed under  //  howto   jquery  
    Comments (15)
    Posted

    jQuery validation and TinyMCE

    Posted

    Just solved a problem where the jQuery validation plugin wasn't playing so nicely with TinyMCE -- the validation plugin was trying to validate the textarea before TinyMCE had a chance to copy the editor contents back to the textarea. I was about to yank TinyMCE out of the page but a little reading through the TinyMCE docs led me to try this:

    $('#mySubmitButton').click(function() {
        var content = tinyMCE.activeEditor.getContent(); // get the content
        $('#myTextarea').val(content); // put it in the textarea
    });
    
    $('#myForm').validate();
    And what do you know, it works. One note: it's important to bind the content replacement to the click event of the submit button, not to the actual form submission, or else the validation may try to run before the content gets copied back to the textarea.

    Filed under  //  howto   jquery  
    Comments (23)
    Posted

    Planning a Wordpress CMS site

    Posted

    There have been plenty of rumblings lately about how WordPress can be used as a content management system, beyond its core competency as a blogging platform. By harnessing the power of pages and subpages, writing custom Page templates, segmenting posts into category-based content feeds, and using handy little plugins like my brother's Page Link Manager, you can do some pretty neat stuff. Lots of people have caught on, including small advertising and marketing agencies that want to be in the content and design business while staying away from programming. In my work with some of those agencies lately, I've often found that there's a gap between having the idea to use WordPress as a CMS and knowing the inner workings of WordPress that allow it to be used as a CMS. Since I often find myself needing to explain the building blocks of WordPress and how to use them as part of a lightweight CMS, I thought I should write my thoughts down. My goal here is not to get into the nitty gritty of actually implementing a WordPress-as-CMS site; I leave that to skilled WordPress developers ;). Rather, it's to give non-technical people an overview of how WordPress works so they can make the most of it during the site planning process. Note: In doing my homework for this post, I discovered lots of posts that mentioned workarounds for earlier versions of WordPress. Many of these things -- such as setting a particular Page to be your homepage -- are build in to the newer versions of WordPress. Make sure if you're reading a WordPress as CMS tutorial that you are reading one for the current version. Content building blocks WordPress offers a few building blocks for content management: posts, categories, pages (better referred to as sections), and custom fields.

    • Posts and Categories are used to organize related content. By assigning a Post to a Category, it can be grouped with other Posts in that Category. For example, you could have a "team members" Category and a "news" Category, and then easily display all team members in one location, and all news items in a completely separate location. You could even have a "blog" Category, which would allow you to have a blog on the site while still using Posts to manage other content as well. (Besides allowing this type of organization, Categories also automatically create Category Archives -- an easy way for your users to browse all content related to a Category, if you choose.)
    • Pages are used to manage static content, and are used for the site's navigation. They can also display other content items -- both Posts and Pages -- that match certain criteria. (For the sake of this discussion, pages and subpages may be better thought of as sections and subsections; not every "page" (i.e. URL) on a Wordpress site is managed using a Page in the Wordpress admin, but every section and subsection is managed that way. When I'm talking about a Page with a capital "P", I'm referring to the kind that you manage through the WordPress admin; when I'm talking about a page with a lowercase "p", I mean any part of your site that has a unique URL.) It's important to note that, using the Page Link Manager plugin mentioned above, you can easily exclude any Page from the site navigation, which makes them much more powerful for managing pieces of content that don't need to show up in the navigation.
    • Custom fields can be added to both Pages and Posts. They can contain all sorts of extra information related to a content item, which can then be accessed whenever the content item is displayed.
    Theme building blocks The design of a WordPress site is managed using a theme. Themes include a global header, footer and sidebar. They also include a variety of files for managing the design and structure of the content area, depending on the page you are viewing. A very basic theme will include the following content area templates:
    • Latest Posts template
    • Single Post template
    • Default Page template
    • Search results template (used for displaying all Posts and Pages containing a term)
    • Archives template (used for displaying date-, author- and Category-based Post archives)
    Harnessing the power of custom templates This is where things get interesting. On a regular WordPress blog, the default Page template will just display some static content that's entered into the Page using WordPress admin, plus the site's header, footer, and sidebar. This is fine for Pages that don't need to pull in any other content, like, say, an About section. When you start using WordPress as a CMS, you need to start doing more creative things with the content area of your Pages by using custom Page templates. For the News Page of a site, you might create a custom template that would include introductory content entered via the WordPress admin, along with the 10 most recent posts in the "news" Category, ordered from newest to oldest, and a link to view all items in the "news" category. For the Home Page of a site, you might create a more elaborate custom template, pulling Posts from a variety of Categories, pulling static content from a variety of hidden Pages, and displaying it all in a variety of ways: full content, titles only excerpts, etc. You might even make use of custom fields. Do you want to be able to show a thumbnail with each Post in the "video" category, but only when you're showing those posts on your homepage? Add a custom field to those posts with the URL to the thumbnail, then tell your custom Home Page template to look for that custom field and use it to insert an image tag. You could just as easily create a custom field containing the name of a PHP file to be used as a custom dynamic sidebar for that page; the options here are pretty much limited by your imagination (and your developer). Developing the site architecture In planning a site that will use WordPress as a CMS, it's imperative to think of how your content, sections and subsections fit into the WordPress model. It will save you lots of headaches when it comes time to actually develop the site, and lots of calls and emails from your developer trying to figure out what you had in mind. In fact, I strongly recommend getting a skilled WordPress developer involved early in the process, to help you turn your ideas into a viable spec. Here's the basic process I recommend:
    • Develop a simple sitemap for the site. Figure out what the sections and subsections are, and how they're organized.
    • Identify the pieces content that will appear on each item in your sitemap, and evaluate how they should be managed. Is the content static and self-contained, such as "About Our Company" text? It should probably be managed through a Page. Is the content some kind of list of related items, such as news items or team members? It should probably be managed through Posts and Categories. Is it a secondary sidebar that appears on several pages? Consider using a hidden page if the sidebar is static or a PHP include if the sidebar uses other WordPress content items.
    • Identify the Categories you'll use to distribute the Posts to their proper place(s). For each category, evaluate whether you'll need to collect information beyond title, body, and author. If so, identify these as custom fields.
    • Identify the Pages you'll be using on the site -- including Pages that will be hidden in the navigation and used solely for placing static content on multiple pages.
    • Decide how you will use your header, sidebar and footer to help users navigate your site. Will you provide links to Category archives? Will you allow your visitors to search your site? Will you show subpages of the Page a visitor is on?
    • Identify which visible Pages will use a default Page template, and which will require a custom template, and identify the types of custom templates that will be required. Ideally, you will not require a separate custom template for every custom Page; look for similarities among the custom Pages, and try to identify the fewest number of custom templates that will do the job.
    Plan the design based on the architecture Once you've completed the site architecture, you are ready to actually design the site. For your own sanity, don't try to do too much design before this point. You'll need the information gathered in the site architecture phase to guide you in developing the design document upon which your template developer will base their work; a design document that doesn't take all of these considerations into account is going to lead your developer in circles as they try to make sense of what you had in mind. Before you begin designing, you may want to draw up some wireframes that show the different elements you'll be designing -- header, footer, sidebar, custom page views, single post views, etc. They can guide the design process and be valuable to the developer who implements your design. When you're finished, your design document should include the following:
    • General header. Note any ways in which the header should change according to the context in which it's being displayed -- for example, a breadcrumb or an indication of the current navigation item.
    • General sidebar (or left and right sidebar). Again, note ways in which the sidebar should change according to the context in which it's being displayed.
    • General footer.
    • Latest Posts view. If you are going to give your visitors the ability to view your latests Posts, make sure you design a view for this. The latest Posts view will usually include 10 posts; for each Post, you'll usually show the title, author, date, category, body, and comment count.
    • Single Post view. Ideally the structure of this design will vary little from one Category to another, though the visual presentation may change. This design will likely include the post's title, body, author, date, category/ies, navigation to the next and previous post, a comment submission form, and user comments.
    • Archive view. This will be used for viewing all of the Posts by Category, month, year, author, etc. This is only necessary if you will give users the option of viewing posts this way. Usually this view will include multiple Posts. For each post, you'll show the title and an excerpt; you may also choose to show date, author and category information.
    • Search view. This will be used for displaying user search results. It is usually very much like the Archive view: a list of posts, for which you show the title, an excerpt, and any other relevant information.
    • Default Page view. This will be used for displaying Pages that do not have custom templates associated with them. Usually this will consist of the Page title and body, and potentially a secondary sidebar.
    • Custom Page views. For every custom view identified in the site architecture phase, create a corresponding custom design.
    Giving consideration to how a site will be built in WordPress is a tad more complicated than deciding that it should be built in WordPress and then handing it off to a developer; of course, you can do it that way, but it's not exactly the most cost-effective method. Understanding the system, working with a developer from the start, and doing the planning before you dive into design will give you the results you intend, and probably open your eyes to some connections and possibilities that you can then decide how to take advantage of, rather than leaving it up to your developer to see them and decide whether to bring them to your attention. It's a lot to wrap your brain around at first, perhaps, but I've found that the planning pays for itself by smoothing the rest of the process and truly taking advantage of the CMS features built into Wordpress.

    Filed under  //  howto   small business websites  
    Comments (5)
    Posted

    Attach data from classnames to an element using $().data()

    Posted

    At DailyStrength, we're working on an abstract way to attach a Javascript behavior (popping a modal, sliding in a form) to objects -- journals, comments, etc. -- being displayed on the site. We wanted to attach some information to the objects by putting their type and ID in classnames. Then it would be up to Javascript to extract that data if it needed it. Previously, I'd done this on an as-needed basis with a function custom to the object and behavior, but I wanted a way to do it going forward without thinking about it. I'd read about the $().data() function that was made available in jQuery 1.2.3, and figured I could leverage it for the purpose. Now, given an element like

    an element
    I can use a simple plugin to assign data to an element:
    {
      key1 : [ value1, value2, value3],
      key2 : value4
    }
    This means that I can then do $(element).data('key1') and get back the array. I also added a little helper to get back all of the keys for an element, so I can do:
    $(element).getDataFromClassname();
    var element_keys = $(element).data('keys');
    var element_data = [ ];
    $.each(element_keys, function(key,value) {
      element_data[key] = value;
    });
    Here's the simple plugin:
    $.fn.getDataFromClass = function() {
            return $(this).each(function() {
                    var $this = $(this);
                    if ($this.attr('class').match('_')) {
                            var classes = $this.attr('class').split(' ');
                            var keys = [ 'keys' ];
                            $.each(classes, function(i,c) {
                                    if (c.match('_')) {
                                            var parts = c.split('_');
                                            var prefix = parts[0];
                                            keys.push(prefix);
                                            var data;
                                            if (parts[1].match('-')) {
                                                    data = parts[1].split('-');
                                            } else {
                                                    data = parts[1];
                                            }
                                            $this.data(prefix,data);
                                    }
                            });
                            $this.data('keys',keys);
                    }
            });
    };

    Filed under  //  front-end development   howto   jquery  
    Comments (0)
    Posted

    Track user clicks on certain links

    Posted

    We recently added a new feature to dailystrength.org, and there was much debate about whether users would use it as we intended. To find out, I wrote a clicktrack ditty that sends some data to the server when a link is clicked. It was somewhat inspired by a post I wrote a while ago about tracking outbound links with Google Analytics and jQuery, but is actually even simpler. I've retooled my clicktrack script to work as a generic, 2.5k plugin, which you can download here.

    Usage

    To use this plugin, add a distinct classname to all links you would like to track. For example:
    this is a link
    You can also add additional classes to a link with a distinct prefix — the default prefix is "ct_". These classes will be passed to the server along with other clicktrack data:
    this is a link
    Then, include the script and a line of code to initialize it in the foot of your page. Note that you must at least pass a URL for the remote (server-side) script that will handle the clicktrack data ("foo.php" in the example below):
    type="text/javascript">
    
    $(document).ready(function() { $("a.clicktrack").clicktrack("foo.php"); });
    This will pass the following data to a script called "foo.php" on your server:
    • source (string) the URL of the current page
    • target (string) the HREF of the clicked link
    • classes (array) classes on the link that matched the prefix (default prefix is "ct_")

    Optional parameters

    The only required parameter is the URL of the remote script. However, you can pass a more elaborate configuration object. The options and their defaults are as follows:
    var defaults = {
                    remote_script: null,
                    prefix: 'ct_',
                    extraData: null,
                    callback: function(){},
                    dataType: null,
                    sendOnce: true,
                    preventDefault: false
            };
    • remote_script the remote script that will handle the clicktrack data. this can be passed as a single, string argument to clicktrack(), or it can be passed as part of the configuration object, but it must be specified
    • prefix the prefix for classnames that should be passed as part of the clicktrack data. defaults to "ct_".
    • extraData additional data that should be passed as part of the clicktrack data.
    • callback function to be executed upon successful execution of the remote script. you'll need to set preventDefault to true for this callback to get executed, and then redirect the user using window.location within the callback as required.
    • dataType type of data you expect to be returned by the remote script. see the jQuery documentation on $.post() for details.
    • sendOnce whether clicktrack data for a given link should be sent just once. defaults to true.
    • preventDefault whether clicks on clicktrack links should be prevented from taking the user to the link destination. defaults to false (links will work as expected by default).

    Filed under  //  analytics   howto   jquery   plugins  
    Comments (3)
    Posted