Recent Weblogs

Links I like

An Evolution of Service tackles the Ajax History paradigm

The idea of an Ajax Service is a class instance that handles all interactions with remote requests(XHRs). Coupled through an observer pattern is the interface components that render the data into DOM elements.

Ajax.HistoryService

The idea of the Ajax Service quickly lead to realization that if instead of dispatching this information per request, I could save it, then potentially have it available for dispatching that same request, thus bringing my interface back to that state.

An HTML Microformat, HistoryElement

I had looked into previous methods of the history paradigm and the problem most people were running into was that JS created objects had horrible problems, but an object created from HTML and the browser's native processing worked much better across the board. So instead of attempting to create objects inside the JS class I have used an HTML object which follows a microformat. The microformat being that it will contain at least two elements, one being an IFrame and the other being a Form. We will use these objects to mimic form submissions in the window without a top level reload. This action will register with the browser's history module as that is what we're going for to capture the back and forth buttons on our page, and how can we do that? By capturing the load event of the IFrame.

Look Ma, No Polling

Instead of polling the window.location like other history solutions. The HistoryService just listens to the iframe's load event, which occurs everytime the service registers a request, or when the user hits back and forward. So basically it only takes action when you'd expect, and doesn't slow already intense applications with polling intervals.

Nuts and Bolts

Instead of the service directly dispatching events upon receiving the data it stashes the data and the event type in an object and pushes it onto the historyArray that it stores as a property of the instance. Upon stashing it immediately submits the form which initiates a load event which will infact dispatch that event. The advantage of this is that the event architecture is completely reliant on the iframe's reloading, such that when the user clicks back it is dispatching previously registered requests, thus updating the information in each interface component.

For a page dedicated to displaying all class extensions on top of prototype.js needed to get your own copy of Ajax.HistoryService working, check out the code base

The Demo

Current History Index :
Main Number
0
Addition Multiply Factorial
0
0
0

A Grander Demonstration

I understand that the math demo doesn't entail all aspects of an application, I've set up a Google Map interface so you can progress into deeper, richer levels of state and see how the interface responds to the actions of back and forward. View Google Map demo

The Code

This code relies on the EventDispatcher class and Ajax.Application.Base to build the Ajax Service which is what the code below is built upon.

/**

Copyright (c) 2007 Matthew E. Foster

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Ajax.Application.Event = function(){};
        
Object.extend(Object.extend(Ajax.Application.Event.prototype, Ajax.Application.Base.prototype), EventDispatcher.prototype);

Ajax.Service = {};
    
Ajax.Service.Base = function(){};

Ajax.Service.History = function(){};
    
Object.extend(Object.extend(Ajax.Service.Base.prototype, Ajax.Application.Event.prototype),
                    {
                        sendRequest : function(dto, cb){
                            
                            this.dispatchEvent("request", { dto : dto, callback : cb });
                            
                            Ajax.Application.Event.prototype.sendRequest.apply(this, [dto, cb]);
                        
                        },
                        receiveRequest : function(cb, eAja){
                            
                            this.dispatchEvent("response", eAja);
                            
                            Ajax.Application.Event.prototype.receiveRequest.apply(this, [cb, eAja]);
                        
                        }
                        
                    }
                );


Object.extend(Object.extend(Ajax.Service.History.prototype, Ajax.Service.Base.prototype),
            {
                
                    buildInterface : function(obj){
                        
                        this.historyArr = [];
                        this.historyIndex = undefined;
                        
                        
                        this.container = $(obj);
                        this.historyFrame = this.container.down("iframe");
                        this.form = this.container.down("form");
                    
                    },
                    attachListener : function(){
                    
                        Event.observe(this.historyFrame, "load", this.reloadHandle);
                    
                    },
                    createListener : function(){
                                                        
                        this.reloadHandle = this.handleReload.bindAsEventListener(this);
                    
                    },
                    handleReload : function(e){
                                    
                        var index = parseInt(this.getHistoryIndex());
                        
                        var obj = this.historyArr[index];
                        
                        if(!obj)
                            return true;
                        
                        this.historyIndex = index+1;
                        
                        this.dispatchEvent("reload", [obj, index]);
                        this.dispatchEvent(obj.type, obj.arg);
                        
                    
                    },
                    
                    getHistoryIndex : function(){
                        
                        return this.getIndex(this.historyFrame.contentWindow.location.toString());
                        
                    },
                    getIndex : function(str){
                        
                        return str.replace(/.*index=/gi, "");
                    
                    },
                    getQuery : function(str){
                        
                        return str.replace(/[^?]+?/gi, "");
                    
                    },
                    
                    registerRequest : function(type, eAja){
                    
                        if(this.historyIndex && this.historyIndex < this.historyArr.length)
                            this.historyArr.length = this.historyIndex;
                            
                        this.form.index.value = this.historyArr.length;    
                        
                        this.historyArr.push({ type : type, arg : eAja});
                                                        
                        this.form.submit();
                    
                    }
            
            
            
            }
        );
					

Comments

August 17, 2007Gregory

This code is pretty genius, you're the only one who seems to have cracked the safari problem.
How does this degrade for a browser that does not support iFrames? Does it still load the new content, or will it fail at the iFrame step?

September 12, 2007Casper

This is great. Any chance this could somehow be used to tackle the Ajax bookmarking problem also?
I.e. let's say I press the "Get Number" button in your demo a couple of times. I find a great number and I want to bookmark it. Right now it is not possible to do..

October 15, 2007the friendly ghost

Sweet! Since you're not polling this even works on Opera 9.23.

October 27, 2007Mr Friendly

Yeah this code seems great, just need some help implementing it lol, if anyone can help me out let me know. Sent you an email with my email too hope u got 5-10 mins to spare cheers Mr Friendly.

November 08, 2007Ehsun

Hi,
Is it using IFrames or XHRs???
It seems to use IFrames somewhere, right?

November 27, 2007Steve

I'm not quite sure how to integrate this .
I've been staring at the HistoryService for a while, and gather that this getNumber() notion could be altered into some other function like getCircumference(d) -
I've got a dropdown that calls a function that essentially means getPage(this.value); I'd like these page calls to be entered into history. How would you think one could use this framework for this?

January 12, 2008Yusuf Akyol

Hi,
Wolud you please send simple demo files? Thanks . best regards. yusufakyol@gmail.com

May 29, 2008Rushiraj Yadav

Would please send me some demo files how to implement the provided code.
It would be very helpful if i have some illustrative code.
If possible please send it on rushi.yadav@gmail.com

June 18, 2008qsd

sqd

July 09, 2008Jimmy

First of all, love your work! So thank you for sharing it with us.
The number demo above does not work for me in Firefox 2.0 on Win. Works in IE7. Is this a known issue or am I missing something?
Thanks!

Name
Site
Comment