Matt Snider JavaScript Resource

Understanding JavaScript and Frameworks

Saturday, September 27, 2008

Animated ScrollTo

This week I had the opportunity to write a method that wraps the native JavaScript ‘window.scroll’ method with one that animates the scroll. The native ‘window.scroll’ method is well supported by all Tier-A browsers and is a great way to ensure that dynamic content is visible on the screen. However, it is often jarring to the user when the page jumps to the desired position, so instead we will animate the scrolling.

Example 1: ScrollTo Method

document.scrollTo = function(x, y, n, ms, ease) { var offset = Core.Client.getScrollOffset(), steps = n || 5, i = steps, time = ms || 250, xdiff = x - offset.x, ydiff = y - offset.y, fx = ease ? ease : function(i, steps) { return Math.pow(2, i); }; clearInterval(scrollInterval); scrollInterval = setInterval(function() { i -= 1; var divisor = fx(i, steps); window.scroll(xdiff / divisor + offset.x, ydiff / divisor + offset.y); // last step if (0 === i) { clearInterval(scrollInterval); window.scroll(x, y); } }, time / steps); };

I have attached the ‘scrollTo’ method to document, but you can attach it to any object or variable. The ‘getScrollOffset’ method is from Core.js and is used to determine how much the user has scrolled the window. The ‘x’ and ‘y’ parameters are the position that you want to scroll the page too, and is required. The remaining parameters: ‘n’ is the number of steps to make for animation (default is 5), ‘ms’ is the animation time in milliseconds (default is 250ms), and ‘ease’ is the function used to determine each step length. The default easing method is a fractal algorithm where each step length is ½ the prevent step.

Here are two additional easing functions; the first method is the exact opposite of the default easing method and the second eases with event steps.

Example 2: Easing In

var easeIn = function(i, steps) { return Math.pow(2, steps - i); };

Example 3: Easing None

var easeNone = function(i, steps) { return i; };

So, if you wanted to scroll the y-axis to 750 using the ‘easingIn’ method in 10 steps over 500ms, you would call ’scrollTo’ with the following signature:

Example 4: ScrollTo Signature

document.scrollTo(0, 750, 10, 500, easeIn);

Lastly, the ’scrollTo’ method properly handles sign, so if you are farther down the page, it will scroll up and if you are farther up the page, it will scroll down.

posted by Matt Snider at 12:03 am  

Wednesday, September 24, 2008

Breadth First DOM Search Algorithm

Today I ran an experiment to compare the native JavaScript ‘getElementsByTagName’ and YUI’s ‘getElementsByClassName’ on the ‘document’ against a breadth first search of the ‘document’. My assumptions were that when searching for a single tag and/or class the first two methods would be substantially faster, and that breadth first was the right way to search, because the DOM is usually not as deep as it is wide. My hypothesis, however was that I could do more complex operations, such as finding multiple tags or multiple classes faster using the breadth first search.

Improving a manual DOM search is useful, because I wrote a module that manages tabs inside of a form. Normally, setting the ‘tabIndex’ will work just fine, but when dynamically loading content into a form, it is easy to confuse the browser. The tab management module cares about 3 tag types: input, select, and textarea. Now one could do three ‘getElementsByTagName’ and combine the results, but the elements won’t necessarily be in the order that they appear in the DOM, which is important when tabbing. Instead, I manually iterate through the form DOM, so that I get the elements in the right order.

Unfortunately, this is a slow process, and I was hoping to improve it. However, since every node in the tree (below a given form node) needs to be searched, the breadth first search does not improve performance much. Anyway, here is the algorithm:

Example 1: Breadth First Search Algorithm

Core.Widget.BreadthFirstSearch = function(root, matchFx) { if (! (root)) {alert('Missing parameter in Core.Widget.BreadthFirstSearch.');} var mFx = matchFx || function() {return true;}, queue = [], results = [], i = 0, n = 0; /** * Recursively searches the DOM, creating the queue. * @method recursiveFx * @param param {Element} Required. The parent node to examine. * @private */ var recursiveFx = function(parent) { // ensure that the parent has children if (parent.childNodes.length) { var children = Array.get(parent.childNodes); queue = queue.concat(children); children.batch(recursiveFx); } // criteria matched for result queue if (mFx(queue[n])) { results[i] = queue[n]; i += 1; } n += 1; }; // start recursion recursiveFx(root); return results; };

Simply pass in a DOM node and the algorithm will return an array of all its children. If you provide a match function, then the algorithm will only return an array of matching results. I threw together a simple test page to compare a few different searches of the ‘document’. Each time you run one of the tests, a ‘p’ tag is added to the DOM making the DOM tree large, so the more you run tests, the longer each search takes. Unfortunately, my hypothesis was proven wrong and even the complex 3 tag search was out performed by simply concatenating the native JavaScript ‘getElementsByTagName’ quickly outperforms the breadth first search.

posted by Matt Snider at 9:18 am  

Sunday, September 21, 2008

Interval Manager

I am a big fan of the Lazy Function Pattern, however, yesterday I realized a serious pitfall that you can encounter when using this pattern with functions that need to be executed in a certain order. Assume for a minute that you have an object requiring an AJAX request to initialize its content, but when you instantiate the object, you immediately want to manipulate it. For example, you have an Object ‘DomModule’, with two public functions: ’show’ and ‘update’. When you instantiate the ‘DomModule’ object, it fires an AJAX request to update itself, then the code instantiating ‘DomModule’ immediately calls “DomModule.update” and “DomModule.open”. Both functions require that the DOM node be set, which happens when the AJAX returns, before executing, so each function has the following code snippet:

Example 1: Lazy-Loading Snippet

if (! that.node) { // node is set by AJAX setTimeout(function() {that.open.call(that);}, 500); return; }

This works great except when we need to guarantee that the “DomModule.update” occurs before “DomModule.open”. Since both have unrelated timeouts the behavior is non-deterministic. We can add special logic each time we need deterministic order for lazy-loaded functions, however that is less than ideal. It would be better if we could use general logic to specify order priority to our timeouts. For that reason I have written a beta Interval object that manages priority, number of executions, execution limits, and more. Also, it may be possible to game some performance improvement or more deterministic times over the native JavaScript ’setTimeout’ and ’setInterval’ by managing one instance of the interval, instead of relying on the browser.

The code is still in beta and rather than attempt to walk through, I will summarize its features and provide a demo page for those interested with exploring. The Interval object can handle any interval divisible by 25ms, or will covert the interval to the nearest multiple of 25. You can provide a stop function that will terminate the interval when a certain criteria is reached. In addition, you can also specify the max number of times an interval callback can be executed and its priority from 1 through 10, where lower priority executes first, and if the priority is tied, then the execution order is the order that the interval callbacks were passed into the Interval object. Lastly, when the callback function is executed, it will be passed an object representing the current state of the interval.

When setting up an interval, use the following:

Example 2: Starting A New Interval

Interval.setInterval(callbackFunction, stopFunction, timeoutInterval, priority, maximumNumberofExecutions);

If we look back at our original example, we can now use the Interval object to set a priority to our lazy functions, so that “DomModule.update” callback occurs before the “DomModule.open”. No special logic is required and Interval can be used to manage other page intervals, whether they need priority or not.

The only required value for ’setInterval’ is the ‘callbackFunction’, everything else can be ‘undefined’. Right now, I would not recommend using any ‘timeoutInterval’ shorter than 100ms as the code execution starts to affect the interval time. I have also noticed that the performance tends to degrade when adding 10+ intervals at a given time. I have attempted to optimize, but there is still room for improvement. The source code Interval.js is available here and a test page here

posted by Matt Snider at 12:56 am  

Wednesday, September 17, 2008

Listening For Browser Changes

Have you ever wanted to adjust your design when the user resizes their browser or changes their font-size? While some browsers have added special (non-standard) events, so that you can listener for these changes, most browser have not. Today I will show you how to use YUI custom events to subscribe to manage events that fire when the user changes the browser’s font-size or resizes their browser window.

Here is the module BrowserEventMonitor.js:

Example 1: Browser Event Monitor

Core.Widget.BrowserEventMonitor = (function() { // Module Private Variables var Dom = YAHOO.util.Dom, CE = YAHOO.util.CustomEvent, F = function() {}, that = null, monitors = []; // collection of monitors // event namespace var evt = { /** * The CustomEvent to fire when font resizes. * @property ceFontResize * @type {Number} * @private * @const */ cefontresize: new CE('fontResize', that, true, CE.FLAT), /** * The CustomEvent to fire when window resizes. * @property ceWindowResize * @type {Number} * @private * @const */ cewindowresize: new CE('windowResize', that, true, CE.FLAT) }; // Public Methods and Variables F.prototype = { /** * The Event name constant for listening to "change" CustomEvent events. * @property FONT_RESIZE * @type {String} * @const * @static */ FONT_RESIZE: 'fontResize', /** * The Event name constant for listening to "windowResize" JavaScript events. * @property WINDOW_RESIZE * @type {String} * @const * @static */ WINDOW_RESIZE: 'windowResize', /** * Subscribe to the custom events for this object. * @method subscribe * @param type {String} Required. The custom event name. * @param func {Fucntion} Required. The callback function to be fired on event. * @param obj {Object} Optional. The arbitrary data passed through. * @public */ subscribe: function(type, func, obj) { var name = 'ce' + ('' + type).toLowerCase(); if (evt[name]) {evt[name].subscribe(func, obj);} } }; that = new F(); // Define windowResizeEvent var size = Core.Client.getViewportSize(); monitors[0] = { evt: evt.cewindowresize, test: function() { var size = Core.Client.getViewportSize(), value = size.x + '-' + size.y; if (monitors[0].value !== value) { monitors[0].value = value; return true; } else { return false; } }, value: size.x + '-' + size.y }; // Define fontResizeEvent var bd = Core.Client.getBody(); monitors[1] = { evt: evt.cefontresize, test: function() { var fontSize = Dom.getStyle(bd, 'font-size'); if (monitors[1].value !== fontSize) { monitors[1].value = fontSize; return true; } else { return false; } }, value: Dom.getStyle(bd, 'font-size') }; // Start timer setInterval(function() { // iterate on the monitors and trigger custom events as necessary monitors.batch(function(o) { if (o.test()) {o.evt.fire(that);} }); }, 250); return that; })();

The code is pretty simple. We first create 2 custom events and apply the subscription pattern to the public part of the module, so that the rest of your application can listen for these events. We then create an array of internal monitor objects with three keys: evt, test, value. The ‘evt’ is a pointer to the CustomEvent for the monitor and the ‘value’ is the current value being monitored. The ‘test’ function should see if the value you are monitoring has changes, updating ‘value’ if necessary and returning true or false. If you decide to monitor additional browser events, you simply need to append them to the monitors array. Lastly, we have an interval timer that calls the ‘test’ function on each monitor in the monitors array every 250ms, firing a CustomEvent whenever the ‘test’ function returns true.

To subscribe to either of these events, use the following:

Example 2: Subscribing to Browser Event

var Monitor = Core.Widget.BrowserEventMonitor; Monitor.subscribe(Monitor.FONT_RESIZE, function() { // your code here }); Monitor.subscribe(Monitor.WINDOW_RESIZE, function() { // your code here });

I have also put together a simple test page, so you can see the BrowserEventMonitor working.

posted by Matt Snider at 9:42 am  

Saturday, September 13, 2008

Data Singleton For AJAX Requests

Although invented years ago, it is the emergence of AJAX over the last few years that has allowed the development of today’s powerful, web applications. And while I love what I can do with AJAX, working with asynchronous requests can be tricky. YUI and other frameworks have done a great job allowing for callbacks functions and custom events, so that you do not have to worry as much about null data pointers and managing the request timing. However, I have always believed that there was a better way. That belief led me to develop an Object, I have called DataSingleton, which fetches and manages a singleton instance of all JSON data that I need to retrieve from the server.

On Mint.com I frequently have to fetch JSON data from the server. I was using YUI’s Connection.js with a callback function for each request. Then when the request returned, I would populate a scoped instance of the JSON object, before performing a series of carefully choreographed page/JavaScript updates. While this works, it becomes more and more difficult as you modularize your JavaScript. One starts to have dozens of custom events and or accessor functions on objects, so that the data can be passed around between them; when it would be much simpler to just have one function to call, which always gives you the most up-to-date version of that JSON object, no matter where you are, who last updated that object, or whether it is in the middle of an AJAX request or not.

Three solutions came to mind when I thought about this problem: sleep the thread, use a synchronous request, or use the command pattern. Since, JavaScript does not have a sleep function and any attempt to emulate it (short of using the command pattern) has failed, the thread sleep method is out. A synchronous request would work, but it would also tie up the JavaScript processor until the request returned, and make the page temporarily unavailable to any users… not a good idea. So, we are left with the command pattern, where we pass in a function as the action and use the Lazy-Loading Callback Pattern to wait for the AJAX request to complete; or instantly return if the data is not stale.

Example 1: Data Singleton Object

Core.Widget.DataSingleton = (function() { // Module Private Variables var FETCHING = 'fetching', dataCache = {}, F = function() {}, that = null; // Module request namespace var req = { /** * Is the AJAX request callback for retrieving the popup DOM. * @method get * @param data {Object} Required. The data hash for the ajax request. * @private */ callback: function(data) { if (data.responseText) { var text = (data.responseText), json = YAHOO.lang.JSON.parse(text); json.batch(function(o) { dataCache[o.id] = o.data; }); } }, /** * Handles the AJAX request to retrieve the popup DOM. * @method get * @param id {String} Required. The name of the DOM node to fetch (url-name). * @param params {Object} Optional. An Object of additional parameters for request. * @private */ get: function(id, params) { var o = Array.is(params) ? params : []; // stuff the parameter object o[o.length] = 'jsonIdSet=' + id; o[o.length] = 'r=' + (new Date()).getTime(); var callback = { success: req.callback }; YAHOO.util.Connect.asyncRequest('GET', 'getJSON.php?' + o.join('&'), callback, null); } }; // Public methods and constants F.prototype = { /** * Attempts to call the process with the cached data, otherwise fetch it. * @method getPopup * @param id {String} Required. The ID of the data to fetch. * @param proc {Function} Required. The process function to pass data into. * @static */ call: function(id, proc) { // verify proper parameters if (! (String.is(id) || isType(func, 'function'))) {return null;} // if the cache exists, call the process if (dataCache[id] && FETCHING !== dataCache[id]) { proc(dataCache[id]); } // cache doesn't exist, initialize it else { if (FETCHING !== dataCache[id]) {that.init([id]);} // set an inverval to check for existence of the data //noinspection JSUnusedLocalSymbols var pointer = null; pointer = setInterval(function() { // cache now exists, stop interval and call process if (dataCache[id] && FETCHING !== dataCache[id]) { clearInterval(pointer); proc(dataCache[id]); } }, 250); } }, /** * Initializes the singleton data for this page. Call as early as possible. * @method init * @param idList {Array} Required. The ID list of data to fetch. * @param conf {Object} Optional. The configuration object. * @static */ init: function(idList, conf) { // verify proper parameters if (! Array.is(idList)) {return;} var cfg = Object.is(conf) ? conf : {}; // configuration if (! Array.is(cfg.params)) {cfg.params = [];} idList.batch(function(id) { delete dataCache[id]; dataCache[id] = FETCHING; }); // fetch the ids req.get(idList, cfg.params); }, /** * Refetches the data from the server. * @method init * @param idList {Array|String} Required. The ID or list of IDs for data to fetch. * @param conf {Object} Optional. The configuration object. * @static */ refresh: function(idList, conf) { that.init(Array.is(idList) ? idList : [idList], conf); } }; that = new F(); return that; })();

You will first need to setup a server request that returns your JSON object(s). I use the parameter ‘jsonIdSet’ to pass a list of JSON data objects to fetch. Your server response should return the following:

Example 2: Server Response

[ {"id": "task1", "data": {/*JSON data for task 1*/}}, {"id": "task2", "data": {/*JSON data for task 2*/}}, {"id": "task3", "data": {/*JSON data for task 3*/}}, ... ]

Before using DataSingleton, you need to initial the data by calling the public ‘init’ method. You should pass in an array of JSON data ids that your current page needs, and this data will be fetched from the server. The list of JSON ids will become a comma delimited string, which you will need to parse server-side. Client-side, if the data ever becomes stale, use the ‘refresh’ method to update the data (you can pass either an array of JSON ids or just a single id).

Once you have your server request setup and have initialized your JSON data ids, then you can start making calls for that data. Here is the syntax:

Example 3: Command Call Syntax

var $DS = Core.Widget.DataSingleton; // creating a shorthand name $DS.call('task1', function(json) { // code that requires 'json' });

The ‘call’ method ensures that the singleton instance of the data you are requesting exists, before executing the passed in command function, otherwise it will wait until the data is populated. If you have not initialized the requested JSON data id, it will go ahead and initialize it for you, but it is faster if the data is already initialized (that is why I load it ‘onload’). So far the only drawback, I have encountered to using this method, is that the command pattern is cumbersome to use until you familiarize yourself with it. However, this technique has made working with server-driven JSON objects much easier and helped me compact my code.

The DataSingleton Object has a dependency on YUI “connection.js”, and my “core.js”, “object.js”, and “array.js”. Here is an example page that uses this code.

posted by Matt Snider at 7:06 pm  

Wednesday, September 10, 2008

Absolute Position Layer

In my projects, I frequently find the need to absolute position a div somewhere on the page (on Mint.com we things like position popups and autocomplete/menu widgets). At one time absolute positioning elements was a real chore, as browsers treat absolute positioning differently, depending on where the element is in the DOM and the positioning of the parent elements (more on positioning: Position Everything). I needed a technique to that allowed me to position any number of elements absolutely, without having to worry about x-browser hacks. The solution is to insert a ‘layer’ div as the first element of body, which will parent all other elements needing to be position absolutely.

Example 1: HTML for Layer

<body> <div id="layer"></div> ... the rest of the page </body>

This layer then needs the following CSS:

Example 2: CSS for Layer

#layer { height: 0px; left: 0px; overflow: visible; position: absolute; top: 0px; width: 100%; z-index: 10; }

This CSS positions the layer, absolutely itself to the top left of the page and will work in all browsers (unless your body element is absolutely positioned). We give it a height of ZERO so that it doesn’t affect or cover up any portion of the DOM. The width of 100% is a fix for browsers where overflow doesn’t work right when the element has no width, and the z-index is set so that all children of ‘layer’ have a slightly higher z-index than the rest of the page (as you will probably be positioning over other elements on the page). The final key to make this all work is setting the ‘overflow’ to ‘visible’. That way, even though the children will have vertical dimensions greater than the ‘layer’ div, they will still be visible and not affect other elements in the DOM.

Now if you include ‘layer’ on your sites main template, then you always have the ability to add an x-browser enabled, absolutely positioned element. Simply append the element to the layer, position it to your liking, and manage whether it is visible or not. Here is a fun little layer example where I put a bunch of elements in layer and positioned them to look like the acid 2 test.

posted by Matt Snider at 10:19 am  

Saturday, September 6, 2008

String Buffer For Better Performance

Update
I survived my vacation in the Sierras! King’s Canyon is an awesome national park and I had a lot of fun hiking at 10,000+ ft (3,048+ meters) (although, my pinky toes did not enjoy my new boots). I noticed that many of you continued to visit, even though I wasn’t here, and I thank you for your interest. Starting next week, I will be resuming my previously schedule 2 weekly articles.

While I was gone, Google decided to launch their new browser Chrome, which looks impressive and is based on the Safari Webkit engine (so it is mostly standards compliant). If you haven’t checked it out yet, you should, because it is another browser that you will need to ensure works with your websites.

Article
Continuing our performance discussions, today we will be discussing a technique to improve JavaScript performance when concatenating strings. The problem occurs when concatenating large strings in JavaScript, or small strings repeatedly. According the JavaScript specifications, a string is an immutable object, so each time a concatenation occurs, a new object (and pointer to that object) must be created and mutated before being returned for use. So if you are concatenating a large string, you are creating multiple copies of a large object, and when repeatedly concatenating a small string, you are creating many needless objects. Both operations will slow down your JavaScript code and provide a very unfriendly user experience.

Here is an example of how not to concatenate a large set of strings:

Example 1: Inefficient Concatenation With +=

var sb = ''; for (var i = 0; i < 100000; i += 1) { sb += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; } alert(sb);

You could also write this using the ‘concat’ method instead of “+=”:

Example 2: Inefficient Concatenation With Concat

var sb = ''; for (var i = 0; i < 100000; i += 1) { sb = sb.concat('ABCDEFGHIJKLMNOPQRSTUVWXYZ'); } alert(sb);

FireBug profiled Example 1 concatenation technique with an average runtime of about 5159.375ms and Example 2 at about 5203.125ms (averaged over 10x each). As you might expect, Example 2 was slightly slower, most likely a result of the constant runtime cost of calling the extra function, ‘concat’.

Unfortunately, JavaScript does not have a built in StringBuffer object, but by using Array you can create your own buffer:

Example 3: Efficient Concatenation With Array.Push

var sb = []; for (var i = 0; i < 100000; i += 1) { sb.push('ABCDEFGHIJKLMNOPQRSTUVWXYZ'); } alert(sb.join(''));

And you can improve the performance of this technique if you know the Array indices, instead of calling ‘push’:

Example 4: Efficient Concatenation With Known Index

var sb = []; for (var i = 0; i < 100000; i += 1) { sb[i] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; } alert(sb.join(''));

FireBug profiled Example 3 concatenation technique with an average runtime of about 315.625ms and Example 4 at about 220.3125ms (averaged over 10x each). As you might expect, Example 4 was slightly faster, because we do not need to call ‘push’. Both example 3 and 4 are almost 20x faster than the straight concatenation technique.

Obviously, you could just create an Array anywhere you need to buffer a string, but if you want an optimized StringBuffer object for your JavaScript, here is what I came up with:

Example 5: StringBuffer Object

var StringBuffer = function() { this.buffer = []; this.index = 0; }; StringBuffer.prototype = { append: function(s) { this.buffer[this.index] = s; this.index += 1; return this; }, toString: function() { return this.buffer.join(''); } };

Doing the same operation using this StringBuffer object, will take about 448.4375ms. So it is obviously faster to just work with an Array, however it is not much slower to use the StringBuffer object if you prefer.

There are many examples of StringBuffer objects on the net (with an average runtime of 507.8125ms on the same dataset), but I have improved its performance slightly by including the ‘index’, instead of using “Array.push”. Keep in mind that you only need to buffer when you are repeatedly concatenating a string or with very large strings. Although, I have not tested across all browsers, my rule of thumb is to use a String Buffer anytime I am concatenating more that 5 times. To test these concatenation techniques yourself, see the Concatenation Test Page.

——–

Additional Notes
Further research led me to realize that Opera actually performs the “+=” concatenation faster than it does the buffer technique, but only marginally (just a few ms on the example page). Safari is also well optimized, only improving about 100ms using the buffer concatenation technique example. Internet Explorer is so bad at the “+=” concatenation that it takes minutes to complete the examples, not seconds. Google’s Chrome is the fastest by far, about 3x faster that Safari and Opera in our tests, except when using the ‘concat’ method, where it performs almost as badly ad IE. So to summarize: buffering is a win in all browsers except Opera (but only marginally worse in Opera), and a huge win for FireFox and Internet Explorer.

posted by Matt Snider at 5:42 pm  

Powered by WordPress