source: Dev/branches/rest-dojo-ui/client/dojox/rpc/OfflineRest.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 8.4 KB
Line 
1define("dojox/rpc/OfflineRest", ["dojo", "dojox", "dojox/data/ClientFilter", "dojox/rpc/Rest", "dojox/storage"], function(dojo, dojox) {
2// summary:
3//              Makes the REST service be able to store changes in local
4//              storage so it can be used offline automatically.
5        var Rest = dojox.rpc.Rest;
6        var namespace = "dojox_rpc_OfflineRest";
7        var loaded;
8        var index = Rest._index;
9        dojox.storage.manager.addOnLoad(function(){
10                // now that we are loaded we need to save everything in the index
11                loaded = dojox.storage.manager.available;
12                for(var i in index){
13                        saveObject(index[i], i);
14                }
15        });
16        var dontSave;
17        function getStorageKey(key){
18                // returns a key that is safe to use in storage
19                return key.replace(/[^0-9A-Za-z_]/g,'_');
20        }
21        function saveObject(object,id){
22                // save the object into local storage
23               
24                if(loaded && !dontSave && (id || (object && object.__id))){
25                        dojox.storage.put(
26                                        getStorageKey(id||object.__id),
27                                        typeof object=='object'?dojox.json.ref.toJson(object):object, // makeshift technique to determine if the object is json object or not
28                                        function(){},
29                                        namespace);
30                }
31        }
32        function isNetworkError(error){
33                //      determine if the error was a network error and should be saved offline
34                //      or if it was a server error and not a result of offline-ness
35                return error instanceof Error && (error.status == 503 || error.status > 12000 ||  !error.status); // TODO: Make the right error determination
36        }
37        function sendChanges(){
38                // periodical try to save our dirty data
39                if(loaded){
40                        var dirty = dojox.storage.get("dirty",namespace);
41                        if(dirty){
42                                for (var dirtyId in dirty){
43                                        commitDirty(dirtyId,dirty);
44                                }
45                        }
46                }
47        }
48        var OfflineRest;
49        function sync(){
50                OfflineRest.sendChanges();
51                OfflineRest.downloadChanges();
52        }
53        var syncId = setInterval(sync,15000);
54        dojo.connect(document, "ononline", sync);
55        OfflineRest = dojox.rpc.OfflineRest = {
56                turnOffAutoSync: function(){
57                        clearInterval(syncId);
58                },
59                sync: sync,
60                sendChanges: sendChanges,
61                downloadChanges: function(){
62                       
63                },
64                addStore: function(/*data-store*/store,/*query?*/baseQuery){
65                        // summary:
66                        //              Adds a store to the monitored store for local storage
67                        //      store:
68                        //              Store to add
69                        //      baseQuery:
70                        //              This is the base query to should be used to load the items for
71                        //              the store. Generally you want to load all the items that should be
72                        //              available when offline.
73                        OfflineRest.stores.push(store);
74                        store.fetch({queryOptions:{cache:true},query:baseQuery,onComplete:function(results,args){
75                                store._localBaseResults = results;
76                                store._localBaseFetch = args;
77                        }});
78                                               
79                }
80        };
81        OfflineRest.stores = [];
82        var defaultGet = Rest._get;
83        Rest._get = function(service, id){
84                // We specifically do NOT want the paging information to be used by the default handler,
85                // this is because online apps want to minimize the data transfer,
86                // but an offline app wants the opposite, as much data as possible transferred to
87                // the client side
88                try{
89                        // if we are reloading the application with local dirty data in an online environment
90                        //      we want to make sure we save the changes first, so that we get up-to-date
91                        //      information from the server
92                        sendChanges();
93                        if(window.navigator && navigator.onLine===false){
94                                // we force an error if we are offline in firefox, otherwise it will silently load it from the cache
95                                throw new Error();
96                        }
97                        var dfd = defaultGet(service, id);
98                }catch(e){
99                        dfd = new dojo.Deferred();
100                        dfd.errback(e);
101                }
102                var sync = dojox.rpc._sync;
103                dfd.addCallback(function(result){
104                        saveObject(result, service._getRequest(id).url);
105                        return result;
106                });
107                dfd.addErrback(function(error){
108                        if(loaded){
109                                // if the storage is loaded, we can go ahead and get the object out of storage
110                                if(isNetworkError(error)){
111                                        var loadedObjects = {};
112                                        // network error, load from local storage
113                                        var byId = function(id,backup){
114                                                if(loadedObjects[id]){
115                                                        return backup;
116                                                }
117                                                var result = dojo.fromJson(dojox.storage.get(getStorageKey(id),namespace)) || backup;
118                                               
119                                                loadedObjects[id] = result;
120                                                for(var i in result){
121                                                        var val = result[i]; // resolve references if we can
122                                                        id = val && val.$ref;
123                                                        if (id){
124                                                                if(id.substring && id.substring(0,4) == "cid:"){
125                                                                        // strip the cid scheme, we should be able to resolve it locally
126                                                                        id = id.substring(4);
127                                                                }
128                                                                result[i] = byId(id,val);
129                                                        }
130                                                }
131                                                if (result instanceof Array){
132                                                        //remove any deleted items
133                                                        for (i = 0;i<result.length;i++){
134                                                                if (result[i]===undefined){
135                                                                        result.splice(i--,1);
136                                                                }
137                                                        }
138                                                }
139                                                return result;
140                                        };
141                                        dontSave = true; // we don't want to be resaving objects when loading from local storage
142                                        //TODO: Should this reuse something from dojox.rpc.Rest
143                                        var result = byId(service._getRequest(id).url);
144                                       
145                                        if(!result){// if it is not found we have to just return the error
146                                                return error;
147                                        }
148                                        dontSave = false;
149                                        return result;
150                                }
151                                else{
152                                        return error; // server error, let the error propagate
153                                }
154                        }
155                        else{
156                                if(sync){
157                                        return new Error("Storage manager not loaded, can not continue");
158                                }
159                                // we are not loaded, so we need to defer until we are loaded
160                                dfd = new dojo.Deferred();
161                                dfd.addCallback(arguments.callee);
162                                dojox.storage.manager.addOnLoad(function(){
163                                        dfd.callback();
164                                });
165                                return dfd;
166                        }
167                });
168                return dfd;
169        };
170        function changeOccurred(method, absoluteId, contentId, serializedContent, service){
171                if(method=='delete'){
172                        dojox.storage.remove(getStorageKey(absoluteId),namespace);
173                }
174                else{
175                        // both put and post should store the actual object
176                        dojox.storage.put(getStorageKey(contentId), serializedContent, function(){
177                        },namespace);
178                }
179                var store = service && service._store;
180                // record all the updated queries
181                if(store){
182                        store.updateResultSet(store._localBaseResults, store._localBaseFetch);
183                        dojox.storage.put(getStorageKey(service._getRequest(store._localBaseFetch.query).url),dojox.json.ref.toJson(store._localBaseResults),function(){
184                                },namespace);
185                       
186                }
187               
188        }
189        dojo.addOnLoad(function(){
190                dojo.connect(dojox.data, "restListener", function(message){
191                        var channel = message.channel;
192                        var method = message.event.toLowerCase();
193                        var service = dojox.rpc.JsonRest && dojox.rpc.JsonRest.getServiceAndId(channel).service;
194                        changeOccurred(
195                                method,
196                                channel,
197                                method == "post" ? channel + message.result.id : channel,
198                                dojo.toJson(message.result),
199                                service
200                        );
201                       
202                });
203        });
204        //FIXME: Should we make changes after a commit to see if the server rejected the change
205        // or should we come up with a revert mechanism?
206        var defaultChange = Rest._change;
207        Rest._change = function(method,service,id,serializedContent){
208                if(!loaded){
209                        return defaultChange.apply(this,arguments);
210                }
211                var absoluteId = service._getRequest(id).url;
212                changeOccurred(method, absoluteId, dojox.rpc.JsonRest._contentId, serializedContent, service);
213                var dirty = dojox.storage.get("dirty",namespace) || {};
214                if (method=='put' || method=='delete'){
215                        // these supersede so we can overwrite anything using this id
216                        var dirtyId = absoluteId;
217                }
218                else{
219                        dirtyId = 0;
220                        for (var i in dirty){
221                                if(!isNaN(parseInt(i))){
222                                        dirtyId = i;
223                                }
224                        } // get the last dirtyId to make a unique id for non-idempotent methods
225                        dirtyId++;
226                }
227                dirty[dirtyId] = {method:method,id:absoluteId,content:serializedContent};
228                return commitDirty(dirtyId,dirty);
229        };
230        function commitDirty(dirtyId, dirty){
231                var dirtyItem = dirty[dirtyId];
232                var serviceAndId = dojox.rpc.JsonRest.getServiceAndId(dirtyItem.id);
233                var deferred = defaultChange(dirtyItem.method,serviceAndId.service,serviceAndId.id,dirtyItem.content);
234                // add it to our list of dirty objects
235                dirty[dirtyId] = dirtyItem;
236                dojox.storage.put("dirty",dirty,function(){},namespace);
237                deferred.addBoth(function(result){
238                        if (isNetworkError(result)){
239                                // if a network error (offlineness) was the problem, we leave it
240                                // dirty, and return to indicate successfulness
241                                return null;
242                        }
243                        // it was successful or the server rejected it, we remove it from the dirty list
244                        var dirty = dojox.storage.get("dirty",namespace) || {};
245                        delete dirty[dirtyId];
246                        dojox.storage.put("dirty",dirty,function(){},namespace);
247                        return result;
248                });
249                return deferred;
250        }
251               
252        dojo.connect(index,"onLoad",saveObject);
253        dojo.connect(index,"onUpdate",saveObject);
254
255        return dojox.rpc.OfflineRest;
256});
Note: See TracBrowser for help on using the repository browser.