Matt Snider JavaScript Resource

Understanding JavaScript and Frameworks

Wednesday, August 27, 2008

Event Bubbling and Event Capture

Todays article is going to be a quick insight into what is meant when developers talk about event bubbling and capturing. Although there is a lot to write about this topic, Peter-Paul Koch explains most of it at Quirksmode: Event Ordering. He does not mention Safari in his writeup, but it also supports both bubbling and capture.

Event capture and bubbling are a result of the browsers works of the last 1990s. When two elements, one the child of the other, have the same JavaScript event attached to them, the browser determines which event happens first. Netscape decided that the events should flow from the parent down to the child node (capture), while IE decided that the event should flow from the child to the parent node (bubble). Most modern browsers (except IE) now support both models, but implicitly use the bubble model. If you want your code to be browser independent, you should only use the bubble model. Here is an example of what I mean:

Example 1: Nodes

<div id="outerDiv" onclick="someFunctionA(){}"> <div id="innerDiv" onclick="someFunctionB(){}"></div> </div>

According to the bubble event model, if ‘innerDiv’ is clicked then its onclick event happens first, then the ‘outerDiv’ onclick event happens, assuming you haven’t stopped the bubbling of the event. Conversely, according to the capture event model, if ‘innerDiv’ is clicked, then the ‘outerDiv’ onclick event happens first, followed by the ‘innerDiv’ onclick (assuming you don’t stop the event from continuing the capture phase). Obviously, this these two models could lead to very different results.

Fortunately, you can safely assume that you are only dealing with the bubbling model, unless you explicitly define your event handlers to fire using the capture model. I strongly advise against doing that unless you are coding something that you do not want IE to support and absolutely require the capture model. For more information, please see PPK’s article that I mentioned above.

Lastly, I am going backpacking in Kings Canyon for the rest of this week and until Wednesday of next week. Unfortunately, my laptop, a portable power generator, and a satellite uplink are all a little to heavy for me to carry, so I will not be blogging again until next Friday. Enjoy your Holiday, if you have one, and when I come back we can continue our exploration of the wonderful world of JavaScript.

posted by Matt Snider at 8:51 am  

Saturday, August 23, 2008

How To Efficiently Search A JSON Array

We all know that JavaScript can be a great tool for enhancing the user experience of your website. However, when written poorly that tool can actually do the opposite. Today we will be addressing how to efficiently iterate on a JSON Array to see if one of the JSON objects contains a desired unique value (this technique only works with values that will be unique). A JSON Array is simply a JavaScript Array where each element is an Object, and each Object have the same keys. Lets consider the following JSON Array for a list of tag objects:

Example 1: Tags JSON Array

var tags = [ {tagId: 1, tagName: 'tag 1'}, {tagId: 2, tagName: 'tag 2'}, {tagId: 3, tagName: 'tag 3'}, ... {tagId: 99, tagName: 'tag 99'} {tagId: 100, tagName: 'tag 100'} ];

The ‘tags’ JSON Array contains 100 JSON objects, each containing the ‘tagId’ and ‘tagName’ keys. Now, assume that we have a text input somewhere on the page where a user can add a new tag. Before we let that user add a tag, we want to make sure that it doesn’t exist, therefore you might do the following:

Example 2: Common Way to Test For Existence

var hasTag = function(tagName) { var i = null; for (i = 0; tags.length > i; i += 1) { if (tags[i].tagName === tagName) { return true; } } return false; };

If you use Array.js then you could write the same method as:

Example 3: Common Way to Test For Existence Using Array.js

var hasTag = function(tagName) { return tags.batch(function(tag) { if (tag.tagName === tagName) { has = true; return has; } }); };

Now, if you only had to test if a tag existed once, then this technique would be fine. You can just call the ‘hasTag’ method once and then you are done. However, in the case we outlined above, each time a user enters a new tag, we have to call the ‘hasTag’ method. This is a very inefficient operation to call repeatedly and although it won’t severely affect performance in this example, because it is not done on page load or very frequently, it is bad practice. The more efficient way to test if a value exists inside of one of the contained objects, is to iterate through the Array once and create a Map of the keys to their values. Then the ‘hasTag’ method need only check against this map, which is much faster than iterating on the list:

Example 4: Creating a Map of Keys

var tagMap = {}; var i = null; for (i = 0; tags.length > i; i += 1) { tagMap[tags[i].tagName] = tags[i]; } var hasTag = function(tagName) { return tagMap[tagName]; };

The ‘hasTag’ method in Example 4 has a negligible performance hit, so you could even use it in a ‘mousemove’ event callback on the ‘document’, wihtout ruining your user’s experience. The point is that if you have a JSON Array (or an Array of like Objects) and have to search for a key value on an Object inside that Array (more than once), it is far more efficient to create a map of the ‘key’ or ‘keys’ you wish to search on, than to search the Array each time. Using this technique will improve the performance of your application and improve your the experience of your users.

posted by Matt Snider at 3:50 pm  

Wednesday, August 20, 2008

Tab Manager With Simple History

We recently released a new version of the Mint.com homepage with a tabbing system that: allows linking to a specific tabs, restoring the last tab on a page refresh, and basic tab history management in the better browsers (FireFox and Safari)… along with managing the tabs themselves to display the appropriate content. I have taken that concept, rewriting it to be lightweight (only 8kb uncompressed), leverage YUI and be configurable, in case you do not use the same conventions that we use at Mint. The new widget is HashTab and we will be today’s topic.

This widget uses a combination of naming convention and DOM queues to know which tab to display in a variety of situations. Lets discuss the simplest (default) case where you have a collection of tabs inside of a UL tag and a collection of tab content inside of a DIV tag. Your HTML will look something like this:

Example 1: Default HTML

<ul id="tab-example"> <li class="selected" id="tab-nav1"><a href="#nav1">tab 1</a></li> <li id="tab-nav2"><a href="#nav2">tab 2</a></li> <li id="tab-nav3"><a href="#nav3">tab 3</a></li> </ul> <div id="content-example"> <div class="selected" id="content-nav1">Content 1</div> <div id="content-nav2">Content 2</div> <div id="content-nav3">Content 3</div> </div>

First you should notice that all the IDs consist of two words separated by a dash. Each word is a key used by HashTab to identify elements and interact with the DOM. The tab UL container ID begins with the key “tab”, which is also the first key of the IDs for each tab. The second word in the tab UL container ID is used to uniquely identify this tab container from any other on the page and won’t be explicitly used by HashTab. The content container works in the same way, where the first key in the container ID is the same as the first key in the IDs of each of the content sections. The second word in each of the tabs and content sections should have a 1 to 1 ratio with each other, so if there is a tab with ID “tab-nav1″, then there should be a content section with ID “content-nav1″. That is how the HashTab knows that clicking on the first tab should show the first content section. Lastly, inside of each tab is an anchor, that should link to the same hash as the second key in the tab ID (so “tab-nav1″ should have an anchor tag with a href of “#nav1″).

In addition to responding to clicks, HashTab also understands that if the page is requested with one of the hashes that is recognizes, then it will display that tab when the page loads. For example, the url: “http://www.mattsnider.com/hashTabTest.html#tab4″ will initialize the page with the 4th tab selected. HashTab is also smart enough to ignore hash values that are not part of the tab system, so you can still link to other anchors on your pages while using HashTab. In addition to linking to specific tabs, when the page refreshes HashTab will initialize the tabs to the last selected tab. And if you are using Safari or FireFox, HashTab will handle both forward and backwards history navigation (these browsers could be supported with only 6 lines of code).

Instantiating a new HashTab is easy, you need to only pass in the ID for the tab and content containers, plus an optional config Object as a third parameters:

Example 2: HashTab Instantiation

var hashTab = Core.Widget.HashTab('tab-example', 'content-example');

The configuration object allows you to customize almost any part of the widget. Below is an explanation of the keys recognized by HashTab:

Example 3: HashTab Configuration

delimiter: the character used to separate keys in the ID attribute for hashTab DOM nodes; default is '-' elementContent: the type of node used for each of the content nodes; default is 'div' elementTab: the type of node used for each of the tab nodes; default is 'li' interval: the poll time to check for history changes; default is 250 (1/4 second) selected: the class to apply when a node is selected; default is 'selected' triggerEvent: the event to trigger tabbing; default is 'click'

Here is a Test Page showing how, in the first example, the default HashTab work, and how to use a few of the configuration settings in the second. Most importantly, notice how both HashTab widgets work independent of each other.

posted by Matt Snider at 9:31 am  

Thursday, August 14, 2008

News - YUI 3.0 Pre-Release And ES4 Died

Lots of good things out of Yahoo today:

YUI 3.0 Preview Release 1

The YUI team has provided us with a preview version of 3, scheduled to be released in December 2008. It will not be backwards compatible with version 2.*, but will be able to run in parallel until you have time to upgrade your code base to version 3. They state that the framework will be smaller and more efficient. Also, they have adopted to use a JQuery-like method for querying the DOM. I am both excited and apprehensive about the upgrade, as I have written thousands of lines of code, depending on the existing framework, but a better quality framework is best in the long run.

The Only Thing We Have To Fear Is Premature Standardization

Douglas Crockford writes about standardization and why the EcmaScript 4 (ES4) standard project fell apart. Personally, I am thankful for this, as I was never a big fan of ES4 and favor the ES3.1 standard. Not that there wasn’t good parts to ES4, but on a whole I agree with Douglas Crockford, “it was a mess.” There is a new project, Harmony, that will try to take some of the best parts of ES4 and integrate them with ES3.1, but it is still in its infancy.

posted by Matt Snider at 4:39 pm  

Tuesday, August 12, 2008

Multi-State Checkboxes

Recently, I was asked to build a 3-state system for checkboxes, so that there would be a checked, unchecked, and mixed state. So, I built a re-usabled widget that supports any number of states (not just the 3 mentioned above), and any number of checkboxes. I also desired that it be simple to use and easy to read.

The widget requires that you provide it a node in the DOM (preferably, an empty one) to contain the checkboxes, which it will clear and build your checkboxes inside. The DOM will be built as follows:

Example 1: Checkbox DOM

<ul> <li><label><input name="nameOfCheckbox1" type="checkbox" value="stateOfCheckbox1"/>Label for Checkbox 1</label></li> <li><label><input name="nameOfCheckbox2" type="checkbox" value="stateOfCheckbox2"/>Label for Checkbox 2</label></li> ... </ul>

We will be styling the checkboxes to be invisible (opacity = 0) and applying a ‘click’ event to each checkbox, which will be used to trigger the checkbox state change. The checkbox is wrapped in a label, so that supporting browsers will allow users to click on the label to fire the checkbox ‘click’ event as well. Also, by using an invisible checkbox we allow users to tab through the checkboxes and use space to trigger the ‘click’ event, not limiting users to mouse only interaction (this is why I didn’t use the traditional ‘hidden’ input to store state). Since the checkbox is invisible, we will need to use a background image attached to the label for our multi-state checkboxes. Here are the styles that I recommend:

Example 2: Checkbox Styles

.checkbox ul li { clear: both; list-style: none; } .checkbox ul li input { opacity: 0; filter: alpha(opacity=0); } .checkbox ul li label .checkbox ul li label.unchecked { background: transparent url(images/unchecked.png) 0px 2px no-repeat; } .checkbox ul li label.mixed { background: transparent url(images/mixed.png) 0px 2px no-repeat; } .checkbox ul li label.checked { background: transparent url(images/checked.png) 0px 2px no-repeat; }

Example 3: State Images

uncheckeduncheckedunchecked

For the most part the this widget was designed to just work. However, it requires that you provide a DOM node and a list of states you wish to setup. The state list specifies what classes should be applied to the label tag for each state, the value of the state, and what the next state will be when the user clicks on the checkbox. You may also pass an optional configuration parameter which may include: the initial data representing the checkboxes, whether you want to render the checkboxes immediately, and/or a function you would like to attach to the ‘onCheck’ event. Here is an example of how to initialize a checkbox widget:

Example 4: Initialize the Checkbox

Mint.Widget.Checkbox('checkbox', [ {klass: 'unchecked', next: 'B', state: 'R'}, {klass: 'mixed', next: 'B', state: 'G'}, {klass: 'checked', next: 'R', state: 'B'} ], { data: [ {name: 'tag-A', label: 'Tag A', value: 'R'}, {name: 'tag-B', label: 'Tag B', value: 'G'}, {name: 'tag-C', label: 'Tag C', value: 'B'}, {name: 'tag-D', label: 'Tag D', value: 'G'}, {name: 'tag-E', label: 'Tag E', value: 'R'} ], render: true });

If you do not provide a configuration parameter, you can call the public ‘render’ Function at any time to build the checkboxes. You may also call the ‘render’ Function with a new data set at any point in the future to redraw the checkboxes with new values. I have illustrated several different instantiations of this widget for your testing pleasure: Checkbox Widget.

posted by Matt Snider at 11:09 pm  

Friday, August 8, 2008

Calling Flash From JavaScript

First, let me say that there are many different ways in which to embed Flash content on your website, including using nested HTML tags, browser detection, and/or JavaScript. There are also different Flash movie formats, but most people choose the SWF format, so we will discuss how to show SWF files. I do not regularly need to work with Flash, as I can do almost everything I want with JavaScript, however, there have been clients who demand its use. And my experience with Flash and SWF files has led me to chose SWFObject as my favorite technique for embedding Flash content on a page.

When embedding Flash content on a page, you will encounter issues in at least these 3 areas:

  • Is Flash supported in the client browser?
    Flash needs to be installed and enabled in the client browser, and you need to handle situations when it is not.

  • How does this browser handle Flash ?
    Flash is a proprietary technology and different browsers have chosen to support it in differing ways, especially detecting Flash versions and rendering alternative content when the flash player is not present.

  • Is the required Flash version support?
    Your code was written for Flash 9, but the client browser only has Flash 8. The user should be asked to upgrade their Flash version.

I am normally adamant about following standards and accessibility practices, which dictates that I not use JavaScript, but an HTML technique, such as found in the List Apart Article: Flash Satay: Embedding Flash While Supporting Standards. This method worked great for me, and until I started working on secure servers (SSL) where this technique causes IE 6 to show a security warning, it was my first choice.

When the Satay method failed me, I then looked to Adobe, the owner of the proprietary Flash technology. Adobe supports several methods for embedding Flash: Adobe Flash Detection Kit. I was hoping that since I was not able to use the more accessible Flash Satay method, that I could at least use officially supported technologies. Unfortunately, I was still receiving the same security warning on secure servers in IE 6 (although, the Adobe site claims that their script works around this).

Then, I asked myself it the IE 6 security warning was really a serious issue. And It is a huge issue, as the site is most likely secure, because it contains sensitive information. Users of a site, where their browser frequently warns them that they not secure, will be less likely to trust it that site. And even though the warning is bogus, your users won’t care.

The only technique successfully defeating the IE 6 security warning bug, is SWFObject, which is why I choose to use it above all others. It is also simple to use, and easy to setup alternative content. Here is a simple example:

Example 1: Using SWFObject

var so = new SWFObject('/assets/swf/mySWF.swf', 'embeddedSwfId', '950', '950', '9.0.115', '#FFFFFF'); so.addParam('quality', 'high'); so.addParam('wmode', 'transparent'); so.addParam('salign', 'tl'); so.write('flash-container'); // DOM node to embed flash content into

This little code snippet will embed the ‘mySWF.swf’ inside the ‘flash-container’ element and assign the embedded SWF the id of ‘embeddedSwfId’. The create DOM node will have width and height of 950 and a background of black. Lastly, we tell the ‘SWFObject’ that we require version ‘9.0.115′ of the Flash player.

In addition to all the issues above, you may encounter server-related issues when requesting the Flash content (usually a SWF file) as a result of compression and/or caching techniques. Fortunately, these issues are well documented and you will most likely find a reasonable solution with a quick google search.

posted by Matt Snider at 8:59 pm  

Tuesday, August 5, 2008

Simple Image Viewer With Captions

It seems that, if I am to leave a legacy after this blog is gone, it will be my Super Simple Image Viewer, as more people read and ask about this widget than any other part of this blog. I attempted to address most people concerns in last weeks Somewhat Simple Image Viewer article, but I was only partially successful, as I did not add in the second most requested feature: captions. So I went back to the drawing board and have written a third version of the Image Viewer.

Version 3 supports both single collection and multiple collections of images (such as portfolios or events), and captions are optional. This way it is nearly backwards compatible with Version 2 that we discussed last week.

Users will quickly notice that the PhotoViewer Function now only requires two parameters: ‘cfg’ and ‘data’. Where ‘cfg’ is an Object containing DOM references and ‘data’ is the collection of image URLs (and other meta data). Below is an example of the ‘cfg’ Object:

Example 1: Configuration Object

var cfg = { caption: 'viewer-caption', // optional image: 'viewer-image', // required next: 'viewer-next', // required prev: 'viewer-prev' // required };

While the caption value is optional, if any of the other 3 DOM references are missing, PhotoViewer will alert you and not initialize. The ‘data’ can be in two different formats, first it can just be a Array of image URLs, second it can be an Object where each key is the DOM ID of the anchor tag to select that set and the value is the Array of images. Here are 3 different valid data sets:

Example 2: Data Object

// 3 sets of objects, containing the image and caption var data = { set1: [// this needs to be the DOM ID attribute for the anchor tag to select this set {image: 'http://farm1.static.flickr.com/142/331222000_76f271e745.jpg?v=0', caption: 'frozen landscape'}, {image: 'http://farm1.static.flickr.com/159/384269775_f8b21b5e52.jpg?v=0', caption: 'mountain stream'}, {image: 'http://farm1.static.flickr.com/178/415791942_4a4e411464.jpg?v=0', caption: 'mountain lake'} ], set2: [// this needs to be the DOM ID attribute for the anchor tag to select this set {image: 'http://farm1.static.flickr.com/186/421171930_80ef8ee7b2.jpg?v=0', caption: 'cool canpopy'}, {image: 'http://farm1.static.flickr.com/155/421172107_2f20ec7aeb.jpg?v=0', caption: 'cool tree'}, {image: 'http://farm1.static.flickr.com/54/133256387_e99cd967ff.jpg?v=0', caption: 'vibrant rose'} ], set3: [// this needs to be the DOM ID attribute for the anchor tag to select this set {image: 'http://farm1.static.flickr.com/46/167939744_2357ffe1af.jpg?v=0', caption: 'white mountains'}, {image: 'http://farm1.static.flickr.com/48/177853809_ad9454a89e.jpg?v=0', caption: 'small turtle'}, {image: 'http://farm1.static.flickr.com/54/183725415_ff609f45e4.jpg?v=0', caption: 'mountain sunset'} ] }; // 3 sets of image URLs var data2 = { set4: // this needs to be the DOM ID attribute for the anchor tag to select this set ['http://farm1.static.flickr.com/142/331222000_76f271e745.jpg?v=0', 'http://farm1.static.flickr.com/159/384269775_f8b21b5e52.jpg?v=0', 'http://farm1.static.flickr.com/178/415791942_4a4e411464.jpg?v=0'], set5: // this needs to be the DOM ID attribute for the anchor tag to select this set ['http://farm1.static.flickr.com/186/421171930_80ef8ee7b2.jpg?v=0', 'http://farm1.static.flickr.com/155/421172107_2f20ec7aeb.jpg?v=0', 'http://farm1.static.flickr.com/54/133256387_e99cd967ff.jpg?v=0'], set6: // this needs to be the DOM ID attribute for the anchor tag to select this set ['http://farm1.static.flickr.com/46/167939744_2357ffe1af.jpg?v=0', 'http://farm1.static.flickr.com/48/177853809_ad9454a89e.jpg?v=0', 'http://farm1.static.flickr.com/54/183725415_ff609f45e4.jpg?v=0'] }; // an array of image URLs var data3 = [ 'http://farm1.static.flickr.com/142/331222000_76f271e745.jpg?v=0', 'http://farm1.static.flickr.com/159/384269775_f8b21b5e52.jpg?v=0', 'http://farm1.static.flickr.com/178/415791942_4a4e411464.jpg?v=0', 'http://farm1.static.flickr.com/186/421171930_80ef8ee7b2.jpg?v=0', 'http://farm1.static.flickr.com/155/421172107_2f20ec7aeb.jpg?v=0', 'http://farm1.static.flickr.com/54/133256387_e99cd967ff.jpg?v=0', 'http://farm1.static.flickr.com/46/167939744_2357ffe1af.jpg?v=0', 'http://farm1.static.flickr.com/48/177853809_ad9454a89e.jpg?v=0', 'http://farm1.static.flickr.com/54/183725415_ff609f45e4.jpg?v=0' ];

Data set 1 shows how to add captions into your PhotoViewer, while set 2 is exactly the same as we used last week, and set 3 is supported to for those of you who don’t need multiple image collections. You can see an example of all 3, Image Viewer Example and the JavaScript code here. Also, to support this more powerful version, I needed extra library code, and you will need to include the following 3 files before importing the PhotoViewer script.

Example 3: JavaScript Files to Include

// core dom and event framework <script type="text/javascript" src="http://yui.yahooapis.com/2.5.2/build/yahoo-dom-event/yahoo-dom-event.js"></script> // required for type detection <script type='text/javascript' src="http://www.mattsnider.com/assets/js/core/type.js"></script> // required for object manipulation <script type="text/javascript" src="http://www.mattsnider.com/assets/js/object.js"></script>

Lastly, version 3 has a brand new feature, that improves the initial performance of the PhotoViewer widget. I have noticed that in some browsers, like FF2+, even though we are pre-caching the images (using circa 1999 technology), the first time we clicked to see an image it takes an exceptionally long time to load, as if it wasn’t pre-cached (and it probably isn’t). Browsers have become rather smart and to improve performance they don’t always download files, when they don’t believe they are necessary. Therefore, to defeat the browser intelligence, I have created a hidden node at the end of the body tag, in which we will append each image that we want to cache. This tricks the browser into downloading (and pre-caching) the images, thereby drastically improving the performance the first time iterates through the images in PhotoViewer.

posted by Matt Snider at 9:56 am  

Saturday, August 2, 2008

Boolean Functions

There is very little you need to attach to Boolean, as it is a very simple object. I have only found two methods that I like to attach to the ‘Boolean’ Object: ‘is’ and ‘get’. The ‘is’ method is a type detection like I have attached to other native JavaScript objects. The ‘get’ method is to convert falsy/truthy values into ‘false’ and ‘true’. While this isn’t necessary because you can use “! yourObject”, however I frequently like to use strict, type comparisons operations, such as ‘===’ and ‘!==’.

Check out object.js here and the unit test here.

As a side note, I have been updating the blog layout a bit. Hopefully, I am making this blog more useful.

posted by Matt Snider at 5:33 pm  

Powered by WordPress