Abstracting jQuery

859 Views Asked by At

In these talks by Nicholas Zakas and Addy Osmani they discuss the idea of using the facade pattern as a sandbox when building large scale Javascript applications, in order to decouple the application from the underlying base libraries.

This decoupling would in theory allow you to switch out a base library without needing to rewrite your application modules. However in practice this seems to be more difficult to implement.

There are concrete implementations of this proposed architecture, such as AuraJS. However from looking at the source it seems that the sandbox still has leaky abstractions by returning jQuery objects from some of its methods.

I'm not concerned with AuraJS specifically, but more the general concept of trying to abstract a library like jQuery without losing so much functionality.

As an example, say my facade/sandbox has a dom method .find(selector). I can think of 3 options for what it might return:

  1. A jQuery object - This would leak jQuery out into the consuming modules.

  2. A raw dom element - Loss of functionality, nobody really wants to work with this! No chaining.

  3. A custom jQuery-like wrapper - Could be quite complex, but seems like the ideal solution.

So my question is, how would you abstract a library like jQuery without losing too much functionality, such that it could be replaced at some point in the future with minimal effort?

2

There are 2 best solutions below

1
On

I think you are asking about writing more modular code, but jquery isn't a good case for it.

Asynchronous Module Definition http://addyosmani.com/writing-modular-js/

7
On

Here's a very simple example of using modules as an architecture:

<!DOCTYPE html>
<title>Module play</title>
<body>
<script>

// myCore provides all functionality required by modules
// Could use a library in here
var myCore = {

  getContainer: function() {
    // code in here to find a suitable container in which to put widgets
    // This is where different client capabilities will be tested to ensure the
    // widget behaves in it's user agent context - desktop, phone, tablet, pad, etc.

    // very simple shortcut
    return {
            element: document.body,

            // This function could use a general purpose library
            add: function(widget) {
              this.element.appendChild(widget.getElement());
            }
    };

  },

  // This function could use a general purpose library
  getNewWidget: function() {
    var element = document.createElement('div');

    return {

      getElement: function() {
        return element;
      },

      display: function(text) { 

        // Tightly couple to itself or not? 
        this.getElement().innerHTML = '<em>' + text + '</em>';

        // or
        element.innerHTML = '<em>' + text + '</em>';
      }
    }
  }
};

// Missing sandbox layer...

// Add a module - only uses myCore API (access should be controlled by
// the sandbox), does not deal with underlying library or host objects
(function() {

  // Get a container to add a widget too
  var container = myCore.getContainer();

  // Create a widget
  var widget = myCore.getNewWidget();

  // Add the widget to the container
  container.add(widget);

  // Give something to the widget to display
  widget.display('Hello World');

}());

</script>
</body>

So you can see that at the module level, you don't care about the host environment or underlying library, you are just writing plain ECMAScript. You can get really defensive and do stuff like:

(function() {
    var container, widget;

    if (!myCore) return;

    if (myCore.getContainer) { // Some would include an isCallable test too

      container = myCore.getContainer();
    }

    // getWidget could be a method of container instead so that
    // everything you need is either a method or property of container
    // or widget
    if (myCore.getWidget) {
      widget = myCore.getWidget();
    }

    ...
}

and so on so everything is tested and checked. I've left out error handling, but hopefully the example is sufficient.