blog

Photo by Immo Wegmann on Unsplash

Behold! (JavaScript Views)

by

JavaScript has the propensity to be very untidy – if you let it, it will sprawl all over the place. Hundreds of global variables scattered across dozens of files, messy half-measures towards object-orientation, mixed in seemingly at random with ungrouped functions – anyone who’s had a client bring them a failed project from some other development team knows just how bad it can get.

JavaScript also has the potential to be neat and tidy, and keep private variables and methods truly private – if you know how. Closures and an understanding of prototypical inheritance are the secrets, of course – but these are general purpose tools. For more specific scenarios – say, attaching logic to an existing or dynamically created view – tidy encapsulation is just the beginning of the suite of features we covet.

We’d like to be able to declare and bind to page elements all in one spot, so we never duplicate jQuery selectors unnecessarily, it’s clear what we’ve bound when looking at the view, and we can easily re-use those bindings through a variety of functions. Heck, while we’re at it, we’d like to be able to bind events to these elements using the same mapping we’ve just created (maybe with some nice syntactical sugar to simplify statements), and assign arbitrary functions within our view to handle the events.

All of this should be neatly encapsulated within a JavaScript’class’, and instantiable when and where we desire; and as long as we’re dreaming, how about a nice, modular system that keeps private variables and methods from being exposed to other classes, automatically offers us safe, localized references to jQuery, underscore or whatever other libraries we’re using, and offers us the ability to initialize the module under conditions of our choosing.

Pinch yourself, it’s not a dream. After a few projects working with Backbone and Marionette, I was loath to do without those tidy and functional views and all the features above (and more) on future projects, even those that aren’t a good fit for Backbone. Thus, Behold! A view framework was written.

Behold is for those who like the tidy encapsulation and functionality that frameworks like Chaplin and Marionette provide for js views, but don’t need everything that Backbone offers, and can’t justify the extra weight of it.

Sold on the concept already? Go take a look at the Github Repo.

Need more convincing? Carry on, then.

Contents

Why Views?

What are js views? In a nutshell, the intent of a js view is to provide a neat encapsulation for all the ui binding, event handling and functionality associated with a given page, or set of pages. Novice JavaScriptoften involves terrible spaghetti code, spread throuhgout onevent handlers in the html, bunches of script tags littered randomly through the markup, and an ugly mess of global variables and functions sitting in a single master file.

With Views enabled by a library like Behold, you instead have nicely encapsulated modules that don’t leak private variables or declare any new global variables (unless you really want to, or forget to prepend var to your variable declaration, since no framework can save you from sloppy coding).

With Views, all of your ui binding is scoped to a root element, keeping selectors performant; all of your ui binding can be declared in a single object, keeping the bindings easy to find and use throughout the code, reducing wasteful rebinding across functions; all of your event binding can take advantage of existing ui binding, is also declared in a single, easy to find object, and keeps its functions enclosed within the view, eliminating the chance of overriding like named functions by declaring them globally, or conflicting with other libraries you’ve added.

In other words, Views can be the answer to keeping your site’s Javascript tidy and maintainable, whether you have a few pages that just need some animation added, a js-based browser plugin, or a complex webapp.

Why Behold?

If you need:

  • routing,
  • Collections,
  • client-side models of db tables,

you should check out a full MVC framework like Backbone and one of the many frameworks building on it, such as the two mentioned above, or similar frameworks like Angular or Ember.

If, on the other hand, you just need:

  • Modular encapsulation that keeps global objects to a minimum, and keeps private variables really private;
  • Easy, organized declaration of UI elements to bind to, with automatic binding and all the niceties that jQuery has to offer immediately available;
  • Similarly easy and organized event binding, which are automatically bound upon view initialization, can be bound and unbound as a unit, and are compatible with the jQuery style bindings you’re used to (including supporting event name-spacing);
  • Clear organization and encapsulation of functions being bound to, or that operate on a given view;
  • and similar helpful functionality (see below for more details)

then Behold is for you.

Dependencies

jQuery, or API-compatible replacement library, such as Zepto.

Underscore – Optional – if not supplied, Behold includes a stripped down, native-reliant re-implementation of some of underscore’s functionality. See below for more details.

Usage Example

In your JavaScriptentry point of execution:

var gApp = new Behold.Application();
$(document).ready(function() {
    gApp.start(); // Initializes all registered modules
});

Then, in any other JavaScriptfile included in the page (you can include it before or after your entry point, so long as it will be loaded before whatever criteria you’ve selected for executing start() on your application object):

/**
 * Module constructor functions are passed four arguments by default.
 * self = A reference to the module.
 * app = A reference to the application object that the module has been registered on.
 * $ = A reference to the jQuery library.
 * _ = A reference to the underscore library, or our fill in if underscore is not present and passed in.
 * Additional parameters can be fed into the constructor function by adding them, comma separated, after the
 * constructor function (see below).
 */
gApp.module("moduleName", function(self, app, $, _) {
    // Variables declared with var are private to this closure.
    // Convention is to preface the variable name with an underscore to visually indicate this.
    var _Header = Behold.extend({ // _Header is now a constructor for a new Behold View.
            el: "#header", // Root element
            ui: { // ui bindings
                // the key 'fbLogin' will automatically be bound to the element with the id #fbHeaderLogin, found
                // somewhere beneath the root element #header
                // Any valid jQuery selector can be used
                fbLogin: "#fbHeaderLogin",
                gpLogin: "#gpHeaderLogin"
            },
            events: { // event bindings
                // A click event will automatically be bound to the element with the key 'fbLogin' within the ui
                // object, as seen above. When this event fires, the 'onClick' handler within this view will be triggered.
                "click @ui.fbLogin": "onClick"
            },
            /**
             * The initialize function will be called when this view is instantiated, and is the perfect place to put
             * code that should be run at that time, like bindings that can't live in the events object for whatever
             * reason, or function calls to make first-run changes.
             */
            initialize: function() {},
            /**
             * This is the event handler that we bound to in the events object, above. Notice it takes one parameter,
             * event, which is the jQuery Event, as per usual handler behaviour.
             */
            onClick: function(event) {}
        }),
        _header; // We declare another local variable for the instantiated view.
    /**
     * Initialize this module. This function will be called automatically by Behold.Application.start().
     * In this example, if we detect that the header element is present, we instantiate the Header view.
     */
    self.initialize = function() {
        if ($("#header").length) {
            _header = new _Header({} /* We can pass in options in this object, that will be available via this.options in the view */ );
        }
    };
    // Here we could add a comma separated series of variables to pass into the module constructor function, after the
    // functions closing curly bracket, should we wish to.
});

More Details

View, download and/or Fork the code on GitHub.

For a detailed view into the internals of Behold, head to the doxx pages.

+ more

Accurate Timing

Accurate Timing

In many tasks we need to do something at given intervals of time. The most obvious ways may not give you the best results. Time? Meh. The most basic tasks that don't have what you might call CPU-scale time requirements can be handled with the usual language and...

read more