Thursday, June 28, 2012

Code of Three Kinds

Client-side javascript can be generally be put into three categories:

  • Frameworks  ("raw material")
  • Plugins          ("wheel")
  • App code      ("ford taurus")
The first two are what i often call "libraries".  They are reusable features and functions meant to be shared across projects.  The third is the code that makes your app what it is and is typically useless to others except perhaps as an example or template of how to use the frameworks and plugins.  Even then, it is often so busy and complex that it rarely works as a clear example.

Frameworks

These are the libraries we are required to use (See Use jQuery).  Probably single most distinguishing feature of these libraries is that your webapps can (and should!) pull them off of a CDN.  These have become part of the plumbing of the internet, and the more people who use a CDN for them, the better.  It speeds things up for everyone involved.

Another common reality about frameworks is that they usually offer far more features than any one app will use.  This is to be expected and accepted.  Customizing them to only download what you need defeats the point of common CDNs and all the caching available that way.  Don't worry about the extra code, it's not worth the worry.

Again, DON'T CUSTOMIZE and DON'T REINVENT, at least never a core feature.  These are not wheels, these have become the raw materials of a webapp.  Reinventing or rearranging them one molecule at a time is a waste of time, unless perhaps, you are doing it on behalf of a major tech company that plans to market and fund it all for you.

Plugins

These are what i am calling "wheels".  They are the shims, polyfills, widgets, extensions and other features that are used to build applications.  They are not specific to a particular application but are not likely to be needed or wanted by a majority of applications out there.  These cannot be found on CDNs but are rather all over GitHub and Google Code and hundreds of personal blogs and websites scattered around the internet.  For each polyfill or widget i might want, there are a dozen competing implementations, all of which do either too much or too little or both.  I usually think i could write them better myself and odds are 70/30 that i'm right.  This isn't just arrogance, the reality is simply that they wrote it for a particular app or audience that doesn't exactly line up with me and my app.  Personally, even when i'm wrong about this, i often learn some good things in the process of trying to write my own version or port one over to jQuery.

I will probably freely switch between calling these "plugins", "wheels" and just "js libs" to keep things confusing.

App Code

This is the code that uses the plugins and framework(s) together in a unique way to make your webapp (or just web page) what it is.  Unless your next app is merely a variation on your previous one, then you can expect that none of this will be reused.  And, in general, it is only worth talking about in the context of ways to reduce and simplify it via better plugins and frameworks.  Just as i would never pollute a framework with plugin customizations, i strive to keep my plugins free of app code.


Make sense?

Tuesday, June 26, 2012

Example Wheel


Here's an example of a typical wheel i write these days:

/**
 * Copyright (c) 2012, Nathan Bubna
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * @version 0.1
 * @requires jQuery 1.7+
 * @optional roll.js
 */
;(function($, window, document) {

    var wheels = window.wheels = {
        init: function() {
            $('vehicle').each(function() {
                var vehicle = $(this);
                vehicle.trigger('addWheels', vehicle.attr('wheels'));
            });
        },
        add: function(e, count) {
            var vehicle = $(e.target).closest('vehicle');
            for (var i=0; i<count; i++) {
                wheels.attachOneTo(vehicle);
            }
            vehicle.trigger('addedWheels', [count]);
            if (!!vehicle.attr('roll')) {
                vehicle.trigger('roll');
            }
        },
        attachOneTo: function(vehicle) {
            // magic happens here
        }
    };

    $(document)
    .on('addWheels', wheels.add)
    .ready(wheels.init);

})(jQuery, window, document);

This is designed to be interacted with via events and DOM configuration.  It uses jQuery to fire and handle events as well as discover <vehicle> elements in the document.  It adds wheels according to the number specified in that element's "wheels" attribute:
<vehicle wheels="3" roll="true">some tricycle markup here</vehicle>
 It uses events to communicate what it is doing for others to listen and attach behaviors.  It even optionally fires a "roll" event once all the wheels are attached that is meant for a possibly non-existant "roll.js" to handle.

If a <vehicle> is added later, the user can give it wheels by simply firing an "addWheels" event on it.

The user of this "wheels.js" code can enjoy its benefits without ever directly accessing the window.wheels object, making his code both simpler and more robust.  But, if he wants to, he can use, override, or wrap any of the functions in wheels, as well as use them directly during a console debugging session.

Rules for Wheel Reinvention

Here's my rules for reinventing "wheels" for client webapp development:


Here's the explanations:

Never Make Users Call .init()

...because it's utterly redundant.  This isn't server-side java where you have no idea what namespaces and libraries have been pulled in by your Maven build and everything needs to be tightly managed.  This is the browser, baby!  Client coders frequently bend over backward to avoid including things they aren't using.  If your code finds itself loaded, consider that declaration of intent to be used!  Wait for window load or document ready and go for it!  Don't be shy about it, presence === permission to function!

Declarative, Intuitive DOM Configuration

This is really a logical extension of the first rule.  It does little good to avoid forcing init() calls if your code can't/won't know what to do without specific user instructions.  Remember, we're talking about "wheel" code here, not application code.  These things are meant to be generic and reusable widgets, components, and behaviors.  You shouldn't be writing a new fancy address mapping widget that makes the user call: AddressMapper.mapOnBlur($('input.address')).  Gag me!

Just extend HTML.  Seriously.  Browsers are wildly, amazingly robust markup processors. Running HTML through some fancy spec validation is generally a waste of time.  This is why HTML5 has officially become a moving target and those arcane doctype declarations are going the way of the dinosaur.  Just declare <!doctype html> and stop worrying about having 100% valid HTML.  The platform is developing too rapidly and inconsistently for anyone to be strict about following HTML specifications now.  Don't use invalid syntax, but don't be shy about creating your own elements, attributes and attribute values.

So, instead of exposing AddressMapper.mapOnBlur(), have addressmapper.js go looking for <input type="address" map-on="blur">.  Be creative, literally.  All browsers can handle this.  Both the code for your wheel and your user's app will be cleaner and clearer because of this.  Instead of three files to be coordinated (the wheel, the user's js and the DOM), there are just two.  The brain power and bloat saved here is well worth it.

Use jQuery

MooTools, YUI, Dojo and the like are all fine projects.  But jQuery is both everywhere and much more than good enough.  If you want to port your wheels to a different framework later, fine, but start with jQuery.  If your boss or customer has already committed to YUI, fine, but port to jQuery at your first opportunity.

But beyond all debates about which framework is best, you should not write your own code to do things that these frameworks all do: event binding, DOM traversal, DOM manipulation, etc.  Don't even write your own feature detection.  Use Modernizr.  The real point of this rule is that there are some wheels that you really, really, really should not reinvent.  The window for reinventing jQuery or Modernizr has long closed.  You cannot wisely develop a modern web app without using an established javascript framework. Deal with it.

Use Custom Events

This follows right with the custom attributes and elements business.  If your user's app needs to know when your code has done something, you should trigger a custom event, either on a related DOM element or else on the document.  You should NOT force them to do:
AddressMapper.doneMapping(function(map){ app.canUse(map); }); //BAD!
This violates the "Use jQuery" rule, since this is a textbook perfect case for custom events, a wheel that jQuery already adeptly "invented" for you.  Your AddressMapper should be doing $mapEl.trigger('doneMapping');  Then the user does:
$(document).on('doneMapping', function(e){ app.canUse(e.target); }); //GOOD!
This doesn't save the user keystrokes, but it saves them brain cycles.  They know jQuery's event system and don't have to learn one you reinvented.

On a lesser note, this also makes it easy for other wheels to interact with your wheel without creating a hard dependency.  And extensions like that are where the "like crazy" part comes in.  If it is useful for users or extenders to know when AddressMapper is 'doneMapping', it is probably handy for them to have a 'startMapping' event too.  Then they can configure something like a custom <loading> element to watch for both:
<loading on="startMapping" off="doneMapping>Creating Your Map...</loading>
             <!-- Heh. Now i want to make a loading.js that supports this... -->
The key thing here is this:  Events drive web app behavior.  If you are going to be adding behaviors to a web app, i strongly recommend that you use events to drive them too.

Expose Yourself

Make your functions accessible.  If you are a god among coders and your wheel is thoroughly bug-free, your "internal" functions may still be useful for extenders, coders trying to reinvent your wheel, or maybe even power users who want to add a little spice by wrapping a function.  And if you are human, being able to call internal functions sure helps other people find your bugs.  Heck, it may help *you* debug your code; who knows if anyone else will ever use it. ;)

Write your code to be readable, reusable, and recycle-able; leave the reduction to the minifiers and compressors.

For each wheel you write, you should have a single, descriptively-named object (or function) to which you attach all your functions.  This ideally matches the first portion of the file name.  Do NOT expose all of your functions directly in the global namespace.  Don't share all your variables under your primary object/function either; restrict yourself to the important ones.  You can expose this primary object/function as an attribute of the window, jQuery, or some other variable.  The only goal is to make it accessible to extenders and the console's command line.

Reinventing The Wheel


I used to live in the server-side Java world.  Now i live and breathe client-side javascript.

My primary observation about both so far is that in an environment of fast developing platforms, the frameworks and tools never quite seem to keep up or otherwise meet the needs of even half the developers out there.  When faced with a shortcoming, rather than wait for fixes to be incorporated, there are always plenty of coders who would just rather "do it better."  So they reinvent the wheel, sometimes for better, sometimes not.

I increasingly find myself becoming part of the "problem." I see holes in frameworks and browser APIs and get ambitious ideas about filling them.  Or i go look for other people who filled them already only to find that they don't conform to the ideology about client web apps that has formed in my head over the last five years.

So i'm starting this blog to pontificate about the ideology that has formed thus far, and honestly, to codify it for the benefit of my own brain.  As a verbal processor, the act of setting these ideals outside my brain is of great personal benefit.  These ideals currently take the form of "rules" i intend to promote to the client-side javascript developers of the world.  I will probably also use this to share "wheels" that i have personally bothered to (re)invent for public consumption.  And yeah, there may be the occasional useless rant about something in the client javascript world that currently irritates me.

The rules to follow are like any other code i write: subject to continual revision and occasional reinvention.  Thoughtful feedback is likely to drive that process.  Kneejerk responses and angry ego-bashing are liking to inhibit it.  But you knew that already, right?

Thanks for your patience.  Especially with the "wheel" metaphor, as it is likely to get tired.  Bah-dum- ching!