Recent Weblogs

Links I like

A Method for Saving State in Ajax Applications

Two distinct operations are involved in saving state in an ajax application. First operation is to observe all XHR requests and keep the most recent particular to a server operation. I have heard it refered to in many ways, implicit invocation, or service oriented architecture(SOA), what have you the idea is that you send a particular variable that defines which chunk of logic is executed on the server. Some sort of method or group of methods that execute specific to this request. In most of mine I call it an action, in MachII they call it the event, in cold fusion components they call it the method. The point of this is you have your action line up to a hash, such that a new action will overwrite the last action in the "bookmark hash".

The Demo

Bookmark this page
Main Number
0
Addition Multiply Factorial
0
0
0

BookmarkListener

So in the end you must maintain all of the most recent unique requests to rebuild your current interface state. The BookmarkListener class observes the request event of an Ajax.Service.Base class and does exactly this. As noted above, the action variable is my "service identifier", once again I have no solid term for this, so i'll just refer to it as the action.

The BookmarkListener class throws its own event after its stashes the new request. The event name is called "refresh" and it passes a hash object, I think the method of interest for this case becomes toJSON. Where to put this JSON string is another question. First thought that comes to mind is the window.location after a hash mark, but half the reason this article took me so long to publish was to see if I couldn't get it to play nice with Ajax.Service.History but adjusting anything in the window.top.location completely fubars the history operations, its a long story. Anyways there are alternative opportunities for allowing a user to access this bookmarked URL. I thought that social network buttons would benefit from this greatly. The advantage is that it is decoupled from any specific implementation, such that you can stuff it in window.location or you can put in an href, its up to you, I think i'll do both.

BookmarkPrimer

That is half the battle and could be the end if you employed some smooth server side scripting to statically "prime" the Ajax service. That is how I envisioned it, a mechanism to prime the service, to boost the engine before it begins running normally. This requires a subclass of Ajax.Service.Base called Ajax.Service.Bookmark (Who saw that coming?!) The only extra functionality of the bookmark class is there are functions for the primer to determine which action gets which call back function. The primer extracts the JSON from the window.location or actually any string you send it in the constructor but trust me, you'll want window.location.href or window.location.toString(). It then extracts data past the hash mark and is expecting an array of objects to send to server, in JSON notation. By having access to the appropriate callback function the primer doesn't need to know the methods of the service, it just has to use the sendRequest method and the object it has from the window.location and the appropriate callback, the service takes over from the response.

The Code

/**

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.
**/
var BookmarkListener = Class.create();

Object.extend(Object.extend(BookmarkListener.prototype, EventDispatcher.prototype),
                {
                    initialize : function(){
                        
                        this.createListener();
                        
                    },
                    createListener : function(){
                        
                        this.serviceHandle = this.handleRequest.bind(this);
                    
                    },
                    handleRequest : function(obj){
                        var cache = this.getCache();
                        
                        cache[obj.dto.action] = obj.dto;
                        
                        this.setCache(cache);
                        
                        this.dispatchEvent("refresh", this.getCache());
                        
                    },
                    getCache : function(){
                        
                        if(!this.bookmarkHash)
                            return this.bookmarkHash = $H();
                        
                        return this.bookmarkHash;
                                                        
                    },
                    
                    setCache : function(obj){
                        
                        if(!this.bookmarkHash)
                            this.bookmarkHash = {};
                            
                        Object.extend(this.bookmarkHash, obj || {});
                        
                    }
                    
                    
                
                }
            );
var BookmarkPrimer = Class.create();

Object.extend(Object.extend(BookmarkPrimer.prototype, EventDispatcher.prototype),
                {
                    initialize : function(service, url){
                        this.service = service;
                        
                        this.parseBookmark(url);
                    
                    },
                    parseBookmark : function(url){
                        if(url == "" || url.search(/#[[a-z{}%0-9]+/gi) == -1)
                            return false;
                            
                        var q = this.parseURL(url);
                        if(!q.isJSON())
                            return false;
                        
                        var obj = q.evalJSON();
                        
                        this.processBookmark(obj);
                        
                        
                    },
                    parseURL : function(str){
                        
                        return str.match(/([a-z]{3,}#)(.*)/i, "").last().replace(/%22/g, "\"").replace(/%20/g, " ");
                    
                    },
                    processBookmark : function(obj){
                        if(obj.length)
                            obj.each(this.dispatchAction.bind(this))
                        else
                            this.dispatchAction(obj);
                        
                    
                    },
                    dispatchAction : function(obj){
                        
                        this.service.sendRequest(obj, this.service.getOperation(obj.action));
                    
                    }
                
                }
            );

Ajax.Service.Bookmark = Class.create();
        
Object.extend(Object.extend(Ajax.Service.Bookmark.prototype, Ajax.Service.Base.prototype),
                {
                    getBookmarkHash : function(){
                        
                        if(!this.bookmarkHash)
                            return this.bookmarkHash = $H();
                        
                        return this.bookmarkHash;
                    
                    
                    },
                    getOperation : function(name){
                    
                        return this.getBookmarkHash()[name];
                                                    
                    },
                    setOperation : function(name, arg){
                    
                        this.getBookmarkHash()[name] = arg;
                    
                    },
                    getOperationKeys : function(){
                        
                        return this.getBookmarkHash().keys();
                    
                    }
                    
                    
                    
                    

                }

Comments are Disabled.