One of the most common tasks for a web developer is implementing an AJAX solution to create a more dynamic interface. A common method is to query a database, get some results, parse the results and update the interface without needing to refresh the page. But how do we customize our data, prepare it for some parsing, use the data returned to create something useful? The other day I had come up with a nice little tidbit that I figured web developers out there might appreciate. We will talk about some code for setting up some results from a query, putting it into a format we can use and then use that format to quickly update some list boxes. The end result are list boxes, you might have seen, where you can list items based on the selection of a combo box. We will show you the tools you need for this mission. So don’t hit that back button… this is the Programming Underground!
The old story of AJAX goes a bit like this… you create an XMLHttpRequest object which you then use to send a GET or a POST request to a server-side script using JavaScript. The server-side script processes the results, sends the results back to the JavaScript which then parses that info and updates controls on the page. All without a page refresh. Pretty straight forward right?
I am going to assume you can do the basics of that and jump up a little into some code that will send a request for data to a PHP script which then puts our results into an XML format and sends it back to the JavaScript. Follow me so far? Good! Then we will pull out the nodes we want and use them to update a list box control on our screen. This process can be kicked off by an onchange event from a drop down box, an onclick even from a button or even called from another function. That is not too complicated. However, here is the little twist that I think is going to be cool. In our call to get data we are also going to give it the name of a function of our choosing. Programmers may know this as a “callback” function. We will check if this function exists and then call it providing it the results returned from our server-side script.
Here is the code….
// Gets data from a file that provides a list (great for listboxes, combos, anything with a list) // Returns an array of list items. function getListData(sourceFile, callbackName) { XMLRequestGet = getObject(); var result = new Array(); if (XMLRequestGet) { XMLRequestGet.open("GET",sourceFile); XMLRequestGet.onreadystatechange = function() { // Get response, create appropriate XML Dom Document based on browser type // Expects document to be in <root><item>Item1</item><item>Item2</item>...</root> format if ((XMLRequestGet.readyState == 4) && (XMLRequestGet.status == 200)) { DomDoc = getXMLDocument("root"); DomDoc.async = false; DomDoc.preserveWhiteSpace = true; // Parse and read nodes if it is FireFox if (document.implementation && document.implementation.createDocument) { parser = new DOMParser(); DomDoc = parser.parseFromString(XMLRequestGet.responseText,"text/xml"); var rssItems = DomDoc.getElementsByTagName("item"); for(var i = 0; i < rssItems.length; i++) { result.push(rssItems[i].childNodes[0].nodeValue); } } else { // Parse XML if it is IE DomDoc.loadXML(XMLRequestGet.responseText); var rssItems = DomDoc.selectNodes("/root/item"); for(var i = 0; i < rssItems.length; i++) { result.push(rssItems[i].childNodes[0].nodeValue); } } // function exists, call it with the results if (window[callbackName]) { window[callbackName](result); } } } // Send the request XMLRequestGet.send(null); } } function getXMLDocument(rootTagName, namespaceURL) { if (!rootTagName) rootTagName = ""; if (!namespaceURL) namespaceURL = ""; if (document.implementation && document.implementation.createDocument) { // This is the W3C standard way to do it return document.implementation.createDocument(namespaceURL, rootTagName, null); } else { // This is the IE way to do it // Create an empty document as an ActiveX object // If there is no root element, this is all we have to do var doc = new ActiveXObject("MSXML2.DOMDocument"); // If there is a root tag, initialize the document if (rootTagName) { // Look for a namespace prefix var prefix = ""; var tagname = rootTagName; var p = rootTagName.indexOf(':'); if (p != -1) { prefix = rootTagName.substring(0, p); tagname = rootTagName.substring(p+1); } // If we have a namespace, we must have a namespace prefix // If we don't have a namespace, we discard any prefix if (namespaceURL) { if (!prefix) prefix = "a0"; // What Firefox uses } else prefix = ""; // Create the root element (with optional namespace) as a // string of text var text = "<" + (prefix?(prefix+":"):"") + tagname + (namespaceURL ?(" xmlns:" + prefix + '="' + namespaceURL +'"') :"") + "/>"; // And parse that text into the empty document doc.loadXML(text); } return doc; } }
Now at first glance this code looks a bit complex and long. The idea behind it is rather simple though if you take a minute to break it down. First we have a function called getListData() which takes two parameters… a sourceFile which is going to be our PHP script (I am not showing here) and the name of a function we are going to use as our callback. When the script is finished, it will use this function name to call the function (also not shown) and give it the results array we created.
The function “getObject()” is a JavaScript function I have for getting the appropriate XMLHttpRequest for the given browser. You can substitute your own XMLHttpRequest creation function if you like. Once you have this object, we send out the request to our server-side script using the file name with any parameters you like. An example of the sourceFile might be myfile.php?param=value¶m2=value.
The script I have built simply checks the value of the parameters, builds a SQL query, queries the database and returns the values in a XML setup like this….
<?xml version="1.0" encoding="UTF-8" ?> <root> <item>Test 1</item> <item>Test 2</item> <item>Test 3</item> </root>
So our server-side script is going to return this to the JavaScript code and trigger the code which checks the “readyState” and the response status code. Then we go into calling our second function, titled getXMLDocument(), which will create a DOMDocument object. This object is a node hierarchy of sorts which we will then use to parse our XML. This part has to be done asynchronously and to preserve white space just to make it usable for both IE and Firefox. You will notice it also takes an optional namespaceURL parameter which we will not use in this example.
Now we have our XML results and we have loaded them into a DOMDocument. Now again we have to parse the document according to the browser type. If it is Firefox we have to setup a DomParser object and call our item elements by their tag name. This will create an array of elements which we can loop through and store in our “result” array. Otherwise we have IE load the XML straight in and use the DOM to call a method called “selectNodes” to select our items. This too creates an array which we loop through and add to the results array.
Once our results array is filled, here is where the magic starts. Using a bit of Javascript wizardry, and a little known tip that functions must always belong to an object (in this case the window object), we can concatenate our callback function name to the window object and evaluate it. This is placed in an if statement which will check to see if the window object has this function defined. If it does, it will then call it using the window object itself.
So to hit this from another way, whenever you create a JavaScript function, it either belongs to an object you created or it will belong to the “window” object. When a function is added to window, you can access that function using the window object array and the function name as its index (aka key) and give it our results as a parameter. This little bit of code will complete the code for using a callback. With this array and passing the result array as a parameter, we can essentially call any function you want (but it must take one parameter, our result array) and that function then could update our interface.
I created this setup because I wanted a script that could generate list of choices, lists of items, lists of numbers or months or whatever and give it a function to use as a callback. Now I can update the server-side script and use one file to generate any kind of database lists I want to fill my listboxes.
You can read through the code and check out my in-code comments. Don’t pay too much attention to the getXMLDocument function more than just finding out how I am fetching a DOM Document. You can ignore the namespace stuff for now. The idea here is that your server-side script will generate a very basic item list of XML which you can then parse and populate lists.
I hope you find this script of use to you in the future and that you can get some ideas on how you might setup a JavaScript function which will setup a callback function. That feature alone is worth its weight in gold and would be essential in any JavaScript framework you might be building.
Enjoy and thanks for reading. 🙂