Thursday, September 24, 2009

Javascript Design Patterns

JavaScript Design Patterns

Intent: to convey the meaning of the most commonly used javascript design patterns.
Level: Intermediate to Advanced

Who should read this?
Everyone who touches with javascript, no matter the seniority.


Naming conventions used below:
I like to use modified Hungarian Naming Convention for my variables in loosely-typed languages like PHP or JavaScript. This convention uses first letter of the variable to denote the variable type: sName - string, nCount - numeric, fCallback - function, bIsAwesome - boolean, mParam - mixed (more than one type can be expected).

JavaScript Library used in the examples:
For the following examples I use PrototypeJS library. Documentation is readily available on the web.



Namespace Pattern




Namespaces are used to confine multiple objects and methods to a scope to prevent their cross-interference and for organizational purposes. It is also a syntax sugar for those appreciating order and structure.

var MYAPP = {};
MYAPP.MyModule = function() { ... };

Singleton Pattern




Singleton pattern is used to create a singular object instance and restrict all further instantiations. It is useful when an object that has unique meaning in the scope of the application and no two (or more) objects of the kind can exist.

Example: User Agent, JavaScript Page Controller, Logged-in user, etc.

There are various ways to implement singleton in JavaScript:
  1. The Object Literal Singleton:
    var s = {
    property: 'value'
    };

    PROS: Ease, Compliant with Prototype's extension model
    CONS: No private encapsulation, Have to maintain comma-separation

  2. The Closure Singleton:

    var s = function {
    var private_property = 'value1';
    return {
    public_property: 'value2'
    }
    }();

    PROS: Encapsulation of private members
    CONS: Readability - must see the end parentheses of potentially long declaration to understand the pattern; not compliant with Prototype's extension model

  3. The Constructor Singleton:

    var s = new function {
    var private_property = 'value1';
    this.public_property = 'value2';
    };
    PROS: Encapsulation of private members, Readability - the 'new' keyword on the first line is easy to understand.
    CONS: Not compliant with Prototype's extension model.

After evaluation of pros and cons you can see that a case of extending an object to create a singleton is rare to non-existent, so the third pattern has virtually no cons.

Registerer Pattern




Registerer pattern is used to attach behavior to a group of similar controls on the page, who's markup was created on the server side. Note, it may also be conjoined with singleton pattern as the following example:

var Registerer = new Function() {
this.registerInputExample1 = function(sElementId) {
var oElement = $(sElementId); // get dom object
oElement.observe('click', function() {
// handle click occurred on oElement
});
oElement.observe('mouseover', function() {
// handle mouseover occurred on oElement
});
};

this.registerOutputExample2 = function(sElementId) {
var oElement = $(sElement); // get dom object
document.observe('registerer:somecustomevent', function() {
oElement.update('received an event'); // handle how custom event affects the element.
});
}
}



Output Example: Imagine a progress bar that must be updated while user rates images. When user rates 100 images using five-star rating widget (described further) The progress bar would show 100% completion.

Input Example: Imagine a five star rating widget that will fire event whenever one of the stars is clicked.

The two widgets are independent from one another and can be rendered multiple times across the page. Both will follow registerer pattern.

Output Example:

var ProgressBar = new Function() {
this.registerOutputBar = function(sElementId) {
var oElement = $(sElementId);
document.observe('progressbar:setpercent', function(oEvent) {
nParentWidth = oElement.parentNode.getWidth();
nWidth = Math.round(nParentWidth / 100 * oEvent.memo.percent);
oElement.setStyle({width: nWidth + 'px'});
});
}
};

Input Example:

var FiveStarRater = new Function() {
/**
* @param sElementId (String) Id of a clickable star
* @param nImageId (Number) Database id of image to rate
* @param nRating (Number) An integer 1-5 to signify rating
*/
this.registerInputStar = function(sElementId, nImageId, nRating) {
var oElement = $(sElementId);
oElement.observe('click', function() {
Server.updateImageRating(nImageId, nRating); // update image rating via ajax
});
}
};


This way, you can apply behaviors to numerous inputs and outputs on the page without having to duplicate functionality for each instance.

PROBLEM:
What if a single star rater is responsible for rating a stream of images? Do we need to register the star for each image? No. The Getter Parameter Pattern comes to the resque.


Getter Parameter Pattern





Consider the problem described in the Registerer Pattern section. The solution is very simple: supply a getter function as a parameter instead of a static image id. The modified code of a registerer will look like this:

var FiveStarRater = new Function() {
/**
* @param sElementId (String) Id of a clickable star
* @param nImageId (Number) Database id of image to rate
* @param nRating (Number) An integer 1-5 to signify rating
*/
this.registerInputStar = function(sElementId, fGetImageId, nRating) {
var oElement = $(sElementId);
oElement.observe('click', function() {
var nImageId = fGetImageId();
Server.updateImageRating(nImageId, nRating); // update image rating via ajax
});
}
};


The getter would be a zero parameter function that in the context of a situation knows how to retrieve a single value:

var nImageId = 234;
function getImageId() {
return nImageId;
}

// Register the elements
FiveStarRater.registerInputStar('star-one', getImageId, 1);
...
FiveStarRater.registerInputStar('star-five', getImageId, 5);


This makes our FiveStarRater context-independent: the knowledge of how to retrieve a context-dependent value (such as nImageId) is now delegated to the caller scope.

Thursday, May 7, 2009

Prototype.js decoupling - Antenna

I am referring to "shedding the vile old ways"
(as Prototype developers called it) in http://www.prototypejs.org/api/event/observe

This is called a "decoupling".

Pros of this approach:
  1. Can dynamically attach listeners to any event in DOM
  2. Can attach multiple listeners
Cons:
  1. Easy to lose track of what is going on in the system on each event
  2. Very hard to debug without proper tools

Imagine if all of your objects are communicating via events (not method calls),
how would you trace these communications?

Apparently, there is no tools yet to do exactly that. And prototype.js does not provide a good way to trace events. So I decided to solve it for myself and, hopefully, some of you may find this solution useful.

I called this solution "Antenna" just because I could not think of a better way to describe what it does in one word. It catches the events "in the air", much like a real antenna does with all sorts of electromagnetic events.

So here goes:

1. We are going to modify prototype.js to fire debug events whenever the following things happen:
  • Event listener is registered
  • Event listener is unregistered
  • Event is fired
  • Event is handled
Find line that defines "Object.extend(Event, (function() { .... " (line 3936 in version 1.6.0.3)
Find "return" clause for this object (line 4009)
Modify it as follows.

return {
observe: function(element, eventName, handler) {
element = $(element);
var name = getDOMEventName(eventName);
/*************** ANTENNA CODE *****************/
if (DEBUG && eventName.indexOf('antenna:') != 0) {

var callerScope = arguments.callee.caller.caller;

handler = handler.wrap( function (f, e) {
document.fire('antenna:event_capture',{
eventName: (e.eventName ? e.eventName : e.type),
element: e.findElement(),
handler: f,
callerScope: callerScope
});
return f(e);
});

document.fire('antenna:event_observe', {
eventName: eventName,
element: element,
handler: handler,
callerScope: callerScope
});
}
/**********************************************/

var wrapper = createWrapper(element, eventName, handler);
if (!wrapper) return element;

if (element.addEventListener) {
element.addEventListener(name, wrapper, false);
} else {
element.attachEvent("on" + name, wrapper);
}

return element;
},

stopObserving: function(element, eventName, handler) {
element = $(element);
var id = getEventID(element), name = getDOMEventName(eventName);


if (!handler && eventName) {
getWrappersForEventName(id, eventName).each(function(wrapper) {
element.stopObserving(eventName, wrapper.handler);
});
return element;

} else if (!eventName) {
Object.keys(getCacheForID(id)).each(function(eventName) {
element.stopObserving(eventName);
});
return element;
}

var wrapper = findWrapper(id, eventName, handler);
if (!wrapper) return element;

/*************** ANTENNA CODE *****************/
if (DEBUG && eventName.indexOf('antenna:') != 0) {
document.fire('antenna:event_unobserve', {
eventName: eventName,
element: element,
handler: handler,
callerScope: arguments.callee.caller.caller
});
}
/**********************************************/

if (element.removeEventListener) {
element.removeEventListener(name, wrapper, false);
} else {
element.detachEvent("on" + name, wrapper);
}

destroyWrapper(id, eventName, handler);

return element;
},

fire: function(element, eventName, memo) {
element = $(element);
if (element == document && document.createEvent && !element.dispatchEvent)
element = document.documentElement;

/*************** ANTENNA CODE *****************/
if (DEBUG && eventName.indexOf('antenna:') != 0) {
document.fire('antenna:event_fire', {
eventName: eventName,
element: element,
memo: memo,
callerScope: arguments.callee.caller.caller
});
}
/**********************************************/

var event;
if (document.createEvent) {
event = document.createEvent("HTMLEvents");
event.initEvent("dataavailable", true, true);
} else {
event = document.createEventObject();
event.eventType = "ondataavailable";
}

event.eventName = eventName;
event.memo = memo || { };

if (document.createEvent) {
element.dispatchEvent(event);
} else {
element.fireEvent(event.eventType, event);
}

return Event.extend(event);
}
};
})());


Note, that we must have DEBUG=true in order for this to work - we would not want this to happen all the time: too much overhead, right?

2. In order to catch these events all you need to do is to define 4 observers as follows:



document.observe('antenna:event_observe', function(e) { ... });
document.observe('antenna:event_unobserve', function(e) { ... });
document.observe('antenna:event_fire', function(e) { ... });
document.observe('antenna:event_capture', function(e) { ... });



I am currently working on a crossbrowser presentation layer for this solution, so more code to follow.

Friday, April 24, 2009

CSS guidelines

Purpose

As we all know, css is not the most straight-forward of the languages.
There are several dimensions of inheritance. There are different browsers to write for. There are hacks that defy logic. Etc.
Needless to say, css can get very messy and intangled. Not a surprize that it often treated like garbage, semi-intentionally swept under the couch.
Sometimes, it gets pushed into the most unpredictable places: html, js, php, java and sometimes even SQL. Good luck debugging this!

The purpose of this guideline is to introduce a set of clear and simple rules that, if followed, will:
    1. Void most of the headache.
    2. Bring beauty and structure to our CSS development cycle.
    3. Minimize size of CSS load.
    4. SIGNIFICANTLY ease the code maintenance.

1. Clear separation

Treat CSS like any other language - separate it into it's own tier, let it shine on it's own.

  1.  The only place for CSS is in ".css" file.
    1. No inline CSS - the only part of CSS that should appear inside HTML tags are class names and ids.
    2. No CSS on server side  - class names and ids are ok
    3. No CSS in JS - Javascript should NOT edit styles, but swap classes instead.
                  
      Example
      :
      [ CORRECT ]
      myButton.onMouseOver = function() { myButton.addClass('my-button-over'); }

      [ WRONG ]
      myButton.onMouseOver = function() { myButton.setStyle('background-color','red'); }
      The ONLY Exception:
      Sometimes width, height, top, left and
      display, etc. properties are changed internally by various animation
      routines in JS.  It is ok to define these
      css properties inline, but only these. If the new routine is being
      written - please try only using addClassName and removeClassName routines to swap css classes that are defined externally.

  2. DO NOT use ids (#id) notations in CSS, reserve them for JavaScript identification. Instead use CSS classes (.myclass).

  3. If JS needs to use CSS classes, they must be prepended with "js-" notation, and NEVER be used to apply styles with CSS to
    avoid introducing bugs in JavaScript when CSS selector is changed.

2. File system structure

  1. Take advantage of minimization - create separate CSS file for each specific purpose.
    It is easier to read, debug and remove. Writing short, consise CSS files will help making the code self-explanatory.

  2. Divide files into two groups:
    a. Generic - Styles that are used to skin reusable widgets or modules. Styles that MAY be applied to a similar module elsewhere.
    b. Specific - Styles responcible for particular page or a module. Styles that will NOT be used anywhere else but there.

    Example:
    Consider a dialog widget that is used in multiple places across the site.
                
    Our generic skin css file will be called module.dialog.skin1.css.
    This file will abstract the generic looks of the dialog widget regardless of the markup that goes inside it. The module.dialog.skin1.css does not,
    and should not know about it's possible contents. This file is
    similar to a base class in the inheritance paradigm of any programming
    language.

    For a specific dialog that will, say, display a user profile information, the specific css file will be called module.dialog.profilepopup.css.
    This file will contain styling properties of the profile
    displayed within the dialog. It may or may not override styles
    inherited from module.dialog.skin1.css.
    The module.dialog.profilepopup.css can be compared to an OOP subclass of module.dialog.skin1.css.

    This
    way, we will have only one file that defines the generic looks of the
    dialog, and multiple specific files for any specific dialog. And no
    extra CSS.
      
3. File naming conventions

  1. Use all-lowercase filenames.
  2. Use underscores (not dashes) in filenames
  3. Use periods "." to separate hierarchial namespaces within the filename:
    <module_type>.<module_name>.<module_specific>.css
    <module_type> : [
        common,  // contains element styles common across ALL pages (reset, fonts, grids, forms, etc.)
        page,       // a single html page, including BODY tag
        partial,     // a single partial page loaded with ajax
        module    // a widget like dialog, button, pager, grid, custom form element, etc.
    ]
    <module_name>  : arbitrary   // a name of a page, layout
    or widget. Corresponds to js or php widget file name.
    <module_specific> : arbitrary  // a
    specific instance of <module_name>. Corresponds to the page name of occurrence.
    This will spare you firebugging, greping and searching your
    entire codebase for styles that correspond to your module or widget.
    You will know EXACTLY the file you need.            

4. CSS Selectors

  1. Use dashes instead of underscores in your selectors.  This is just a widely accepted web standard.
  2. Add a CSS class to the outmost HTML tag of your module/widget/layout that has the SAME NAME as <module_name> or <module_name>-<module_specific> your CSS file
    (replacing underscores with dashes, of course)
  3. Use short but descriptive names for classes in any other inner tag. (Further referred to as "inner classes")
  4. ALWAYS define inner classes through the class of the outmost tag.

    Examp
    le:
    Inside of our module.dialog.skin1.css. file we need to skin a title bar and four corners:
    [ CORRECT ]
    .dialog-skin1 {
        /* define css properties to inherit within the skin: font, background, etc */
        background-color: #CCC;
     }
         .dialog-skin1 .title {
             background-color: #666;
             color: #FFF;
             font-weight: bold;
         }
         .dialog-skin1 .corner-n {
             width: 15px;
             height: 15px;
             background: url(images/dialog_corner_north.gif) no-repeat;
         }
    Prepending style selector with the widget/module/layout
    identifier will make sure that these styles will NEVER interfere with
    any other style outside the module.
    [ WRONG ]
    .dialog-skin1 {
        background-color: #CCC;
    }
        .title {
            background-color: #666;
            color: #FFF;
            font-weight: bold;
        }
        .corner-n {
            width: 15px;
            height: 15px;
            background: url(images/dialog_corner_north.gif) no-repeat;
        }
    There is a great chance that ".title" or ".corner-n" may
    be defined elsewhere in the application and you will end up overriding
    it.
            
         
5. Indentation and grouping of styles

  1. Group styles inside the CSS file just the way they appear in HTML - Outer first, inner after
  2. [Optional] You may want to indent styles just as you would indent HTML tags they appear in - Outer less indented, inner more indented

Example:

selector1 {
}
    selector1 selector2 {
    }
        selector1 selector2 selector3 {
        }
    selector1 selector4 {
    }
    selector1 selector5 {
    }
            
          This way you will ALWAYS know where in the file to look for a specific style.
          You will clearly see the style inheritance structure.
          You can at-a-glance identify how your CSS applies to HTML (vertical readability)

6. Grids

  1. Use grid classes to define positioning of your HTML elements. (Documentation coming soon)

7. Appendix. Crossbrowser CSS Hacks

Browser specific hacks (order is important):

.myClass {
    width: 100px;    /* Affects: FF, Safari, Opera, IE5, IE6, IE7, Targeting: FF, Safari, Opera */
    .width: 102px;   /* Affects: IE5, IE6, IE7, Targeting: IE7 */
    _width: 101px;  /* Affects: IE6, IE5, Targeting: IE6, IE5 */
}

IE8 Support
The best (google and facebook approved) solution is to make IE8 behave like IE7 by adding the following meta tag:

<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />

One less IE browser to code for :)