Archive by Author

Fluent Conf: Maintainable JavaScript

Nicholas C. Zakas (WellFurnished) @slicknet
Code Style / Programming Practices

Below are my notes from Zakas’ presentation at Fluent Conf; slides can be viewed with slideshare.

Why Write Maintainable Code

  • Most of the time you’re maintaining code
  • Maintainability is good for the business, your code is supposed to keep working after you leave.
  • Your co-workers care a lot about your code
  • Pretty much everybody learned (front-end developers) on their own.
    • They all code the best way (rock stars)
    • Be on a team, let go of the rock star mentality; teams succeed (minus Tennis)

What is Maintainable Code

  • Maintainable code works for 5 years without major changes (re-write)
  • Intuitive: look at it and it kind of makes sense
  • Understandable: figure it out relatively quickly
  • Adaptable
  • Extendable: built on to do more things
  • Debuggable: easily step through
  • Testable, Unit tests, Functional Test
  • Be kind to your future self, Chris Epstein (Compass)

Code Conventions

Style Guide

Commenting

  • Self-documenting code is a myth perpetuated by those who hate to write documentation
  • Like seasoning, just enough is the right amount of code commenting
  • Difficult to understand code needs comments!
  • Code that might seem wrong when you first look at it needs comments (I meant to do that!)
  • Browser specific code to support a hack for IE needs comments

Naming

  • Use logical names for variables & functions
  • Don’t worry about length
  • You are communicating with yourself and other developers
    • Verbs do stuff, use for methods
    • Nouns are things, properties
  • Avoid useless names
  • camelCase is a good standard for variables and functions
    • What about acronyms? new XMLHttpRequest() use upper or camelCase, not both, please.
  • Constant-like varibles: HOVER_CLASS
  • Constructor functions should have the first letter in Uppercase

Programming Practices

  • Do not have HTML strings in your JavaScript
  • Do not have JavaScript in your HTML
  • Do not have a JavaScript expression in CSS
  • Keep CSS out of JavaScript, go to CSS and style it
  • Many people write event handlers the wrong way, they should be very small and only handle events themselves, e.g. call other functions.
  • Don’t pass the event object around, WHY? If you want to call the function somewhere else you need the event, and you won’t know what specfic data is needed from the event object
    • Event handlers should pass relevant data to a standard function
  • Don’t modify objects you don’t own; adding methods to prototype, other people have an expectation about how the API is supposed to work, when you change that you create a mine field
  • Avoid global function and variables
  • Throw your own errors! this is a really, really helpful thing.
    • “I don’t like when I get errors and I have no idea where they are coming from”
    • Put method the name and a message in the error.
    • Just in the places where you are likely to cause an error
    • Or, when you are creating an API, just to help people out
  • Avoid null comparisons, use instanceof to test for specifit object types
    • object instanceof MyType
    • typeof null is equal to object
  • Separate config data from methods, keep data out of application logic, changing data should not introduce errors
    • All URLs should be pulled out of JavaScript logic
    • Any settings, repeated unique values
  • Props2js: https://github.com/nzakas/props2js

Automation

Vertebrae front-end framework built with Backbone.js and RequireJS using AMD

Vertebrae provides AMD structure and additional objects for extending Backbone.js as an application framework.

The plan: build an application, mostly with open source libraries

Following a review of many MV* front-end frameworks/libraries, such as Ember, JavaScriptMVC, Spine, Knockout, and Backbone.js our team decided to begin building a framework with Backbone.js, RequireJS, Underscore.js, jQuery, Mustache.js with code organized in packages of (AMD) modules, see previous post: Optimize and Build a Backbone.js JavaScript application with Require.JS using Packages

We needed to add some logic on top of the chosen stack of libraries

The libraries we selected provide: module loading, dependency management, application structure (for models, collections, views and routes), asynchronous interactions with API, various utilities and objects to manage asynchronous behaviors, e.g. (Promises) Deferreds, Callbacks. The remaining logic needed to complete the application include:

  • An object (collection) to manage state(s) in the single-page application;
  • A layout manager to present, arrange/transition and clear views, and
  • Controllers which respond to routes, get/set application state, and hand off views and models/collections to layout manager.

The project goals include:

  • Modules for code organization (a module should have a single responsibility)
  • MVC structure for separation of concerns
  • Dependency management using a script loader
  • Build tools for optimization in packages which should support various experiences in the app
  • Well documented and actively used open source libraries
  • Framework code should have unit tests, build framework by writing unit tests.
  • Solutions for managing asynchronous behaviors, e.g. Promises

We are using: RequireJS, Underscore.js, jQuery, Backbone.js, Mustache.js; and for BDD… Jasmine, Sinon, jQuery-Jasmine.

Solutions

Application state manager

The application manager stores data in memory (collection) and also persists data in browser storage to provide a resource for storing and retrieving data/metadata. The application states collection also provides data (state) to reconstruct a page (layout view) based on previous interactions (e.g. selected tab, active views, etc.). The application state manager provides a strategy for resources to retrieve state for a common interface for localStorage, sessionStorage, or a cookie; also sets an expires property.

Also, data used to bootstrap a page can be stored as well, e.g. user/visitor information (items in cart, etc.), data that is reused for many pages, interactions on a page that should be recalled when a user revisits the page.

Layout manager

The layout manager has one or many views as well as document (DOM) destinations for each (rendered) view. A page may transition between views, so the layout manager keeps track of view states, e.g. rendered, not-rendered, displayed, not-displayed. The layout manager can load and render (detached) views that a user/visitor is very likely to request, e.g. tab changes on page. The transition between view states is managed by this object. An entire layout may be cleared so that view objects and their bindings are removed, preparing these objects for garbage collection (preventing memory leaks). The layout manager also communicates view state with controller(s).

Controller

A controller object is called by a route handler function, is responsible for getting relevant state (application models), and does the work to construct a page (layout); also responsible for setting state when routes change. The controller passes dependent data (models/collections) and constructed view objects for a requested page to the layout manager. As a side-effect the use of controllers prevents the routes object from becoming bloated and tangled. A route should map to a controller which then kicks off the page view, keeping the route handling functions lean.

Behavior Driven Development -

We made the decision to develop the framework using behavior driven development. Jamsine is the test framework we selected, below are the specs that were used to discover the solutions for our need to have the additional logic we wanted to add on top of the open source stack of libraries…

Specs

Application state manager specs
(an model object to manage state within the single-page application)

should use options for persistent storage of JSON data
should reference stored data with string name, e.g. route name
should have expiration for data objects that are persisted in localStorage
should store (in memory) reference to model/collection objects for reuse
should be a singleton object
should provide strategy for data retrieval (memory/storage/api)
should store view state of page layouts

Layout manager specs
(presents, arranges, transitions and clears views)

should use ‘destination’ property for location on dom to show each view
should keep track of current state for view objects, e.g. not-rendered, shown, hidden
should lazy/load or render hidden (detached) views within a layout
should have show and close methods to render and display a view
should manage transition between views within a layout scheme
should close (destroy) managed views/bindings within a layout (e.g. on transition to new layout)
should have access to view’s deferred object (returned on render) for asynchronous display
should have option to display layout when all views’ deferreds are resolved or as ready

Controller specs
(gets/sets application state, and delegates work to a layout manager object, used within route handlers)

should get data from application state manager object
should initialize views/models with relevant data received from application state manager
should call layout manager with arguments including relevant views/data
should send data to manager when route changes to store view state or dependent data
should receive data from view objects which publish change in view state

Views:

We studied many posts on the topic of Backbone.js and came up with a set of views that will support all the work we needed and provide some patterns to reuse in our application…

BaseView, CollectionView, SectionView, LayoutView (Manages Sections)

All the views extend the BaseView which extends the Backbone.View object.

Base View

A view object to construct a standard view with common properties and utilties The base view extends Backbone.View adding methods for resolving deferreds, rendering, decorating data just in time for rendering, adding child views to form a composite of views under one view object, add a destroy method.

Collection View

Manages rendering many views with a collection. The CollectionView extends BaseView and is intended for rendering a collection. A item view is required for rendering within each iteration over the models. This was a great source for the CollectionView: http://liquidmedia.ca/blog/2011/02/lib-js-part-3/

Section View

View object to track view’s state ‘not-rendered’, ‘rendered’, ‘not-displayed’, ‘displayed’; can be mixed in to another view type, e.g. CollectionView.

A section view is the required view object for a layout view which expects views to track their own state. This view may be extended as need. to use in a layout, perhaps adding the Section prototype properties to another view.

Layout Manager View

Presents, arranges, transitions and clears views

The layout manager has one or many views as well as document (DOM) destinations for each (rendered) view. A page may transition between many views, so the layout manager keeps track of view states, e.g. ‘not-rendered’, ‘rendered’, ‘not-displayed’, ‘displayed’.

The layout manager can lazy load and render (detached) views that a member is very likely to request, e.g. tab changes on events page. The transition between view states is managed by this object. An entire layout may be cleared so that view objects and their bindings are removed, preparing these objects for garbage collection (preventing memory leaks). The layout manager also communicates view state with controller(s).

Models:

BaseModel, ApplicationState

Application State Model

A model object to manage state within the single-page application Attributes: {String} name, {Object} data, {String} storage, {Date} expires

All the models extend the BaseModel which extends the Backbone.Model object.

Collections

BaseCollection, ApplicationStates

Application State Collection

A collection object to reference various states in the application.

The application manager stores data in memory and also persists data in browser storage to provide a resource for common data/metadata. Also provides data (state) to reconstruct the page views based on previous interactions (e.g. selected tab, applied filters). The application state manager provides a strategy for resources to retrieve state.

All the collections extend the BaseCollection which extends the Backbone.Collection object.

Syncs:

syncFactory, application-state, storageFactory

We have a sync to use localStorage, sessionStorage, or a cookie using the same interface. The application state manager (collection) uses a specific sync object for the browser storage options.

Utils:

ajax-options, docCookies, debug, storage, shims, lib [checkType, duckTypeCheck, Channel (pub/sub), loadCss, formatCase, formatMoney]

We put any of our utilities into a library module.

Controller

A controller object should called within a route handler function, and may be responsible for getting relevant state (application models) to generate a page (layout), (also responsible for setting state when routes change). The controller passes dependent data (models/collections) and constructed view objects for a requested page to the layout manager. As a side-effect the use of controllers prevents the routes object from becoming bloated and tangled. A route should map to a controller which then kicks off the page view, keeping the route handling functions lean.

Facade

Vendor libraries and specific methods used in the framework are required in the facade, and referenced from the facade module in the views, models, collections, lib and other objects in the framework.

AMD – Asynchronous Module Definition

The the examples below show how we are using facade module as a dependency. RequireJS has various options for the syntax you can use to manage dependencies, below are a couple we use:

define(['facade','utils'], function (facade, utils) {

    var ModuleName,
        // References to objects nested in dependencies
        Backbone = facade.Backbone,
        $ = facade.$,
        _ = facade._,
        lib = utils.lib;

    ModuleName = DO SOMETHING HERE

    return ModuleName;
});

define(['require','facade','utils'], function (require) {

    var ModuleName,
        // Dependencies
        facade = require('facade'),
        utils = require('utils'),
        // References to objects nested in dependencies
        Backbone = facade.Backbone,
        $ = facade.$,
        _ = facade._,
        lib = utils.lib;

    ModuleName = DO SOMETHING HERE

    return ModuleName;
});

References:
* AMD spec
* RequireJS why AMD
* RequireJS AMD

Docs

View docs on the demo site, hosted on Heroku at:

http://vertebrae-framework.herokuapp.com/docs/
http://vertebrae-framework.herokuapp.com/models/docs/
http://vertebrae-framework.herokuapp.com/collections/docs/
http://vertebrae-framework.herokuapp.com/syncs/docs/
http://vertebrae-framework.herokuapp.com/utils/docs/
http://vertebrae-framework.herokuapp.com/views/docs/

Optimize and Build a Backbone.js JavaScript application with Require.JS using Packages

When a JavaScript application is too complex or large to build in a single file, grouping the application’s components into packages allows for script dependencies to download in parallel, and facilitates only loading packaged and other modular code as the site experience requires the specific set of dependencies.

Require.JS, the (JavaScript) module loading library, has an optimizer to build a JavaScript-based application and provides various options. A build profile is the recipe for your build, much like a build.xml file is used to build a project with ANT. The benefit of building with r.js not only results in speedy script loading with minified code, but also provides a way to package components of your application.

In a complex application, organizing code into packages is an attractive build strategy. The build profile in this article is based on an test application currently under development (files list below). The application framework is built with open source libraries. The main objective in this build profile is to optimize an application developed with Backbone.js using modular code, following the Asynchronous Module Definition (AMD) format. AMD and Require.JS provide the structure for writing modular code with dependencies. Backbone.js provides the code organization for developing models, views and collections and also interactions with a RESTful API.

Below is an outline of the application’s file organization, followed by the build profile to build modular (or packaged) layers a JavaScript driven application.

File organization

Assume the following directories and file organization, with app.build.js as the build profile (a sibling to both source and release directories). Note that the files in the list below named section can be any component of the application, e.g. header, login)

.-- app.build.js
|-- app-release
`-- app-src
    |-- collections
    |   |-- base.js
    |   |-- sections-segments.js
    |   `-- sections.js
    |-- docs
    |   `--docco.css
    |-- models
    |   |-- base.js
    |   |-- branding.js
    |   `-- section.js
    |-- packages
    |   |-- header
    |   |   |-- models
    |   |   |   |-- nav.js
    |   |   |   `-- link.js
    |   |   |-- templates
    |   |   |   |-- branding.js
    |   |   |   |-- nav.js
    |   |   |   `-- links.js
    |   |   `-- views
    |   |       |-- nav.js
    |   |       |-- branding.js
    |   |       `-- link.js
    |   |-- header.js
    |   `-- ... more packages here e.g. cart, checkout ...
    |-- syncs
    |   |-- rest
    |   |   `-- sections.js
    |   |-- factory.js
    |   `-- localstorage.js
    |-- test
    |   |-- fixtures
    |   |   `-- sections.json
    |   |-- header
    |   |   |-- index.html
    |   |   `-- spec.js
    |   |-- lib
    |   |   `-- Jasmine
    |   |-- models
    |   |-- utils
    |   |-- global-spec.js
    |-- utils
    |   |-- ajax.js
    |   |-- baselib.js
    |   |-- debug.js
    |   |-- localstorage.js
    |   `-- shims.js
    |-- vendor
    |-- |-- backbone-min.js
    |   |-- jquery-1.7.1.min.js
    |   |-- jquery.mobile-1.0.min.js
    |   |-- json2.js
    |   |-- modernizr-1.6.min.js
    |   |-- mustache.js
    |   |-- require.js
    |   |-- text.js
    |   `-- underscore.js
    |-- views
    |   |-- base.js
    |   `-- collection.js
    |-- application.js
    |-- collections.js
    |-- index.html
    |-- main.js
    |-- models.js
    |-- syncs.js
    |-- utils.js
    |-- vendor.js
    `-- views.js

Build profile to optimize modular dependencies with code organized in packages

The build profile can be organized to divide parallel downloads for various sections of the application.

This strategy demonstrated builds common or site-wide groups of (core) models, views, collections which are extended from a base.js constructor which extends the appropriate backbone method, e.g. Backbone.Model. The packages directory organizes code by section / responsibility, e.g. cart, checkout, etc. Notice that within the example header package the directory structure is similar to the app root directory file structure. A package (of modularized code) has dependencies from the common libraries in your application and also has specific code for the packages execution alone; other packages should not require another packages dependencies. A utils directory has shims, helpers, and common library code to support the application. A syncs directory to define persistence with your RESTful api and/or localStorage. The vendor libraries folder will not be built, there is no need to do so, you may decide to use a CDN (then set these paths to : empty:). And finally a test directory for Jasmine unit test specs, which may be ignored in the build as well if you choose.

Also notice the there are .js files named the same as the directories, these are the files listed in the paths. these are strategic to group sets of files to build, examples follow the build profile below.

({
    appDir: './app-src',
    baseUrl: './',
    dir: './app-build',
    optimize: 'uglify',
    paths: {
        // will not build 3rd party code, it's already built
        'text'         : 'vendor/text',
        'json2'        : 'vendor/json2.min',
        'modernizr'    : 'vendor/modernizr-1.6.min',
        'jquery'       : 'vendor/jquery-1.7.1',
        'jquerymobile' : 'vendor/jquery.mobile-1.0.min.js',
        'underscore'   : 'vendor/underscore',
        'mustache'     : 'vendor/mustache',
        'backbone'     : 'vendor/backbone',
        // files that define dependencies...
        // ignore vendor libraries, but need a group to do so
        'vendor'       : 'vendor',
        // application modules/packages these files define dependencies
        // and may also group modules into objects if needed to require
        // by groups rather than individual files
        'utils'        : 'utils',
        'models'       : 'models',
        'views'        : 'views',
        'collections'  : 'collections',
        // packages to build
        'header'       : 'packages/header'
        //... more packages
    },
    modules: [
        // Common libraries, Utilities, Syncs, Models, Views, Collections
        {
            name: 'utils',
            exclude: ['vendor']
        },
        {
            name: 'syncs',
            exclude: ['vendor', 'utils']
        },
        {
            name: 'models',
            exclude: ['vendor', 'utils', 'syncs']
        },
        {
            name: 'views',
            exclude: ['vendor', 'utils', 'syncs', 'models']
        },
        {
            name: 'collections',
            exclude: ['vendor', 'utils', 'syncs', 'models', 'views']
        },
        // Packages
        {
            name: 'header',
            exclude: ['vendor', 'utils', 'syncs', 'models', 'views', 'collections']
        }
        // ... and so much more ...
    ]
})

The above build profile is designed for balancing scalability and performance.

Examples of the grouped sets of code dependencies

The contents of the vendor.js which is not built into a package may use some no conflict calls as well.

// List of vendor libraries, e.g. jQuery, Underscore, Backbone, etc. <br>
// this module is used with the r.js optimizer tool during build <br>
// @see &lt;http://requirejs.org/docs/faq-optimization.html&gt;
define([ "jquery", "underscore", "backbone", "modernizr", "mustache" ], 
function ($,        _,            Backbone,   Modernizr,   Mustache) {
    // call no conflicts so if needed you can use multiple versions of $
    $.noConflict();
    _.noConflict();
    Backbone.noConflict();
});

For your application common library code.

// List of utility libraries,
define([ "utils/ajax", "utils/baselib", "utils/localstorage", "utils/debug", "utils/shims" ], 
function (ajax,         baselib,         localstorage,         debug) {
    // do nothing here, the shim only extend JavaScript when needed, e.g. Object.create
});

An example where you intend to use require the common models in another package file.

// List of models <br>
// models in this directory are intended for site-wide usage <br>
// grouping site-wide models in this module (object)
// optimizes the performance and keeps dependencies organized
// when the (build) optimizer is run.
define([ "models/branding", "models/section" ], 
function (Branding,          Section) {
    return {
        'Branding' : Branding,
        'Section'  : Section
    };
});

A quick note on code standards

Notice that in the above examples the parameters may begin with lower or upper case characters. The variable names uses in the parameters that begin with Uppercase are Constructors and the lowercase variable names are not, they may be instances created by a constructor, or perhaps an object or function that is not meant to used with new.

The convention recommended is to use Upper CamelCase for constructors and lower camelCase for others.

Common pitfall when organizing code in modules

Be careful not define circular dependencies. For example, in a common models package (models.js) dependencies are listed for the files in your models directory

define([ "models/branding", "models/section" ], function (branding, section)  
// ...  
return { "branding" : branding, "section", section }

Then when another packages requires a common model you can access the models objects returned from your common models.js file like so…

define([ "models", "utils" ], function (models, utils) {  
var branding = models.branding, debug = utils.debug;

Perhaps after using the model a few times you get into the habit of requiring “model”. Later you need add another common model with extends a model you already defined. So the pitfall begins, you add a new model inside your models directory and add a reference this same model in the model.js:

define([ "models/branding", "models/section", "models/section-b" ], function (branding, section)  
// ...  
return { "branding" : branding, "section", section, "section-b" : section-b }

However in your models/section-b.js file you define a dependency using the model.js which returns the models in an object like so…

define([ "models" ], function (models, utils) {  
var section = models.section;

Above is the mistake in models.js a dependency was added for models/section-b and in section-b a dependency is defined for model. The new models/section-b.js requires model and model.js requires models/section-b.js – a circular dependency. This should result in a load timeout error from require.js, but not tell you about the circular dependency.

For other common mistakes see the COMMON ERRORS page on the RequireJS site.

Executing the Build with r.js

If you intalled r.js with Node’s npm (package manager) like so…

> npm install requirejs

…you can execute the build on the command line:

> r.js -o app.build.js