Object Oriented Javascript for views

This is a mail that I recently sent out internally that I thought might be useful to a larger audience. I haven't polished it much, so it's rather specific to the context of this particular project.

Here are a few rules of thumb that I've come up with over the years for simple client side javascript - i.e. your typical web application that is not a GMail style single page app, but still requires fairly complex js for some areas and routinely uses AJAX. Much of it would still apply even within a framework like backbone.js but YMMV.

General:

  • Always use a namespace
  • No free standing functions, always use objects
  • _.extend or $.extend copy functions, they don't do inheritance
    • Understand when a function is a function and when it is a constructor and how a prototype chain works
  • Never use anonymous callbacks (unless it's a truly trivial usage), always bind to a method of an object
  • Use utilities and libraries that don't do "this" hacks as far as possible. Thus prefer _.each over $.each
  • Understand scoping. You have exactly two choices to manage "this" correctly (and don't mix them)
    • Any function that in turn uses anonymous functions (for, say, iteration) should set "var self = this" in the first line as a closure based hack to reduce ambiguity
    • Any function that need to be called later (like an event handler) should use _.bind to set "this" correctly
  • All objects have an "initialize" method as a convention that is invoked in the last line of the constructor
  • The constructor just assigns values, any complicated state setup is carried out in initialize
  • Test drive everything except methods that affect the DOM

View code:

  • You are writing rich client view code. All of this has been done over and over again in the '90s. Go read up the patterns that emerged instead of re-inventing the wheel. The observer and decorator patterns are especially important.
  • All ViewModels accept one target (or wrapper) dom element. It will eventually render itself into this.
  • All ViewModels have a template that adheres to a structure that is also a contract. The ViewModel will render this template as its viewElement, and append it to the target when appropriate. The ViewModel can look up sub elements within its template based on selectors, hence the need for a standard structure-as-contract.
  • $("selector") or domElement.find is only permitted in the initialize method. No dom lookups anywhere else.
  • ViewModels always use a template library like Mustache. Never hand-roll your DOM, it gets brittle and hard to maintain.
  • All dom related lookups should work perfectly without it being appended to the browser dom (ie without render() being called)
    • This allows render to be stubbed without the object breaking (as in, the object is perfectly valid without having to be rendered first) so TDD is easy
    • This also means that you don't need shared view fixtures for specs
  • render() simply appends the objects viewElement to its wrapper (or target) element
  • Never tunnel data through the Dom. No looking up links from hrefs (it's ok to have them for usability though, but don't depend on them). Definitely no custom data attributes. Everything comes through the constructor or from an ajax API call.

Custom events:

  • jQuery's eventing is pretty nice, and works for custom events on non-dom elements. You can decorate any object like so $({}) and that object can now have events registered and triggered on it.
  • Avoid binding and triggering events on global variables like "document" or "window" unless it's absolutely necessary.
  • Every object that publishes events should have a registerForFoo() and notifyFoo() pair of methods for every event. Internally, this should delegate to jQuery's events. This keeps the string based event keys internal to the observable object.
  • Follow a clear, consistent naming convention for everything event related, both event keys as well as method names.
  • Like so much else in jQuery, the APIs to manage event payloads are different for on and trigger

No comments: