source: Dev/trunk/src/client/dojox/rpc/OfflineRest.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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