Random Entry: Opera 9 Object Detection

< Ajax: What is it Good For? Page 1 | Chitika Fading Ads >


Ajax History: It’s in a Name


Download Example Files


While Ajax and Web 2.0 are all the rage at the moment one of the most frequent criticisms of Ajax is the lack of the browsers back and forward history functions when using Ajax powered applications. There have been a few attempts at fixing this problem with variable degrees of success. It has to be stated though that no technique I investigated fitted the needs of this site. Part of the difficulty here is that this site is essentially a multi-application site, where multiple blog applications are appearing to the client as one application.


To gain an appreciation of how difficult storing history is I suggest you go over to http://www.live.com/ where some states are stored in history and others not. For example if I click on the entertainment category and then click on the sub category fashion a history event is recorded. If I click on the browser back button then it takes me back to the start page. So far so good.



However if I click on the entertainment category and then click on the sub category fashion and then additionally click on another category let’s say business and then fast company then it takes me back to the front page again, not the page I was previously viewing. This is not necessarily a criticism of Microsoft as many Web 2.0 applications fail in this same manner and as I stated before every history solution I have seen certainly doesn’t work on this site.



Aside from that even the history solutions that work on relatively simple Ajax examples typically involve a lot of JavaScript which consequently slows the loading of the page down and in the more complicated examples; they do not work at all. Not a good thing…



So what then is the answer? The answer believe it or not does not involve JavaScript at all. Rather it has to do with name attribute and an IFrame but not in the way you probably envisage.



You may have already heard of people using a hidden IFrame to store browser history which is in fact the method employed at live.com and quite a number of other well known Web 2.0 applications. But the problem with that method is that it only allows partial history to be retained as we saw with the live.com example. What we really need is for the IFrame to store all history regardless of what page is being loaded into the Ajax framework.



In order to achieve this we need to do the following:



First we do not embed a hidden IFrame on the main interface page. Rather what we do is embed an IFrame in separate HTML page and suck that into the main interface with xmlhttprequest. Our code for the separate page should then end up looking similar to this:



<html>



<head>



<title>Ajax Example</title>



</head>



<body>



<iframe src="loadexample1.html" name="contentFRM" id="contentFRM" width="100%" height="100%" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" allowtransparency="true"></iframe>



</body>



</html>



Let’s call this page loadexample1.html. What is important to note at this stage is the usage of the name attribute on the IFrame (name=”contentFRM”) and the src attribute of the IFrame which should point to page you want to be displayed in your main interface.



We then load this page using xmlhttprequest, for example,



<a href="components/mainblog.html" onmouseup="ajaxManager('load_page','loadexample1.html ','contentLYR')">Example 1 </a>



A couple of things to note here. First if you use onmouseup instead of onmousedown opera 8.5 triggers the history properly. I have found that using the onmousedown event with the method that is being demonstrated here functions inadequately in that browser were repeated clicks on the forward or back button are required to get history going. To circumvent all that use the onmouseup event when calling your Ajax loader function.



The ajaxManager function is a simple Ajax loader that loads pages by using xmlhttprequest and puts the content into a specified layer element; in this case the layers id attribute is contentLYR.



I repeated this same procedure for a few pages so that we can gain an appreciation of how this method works as its being built up. Let us look at what we have so far



View Example 1



As you can see in terms of history it tends to function much along the lines of live.com with the added disadvantage of not using Persistent Interface Design principles. We need to do better.



In the next example we set a target attribute on the links e.g.,



<a href="loadexample1.html" target="contentFRM" onmouseup="ajaxManager('load_page','loadexample1.html ','contentLYR')">Load Example 1 </a>



View Example 2



As you can see from this example, we have a Persistent Interface design where only the content is being changed, but our history has disappeared altogether. If we think about these two examples, then we should be able to conclude that xmlhttprequest is loading the page as expected but also overriding the target attribute we set on our links which in turn doesn’t allow for the storing of the browsers history. In theory by using the target attribute in such a manner the iframe should update and load history. Instead what is happening is just the xmlhttpprequest is updating the content. We need a hybrid of both methods to really store history properly.



Let us see what happens when we put in an additional IFrame with the same id and name attribute as the externally loaded content into the main interface.



View Example 3



If you viewed the example in Firefox both the IFrame in the main interface is being updated as well as the xmlhttprequest that is loaded into the contentLYR. What is more we have history functioning properly in that browser. Internet Explorer and Opera on the other hand are only updating the xmlhttprequest content. The IFrame doesn’t change. But there is something interesting going on, a history array is being created in these browsers if we click in a link twice. We can decipher this because the back button becomes functional, but nothing really gets updated since the xmlhttprequest object cannot store history.



It is progress at least, as we have one browser working and at least something going on in the other two. Now we need to make Internet Explorer behave like Firefox and in order to do that we need to outsmart the browser.



We have to get the browser to load the xmlhttprequest object and render that content but at the same time update the IFrame. To achieve this we use two separate layers where one layer will load content through xmlhttprequest but will remain hidden and the other layer will be used to track the history and ultimately render the content.



Before proceeding let us be clear, we are not loading content twice, rather the content is simultaneously being loaded into both an IFrame and the xmlhttprequest object. In practical terms it would be similar to having ten images named somepic.jpg on the same page, that image would be loaded once, the 9 replica images would consequently be pulled from the cache and rendered so that there is no degradation in performance. Same thing is going to happen here with the added advantage of not creating speed bottlenecks because we use xmlhttprequest to load other components asynchronously.



Let us set up a proper working example to demonstrate. First in the main interface I am going to create two identical layers, with the only differences being different id attributes and one layer set to hidden while the other remains visible. I have named the two layers contentrenderLYR and contentLYR respectively. The contentrenderLYR will be the one with visibility attribute set to visible where the other layer, contentLYR visibility attribute is set to hidden.



We also take out the IFrame on the main interface as we do not need a second IFrame, rather we used that previously because it is useful to see what is going on between browsers in a visual manner.



The other change we make is to initially load the content into the contentrenderLYR.



<body onload="ajaxManager('load_page', 'loadexample1.html', 'contentrenderLYR')">



Now remember that an IFrame is being loaded into that layer via xmlhttprequest, where the IFrame’s name attribute is set to contentFRM. In fact all pages being loaded use the same name attribute contentFRM.



If you look at the links on the main interface, e.g.



<a href="loadexample1.html" target="contentFRM" onmouseup="ajaxManager('load_page','loadexample1.html ','contentLYR')">Load Example 1 </a>



You will notice that subsequent calls to the ajaxManager function load pages into the contentLYR, the one that is hidden. But since we also have a target attribute set on the links, the content is being shifted over to the contentrenderLYR, the one that has also has a contentFRM attributed embed.



Each time we click, the content comes in via xmlhttprequest and is continually passed over to the other layer via the target attribute so that the history updates as well as maintaining a Persistent Interface Design where the content is rendered as intended.



Let us view this in action:



View Example 4



As you can see we have back and forward history functionality, as well as a Persistent Interface Design where the only thing being updated is the content. In addition we also are still able to utilize the benefits of using xmlhttprequest, in fact I load a number of components here on my site on demand by using Ajax. Plus there is one more added benefit and it’s a biggie. Accessibility…



<a href="loadexample1.html" target="contentFRM" onmouseup="ajaxManager('load_page','loadexample1.html ','contentLYR')">Load Example 1 </a>



You will note that we do not use null links with this method. We use a proper link so that search engines and browsers not capable of using JavaScript can still follow the links. In the loaded pages itself, you should place links in between the start and end IFrame tags so as to allow search engines and other JavaScript unaware browsers to follow the links.



So there you have it: A way of keeping Ajax history without a single scrap of additional JavaScript code. But just before I leave you, I actually do run a bit of JavaScript code to attach the target attribute dynamically. This is useful for some DTD’s and if you have a lot of links then typing out the name attribute repeatedly can be a pain. The following function will allow you to dynamically add the name attribute to links:



function setnameAttribute()



theLinks = document.getElementsByTagName("a");



for (i = 0; i < theLinks.length; i++)



{



theLinks[i].setAttribute("target", "contentFRM");



}



}



Just call it on the body onload event and it will attach the target attribute automatically.



I have tested this method on Opera 8.5, Opera 9 preview, Internet 6 and 7, and Firefox 1.5 and it is working there fine. Unfortunately I don’t have a Mac to test out Safari, so if it works or breaks there please leave a comment and we will take it from there. Either that of you send me many thousands of dollars to buy a Mac ;-)



I also know it breaks on Mozilla 1.7 but that has to be a regression bug that I will file when I find a bit of spare time.



So anyway enjoy and have fun with this history technique. Oh and in case you’re wondering about bookmarking, Ill demonstrate a neat bookmarking technique in the near future. Just got a few little bugs to iron out first.



Here are the Download Example Files in case you missed them at the top of this page.



Defined tags for this entry: Ajax, Ajax History, Web 2.0

Posted by Eddie Traversa in Ajax at 04:00 | Comments (4) | Trackbacks (0)


Trackbacks

Trackback specific URI for this entry


No Trackbacks


Comments

Display comments as (Linear | Threaded)


Awesome work!! Thanks for sharing.

#1 peter on 2006-01-09 09:34 (Reply)

Thanks Peter

#1.1 Eddie Traversa (Link) on 2006-01-11 23:10 (Reply)

Back buttons and history can be easily implemented in ajax see here

#2 Scott (Link) on 2006-02-22 09:18 (Reply)

im just testing

#2.1 alex (Link) on 2006-03-15 07:17 (Reply)


Add Comment

http://dhtmlnirvana.com/

http://dhtmlnirvana.com/svg/index.htm

http://dhtmlnirvana.com/ajax/ajax_tutorial/

http://dhtmlnirvana.com/alchemy/externalload.htm

http://dhtmlnirvana.com/program/permalink/ajax_history.html

http://dhtmlnirvana.com/content/dhtml.html

http://dhtmlnirvana.com/ajax/gallery/ajaxgallery.html

http://dhtmlnirvana.com/spiritual/2005/05/07/uber-dream-analysis/

http://dhtmlnirvana.com/content/widgets/killautolink/index.html

http://dhtmlnirvana.com/oldalchemy/externalload.htm

http://dhtmlnirvana.com/permalink/wpfe.html

http://dhtmlnirvana.com/svg/intro1.htm

http://dhtmlnirvana.com/program/permalink/ajax_tutorial1.html

http://dhtmlnirvana.com/content/blog.html

http://dhtmlnirvana.com/svg/

http://dhtmlnirvana.com/program/

http://dhtmlnirvana.com/spiritual/

http://dhtmlnirvana.com/alchemy/preload.htm