When I start learning a programming language, which is not very often, it always helps me to play around with stuff the minute I learned it. Sometimes reading and playing happens at the same time. Understanding the basic concepts is one part but the other one is using them. When I learned Perl I started to program a small address book application , when I learned Actionscript I started to program animations rather than animating them the Flash way. Now with Javascript I programmed something useless – useless for all of you guys but it helped me a lot learning the features of the language. Since I wanted to find out how all this Web 2.0 madness works I tried to play with every aspect of Javascript. My simple project is located here. What is it about? Well on the surface its an input field where you can enter an URL to a favicon.ico file. Once you entered the URL and pressed Return, the favicon of your choice appears in the 20 x 20 matrix below. But of course it is not that simple. When you enter the URL and press Return, the Javascript fires an asynchronous post request to a perl cgi which is checking if the favicon really exists. It is not possible to do that directly in Javascript because of a very useful security policy which says that Javascript is not allowed to connect to foreign domains unless you sign your Javascript file with a Root CA. Javascript can fire post and get requests but when you try that on a foreign domain you’ll get a »permission denied«. Firefox handles this one pretty strict, IE forbids it with the right security setting, Safari however ignores this policy completely. I haven’t checked Opera but from what I read it should give you some sort of warning as well. This one was very confusing since Safari allowed me to fire Get and Post requests to foreign domains. I should probably tell the webkit guys. Anyway, the cgi script is an open proxy which gets an URL and checks if it gets back a 200 HTTP Header. It doesn’t fetch the favicon, it is just looking if everything is fine. After that the cgi connects to a mysql database to look if the URL is already in it, if not it inserts the URL. The cgi gives back different error messages to the Javascript which are taken care of in the XMLHttpRequest callback function. This way the user gets feedback either if the URL is already existing or if it isn’t valid. This all happens in the background without any reloading which is the point of this whole AJAX mambojambo and it works great.
When you load the page there is a timeout of one second before the window.onload event fires a function which calls another cgi to request all existing URLs in the database. The query.pl queries the database and sends back a list to the XMLHttpRequest callback function. The function is splitting up the list of URLs and generates new IMG tags for each URL. This is what it does, but how does it really work?
All the favicons are objects. When the callback function of the initial database query gets back all the URLs and splits them up it creates a new Javascript object for every URL. The list also provides the database ID of the URL so every object has an ID, an URL, and a FaviconURL. The favicon objects also get a prototype function called “show”. This prototype function takes care of the HTML representation of the favicon object. It creates an a element which links to the Object.URL, within the a tag it creates an img tag which has the attribute »src« which links to the Object.FaviconURL, it also sets the id of the img tag to the Object.ID and in the end it attaches a couple of event handlers. The prototype function, in fact, looks like this:
Favicon.prototype.show = function() { var faviconContainer = document.createElement("a"); faviconContainer.setAttribute("href", this.url); var favicon = document.createElement("img"); favicon.setAttribute("id", this.id) favicon.setAttribute("src", this.faviconLink); favicon.setAttribute("class", "favs"); favicon.onmouseover = function(oEvent) { Tooltip.create(this.id, FaviconObjects[this.id].url, oEvent); } favicon.onmousemove = function(oEvent) { Tooltip.move(oEvent); } favicon.onmouseout = function() { Tooltip.hide(); } faviconContainer.appendChild(favicon); document.getElementById("wrapper").appendChild(faviconContainer); }
The huge advantage is that you don’t have to take care of getting the right element references all the time. Especially when I attach the event handlers for the tooltip this comes in handy. Before I discovered prototype functions I told the tooltip to get the id via DOM from the HTML element which was terribly slow compared to this. And it was much more complicated as well.
Another nice thing is the FaviconObjects object. Here I used this object as an associative array to hold the references between the HTML objects and the Javascript objects. So with the id which is the same of the HTML and the Javascript world I can get instant access to either one of those. The callback function looks like this:
/*Callback Funtion for initial Pageload /getExistingIcons()/ */ function queryCallBack(sData) { var splitArray = new Array; var existingURLs = sData.split("\n"); urlCounter = existingURLs.length-1; for (p=0; p<existingURLs.length-1; p++) { splitArray = existingURLs[p].split("_"); FaviconObjects[splitArray[0]] = new Favicon(splitArray[0], splitArray[1]); FaviconObjects[splitArray[0]].show(); } }
So if you’re still there you can look at the entire javascript by clicking here. I commented all the functions so you should understand it pretty easily. I rewrote it after I finished all the all the small components to make it as readable and efficient as possible. If you have any annotations to it – let me know.
I learned to love objects, literals and anonymous functions as well as prototyping. This is why I fell in love with Javascript. Well and because most of the perl regexps work too.
Now I’d like you guys to fill the 20 x 20 Matrix with more favicons – during debugging I ended up adding already 124 favicons and in the end it was hard for me to come up with any more. When the matrix is filled I want to implement drag and drop and position coordinates which are also saved in the database. It would be nice to parse standard compliant favicons as well but I haven’t had enough time recently to get that working too. I guess I would have to do that in a cgi as well so this is more a question of Perl coding than Javascript voodoo.
As I said, I would be glad if more experienced Javascript coders would give comments on my code. So long.
// Not everything is escaped properly like backslash n so for the real Javascript check the linked file.
Technorati Tags:
web, ajax, web 2.0, javascript