Book Image

jQuery Design Patterns

By : Thodoris Greasidis
Book Image

jQuery Design Patterns

By: Thodoris Greasidis

Overview of this book

jQuery is a feature-rich JavaScript library that makes HTML document traversal and manipulation, event handling, animation, and Ajax much simpler with an easy-to-use API that works across a variety of browsers. With a combination of versatility and extensibility, jQuery has changed the way that millions of people write JavaScript. jQuery solves the problems of DOM manipulation, event detection, AJAX calls, element selection and document queries, element attribute and data management, as well as object management utilities. This book addresses these problems and shows you how to make the best of jQuery through the various design patterns available. The book starts off with a refresher to jQuery and will then take you through the different design patterns such as facade, observer, publisher/subscriber, and so on. We will also go into client-side templating techniques and libraries, as well as some plugin development patterns. Finally, we will look into some best practices that you can use to make the best of jQuery.
Table of Contents (18 chapters)
jQuery Design Patterns
Credits
About the Author
About the Reviewer
www.PacktPub.com
Preface
Index

The Composite Pattern


The key concept of the Composite Pattern is to enable us to treat a collection of objects in the same way as we treat a single object instance. Manipulating a composition by using a method on the collection will result in applying the manipulation to each part of it. Such methods can be applied successfully, regardless of the number of elements that are part of the composite collection, or even when the collection contains no elements.

Also, the objects of a composite collection do not necessarily have to provide the exact same methods. The Composite Object can either expose only the methods that are common among the objects of the collection, or can provide an abstracted API and appropriately handle the method differentiations of each object.

Let's continue by exploring how the intuitive API that jQuery exposes is highly influenced from the Composite Pattern.

How the Composite Pattern is used by jQuery

The Composite Pattern is an integral part of jQuery's architecture and is applied from the very core $() function itself. Each call to the $() function creates and returns an element collection object, which is often simply referred as a jQuery object. This is exactly where we see the first principle of the Composite Patterns; in fact, instead of returning a single element, the $() function returns a collection of elements.

The jQuery object returned is an Array-like object that acts as a wrapper object and carries the collection of the retrieved elements. It also exposes a number of extra properties as follows:

  • The length of the retrieved element collection

  • The context that the object was constructed

  • The CSS selector that was used on the $() function call

  • A prevObject property in case we need to access the previous element collection after chaining a method call

Tip

Simple Array-like object definition

An Array-like object is a JavaScript object { } that has a numeric length property and the respective number of properties, with sequential numeric property names. In other words, an Array-like object that has the length == 2 property is expected to also have two properties defined, "0" and "1". Given the above properties, Array-like objects allow you to access their content using simple for loops, by utilizing JavaScript's Bracket Property Accessor's syntax:

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

We can easily experiment with the jQuery objects returned from the $() function and inspect the properties described above, by using the developer tools of our favorite browser. To open the developer tools on most of them, we just need to press F12 on Windows and Linux or Cmd + Opt + I on Mac, and right after that, we can issue some $() calls in the console and click on the returned objects to inspect their properties.

In the following figure, we can see what the result of the $('#pageHeader') call, which we used in the example earlier, looks like in Firefox Developer Tools:

The result of the $('.boxContainer .box') call looks as follows:

The fact that jQuery uses Array-like objects as a wrapper for the returned elements allows it to expose some extra methods that apply on the collection returned. This is achieved through prototypical inheritance of the jQuery.fn object, resulting in each jQuery object also having access to all the methods that jQuery provides. This completes the Composite Pattern, which provides methods that, when applied to a collection, are appropriately applied to each of its members. Because jQuery uses Array-like objects with prototypical inheritance, these methods can be easily accessed as properties on each jQuery object, as shown in the example in the beginning of the chapter: $('#pageHeader').css('font-size', '3em');. Moreover, jQuery adds some extra goodies to its DOM manipulating code, following the goal of smaller and less error-prone code. For example, when using the jQuery.fn.html() method to change the inner HTML of a DOM node that already contains child elements, jQuery first tries to remove any data and event handlers that are associated with the child elements, before removing them from the page and appending the provided HTML code.

Let's take a look at how jQuery implements these collection-applicable methods. For this task, we can either download and view the source code from the GitHub page of jQuery (https://github.com/jquery/jquery/releases), or we can use a tool such as the jQuery Source Viewer that is available at http://james.padolsey.com/jquery.

Note

Depending on the version you are using, you might get different results to some degree. The most recent stable jQuery version that was released and used as a reference while writing this book, was v2.2.0.

One of the simplest methods to demonstrate how methods that apply to collections are implemented, is jQuery.fn.empty(). You can easily locate its implementation in jQuery's source code by searching for "empty:" or using the jQuery Source Viewer and searching for "jQuery.fn.empty". Using either one of the ways will bring us to the following code:

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

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

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

  return this; 
}

As you can see, the code is not complex at all. jQuery iterates over all the items of the collection object (referred to as this since we are inside the method implementation) by using a plain for loop. For each item of the collection, that is, an Element Node, it clears any data-* property values using the jQuery.cleanData() helper function, and right after this, it clears its content by setting it to an empty string.

Note

For more information on the different specified Node Types, you can visit https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType.

Comparing the benefits over the plain DOM API

To clearly demonstrate the benefits that the Composite Pattern provides, we will rewrite our initial example without the abstractions that jQuery offers. By using just plain JavaScript and the DOM API, we can write an equivalent code that looks as follows:

setTimeout(function() { 
  var headerElement = document.getElementById('pageHeader'); 
  if (headerElement) { 
    headerElement.style.fontSize = '3em'; 
  } 
  var boxContainerElement = document.getElementsByClassName('boxContainer')[0]; 
  if (boxContainerElement) { 
    var innerBoxElements = boxContainerElement.getElementsByClassName('box'); 
    for (var i = 0; i < innerBoxElements.length; i++) { 
      var boxElement = innerBoxElements[i]; 
      boxElement.innerHTML +='<br /><br /><i>In case we need simple things</i>.'; 
      boxElement.parentNode.className += ' boxsizer'; 
    } 
    var clearFloatDiv = document.createElement('div'); 
    clearFloatDiv.className = 'clear'; 
    boxContainerElement.appendChild(clearFloatDiv); 
  } 
}, 700);

Once again, we use setTimeout with an anonymous function and set 700 milliseconds as the second parameter. Inside the function itself, we use document.getElementById to retrieve elements that are known to have a unique ID in the page, and later document.getElementsByClassName when we need to retrieve all the elements that have a specific class. We also use boxContainerElement.getElementsByClassName('box') to retrieve all the elements with the box class that are descendants of the element with the boxContainer class.

The most obvious observation is that, in this case, we needed 18 lines of code in order to achieve the same results. For comparison, when using jQuery, we only needed 9 lines of code, that's half the number of lines of code compared to the later implementation. Using the jQuery $() function with a CSS selector was an easier way to retrieve the elements that we needed, and it also ensures compatibility with browsers that do not support the getElementsByClassName() method. However, there are more benefits than just the code line count and the improved readability. As an implementer of the Composite Pattern, the $() function always retrieves element collections, making our code more uniform when compared to the differentiated handling of each getElement* method we used. We use the $() function in exactly the same way, regardless of whether we just want to retrieve an element with a unique ID or a number of elements with a specific class.

As an extra benefit of returning Array-like objects, jQuery can also provide more convenient methods to traverse and manipulate the DOM, such as those we saw in our first example, .css(), .append() and .parent(), which are accessible as properties of the returned object. Additionally, jQuery also offers methods that abstract more complex use cases such as .addClass() and .wrap() that have no equivalent methods available as part of the DOM API.

Since the returned jQuery collection objects do not differ in anything other than the elements they wrap, we can use any method of the jQuery API in the same way. As we saw earlier, these methods apply to each element of the retrieved collection, regardless of the element count. As a result, we do not need a separate for loop to iterate over each retrieved element and apply our manipulations individually; instead, we apply our manipulations (for example, .addClass()) directly to the collection object.

To continue providing the same execution safety guaranties in the later example, we also need to add some extra if statements to check for null values. This is required because, for example, if the headerElement is not found, an error will occur and the rest of the lines of code will never be executed. Someone could argue that these checks, such as if (headerElement) and if (boxContainerElement), are not required in this example and can be omitted. This might appear to be correct in this example, but actually this is among the top reasons for errors while developing large-scale applications, where elements are created, inserted, and removed from the DOM tree continuously. Unfortunately, programmers in all languages and target platforms tend to first write their implementation logic and fill such checks at a later time, often after they get an error when testing their implementation.

Following the Composite Pattern, even an empty jQuery collection object (one that contains no retrieved elements) is still a valid collection object, where we can safely apply any method that jQuery provides. As a result, we do not need the extra if statements to check whether a collection actually contains any element before applying a method such as .css(), just for the sake of avoiding a JavaScript runtime error.

Overall, the abstractions that jQuery offers by using the Composite Pattern lead to fewer lines of code, which is more readable, uniform, and with fewer typo-prone lines (compare typing $('#elementID') versus document.getElementById('elementID')).

Using the Composite Pattern to develop applications

Now that we have seen how jQuery uses the Composite Pattern in its architecture and also did a comparison on the benefits it provided, let's try to write an example use case of our own. We will try to cover all concepts that we have seen earlier in this chapter. We will structure our Composite to be an Array-like object, operate on totally different structured objects, provide a Fluent API to allow chaining, and have methods that apply on all the items of the collection.

A sample use case

Let's say that we have an application that at some point needs to perform operations on numbers. On the other hand, the items that it needs to operate on come from different sources and are not uniform at all. To make this example interesting, let's suppose that one source of data provides plain numbers and another one provides objects with a specific property that holds the number we are interested in:

var numberValues = [2, 5, 8]; 

var objectsWithValues = [ 
    { value: 7 }, 
    { value: 4 }, 
    { value: 6 }, 
    { value: 9 } 
];

The objects returned by the second source of our use case could have a more complex structure and probably some extra properties. Such changes wouldn't differentiate our example implementation in any way, since when developing a Composite we are only interested in providing a uniform handling over the common parts between the targeted items.

The Composite Collection Implementation

Let's proceed and define the Constructor Function and the prototype that will describe our Composite Collection Object:

function ValuesComposite() { 
    this.length = 0; 
} 

ValuesComposite.prototype.append = function(item) { 
    if ((typeof item === 'object' && 'value' in item) || 
        typeof item === 'number') { 
        this[this.length] = item; 
        this.length++; 
    } 

    return this; 
}; 

ValuesComposite.prototype.increment = function(number) { 
    for (var i = 0; i < this.length; i++) { 
        var item = this[i]; 
        if (typeof item === 'object' && 'value' in item) { 
            item.value += number; 
        } else if (typeof item === 'number') { 
            this[i] += number; 
        } 
    } 

    return this; 
}; 

ValuesComposite.prototype.getValues = function() { 
    var result = []; 
    for (var i = 0; i < this.length; i++) { 
        var item = this[i]; 
        if (typeof item === 'object' && 'value' in item) { 
            result.push(item.value); 
        } else if (typeof item === 'number') { 
            result.push(item); 
        } 
    } 
    return result; 
};

The ValuesComposite() constructor function in our example is quite simple. When invoked with the new operator, it returns an empty object with a length property equal to zero, representing that the collection it wraps is empty.

Note

For more information on the Prototype-based programming model of JavaScript, visit https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript.

We first need to define a way that will enable us to populate our composite collection objects. We defined the append method that checks whether the provided parameter is one of the types that it can handle; in this case, it appends the parameter on the Composite Object on the next available numeric property and increments the length property value. For example, the first appended item, whether it is an object with a value property or a plain number, will be exposed to the "0" property of the Composite Object and will be accessible with the Bracket Property Accessor's syntax as myValuesComposition[0].

The increment method is presented as a simple example method that can manipulate such collections by operating over all the collection items. It accepts a numeric value as a parameter and then appropriately handles it by adding it to each item of our collection, based on their type. Since our composite is an Array-like object, increment uses a for loop to iterate over all the collection items and either increases the item.value (in case the item is an object) or the actual numeric value stored (when the collection item stored is a number). In the same manner, we can continue and implement other methods that will, for example, enable us to multiply the collection items with a specific number.

In order to allow chaining the methods of our Composite Object, all the methods of the prototype need to return a reference to the instance of the object. We achieve this goal by simply adding a return this; statement as the last line for all the methods that manipulate the collection, such as append and increment. Keep in mind that methods such as getValues that do not manipulate the collection but are used to return a result, by definition, can't be chained to relay the collection object instance to subsequent method calls.

Finally, we implement the getValues method as a convenient way to retrieve the actual numeric values of all the items in our collection. Similar to the increment method, the getValues method abstracts away the handling between the different item types of our collection. It iterates over the collection items, extracts each numeric value, and appends them to a result array that it returns to its caller.

An example execution

Let's now see an actual example that will use the Composite Object we just implemented:

var valuesComposition = new ValuesComposite(); 

for (var i = 0; i < numberValues.length; i++) { 
    valuesComposition.append(numberValues[i]); 
} 

for (var i = 0; i < objectsWithValues.length; i++) { 
    valuesComposition.append(objectsWithValues[i]); 
}

valuesComposition.increment(2) 
    .append(1) 
    .append(2) 
    .append({ value: 3 }); 

console.log(valuesComposition.getValues()); 

When the preceding code is executed in a browser, by writing the code either in an existing page or directly in the browser's console, it will log a result that looks as follows:

► Array [ 4, 7, 10, 9, 6, 8, 11, 1, 2, 3 ]

We are using our data sources such as the numberValues and objectsWithValues variables that were shown earlier. The preceding code iterates over both of them and appends their items to a newly created Composite Object instance. We then proceed by incrementing the values of our composite collection by 2. Right after this, we chain the three item insertions using append, with the first two appending numeric values and the third appending an object with a value property. Finally, we use the getValues method in order to get an array with all the numeric values of our collection and log it in our browser's console.

Alternative implementations

Keep in mind that a Composite does not need to be an Array-like object, but is commonly preferred since JavaScript makes it easy to create such an implementation. Additionally, Array-like implementations also have the benefit of allowing us to iterate over the collection items using a simple for loop.

On the other hand, in case an Array-like object is not preferred, we can easily use a property on the Composite Object to hold our collection items. For example, this property can be named as items and be used to store and access the items of the collection inside our methods using this.items.push(item) and this.items[i], respectively.