« December 2005 | Main | February 2006 »

January 31, 2006

Encapsulating Ajax XMLHTTPRequest Calls within JavaScript classes

The guys over at Fold posted an article on how to use multiple XMLHTTPRequest (XHR) calls within a single page. This is something that I have had to tackle in my own work, and though I would write up how I solve this issue. This post describes a simple technique for encapsulating XMLHTTPRequest calls within JavaScript classes. In general, this makes code more maintainable, allows for the creation of easier to use APIs, and makes it much easier to create components / widgets that load data from the server. Basically, the technique associates and encapsulates XMLHTTPRequest instances within individual JavaScript class instances. For this post, we will create a simple widget that uses XHR and Ajax techniques to dynamically load some data from the server, and then render it in the page. Nothing super exciting, but it provides enough to get an idea of how everything works. First, the examples contains the following files: You can download all of the files from here. First, lets look at what the final HTML file will look like: [code] index [/code] Pretty simple. We link to the style sheet and MessageLoader.js file in the head tag. Then, we just have a simple script block in the body that will render the content. The main work goes on in the scripts/MessageLoader.js file. [code]/** * @fileoverview Simple example that shows how to encapsulate * XMLHTTPRequestCalls * * @author Mike Chambers (mesh@adobe.com) */ /** * Constructor for the class. * * @param {String} dataURL The path to the data that the class * will load (OPTIONAL) * * @constructor */ function MessageLoader(dataURL) { if(dataURL != undefined) { this._dataURL = dataURL; } this._writeContainer(); } //where to load the data from MessageLoader.prototype._dataURL = "data.txt"; //var to hold an instance of the XMLHTTPRequest object MessageLoader.prototype._request = undefined; //ID for the html div we will create to display the data MessageLoader.prototype._containerID = "container"; //name of the css class for the HTML container MessageLoader.prototype._containerClass = "ml_container"; /**************** Public APIs **********************/ /** * Tells the class to load its data and render the results. */ MessageLoader.prototype.load = function() { //get a new XMLHTTPRequest and store it in an instance var. this._request = this._getXMLHTTPRequest(); //set the var so we can scope the callback var _this = this; //callback will be an anonymous function that calls back into our class //this allows the call back in which we handle the response (_onData()) // to have the correct scope. this._request.onreadystatechange = function(){_this._onData()}; this._request.open("GET", this._generateDataUrl(), true); this._request.send(null); } /***************Private Rendering APIs ********************/ //writes the top level div for the class / widget MessageLoader.prototype._writeContainer = function() { //styles should be in external CSS document.write("
"); } //renders the entire widget MessageLoader.prototype._render = function(title) { var content = document.getElementById(this._containerID); content.appendChild(document.createTextNode(title)); } /***************Private Data Loading Handlers*******************/ //return the URL from which the data will be loaded MessageLoader.prototype._generateDataUrl = function() { return this._dataURL; } //callback for when the data is loaded from the server MessageLoader.prototype._onData = function() { if(this._request.readyState == 4) { if(this._request.status == "200") { this._render(this._request.responseText); //if the onDraw callback has been defined //call it to let the listener know //that we are done creating the list if(this.onDraw != undefined) { this.onDraw(); } } else { //check if an error callback handler has been defined if(this.onError != undefined) { //pass an object to the callback handler with info //about the error this.onError({status:this_request.status, statusText:this._request.statusText}); } } //clean up delete this._request; } } /***************Private Data Util Functions ********************/ //returns an XMLHTTPRequest instance (based on browser) MessageLoader.prototype._getXMLHTTPRequest = function() { var xmlHttp; try { xmlHttp = new ActiveXObject("Msxml2.XMLHttp"); } catch(e) { try { xmlHttp = new ActiveXObject("Microsoft.XMLHttp"); } catch(e2) { } } if(xmlHttp == undefined && (typeof XMLHttpRequest != 'undefined')) { xmlHttp = new XMLHttpRequest(); } return xmlHttp; }[/code] I have tried to comment the code to make it self documenting, but lets do a quick overview of what it going on. First, the class constructor does two main things: [code]function MessageLoader(dataURL) { if(dataURL != undefined) { this._dataURL = dataURL; } this._writeContainer(); }[/code] It allows the developer to optionally specify the path to the data to be loaded, and it calls the this._writeContainer(); method. All this method does is write a small piece of content into the HTML page into which the dynamic data that will be loaded will be attached to. Everything is set in motion when the load() method is called. [code]/** * Tells the class to load its data and render the results. */ MessageLoader.prototype.load = function() { //get a new XMLHTTPRequest and store it in an isntance var. this._request = this._getXMLHTTPRequest(); //set the var so we can scope the callback var _this = this; //callback will be an anonymous function that calls back into our class //this allows the call back in which we handle the response (_onData()) // to have the correct scope. this._request.onreadystatechange = function(){_this._onData()}; this._request.open("GET", this._generateDataUrl(), true); this._request.send(null); }[/code] This is pretty standard XHR code, but there are two things worth calling out. First, we store the XHR object within a local variable named _request. This will allow it to be accessed later within the class instance. [code]this._request = this._getXMLHTTPRequest();[/code] Second, we create a local variable named _this that points to the class instance, and then create an anonymouse function to use as the onreadystatechange callback handler. [code] //set the var so we can scope the callback var _this = this; //callback will be an anonymous function that calls back into our class //this allows the call back in which we handle the response (_onData()) // to have the correct scope. this._request.onreadystatechange = function(){_this._onData()}; [/code] This is necessary because of how scoping works within JavaScript. Basically, any functions created contain references to variables within their scope. Thus, when we define the anonymouse function here: [code]this._request.onreadystatechange = function(){_this._onData()};[/code] The _this variable is copied into the function. This allows us to essentially proxy the data handler call to data handler that is called within the scope of our class instance. This makes it much, much easier to encapsulate the XHR call, as if we did not do this, then "this" within the callback would refer to the browser's global window object, and NOT to our class instances. You can see this in the data handler which is called when the data is loaded from the server: [code]MessageLoader.prototype._onData = function() { if(this._request.readyState == 4) { if(this._request.status == "200") { this._render(this._request.responseText); //if the onDraw callback has been defined //call it to let the listener know //that we are done creating the list if(this.onDraw != undefined) { this.onDraw(); } } else { //check if an error callback handler has been defined if(this.onError != undefined) { //pass an object to the callback handler with info //about the error this.onError({status:this_request.status, statusText:this._request.statusText}); } } //clean up delete this._request; } }[/code] This handler is called within the scope of our class instance. This allows us to get a reference to the XHR instance through this._request, and to call isntance methods directly. All this code does is check to make sure the data has completed loading. If it has, then it calls the _render method, which renders the content in the page. If an error occurs, then we throw an error event that the developer can listen for. Once we have retrieved the data we no longer need the XHR instance hanging around within our class instance, so we delete it. That is pretty much all there is to it. You could now instantiate multiple instances of the class, and each would contain its own XHR isntance. Furthermore, you can completely abstract away the XHR object, and provide a simple API specific to whatever you code / widget does. Now, in this example, you can only really crete one instance of the widget in a page, since we don't do anything to create unique IDs for the dynamically generated DIVs / HTML content. However, that is easy enough to solve, and I will update this example to do this in the next day or two. Post any questions / corrections or suggestions in the comments.

Posted by mesh at 1:44 PM | Comments (23)

Flash Player 8 already at 50%

As Emmy posted, we have just released new Flash Player penetration numbers.

Flash Player 8 can be viewed by around 50% of people online (45% US, 55% Canada and Europe). That is pretty amazing considering that the player was released just 5 months ago. This puts the player on course to reach 80% in well under a year from release.

You can view all of the stats here.

Posted by mesh at 10:22 AM | Comments (12)

January 28, 2006

ActionScript 2.0 Cheatsheet

Super useful reference.

Anyone know of an ActionScript 3 one?

Posted by mesh at 10:51 PM | Comments (6)

January 27, 2006

Learning Object Oriented JavaScript Programing from ActionScript

I have been doing some JavaScript / Ajax development over the past couple of weeks, and have been trying to figure out the best way to structure my JavaScript and HTML code. One thing that really surprised me when doing research is how many JavaScript projects / examples do not try to encapsulate their code (aside from within script files) or use object oriented programing. Thinking back on this, and given the evolution of JavaScript from a web scripting glue to more of an application programing language, I can understand this, but it did surprise me none-the-less.

Of course, Object Oriented programing in JavaScript can feel a little odd, especially if you are used to more traditional object oriented languages. While prototype based classes can seem weird and quirky, they do allow some amazing flexibility, as well as the use of traditional class based development.

Indeed, this may be one area where long time Flash developers have an advantage when doing JavaScript development, as application development using ActionScript 1.0 requires understanding object oriented programing with JavaScript / ActionScript.

Indeed, I still think that Robin Debreuil's Building Object Oriented Apps in Flash 5 is one of the best introductions to object oriented programing in JavaScript anywhere. Chapter 12 (Objects and Classes) of Colin Moock's ActionScript for Flash MX is also an excellent resource on object oriented programing in a prototype based language.

Anyways, if you are interested in doing object oriented JavaScript development, I suggest that you also check out some of the older resources and tutorials around object oriented programing in Flash 5 / ActionScript 1.0.

Posted by mesh at 11:57 AM | Comments (8)

January 24, 2006

Macromedia News Firefox Extension Updated

I have uploaded a new version of the Macromedia News Firefox extension. This version adds support for Firefox 1.5.

Sorry it took so long.

You can find more information, as well as install the extension from here.

Ill probably do another build in the coming weeks to update the name and branding.

If you run into any issues, just post them in the comments.

Posted by mesh at 11:55 PM | Comments (9)

Macromedia News Firefox Extension Fixed!

Just a quick fyi, but I just fixed the issue with the Macromedia News Firefox Extension that was keeping it from working in Firefox 1.5.

As I suspected, it was the same problem that I posted about earlier.

Anyways, I will post an updated version later tonight or first thing in the morning.

Posted by mesh at 6:25 PM | Comments (4)

Removing HTML Element children with JavaScript

Just a quick fyi, but if you are doing work in JavaScript and need to dynamically remove all of the childNodes from a DOM element, make sure to do it with a while loop, and not a for loop. For example, this is bad: [code]function removeChildrenFromNode(node) { if(node !== undefined && node !=== null) { return; } var len = node.childNodes.length; for(var i = 0; i < len; i++) { node.removeChild(node.childNodes[i]); } }[/code] Since, as soon as you remove one child, the length of node.childNodes is 1 smaller, and you will eventually access an index that does not exist (and get a JavaScript error / exception). The correct way to do this is with a while loop with node.hasChildNodes, like so: [code]function removeChildrenFromNode(node) { if(node !== undefined && node !=== null) { return; } var len = node.childNodes.length; while (node.hasChildNodes()) { node.removeChild(node.firstChild); } }[/code] I just spent quite a bit of time debugging this. In fact, I am pretty sure that this is why the Macromedia News Firefox extension does not currently work well in Firefox 1.5. The odd thing is that this was not an issue prior to Firefox 1.5 (at least not in the Firefox extension). Anyways, just a little heads up of something to watch out for. Thanks to everyone on the Firefox development forums for helping me track this down.

Posted by mesh at 12:00 PM | Comments (19)

January 12, 2006

Nine Tips for Designing Rich Internet Applications

Useful post by Bill Scott. Most of them apply to both Flash and Ajax based RIAs.

If you are interested in this, you might also want to check out the Adobe XD (experience Design) blog.

via Ajaxian.

Posted by mesh at 11:12 AM | Comments (0)

January 10, 2006

Resources for Compiling ActionScript 3 and MXML from the command line

In this post, I am going to provide all of the links you need to get started with compiling ActionScript 3 and Flex 2 / mxml from the command line (on Mac, Windows and Linux). I have had to send this info out in about 3 different emails today, so I figured I would post it here to get everything in one place.

Remember that both ActionScript and mxml / Flex files are text based, so you can use any editor that you want, and with mxmlc compile on virtually any system that you want to (although the alpha is currently only officially supported on Windows).

First of all, the command line compiler for Flex 2 and ActionScript 3 is called mxmlc. An alpha version is currently available as part of the Flex Builder 2 alpha install.

You can find information on how to get the compiler, and how to set it up here:

http://labs.macromedia.com/wiki/index.php/Flex_Framework:tutorials:compiling_mxmlc

You can find information on compiling on non-Windows platforms here:

Compiling on Mac and Linux
http://labs.macromedia.com/wiki/index.php/Flex_Framework:tutorials:mac_development

and here:
http://weblogs.macromedia.com/mesh/archives/2005/12/compiling_actio.cfm

You can find a listing of command line options for MXMLC here:
http://livedocs.macromedia.com/labs/1/flex/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00001191.html

Once you get everything setup, then check out the Labs wiki, for tons of ActionScript and Flex info and examples.

Post anything that I missed in the comments.

Posted by mesh at 8:33 PM | Comments (2)

ExternalInterface Bug with new line chars

A couple of days ago I ran into a bug with ExternalInterface which caused me quite a hassle (a couple of people ran into it before I did). I finally figured it out, and wanted to post here in case anyone else runs into it.

Basically, if the data being passed from JavaScript to ActionScript or ActionScript to JavaScript via ExternalInterface contains any line returns (\n) then the string is not serialized correctly, and the call will fail (you will probably see a JavaScript error). This affects both Flash Player 8, and the Flash Player 8.5 alpha.

The issue has been logged and is being looked at it. Luckily, there is a simple workaround, which is to manually escape the line returns by replace \n with \\n.

Here is how to do it in ActionScript 3:

var data:String = stringToPass.replace(/\n/g, "\\n" );

and here is how to do it in ActionScript 2:

function escapeLineReturns(s:String):String
{
    trace("a");
    var len:Number = s.length;
    var sb = "";
    
    var c;
    for(var i:Number = 0; i < len; i++)
    {
        c = s.charAt(i);
        if(c == "\n")
        {
            sb += "\\n";
        }
        else
        {
            sb += c;
        }
    }
    
    trace("r : " + sb);
    return sb;
}

var data:String = escapeLineReturns(stringToPass);

On the JavaScript side you can basically use the same solution as in ActionScript 3.0:

var data = stringToPass.replace(/\n/g, "\\n" );

Posted by mesh at 5:08 PM | Comments (5)

mesh@macromedia.com going away

Just a quick fyi, but my mesh@macromedia.com email address is going away. It has been killed by spam and viruses. I got an email from IT last week letting me know that for that day I had received 96,000 virus emails (most of which were blocked before they got to me). Needless to say, this was a concern, and so I am having to retire the email address.

Anyways, I do still have mesh AT adobe DOT com, so mesh is still around. If you have my email address and need to ping me from time to time, make sure that you update your address book (unless you have a virus that is going to send emails in my name).

Posted by mesh at 9:17 AM | Comments (11)

January 9, 2006

Parsing XML in JavaScript?

I have been doing some work with JavaScript / Ajax lately, and found myself needing to parse some XML (something I do quite often when building apps). However, I have not had much luck successfully parsing XML with JavaScript cross browser.

Firefox 1.5 has support for E4X, which makes parsing a breeze, but it is only supported in the latest version of Firefox. The XMLHttpRequest object parses loaded XML into a DOM, but my experience has been that the DOM is different depending on the browser you are running in (I tested on Firefox and Sarafi on Mac).

I know that I must be missing something pretty obvious, but just about every article I have read online about this suggests to NOT use XML, and use something like JSON where possible (which I can't do in this case).

So, if you are doing JavaScript development, what are you using to do cross-browser XML parsing in JavaScript on the client side?

Posted by mesh at 3:56 PM | Comments (23)

January 6, 2006

Flash Player Switching on Mac

I have been doing most of my work recently using ActionScript 3 and the Flash Player 8.5 public alpha. However, I recently had to do some Flash Player 8 / ActionScript 2 work, and ran into the issue of switching the Flash Player version installed on my machine. Of course, I could just install / reinstall players as I need them, but that it tedious, and doesn't lend itself to quickly switching back and forth between player versions (especially during the same session). So, I put together a simple bash script that will quickly switch between player versions (for Firefox and Safari).

First, you need to install Flash Player 8 (I suggest installing the debug version which you can get from the Flash Authoring install / trial). Once it is installed, you can find the plugins in:

/Library/Internet Plug-Ins/

The plugin files are:

Flash Player Enabler.plugin
Flash Player.plugin

Inside of this director, create two new directories called 8.0 and 8.5, so that you end up with the following paths:

/Library/Internet Plug-Ins/8.0/
/Library/Internet Plug-Ins/8.5/

Copy the two Flash Player plugin files into the newly created 8.0 directory.

Now, download and install Flash Player 8.5 for Mac. This will overwrite the previous plugins, and now you can copy the

Flash Player Enabler.plugin
Flash Player.plugin

files into the 8.5 directory.

You probably noticed that the directory that contains the plugin has a space in its name. This normally isn't a big deal, but it makes accessing the path in a shell script a bit of a pain. So, we are going to make things a little easier, by creating a soft-link to that directory that does not contain a space.

Switch to the /Library directory and run the following command:

sudo ln -s Internet\ Plug-Ins/ plugins

Basically, this creates a link called plugins that points to /Library/Internet Plug-Ins. So, we can refer to the Flash Player 8.5 plugin directory like so:

/Library/plugins/8.5/

This will make writing our shell script a lot easier.

Finally, we need to create a shell script that will allow us to easily switch between plugin versions. Below is a simple bash script that I put together called cfp (change Flash Player). Basically, you call it and pass to it the Flash Player version you want to run, and it will switch to the Player.

Here is the script

#!/bin/bash

plugin_dir='/Library/plugins'

np_dir="$plugin_dir/$1"

if [ ! -d $np_dir ]
then
echo "$np_dir does not exist."
exit
fi


cp -rv $np_dir/* $plugin_dir/

You use it like so:

To switch to Flash Player 8.0:

sudo cfp 8.0

and to switch to Flash Player 8.5

sudo cfp 8.5

You will be prompted for your password, since you need admin rights to move the plugin files around.

Note that the Flash Player version passed in is just the name of the Player directories we created earlier. So you can easily add new player versions by just creating new directories and copying the correct plugin files into that directory.

You can confirm that the correct player version is installed by visiting the Flash Player Version Test page. My experience has been that you can switch the players without having to restart the browser, however if you run into any weird issues, I would just try and restart the browser.

Now, there are other plugin switchers, but one of the advantages to doing it via bash, is it makes it very easy to automate the plugin switching via a bash or ant script. For example, you could write a script that:

  1. Compiles your ActionScript using MXMLC (AS3) or MTASC (AS2)
  2. Switches the browser to use the correct plugin using this script.
  3. Launches the newly compiled file into the browser.

The next step is to hook up the command line FDB debugger so that you can debug running SWFs on the Mac. Danny Dura and I got this working yesterday, and I will try and post some info on it next week.

Post any comments / suggestions / questions in the comments.

Posted by mesh at 10:25 AM | Comments (6)