Matt Snider JavaScript Resource

Understanding JavaScript and Frameworks

Saturday, January 3, 2009

Library - YUI 3 - Dynamic DOM Search Experiment

Today’s article illustrates an experimental function that parses a string, containing instructions for searching the DOM relative to a node. This method was inspired by a desire to allow a separate design team to specify the relative location of other DOM nodes. This way, parts of the JavaScript that handle user triggered events (such as ‘click’) need not hardcode relative nodes.

For example, suppose when the user clicks on an anchor, a relative node is made visible and the design team frequently changes the location of that node. An ID could be used, but that doesn’t work as well with dynamically generated content, instead the following method might be useful:

Example 1: Exec Function

YAHOO.util.Dom.exec = function(elem, instructions) { var node = YAHOO.util.Dom.get(elem); if (! (node && instructions)) {return;} var _s = instructions.split('.'); for (var i = 0; i < _s.length; i += 1) { if (node) { var task = _s[i]; if ($D[task]) { node = $D[task](node); } // todo: support childNodes[] else if (node[task]) { node = node[task]; } else { // unsupported technique } } else { return true; } } return node; };

The ‘exec’ method requires two parameters: the target node, and a string containing instructions for finding a node relative to the target. When both parameters are provided, the instructions string is split around period (’.'), creating an array of instructions to iterate on. YUI Dom methods and/or regular DOM methods may be used (as long as they don’t require brackets), such as the following: “parentNode.nextSibling.getFirstChild” (will execute the following “YAHOO.util.Dom.getFirstChild(node.parentNode.nextSibling)”). Lastly, ‘exec’ returns ‘undefined’ when a node is not found.

While, this method is useful, it is lacking a lot of features such as searching by class/tag name or children nodes. Libraries such as YUI 3 and jQuery have optimized DOM searching methods, which could be leveraged by this method. The reason I didn’t use these methods is that they only search down the DOM tree and not up, which I needed. Although there probably is an extension out there for this, but a quick search didn’t find it and what I have worked to meet my immediate needs. Next week, I will show a technique for dynamically attaching blind animations to the DOM that uses this method to relatively reference the node to animate from the triggering anchor.

posted by Matt Snider at 11:20 pm  

Thursday, December 25, 2008

JS Native - String - Extending With SliceWord Function

The ’sliceWord’ function easily slice out a section of words from a string. This may be useful for form validation, DOM class manipulation, test replacement, and a whole lot more. The method is attached to “String.prototype”, but could be easily modified to be standalone. Up to two optional parameters can be applied, where the first parameter is the word index to start (start index) slicing from, and the second parameter is the index of the word to stop slicing at (’sliceWord’ works much like “Array.slice”). If the stop index is not provided, then all words after and including the word at the start index will be returned. If the start index is larger than the number of words in the string, then NULL is returned. However, when no or invalid parameters are provided the string itself is returned, and if the stop index exceeds the number of words in the string, then the method will return the whole string after and including the start index.

Example 1: String.prototype.sliceWord

String.prototype.sliceWord = function(i, j) { var args = arguments, str = '' + this, m = 0 < i ? i : 0, n = 0 < j ? j : 0, _split = str.split(' '); // if no args are sent, then return str if (args.length) { if (_split.length < m) {return;} str = (1 < args.length && n < _split.length ? _split.slice(m, n) : _split.slice(m)).join(' '); } return str; };

This method is particularly helpful, when designer use CSS classes to style the markup. Generally, the first class is going to be provided by the designer and it will apply the defaults styles. Additional, classes can be applied by the JavaScript engineer in response to user interactions with the page to change styles. For example, suppose there is an input node with the class ‘button’, which also has the class ‘disabled’ applied to it. By default “input.button” styles make the button look like a standard HTML button, however, when ‘input.button.disabled’ is applied, the button grays out an looks disabled. The JavaScript adds the ‘disabled’ class to the button when the form is incomplete, but the ‘button’ class always removes. Once the form validates the JavaScript needs to remove the ‘disabled’ class, so we could use the following:

Example 2: dom_removeClasses Function

var dom_removeClasses = function(elem, i) { var node = YAHOO.util.Dom.get(elem); if (! node) {return;} // safety measure node.className = node.className.sliceWord(0, i); }; dom_removeClasses('myButton', 1)

Example 2 will remove all classes applied to ‘myButton’, except the first one (’button’). Now, this example is relatively simple, and ’sliceWord’ is probably overkill, because we know all the classes applied to button and there are only 2 classes. However, it becomes much more useful when a node can have any combination of 5 classes applied to it.

In the next release of Mint.com we frequently use this technique to normalize elements before applying additional classes to them. This helps to make the JavaScript code less fragile when design makes simple class changes or when they forget to remove classes from the views.

posted by Matt Snider at 12:42 pm  

Saturday, December 20, 2008

Library - YUI 3 - Namespaces and Dynamic Loading

During my development of the YUI port of dojo.storage, there has been a lot of time to take a look under the hood of YUI version 3. It is more intuitive and easier to use than version 2.x, although it takes a while to get used to the new nomenclature. Today, we will take a look at the new namespace function ‘use’ and how to self-contain your namespace, while dynamically loading additional JavaScript functionality.

With YUI 3 you need only include 1 JavaScript file to start your application, which is:

<script type="text/javascript" src="http://developer.yahoo.com/yui/3/build/yui/yui-min.js"></script>

All interactions with the YUI library are now handled by chaining the global function “YUI()”. A configuration parameter can be passed to create multiple namespaces, and to modify other properties. However, by default, the first time this method is called, it will create the namespace and each subsequent call will return the originally created namespace. Additional information available at the YUI developer center

The “YUI()” method can be chained with the ‘usage’ function, allowing dynamic loading of additional YUI modules. Here is how

Example 1: Using YUI().use()

YUI().use('node', 'event', 'io', 'animation', function(Y) { // you can now use 'Y' to access all YUI methods, included those added by this call });

The ‘use’ method utilizes the ‘arguments’ variable to iterate through all parameters passed to it. Any parameter that is a string will be dynamically added to the namespace. While the last parameter can be a function, which will be called back once the additional libraries have been loaded. If the libraries were already added to the namespace, then the callback will be executed immediately. The first parameter of the callback will be a pointer to the YUI namespace, which will be extended with any new methods provided by the libraries requested.

Therefore, any place a library is needed, simply call “YUI().use()” and those library methods will be provided. Using YUI in version 3 will heavily utilize the command pattern, reducing the need for worrying about timing and availability of libraries and/or data.

posted by Matt Snider at 4:03 pm  

Wednesday, December 17, 2008

Update - No Mid-Week Article This Week

No article this week, as I am in the middle of two blog related projects: a redesign and a new YUI widget.

The first part of the redesign is complete (upgrading to the latest wordpress), but I will be creating a new homepage, a table of content, and other improvements over the next few weeks. Part of the redesign will involve renaming many blog titles, which will affect their URLs, which is bad. I am trying to figure out a way (without having to create a mod_rewrite rule for each URL) to preserve the existing URLs, while supporting new ones.

For YUI, I am working on porting Dojo Storage, which allows for large, secure client-side data storage. Hopefully, I will have made some by the end of the week.

posted by Matt Snider at 10:37 am  

Monday, December 15, 2008

Upgraded to WordPress 2.7

Part of my 2008 New Years Resolution was to improve the layout of this blog. I have made a few minor stylistic changes this year, but the vast majority of changes will be coming in the next couple of weeks. It is amazing how much I postpone when I don’t think of myself as a client. Anyway, the first major change is upgrading the software to the newest Wordpress (v2.7).

I’m sorry for any inconvenience if you accessed the site during the last 30 minutes while I troubleshooted. I believe I have fixed all issues, but please let me know if something is not working.

Thanks
-matt snider

posted by Matt Snider at 4:50 pm  

Sunday, December 14, 2008

News: Cool Development Tools

Visual Event

Recently, on Mint.com I have been revisiting old code and improving the performance of events, mostly by reducing the number of handlers on a page. Consequently, I introduce an Event Disptacher tool a couple of weeks ago, which illustrates how to reduce event handlers. However, attaching Event Dispatcher to ‘document’ can have its own performance drawbacks. Sometimes it makes sense to break the page into smaller node trees and dispatch events on those smaller trees, so less of the document must be iterated through. I have found Visual Event really helps me to understand what events are attached to which nodes in the document and better dispatch events. Visual Event is a JavaScript bookmarklet that visualizes the events attached to nodes as you move the mouse around a web page.

YUI 3.x PR2

Lots of changes to the framework, especially DOM manipulation (much more like jQuery) and widget/plugin infrastructure. They have some really good documentation up at YUI 3

Yahoo Query Language

Yahoo Query Language (YQL) exposes the Yahoo Network to outside services using an SQL-like language and a public URL. See an example of how to use it with flickr, here

YUI Doc: A New Tool for Generating JavaScript API Documentation

This is an amazing piece of software that allows you to easily convert your JavaScript documentation into easy to read and share HTML documents. Have a complete set of documentation for your own code makes it easier to understand, both for your future self, and clients and other developers. YUI Doc also helps organize your code by requiring commenting and architecture standards.

posted by Matt Snider at 5:32 pm  

Wednesday, December 10, 2008

Solving Dustin Diaz RegEx Brain Teaser Part II

Dustin Diaz of “./with Imagination” blog and Google, wrote a regex challenge. Basically, write a regular expression that wraps any alpha sequence (”\w+”) occurring more than a N number of times, with parenthesis. It actually ended up being pretty easy, not requiring look aheads or any more complex than a backreference. Here is my solution:

Example 1: Bookend Function

var bookend = function(text, start) { var rx = new RegExp("(\\w+)\\s((\\1\\s){2})((\\1\\s)*\\1)".replace('2', start - 1), "g"); return text.replace(rx, '$1 $2($4)'); };

Here is a good visualization of this regular expression, Regex Visualization. The “Not Yet Implemented” are backreferences to the first wordchar loop.

In other words, the regular expression first matches a group of word characters from the “(\\w+)”, this becomes the “\\1″ backreference. Next a space is matched “\\s”, followed by the matched word characters and a space repeated one less than start “((\\1\\s){2})”. The two will be replaced at runtime with the right value, and the outside parenthesis creates the second backreference that we will use in the replace statement. Last we match any number of word characters and space repetitions, ending with a single instance of the word characters “((\\1\\s)*\\1)”. This will become the forth backreference used in the replace statement.

Here is a test page so you can play with it. Keep in mind I don’t do any parameter checking, so if start is less than 1, it is not going to work.

posted by Matt Snider at 10:07 am  

Saturday, December 6, 2008

Get Common Ancestor Function

One way to improve the dispatch technique from last week is to only attach the single listener to the greatest common ancestor of all elements that we are dispatching. To achieve this, the following method to find the common ancestor of two elements was needed:

Example 1: Get Common Ancestor Function

YAHOO.util.Dom.getCommonAncestor = function(elem1, elem2) { var node1 = YAHOO.util.Dom.get(elem1), node2 = YAHOO.util.Dom.get(elem2); if (! (node1 && node2)) {return null;} // missing parameter, fail node1 = node1.parentNode; // iterate up the DOM tree while (node1) { if (YAHOO.util.Dom.isAncestor(node1, node2)) {return node1;} node1 = node1.parentNode; } return null; };

This method requires two parameters, two elements to compare. It iterates through the ancestors of the first node and leverages the existing YUI ‘isAncestor’ method to check if the current ancestor of node1 is also an ancestor of node2, returning the that node. When an ancestor is not found, the method returns ‘null’. The ‘null’ state can happen when one of the elements has not been add to the document yet or belongs to another window.

posted by Matt Snider at 7:09 pm  

Wednesday, December 3, 2008

Event Dispatcher

Modern web applications behave a lot like desktop applications, with nearly asynchronous updates, pre-caching of events and DOM, animations, drag and drop, etc. However, all these features come with a price, and a web application developer constantly considers add a new feature versus degrading site performance. Todays topic illustrates how you can reduce the cost of events leveraging an event dispatcher, ‘EventDispatcher’.

From Widipedia:

Multiple dispatch or multimethods is the feature of some object-oriented programming languages in which a function or method can be dynamically dispatched based on the run time (dynamic) type of more than one of its arguments.

The dynamic type used by EventDispatcher is special command classes starting with ‘com_’ and ending with the event id, so the class ‘com_doSomething’ would dispatch the ‘doSomething’ event (as long as it is defined). A public method exists on the EventDispatcher object with allows for event registration, so the dispatcher knows how to delegate a given event id. The first time a type of event (such as ‘click’) is registered, a new listener is added to the document. This means that for each type of event there will be only 1 callback, even if the page has hundreds of events of that type registered. When one of the registered event types fires on the page, the target element is checked for command classes and all of its ancestor elements, dispatching as needed.

Example 1: Event Dispatcher Object

Core.Util.EventDispatcher = (function() { // local variables var callbackMap = {}, doc = document, F = function() {}, rx = /\\bcom_\\w+\\b/g, that = null, YUE = YAHOO.util.Event; // event namespace var E = { dispatcher: function(e) { var node = YUE.getTarget(e); // simulate bubbling while (node && node !== doc) { var coms = node.className.match(rx); // not matched if (null === coms) { // not found, do nothing for now } // command class exists else { var i = 0, j = 0; // iterate on matching commands for (; i < coms.length; i += 1) { var id = coms[i].replace(/com_/, ''), carr = callbackMap[e.type][id]; // object for command exists, command could be for another event if (carr && carr.length) { // iterate on command callbacks for (j = 0; j < carr.length; j += 1) { var o = carr[j], args = [e]; if (o.eventFx) {o.eventFx(e);} // event stop events o.callback.apply(o.scope, args.concat(o.arguments)); } } } } node = node.parentNode; } } }; // public interface F.prototype = { register: function(type, o) { // check for required if (! (type && o && o.id && o.callback)) { alert('Invalid regristration to EventDispatcher - missing required value, see source code.'); } // allows for lazy-loading of events if (! callbackMap[type]) { callbackMap[type] = {}; YUE.on(doc, type, E.dispatcher); } if (! callbackMap[type][o.id]) {callbackMap[type][o.id] = [];} if (! o.scope) {o.scope = window;} if (! o.arguments) {o.arguments = [];} if (! YAHOO.lang.isArray(o.arguments)) {o.arguments = [o.arguments];} // support arguments that are non arrays callbackMap[type][o.id].push(o); } }; that = new F(); return that; })();

The EventDispatcher object is a static object that must be registered with for dispatching to occur. The ‘register’ method requires 2 parameters: the first is the name of the event, and the second is an object (Dispatcher Object), which must at least have a unique event id (this is the second half of your command class ‘com_eventId’) and a ‘callback’ function. In addition, the ’scope’ of the callback function can be can specified, as well as any additional ‘arguments’ to be passed into the callback function. The registration function first validates these parameters, then determines if the event type is listened for; attaching the event and initializing the callback map for that event type as necessary. When the event id is not yet mapped, a new array is added for that event id and the second parameter object is pushed onto this array (we use an array, because many elements can share the same command class).

Later when the registered event is triggered, the generic dispatcher method is called. This method first gets the event target, then iterates up the DOM until the document or NULL is reached. During each iteration the DOM node is checked to see if it contain a command class (using a simple regex). Any command class found is compared to those mapped in the ‘callbackMap’ and when a mapping is found, it is iterated on, finding each Dispatcher Object and executing the defined callback. The ‘eventFx’ is another option defined on the Dispatcher Object and can be any method used to stop events (such as ’stopEvent’ or ’stopPropogation’). Lastly, the callback function is executed using the ‘apply’ method, so that the scope can be overridden and the arguments defined in the Dispatcher Object will become the parameters of the callback function (first argument is the event, followed by the values in ‘arguments’).

Here is a test page, so you can experiment with the dispatcher. Mostly it is meant to show that the dispatcher works in many different kinds of situations.

posted by Matt Snider at 10:25 am  

Tuesday, November 25, 2008

Simulating Events Using YUI

Sometimes when managing pages with unobtrusive JavaScript, especially ones leveraging events that need to bubble, two code paths are required: one for the event handler that properly bubbles, and another for the same action triggered by code. For example, a ‘click’ event is attached to a ‘div’, containing many children, including several ‘input’ elements. If the client clicks on one of the inputs, then both the ‘input’ and the ‘div’ ‘click’ events fire. However, if you programmatically focus on the element neither event fires, even though it might be desired.

Some browsers have begun to support event emulation allowing engineers to trigger client events, but most are incomplete and there is no standard x-browser way to simulate events. Fortunately, JavaScript Frameworks have come to the rescue, allowing us to simulate client events. YUI has the YUI Test utility, which allows for the emulation of 7 mouse events and 3 keyboard events: click, dblclick, mousemove, mousedown, mouseup, mouseover, mouseout, keydown, keyup, and keypress. So by using YUI there actually is a standard, x-browser way to emulate events.

Most JavaScript Frameworks have an x-browser event handling system, and many keep lists of all the events that you have attached using their utilities. By iterating on the list of cached event handlers, libraries can properly bubble (or capture) events starting from a DOM node and moving up the node tree. Here is a simple example of a click simulator that I wrote using YUI, illustrating how the Test library can leverage the Event utility to simulate events:

Example 1: Click Handler Function

var simulateClickEvent = function(elem) { var node = YAHOO.util.Dom.get(elem); while (node && window !== node) { var listeners = YAHOO.util.Event.getListeners(node, 'click'); if (listeners && listeners.length) { listeners.batch(function(o) { o.fn.call(o.adjust ? o.scope : this, {target: node}, o.obj); }); } node = node.parentNode; } };

To use ’simulateClickEvent’, simply pass in the ID or pointer to a DOM element. The method tests if the ‘node’ exists and that it is not equal to the ‘window’ before continuing its operation. At the end of the ‘while’ statement, it moves to the parent node of ‘node’, and continues that way up the DOM tree until window is reached or ‘null’ if the node was not properly attached to the DOM. Inside the loop the ‘getListeners’ method of the ‘Event’ utility is called, passing in the current node pointer and the desired event (in this case ‘click’). If nothing is found for the event/node combination, then YUI will return ‘null’, otherwise an array of events objects. Then to simulate events, the listeners array is iterated on and the function attached to the event object is called. In this example, a simple Event Object is created, with only the ‘target’ key set.

The YUI Test infrastructure is much more robust than my simple example, allowing engineers to specify many more properties of the event that will be passed into the callbacks. However, most of the time you probably won’t need that much power and something more rudimentary is acceptable, especially if you do not want to include another JavaScript file into your library. In those cases, I think my little example, with little modifications for your specific requirements, will probably suffice.

posted by Matt Snider at 9:47 am  
Next Page »

Powered by WordPress