9

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?

jmac
  • 7,078
  • 2
  • 29
  • 57
Brett Postin
  • 11,215
  • 10
  • 60
  • 95
  • jQuery is quite convoluted internally, creating an abstraction such that it could be replaced by any (or even one) other library seems extremely difficult. And what's the point? If you think you might want to swap libraries in future, perhaps you shouldn't be using the one you're using in the first place. – RobG Apr 16 '13 at 09:08
  • @RobG I agree it seems difficult, which is why I'm asking the question. However far smarter people than myself are making these proposals and providing valid reasons for them (see the links to the talks provided). – Brett Postin Apr 16 '13 at 09:22
  • In Nicholas' architecture, if you change the base library, you'd also have to re-write the application core. Once you get above the core, you aren't writing code that uses the library anymore. If you're using jQuery, the person writing a module doesn't write **any** jQuery. – RobG Apr 16 '13 at 12:10
  • @RobG I understand that. Writing methods in the core to abstract the base libraries is straight forward. Its what to return from those abstractions and expose through the sandbox that is the issue. e.g. If have a dom module in the core, what should be returned from .find() if not a jQuery object? What about something like .deferred()? – Brett Postin Apr 16 '13 at 12:41
  • The idea is to build the core to do only what the application needs, nothing more. You do not allow any base library objects (or even DOM objects) to leak into the sandpit or modules. As Nicholas says in the early part, a library needs to do everything (i.e. be general), an application doesn't. So many of the goals of a general library, e.g. fixing every cross-browser difference or shims for every new ES5 feature, aren't needed in the core. You just fix the issues you need to and supply the functionality required for higher levels. – RobG Apr 16 '13 at 20:14
  • Oh, when I say "a library needs to do everything", I should have said "a **general purpose** library…", like Dojo, ext, jQuery, etc. Once you focus on what you actually need to do, often you can throw away 90% of most libraries. – RobG Apr 16 '13 at 22:09
  • @RobG I already understand all of what you are saying, it doesn't really answer my question. Take this simple case. I want to provide some simple dom selection/manipulation to devs. I implement this in the core, and expose it as an abstraction in the sandbox. If you say not to allow any base library objects, or even dom objects to leak into the modules, how is this functionality achieved? Are you suggesting to create simple methods that do the logic but with no return values, because that would be painful to work with! – Brett Postin Apr 16 '13 at 23:05
  • 1
    The modules should only deal with [native objects](http://www.ecma-international.org/ecma-262/5.1/#sec-4.3.6) and methods provided by the core. You might have `var x = new Widget(); container.add(x);`, then `x.display('hellow world')`. The widget object might encapsulate a host (e.g. DOM) object, but you never reference it directly, nor do you specify how it's added to the container (it might be appended, or replace something, whatever). Nor do you display the text by setting the innerHTML, innerText, textContent, or by adding a text node, or anything. That is all handled by the core. – RobG Apr 17 '13 at 02:11
  • Doesn't Ender and Zepto do this? – fiatjaf Dec 23 '13 at 21:37

2 Answers2

1

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.

RobG
  • 142,382
  • 31
  • 172
  • 209
  • Thanks for this detailed answer, I see where you're coming from. However it seems that you are essentially pushing the widget functionality and behaviour down into the core, leaving the actual modules as really dumb consumers. I think this is too restrictive and don't think it would scale for an enterprise application. My understanding is that the core should provide sets of **functionality** (i.e. dom manipulation/ajax/mediator) that hide the base library implementations and are then exposed through a sandbox that acts as a consistent "glue" layer between the modules and the core. – Brett Postin Apr 17 '13 at 09:48
  • See slides 40/51/66 of Nicholas's talk. There is nothing to suggest modules cannot control the dom within their own scope. In order to do that some form of dom manipulation "service" is required from the core/sandbox. See slides 105-109 of Addy's talk for an example of what I mean. Essentially the sandbox should provide a set of "services" to the modules. It's the return values (if any) of those service calls that I'm finding difficult to abstract. – Brett Postin Apr 17 '13 at 10:03
  • Also check out the source for [AuraJS](https://github.com/aurajs/aura/tree/master/lib) (Addy's implementation of Nicholas's architecture). You will see how the sandboxes are created from base.js, and what I see as the leaky abstractions in what is being returned from the sandbox methods (mainly jQuery objects). Thank you for your time btw! – Brett Postin Apr 17 '13 at 10:14
  • I think you misunderstood those slides - "don't access DOM elements outside your sandbox" doesn't mean "play with DOM elements in your sandbox using DOM methods". Allowing a DOM object out of the core means that your module must do feature detection, so you're back to square one. What if the element used to display the text in my example is changed from a DIV to a textarea? Your module must now set the *value* property, not the *innerHTML* (or equivalent) property. Or maybe feature detect *textContent/innerText*. That is what you are trying to avoid. – RobG Apr 17 '13 at 10:45
  • 1
    Ok. So just to clarify, each module will have its own sandbox that will act as a very high-level "interface" for the module? e.g. a "tabs" widget may have a `.showTab()` method in its sandbox that then calls on the dom manipulation abstraction in the core? Out of interest, what is your take on the sandbox implementation in Aura? – Brett Postin Apr 17 '13 at 11:06
  • I would also love to see the clarification to this... I'm pondering the same question. Did you figure anything out @BrettPostin? – Korijn Jul 07 '13 at 19:36
  • @Korijn In the end I moved away from the architecture above as I found it to be too restrictive in practice for an enterprise scale web application. Instead I went with AngularJS, which provides a good enough architecture with good abstractions plus a whole bunch of other really amazing features out of the box. It also does the hard work of implementing point 3 in my question above by providing it's own jQuery subset called jQuery-lite. When you start doing things the "Angular" way, your dependency on jQuery becomes much less important, almost to the point that you can remove it completely. – Brett Postin Jul 09 '13 at 07:47
-1

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/

Greg Benner
  • 668
  • 7
  • 15