Applying Foundational Design Patterns in jQuery Development

Since its introduction in 2006, the jQuery library has dramatically simplified Document Object Model (DOM) traversal and manipulation. This ease of use fostered the development of web pages with increasingly complex user interactions, contributing to the web's evolution as a platform capable of supporting substantial application implementations.

This article explores a series of best practices that enhance web application development efficiency. It also analyzes key computer science design patterns applicable to web development, demonstrating how to leverage widely used and tested techniques from other programming domains, originally conceived as generic solutions for complex problems.

By examining various design patterns in their jQuery implementations, this discussion illustrates how they can improve the organization of our code. Adopting these patterns empowers developers to create more structured implementations, solving broad categories of problems more quickly. Furthermore, in team development environments, these patterns can foster better communication and lead to more consistent implementations, making each segment of the codebase easier for others to understand.

jQuery and the Composite Pattern

Before the advent of Web 2.0, the web primarily served as a document-based medium, offering basic page linking and client-side scripting largely confined to form validation. By 2005, with the release of applications like Gmail and Google Maps, JavaScript proved its capability as a language for enterprises to build large-scale applications with rich user interfaces.

While JavaScript's core language evolved slowly, expectations for web page functionality dramatically shifted. Web developers were increasingly required to deliver intricate user interactions, leading to the popularization of the term "Web Application." Consequently, the need for code abstractions, defined best practices, and the adoption of applicable computer science design patterns became apparent. JavaScript's growing use in enterprise applications spurred language development, and with the release of ECMAScript 2015 (ES6), the language expanded to facilitate the use of more design patterns.

In August 2006, John Resig launched the jQuery library on jquery.com, aiming to provide a convenient API for targeting DOM elements. It swiftly became an essential component of web developers' toolkits. jQuery incorporates several design patterns at its core, implicitly encouraging their use through its API. The Composite pattern, in particular, is central to jQuery, exposed directly through the fundamental jQuery() function, which is pivotal for DOM traversal.

This section will review DOM scripting with jQuery, introduce the Composite pattern and its application within jQuery, highlight jQuery's benefits over vanilla JavaScript DOM manipulation, and introduce the Iterator pattern, demonstrating its use in a sample application.

DOM Scripting with jQuery

DOM scripting refers to any process that modifies or manipulates web page elements after the browser has loaded them. The DOM API, standardized in 1998, is a JavaScript API providing methods for developers to manipulate elements within the DOM tree built by the browser after parsing the HTML code. Initially, DOM scripting was mainly used for client-side form validation, but over time, as JavaScript gained enterprise trust, more complex user interactions became feasible.

The initial release of the jQuery library in August 2006 aimed to simplify how web developers traversed and manipulated the DOM tree. A primary goal was to offer abstractions leading to shorter, more readable, and less error-prone code, while ensuring cross-browser compatibility.

jQuery's guiding principles are evident on its homepage, where it describes itself as: "…a fast, small, and feature-rich JavaScript library. It makes things like HTML document traversal and manipulation, event handling, animation, and Ajax much simpler with an easy-to-use API that works across a multitude of browsers. With a combination of versatility and extensibility, jQuery has changed the way millions of people write JavaScript."

The abstract API jQuery provided from the start, coupled with its orchestration of various design patterns, led to its widespread adoption. Many sources indicate that over 60% of the world's most visited websites rely on the jQuery library.

Manipulating the DOM with jQuery

Let's review jQuery by performing some basic DOM manipulations on a sample web page. We'll load a page with a simple structure, then use jQuery to alter its content and layout. To clearly show the effects, we'll configure the changes to occur approximately 800 milliseconds after page load.

Consider the following initial HTML structure:


<html>
  <head>
    <title>Page Modifiers</title>
    <link rel="stylesheet" type="text/css" href="page-styles.css">
  </head>
  <body>
    <h1 id="mainHeading">Page Modifiers</h1>

    <div class="itemWrapper">
      <div>
        <p class="itemCard">
          DOM manipulation is easy with modern JS!
        </p>
      </div>
      <div>
        <p class="itemCard">
          DOM manipulation is easy with modern JS!
        </p>
      </div>
      <div>
        <p class="itemCard">
          DOM manipulation is easy with modern JS!
        </p>
      </div>
    </div>

    <p class="itemCard">
      DOM manipulation is easy with modern JS!
    </p>
    <p class="itemCard">
      DOM manipulation is easy with modern JS!
    </p>

    <script type="text/javascript" src="path/to/jquery-2.2.0.min.js"></script>
    <script type="text/javascript" src="page-script.js"></script>
  </body>
</html>

The accompanying CSS is straightforward, defining three utility classes:


.itemCard {
    padding: 8px 12px;
    border: solid 1px #444;
    margin: 6px 4px;
    box-shadow: 0 1px 3px #888;
}

.columnSizer {
    float: left;
    width: 32%;
}

.clearFloat { clear: both; }

Initially, the page will display the content vertically. In the CSS, .itemCard adds styling to elements, making them appear like distinct cards. .columnSizer will set the width of its parent to approximately one-third, facilitating a three-column layout. .clearFloat will be used to break the column layout, ensuring subsequent elements appear below. The .columnSizer and .clearFloat classes are not initially present in the HTML but will be applied through our JavaScript DOM manipulations.

Within the HTML <body>, we define an <h1> heading with the ID mainHeading for easy selection via JavaScript. Below it, five <p> elements, each with the itemCard class, are present. The first three are nested within <div> elements, themselves within an outer <div> with the itemWrapper class.

The two <script> tags first link to the jQuery library (e.g., from a CDN), then to our JavaScript file containing the manipulation code:


setTimeout(function() {
    $('#mainHeading').css('font-size', '3.2em');

    var $displayCards = $('.itemWrapper .itemCard');
    $displayCards.append(
      '<br /><br /><i>Additional information can go here.</i>');
    $displayCards.parent().addClass('columnSizer');

    $('.itemWrapper').append('<div class="clearFloat"></div>');
}, 800);

All our code is wrapped in a setTimeout call to defer its execution. The anonymous function passed as the first argument will execute 800 milliseconds later. In the first line of our callback, we use jQuery's $() function to target the element with ID mainHeading and increase its font-size using the css() method. Next, we provide a more complex CSS selector to $() to locate all elements with the itemCard class that are descendants of an element with the itemWrapper class, storing the result in the $displayCards variable.

It's a common practice for developers to use a naming convention for variables holding specific types of objects. For jQuery developers, variable names starting with a "$" symbol are often used when the variable stores the result of the $() function (also known as a jQuery collection object).

After selecting our target itemCard elements, we append two line breaks and some italicized text to each. Then, using the $displayCards variable, we traverse up one level in the DOM tree with the parent() method. The parent() method returns a new jQuery object containing the parent <div> elements of our originally selected cards. We then chain the addClass() method to assign them the columnSizer CSS class.

If you need to traverse all parent nodes of the selected elements, consider using the $.fn.parents() method. If you only need to find the first ancestor element matching a given CSS selector, use $.fn.closest() instead.

Finally, since the columnSizer class uses floats for its three-column layout, we need to clear the floats within the itemWrapper. We again use the simple .itemWrapper CSS selector with the $() function. Then, we call the append() method to create a new <div> element with the .clearFloat CSS class and insert it at the end of the itemWrapper.

After 800 milliseconds, our jQuery code will complete, resulting in the desired three-column layout. In its final state, the HTML for our itemWrapper element would resemble:


<div class="itemWrapper">
  <div class="columnSizer">
    <p class="itemCard">
      DOM manipulation is easy with modern JS!
      <br><br><i>Additional information can go here.</i>
    </p>
  </div>
  <div class="columnSizer">
    <p class="itemCard">
      DOM manipulation is easy with modern JS!
      <br><br><i>Additional information can go here.</i>
    </p>
  </div>
  <div class="columnSizer">
    <p class="itemCard">
      DOM manipulation is easy with modern JS!
      <br><br><i>Additional information can go here.</i>
    </p>
  </div>
  <div class="clearFloat"></div>
</div>

Method Chaining and Fluid Interfaces

The example above could be further condensed by merging the three card-related statements into a single chain:


$('.itemWrapper .itemCard')
  .append('<br /><br /><i>Additional information can go here.</i>')
  .parent()
  .addClass('columnSizer');

This syntactic pattern is known as method chaining and is widely endorsed within the jQuery and broader JavaScript communities. Method chaining is a component of the fluid interface object-oriented design pattern, where each method passes its operational context to the subsequent method. Most jQuery methods that operate on jQuery objects return either the same or a new jQuery element collection object. This enables chaining multiple methods, which not only makes the code more readable and expressive but also reduces the number of required variable declarations.

The Composite Pattern

The core principle of the Composite pattern is to enable treating a collection of objects in the same way as a single instance. Applying operations to a composite collection results in those operations being applied to each of its constituent parts. Such methods can be successfully applied regardless of the number of elements in the composite collection, even if the collection is empty.

Additionally, objects within a composite collection do not necessarily need to offer identical methods. A composite object can expose only the methods common to the objects within its collection, or it can provide an abstract API that gracefully handles method differences among its contained objects.

Let's delve into how jQuery's intuitive API is significantly influenced by the Composite pattern.

How jQuery Utilizes the Composite Pattern

The Composite pattern is integral to jQuery's architecture, applied from the very core of the $() function. Every invocation of the $() function creates and returns an element collection object, commonly referred to as a jQuery object. This demonstrates the Composite pattern's first principle: the $() function returns a set of elements rather than a single one.

The returned jQuery object is an array-like object that acts as a wrapper, holding the retrieved collection of elements. It also exposes several additional properties, such as:

  • The length of the retrieved element collection.
  • The context in which the object was constructed.
  • The CSS selector used in the $() function call.
  • A prevObject property, useful for accessing the previous element collection if needed after chained method calls.

An array-like object is a JavaScript object {} that possesses a numeric length property and a corresponding number of properties with consecutive numeric names. For example, an array-like object with length == 2 is expected to also define properties "0" and "1". Given these characteristics, array-like objects can be iterated using a simple for loop, leveraging JavaScript's bracket property accessor syntax:


for (var i = 0; i < obj.length; i++) {
  console.log(obj[i]);
}

We can easily inspect these properties of a jQuery object returned by the $() function using browser developer tools. For instance, the result of $('#mainHeading') would show the length, context, and selector properties in the console.

jQuery uses array-like objects as wrappers for its returned elements, enabling it to expose additional methods applicable to the entire collection. This is achieved through prototype inheritance from the jQuery.fn object, giving every jQuery object access to all methods provided by jQuery. This fulfills the Composite pattern's goal of offering methods that apply to each member of a collection. Because jQuery uses array-like objects with prototype inheritance, these methods are easily accessible as properties of every jQuery object, as shown in the example: $('#mainHeading').css('font-size', '3.2em');. Furthermore, jQuery adds advantages to its DOM manipulation code, aligning with the goal of smaller, less error-prone code. For instance, when using the jQuery.fn.html() method to change the inner HTML of a DOM node that already contains child elements, jQuery first attempts to remove any data and event handlers associated with the child elements before detaching them from the page and appending the new HTML.

To understand how jQuery implements these collection-aware methods, we can examine its source code (available on GitHub or through tools like the jQuery Source Viewer). One of the simplest examples is jQuery.fn.empty(). A simplified view of its implementation would show:


empty: function() {
  var element, i = 0;

  for ( ; ( element = this[ i ] ) != null; i++ ) {
    if ( element.nodeType === 1 ) {
      // Prevent memory leaks
      jQuery.cleanData( getAll( element, false ) );

      // Remove any remaining nodes
      element.textContent = "";
    }
  }

  return this;
}

The code is not complex. jQuery iterates through all items in the collection object (referred to as this within the method's scope) using a simple for loop. For each item that is an element node, it uses the jQuery.cleanData() helper function to clear any associated data-* attributes, then immediately sets its content to an empty string.

Advantages Compared to Native DOM API

To clearly illustrate the benefits of the Composite pattern, let's rewrite our initial example without jQuery's abstractions, using only plain JavaScript and the native DOM API:


setTimeout(function() {
  var headerElem = document.getElementById('mainHeading');
  if (headerElem) {
    headerElem.style.fontSize = '3.2em';
  }
  var wrapperElem = document.getElementsByClassName('itemWrapper')[0];
  if (wrapperElem) {
    var innerCards = wrapperElem.getElementsByClassName('itemCard');
    for (var i = 0; i < innerCards.length; i++) {
      var cardElem = innerCards[i];
      cardElem.innerHTML += '<br /><br /><i>Additional information can go here.</i>';
      cardElem.parentNode.className += ' columnSizer';
    }
    var clearDiv = document.createElement('div');
    clearDiv.className = 'clearFloat';
    wrapperElem.appendChild(clearDiv);
  }
}, 800);

This code also uses setTimeout with an anonymous function and an 800ms delay. Inside the function, document.getElementById retrieves the element with a unique ID, while document.getElementsByClassName is used for elements sharing a specific class. wrapperElem.getElementsByClassName('itemCard') retrieves descendants of the itemWrapper with the itemCard class.

The most striking observation is that this native implementation requires 18 lines of code to achieve the same result, compared to 9 lines with jQuery – half the amount. Using jQuery's $() function with CSS selectors offers a simpler way to retrieve elements and ensures compatibility with browsers that might not fully support getElementsByClassName(). Beyond line count and readability, there are further advantages. As an implementer of the Composite pattern, the $() function consistently retrieves element collections, making our code more uniform compared to the varied handling required by different getElement* methods. We use $() identically, whether retrieving a single uniquely identified element or multiple elements with a specific class.

As an added benefit of returning array-like objects, jQuery provides more convenient methods for DOM traversal and manipulation, such as .css(), .append(), and .parent() methods, which are accessible as properties of the returned object. Furthermore, jQuery abstracts more complex use cases with methods like .addClass() and .wrap(), which have no direct native DOM API equivalents.

Because jQuery collection objects return an object with the same interface regardless of the encapsulated elements, we can apply any jQuery API method consistently. As seen, these methods apply to each retrieved element, irrespective of the element count. Thus, we avoid separate for loops to iterate and apply operations individually; instead, we apply operations (e.g., .addClass()) directly to the collection object.

To maintain execution safety in the native example, we'd need additional if statements to check for null values. For instance, if headerElem isn't found, an error would occur, halting subsequent code. While these checks (e.g., if (headerElem)) might seem unnecessary in a small example, their absence is a common source of bugs in large applications where elements are dynamically created, inserted, and removed. Developers often implement core logic first and add error checks later, typically after encountering runtime issues.

Following the Composite pattern, even an empty jQuery collection object (containing no retrieved elements) remains a valid object, allowing safe application of any jQuery methods. This eliminates the need for extra if statements to check if a collection contains elements before applying methods like .css(), thereby preventing JavaScript runtime errors.

Overall, jQuery's use of the Composite pattern provides abstractions that lead to fewer lines of code, improved readability, greater uniformity, and reduced error proneness (comparing $('#elementID') to document.getElementById('elementID')).

Developing Applications with the Composite Pattern

Having seen how jQuery integrates the Composite pattern and its benefits, let's create our own example. We'll structure a composite object as an array-like entity, operate on disparate objects, offer a fluid API for chaining, and apply methods to all items in the collection.

A Sample Use Case

Imagine an application that needs to perform operations on numbers, but these numbers come from varied and inconsistent sources. For instence, one source might provide raw numbers, while another supplies objects with a specific property containing the number we're interested in:


var numericArray = [5, 10, 15];

var dataObjects = [
    { quantity: 8 },
    { quantity: 4 },
    { quantity: 12 },
    { quantity: 20 }
];

The objects from the second source could have a more complex structure with additional properties. Such variations won't affect our example, as the goal when developing a composite object is to provide a unified way to process the common parts of target items.

Composite Collection Implementation

Let's define a constructor and prototype to describe our composite collection object:


function ValueCollection() {
    this.size = 0;
}

ValueCollection.prototype.addItem = function(item) {
    if ((typeof item === 'object' && 'quantity' in item) ||
        typeof item === 'number') {
        this[this.size] = item;
        this.size++;
    }
    return this;
};

ValueCollection.prototype.adjustBy = function(amount) {
    for (var i = 0; i < this.size; i++) {
        var currentItem = this[i];
        if (typeof currentItem === 'object' && 'quantity' in currentItem) {
            currentItem.quantity += amount;
        } else if (typeof currentItem === 'number') {
            this[i] += amount;
        }
    }
    return this;
};

ValueCollection.prototype.retrieveValues = function() {
    var extractedValues = [];
    for (var i = 0; i < this.size; i++) {
        var currentItem = this[i];
        if (typeof currentItem === 'object' && 'quantity' in currentItem) {
            extractedValues.push(currentItem.quantity);
        } else if (typeof currentItem === 'number') {
            extractedValues.push(currentItem);
        }
    }
    return extractedValues;
};

Our ValueCollection() constructor is simple. When invoked with new, it returns an empty object with a size of zero, indicating an empty wrapped collection.

We need a method to populate our composite collection. The addItem method checks if the provided argument is a type it can handle (an object with a 'quantity' property or a plain number). If so, it appends the argument to the next available numeric property on the composite object and increments the size property. For instance, the first item added will be accessible via myValueCollection[0].

The adjustBy method serves as a simple example of a method that operates on all collection items. It takes a numeric amount, then appropriately adds it to each item in our collection based on its type. Since our composite is an array-like object, adjustBy uses a for loop to iterate through all items and increment item.quantity (if the item is an object) or the actual numeric value (if the item is a number). We could similarly implement other methods, such as one to multiply collection items by a specific number.

To enable chaining of our composite object's methods, all prototype methods must return a reference to the object instance. We achieve this by adding return this; at the end of all methods that manipulate the collection, such as addItem and adjustBy. Methods like retrieveValues, which do not manipulate the collection but return a result, cannot be chained to subsequent method calls that expect the collection object instance.

Finally, we implement the retrieveValues method as a convenient way to get the actual numeric values of all items in our collection. Similar to adjustBy, retrieveValues abstracts the handling of different item types in our collection. It iterates through the collection items, extracts each numeric value, pushes it into an extractedValues array, and returns it to the caller.

A Sample Execution

Let's see an example using our newly implemented composite object:


var myValueCollection = new ValueCollection();

for (var i = 0; i < numericArray.length; i++) {
    myValueCollection.addItem(numericArray[i]);
}

for (var i = 0; i < dataObjects.length; i++) {
    myValueCollection.addItem(dataObjects[i]);
}

myValueCollection.adjustBy(3)
    .addItem(1)
    .addItem(2)
    .addItem({ quantity: 4 });

console.log(myValueCollection.retrieveValues());

Executing this code in a browser console will yield:


► Array [ 8, 13, 18, 11, 7, 15, 23, 1, 2, 4 ]

We use our data sources, numericArray and dataObjects. The code iterates through them, appending their items to a new composite object instance. Then, we increment the values in our composite collection by 3. Immediately after, we chain three more item insertions using addItem: two numeric values and one object with a 'quantity' property. Finally, we use retrieveValues to get an array containing all numeric values from our collection and log it to the browser console.

Alternative Implementations

It's important to remember that composite objects don't strictly have to be array-like. However, this implementation is often favored in JavaScript due to the ease of creating such structures and the convenience of iterating through collection items with simple for loops.

Alternatively, if an array-like object is not preferred, we could easily use a property on the composite object to hold our collection items. For example, this property could be named _items, and we would use this._items.push(item) to store items and this._items[i] to access them within our methods.

The Iterator Pattern

The central concept of the Iterator pattern is to employ a function responsible for traversing a collection and providing access to its items. This function, the iterator, offers a way to access collection items without exposing the concrete implementation and underlying data structure used by the collection object.

Iterators encapsulate how iteration occurs, decoupling the iteration of collection items from the implementation logic of their consumers. This adheres to the Single Responsibility Principle, where a component should have only one reason to change.

How jQuery Utilizes the Iterator Pattern

As previously discussed, the core jQuery $() function returns an array-like object that wraps a set of page elements and provides an iteration function to traverse it and access each element individually. It also offers a general-purpose helper method, jQuery.each(), capable of iterating over arrays, array-like objects, and object properties.

The jQuery API documentation describes jQuery.each() as: "A generic iterator function, which can be used to seamlessly iterate over both objects and arrays. Arrays and array-like objects (e.g., arguments objects) with a length property are iterated by numeric index, from 0 to length-1. Other objects are iterated via their named properties."

The jQuery.each() helper function is used internally in numerous places within jQuery's source code, including iterating through items in jQuery objects and applying operations to each, as recommended by the Composite pattern. This method's implementation can be traced in the jQuery source.

This helper function is also accessible on any jQuery object through prototype inheritance, similar to how methods like .append() are. You can easily find the code that enables this by searching for jQuery.fn.each() in the jQuery source code. The method version of .each() allows for a more convenient syntax to directly iterate over elements in a jQuery collection object.

The following code demonstrates how to use both forms of .each() in our code:


// using the helper function on an array
$.each([10, 20, 30], function(index){
    console.log(this * 2);
});

// using the method on a jQuery object
$('.itemWrapper .itemCard').each(function(index) {
    console.log('This is item card #' + (index + 1)); // index is zero-based
});

When executed, the preceding code will log the following to the browser console:


20
40
60
This is item card #1
This is item card #2
This is item card #3

Complementary with the Composite Pattern

Given that the Composite pattern encapsulates a collection of items as a single object, and the Iterator pattern is used to traverse abstract data structures, these two patterns are often described as complementary.

Where to Apply the Iterator Pattern

The Iterator pattern can abstract how we access items from data structures within our applications. For example, suppose we need to retrieve all items greater than 10 from the following tree structure:


var hierarchicalData = {
    rootValue: 15,
    leftBranch: {
        branchValue: 7,
        leftLeaf: 4,
        rightLeaf: {
            leafValue: 12,
            subLeft: 11,
            subRight: 18
        }
    },
    rightBranch: {
        branchValue: 16,
        leftLeaf: 9
    }
};

Let's implement an iterator function. Due to the nested nature of tree data structures, we'll use a recursive implementation:


function walkTreeValues(node, processCallback) {
    if (node === null || node === undefined) {
        return;
    }

    if (typeof node === 'object') {
        if ('leftBranch' in node) {
            walkTreeValues(node.leftBranch, processCallback);
        }
        if ('rootValue' in node) {
            processCallback(node.rootValue);
        } else if ('branchValue' in node) {
            processCallback(node.branchValue);
        } else if ('leafValue' in node) {
            processCallback(node.leafValue);
        }
        if ('leftLeaf' in node) {
            walkTreeValues(node.leftLeaf, processCallback);
        }
        if ('subLeft' in node) {
            walkTreeValues(node.subLeft, processCallback);
        }
        if ('rightLeaf' in node) {
            walkTreeValues(node.rightLeaf, processCallback);
        }
        if ('subRight' in node) {
            walkTreeValues(node.subRight, processCallback);
        }
    } else {
        // it's a numeric leaf node
        processCallback(node);
    }
}

Finally, the execution of our iterator would look like this:


var filteredValues = [];
walkTreeValues(hierarchicalData, function(val) {
    if (val > 10) {
        filteredValues.push(val);
    }
});
console.log(filteredValues);

When executed, this code will log the following to the browser console:


► Array [ 11, 12, 18, 15, 16 ]

The iterator clearly simplifies our code. We no longer need to repeatedly deal with the implementation details of the data structure when accessing items that meet specific criteria. Our implementation builds upon the generic API exposed by the iterator, with our specific logic residing within the callback provided to the iterator.

This encapsulation decouples our implementation from the underlying data structure, provided an iterator with a consistent API is supplied. In this example, we could easily change the data structure to a sorted binary tree or a simple array without altering our core logic.

The Observer Pattern

This section explores the Observer pattern and its convenient application within jQuery. Subsequently, it explains event delegation, a variation of the Observer pattern that, when correctly applied to web pages, can simplify code and reduce memory consumption.

We will introduce the Observer pattern, examine its use in jQuery, compare it with traditional event attributes, discuss how to prevent observer-related memory leaks, and finally, introduce and demonstrate the benefits of the delegated event observer pattern.

Introducing the Observer Pattern

The fundamental concept of the Observer pattern involves an object, typically called the observable or subject, whose internal state changes over its lifetime. Several other objects, known as observers, wish to be notified of these state changes in the observable to perform specific actions.

Observers may need to be notified of any state change in the observable or only specific types of changes. In most common implementations, the observable maintains a list of observers and notifies them when appropriate state changes occur. When an observable's state changes, it iterates through the list of observers interested in that specific type of change and executes their defined methods.

In traditional object-oriented programming, observers are often objects that implement a well-known programming interface, specific to each observable they are interested in. When a state change occurs, the observable invokes the defined method on each observer.

Within the web stack, the Observer pattern typically uses anonymous callback functions as observers rather than objects with well-known methods. This achieves equivalent results because callback functions retain references to the variables in their defining environment – a pattern often referred to as a closure. A key advantage of using the Observer pattern over simple callbacks as invocation parameters is that the Observer pattern can support multiple independent handlers on a single target.

A callback is a function passed as an argument to another function or method, or assigned to an object's property, with the expectation that it will be executed at a later point. In this way, the code that receives our callback will invoke it, propagating the result of an operation or event back to the context where the callback was defined.

The pattern of registering functions as observers has proven more flexible and straightforward for programming, extending beyond the web stack to other programming languages. These languages provide equivalent functionality through language features or special objects like subroutines, lambda expressions, blocks, and function pointers. For example, Python, like JavaScript, treats functions as first-class objects, allowing them to be used as callbacks, while C# defines delegates as special object types to achieve the same result.

The Observer pattern is fundamental to developing web interfaces that respond to user actions, and nearly every web developer uses it to some extent, even if unconsciously. This is because creating rich user interfaces often begins with adding event listeners to page elements and defining how the browser should respond to them.

Traditionally, this was done by using the EventTarget.addEventListener() method on the page elements where events (e.g., "click") needed to be monitored, providing a callback function containing the code to execute when the event occurred. For compatibility with older versions of Internet Explorer, checking for EventTarget.attachEvent() and using it as an alternative was necessary.

How jQuery Applies It

The jQuery library extensively employs the Observer pattern in several parts of its implementation, either directly using the native addEventListener method or by creating its own abstractions. Furthermore, jQuery provides a range of abstractions and convenience methods that simplify the use of the Observer pattern on the web, some of which are internally used to implement other methods.

jQuery's .on() Method

The jQuery.fn.on() method is the central jQuery method for attaching event handlers to elements. It offers a simple way to adopt the Observer pattern while maintaining readable and understandable code. It attaches the requested event handler to all elements within the composite jQuery collection object returned by the $() function.

Examining the jQuery source code for jQuery.fn.on reveals that its initial lines primarily manage the various ways the method can be invoked. Towards the end, it utilizes an internal method, jQuery.event.add():


jQuery.fn.extend({
  on: function( types, selector, data, fn ) {
    return on( this, types, selector, data, fn );
  }
});

function on( elem, types, selector, data, fn, one ) {

  /* code handling method overloads */
  return elem.each( function() {
    jQuery.event.add( this, types, fn, data, selector );
  } );
}

And a trimmed-down version of jQuery.event.add() (removing implementation details not directly related to the Observer Pattern for clarity):


add: function( elem, types, handler, data, selector ) {
    /* ... setup ... */
        elemData = dataPriv.get( elem );
    /* ... more setup ... */

    // Ensure the handler has a unique ID for later lookup/removal
    if ( !handler.handlerId ) { // Renamed from guid
        handler.handlerId = jQuery.uniqueId++; // Renamed from jQuery.guid++
    }

    // Initialize the element's event structure if first handler
    if ( !( events = elemData.events ) ) {
        events = elemData.events = {};
    }
    /* ... event type parsing ... */

    // Process multiple events separated by space
    types = ( types || "" ).match( rnotwhite ) || [ "" ];
    t = types.length;
    while ( t-- ) {
        /* ... event specific setup ... */

        // Initialize handler queue for event type if first
        if ( !( handlers = events[ type ] ) ) {
            handlers = events[ type ] = [];
            handlers.delegateCount = 0;

            // Use addEventListener unless special setup returns false
            if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
                if ( elem.addEventListener ) {
                    elem.addEventListener( type, eventHandle );
                }
            }
        }

        /* ... handler object creation ... */

        // Add to the element's handler list, delegates first
        if ( selector ) {
            handlers.splice( handlers.delegateCount++, 0, handleObj );
        } else {
            handlers.push( handleObj );
        }
        /* ... finalization ... */
    }
}

Let's examine how jQuery.event.add() implements the Observer pattern, referencing the highlighted code sections above.

The handler variable in jQuery.event.add() stores the function initially passed to jQuery.fn.on(). This function acts as our observer, executing when the corresponding event is triggered on the attached element. In the first highlighted area, jQuery creates and assigns a handlerId property to this observer function. In JavaScript, functions can have properties because they are first-class objects. The jQuery.uniqueId++ statement (renamed from jQuery.guid++ for this rewrite) increments a global counter used internally by jQuery and its plugins. The handlerId property identifies and locates the observer function within the list of observers jQuery maintains for each element, for example, enabling jQuery.fn.off() to remove it.

jQuery.uniqueId is a page-wide counter used by plugins and jQuery itself to retrieve unique integer IDs. It's typically used to assign unique IDs to elements, objects, and functions, making them easier to locate in collections. Any implementer retrieving and using jQuery.uniqueId is responsible for incrementing its value (by one) after each use. Failure to do so can lead to hard-to-debug failures across the page, as it's a shared counter for identification.

In the second and third highlighted areas, jQuery initializes an array to hold the list of observers for each event that might trigger on the element. Notably, the observer list in the second highlighted area is not a direct property of the DOM element itself. As indicated by the dataPriv.get( elem ) statement near the beginning of jQuery.event.add(), jQuery uses a separate mapping object to maintain the association between DOM elements and their observer lists. This data caching mechanism allows jQuery to avoid adding its internal properties directly to DOM elements.

The next highlighted section shows jQuery checking for the availability of the native EventTarget.addEventListener() method on the element, then using it to add the event listener. In the final highlighted area, jQuery adds the observer function to its internal list, which contains all observers attached to that specific element for the same event type.

jQuery's implementation aligns with the operational model described by the Observer pattern, while also incorporating implementation techniques that make it more effective with the APIs available in web browsers.

The Document Ready Observer

Another convenient and widely used method provided by jQuery is $.fn.ready(). This method accepts a function parameter and executes it only after the page's DOM tree has fully loaded. This is useful if your code isn't the last to load on the page, if you want to avoid blocking initial page rendering, or if it needs to operate on elements defined after its own <script> tag.

Remember that $.fn.ready() behaves slightly differently from the window.onload callback and the page's "load" event, which wait for all page resources (images, iframes, etc.) to load. For more details, consult the jQuery API documentation for .ready().

The following code demonstrates the most common usage of the $.fn.ready() method:


$(document).ready(function() {
    /* this code will execute only after the page has been fully loaded */
});

If we examine the implementation of jQuery.fn.ready, we find that it internally relies on jQuery.ready.promise:


jQuery.fn.ready = function( fn ) {
  // Add the callback
  jQuery.ready.promise().done( fn );

  return this;
};
/* ... extensive code in between ... */
jQuery.ready.promise = function( obj ) {
  if ( !readyList ) {

    readyList = jQuery.Deferred();

    // Handle cases where $(document).ready() is called after the browser event.
    // Supports: IE9-10 only, older IE sometimes signals "interactive" too soon
    if ( document.readyState === "complete" || ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
      // Handle asynchronously to allow ... to delay ready
      window.setTimeout( jQuery.ready );

    } else {
      // Use the convenient event callback
      document.addEventListener( "DOMContentLoaded", completed );

      // Fallback to window.onload, which will always work
      window.addEventListener( "load", completed );
    }
  }
  return readyList.promise( obj );
};

As seen in the highlighted section of the implementation, jQuery uses addEventListener to observe when the DOMContentLoaded event is triggered on the document object. Additionally, to ensure cross-browser compatibility, it also listens for the load event on the window object.

The jQuery library also provides shorter ways to add this functionality. Since the implementation doesn't strictly require a reference to the document, $().ready(function() {/* ... */ }) can be used. There's also an overload of the $() function that achieves the same effect: $(function() {/* ... */ }). These alternative methods are often discouraged due to potential confusion, especially the second, shorter version, which closely resembles an Immediately Invoked Function Expression (IIFE) – a pattern JavaScript developers are accustomed to. It's often advisable to discuss such shorthand with a development team before widespread adoption.

Demonstrating a Sample Use Case

To see the Observer pattern in action, let's create a skeletal implementation of a dashboard. In our example, users can add information cards related to various sample items and categories available in a header bar to their dashboard.

Our example will feature three predefined categories for our items: Products, Sales, and Promotions. Each category will have a list of associated items displayed in an area just below the category selector. Users can change the visible selection of items by choosing the desired category from a dropdown selector.

Initially, our dashboard will contain a hint card about its usage. Whenever a user clicks one of the category items, a new information card will appear in our three-column dashboard layout. Users can also dismiss any of these information cards by clicking the red close button in the top-right corner of each card.

From this description, we can readily identify all the user interactions required for our dashboard. For each of these interactions, we will need to attach observers and write the appropriate DOM manipulation code within their callback functions.

Specifically, our code will need to:

  • Observe changes made to the currently selected category, responding by hiding or showing the appropriate item lists.
  • Observe clicks on each item button and respond by adding a new information card.
  • Observe clicks on the close button of each information card and respond by removing it from the page.

Let's examine the necessary HTML, CSS, and JavaScript for this example. Starting with the HTML, saved as dashboard-app.html:


<html>
  <head>
    <title>Dashboard Application</title>
    <link rel="stylesheet" type="text/css" href="dashboard-styles.css">
  </head>
  <body>
    <h1 id="appHeader">Dashboard Application</h1>

    <div class="appContainer">
      <section class="categoryControls">
        <select id="categoryPicker">
          <option value="0" selected>Products</option>
          <option value="1">Sales</option>
          <option value="2">Promotions</option>
        </select>
        <section class="categoryList">
          <button>Product A</button>
          <button>Product B</button>
          <button>Product C</button>
          <button>Product D</button>
          <button>Product E</button>
        </section>
        <section class="categoryList hidden">
          <button>Week 1 Sales</button>
          <button>Week 2 Sales</button>
          <button>Week 3 Sales</button>
          <button>Week 4 Sales</button>
        </section&gt>
        <section class="categoryList hidden">
          <button>Ad Campaign Alpha</button>
          <button>Ad Campaign Beta</button>
          <button>Ad Campaign Gamma</button>
        </section>
        <div class="clearFloats"></div>
      </section>

      <section class="infoCardDisplay">
        <div class="cardWrapper">
          <article class="infoCard">
            <header class="cardHeader">
              Hint!
              <button class="cardCloseBtn">&#10006;</button>
            </header>
            Click the buttons above to add information cards...
          </article>
        </div>
      </section>
      <div class="clearFloats"></div>
    </div>

    <script type="text/javascript" src="path/to/jquery.js"></script>
    <script type="text/javascript" src="dashboard-script.js"></script>
  </body>
</html>

In the HTML, all dashboard elements are nested within a <div> with the appContainer CSS class. This provides a central starting point for element searches and scopes our CSS. Inside, two <section> elements use HTML5 semantics to logically divide the dashboard.

The first <section>, with class categoryControls, holds the category selector. It contains a <select> element with ID categoryPicker to filter visible category items, and three child <section> elements with class categoryList, wrapping <button> elements that will populate the dashboard with info cards on click. Two of these also have the hidden class, so only the first displays on page load, matching the initially selected <option> in the category picker. A <div> with clearFloats is added at the end of this section to clear button floats.

The second <section>, with class infoCardDisplay, holds the information cards. Initially, it contains only a hint card. We use a <div> with class cardWrapper for sizing and an HTML5 <article> element with class infoCard for styling (border, padding, shadow).

Each information card includes a <header> with class cardHeader and a <button> with class cardCloseBtn, which removes the containing card when clicked. The HTML character code &#10006; is used for an "x" mark, avoiding an image. Finally, another <div> with clearFloats is placed at the end of infoCardDisplay to clear floats from the information cards.

In the HTML <head>, we reference a CSS file named dashboard-styles.css:


.categoryControls {
    margin-bottom: 12px;
}

.categoryControls select,
.categoryControls button {
    display: block;
    width: 210px;
    padding: 6px 4px;
    border: 1px solid #444;
    margin: 4px 6px;
    border-radius: 4px;
    background-color: #F8F8F8;
    text-align: center;
    box-shadow: 0 1px 2px #888;
    cursor: pointer;
}

.categoryControls select:hover,
.categoryControls button:hover {
    background-color: #EEE;
}

.categoryControls button {
    float: left;
}

.infoCard {
    padding: 8px 12px;
    border: solid 1px #444;
    margin: 6px 4px;
    box-shadow: 0 1px 3px #888;
}

.cardWrapper {
    float: left;
    width: 32%;
}

.cardHeader {
    padding: 4px 12px;
    margin: -8px -12px 8px;
    background-color: #BBBBBB;
    box-shadow: 0 1px 2px #AAAAAA;
}

.cardCloseBtn {
    float: right;
    height: 22px;
    width: 22px;
    padding: 0;
    border: 1px solid #111;
    border-radius: 4px;
    background-color: #FF4444;
    font-weight: bold;
    text-align: center;
    color: #FFFFFF;
    cursor: pointer;
}

.clearFloats { clear: both; }
.hidden { display: none; }

The CSS first adds spacing below .categoryControls and styles both the <select> and its buttons. It differentiates from default browser styles with padding, rounded borders, hover effects, and spacing. The <select> displays as a block, while category item buttons float adjacent to each other. .cardWrapper and .infoCard classes are reused to create a three-column layout and style the information cards. .cardHeader styles the <header> elements within cards with padding, a grey background, subtle shadow, and negative margins to align with the card border. .cardCloseBtn floats the close button to the top-right of the card's header, sets its dimensions, overrides default button styles, and adds a black border, rounded corners, and a red background. Finally, .clearFloats prevents elements from being positioned next to preceding floated elements, and .hidden provides a utility for concealing page elements.

Our HTML file references jQuery and a JavaScript file named dashboard-script.js, containing our dashboard implementation. Following best practices for high-performance web pages, these scripts are placed just before the </body> tag to avoid delaying initial page rendering:


$(document).ready(function() {

    $('#categoryPicker').change(function() {
        var $picker = $(this);
        var selectedIdx = +$picker.val();
        var $categoryLists = $('.categoryList');
        var $visibleList = $categoryLists.eq(selectedIdx).show();
        $categoryLists.not($visibleList).hide();
    });

    function initializeCardCloseButton($card) {
        $card.find('.cardCloseBtn').click(function() {
            $(this).closest('.cardWrapper').remove();
        });
    }

    // Initialize the close button for the initial hint card
    initializeCardCloseButton($('.infoCard'));

    $('.categoryList button').on('click', function() {
        var $btn = $(this);
        var cardContentHtml = '<div class="cardWrapper"><article class="infoCard">' +
                '<header class="cardHeader">' +
                    $btn.text() +
                    '<button class="cardCloseBtn">&#10006;' +
                    '</button>' +
                '</header>' +
                'Details about ' + $btn.text() +
            '</article></div>';
        $('.infoCardDisplay').append(cardContentHtml);
        initializeCardCloseButton($('.infoCard:last-child'));
    });

});

All code is within a $(document).ready() call, delaying execution until the DOM is fully loaded. This is crucial if scripts are in the <head> but good practice generally. First, we use $.fn.change() to add an observer for the change event on the categoryPicker element (a shorthand for $.fn.on('change', /* … */)). In jQuery, the this keyword within an observer function refers to the DOM element that triggered the event. This applies to all jQuery methods that register observers, from core .on() to convenience methods like .change() and .click(). So, we create a jQuery object for the this element and store it in $picker. We retrieve the selected <option>'s value using $picker.val() and convert it to a number with the + operator. Next, we select all <section> elements with the categoryList class, caching them in $categoryLists. We then show the category whose position matches selectedIdx, storing its jQuery object in $visibleList. Finally, we use .fn.not() to retrieve and hide all other category elements except the one just made visible.

In the next section, we define initializeCardCloseButton, which sets up the close button functionality. It expects a jQuery object containing card elements, then finds descendants with the cardCloseBtn class. Using $.fn.click() (a convenience for $.fn.on('click', /* fn */)), we register an anonymous function to execute on each click. This function uses $.fn.closest() to find the first ancestor with the cardWrapper class and removes it from the page. We then call this function once for the hint card initially present.

Another point regarding $.fn.closest() is that it tests the given selector against the current element in the jQuery collection before testing its ancestor elements.

In the final code section, we use $.fn.on() to add an observer for click events on each category button. Inside the anonymous observer function, this refers to the clicked <button> DOM element. We create a jQuery object from it and cache the reference in $btn. We then get the button's text content using $.fn.text() and construct the info card's HTML. For the close button, we use the &#10006; HTML character code, which renders as a clear "X". The generated HTML is based on the initial hint card. Finally, we append the HTML to infoCardDisplay, and because we expect the new card to be the last element, we find it with $() and pass it to initializeCardCloseButton.

Comparison with Event Attributes

Before EventTarget.addEventListener() was defined in the DOM Level 2 Events specification, event listeners were registered using either event attributes directly on HTML elements or element event properties on DOM nodes.

Event attributes are a set of properties available on HTML elements that provide a declarative way to define JavaScript code snippets (preferably function calls) to execute when a specific event triggers on that element. Due to their declarative nature and straightforward usage, this is often how new developers first encounter events in web development.

If we used event attributes in our example, the HTML for a card's close button would look like:


<article class="infoCard">
    <header class="cardHeader">
        Hint!
        <button onclick="dismissInfoCard(this);"
                class="cardCloseBtn">&#10006;</button>
    </header>
    Click the buttons above to add information cards...
</article>

Additionally, we would need to define a dismissInfoCard function and expose it on the window object to be accessible from the HTML event attribute:


window.dismissInfoCard = function(buttonElement) {
    $(buttonElement).closest('.cardWrapper').remove();
};

Some drawbacks of using event attributes instead of the Observer pattern include:

  • It makes it harder to define multiple distinct actions that need to execute when an event triggers on an element.
  • It can inflate the page's HTML code, making it less readable.
  • It violates the Separation of Concerns principle by embedding JavaScript code within HTML, potentially making errors harder to trace and fix.
  • Most often, this results in functions called from event attributes being exposed to the global window object, "polluting" the global namespace.

Using element event properties requires no changes to our HTML, with all implementation remaining in our JavaScript file. The changes needed in our initializeCardCloseButton function would make it look like this:


function initializeCardCloseButton($card) {
    var $closeButtons = $card.find('.cardCloseBtn');
    for (var i = 0; i < $closeButtons.length; i++) {
        $closeButtons[i].onclick = function() {
            this.onclick = null; // Clear handler to avoid memory leaks
            $(this).closest('.cardWrapper').remove();
        };
    }
}

Even with jQuery for DOM manipulation, the resulting code still suffers from some of the aforementioned drawbacks. Crucially, to prevent memory leaks, we'd also need to nullify the function assigned to the onclick property before removing the element from the page, especially if it contains references to the DOM element it's applied to.

Modern browser developer tools provide features that match the convenience offered by declarative event attributes. For instance, in Firefox Developer Tools, elements with attached event listeners display an "ev" flag. Clicking this flag reveals a dialog showing all currently attached event listeners, including their file and line numbers, and allows navigation to the source code for breakpoint setting.

One of the greatest benefits of using the Observer pattern over event attributes is evident when multiple actions need to occur upon an event. Suppose we wanted to add a new dashboard feature that prevents accidental double-clicking on category item buttons, which would add the same info card twice. This new functionality should ideally be entirely independent of existing implementations. With the Observer pattern, we simply add the following code to observe button clicks and disable the button for 700 milliseconds:


$(document).ready(function() {
  $('.categoryList button').on('click', function() {
    var $btn = $(this);
    $btn.prop('disabled', true);

    setTimeout(function() {
      $btn.prop('disabled', false);
    }, 700);
  });
});

This code is entirely independent of the base implementation and can reside in the same or a different JavaScript file. This would be much harder with event attributes, requiring both actions to be defined within the same event handler function, leading to tight coupling of two distinct behaviors.

Avoiding Memory Leaks

As we've seen, using the Observer pattern for web page events offers powerful advantages. When adding observers to elements using the EventTarget.addEventListener() method, it's crucial to remember that to prevent memory leaks, we must also call EventTarget.removeEventListener() before removing those elements from the page. This ensures the observers are properly detached.

The developers of the jQuery library recognized that this implementation concern could be easily overlooked or mishandled, making the Observer pattern seem more complex. Therefore, they encapsulated the proper handling within jQuery's jQuery.event implementation. As a result, when using any of jQuery's event handling methods (e.g., core $.fn.on() or convenience methods like $.fn.click() or $.fn.change()), the observer functions are tracked by jQuery itself. If we later decide to remove the elements from the page, these observers will be correctly de-registered. As we saw in the jQuery.event implementation, jQuery stores each element's observers in a separate mapping object. Each time a jQuery method removes DOM elements from the page, it first inspects this mapping object to ensure any attached observers on those elements or their descendants are also removed. Thus, even if we don't explicitly use methods to remove observers from created elements, our previous example code does not cause memory leaks.

Even though jQuery methods protect you from memory leaks caused by unregistered observers, be cautious when mixing jQuery and native DOM operations. If you remove elements from the document using native DOM API methods like Element.remove() and Element.removeChild(), any attached observers on those elements or their descendants will not be automatically de-registered. The same applies when assigning to the Element.innerHTML property.

Introducing the Delegated Event Observer Pattern

Having explored advanced aspects of the Observer pattern with jQuery, we now introduce a specialized variation perfectly suited for the web platform, offering additional benefits. The Delegated Event Observer pattern (or simply, delegated observer pattern) is commonly used in web development and leverages the event bubbling characteristic common to most events triggered on DOM elements. For example, when an element is clicked, the click event immediately triggers on it, then bubbles up through all its parent elements until it reaches the root of the HTML document. By using a slightly different overload of jQuery's $.fn.on method, we can easily create and attach observers for delegated events triggered on specific child elements.

The term "event delegation" describes a programming pattern where event handlers are not directly attached to the elements of interest but rather to one of their ancestor elements.

Simplifying Our Code

Re-implementing our dashboard example using the delegated event observer pattern only requires changing the JavaScript file as follows:


$(document).ready(function() {

    $('#categoryPicker').change(function() {
        var $picker = $(this);
        var selectedIdx = +$picker.val();
        var $categoryLists = $('.categoryList');
        var $visibleList = $categoryLists.eq(selectedIdx).show();
        $categoryLists.not($visibleList).hide();
    });

    $('.categoryControls').on('click', 'button', function() {
        var $btn = $(this);
        var cardContentHtml = '<div class="cardWrapper"><article class="infoCard">' +
                '<header class="cardHeader">' +
                    $btn.text() +
                    '<button class="cardCloseBtn">&#10006;' +
                    '</button>' +
                '</header>' +
                'Details about ' + $btn.text() +
            '</article></div>';
        $('.infoCardDisplay').append(cardContentHtml);
    });

    $('.infoCardDisplay').on('click', '.cardCloseBtn', function() {
        $(this).closest('.cardWrapper').remove();
    });

});

The most evident difference is that the new implementation is shorter. This benefit comes from defining a single observer for each action that applies to multiple page elements. Therefore, we use the overloaded variant of the $.fn.on(events, selector, handler) method.

Specificallly, we add an observer to the page element with the categoryControls CSS class and listen for click events triggered on any of its descendant <button> elements. Similarly, we add an observer to the infoCardDisplay element, which will execute when a click event triggers on any of its descendants matching the .cardCloseBtn CSS selector.

Because these observers apply not only to elements present on the page at registration but also to any matching elements added later, we can decouple the code handling close button clicks. Instead of registering a new observer each time an info card is added, this logic now resides in a single, separate observer. Consequently, the observer responsible for adding new info cards to the dashboard becomes simpler, only handling the HTML creation and insertion, thereby achieving greater separation of concerns. Furthermore, we no longer need to manage the observer registration for the hint card's close button in a separate code snippet.

Comparing Memory Usage Benefits

Let's compare the memory usage between jQuery's simple and delegated event observer patterns. To do this, we'll open both implementations of our dashboard example and compare their memory footprints using Chrome's Developer Tools (Timeline tab). After starting a recording, we'll click each category item button 10 times, adding 120 info cards to our dashboard. Once all cards are added, totaling 121 (including the initial hint card), we'll stop the timeline recording.

The initial observer pattern implementation's timeline shows a significant number of event listeners. Repeating the same process for the delegated event observer pattern yields a smoother timeline, indicating fewer object allocations and garbage collections. In the first implementation, we end up with 134 event listeners, whereas the delegated version starts with three event listeners and doesn't add more. The delegated version's memory consumption remains relatively stable, increasing by approximately 200 KB. Conversely, the original implementation's heap size increases more than fivefold, by over 1 MB.

While adding so many elements might not be a typical use case, a dashboard may not be the only dynamic part of your page. Therefore, in a relatively complex web page, if we re-implemented every applicable section using the delegated event observer pattern, we might achieve similar improvements.

The Publish/Subscribe Pattern

This section introduces the Publish/Subscribe pattern, a design pattern similar to the Observer pattern but with more distinct roles, making it suitable for complex use cases. We will differentiate it from the Observer pattern, explore its advantages, and see how jQuery incorporates some of its concepts into its Observer pattern implementation.

Later, we will rewrite the dashboard example from the previous section using this pattern. We'll leverage its strengths to add extra functionality and reduce coupling between our code and web page elements.

We will introduce the Publish/Subscribe pattern, understand its distinctions and advantages over the Observer pattern, examine how jQuery integrates some of its features, learn to emit custom events with jQuery, and rewrite and extend the dashboard example from the previous section using this pattern.

Introducing the Publish/Subscribe Pattern

The Publish/Subscribe pattern is a messaging pattern where message emitters, called publishers, broadcast messages to multiple recipients, known as subscribers, who have expressed interest in receiving such messages. A core concept of this pattern, often abbreviated as Pub/Sub, is to provide a mechanism that avoids direct dependencies between publishers and subscribers.

An additional concept in this pattern is the use of topics, which subscribers use to indicate interest in specific types of messages. This allows publishers to filter subscribers before sending messages, distributing a message only to relevant subscribers, thereby reducing traffic and workload for both parties.

Another common variant involves a central, application-wide object called a broker that relays messages from publishers to relevant subscribers. In this scenario, the broker acts as a well-known message handler for sending and subscribing to message topics. This prevents coupling different application parts directly, instead relying only on the broker and the topics of interest. While topics might not be an absolute requirement in the first variant of this pattern, they are crucial for scalability in this variant, as there are typically far fewer brokers (often just one) than publishers and subscribers.

By following a subscription scheme, a publisher's code is completely decoupled from its subscribers, meaning the publisher doesn't need to know which objects depend on it. Consequently, we avoid hardcoding individual actions that should execute in different parts of the application within the publisher. Instead, application components and potential third-party extensions simply subscribe to the topics/events they need to be aware of. In such a distributed architecture, adding new functionality to an existing application requires minimal to no changes to its dependent components.

Distinctions from the Observer Pattern

The most fundamental difference is that, by definition, the Publish/Subscribe pattern is a one-way messaging pattern for passing messages, whereas the Observer pattern merely describes how observers are notified about specific state changes in a subject.

Furthermore, unlike the Observer pattern, the Publish/Subscribe pattern with a broker leads to looser coupling between different parts of an implementation. This is because observers in the Observer pattern need to be aware of the subject emitting the event; however, in Publish/Subscribe, both publishers and subscribers only need to be aware of the broker being used.

How jQuery Applies It

Again, the jQuery library offers a convenient way to leverage the Publish/Subscribe pattern in our code. Instead of adding new methods named "publish" and "subscribe" and introducing new concepts, the developers extended the capabilities of jQuery.fn.on() and jQuery.fn.trigger() to handle and emit custom events. This allows jQuery to implement the publisher/subscriber communication scheme using its existing, convenient methods.

Custom Events in jQuery

Custom events enable us to use virtually any user-defined string as a generic event, which we can then listen for and manually trigger on page elements. As an additional, valuable feature, custom events can carry extra data to pass to their listeners.

The jQuery library implemented custom events before they were formally added to any web specification, demonstrating their utility in web development. As seen in the previous section, jQuery has a dedicated part that handles both generic element events and custom events. The jQuery.event object encapsulates all internal implementation related to triggering and listening for events. Additionally, the jQuery.Event class is a wrapper specifically used by jQuery to satisfy the needs of both generic element events and its custom event implementation.

Implementing Publish/Subscribe with Custom Events

In the previous section, we observed that the jQuery.fn.on() method can add event listeners to elements. We also saw how its implementation manages a list of added handlers and notifies them when needed. Moreover, event names serve a coordinating purpose, much like topics. This implementation semantic appears to align perfectly with the Pub/Sub pattern.

The jQuery.fn.trigger() method internally uses jQuery.event.trigger(), jQuery's function for triggering events. It iterates over the internal handler list, executing them with the requested event and any additional parameters defined for custom events. This, too, matches the operational requirements of the Pub/Sub pattern.

Thus, jQuery.fn.trigger() and jQuery.fn.on() appear to fulfill the requirements of the Pub/Sub pattern, serving as "publish" and "subscribe" methods, respectively. Since both are available on the jQuery.fn object, we can use these methods on any jQuery object. This jQuery object will then act as an intermediary entity between publishers and subscribers, perfectly fitting the definition of a broker.

A common practice, adopted by many jQuery plugins, is to use the outermost page element containing the application or plugin's implementation as the broker. Alternatively, jQuery allows any object to serve as a broker, as it simply needs a target to emit notifications that observers of our custom events can listen to. Therefore, an empty jQuery object, like $({}), can also be used as a broker if a page element feels too restrictive or not semantically clear enough for the Pub/Sub pattern.

Demonstrating a Sample Use Case

To understand the use of the Pub/Sub pattern and conveniently compare it to the Observer pattern, we will rewrite the dashboard example from the previous section using this pattern. This will clearly demonstrate how this pattern helps us decouple different parts of our implementation, making it more extensible and scalable.

Adapting the Dashboard Example to the Publisher/Subscriber Pattern


$(document).ready(function() {
    window.messageBroker = $('.appContainer'); // Our central broker

    $('#categoryPicker').change(function() {
        var $picker = $(this);
        var messagePayload = { selectedCategoryID: $picker.val() };
        window.messageBroker.trigger('dashboard:categoryChanged', [messagePayload]);
    });

    window.messageBroker.on('dashboard:categoryChanged', function(event, payload) {
        var $categoryLists = $('.categoryList');
        var selectedIdx = +payload.selectedCategoryID;
        var $visibleList = $categoryLists.eq(selectedIdx).show();
        $categoryLists.not($visibleList).hide();
    });

    $('.categoryList').on('click', 'button', function() {
        var $btn = $(this);
        var messagePayload = { itemName: $btn.text() };
        window.messageBroker.trigger('dashboard:itemOpened', [messagePayload]);
    });

    window.messageBroker.on('dashboard:itemOpened', function(event, payload) {
        var cardHtml = '<div class="cardWrapper"><article class="infoCard">' +
                '<header class="cardHeader">' +
                    payload.itemName +
                    '<button class="cardCloseBtn">&#10006;' +
                    '</button>' +
                '</header>' +
                'Details about ' + payload.itemName +
            '</article></div>';
        $('.infoCardDisplay').append(cardHtml);
    });

    $('.infoCardDisplay').on('click', '.cardCloseBtn', function() {
        var cardIdx = $(this).closest('.cardWrapper').index();
        var messagePayload = { closedCardIndex: cardIdx };
        window.messageBroker.trigger('dashboard:itemClosed', [messagePayload]);
    });

    window.messageBroker.on('dashboard:itemClosed', function(event, payload) {
        $('.infoCardDisplay .cardWrapper').eq(payload.closedCardIndex).remove();
    });
});

Similar to our previous implementation, we use $(document).ready() to delay code execution until the page is fully loaded. First, we declare our broker and assign it to a new global variable on the window object for application-wide availability. For our application's broker, we use a jQuery object wrapping the outermost container of our implementation, which is the <div> element with the appContainer class.

While using global variables is often an anti-pattern, storing the broker globally is justified here because it serves as a crucial synchronization point for the entire application, needing to be accessible by all parts of the implementation, even those in separate .js files. As discussed in the next section on the Module pattern, this approach can be improved by storing the broker as a property of an application's namespace.

To implement the category selector, we first observe the change event of the <select> element. When the selected category changes, we create our message using a simple JavaScript object, storing the selected <option>'s value in a selectedCategoryID property. We then use jQuery's jQuery.fn.trigger() method on our broker to publish it to the dashboard:categoryChanged topic. This shifts from a UI element event to an application-semantic message containing all necessary information. In our subscriber's code, we use jQuery.fn.on() on our broker with the dashboard:categoryChanged topic (our custom event) as an argument, just as we would listen for simple DOM events. The subscriber then uses the selectedCategoryID from the received message, as in the previous section, to display the appropriate category items.

Following the same methodology, we split the code for adding and closing info cards in the dashboard into publishers and subscribers. For this demonstration, the message for the dashboard:itemOpened topic simply contains the name of the category item we want to open. In an application retrieving card content from a server, this might be a category item ID. The subscriber then uses the item name from the message to create and insert the requested info card.

Similarly, the message for the dashboard:itemClosed topic contains the index of the card to remove. Our publisher uses jQuery.fn.closest() to traverse the DOM to the child elements of our infoCardDisplay, then jQuery.fn.index() to find its position among siblings. The subscriber then uses the closedCardIndex property from the received message and jQuery.fn.eq() to filter and remove only the requested info card from the dashboard.

In more complex applications, each info card element could be associated with a newly retrieved jQuery.uniqueId (or custom ID) using a mapping object. The publisher could then use that ID in the message instead of a (DOM-dependent) element index. The subscriber would search the mapping object for the ID to locate and remove the corresponding card. This flexibility is left as an exercise for the reader.

In summary, we used dashboard:categoryChanged, dashboard:itemOpened, and dashboard:itemClosed as our application-level events to decouple the handling of user actions from their UI origins. This results in dedicated, reusable code snippets for manipulating our dashboard's content, effectively abstracting them into separate functions. This enables programmatic publishing of a series of messages, allowing us to, for instance, clear all existing info cards and add all category items for the currently selected category. Or, even better, have the dashboard display all items for each category for 10 seconds before switching to the next.

Extending the Implementation

To demonstrate the extensibility offered by the Pub/Sub pattern, we'll extend our current example by adding a counter to display the number of currently open cards in the dashboard.

For the counter implementation, we need to add some extra HTML to our page and create a new JavaScript file to house the counter's logic:


      ...
      </section>
 <div style="margin-left: 6px;">
 Open cards:
 <output id="activeCardCounter">1</output>
 </div>
      <section class="infoCardDisplay">
      ...

In the HTML page, we add an extra <div> element to house our counter and some descriptive text. For our counter, we use an <output> element, a semantic HTML5 element for rendering the result of a user action. Browsers will treat it like a regular <span> element, so it will appear next to its description. Since our dashboard initially has one hint card open, we use 1 as its initial content.


$(document).ready(function() {
 window.messageBroker.on('dashboard:itemOpened dashboard:itemClosed', function (event, payload) {
        var $counterDisplay = $('#activeCardCounter');
        var currentCount = parseInt($counterDisplay.text());

        if (event.type === 'dashboard:itemOpened') {
            $counterDisplay.text(currentCount + 1);
        } else if (event.type === 'dashboard:itemClosed' && currentCount > 0) {
            $counterDisplay.text(currentCount - 1);
        }
    });
});

For the counter implementation, we simply add an extra subscriber to the dashboard's broker, which is globally available. We subscribe to both topics simultaneously by passing them space-separated to the jQuery.fn.on() method. After this, we locate the counter <output> element with ID activeCardCounter and parse its text content into a number. To differentiate our actions based on the received message's topic, we use the event object (the first argument jQuery passes to our anonymous function, the subscriber). Specifically, we use the event object's type property, which holds the name of the received message's topic, and change the counter's content based on its value.

Similarly, we could rewrite the code that prevents accidental double-clicking of category item buttons. All that's required is to add an additional subscriber for the dashboard:itemOpened topic and use the message's itemName property to locate the pressed button.

Using Any Object as a Broker

In our example, we used the dashboard's outermost container element as our broker, but it's also common to use the $(document) object as a broker. Using the application's container element is considered good semantic practice and also scopes the emitted events.

As described earlier, jQuery allows any object to serve as a broker, even an empty one. Therefore, we could use something like window.messageBroker = $({}); as our broker if we prefer it over using a page element.

By using a newly constructed empty object, we can also easily create several brokers if a specific implementation prefers such a scenario. Moreover, if a centralized broker is not favored, we can simply have each publisher act as its own broker, resulting in an implementation closer to the basic variant of the Publish/Subscribe pattern.

Since, in most cases, a declared variable is used to access the application's broker within the page, there is little practical difference between the methods above. Simply choose the approach that best suits your team's preference; if you change your mind later, you only need to assign a different value to the messageBroker variable.

Custom Event Namespacing

To conclude this section, we'll briefly introduce jQuery's custom event namespacing mechanism. The primary benefit of event namespacing is that it allows us to use more specific event names to better describe their purpose, while also helping avoid conflicts between different implementation parts and plugins. It also provides a convenient way to unbind all events of a given namespace from any target (element or broker).

A simple example implementation is:


var eventHub = $({});
eventHub.on('message.log', function (event, message){
    console.log(event.type, event.namespace);
});
eventHub.trigger('message.log', ['sampleMessage']);
eventHub.off('.log'); // Removes all event handlers of the "log" namespace

The Module Pattern: Divide and Conquer

This section introduces the concepts of modules and namespaces, examining how they contribute to more robust implementations. It demonstrates how these design principles can be applied in JavaScript applications by showcasing some of the most widely used development patterns for creating modules.

We will review modules and namespaces, introduce the object literal pattern, explore the module pattern and its variants, discuss the revealing module pattern and its variations, briefly touch upon ES5 strict mode and ES6 modules, and explain how modules benefit jQuery applications.

Modules and Namespaces

The two primary practices in this section are modules and namespaces, used in conjunction to structure and organize code. We'll first analyze the core concept of modules—code encapsulation—then move on to namespaces, which logically organize an implementation.

Encapsulating Implementation Internals

When developing large-scale and complex web applications, the need for a well-defined, structured architecture becomes clear from the outset. To prevent chaotic implementations where different parts of our code interact haphazardly, we must divide the application into small, independent sections.

These independent code segments can be defined as modules. To formalize this architectural principle, computer science has defined concepts such as separation of concerns, where each module's role, operations, and public API should be strictly defined and focused on providing a generic solution for a specific problem.

Avoiding Global Variables and Namespaces

In JavaScript, the window object is known as the global namespace, where every declared variable and function identifier is attached by default. A namespace can be defined as a naming context where each identifier must be unique. The primary concept of a namespace is to provide a way to logically group all related parts of a distinct and independent section of an application. In other words, it suggests creating groups of related functions and variables and making them accessible under the same identifier. This helps prevent naming collisions between different application parts and other JavaScript libraries used, as we only need to keep all identifiers unique within each distinct namespace.

A good example of namespaces is JavaScript's mathematical functions and constants, which are grouped under the built-in Math object. With over 40 short-named mathematical identifiers like E, PI, and floor(), they were designed to be accessible as properties of the Math object, which acts as a namespace for this built-in library, to avoid naming conflicts and group them together.

Without proper namespacing, every function and variable would need a unique name across the entire application, leading to potential conflicts between identifiers from different application parts or even with third-party libraries. Ultimately, while modules offer a way to isolate each independent part of an application, namespaces provide a way to structure different modules into an application's architecture.

Benefits of These Patterns

Designing an application's architecture based on modules and namespaces fosters better code organization and clear separation of concerns. In such an architecture, modules combine related implementation parts, while namespaces connect them to create the application structure.

This architecture helps coordinate large development teams, allowing independent parts to be implemented in parallel. It can also shorten the development time required to add new features to an existing implementation, as existing parts are easily located, and the likelihood of new implementations conflicting with existing code is reduced.

The resulting code structure is not only cleanly separated but also highly reusable in other similar applications, as each module is designed for a single purpose. As an added benefit, tracing the origin of errors in a large codebase becomes easier due to the strictly defined role of each module.

Widespread Acceptance

The community and enterprises recognized that to write maintainable, large-scale frontend applications in JavaScript, they needed to establish a set of best practices and incorporate them into every part of their implementations.

The adoption of modules and namespaces in JavaScript implementations is clearly reflected in best practices and code style guides published by both the community and corporations.

For instance, Google's JavaScript Style Guide recommends adopting namespaces in implementations:

Always prefix global scope identifiers with a unique pseudo-namespace related to the project or library.

Similarly, the jQuery JavaScript Style Guide advises on global variables:

Each project should expose at most one global variable.

Another example of community acceptance comes from the Mozilla Developer Network. Its Object-Oriented JavaScript guide also recommends using namespaces to encapsulate an application's implementation under a single exposed variable, using a simple idiom:


// global namespace
var MYAPP = MYAPP || {};

The Object Literal Pattern

The object literal pattern is perhaps the simplest way to encapsulate all related parts of an implementation under an umbrella object that acts as a module. The pattern's name accurately describes its usage: developers declare a variable and assign all relevant parts to an object literal that needs to be encapsulated into that module.

Let's examine how to create a module that provides unique sequential integers for the page, similar to jQuery.uniqueId:


var idGenUtility = {
  currentId: 1,
  reset: function() {
    this.currentId = 1;
  },
  advance: function() {
    this.currentId++;
    // or idGenUtility.currentId++;
  },
  retrieveNext: function() {
    var nextValue = this.currentId;
    this.advance();
    return nextValue;
  }
};

A simple rule to follow is to define all variables and functions required for each implementation as properties of the object. Our code is reusable and doesn't pollute the global namespace, except for the single variable name defined for our module, in this case, idGenUtility.

Module properties can be accessed internally using the this keyword (e.g., this.currentId) or by using the module's full name (e.g., idGenUtility.currentId). To use this module in our code, we simply access its properties by name. For example, calling the idGenUtility.retrieveNext() method returns the next sequential numeric ID to our code and alters the module's state by incrementing its internal counter.

One drawback of this pattern is its lack of privacy for the module's internal parts. All internal components of the module can be accessed and overwritten by external code, even if we ideally only want to expose idGenUtility.reset() and idGenUtility.retrieveNext(). While naming conventions exist (e.g., adding an underscore to properties intended for internal use), they don't technically enforce privacy.

Another disadvantage is that writing a large module using object literals can be tedious. JavaScript developers are accustomed to semicolons (;) after variable and function definitions, and trying to use commas (,) after each property in a large module can easily lead to syntax errors.

Although this pattern makes it easy to declare nested namespaces for modules, it can result in a bulky and less readable code structure when multiple levels of nesting are required. For instance, consider the framework for a Todo application:


var taskApp = {
  tasks: [],
  addTask: function(item) { this.tasks.push(item); },
  getTasks: function() { return this.tasks; },
  updateTask: function(item) { /*...*/ },
  importers: {
    fromCloudDrive: function() { /*...*/ },
    fromURL: function() { /*...*/ },
    fromRawText: function() { /*...*/ }
  },
  exporters: {
    cloudAPIKey: '#someKey123',
    toCloudDrive: function() { /*...*/ },
    toLocalFile: function() { /*...*/ },
  },
  sharing: {
    toSocialMedia: function(item) { /*...*/ }
  }
};

Fortunately, this can be addressed by splitting the object literal into multiple assignments for each submodule, ideally across different files, as shown:


var taskApp = {
  tasks: [],
  addTask: function(item) { this.tasks.push(item); },
  getTasks: function() { return this.tasks; },
  updateTask: function(item) { /*...*/ },
};
/* ... in a separate file ... */
taskApp.exporters = {
  cloudAPIKey: '#someKey123',
  toCloudDrive: function() { /*...*/ },
  toLocalFile: function() { /*...*/ },
};
/* ... and so on ... */

The Module Pattern

The core concept of the basic Module pattern is to provide a simple function, class, or object for the rest of the application to use via a well-known variable name. It allows us to give a module a minimal API by hiding implementation details that do not need to be exposed. This also prevents variables and utility functions used for the module's internal purposes from polluting the global namespace.

IIFE as a Building Block

We'll briefly cover the IIFE (Immediately Invoked Function Expression) design pattern, as it's a critical component of all module pattern variants. The IIFE is a widely used pattern among JavaScript developers for its clear isolation of code blocks. In module patterns, IIFEs wrap the entire implementation to prevent global namespace pollution and provide declared privacy to the module itself.

Each IIFE creates a closure around its declared variables and functions. This closure allows the IIFE's exposed functions to retain references to the remaining declarations in their environment when executed elsewhere, accessing them normally. Consequently, the IIFE's non-exposed declarations do not leak externally but remain private, accessible only by functions within the created closure.

The most common usage of an IIFE is:


(function() {
  var value = 5;
  console.log(value); // prints 5
})();

Since this code structure might appear unusual at first glance, let's break down its components. An IIFE is nearly equivalent to declaring an anonymous function, assigning it to a variable, and then executing it, as shown:


var tempFunc = function() {
  var value = 5;
  console.log(value);
};

tempFunc(); // or (tempFunc)();

In the above code, we define a function expression and execute it using tempFunc(). In JavaScript, parentheses around an identifier don't change its meaning, so (tempFunc)(); also works. The final step to transform this into an IIFE is to replace the tempFunc variable with the actual anonymous function declaration itself.

As we've seen, the only difference is that with an IIFE, we don't need to declare a variable to hold the function. We simply create an anonymous function and call it immediately after definition.

While IIFEs can be created in several ways, the JavaScript developer community has standardized on the aforementioned code structure as the canonical form. This method of creating IIFEs is considered more readable and is used by large libraries, making it easily recognizable in large JavaScript implementations.

An example of a less common IIFE creation method is:


(function() {
  // code
}());

Simple IIFE Module Pattern

This pattern is characterized by the module returning a single entity. To illustrate how to create a reusable library using this pattern, we'll rewrite the idGenUtility module previously seen. The resulting implementation will be:


var idGenUtility = (function() {
  var module = {}; // Renamed from simpleguid
  var currentId; // Renamed from guid

  module.reset = function() {
    currentId = 1;
  };

  module.advance = function() {
    currentId++;
  };

  module.retrieveNext = function() {
    var nextValue = currentId;
    this.advance();
    return nextValue;
  };

  module.reset(); // Initialize counter

  return module;
})();

This pattern uses an IIFE to define an object that acts as a module container, attaching properties to it, and then returning it. The idGenUtility variable in the first line serves as the module's namespace and is assigned the value returned by the IIFE. The methods and properties defined on the returned object are the module's only exposed parts, forming its public API.

Again, this pattern allows us to use the this keyword to access our module's public methods and properties. It also provides the flexibility to execute any necessary initialization code before the module's definition is complete.

Unlike the object literal pattern, the Module pattern enables us to create truly private members within a module. Variables declared within the IIFE that are not attached to the returned value, such as the currentId variable, act as private members, accessible only internally by other members of the created closure.

Finally, to define nested namespaces, we simply change the assignment of the value returned by the IIFE. As an example of structuring an application with sub-modules, let's see how the exports sub-module for our Todo application skeleton could be defined:


var taskApp = (function() {
  var app = {}; // Renamed from myTodoApp

  var tasks = [];

  app.addTask = function(item) {
    tasks.push(item);
  };

  app.getTasks = function() {
    return tasks;
  };

  return app;
})();

taskApp.exporters = (function() {
  var exporter = {}; // Renamed from exports

  var cloudAPIKey = '#someKey123';

  exporter.toCloudDrive = function() { /*...*/ };

  exporter.toLocalFile = function() { /*...*/ };

  return exporter;
})();

Given that our application namespace taskApp was previously defined, the exporters sub-module can be defined as a simple property on it. A good practice, also recommended by Google's JavaScript Style Guide, is to use lowercase filenames with hyphens to separate sub-modules. For example, the preceding code should be defined in two files named taskapp.js and taskapp-exporters.js, respectively.

How jQuery Uses It

The Module pattern is used by jQuery itself to isolate the source code of its CSS selector engine (Sizzle), which powers the $() function, from the rest of the jQuery source code. From its inception, Sizzle has been a significant part of jQuery's source, now comprising approximately 2135 lines of code. Since 2009, it has been separated into an independent project named Sizzle, making it easier to maintain, develop independently, and reuse by other libraries:


var Sizzle = (function(window) {

  /* ... Sizzle internal code ... */

  function Sizzle(selector, context, results, seed) {
    /* ... Sizzle selector logic ... */
  }

  /*
    ... Sizzle methods like:
    Sizzle.attr
    Sizzle.compile
    Sizzle.contains
    Sizzle.getText
    Sizzle.matches
    Sizzle.matchesSelector
    Sizzle.select
  */

  return Sizzle;

})(window);

jQuery.find = Sizzle;

Sizzle is included within an IIFE in jQuery's source code, and its main function is returned and assigned to jQuery.find for use.

Namespace Parameter Module Variant

In this variant, instead of returning an object from the IIFE and then assigning it to a variable that acts as the module's namespace, we create the namespace and pass it as an argument to the IIFE itself:


(function(moduleNamespace) { // Renamed from simpleguid
  var currentId;

  moduleNamespace.reset = function() {
    currentId = 1;
  };

  moduleNamespace.advance = function() {
    currentId++;
  };

  moduleNamespace.retrieveNext = function() {
    var nextValue = currentId;
    this.advance();
    return nextValue;
  };

  moduleNamespace.reset();
})(window.idGenUtility = window.idGenUtility || {}); // Renamed from simpleguid

The last line of the module definition checks if the module has already been defined; if not, it initializes it as an empty object literal and assigns it to the global window object. In either case, the moduleNamespace argument in the first line of the IIFE will hold the module's namespace.

The expression window.idGenUtility = window.idGenUtility || {}; is nearly equivalent to writing:


window.idGenUtility = window.idGenUtility !== undefined ? window.idGenUtility : {};

Using the logical OR operator (||) makes the expression shorter and more readable. Furthermore, this is a pattern many web developers have learned to easily recognize, appearing in numerous development patterns and best practices.

Again, this pattern allows the use of the this keyword to access public members from the module's exported methods. Concurrently, it enables keeping some functions and variables private, accessible only by other functions within the module.

Even though defining each module as its own JavaScript file is considered good practice, this variant also allows splitting a large module's implementation across multiple files. This benefit stems from checking if the module has already been defined before initializing it as an empty object. This can be useful in certain scenarios, with the only limitation being that each partial file of the module can access private members defined within its own IIFE.

Moreover, to avoid repetition, we can use a simpler identifier for the IIFE's parameter and write our module as:


(function(mod) { // Renamed from namespace
  /* ... */

  mod.retrieveNext = function() {
    var nextValue = currentId;
    this.advance();
    return nextValue;
  };

  mod.reset();
})(window.idGenUtility = window.idGenUtility || {});

When dealing with applications that have nested namespaces, this pattern can become somewhat cumbersome to read. The last line of the module definition will grow longer with each additional level of nested namespaces. For example, let's see how our Todo application's exporters sub-module would look:


(function(exporterMod) { // Renamed from exports
  var cloudAPIKey = '#someKey123';

  exporterMod.toCloudDrive = function() { /*...*/ };

  exporterMod.toLocalFile = function() { /*...*/ };

})(taskApp.exporters = taskApp.exporters || {}); // Renamed from myTodoApp.exports

As seen, each additional level of nested namespaces requires adding assignments on both sides of the expression passed as an IIFE argument. For applications with complex functionality leading to multiple levels of nested namespaces, this can make module definitions look like:


(function(smallComponent) {

  smallComponent.method = function() { /*...*/ };

  return smallComponent;
})(myApplication.mainFeature.subFeature.smallComponent = myApplication.mainFeature.subFeature.smallComponent || {});

Additionally, if we want to provide the same safety guarantees as in the original code example, we need to add similar safety checks for each namespace level. With this in mind, our Todo application's exporters module would need to take the following form:


(function(exporterMod) {
  var cloudAPIKey = '#someKey123';

  exporterMod.toCloudDrive = function() { /*...*/ };

  exporterMod.toLocalFile = function() { /*...*/ };

})((window.taskApp = window.taskApp || {}, taskApp.exporters = taskApp.exporters || {}));

As shown in the preceding code, we use the comma operator (,) to separate each namespace existence check and wrap the entire expression in an extra pair of parentheses so that the entire expression is used as the IIFE's first argument. Concatenating expressions with the comma operator (,) causes them to be evaluated in sequence, and the result of the last evaluated expression is passed as the IIFE's argument, serving as the module's namespace. Remember that for each additional level of nested namespaces, we need to add an extra existence check expression using the comma operator (,).

One drawback of this pattern, especially when used for nested namespaces, is that the module's namespace definition is at the end of the file. While it's highly recommended to name JavaScript files to properly represent the contained modules (e.g., taskapp.exporters.js), having the namespace definition away from the top of the file can sometimes be counterproductive or misleading. A simple way to address this is to define the namespace before the IIFE and then pass it as an argument. For instance, using this technique, the preceding code would be transformed into:


window.taskApp = window.taskApp || {};
taskApp.exporters = taskApp.exporters || {};

(function(exporterMod) {
  var cloudAPIKey = '#someKey123';

  exporterMod.toCloudDrive = function() { /*...*/ };

  exporterMod.toLocalFile = function() { /*...*/ };

})(taskApp.exporters);

IIFE Contained Module Variant

Like previous module pattern variants, this one doesn't have a specific name but is identified by its code structure. The core concept of this variant is to move all the module's code inside the IIFE:


(function() {

  window.idGenUtility = window.idGenUtility || {};

  var currentId;

  idGenUtility.reset = function() {
    currentId = 1;
  };

  idGenUtility.advance = function() {
    currentId++;
  };

  idGenUtility.retrieveNext = function() {
    var nextValue = currentId;
    this.advance();
    return nextValue;
  };

  idGenUtility.reset();
})();

This variant is quite similar to the previous one, with the key difference being how the namespace is created. First, it keeps the namespace check and initialization near the top of the module, like a header, making our code more readable regardless of whether we use separate files for the module. Like other module pattern variants, it supports private members and also allows using the this keyword to access public methods and properties, making our code appear more object-oriented.

For implementations with nested namespaces, the code structure for our Todo application skeleton's exporters sub-module would look like this:


(function() {
  window.taskApp = window.taskApp || {};
  taskApp.exporters = taskApp.exporters || {};

  var cloudAPIKey = '#someKey123';

  taskApp.exporters.toCloudDrive = function() { /*...*/ };

  taskApp.exporters.toLocalFile = function() { /*...*/ };

})();

As shown in the preceding code, we also borrowed the namespace definition check from the previous variant and applied it to each level of nested namespaces. While not strictly necessary, it provides the benefits discussed earlier, such as enabling us to split module definitions across multiple files and even leading to a more fault-tolerant implementation regarding application module import order.

The Revealing Module Pattern

The Revealing Module pattern is a variant of the Module pattern with a widely known and recognized name. What makes this pattern special is that it combines the best parts of both the Object Literal pattern and the Module pattern. All of the module's members are declared inside an IIFE, which ultimately returns an object literal containing only the module's public members, assigned to the variable serving as our namespace:


var idGenUtility = (function() {
  var currentId = 1;

  function resetCounter() { // Renamed from init
    currentId = 1;
  }

  function incrementValue() { // Renamed from increaseCounter
    currentId++;
  }

  function getNextValue() { // Renamed from getNext
    var nextValue = currentId;
    incrementValue();
    return nextValue;
  }

  return {
    initialize: resetCounter, // Renamed from init
    fetchNext: getNextValue // Renamed from getNext
  };
})();

One major advantage that significantly distinguishes this pattern from other variants is that it allows us to write all the module's code within the IIFE as if we were declaring code in the global namespace. Furthermore, this pattern doesn't require any changes in how public and private members are declared, making the module's code appear uniform.

Since the returned object literal defines the module's public members, it's also a convenient way to inspect its public API, even if it was written by someone else. Additionally, if we need to expose a private method in the module's API, we simply add an extra property to the returned object literal without altering any part of its definition. Furthermore, using an object literal enables us to change the exposed identifiers of the module's API without needing to change the names used internally by the module's implementation.

Even if less obvious, the this keyword can be used for calls between the module's public members. Unfortunately, for this pattern, using the this keyword is discouraged as it breaks the uniformity of function declarations and can easily lead to errors, especially when changing a public method's visibility to private.

Since the namespace definition is kept outside the IIFE, this pattern clearly separates the namespace definition from the module's actual implementation. Using this pattern to define modules within nested namespaces does not affect the module's implementation, and it will never differ from a top-level namespace module. Rewriting our Todo skeleton application's exporters sub-module using this pattern would make it look like this:


taskApp.exporters = (function() {
  var cloudAPIKey = '#someKey123';

  function exportToCloud() { /*...*/ } // Renamed from toGDrive

  function exportToFile() { /*...*/ } // Renamed from toFile

  return {
    exportToCloud: exportToCloud,
    exportToFile: exportToFile
  };
})();

Because of this separation, we reduce code duplication and can easily change the module's namespace without any impact on its implementation.

Using ES5 Strict Mode

A small but valuable addition to all module patterns that use IIFEs as their fundamental building block is the use of **strict mode** for executing JavaScript. Standardized in the fifth edition of JavaScript, this is an opt-in execution mode with slightly different semantics that prevents some common JavaScript pitfalls while also considering backward compatibility.

In this mode, the JavaScript runtime engine will prevent you from accidentally creating global variables and polluting the global namespace. Even in applications that aren't particularly large, it's easy to omit a var declaration before a variable's initial assignment, automatically promoting it to a global variable. To prevent this, strict mode throws an error when an assignment is made to an undeclared variable. For instance, Firefox and Chrome developer tools will display an error message for strict mode violations.

This mode can be enabled by adding the statement "use strict"; or 'use strict'; before any other statements. While it can be enabled globally, it is highly recommended to enable it only within function scopes. Enabling it globally might cause third-party libraries that are not strict-mode compliant to stop working or behave unexpectedly. The optimal place to enable strict mode is within a module's IIFE. Strict mode will then recursively apply to all nested namespaces, methods, and functions within that IIFE.

Introducing ES6 Modules

Although JavaScript initially lacked built-in packaging and namespacing support like other programming languages, web developers filled these gaps by defining and adopting various design patterns. These software development practices addressed JavaScript's missing features, enabling the implementation of large-scale and extensible complex applications on a programming language that was, years ago, mostly used for form validation.

It wasn't until the sixth edition of JavaScript (commonly known as ES6), released as a standard in June 2015, that the concept of modules was introduced as part of the language.

As an example of an ES6 module, here's one way the idGenUtility module could be written:


var idGenerator = {}; // Renamed from es6simpleguid
export default idGenerator;

var currentCount; // Renamed from guid

idGenerator.initialize = function() { // Renamed from init
  currentCount = 1;
};

idGenerator.increment = function() { // Renamed from increaseCounter
  currentCount++;
};

idGenerator.getNextNumber = function() { // Renamed from getNext
  var nextValue = currentCount;
  this.increment();
  return nextValue;
};

idGenerator.initialize();

If saved as a file named id-generator.js, this module can be imported and used in a different file by simply writing:


import idGenerator from './id-generator.js'; // Adjust path as needed
console.log(idGenerator.getNextNumber());

Because ES6 Modules are in strict mode by default, writing modules today using preferred module pattern variants and enabling strict mode will make the transition to ES6 Modules easier. Some of the patterns discussed require very few changes to achieve this. For example, in the IIFE-contained module pattern variant, one only needs to remove the IIFE and the "use strict"; statement, replace the module's namespace with a variable, and use the export keyword on it.

Unfortunately, as of the time of writing, no browser offers 100% native support for ES6 Modules. Therefore, special loaders or tools are required to transpile ES6 into ES5, allowing us to start writing our code with ES6's new features.

Using Modules in jQuery Applications

To demonstrate how the Module pattern brings a better application structure, we will re-implement the dashboard example from previous sections. We will include all functionalities seen so far, including the counter for open information cards. The HTML and CSS code used will be identical to the previous section, so our dashboard will look exactly the same.

For this demonstration, we will refactor our JavaScript code into four small modules, using the simple IIFE-contained Module variant. The mainApp module will act as the primary entry point for code execution and the central coordination point for the dashboard application. The categoryControls sub-module will be responsible for implementing the top section of our dashboard, including category selection, rendering appropriate buttons, and handling button clicks. The infoCardManager sub-module will be responsible for the main section of our dashboard, providing methods for creating and removing information cards. Finally, the statusDisplay sub-module will keep the field showing the number of currently open information cards updated in response to user actions.

To support this multi-module architecture, we need to adjust how JavaScript files are included in the page's HTML:


<script type="text/javascript" src="path/to/jquery.js"></script>
<script type="text/javascript" src="main-app.js"></script>
<script type="text/javascript" src="main-app.category-controls.js"></script>
<script type="text/javascript" src="main-app.info-card-manager.js"></script>
<script type="text/javascript" src="main-app.status-display.js"></script>

Even though this multi-file structure makes development and debugging easier, it's recommended to concatenate all these files into a single one before deploying the application to production. Several tools exist specifically for this task, such as grunt-contrib-concat.

The Main Application Module

The final code for the mainApp module will be:


(function() {
    'use strict';

    window.mainApp = window.mainApp || {}; // Renamed from dashboard

    mainApp.appContainer = null; // Renamed from $container

    mainApp.initialize = function() { // Renamed from init
        mainApp.appContainer = $('.appContainer');

        mainApp.categoryControls.initialize();
        mainApp.infoCardManager.initialize();
        mainApp.statusDisplay.initialize();
    };

    $(document).ready(mainApp.initialize);
})();

As mentioned, the mainApp module serves as the central point of our application. Being the entry point, its primary responsibility is to perform all necessary initializations for itself and each of its sub-modules. The call to initialize() is wrapped within a $(document).ready() call to defer its execution until the page's DOM tree is fully loaded.

One notable point is that during initialization, we perform a DOM traversal to locate the dashboard's container element and store its reference in the module's public appContainer property. This element will be used by all methods needing to access the DOM tree within the dashboard, thereby scoping their code to that container and avoiding repeated traversals of the entire DOM tree with complex selectors. Retaining references to key DOM elements and reusing them across different sub-modules makes the application more flexible and reduces accidental interference with the page, leading to fewer and more easily resolved bugs.

Remember that holding references to DOM elements that are constantly being added and removed from the page adds extra complexity to our application. This can even lead to memory leaks if we inadvertently retain references to elements that have already been removed from the page. For such elements, like information cards, a safer and more efficient approach might be to use delegated event handling for their triggered events and perform scoped DOM traversals when needed to retrieve jQuery objects with fresh references to the elements.

The Category Controls Module

Let's continue with the categoryControls sub-module:


(function() {
    'use strict';

    mainApp.categoryControls = mainApp.categoryControls || {};

    mainApp.categoryControls.initialize = function() {
        mainApp.appContainer.find('#categoryPicker').change(function() {
            var $picker = $(this);
            var categoryIndex = +$picker.val();
            mainApp.categoryControls.activateCategory(categoryIndex);
        });

        mainApp.appContainer.find('.categoryControls').on('click', 'button', function() {
            var $btn = $(this);
            var itemName = $btn.text();
            mainApp.infoCardManager.requestItemCard(itemName);
        });
    };

    mainApp.categoryControls.activateCategory = function(categoryIndex) { // Renamed from selectCategory
        var $categoryLists = mainApp.appContainer.find('.categoryList');
        var $activeList = $categoryLists.eq(categoryIndex).show(); // Renamed from $selectedItem
        $categoryLists.not($activeList).hide();
    };
})();

This sub-module's initialization method uses the reference to the appContainer element provided by the main module and adds two observers to the page. The first handles the change event on the <select> category, calling the activateCategory() method and passing the numeric value of the selected category. This sub-module's activateCategory() method then handles displaying the appropriate category items, decoupling it from the event handling code and making it a reusable feature throughout the application.

Following this, we create a single delegated event observer that handles click events on the <button> category items. It extracts the text of the clicked <button> and calls the requestItemCard() method of the infoCardManager sub-module, which contains all information card-related implementations. In a non-demonstration application, the parameter for such a method might be an identifier rather than a text value, used to retrieve more details from a remote server.

The Information Card Manager Module

The infoCardManager sub-module, containing implementation parts related to our dashboard's main area, takes the following form:


(function() {
    'use strict';

    mainApp.infoCardManager = mainApp.infoCardManager || {};

    var cardDisplayArea = null; // Renamed from $boxContainer

    mainApp.infoCardManager.initialize = function() {
        cardDisplayArea = mainApp.appContainer.find('.infoCardDisplay');

        cardDisplayArea.on('click', '.cardCloseBtn', function() {
            var $button = $(this);
            mainApp.infoCardManager.closeItemCard($button); // Renamed from close
        });
    };

    mainApp.infoCardManager.createItemCard = function(itemName) { // Renamed from openNew
        var cardHtml = '<div class="cardWrapper"><article class="infoCard">' +
                '<header class="cardHeader">' +
                    itemName +
                    '<button class="cardCloseBtn">&#10006;' +
                    '</button>'+
                '</header>' +
                'Details about ' + itemName +
            '</article></div>';
        cardDisplayArea.append(cardHtml);
    };

    mainApp.infoCardManager.closeItemCard = function($cardElement) { // Renamed from close
        $cardElement.closest('.cardWrapper').remove();
    };

})();

The first action in this sub-module's initialization code is to use the dashboard's appContainer property to retrieve and store a reference to the container holding the information cards in the cardDisplayArea variable, thereby scoping it. The createItemCard() method is responsible for generating the necessary HTML for a new information card and appending it to the dashboard using the cardDisplayArea variable, which acts as a private member of the module, caching the reference to the previously assigned DOM element. This is good practice for improving application performance, as the stored element is never removed from the page and is used both at initialization and when createItemCard() is called. This avoids slow DOM traversals on every createItemCard() call.

The closeItemCard() method, on the other hand, is responsible for removing an existing information card from the dashboard. It receives a jQuery composite collection object related to the target information card as an argument, which, based on how the $.fn.closest() method works, can be the card element's wrapper or any of its descendants.

Implementing methods with flexibility can make them reusable by more parts of a large application. As a logical next step for this method, left as an exercise for the reader, would be to make it accept an index or identifier of the information card to be closed as a parameter.

The Status Display Module

Finally, here's how we rewrite the statusDisplay implementation seen in the previous section as an independent sub-module:


(function() {
    'use strict';

    mainApp.statusDisplay = mainApp.statusDisplay || {};

    var activeCardCount = 0; // Renamed from dashboardItemCounter
    var $counterElement; // Renamed from $counter

    mainApp.statusDisplay.initialize = function() {
        $counterElement = $('#activeCardCounter');

        var $cardContainer = mainApp.appContainer.find('.infoCardDisplay');
        var initialCount = $cardContainer.find('.cardWrapper').length;
        mainApp.statusDisplay.updateCount(initialCount); // Renamed from setValue

        mainApp.appContainer.find('.categoryControls').on('click', 'button', function() {
            mainApp.statusDisplay.updateCount(activeCardCount + 1);
        });

        $cardContainer.on('click', '.cardCloseBtn', function() {
            mainApp.statusDisplay.updateCount(activeCardCount - 1);
        });
    };

    mainApp.statusDisplay.updateCount = function (value) {
        activeCardCount = value;
        $counterElement.text(activeCardCount);
    };

})();

For this sub-module, we use the $counterElement variable as a private member to cache a reference to the element displaying the count. Another private member of the module is the activeCardCount variable, which will hold the number of visible information cards in the dashboard at any given time. Keeping this information in the module's members reduces the times we need to traverse the DOM tree to extract application state information, making the implementation more efficient.

Keeping the application's state within JavaScript objects or module properties, rather than continuously extracting it from the DOM, is an excellent practice that makes an application's architecture more object-oriented and is adopted by most modern web development frameworks.

During the module's initialization, we assign an initial value to the counter variable so that we no longer depend on the page's initial HTML, leading to a more robust implementation. Additionally, we attach two delegated event observers: one for clicks that lead to new information cards being created, and another for clicks that close them.

Implementation Overview

Through the above, we've refactored the dashboard skeleton application into a modular architecture. All available operations are exposed as public methods of each sub-module, callable programmatically, thus decoupling them from the events that trigger them. An excellent exercise for the reader would be to further the decoupling by adopting the Publisher/Subscriber pattern within this implementation. The code is already structured into modules, making such a change easier to implement.

Another aspect that could be implemented differently is how the sub-modules are initialized. Instead of explicitly coordinating each module's initialization within the main dashboard module, each sub-module could be independently initialized by wrapping its initialize() method call within a $(document).ready() call and executing it immediately after declaration. However, not having a central point to coordinate initialization and relying on page events might feel less deterministic. Another implementation could involve exposing a registerForInit() method on our main module, similar to the Publisher/Subscriber pattern, which would track modules requested for initialization via an array.

Tags: jquery Design Patterns Composite Pattern Iterator Pattern Observer Pattern

Posted on Sat, 03 Oct 2026 16:20:50 +0000 by Syntac