Matt Snider JavaScript Resource

Understanding JavaScript and Frameworks

Wednesday, May 7, 2008

Visual Graphing in JavaScript

While on vacation, I took some time to have fun writing a JavaScript tool that I have never seen before. While on the plane, I decided to explore graphing in JavaScript, using the old-school TI-85 as a visual model. Even though, I never seen this done before, JavaScript is a fast language and the only reason it may have not already been written, is a lack of purpose. Nevertheless, I felt writing this application was fun exercise and an opportunity to explore writing a graphing program.

To start, we have to consider the how to draw the graph that we will be drawing lines on. We need to draw the x and y axises, and the tick marks on those axises. By default, I assumed that most people would be use a square DOM element, but it can be configured to use rectangular shaped elements as well. When instantiating a new instance of the Grapher, use the following:

Example 1: Instantiating Grapher

var graphNode = $(’graph’); var config = { equation: ‘-5 + Math.pow(x, 2)’, // default is ” tickFreq: 10, // default xRange: 10, // default yRange: 10 // default }; var graph = Core.Widget.Grapher(graphNode, config); graph.redraw();

In this example, the graph will range from -10 to 10 x and y, showing a tick my every other even value in those ranges, which happens to be the defaults assumed by the Object. To support this, the graphNode element need only be square; the Object will manage scale. We are also passing in a default equation so a line is initially drawn with the graph.

Inside the Object, we have several internal objects: ‘node’ (the graph DOM), ‘config’ (configuration of graph), ‘points’ (array of line points), and ‘that’ (the closure safe scope); 5 constants for the axis styles, point styles, and tick styles; and two helper Functions: one to clear the graph complete (’clearGraph’) and the second to only clear the points of the current line (’clearPoints’). The ‘clearGraph’ method removes everything from the graph DOM, and the ‘clearPoints’ method iterates through each point and removes them, while leaving the graph intact.

Example 2: Private Helper Functions

/** * Clears all graph related DOM nodes. * @method clearGraph * @private */ var clearGraph = function() { while (node.firstChild) { node.removeChild(node.firstChild); }; }; /** * Clears all graph point DOM nodes. * @method clearPoints * @private */ var clearPoints = function() { for (var i = points.length - 1; 0 <= i; i -= 1) { points[i].parentNode.removeChild(points[i]); } points = []; };

When the page first loads, or the graph DOM first becomes available, we need to make it look more like a graph. For this reason, there is a private method ‘drawGraph’, that calls ‘clearGraph’, then rebuilds the nodes that causes the DOM to look like a graph:

Example 3: Drawing the Graph

/** * Draws the actual graph: axis’ and ticks. * @method drawGraph * @private */ var drawGraph = function() { clearGraph(); var xAxis = node.appendChild($D.createTag(’div’, {style: XAXIS})); var yAxis = node.appendChild($D.createTag(’div’, {style: YAXIS})); Dom.setStyle(xAxis, ‘top’, config.region.top + config.yMid + ‘px’); Dom.setStyle(xAxis, ‘left’, config.region.left + ‘px’); Dom.setStyle(xAxis, ‘width’, config.width + ‘px’); Dom.setStyle(yAxis, ‘top’, config.region.top + ‘px’); Dom.setStyle(yAxis, ‘left’, config.region.left + config.xMid + ‘px’); Dom.setStyle(yAxis, ‘height’, config.height + ‘px’); for (var i = 0; i < config.tickFreq; i += 1) { var xTick = node.appendChild($D.createTag(’div’, {style: XTICK})); var yTick = node.appendChild($D.createTag(’div’, {style: YTICK})); Dom.setStyle(xTick, ‘left’, config.region.left + config.xTickStep * (i * 2) + ‘px’); Dom.setStyle(xTick, ‘top’, config.region.top + config.yMid - (config.tickFreq / 2) + ‘px’); Dom.setStyle(yTick, ‘left’, config.region.left + config.xMid - (config.tickFreq / 2) + ‘px’); Dom.setStyle(yTick, ‘top’, config.region.top + config.yTickStep * (i * 2) + ‘px’); } };

Once the DOM is clear, we create the x and y axises using the internal ‘config’ Object where we store many cached values about the DOM node and other values computed when the graph Object is instantiated. Both axises are lines that are positioned in the center of the node according to their respective dimension, and the height/width of the graph DOM node. Then we iterated through the tick frequency count and add the tick marks along the axises.

Finally, we need a method to draw lines according to a provided equation containing 1 or more ‘x’. For now, we simply eval the equation, but I intend to validate the equation to only support ‘+’, ‘-’, ‘/’, ‘*’, ‘%’, and all methods on the Math Object. Here is the method to draw the line:

Example 4: Drawing the Line

/** * Draws the line for the equation in a given color. * @method drawLine * @param eq {String} Required. The equation to draw. * @param color {String} Optional. The color of the line. * @private */ var drawLine = function(eq, color) { // iterate on the posible x points, stop when limit is reached or y exceeds grid for (var i = 0; i < config.width; i += 1) { var x = config.xStep * (config.xMid + (config.width > config.xMid ? -i : i)); var y = eval(eq.replace(/x/gi, -x).replace(’–’, ”)); var y2 = config.yMid - (y * config.yTickStep); // stop when exceeding graph range if (y > config.yRange || y < -config.yRange || isNaN(y)) {continue;}; var point = node.appendChild($D.createTag(’div’, {style: POINT})); Dom.setStyle(point, ‘left’, config.region.left + i + ‘px’); Dom.setStyle(point, ‘top’, config.region.top + y2 + ‘px’); if (color) { Dom.setStyle(point, ‘background-color’, color); } points[points.length] = point; } var k = points.length; // iterate on each point, determine if and fill all the y positions for (var j = 0; j < k - 1; j += 1) { var p1 = Dom.getRegion(points[j]); var p2 = Dom.getRegion(points[j + 1]); var useP2 = p2.top > p1.top; var diff = Math.abs(p2.top - p1.top); if (1 < diff) { for (var m = 1; m < diff; m += 1) { var left = m < diff / 2 && useP2 ? p1.left : p2.left; var top = (useP2 ? p1.top : p2.top) + m; var p = node.appendChild($D.createTag(’div’, {style: POINT})); Dom.setStyle(p, ‘left’, left + ‘px’); Dom.setStyle(p, ‘top’, top + ‘px’); if (color) { Dom.setStyle(p, ‘background-color’, color); } points[points.length] = p; } } } };

The method requires an equation and also excepts a color, if you wish to not use black to draw a line. This is forward thinking, so that, once I figure out the best way to do it, each graph will be able to support multiple lines. We iterate through each 1px in the width of the graph DOM node. So we first compute ‘x’ (value of x at pixel i), ‘y’ (value produced by the equation), and y2, the actual top position of the point. These values are rather tricky, because computing x changes depending on whether we are on the left or right of the y-axis, and computing y is done using eval and regex replaces. I learned that the equation ‘-x’ would throw an error computing ‘y’, because it had double negatives, so I fixed that using a simple regex.

When iterating, we ‘continue’ when the value of ‘y’ does not fit inside the configured range, then we create the point DOM node and position it (we would also color it, if a color was provided), and cache the point. Then we iterate through the points and fill in each pixel between point n and ‘n - 1′ when they are more than 1-pixel apart in the y-axis, creating a smooth line. Whichever point is higher determines which point to use as the top offset, and since each point is only 1-pixel separated on the x-axis, we also use the higher point to determine which pixel should be used for the ‘left’ style, depending on how close the fill point is to the point used for the ‘top’ style.

When you bring it all together, you get something like this example: Graph Test. I plan on making the graph colors more customizable, cleaning all equation strings to ensure no tomfoolery, additional graph stuff (like titles, legends, labels, etc.), and supporting multiple lines on a single graph.

posted by Matt Snider at 11:43 pm  

Sunday, April 27, 2008

removeNode and appendNode Methods

First, for everyone following the JavaScript Game Engine project, I am putting it on hold for now. Building an engine to support dynamic games requires a lot of work… a lot more more time than I currently have to devote to the project, and I do not want to release code of questionable quality. I will, however, revisit it from time to time and complete the article series as I am able.

For todays article, I would like to introduce two of my favorite DOM extension methods, “removeNode” and “appendNode”. They are basically, simple wrappers for “removeChild” and “appendChilde” with the added functionality to animate the addition/removal of a DOM nodes. I find these methods to be very useful in providing feedback to a user when they are taking an action that modifies the DOM. Most of the time (and the default animation of this method) an opacity fade does the trick, making the addition/removal of a DOM node tangible to the user, but sometimes you need a different animation, so the methods accept animation arguments as optional parameters. I build these methods on top of YUI animator and either add them to the “YAHOO.util.DOM” or Document Object.

Example 1: AppendNode

Document.appendNode = function(root, elem, ap, fn) { var node = $(elem), parent = $(root); if (parent && node) { parent.appendChild(node); var animParams = (isType(ap, ‘object’)) ? ap : {opacity: {from: 0.25, to: 1}}, anim = new YAHOO.util.Anim(node, animParams, 0.5, YAHOO.util.Easing.easeIn); anim.onComplete.subscribe(function() { if (fn && isType(fn, ‘function’)) {fn(node);} }); anim.animate(); } return node; };

The “appendNode” method has two required parameters: the ‘root’ element to append to, and the ‘node’ to append with. As usual, I have written the “YAHOO.util.Dom.get” method as the ‘$’ shortcut method. By default the method will animate with a fade in from 0.25 opacity to 1 opacity, but you can override it by passing in your own arguments for YUI animation. If you want an event to occur after the animation, then provide a Function as the last argument, however it is also not required; this Function will be passed the newly appended ‘node’ as its only parameter.

Example 2: RemoveNode

Document.removeNode = function(elem, ap, fn, isRemoveListeners) { var node = $(elem), parent = node.parentNode; if (parent) { var animParams = (isType(ap, ‘object’)) ? ap : {opacity: {from: 1, to: 0.25}}, anim = new YAHOO.util.Anim(node, animParams, 0.5, YAHOO.util.Easing.easeOut); if (isRemoveListeners) {Event.purgeElement(node, true);} anim.onComplete.subscribe(function() { parent.removeChild(node); if (fn && isType(fn, ‘function’)) {fn();} }); anim.animate(); } };

The “removeNode” method is very similar to the “appendNode” method, with the noted exception that the animation must occur before the actual removal of the child-node. You do not need to pass in the ‘root’ Element as we can extract that directly from the provided node. Lastly, if you have attached any listeners to the node or its children, you should probably remove them. I leveraged YUIs “YAHOO.util.Event.purgeElement” (shortcut name of “Event.purgeElement”) method for this, which will recursively remove the events attached to your node and its children, when you pass true as the last parameters (isRemoveListeners), otherwise no events will be removed.

Again, these methods are simple, but very handy. I have found it is almost always useful to provide feedback to users when they are adding/removing DOM nodes, and these methods abstract the logic away, so that I can do just about anything I need. If you’d like to give them a try, I have provided a test page.

posted by Matt Snider at 12:34 pm  

Tuesday, April 15, 2008

Document CreateTag Method

As I worked on the GameEngine improvements, I realized that it would be nice to have a menu and several other DOM elements. Since the engine will be standalone, it is necessary to dynamically create the DOM elements required to run the game. For this task, I have use a method “document.createTag()”, which I often refactor for various projects. In its most basic form, it looks like the following:

Example 1: document.createTag

YAHOO.lang.augmentObject(document, { /** * Creates and returns an html element and adds attributes from the hash. * * @method createTag * @param tagName {String} Required. Tag name to create. * @param h {Object} Optional. The hashtable of attributes, styles, and classes; defaults is empty object. * @return {Element} The newly created element; returns null otherwise. * @static */ createTag: function(tagName, h) { var Dom = YAHOO.util.Dom, node = document.createElement(tagName), hash = isType(h, ‘object’) ? h : {}; // iterate through the possible attributes for (var k in hash) { var v = hash[k]; k = k.toLowerCase(); if (isType(v, ’string’) || (’style’ === k && isType(v, ‘object’))) { switch (k) { case ‘classname’: case ‘klass’: case ‘class’: case ‘cls’: Dom.addClass(node, v); break; case ‘cellpadding’: node.cellPadding = v; break; case ‘cellspacing’: node.cellSpacing = v; break; case ‘colspan’: node.colSpan = v; break; case ‘checked’: case ‘disabled’: node[k] = v; break; case ‘rowspan’: node.rowSpan = v; break; case ’style’: // iterate through the style object for (var t in v) { var s = v[t]; if (isType(s, ’string’) || isType(s, ‘number’)) { Dom.setStyle(node, t, s); } }; break; case ‘innerhtml’: case ‘text’: if (isType(v, ’string’) && ! v.match(/< .*?>/) && ! v.match(/&.*?;/)) { node.appendChild(document.createTextNode(v)); } else { node.innerHTML = v; } break; default: node.setAttribute(k, v); break; } } }; return node || null; } });

To use this method, simply include it in your JavaScript source files, then call “document.createTag” passing in two parameters: 1st parameter is the tag name you wish to create, and the 2nd is an Object where the keys are tag attribute names and the values are the attribute values. Using a ‘for … in’ loop we iterate on each key in the Object, evaluating only those values that are strings and numbers, or an object in the case of the style attribute. The switch statement then, normalizes the keys, so the key ‘checked’ does the same thing as ‘cHeCkEd’, because capitalization does matter.

Over the years I have added each case of the switch statement based on issues experienced in various browsers. Most attributes seem to be applied just fine, when you use the ’setAttribute’ method, so this is the default case. Some attributes such as ‘checked’ and ‘disabled’ do not work in all browsers with ’setAttribute’, so they are applied only with dot notation. Some attributes such as ‘cellPadding’ and ‘cellSpacing’ are case-sensitive in certain browsers (and capitalizing them doesn’t break the browsers that lower-case worked in), and/or can only be applied via dot notation, so we special-case each of them. The word ‘class’ is a reserved word in at least IE, so you cannot do “object.class”, instead you need to use “object.className”, and this seems to be supported by all browsers; I also support various ways of storing the classname as a key, because different projects have different naming conventions. Style is a special-case that must contain an object, where the keys are style names, and the values are what you want to set the style to (strings and integers are acceptable). Lastly, the ‘text’ or ‘innerhtml’ key will attempt to append the provided value as a text node, unless the value: isn’t a string, contains HTML elements, or contains HTML special characters; in those cases it uses innerHTML, because it will render HTML and special characters correctly, while a text node will simply print the text as is.

Now, this method probably isn’t perfect, but it works with all the browsers that I have ever needed to support. Feel free to chime in, if you know of any other special-case situations or browsers that you believe this method won’t work with.

This solution is homegrown, and there may exist better DOM node creation methods, with more exposure and support. I know the Framework Ext.js has a DOM node creation method, and I wouldn’t be surprised if Dojo or jQuery have a plugin solution as well.

posted by Matt Snider at 9:46 pm  

Tuesday, April 8, 2008

Movable DOM Node Widget

One of my favorite pastimes is playing and collecting video games, which combined with another of my passions, JavaScript, drives me to figure out ways to port simple gaming engines into JavaScript. JavaScript is not the best language for writing such engines (at least not in the browser), so I do not spend much time on this. However, I had a little time and revisited an old project, replacing the ancient coding with some simple YUI calls. When I wrote this, I was thinking of the original Dragon Warrior game, where you had a character that moved around the screen.

Think of the black rectangle as the character. Using YUI animation utility and a custom Object ‘Movable’, I listen to arrow keydown events and trigger animations on Movable. Because YUI manages duplicate animations on the same style of an Element (preventing it, until the previous animation completes), this was much simpler than code I had previously written. Also, the YUI animation code allows for diagonal movement if you press two perpendicular arrow keys within the half-second animation period. Try moving the black box around the screen on the Movable Object Test Page. Here is the code for the Movable Object:

Example 1: Movable Class

Core.Widget.Movable = function(id, useAnim) { // Local Variables var F = function() {}, node = $(id), that = null, anim = null; // shortcut for viewport size var getWSize = Core.Client.getViewportSize; // Public Variables F.prototype = { /** * Move the object down. * * @method down * @public */ down: function() { var r = Dom.getRegion(node), w = r.bottom - r.top, o = r.bottom, s = getWSize(); // prevent exceeding viewport height if (s.y < o + w) {o = s.y - w;} if (useAnim) { anim = new YAHOO.util.Anim(node, {top: {to: o}}, 1, YAHOO.util.Easing.easeOut); anim.animate(); } else { Dom.setStyle(node, 'top', o + 'px'); } }, /** * Move the object left. * * @method left * @public */ left: function() { var r = Dom.getRegion(node), w = r.right - r.left, o = r.left - w; // prevent exceeding viewport width if (0 > o - w) {o = 0;} if (useAnim) { anim = new YAHOO.util.Anim(node, {left: {to: o}}, 1, YAHOO.util.Easing.easeOut); anim.animate(); } else { Dom.setStyle(node, ‘left’, o + ‘px’); } }, /** * Move the object right. * * @method right * @public */ right: function() { var r = Dom.getRegion(node), w = r.right - r.left, o = r.right, s = getWSize(); // prevent exceeding viewport width if (s.x < o + w) {o = s.x - w;} if (useAnim) { anim = new YAHOO.util.Anim(node, {left: {to: o}}, 1, YAHOO.util.Easing.easeOut); anim.animate(); } else { Dom.setStyle(node, 'left', o + 'px'); } }, /** * Move the object up. * * @method up * @public */ up: function() { var r = Dom.getRegion(node), w = r.bottom - r.top, o = r.top - w; // prevent exceeding viewport height if (0 > o - w) {o = 0;} if (useAnim) { anim = new YAHOO.util.Anim(node, {top: {to: o}}, 1, YAHOO.util.Easing.easeOut); anim.animate(); } else { Dom.setStyle(node, ‘top’, o + ‘px’); } } }; // Scoped Operations that = new F(); return that; };

The ‘Movable’ class is designed to work with any DOM node that is absolutely positioned and smaller than the viewport. Simply pass the DOM node as the first parameter and whether you want the movements to be animated as the second. There is any number of reasons that you may need an Element on a page that can move up and down, based on its size, so you needn’t only think of it as part of a gaming engine. Keep in mind that I have used shorthand notation for ‘YAHOO.util.Dom’ (Dom), and ‘YAHOO.util.Dom.get’ ($). This widget also requires that you include ‘core.js‘.

Lastly, if you want to have it animate on the page, like in my example, you will need to use YUI Event package to listen to all keydown events on the ‘document’. Here is the code that I used:

Example 2: Document Keydown Listener

Event.addListener(document, ‘keydown’, function(e) { var test = Core.Widget.Movable(obj, true); switch (Event.getCharCode(e)) { case 37: // left arrow test.left(); break; case 38: // up arrow test.up(); break; case 39: // right arrow test.right(); break; case 40: // down arrow test.down(); break; } });

Again, I have shorthanded the ‘YAHOO.util.Event’ package to just ‘Event’. This code simply listens for all keydown events, ignoring all but the arrow keys. When any key is fired, it calls the appropriate method on ‘Movable’, which then handles the movement.

Now, the next step of the engine is to have the Object move over something, such as a map or a game board, firing custom events that the engine listens to. My next article will show how the ‘Movable’ object can be used to build a simple game.

posted by Matt Snider at 10:45 pm  

Saturday, April 5, 2008

Catching All JavaScript Errors in All Browsers

I recently wrote an article about the JavaScript “onerror” event. The “onerror” event is really powerful in that you can prevent all JavaScript errors from ever being shown to the user, as well giving you the ability to capture each error and send it to the server for logging. The major downside is that only the major browsers (IE and FireFox) support ‘onerror’, because of this you miss all the goodness in Safari, Opera, and other smaller browsers. I spent some time over the last week and devised a solution that will allow you to catch all errors in all browsers. However, it is rather onerous, so I present it here, but hope that some of you can suggest ways to help improve it.

The sure-fire way to capture all errors in all browsers, is to put a try/catch statement inside each Function. Obviously, doing so would be ridiculous, even though you are trying to solve a huge problem: preventing and capturing JavaScript errors. However, you can instead create a proxy Function that handles the try/catch wrap for you. Instead of calling a Function/Method directly, you will use the proxy Function throughout your code. Inside the proxy, we put a try/catch statement to capture exceptions. The catch statement will send an AJAX message to log the error server-side, and return NULL. Otherwise the proxy Function will return the same value that would have normally been returned by executing the passed method. While, still onerous, this is probably a do-able solution:

Example 1: The Call_User_Method_Array Function

/** * Calls the method, scoped to the passed object, with the set of parameters. * * @method call_user_method_array * @param method_name {String} Required. The method name on ‘obj’. * @param obj {Object} Required. The object owning the method, that the method will also be scoped as. * @param paramarr {Array} Required. The collection of parameters. * @static */ var call_user_method_array = function(method_name, obj, paramarr) { try { return obj[method_name].apply(obj, paramarr || []); } catch (e) { window.onerror(e.message, window.location, ‘-1′); // use -1 for exceptions return null; } };

I modeled the method after a similar method used by PHP. The method should be placed early in global namespace, and I would recommend abbreviating the name to no more than 3 characters as it will be called many, many times. Below are a few examples of how to use this method:

Example 2: How to Use Call_User_Method_Array

// calling a method on window var testFunc = function() {/* do something */ }; call_user_method_array(’testFunc’, window); // calling a method on a string var testString = ‘This is a string’; var index = call_user_method_array(’indexOf’, testString, [’string’]); // calling a nested method var bool = call_user_method_array(’isIE’, Core.Client);
posted by Matt Snider at 3:26 pm  

Wednesday, April 2, 2008

JavaScript Gif-Like Animation

First, I like to say how much I hate spammers. I’d even start believing in the beyond again, if God would only reserve a special place in hell for spammers. 35,000 spam comments and counting… Because of this, I’d like to apologize in advanced if I accidentally delete one of your comments. Also, the first time you comment, it goes into an approval pool, so if you don’t see it right away, please give me a day or two to approve it.

I have been pretty busy prepping for a big announcement that we will be making at Mint.com this week, so I did not have as much time to prepare a blog article as I would have liked. So, I decided to have a little fun today and created a JavaScript driven GIF-Like animation tool.

The basic idea is that you take each frame that you would use in an animated GIF and instead create a separate GIF for each. The next step is to put an IMG tag on a page and set its source to the first image. Then at the end of the webpage inside a SCRIPT tag, instantiate GifAnimator, passing in the ID of the IMG tag as the first parameter, and an Array of image sources (your frames) as the second image. Then, go ahead and call the ’start’ method of your new GifAnimator Object to begin animation.

Example 1: Instantiating GifAnimator

<script type=”text/javascript”> var anim = new Core.Widget.GifAnimator(’animator’, [ ‘assets/images/animator/A.gif’, ‘assets/images/animator/B.gif’, ‘assets/images/animator/C.gif’, ‘assets/images/animator/D.gif’, ‘assets/images/animator/E.gif’, ‘assets/images/animator/F.gif’ ]); setTimeout(function() { anim.start(); }, 1000); </script>

Download GifAnimator.js and include it into your project to make Example 1 work. I have created a test page as well.

Hopefully, you will remember that I started this article by saying that I was playing. There is little reason to replace GIF animation with a JavaScript solution, as JavaScript timeouts are fairly inaccurate, and they have a tendency to pause every few seconds as the processor catches up with the browser.

posted by Matt Snider at 10:52 am  

Saturday, March 29, 2008

Window onError Event

Window.onerror is an obscure and little used event with surprising usefulness. Before the days of FireBug it was difficult to debug, in each browser, where an error occurred and why. Now that we have FireBug, most JavaScript works wonderfully in FireFox and decently in other browsers. And even with the robust debugging, there will still be user machines that were not tested against, who see JavaScript errors that you can never capture. The ‘onerror’ event allows for you to assign a Function to be called every time an error occurs in your JavaScript code, passing into the function the browser message, URL of the current page, and the line number that triggered the error, which improves your ability to debug in any browser. In addition to those benefits, assigning a Function to ‘onerror’, also allows you to prevent errors from bubbling to the browser, and showing those annoying browser popups that occur on JavaScript errors.

Surprisingly this event has been around since IE 4, Netscape 3, and FireFox 1, so it is well supported by current browsers, even though it is rarely used. Here is a simple example of how to use this method:

Example 1: Simple ‘onerror’ Function

// declare this early in your codebase window.onerror = function(message, url, lineNumber) { // code to execute on an error return true; // prevents browser error messages };

Something I am experimenting with is to include this code in a file (”error.js”) right after I initialize my Framework. “error.js” contains my “onerror” listener, catching all errors and exception, which I can then use to send AJAX messages to the back-end to log all JavaScript errors. Moreover, if you use a client detection method (core.js (see Core.Client Object), then you can also log the browser and OS that caused the error. Using YUI connection manager you would do something like:

Example 2: Error Logging

window.onerror = function(message, url, lineNumber) { var params = []; params[0] = ‘browser=’ + Core.Client.browser; params[1] = ‘data=’ + navigator.userAgent + ‘|’ + navigator.vendor + ‘|’ + navigator.platform; params[2] = ‘lineNumber=’ + Core.Client.message; params[3] = ‘message=’ + Core.Client.lineNumber; params[4] = ‘os=’ + Core.Client.OS; params[5] = ‘url=’ + Core.Client.url; params[6] = ‘version=’ + Core.Client.version; try { YAHOO.util.Connect.asyncRequest(’GET’, ‘logJavaScriptError.event?’ + params.join(’&'), function() {}, null); } catch(e) { // squelch, because we don’t want to prevent method from returning true } return true; };

In this case you are logging the line number, url, and browser error message, in additional to client information. I also go ahead and send all possible browser variables, for future parsing in case I could not properly discern the browser programatically. The AJAX request, is simply a fire-and-forget pattern, because we don’t really care if it is successful or not, we assume that 99% of the time it will succeed, so that these errors are properly logged. If there is an exception thrown in the AJAX code, say by YUI because the URL is not found, then this method would not completely execute. However, if you wrap the AJAX in a try/catch, you can ensure that the end of the method is reached and ‘TRUE’ is returned, to prevent errors from being shown to the browser.

I have created a test page, which tries to use a non-existant variable, thereby throwing an error. One thing you’ll probably notice is that you don’t get that pesky JavaScript error message in IE, however, you also do not get any FireBug goodness in FireFox. For FireBug to detect errors, the error must be allowed to bubble to the browser, so you will probably want a constant to turn off/on a debug mode, so that you can still use FireBug for development.

I also played around with using Framework Event handlers, however, none of the Frameworks I tested (YUI, MooTools, and Prototype) special-case an event handler for “window.onerror”. There are two problems with “window.onerror” that requires special-case event handling. The first problem is that when using event handlers, the callback Function will only be passed the Event Object, instead of the 3 parameters that should be passed. Second, returning ‘TRUE’ does not stop the error (as the callback Function is wrapped), nor does stopping propagation and/or bubbling, so you cannot prevent errors from bubbling to the browser. You will have to assign your error handling method, directly to “window.onerror”, and replace any implementation that your Framework might use.

Fortunately, after doing a quick search through those sample Frameworks, I found that only YUI attempts to implement the “window.onerror” code, and only in their “logger.js” file. In the definition of the Logger Object, you can specify whether to use an ‘onerror’ callback or not. I believe then, it is relatively safe, to play around with this event.

posted by Matt Snider at 6:38 pm  

Tuesday, March 25, 2008

Session Driven Back

One of the biggest issues I encountered while refactoring my projects to work without the need of JavaScript, is directing the user back to the page they were previously on. This is especially important for form submission and canceling. I look around the web and mostly saw comments directing programmers to use JavaScript to handle back. However, the point of the exercise is to not require JavaScript for anything.

I then spent some time looking at Server Frameworks (like Spring and Rails) that include form managers, before I came up with a light-weight, session-driven solution (example written in JAVA) that I am now using on my projects. These two methods should be added to your base controller, or servlet equivalent, that has access to HttpServletRequest:

Example 1: Server History Code

private void setupCache(HttpServletRequest request) { Boolean noCache = “T”.equalsIgnoreCase(request.getParameter(request, “noCache”)); Integer cacheMaxLength = 3; if (! noCache && “get”.equalsIgnoreCase(request.getMethod())) { List<String> h = (List<String>) request.getSession().getAttribute(”HISTORY”); String url = request.getRequestURI() + “?” + request.getQueryString(); // ensure history cache exists if (null == h) { h = new ArrayList<String>(); } // remove cache greater than max size else if (cacheMaxLength < h.size()) { h.remove(0); } // prevent double caching on refresh if (0 == h.size() || ! h.get(0).equals(url)) { h.add(url); request.getSession().setAttribute(”HISTORY”, h); } } } protected String getHistory(int i) { List<String> h = (List<String>) request.getSession().getAttribute(”HISTORY”); if (null == h || i >= h.size()) {return null;} return h.get(h.size() - i - 1); }

All you need to do is call “setupCache” at the beginning of all your controllers that you want in the history, and then call “getHistory” with the index of the history item you wish to forward to (1 is the last page) to retrieve that pages URL. If you ever want a page to not cache, then pass it the query parameter “&noCache=T”. I also don’t cache “POST” requests, because they are form submission. Assuming neither of these conditions are true, we then add the current URL to the List object that holds our history on the session (mapped as ‘HISTORY’).

When adding to the cache, we limit the cache size to 3, because you do not want the session object per user to grow indefinitely, and I generally only care about the last page. However, you could easily increase this to 5 or 10 by replacing the constant value of “cacheMaxLength”. Lastly, there is no reason to cache the same URL twice, as this usually happens when the user is refreshing the same page, so we ignore duplicate URLs.

Retrieving a URL from the history cache, is as easy as calling “getHistory” with an index; generally, 0 = current page, 1 = last page, 2 = page before last, etc. To do a redirect, you can simply do the following:

Example 2: Response Redirect

String url = getHistory(1); response.sendRedirect(url);

So to summarize, all your view requests are cache by their URL, but only the current and last 2 pages, and you can prevent a page from being cached by passing the “noCache” parameter equal to “T”. In order to retrieve the last page, simply call “getHistory(1)” for the URL.

posted by Matt Snider at 10:56 am  

Tuesday, March 18, 2008

Triggering A Popup On Unload

First, let me say that there are very few good reasons to ever use this technique and the last thing I want is to see the re-institution of the popup mania that plagued the web before the advent of the popup blocker. However, you might need to stop the user before they leave your site to inform the user of: unsaved data, an exit survey about your product, something they may have earned/won (I mean really won something, not one of those sign up for 6 things and get an ipod scams), etc. Keep in mind that popups should be used sparingly, as: most people find them annoying, many browsers will automatically block them, and users generally distrust sites with popups. That said, let me explain how best to trigger a popup onunload.

First, most browsers now automatically block popups when not triggered by an anchor tag event, with the exception of the IEs of the world, as any popup not fired by a user action (onclick) are deemed to be malicious. That means that you should inform your users to disable their popup-blockers for your site and let them know that what to expect, or few non-IE users to ever see the popups. That said, the two JavaScript native popups, alert and confirm, will never be blocked and should be used as a first solution whenever possible. For example, suppose you want to inform the user that they have unsaved data:

Example 1: Using Confirm

YAHOO.util.Event.addListener(window, ‘beforeunload’, function(e) { if (! confirm(’You have unsaved data on this page, leave this page? Clicking “yes” to continue will loose your changes forever.’)) { YAHOO.util.Event.stopEvent(e); } });

To let users know that they have unsaved data, you probably never need anything more than that. However, what if you have a web-service, receiving thousands of unique visitors a day, but only a fraction of those are actually signing up for your service. The biggest question you would have is, what can I do better to reach these potential users. In this case you might consider an exit survey:

Example 2: Exit Survey

YAHOO.util.Event.addListener(window, ‘beforeunload’, function(e) { window.open(’urlofyoursurvey’, ’surveywindow’, ’status=off,toolbar=off,location=off,menubar=off,directories=off,resizable=off,scrollbars=off,height=800,width=800′); });

FYI: A great, free service for making quick, custom surveys is Survey Monkey.

However, like I said before, this is going to be blocked by the vast majority of browsers. But, did you know there is a better way that most browser will not block:

Example 3: Exit Survey on Anchors

var allowedDomains = [’mattsnider.com’]; var isAllowedDomain = function(href) { for (var i = allowedDomains.length - 1; 0 <= i; i -= 1) { if (-1 < href.indexOf(allowedDomains[i])) { return true; } } return false; }; var showPopup = function() { window.open(’urlofyoursurvey’, ’surveywindow’, ’status=off,toolbar=off,location=off,menubar=off,directories=off,resizable=off,scrollbars=off,height=800,width=800′); }; YAHOO.util.Event.addListener(document, ‘onclick’, function(e) { var targ = YAHOO.util.Event.element(e); if (targ.href && ! isAllowedDomain(targ.href) { showPopup(); } } });

In Example 3, we are listening to all clicks on the document, but ignoring everything that is not an anchor tag. The ‘allowedDomain’ Array and ‘isAllowedDomain’ test Function are provided for the case where you do not want to show your exit survey if the user navigates to another page of your site (in my case all urls with the domain ‘mattsnider.com’). Because we have attached the event to onclick, most browsers will not block this popup, giving you greater visibility among different user segments. However, this technique misses people who navigate away from your site using any means other than clicking on a link. To capture those users you will need to attach an event to the onunload:

Example 4: Exit Survey Onunload

YAHOO.util.Event.addListener(window, window.onbeforeunload ? ‘beforeunload’ : ‘unload’, function(e) { if (0 > YAHOO.util.Event.getPageY(e)) { showPopup(); } });

First, we are attaching a listener to ‘onbeforeunload’ or ‘onunload’, depending on whether the browser supports ‘onbeforeunload’ or not. In the callback function we test to see if the Y-coords of the unload event are less than ZERO, because most browsers will return a negative number if the unload event is triggered by any user action above the actual page, but still within the browser (back/forward, changing the url, closing the window/tab, etc.). This way, we won’t double trigger the popup if the user leaves the page by clicking on a link. However, Example 4, will be blocked by most popup blockers, and is it is just a fail-safe to try and reach the largest user segment possible.

Lastly, remember not to abuse this knowledge. Please do not place gratuitous popups on your pages (and if you do, definitely, don’t credit me for them). Also, keep in mind that I have used YUI for this example, but only for the Event handling methods, which could be easily replaced with a Simple Event Package or your favorite JavaScript Framework.

posted by Matt Snider at 9:12 am  

Saturday, March 15, 2008

Accessibility And JavaScript

I probably could link to a million articles about this topic, but rather than overload you with information, I will instead try to sum up my thoughts into a short article. Web Accessibility is a movement/strategy to design websites to be usable by all web users, including the disabled and those surfing the web with technologies like JavaScript and Flash turned off. The use of JavaScript is often in direct conflict with these principles, as people rarely enhance their sites with JavaScript, instead they use JavaScript as a means of interacting with certain parts of their site. Those parts, then are considered to be inaccessible.

For those of you who build web application, such as Mint.com, that must behave like a desktop application, it can be a real chore to make your site accessible. You might even be so bold as to say, “My target audience has JavaScript enabled, and if they don’t, then they just can’t use my product.” We should all avoid such arrogance and realize that building accessible sites is the best way to be both forwards and backwards compatible, and it’s the best way to reach the largest audience. Plus, because so many sites are inaccessible, you can still use the fact that your site is accessible as a marketing tactic.

If you are asking yourself, “How can I make my site accessible?” The rest of this article is for you. I will focus on a 3-step techniques to fix an inaccessible site, but these steps could also be used for the site-design process.

Step 1: Planning

You must first think about your website and all the interactions you have now requiring JavaScript. For this example, I will be using the Popup system I employed on Mint.com. Write down the following information about each JavaScript event: how the event is triggered, what interaction your JavaScript does, and what the goal of this feature is. Probably 90%+ of the logic performed by JavaScript on your site can be done without JavaScript. For this step, a few things you will need to forget about are: animations, AJAX, and event triggers not on anchor tags.

For the Popups, we often use them as tools to inform the user that something is happening or to allow the user to edit some information on that page. They are triggered by onclick events and AJAX requests. In the Web 1.0 era users would have needed to be forwarded to another page for click events and posting alerts. So that is what I need to do, create additional site pages that do exactly what the Popups do, but without needing JavaScript.

Step 2: Building

Take all the interactions that you wrote down in the first step and build out the server-driven pages that do the exact same thing. Remove the JavaScript from your site and hook up the pages that you have written to your forms and anchors. Then drive through the site and ensure than everything works, if not a nicely as before.

In the Popup example, I will need to ensure that all user editing and alerting can be handled by server-driven pages, instead of popups. We actually, are not far away from this, as all the Popups on Mint are already server driven.

Step 3: JavaScriptification

Your site is now usable by anyone without JavaScript, but you have lost the Web 2.0 sparkle; let’s put it back in. Start including your JavaScript back in, one page at a time, and revisiting each of those interactions that you originally wrote down. First ask yourself, does it still make sense to use JavaScript for this interaction? If so, then go right ahead and re-hook in your JavaScript events. Since your forms and anchors will request pages that accomplish the task, you simply need to use JavaScript to do make those same requests, intercepting the response and displaying how you originally wanted. Only users with JavaScript enabled, will experience the desktop-like version of your site, but those without will still be able to completely use your site.

For the Popups, if I was requesting a form, then I would simply make an AJAX call to the page that I build for Step 2, cut out the form, and insert it into the Popup framework. When the user posts that form, I will intercept it using JavaScript, make another AJAX call to the same place that the form would have normally posted and parse the response, showing a success or error state. For alert dialogs, I will again make an AJAX request to a page build in Step 2 and insert it into the Popup framework, then capture the ‘okay’ and ‘cancel’ buttons.

……………..

That’s it. Obviously, each step can take a while (depending on the site of your site), but taking the time to accessify your site will ensure that you not only support the widest audience, but also, employ the most forward thinking approach. If in the future, if you decide to move from JavaScript to another client-side technology, you have only to replace the JavaScript code, not completely rewrite your application.

For more information, checkout:

Web Accessibility Initiative
Wikipedia: Web Accessibility
10 Reasons Clients Don’t Care About Accessiblity
Quirksmode

posted by Matt Snider at 12:39 pm  
Next Page »

Powered by WordPress