First let me preface this post with some history. The previous post can be thought of as part 1, though it was more of a rant whereas this is for reference purposes. My goal was given what I had learned in reading “Beginning Javascript with DOM Scripting and Ajax” by Christian Heilmann to develop a simple Ajax style web application that conformed to proper web design principles.
- The application demonstrates unobtrusive Javascript that “helps” the user
- The application is functionally available for all major browsers
- The application is functionally available without Javascript enabled
To begin I had to choose how I would develop this application. I started off by using PHP Designer 2007 Personal. However, I became frusterated with its code completion style not being as smooth as other applications I had used. So I began looking for an alternative. And I found it: the Eclipse plugin Aptana (Click Here). This is something I dont think I will ever get ride of. It properly shows documentation fro Javascript functions and also their availability depending on the browser. It also provides very complete documentation as to what each function can do, in my mind a tool no developer should be without. But moving on. The inital design was quite easy. I was able to construct the interface with no problems. I used PHP to generate any default content that should be their, ie the list of series.
My first problem I ran into was given the large amount of data I was likely to get back from the PHP script being called asynchronously, how to get that data into the select boxes. I was not about to parse the responseText property, so I opted to play with responseXML. I must give credit to Firebug in this case, this would have been obscenely difficult without its aid. It took me a bit of time, however, to decipher what it was reporting to me for the responseXML property. It apparently calls the response Document object with a ‘null’ name. This makes sense, I suppose, but confused the hell out of me initally.
Once I had the XML document, using the various get* functions provided by JS, acquiring the data and using it was very easy. This was accomplished with very little effort and in no time I was to the point where the user could select the desired episode. While I had the idea of making the information for the episode more complex, I decided to just show some info from the episodes table, rather then joining tables to get the other information.
Part 1 : CreatingThe Script
Using what I had learned in the afore mentioned book, I had constructed the UI with the basic practices needed to make the UI usable even without JS. The basic idea is to show everything, that is assume the user does not have JS. The modify the UI using Javascript if we find that it is available to us. This concept of developing the website assuming their is no JS is not always easy and I think something that escapes many programmers writing websites. We tend to get very caught up in making apps look awesome with a lot of bells and whistles. Professional development is not about bells and whistles, however, and I think a lot of developers struggle with that, I know I do.
But moving on, so using JS I hid the various select buttons and the div’s enclosing each select box, except from the series select, as that needed to be visible. I also added the events to the controls via this method. This is another lesson I learned. It is bad practice to add event handlers inline, because its messy and your assuming the user has Javascript. Here is the code for modifying the application, this is also cross browsers compatible:
// get a listing of all input tags and hide the ones that are submit buttons
tags = document.getElementsByTagName( ‘input’ );
pagexhr.hideTags( tags, ‘submit’ );
// hide the unneeded sections
document.getElementById( pagexhr.seasonSelect ).style.display = “none”;
document.getElementById( pagexhr.episodeSelect ).style.display = “none”;
document.getElementById( ‘episodeShow’ ).style.display = “none”;
// attach the event handlers to the select boxes
pagexhr.addEvent( document.getElementById( ‘series_id’ ), ‘change’, pagexhr.handleSeriesChange, false );
pagexhr.addEvent( document.getElementById( ‘season_id’ ), ‘change’, pagexhr.handleSeasonChange, false );
pagexhr.addEvent( document.getElementById( ‘episode_id’ ), ‘change’, pagexhr.handleEpisodeChange, false );
You’ll notice this code makes a reference to the addEvent & hideTags functions – which you havent seen the defintion for, but you will shortly. Effectively what happns here is we get a list of all tags in our document and use the function ‘hideTags’ to hide those with a type submit. Next, we hide the select boxes for season and episode selection as they are not needed; we also hide the div used for outputting information about the episodes. Next, we call the addEvent function to register events to the controls on the form, specifically in this case we are registering an ‘onchange’ event handler to each of the select boxes on the page.
Here is the code for addEvent (I will not show hideTags as it is rather mundane, please download the source to see it): I would also like to thank Scott Andrew who is the author of this function for cross browser event registration.
if (elm.addEventListener) {
elm.addEventListener( evType, fn, useCapture );
return true;
}
else if ( elm.attachEvent ) {
var r = elm.attachEvent(‘on’ + evType, fn );
return r;
}
else {
elm[ ‘on’ + evType ] = fn;
}
The idea behind this function is very simple: check if we are using a W3C complient event model (ie FireFox, Flock, Opera, etc) or if we are using a non-compliant model (ie Internet Explorer), and register the event appropriately. Notice that the IE call requires the ‘on’ prefix, this must be included for IE to know what you are talking about. For more information on the parameters refer to Google.
So moving on, at this point I know have my application using JS to hide parts of the interface I dont want to show initially and being visible if JS is not available, their by giving all users a means to use the application. But right now, my event handlers dont do anything, so lets define one of the event handlers. For my purposes, I will show the call by the SeriesHandler to setup the Ajax call and then show how I handle the return values. With this one example, it should be sufficient to explain the rest of the functions, as they are all the same – aside from minor difference in parameters being passed. So without further delay, here is the code for the Series OnChange Event Handler:
if (document.getElementById( ‘series_id’ ).selectedIndex)
{
if ( document.getElementById( ‘series_id’ ).selectedIndex == 0 )
{
return;
}
}
else if (document.getElementById( ‘series_id’ ).sourceIndex)
{
if ( document.getElementById( ‘series_id’ ).sourceIndex == 0 )
{
return;
}
}
else
{
return;
}
try
{
// prepare to send the Ajax request
// IE requires these lines precede the definition of the readystatechange event handler
var qs = pagexhr.buildParameters( ‘series’ );
pagexhr.prepareSend( ‘seasonQuery.php’, qs );
pagexhr.xhr.onreadystatechange = function()
{
try
{
if ( pagexhr.xhr.readyState == 1)
{
pagexhr.progressContainer.style.display = “”;
}
else if ( pagexhr.xhr.readyState == 4 )
{
if ( /200|304/.test( pagexhr.xhr.status ) )
{
pagexhr.success( ‘season’ , pagexhr.xhr );
document.getElementById( pagexhr.seasonSelect ).style.display = “”;
pagexhr.progressContainer.style.display = “none”;
}
else
{
pagexhr.failed( pagexhr.xhr );
}
}
}
catch ( error )
{
alert( error.message );
}
};
pagexhr.xhr.send( null ); // send
return;
}
catch ( error )
{
return;
}
So thats a lot of source, I colored it to help with reading. The basic idea is that we first make sure that we are not looking at the 0 index of the selectbox, which is instructional. I did put a rather important comment following that, something I ran into while develolping this to make it work in IE. The order of the lines really does matter, the open call MUST precede the definition of the readystatechange handler, which I have defined an anonymous function. Then we send the request. Their are several support functions here, once again I wont cover their implementation, their names describe what they do. To see their implementations, please download the source file.
So once we have this function in place, we can copy and paste it to work with handling the selection of a season. The selection of an episode is essentially the same, but uses responseText for its output, but I will describe that later. Now, with the code now, you can make selections in the select boxes, but nothing noticeable happens, what gives you ask. Well, we are not tell Javascript what to do with the response, right now it just calls a function that we havent defined yet. So lets create the success function for handling a successful Ajax request, this function is very simple – mainly cause I use it just to determine what population function to call.
success : function( taction, request )
{
switch ( taction )
{
case ‘season’:
pagexhr.loadSeasons();
break;
case ‘episode’:
pagexhr.loadEpisodes();
break;
case ‘show’:
pagexhr.showEpisode();
break;
}
}
So lets look at the loadSeasons function, to continue with our trend of what happens when the Series is selected. Below is the implementation of the loadSeasons() function:
try
{
var theXml = pagexhr.xhr.responseXML.documentElement;
var seasons = theXml.getElementsByTagName( ‘season’ );
var selectBox = document.getElementById( ‘season_id’ );
selectBox.options.length = 0;
// insert the new option first
selectBox.options[ selectBox.options.length ] = new Option( “Please Select a Season”, “” );
for( i=0; i<seasons.length; i++ )
{
var name = theXml.getElementsByTagName( ‘name’ )[i].firstChild.data;
var id = theXml.getElementsByTagName( ‘id’ )[i].firstChild.data;
selectBox.options[ selectBox.options.length ] = new Option( name, id );
}
}
catch ( error )
{
alert( error.message );
}
Ok so this code is quite self explanatory. We acquire the XmlDocument from responseXml.documentElement. Effectively you can think of this reference as another HTML page so your age to use all the DOM techniques to get the data from it. In particular we are interested in all the tags as they contain the information for each season contained within this series. Once we get the array using the getElementsByTagName function we perform a simple iteration and extract the necessary data from each set. We then create a new Option object an insert it into the select box. Note that this interface is not all that intuitive. Whereas most collections have some sort of an add function, Javascript does not. However, we are able to append to the list by adding new items to the end, hence why I call the index based on the length of the options array, which is dynamic and is incremented each time we add a new element. You will also notice some cosmetic manipulation above the for loop, not required, done for affect and to make the user interface most intuitive.
Once that is complete, we hide our progress updater and execution ceases, this is the end of the script. Its actually quite simple when we break it down, but it does take some practice. However, contrary to what many people seem to believe, writing Ajax style is not all that difficult, nor is the DOM manipulation that follows.
Part 2 : Making it work without Javascript
If your new to server side programming this portion of the program might present more of a challenge to you, for those hardend server side programmers this is pretty much what we do all the time anyway. In the first section, I made several references to why its important to have Javascript manipulate the interface after we determine its available. The reason, again, is we must assume the user does not have Javascript enabled so our page works regardless. This does not mean we write multiple versions of the page, it just means we have to add additional logic in to server side programming so that it functions along side Ajax style calls.
For example, I hide the submit buttons so the user cannot submit the form, thereby it becomes impossible for them to use the PHP scripting that is tied to this script. This is a really simple trivial way to handle this situation, and also why you do not declare JS event handlers inline. Lets look at some PHP code I wrote, but first an excerpt from the application showing PHP being included conditionally:
<?php
if (!isset( $_POST[ ‘btnSeriesSubmit’ ]) )
{
?<
Please Select a Series
<?php include_once “./includes/seasonQuery.php”; ?>
Nothing much to say here, basically if I detect that a series id is being submitted, I see the button in the $_POST array then I know that I am going to be filling the select box with seasons, hence saying to select a series is bad UI so I exclude it from being added to the options array. Now I present PHP code from the included file: A small note, I am assuming you are familiar with PHP/MySQL data access functions and thus will not explain them or how to use them. If you do not know how this is done please visit this URL: http://www.php.net/mysql
<?php
if (isset( $_POST[ ‘btnSeriesSubmit’ ]) )
{
?>
“”>Please Select a Season
<?php
// connection and querying code
while ( $row = mysql_fetch_assoc( $s ) )
{
if ($sid == $r[‘id’])
$selected = ‘ selected=”selected”‘;
else
$selected = “”;
?>
<option value="”
><?php echo stripslashes( $row[‘name’] ); ?>
<?php
}
// close the connection
}
?>
For those PHP programmers out there this code looks pretty standard. Its only real flaw is the fact that when compared to the Ajax experience, this one isnt quite as slick as menus reset themselves and dont hold their values in between postbacks. In a real app, this would be a necessary step, but as I am just trying to give people the general idea, this is sufficient. The idea is that you would include similar files for each type based on the data that had been submitted and then display the options in the select boxes. Its very simple and quite easy to implement. Likewise, on a website you would have PHP code look for the submit which you would disable or not allow if you were using an Ajax style callback.
The final step : Making IE work
If you have read my previous post you know that I became quite frusterated last night attempting to get my application to work in IE7. For the most part it seems that the Event model has not changed and has little in common with the set W3C standard followed by just about every other browser on the planet. To further make things difficult IE lacks any kind of extension for debugging JS leaving the developer to the old “alert to check” debugging. I am going to note some things in this script that are changes made to support IE7.
if (elm.addEventListener) {
elm.addEventListener( evType, fn, useCapture );
return true;
}
else if ( elm.attachEvent ) {
var r = elm.attachEvent(‘on’ + evType, fn );
return r;
}
else {
elm[ ‘on’ + evType ] = fn;
}
This is one that I already spoke about in the first section, but it is a good idea to mention it again. IE does not use the W3C standard addEventListener, instead it uses a global object (window.event) which would require the use of another function getTarget we were using the event argument passed into our event handlers. Since I elected to simply get the references to the select boxes, this is not needed, but it may be needed in your case. Google for getTarget and I expect youll find the definition for the function.
The next difference is one I already pointed out again:
try
{
// prepare to send the Ajax request
var qs = pagexhr.buildParameters( ‘series’ );
pagexhr.prepareSend( ‘seasonQuery.php’, qs );
// define readystatechange method ( .. not shown )
// send the request
pagexhr.xhr.send( null );
}
catch ( error )
{
alert( error.message );
}
This is something that literally drove me nuts for the better part of an hour. Because IE showed no errors and every alert test I ran showed it completing. But if you define the header information (which is what prepareSend does in addition to opening the request) after the definition of the readystatechange event handler and you have multiple Ajax requests, it will not work. If you move the prepareSend line above the readystate event handler definition, it works. This is boggling and no one I asked could really explain why this is, so I added it to the list of Microsoftie oddities I have found.
The application now works with JS and without and is cross browser.
Final Thoughts
Given the amount of time I spent developing this I would say its a good idea to use pre-made libraries if available. That being said, Ajax is not so complicated that you should avoid writing your own altogether. I rather enjoyed doing this, for the most part. Admittingly, it was rather annoying and testy trying to make things work in IE because of A) the lack of an Add-On to properly debug JS; VS2005 was no help either and B) the lack of satisfactory error reporting when an error does occur. Of course being compliant with most of the W3C standards is a definte starting point for MS to creating a browser that can challenge FireFox in both usability and under the hood features.
And now a link to the source code: here
As to a license and that jazz, yeah its not that important π do what you want with this code I dont care π
Valuable info. Lucky me I found your site by accident, I bookmarked it.
LikeLike