Matt Snider JavaScript Resource

Understanding JavaScript and Frameworks

Friday, December 7, 2007

JsonObject Model Prototype

I have been doing a lot of server-side work in PHP and JAVA this past month. One of the design practices used by server-side webapp languages is the MVC (model-view-controller) framework. An MVC makes writing extensible and scalable code, much easier. In JavaScript I have seen a couple of attempts at a similar MVC framework, but most of the time it is overkill or poorly implemented. Shutterfly.com on the other-hand, has a lot of JavaScript and a fairly solid MVC implementation.

I have been toying with JavaScript MVC concepts all week and want to share what I developed to improve working with JSON objects. Keep in mind this is a work in progress and I am open to any suggestions:

Example 1: Sample Code

/** * The JsonObject class manages … * * @namespace Core.Model * @class JsonObject * @dependencies core */ Core.Model.JsonObject = function(data) { this.update(data); }; Core.Model.JsonObject.prototype = { JKEYS: [], MODEL: ‘JsonObject’, update: function(data) { var obj = {}, that = this, i = 0; // iterate through the JSON object keys for (var key in data) { var o = data[key]; if (! isType(o, ‘function’)) { // this key is not in the model if (-1 < that.JKEYS.indexOf(key)) { throw('Invalid key (' + key + ') passed into ' + that.MODEL); } obj[key] = o; that[isType(o, 'boolean')? key: 'get' + ckey] = function() {return obj[key];}; that['set' + ckey] = function(o) {return obj[key] = o;}; i += 1; } }; // the keys in the object do not match the keys in the model if (i != JKEYS.length) { throw('Invalid number of keys passed into ' + that.MODEL); } } });

This method takes a JSON object and applies getter/setter methods to that object. Each time update is called, it validates against JSKEYS, which should be an array of the expected keys. We also remove any functions that might have been attached to the ‘data’ object. You may also note that I have a special check for boolean values that does not attach the ‘get’ preface to the getter method. This is because I always preface boolean values with ‘is’ or ‘has’ (or something like that), and I want that preserved in the Function attached to the JsonObject.

Here is an example of how you might use it:

Example 2: Using JsonObject

// a JSON object, most likely returned from the server, representing a car var carJson = { make: ‘ford’, model: ‘contour’, year: ‘1996′, price: ‘$2,000′, isNew: false }; // use the extend (Object Extension) method to create a CarObject, with the appropriate JKEYS set Core.extend(Core.Model.CarObject, Core.Model.JsonObject, { JKEYS: [’make’, ‘model’, ‘year’ , ‘price’], MODEL: ‘CarObject’ }); var car = new Core.Model.CarObject(carJson);

Example 2 will create a CarObject (extended JsonObject), with the following members:

Example 3: CarObject Structure

CarObject = { // constants defined by the prototype object JKEYS: [’make’, ‘model’, ‘year’ , ‘price’], MODEL: ‘CarObject’, getMake(), setMake(), getModel(), setModel(), getYear(), setYear(), getPrice(), setPrice(), isNew(), setIsNew() }

I find it much easier to work with the JsonObjects once the getters and setters have been created, because you have defined what should be there. Plus, with the validation you can be sure that you received the correct JSON Object. Some possible improvements would be to support nested JsonObjects and JsonArrays, and setter type validation (although, this might be overkill).

There are really only 3 types of models that you might need to use in JavaScript: JSON Objects/Arrays, Query strings, and XML. Creating the Models is a good first step in building an MVC as you will need to pass these between the controller and the view, to update the view and send messages to the server. Next week, I will look into these other model types and improve this one a bit.

posted by Matt Snider at 3:35 pm  

Sunday, November 11, 2007

Event Package Final

In case you have not been following the discussion so far, here are links to the other articles:

Event.js

Event Package on 11/07/07
Event Package Part 2 on 10/24//07
Event Package Part 1 on 10/18/07
X-Browser Event Handling on 09/28/07

Today, I will be walking through the Event.js, discussing the parts not previously covered. Let’s start by looking at the caching function:

Example 1: Cache Event Function

var cacheEvent = function(el, eType, fn, opt) { // ensure that opt in an object var o = opt || {}; o.capture = (o.capture || false); // wrap the function so we can control the scope and return the optional second object var wrapFn = function(e) { return fn.call(o.scope || window, e, o.data); }; // cache the listener so we can try to automatically unload var evt = [el, eType, fn, wrapFn, o]; ecache[ecache.length] = evt; return evt; };

This Function will be called any time an event is added using the ‘add’ method and accepts the same parameters as the ‘add’ method. The first part of the Function, ensures that the options exist and that the capture value is set. Capture is a required for the actual attachment of the event, but since it is false 99% of the time, we are making it optional. The ‘wrapFn’ internal Function is used to set the scope of, and pass through the data option into, the callback Function. We then create an array of the important values for the cache and push it onto the stack.

We return the cache object so that the ‘add’ Function can use ‘wrapFn’ as its callback and apply the value of capture. This creates an interesting problem when removing events using the old ‘remove’ Function, as we have attached the the ‘wrapFn’ as the callback, but code outside of this package, does not have access to the wrapping Function. That is why we use a private ‘remove’ Function, so that we can locate ‘wrapFn’ from the cache. The public ‘remove’ Function does just that:

Example 2: Public Remove Function

remove: function(el, eType, fn, fl) { var rs = that.getListeners(el, eType); Core.batch(rs, function(r) { if ((r.fn) == (fn)) { update(r); } }); },

The public remove Function first gets all the cached objects for a given element that match the event type, using the ‘getListeners’ method (discussed later). We then iterate on the results and compare the paremeter ‘fn’ with the cached non-wrapped Function. When this is true, we actually remove the event, otherwise. The update Function handles this removal:

Example 3: Removing Events and Cached Elements

var update = function(r) { remove(r.el, r.type, r.wfn, r.opt.capture); var arr = []; if (0 < r.index) { arr = ecache.slice(0, r.index); } arr.concat(ecache.slice(r.index + 1)); ecache = arr; };

First we actually remove the event with the internal ‘remove’ Fucntion (notice that the wrapped Function is used). The rest of the method slices the cached event object that we are removing out of the stack.

In order to quickly and effectively find the events attached on an Element, both internally and externally, we created the public ‘getListeners’ method. I model the data architecture after a similar Function in YUI, as I could not think of a more elegant way to return matching cache objects.

Example 4: getListeners Function

getListeners: function(el, eType) { var results = []; Core.batch(ecache, function(l, i) { if (l && l[EL] === el && (! eType || eType === l[TYPE]) ) { results.push({ el: l[EL], type: l[TYPE], fn: l[FN], wfn: l[WFN], opt: l[OPT], index: i }); } }); return results; },

We simply iterate through the cache and create special result objects each time the element, and event type when provided, match that of a cached event object. Because the results objects can be passed externally, where the position name variables (’EL’, ‘FN’, ‘TYPE’, ‘WFN’, and ‘OPT’) do not exist, we instead create objects where each member is the lowercase of the constant and is set to the comparable value. The index is added, so the event object can later be removed from the cache.

I added two other Functions that I found to be extremely useful. One is the ‘removeEvents’ Function that removes all events from a given element by leveraging the existing caching infrastructure from event removal. And a ‘removeAll’ Function that removes all events ever assigned using the ‘add’ method. This method is attached to the window unload event and helps prevent memory leaks (especially in IE6) where the memory reference to the dom element that triggers the event is not cleared, because the event is still pointing to the dom element.

Lastly, I removed as spaces from the Event name constants:

Example 5: Event Name Constants

BLUR: ‘blur’, CLICK: ‘click’, DOUBLECLICK: ‘dblclick’, KEYDOWN: ‘keydown’, KEYPRESS: ‘keypress’, KEYUP: ‘keyup’, LOAD: ‘load’, MOUSEDOWN: ‘mousedown’, MOUSEMOVE: ‘mousemove’, MOUSEOVER: ‘mouseover’, MOUSEOUT: ‘mouseout’, MOUSEUP: ‘mouseup’, SUBMIT: ’submit’, UNLOAD: ‘unload’,

I did this because there was a conflict between several events, such as the KEYUP event and the KEY_UP that actual keyboard key ‘up arrow’.

Many of the Function names have become shorter as well. I simply found that I knew when using the event package that ‘add’ meant that I was adding a listener, so I did not need the names to be longer.

Please let me know any feedback you have, especially if you have suggestions for improvement.

posted by Matt Snider at 4:46 am  

Wednesday, October 24, 2007

Event Package Part 2

In my last article (Event Package Part 1), I covered some of the necessary parts of an event package and mentioned some “optional” advanced features. I originally planned to discuss them all in this article, but there is too much material to cover, so I will be breaking it up into two articles. Some of the advanced features (such as, removing all listeners and scope control) require an internal caching mechanism and I will be covering this system in Friday’s article. Todays article will cover the other features, including two more additions to the Core package.

These new core methods are “getBody” and “getScrollOffset”:

/** * X-browser method to find the BODY tag; lazy definition to improve repeat performance * * @method getBody * @return {HTMLElement} the BODY tag * @static */ var getBody = function() { var bd = window.document.body || window.document.childNodes[0].childNodes[1] || window.document.getElementsByTagName(”body”)[0] || window.document; Mint.Client.getBody = function() {return bd;} return Mint.Client.getBody(); };

This method tries 3 ways to get the BODY tag, then defaults to the document. The “bd” variable will then hold the reference to the BODY tag. We then use the lazy initialization technique to create a closure that will improve the performance of any subsequent requests to this method.

/** * X-browser method to find the scroll offset; lazy definition to improve repeat performance * * @method getScrollOffset * @return {Object} the scroll offset object {x, y} * @static */ var getScrollOffset = function() { var de = document.documentElement, db = getBody(); if (de && (de.scrollTop || de.scrollLeft)) { Mint.Client.getScrollOffset = function() {return {x: dd.scrollLeft, y: dd.scrollTop};} } else if (db) { Mint.Client.getScrollOffset = function() {return {x: db.scrollLeft, y: db.scrollTop};} } else { Mint.Client.getScrollOffset = function() {return {x: 0, y: 0};} } return Mint.Client.getScrollOffset(); };

This method returns the scrolled offset of the viewport, in both the x and y directions. We first create the “de” and “db” variables, then evaluate which of 3 methods to lazily define “getScrollOffset” as. The first if statement is true for most compliant browser, the second is true for IE, and the 3rd is a failsafe for older browsers (this way, calling this method won’t throw an exception). Again, by using lazy function definition pattern, we will improve the runtime of subsequent requests.

I also added the following methods as private Functions of the Event package:

/** * Some browsers (Safari) will sometimes return a text node inside the targeted element. This normalizes the return value for getEl and getRelatedEl. * * @method resolveTextNode * @param node {HTMLElement} the node to resolve * @return {HTMLElement} the normized node * @private */ var resolveTextNode = function(node) { return (node && document.TEXT_NODE == node.nodeType)? node.parentNode: node }; /** * Some browsers (IE) do not actually return the event, but store it in a global object. We will try to normalize this in our event callback, but just in case, we resolve this each time we expect an event. * * @method resolveEvent * @param e {Event} The trigger event * @return {Event} the IE event * @private */ var resolveEvent = function(e) { return e || window.event; }

“reseolveTextNode” was modeled after YUI’s method, but is more compact and leverages the ‘TEXT_NODE’ constant. Sometime Safari or Safari-like browsers return the textnode inside of the target element, instead of the target element. This method normalizes these situation and returns the appropriate target element. The “resolveEvent” is used to normalize an Event anytime it is expected as a paremeter. IE attaches the event to a global constant instead of returning it to the Event handler. And although, I plan on passing an already normalized event back to the callback Function, it is possible that another event system was used to get the Event without normalizing it.

Now armed with these methods, we can see how to implement the following Event package methods:

  • getRelatedEl
  • getTime
  • getX
  • getY
  • getXY
/** * X-browser method to return the event related target; tries to find relatedTarget, then toElement (mouseover), then fromElement (mouseout). * Some browsers (Safari) sometimes provide text nodes, so we automatically resolve them. * * @method getRelatedEl * @param e {Event} the trigger event * @return {HTMLElement} the related target element * @static */ getRelatedEl: function(e) { e = resolveEvent(e); return resolveTextNode(e.relatedTarget || e.toElement || e.fromElement); }, /** * Returns the time of the event or set the event to the current time. * * @method getTime * @param e {Event} the trigger event * @return {Date} the time of the event * @static */ getTime: function(e) { e = resolveEvent(e); if (! e.time) { var t = new Date().getTime(); try { e.time = t; } catch(ex) { return t; } } return e.time; }, /** * X-browser method to return the x position of the event * * @method getX * @param e {Event} The trigger event * @return {int} the x coordinate of the event * @static */ getX: function(e) { e = resolveEvent(e); var x = e.pageX; if (! x && 0 !== x) { x = e.clientX || 0; if ( Core.Client.isIE() ) { x += Core.Client.getScrollOffset().x; } } return x; }, /** * X-browser method to return the y position of the event * * @method getY * @param e {Event} The trigger event * @return {int} the y coordinate of the event * @static */ getY: function(e) { e = resolveEvent(e); var y = e.pageY; if (! y && 0 !== y) { y = e.clientY || 0; if ( Core.Client.isIE() ) { y += Core.Client.getScrollOffset().y; } } return y; }, /** * Returns the pageX and pageY properties as an indexed array. * * @method getXY * @param e {Event} The trigger event * @return {Object} the coordinate object {x: xpos, y: ypos} * @static */ getXY: function(e) { e = resolveEvent(e); return {x: self.getX(e), y: this.getY(e)}; },

The JAVADOC comments describing the Functions do a pretty good job of explaining their use. Let me know if you have any questions. On Friday I will be uploading the entire Event package JavaScript, which will probably help clear up confusion.

posted by Matt Snider at 1:52 pm  

Thursday, October 18, 2007

Event Package Part 1

For this article, I will be using the “attachFunc” and “detachFunc” methods outlined in my X-Browser Event Handling article.

Again, sorry about this late post. I am still working 14 hour days this week.

Today I will be introducing a simple event handler package. First, we will discuss the methods you might want to have in an Event Package and whether they are: required, frequently used, or nice to have.

Necessary Functions:

  • Event.add - same as “attachFunc”
  • Event.remove - same as “detachFunc”

Those two functions will allow you to add and remove any event from any element. This is all you need for event management, however, you may frequently wish to know about or change an event (such as triggering element or key code, and preventing default behavior). Here is a list of methods and variables that I frequently use:

Frequently Used Functions and Static Variables:

  • Hash map of key codes - such as {KEY_BACKSPACE: 8}
  • Hash map of event names - such as {CLICK: ‘click’}
  • getEl - returns the element that owns this event
  • getCode - returns the key code of the event
  • stopDefault - stops the capture phase of the event
  • stopProgation - stops the bubbling phase of an event
  • stop - stops both the bubbling and capture phase of an event

Having static key codes is helpful, as you won’t need to look them up everytime you are listening for an “enter” key. I map the event Names because if you try to attach a ‘clck’ event, instead of a ‘click’ event, the browser does not throw an exception. However, your code is not going to work, but if you instead mispell your variable name, then the browser will throw an exception. Here are the function mentioned so far:

var Event = function() { // Private namespace var self = null; // Public namespace return { /** * Event name constants */ BLUR: ‘blur’, CLICK: ‘click’, DOUBLE_CLICK: ‘dblclick’, KEY_DOWN: ‘keydown’, KEY_PRESS: ‘keypress’, KEY_UP: ‘keyup’, LOAD: ‘load’, MOUSE_DOWN: ‘mousedown’, MOUSE_MOVE: ‘mousemove’, MOUSE_OVER: ‘mouseover’, MOUSE_OUT: ‘mouseout’, MOUSE_UP: ‘mouseup’, SUBMIT: ’submit’, UNLOAD: ‘unload’, /** * Common event keycodes */ KEY_BACKSPACE: 8, KEY_TAB: 9, KEY_RETURN: 13, KEY_ESC: 27, KEY_LEFT: 37, KEY_UP: 38, KEY_RIGHT: 39, KEY_DOWN: 40, KEY_DELETE: 46, KEY_HOME: 36, KEY_END: 35, KEY_PAGEUP: 33, KEY_PAGEDOWN: 34, KEY_SPACE: 32, /** * Attach a listener from an element; uses lazy loading to setup the add and remove functions * * @method add * @param el {HTMLElement} the element to bind the handler to * @param sType {string} the type of event handler * @param fn {function} the callback to invoke * @param fl {boolean} OPTIONAL: capture or bubble phase * @static */ add: function(el, eType, fn, capture) { self = Event; if (window.addEventListener) { self.add = function(el, eType, fn, capture) { el.addEventListener(eType, fn, capture); }; self.remove = function (el, eType, fn, capture) { el.removeEventListener(eType, fn, capture); }; } else if (window.attachEvent) { self.add = function(el, eType, fn, capture) { el.attachEvent(’on’ + eType, fn, capture); }; self.remove = function (el, eType, fn, capture) { el.detachEvent(’on’ + eType, fn, capture); }; } else { self.add = function() {}; } }, /** * Extend the Event Object with x-browser retrieval of keycode from event * * @method getCode * @param e {event} The triggering event * @static */ getCode: function(e) { var code = null; if (! e) {e = window.event;} if (e.keyCode) {code = e.keyCode;} else if (e.which) {code = e.which;} return code; }, /** * X-browser event target retrieval * * @param e {event} The triggering event * @param tagName {string} OPTIONAL: HTML tag name * @param className {string} OPTIONAL: HTML tag attribute class name * @static */ getEl: function(e, tagName, className) { if (! e) {e = window.event;} var targ = e.target || e.srcElement; if ($D.TEXT_NODE == targ.nodeType) {targ = targ.parentNode;} // defeat Safari bug if (tagName || className) {targ = Dom.getParent(targ, tagName, className);} return targ; }, /** * Remove a listener from an element * * @method remove * @param el {HTMLElement} the element to bind the handler to * @param sType {string} the type of event handler * @param fn {function} the callback to invoke * @param fl {boolean} OPTIONAL: capture or bubble phase * @static */ remove: function(el, eType, func, fl) {}, /** * Convenience method for stopPropagation + stopDefault * * @method stop * @param e {event} The triggering event * @static */ stop: function(e) { self.stopPropagation(e); self.preventDefault(e); }, /** * Prevents the default behavior of the event * * @method stopDefault * @param e {event} The triggering event * @static */ stopDefault: function(e) { if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; } }, /** * Stops event propagation * * @method stopPropagation * @param e {event} The triggering event * @static */ stopPropagation: function(e) { if (e.stopPropagation) { e.stopPropagation(); } else { e.cancelBubble = true; } } }; }();

There is an endless amount of other ’stuff’ that could be added to an Event package. However, most of it is not necessary. Here are a few that I use often enough to include in my package:

Bells and Whistles:

  • getEvents - returns a collection of events on an element
  • getRelatedElem - some mouse events have a related element other than the triggering element
  • getTime - the time the event was triggered
  • getX - the x position of the event
  • getY - the y position of the event
  • getXY - the x and y position of the event
  • removeAll - removing all events from the page
  • removeEventsFromEl - removes all events or a single event type from an element
  • control scope of the callback Function

On Friday, I will look at these features and explain how they can be implemented.

posted by Matt Snider at 2:39 am  

Tuesday, September 11, 2007

Type Detection Revisited

Mikol Graves, recently commented on my Type Detection article showing a novel way to do type detection. Here is his code snippet:

function isTYPE(o) { return (o != null && typeof o == ‘object’ && o.constructor.toString() == TYPE.toString()); }

For each Object type that you care about, for example: Date, Array, Class, to name a few. You simply replace “TYPE” with the appropriate Object name. These examples would produce functions like:

function isArray(o) { return (o != null && typeof o == ‘object’ && o.constructor.toString() == Array.toString()); } function isDate(o) { return (o != null && typeof o == ‘object’ && o.constructor.toString() == Date.toString()); } function isClass(o) { return (o != null && typeof o == ‘object’ && o.constructor.toString() == Class.toString()); }

This is nice, because it gives you a way of detecting all kinds of Meta Object types that you might care about. However, you end up using the same code over and over again. Therefore, I took some time and looked around the different toolkits for various other ways that type detection was being done. I found a snippet of code that I really liked in mootools.v1.11. Here is the code:

/* Section: Core Functions */ /* Function: $defined Returns true if the passed in value/object is defined, that means is not null or undefined. Arguments: obj - object to inspect */ function $defined(obj){ return (obj != undefined); }; /* Function: $type Returns the type of object that matches the element passed in. Arguments: obj - the object to inspect. Example: >var myString = ‘hello’; >$type(myString); //returns “string” Returns: ‘element’ - if obj is a DOM element node ‘textnode’ - if obj is a DOM text node ‘whitespace’ - if obj is a DOM whitespace node ‘arguments’ - if obj is an arguments object ‘object’ - if obj is an object ’string’ - if obj is a string ‘number’ - if obj is a number ‘boolean’ - if obj is a boolean ‘function’ - if obj is a function ‘regexp’ - if obj is a regular expression ‘class’ - if obj is a Class. (created with new Class, or the extend of another class). ‘collection’ - if obj is a native htmlelements collection, such as childNodes, getElementsByTagName .. etc. false - (boolean) if the object is not defined or none of the above. */ function $type(obj){ if (!$defined(obj)) return false; if (obj.htmlElement) return ‘element’; var type = typeof obj; if (type == ‘object’ && obj.nodeName){ switch(obj.nodeType){ case 1: return ‘element’; case 3: return (/S/).test(obj.nodeValue) ? ‘textnode’ : ‘whitespace’; } } if (type == ‘object’ || type == ‘function’){ switch(obj.constructor){ case Array: return ‘array’; case RegExp: return ‘regexp’; case Class: return ‘class’; } if (typeof obj.length == ‘number’){ if (obj.item) return ‘collection’; if (obj.callee) return ‘arguments’; } } return type; };

This was a good foundation for an improvement upon my type detection Functions. The $type Function can detect nearly all of my “isType” Functions straight away and has the power to detect some other important types, such as: arguments, textnode, element, collection, and more. So far, I have only added Date in addition to the types already defined and created an additional Function “isType” which can be used to test the object against a desired type.

We lose, “isAlien” Function and whether the number is finite. However, I never use these anymore, so I do not really need them. So far I believe the benefit of using these Functions will out-weight the loss. Although, I have also found these issues: testing for ‘whitespace’ does not work with IE because IE ignores whitespaces when parsing the DOM; in Opera testing for ‘arguments’ will return ‘array’, because Arguments constructor is Array (go figure). Otherwise, it has worked flawlessly and these issues can be worked around. I put together a test page, to verify the results of the isType Function and checked variations on the most common browsers.

You can download my Type Detection JavaScript file.

posted by Matt Snider at 10:09 pm  

Friday, June 15, 2007

Introducing Core JavaScript File and Namespace

Today’s post is going to introduce the file that provides the core functionality for my personal JavaScript Framework. I have already introduced several parts of the file, such as type detection and cookie management. You can view the code @ http://mattsnider.com/corejs.

The first thing you’ll probably notice is that I comment everything. This is the most important part of programming, and often overlooked in Web Applications. You almost can not have enough comments in any given file. In my case, I write so much code and work on so many different projects on a daily basis that without comments, I would be lost. But beyond that, if I wrote a file (of any reasonable length) 4 months ago and revisit it now, I am probably going to have to reacquaint myself with what it is doing (and most likely improve it a little). Figuring out what is going is exponentially faster when you comment your code. It’s best to comment: all functions, large blocks of code, anything that might be confusing, and all hacks.

Now, back to ‘Core.js’. What is does this all do? First, you will see a section that sets up some W3C standard constants, which should be attached to the ‘document’. These constants will be used anytime you want to compare the ‘nodeType’ of a DOMElement. Older browsers and IE do not define these by default, so this block of code fixes that. Note that I do not use browser detection to figure this out, but instead see if the variables are defined in the ‘document’ namespace. Always try not to use browser detection if you can.

/**
 *  W3C DOM Level 2 standard node types; for older browsers and IE
 */
if (null == document.ELEMENT_NODE) {
	document.ELEMENT_NODE                   = 1;
	document.ATTRIBUTE_NODE                 = 2;
	document.TEXT_NODE                      = 3;
	document.CDATA_SECTION_NODE             = 4;
	document.ENTITY_REFERENCE_NODE          = 5;
	document.ENTITY_NODE                    = 6;
	document.PROCESSING_INSTRUCTION_NODE    = 7;
	document.COMMENT_NODE                   = 8;
	document.DOCUMENT_NODE                  = 9;
	document.DOCUMENT_TYPE_NODE             = 10;
	document.DOCUMENT_FRAGMENT_NODE         = 11;
	document.NOTATION_NODE                  = 12;
}

An example of how you might use this is:

var isTextNode = function(node) {
	return document.TEXT_NODE == node.nodeType;
}

So, if you pass a DOMElement into the Function ‘isTextNode’, it will return ‘true’ if the node is a text node.

The next block of code defines the ‘Core’ Object and namespace. I say namespace, because the Core Object will be the global Object that the Framework will be attached to. I use the ‘module’ pattern as coined by Douglas Crockford to create my Core Object. This gives me the freedom to create ‘private’-like variables: ‘debugLevel’ and ‘clientInitFunc’. Debug level is a variable I use to differentiate between production and development messaging. ‘clientInitFunc’ also uses the ‘module’ pattern, creating the template that will evaluate the user’s browser and OS. This is also a logical place to attach cookie management Functions. Here are some references for these methods:

Again, let me reiterate, “DO NOT USE BROWSER DETECTION, UNLESS ABSOLUTELY NECESSARY”. That said, the two browsers you might have to detect most often are IE and Opera, so I have two shortcut methods: ‘isIE’ and ‘isOpera’ that compares against the Core.browser variable.

The next statement returns the constants and methods attached directly to the Core namespace. These include additional namespace variables: Biz, Debug, Client, Widget, and Util. Each of these have been commented to explain what to use them for. I frequently use Core.Client and therefore have attached the Client Object created during the initialization of the Core Object to Core.Client for ease of access.

The next two Functions are helper methods that did not belong in the global namespace, nor any other namespace, so I have attached them to Core. ‘deepCopy’ copies an Object or Array and all of its member Objects and Arrays until all children have been copied. I need this from time-to-time to prevent Objects from being passed by reference, when I do not want a Function to modify the passed element. ‘getImage’ is a short-cut method to create an ‘Image’ instance and then attach the URI src to the image Object. This allows pre-caching of images and simplifies code.

‘getDebugLevel’ is used to retrieve the debugLevel variable. I have designed it this way, because I want to ensure that I (or a hacker) can never update the debug level directly once the script is loaded into the page. Lastly, the ‘init’ Function initializes some internal ‘Debug’ variables and initializes the Core.Client Object.

Now, the ‘emptyFunction’ Function and ‘Class’ Object are copies of similar elements in the ‘Prototype’ Framework. They probably should not be in the global namespace, but i have not had a good enough reason to move them yet. ‘emptyFunction’ is handy, because it allows you to supply a generic do-nothing method. The ‘Class’ Object, can be used to create ‘Prototype’ Objects, but is something I try to stay away from. I have only one instance in the Form Validator that benefited from this design pattern, and so it remains. Also, the amount of code is negligible and some people may prefer to build ‘Prototype’ Objects.

The file closes with all the type detection functions. These are described in detail for the Type Detection article.

Although, short and sweet, this file has some complex ideas and may take a little getting used to. Please let me know your thoughts and any possible improvements, especially with the ‘deepCopy’ method.

posted by Matt Snider at 8:57 am  

Wednesday, June 6, 2007

Cookies

Cookie Monster

Well, unfortunately, not the kind you can eat (sorry, cookie monster). I was explaining cookies to someone in a forum today and thought I would share my thoughts.

Cookies are a good way to store temporary information about a user on their computer as long as they have them enabled. Fortunately, most people who have JavaScript enabled also have cookies enabled, so we will not worry about accessibility too much. Cookies have the advantage of being stored client-side in the user’s temporary internet files, so they are computationally cheap to set and retrieve. However, you should never store any private data in cookies or use them to access any private systems, as cookies are hackable and easy to forge. That said, many login systems rely on cookies to store a temporary session key; cookies are a well established technology and can be powerful if used right.

I often use cookies with tabs or toggles so that when a user switches pages and returns to the page with my widget the last state is remembered (this will be discussed at some point in the future). For the sake of today’s article, here are three simple function that I use as part of my Core package.

var createCookie = function(name, value, days) {
	var expires=null;
	if (days) {
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		expires = "; expires="+date.toGMTString();
	}
	else {expires = "";}
	document.cookie = name+"="+value+expires+"; path=/";
};
//
var eraseCookie = function(name) {
	Core.getClient().createCookie(name, '', -1);
};
//
var readCookie = function(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++) {
		var c = ca[i];
		while (c.charAt(0)==' ') {c = c.substring(1,c.length);}
		if (c.indexOf(nameEQ) == 0) {return c.substring(nameEQ.length, c.length);}
	}
	return null;
};

I do not remember where I originally found them from, so I cannot take credit, but I have used these functions for a long time.

posted by Matt Snider at 10:44 pm  

Monday, June 4, 2007

Prototype vs. YUI Round 2: I love $

Probably the most commonly needed Function for a Web Application Developer (WAD), is document.getElementById. As you will most certainly need to retrieve HTMLElements from the DOM by the element’s ID. Using ‘getElementById’ is perfectly valid, but has potential drawbacks such as:

  • ‘document.getElementById’ is verbose
  • multiple elements cannot be retrieved with one simple call
  • Some browsers, especially old browsers, do not support this call.
  • The more IDs you use in your document, the longer the seek time of this method.

As the title says, I am in love with the ‘$’ (I am also in love with money, in case you were wondering). The ‘$’ function was first developed by Prototype (and IMHO is Prototype’s greatest contribution to the JavaScript community). My love for the ‘$’ function is two-fold:

  • It is elegant, simple, and concise
  • Using the $ symbol as a shortcut, should be safe and most people did not know this was possible (so it is cool)

But beyond those points, let us really consider what an element retrieval function should and should not do:

Dos

  • Process lists of elements as well (both arguments list and arrays)
  • Handle all x-browser issues and old browsers, or degrade nicely
  • Caching. It is faster to maintain an array for all previously retrieved IDs, than to retrieve them each time
  • Do nothing when passing an HTMLElement (it should be acceptable to use ‘foo= $(foo)’, just to ensure that is an HTMLElement

Do Nots

  1. Attach a bunch of variables and/or functions to the HTMLElement*
  2. DO NOT do item 1

* This is my single biggest gripe with pretty much all JavaScript Frameworks. Why do each of my HTMLElements need a slew of helper functions that are already available to me elsewhere? I am not that lazy and frankly it is expensive to add Function references to every HTMLElement that I retrieve. Plus: you increase your chances of circular references causing memory leaks, different Frameworks and Toolkits may conflict with each other, and some future version of JavaScript may define that same function.

That said, let us move on to comparing YUI ‘YAHOO.util.Dom.get’ vs. Prototype ‘$’. I have created an simple rating system that I will be using for this and future discussion. I will walk through each Function and rate them using a point system, where you get plus points for each ‘Dos’ (ex. +1) and minus points for each (ex. -3) ‘Do not’ used. Here is the newest prototype ‘$’ function from version 1.5.1:

Prototype ‘$’ Function

function $(element) {
if (arguments.length > 1) {
for (var i = 0, elements = [], length = arguments.length; i < length; i++)
{elements.push($(arguments[i]));}
return elements;
}
if (typeof element == 'string')
{element = document.getElementById(element);}
return Element.extend(element);
}

I am happy in this release that they allow you to pass a list of arguments and retrieve multiple HTMLElements at the same time (+1). I am going to give the Function (+1) for using the ‘$’ name. However, it does not allow you to pass in an array as an argument, which is important because sometimes you will have gotten your lists of IDs from a method that created an array of Strings (+0) and will be required to use a work around to pass these Strings into this Function. The Function does retrieve arguments that are Strings and returns the non-string Objects (hopefully HTMLElements) (+1). Unfortunately, the method attaches needless and dangerous methods to the HTMLElement that you pass or retrieve (-3). It does not work with old browsers or degrade well (+0) and has no built in caching system (+0). This gives Prototype an overall rating of (0), which is pretty poor.

YUI ‘YAHOO.util.Dom.get’ Function

get = function(el) {
if (!el) { return null; } // nothing to work with
//
if (typeof el != 'string' && !(el instanceof Array) ) { // assuming HTMLElement or HTMLCollection, so pass back as is
return el;
}
//
if (typeof el == 'string') { // ID
return document.getElementById(el);
}
else { // array of ID's and/or elements
var collection = [];
for (var i = 0, len = el.length; i < len; ++i) {
collection[collection.length] = Y.Dom.get(el[i]);
}
//
return collection;
}
//
return null; // safety, should never happen
}

As with Prototype you are allowed to pass multiple arguments at the same time (+1) and although you cannot pass in an array of arguments, you can pass an argument that is an array, which is more powerful (+1). There is no ‘$’ function short-cut (+0), but I always map this method to the ‘$’ symbol anyway, so it is really not a big deal. The function does retrieve arguments that are Strings and returns the non-string Objects (hopefully HTMLElements) (+1). It does not attach needless/dangerous methods to the HTMLElement (-0). Unfortunately, it does not work with old browsers (+0) (it does degrade well, by return ‘null for unexpected values) and has no built in caching system (+0). This gives YUI ‘get’ Function an overall rating of (+3), which is average, however, if you map the ‘$’ symbol to the ‘get’ Function, then it would bump the rating up one, making YUI ‘get’ Function slightly above average.

Therefore, using my metrics, YUI’s ‘get’ Function is better than Prototype’s ‘$’ Function. I recommend using the YUI one and mapping it to a function named ‘$’ in the global space, emulating Prototype. Not only will you be able to use the concise ‘$’ symbol to retrieve HTMLElements, but doing this will allow you to use YUI with some of Prototype-based Toolkits and Frameworks, without the down-side of using Prototype. Obviously, there is a little room for improvement, as there is no caching yet and there is not a work around for browsers that do not support the ‘document.getElementById’ method. However, 95%+ of internet users (with JavaScript enabled) will have a browser that supports ‘document.getElementById’ (and this number increases every year), and caching gives a marginal improvement only when repeatedly retrieving the same element using the ‘get’ Function (and may be detrimental to performance if you use your own caching system).

Lastly, I want to leave you with a slightly modified YUI ‘get’ function that I use. I use YUI’s namespace approach and attach everything to an Object named ‘Core’, which I later shortcut key methods (such as ‘get’) as part of my JavaScript build process. Here is my method (it will be made available as part of my Core.Util.Dom package after I have further discussed DOM manipulation:

Core.Util.Dom.get: function() {
var multiArgs = (1 < arguments.length); // arguments is not an Array
var elem = multiArgs? arguments: arguments[0];
//
// nothing to work with
if (! elem) {
return null;
}
//
// assume is a DOMElement, but could be any Object
if (! isString(elem) && ! isArray(elem) && ! multiArgs) {
return elem;
}
//
// ID
if (isString(elem)) {
return document.getElementById(elem);
}
// array of ID's and/or elements
else {
var collection = [];
for (var i = 0, j=elem.length; i collection[i] = Core.Util.Dom.get(elem[i]);
}
return collection;
}
}

posted by Matt Snider at 3:08 pm  

Wednesday, May 23, 2007

Fun With Forms

I have been spending a lot of time improving my Form Utility Framework and wanted to share a little with you. First, we should address the reasons why we would want a Form Utility Framework. Here are some of my reasons:

  • Serialization for ajax - converts forms into key/value pair string (ex. “&key1=value1&key2=value2…”), often used with AJAX requests
  • Validation - rather than writing code to manually validate each form, i developed a system that allows you to add any number of validators for any number of fields in a form
  • Abstracted Value Retrieval - a universal method used to always retrieve the value of an input whether it is an input, a select, or a textarea HTML tag, so you do not need to remember how or worry about x-browser issues
  • Helper Methods - functions such as ones that: retrieve inputs by name, gets the first visible input of the form, disable/enable forms, etc.

Since every page will not always have a form and the Form Utility Framework will not always be used, try to keep it as lightweight as possible; it is easy to bloat your Utility with functions that you infrequently or never use.

If you find yourself using a Form Utility Framework, you might run into this problem that I had. I had not used the ‘reset’ HTMLInput type in a long time and found myself wanting to use it to clear the fields in a form. In the past that had always worked for me, but if you are using a server-side scripting language to set the values of the form fields when the page load, then you will find that reseting the form does not clear it. Instead, it reverts to the values that the form had when the page loaded. This is because in most modern browsers, when initializing a page that sets the form field value attribute, those values also are set the the fields defaultValue attribute. According to the W3C standards, the defaultValue attribute is what should be used for form reseting. Therefore, if the form fields had no values when the page load, then the reset function worked as I wanted, otherwise, it did not. To defeat this I created my own method ‘clear’ that iterates through the different form field elements and removes their values.

if (! Form) {Form = {};}
Form.clear = function(form, ingore) {
var inputs = form.getElementsByTagName('input');
// inputs
for (var i=0, input; input=inputs[i]; i++) {
if (-1 == ingore.indexOf(input.type.toLowerCase())) {
input.value = "";
if (input.checked) {input.checked = false;}
}
}
// textareas
if (-1 == ingore.indexOf('textarea')) {
inputs = form.getElementsByTagName('textarea');
for (var i=0, input; input=inputs[i]; i++) {
input.value = "";
}
}
// selects
if (-1 == ingore.indexOf('select')) {
inputs = form.getElementsByTagName('select');
for (var i=0, sel; sel=inputs[i]; i++) {
sel.selectedIndex = 0;
}
}
};

This method is pretty straight forward. Pass the method an HTMLFormElement Object and it will parse through the object’s child nodes by the tag names using the appropriate reset method. Lastly, I tossed in an array called ignore (array of string types that are not to be cleared), because I often don’t want to reset types such as “button”, “hidden”, “submit”, and “reset”. I was thinking, it might be helpful also to have a list of field names to ignore, but I did not have a need for it yet.

posted by Matt Snider at 2:13 pm  

Wednesday, April 4, 2007

Type Detection

Common JavaScript Interview question: What are the object types in JavaScript?

The short answer is that there is only one type in JavaScript: Object. However, this is not really the whole truth, as JavaScript has 7 meta-types: Number, String, Boolean, Function, Object, Null, Undefined.

Since JavaScript is not a strongly typed language and in fact every object descends from the ‘Object’ type, you may find it helpful to manage types yourself. I personally, always manage types as it allows me to bulletproof my JavaScript functions and prevent other developers from breaking my code. Also, AJAX applications often return bad data because lot can go wrong with the request and server-side code execution. I have developed a simple set of functions that I always include in my ‘core.js’

Below is a list of functions that I use to detect object types:

isAlien:
/* True, if the object is not part of a function. */

function isAlien(o) {return isObject(o) && !isFunction(o.constructor);}

isArray:
/* True, if the object is constructed of the native Array Object */

function isArray(o) {return isObject(o) && o.constructor == Array;}

isBoolean:
/* True, if the object is of the meta-type ‘boolean’ */

function isBoolean(o) {return ‘boolean’ == typeof o;}

isDate:
/* True, if the object is an Object with the getMonth method */

function isDate(o) {return isObject(o) && o.getMonth;}

isDomElement:
/* True, if the object is set and has children nodes or a node type */

function isDomElement(o) {return o && (”undefined” !== typeof o.childNodes || o.nodeType);}

isEvent:
/* True, if the object is set, the native Event Object is defined, and the object has an event phase */

function isEvent(o){return o && “undefined” != typeof Event && o.eventPhase;}

isNull:
/* True, if the object is null */

function isNull(o) {return null === o;}

isFunction:
/* True, if the object is of the meta-type 'function' */

function isFunction(o) {return ‘function’ == typeof o;}

isNumber:
/* True, if the object is of the meta-type ‘number’ and is finite as determined by the native function */

function isNumber(o) {return “number” == typeof o && isFinite(o);}

isObject:
/* True, if the object is of the meta-type ‘object’ or ‘function’ */

function isObject(o) {return (o && “object” == typeof o) || isFunction(o);}

isSet:
/* True, if the object has any value, including 0 and false, both of which are normally falsy */

function isSet(o) {return ((o || false === o || 0 === o) && ! isNull(o) && ” !== o && ! isUndefined(o));}

isString:
/* True, if the object is of the meta-type 'string' */

function isString(o) {return ’string’ == typeof o;}

isUndefined:

/* True, if the object is of the meta-type 'undefined' */

function isUndefined(o) {return ‘undefined’ == typeof o;}

isEmpty:

/* True, if the object does not have any members that are not functions */

function isEmpty(o) {
var i, v;
if (isObject(o)) {
for (i in o) {
v = o[i];
var t = isUndefined(v);
var t1 = isFunction(v);
if (! isUndefined(v) && ! isFunction(v)) {
return false;
}
}
}
return true;
}

*note: most of these functions are not bulletproof and can be fooled. For example:
var fakeDate = {
getMonth: 1234
};
isDate(fakeDate) /* this will be true */

I have not found a good way to prevent this type of abuse, except through intelligent coding. You should never use reserved names or those names used by other native JavaScript Objects, like Date and Array, when writing application objects.

posted by Matt Snider at 4:05 pm  

Powered by WordPress