Matt Snider JavaScript Resource

Understanding JavaScript and Frameworks

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  

Friday, March 21, 2008

News - New Mint, YUI 2.5.1, JSON, IE8

New Version of Mint

A new version of mint went live last night. Most of this release was a massive bug scrub and preparation for future releases. However, we have launched an IRA center for those of you looking to put a little money towards retirement, or reduce your taxable income.

Ira Center

YUI 2.5.1

This release introduces YUI configurator and contains additional AIR support, however, it is primarily a bug fix release, cleaning up the issues with version 2.5.

YUI 2.5.1 Released: Improved AIR support, JSON security patch, YUI Configurator, and bug fixes

JSON.js Security Improvement

Douglas Crockford updated his JSON.js with better security to prevent additional unsafe strings from being evaluated.

JSON.js

IE 8 Announcements

There is so much going on with IE 8 that it is hard to follow it all. Fortunately, the Microsoft team has been really good about documenting IE 8, every step of the way. If you haven’t been following, check it out:

IE Blog

I’m Biking to Work Now!

Woot! I only live 5 miles from the office and have been meaning to start biking, but have been reticent to buy a new bike, since my last was stolen. However, with gas prices on the rise and my inability to find time to exercise, it seemed like the right time. Now, I am getting 40 minutes of exercise each day (20 min each way) and saving $40 in gas, every two weeks. This is a seriously sweet investment:

Trek 7.5 fx

posted by Matt Snider at 9:34 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  

Wednesday, March 12, 2008

Building Better Forms

Most of the times when building forms, you will have a label either to the left or above the field that the label is associated with. Designers come up with all kinds of crazy ways to handle the layout, but most are not semantic and require browser hacks. In the past, I have used the following markup:

Example 1: Semantic Forms Version 1 HTML

<form> <ul> <li><label>Label1</label><input type=”text” value”" /><li> <li><label>Label2</label><input type=”text” value”" /><li> … </ul> </form>

The label can then either float left or right, or be displayed as block, depending on your needs. However, I recently saw on the social networking site Facebook.com a nice technique using the definition list tag (dl). I found this particularly appealing, because browsers (by default) and designers apply less styles to DL tags, so your design is more portable. Plus, it is a semantic to use a DL tag anytime you have key/value pairs; in this case, the key is the label and the value is the field.

Here is an example of what your Form could look like:

Example 2: Facebook Message Form

Example of Facebook Message Form

To produce such a nice looking Form, your HTML will be something like:

Exmaple 3: Semantic Form Version 2 HTML

<form action=”someURL” class=”clearfix” id=”someID” method=”post”><dl> <dt><label for=”form-msg-to”>To:</label></dt> <dd><input id=”form-msg-to” name=”to” type=”text” value=”"/></dd> <dt><label for=”form-msg-subject”>Subject:</label></dt> <dd><input id=”form-msg-subject” name=”subject” type=”text” value=”"/></dd> <dt><label for=”form-msg-message”>Message:</label></dt> <dd><textarea cols=”30″ id=”form-msg-message” name=”body” rows=”5″></textarea></dd> </dl></form>

And the associated styles:

Example 3: Semantic Form Version 2 CSS

#message form { /* Optional, just a littl nice padding */ padding: 3em 0pt 1em 9em; } #message dl { /* Replace with desired width */ width: 47em; } #message dl dt { float: left; /* Spacing between label and field */ padding: 0 1em 0 0em; text-align: right; /* Width of largest label */ width: 6.5em; } #message dl dt label { /* Make whatever you like */ color: #666; /* Labels with “for” attribute are clickable, ensure they look that way */ cursor: pointer; display: block; /* Use whatever font-size you prefer */ font-size: larger; /* Adjust padding to align with fields */ padding-top: 0.5em; } #message dl dd { float: left; /* This is the padding between form field rows */ margin: 0 0 1em; /* Remaining width dl - dt - padding */ width: 39em; } #message input.txt, #message textarea { /* Prevents x-browser bug */ overflow: hidden; padding: 0.4em 0 0.4em 0.2em; width: 33em; }

You may also need to add the “clearfix” style to the form if other elements depend on its positioning, as the content of the form floats, so it won’t have any height. Otherwise, I think the comments are pretty self-explanatory.

posted by Matt Snider at 11:31 am  

Friday, March 7, 2008

IE-8 Development Tools and Mobil(ize) My Site

The IE blog announced today the development tools that they plan to include in IE 8. As a developer, I am pleased to see a FireBug like interface:

Improved Productivity Through Internet Explorer 8 Development Tools.

The company Mofuse has a toolkit that allows you to convert your website into a mobile version, targeting specific platforms. While it is better to design in a standards compliant way and only maintain one version of your site. Sometimes, the manufacturers just won’t let you. Check this out:

Mofuse

Also, Christian Heilmann talks about YUI evolution:

Christian Heilmann’s Talk on YUI at GeekUp Leeds

posted by Matt Snider at 3:26 pm  

Saturday, March 1, 2008

Using For In Loops Safely

Many of the JavaScript Frameworks and Libraries used today have the bad habit of attaching methods to the Prototype of Array and Object. While, in theory it would be really nice to be able to extend these Object, in practice, doing so breaks “For … in” loops. For example, if you include Json.js” in your project, you have the power to easily work with JSON objects. However, it attaches “toJSONString” method to “Array.prototype”. So when you use the “For … In” loop on any array, one of the keys will be the bogus key “toJSONString”.

This is frustrating as “For … In” loops are the only way, in JavaScript, that you can iterate on the keys of an Object and associative Arrays. Most Frameworks that modify these objects also add functions that allow you to work around this limitation, but then you become dependent on them to do any Array iterations. Douglas Crockford (see JSLint) and other JavaScript guru’s recommend keeping a list of keys that you want to ignore and always filtering your “For … In” loop with it. This is one way to fix the problem, but what does one do when they don’t know the keys or Array/Object prototypes change dynamically as your JavaScript executes, such as when you use YUI to extend Object and it attaches the “constructor” variable to Object’s prototype.

Often you need the ability to dynamically determine what keys to filter before each “For … In” loop. To accomplish this, I have developed the following function:

Example 1: GetKeysToIgnore

var getKeysToIgnore = function() { var tarr = [], // empty array tobj = {}, // empty object tdom = document.getElementsByTagName(”body”), // empty nodelist keys = {’item’: true, ‘length’: true};// default keys to ignore // iterate on the native array object for (var k in tarr) {keys[k] = true;} d // iterate on the native object object for (var l in tobj) {keys[l] = true;} // iterate on the nodelist, but don’t ignore indices for (var m in tdom) { if (isType(parseInt(m, 10), ‘number’)) {continue;} keys[m] = true; } return keys; };

This method creates an instance of each of the three type of iterable JavaScript objects: Array, Object, NodeList (especially important of Safari). Since, these objects are empty, they contain only the keys for elements that have been attached to their Prototypes. Therefore, when we iterate on it we can create a hash Object of all these keys. The default list is just some keys that you want to reserve for your project, which may or may not always be attached to one of these 3 objects.

Call “getKeysToIgnore” before any “For … In” loop and then filter these keys out:

Example 2: Filtering

var test = { key1: 1, key2: “1223′, key3: new Date() }; var ikeys = getKeysToIgnore(); for (var key in test) { // key is not in our ignore hash if (! ikeys[key]) { // put your logic here } }

Using this technique, you could modify the “batch” Function of Core.js to use this technique, thereby supporting both Array and Object iteration:

Example 3: Batch

Core.batch = function(o, fn) { var args = Array.prototype.slice.apply(arguments, [2]); var ikeys = getKeysToIgnore(); args.unshift(null, null); // iterate on the items, executing the function, and stoping when function returns true or visited all elements for (var key in o) { // key is not in our ignore hash if (! ikeys[key]) { args[0] = o[i]; args[1] = i; var rs = fn.apply(this, args); if (rs) { return rs; // allows the batch function to return a found result } } } };

While this is a little more powerful than the Array based “batch” Function, it is a little slower because the ikeys array must be created each time before batching. Something to think about is how to improve/cache getKeysToIgnore better and increase the performance of this Function.

posted by Matt Snider at 12:46 pm  

Powered by WordPress