Tuesday, June 26, 2012

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.