dojo.provide("dojox.rpc.OfflineRest"); dojo.require("dojox.data.ClientFilter"); dojo.require("dojox.rpc.Rest"); dojo.require("dojox.storage"); // summary: // Makes the REST service be able to store changes in local // storage so it can be used offline automatically. (function(){ var Rest = dojox.rpc.Rest; var namespace = "dojox_rpc_OfflineRest"; var loaded; var index = Rest._index; dojox.storage.manager.addOnLoad(function(){ // now that we are loaded we need to save everything in the index loaded = dojox.storage.manager.available; for(var i in index){ saveObject(index[i], i); } }); var dontSave; function getStorageKey(key){ // returns a key that is safe to use in storage return key.replace(/[^0-9A-Za-z_]/g,'_'); } function saveObject(object,id){ // save the object into local storage if(loaded && !dontSave && (id || (object && object.__id))){ dojox.storage.put( getStorageKey(id||object.__id), typeof object=='object'?dojox.json.ref.toJson(object):object, // makeshift technique to determine if the object is json object or not function(){}, namespace); } } function isNetworkError(error){ // determine if the error was a network error and should be saved offline // or if it was a server error and not a result of offline-ness return error instanceof Error && (error.status == 503 || error.status > 12000 || !error.status); // TODO: Make the right error determination } function sendChanges(){ // periodical try to save our dirty data if(loaded){ var dirty = dojox.storage.get("dirty",namespace); if(dirty){ for (var dirtyId in dirty){ commitDirty(dirtyId,dirty); } } } } var OfflineRest; function sync(){ OfflineRest.sendChanges(); OfflineRest.downloadChanges(); } var syncId = setInterval(sync,15000); dojo.connect(document, "ononline", sync); OfflineRest = dojox.rpc.OfflineRest = { turnOffAutoSync: function(){ clearInterval(syncId); }, sync: sync, sendChanges: sendChanges, downloadChanges: function(){ }, addStore: function(/*data-store*/store,/*query?*/baseQuery){ // summary: // Adds a store to the monitored store for local storage // store: // Store to add // baseQuery: // This is the base query to should be used to load the items for // the store. Generally you want to load all the items that should be // available when offline. OfflineRest.stores.push(store); store.fetch({queryOptions:{cache:true},query:baseQuery,onComplete:function(results,args){ store._localBaseResults = results; store._localBaseFetch = args; }}); } }; OfflineRest.stores = []; var defaultGet = Rest._get; Rest._get = function(service, id){ // We specifically do NOT want the paging information to be used by the default handler, // this is because online apps want to minimize the data transfer, // but an offline app wants the opposite, as much data as possible transferred to // the client side try{ // if we are reloading the application with local dirty data in an online environment // we want to make sure we save the changes first, so that we get up-to-date // information from the server sendChanges(); if(window.navigator && navigator.onLine===false){ // we force an error if we are offline in firefox, otherwise it will silently load it from the cache throw new Error(); } var dfd = defaultGet(service, id); }catch(e){ dfd = new dojo.Deferred(); dfd.errback(e); } var sync = dojox.rpc._sync; dfd.addCallback(function(result){ saveObject(result, service.servicePath+id); return result; }); dfd.addErrback(function(error){ if(loaded){ // if the storage is loaded, we can go ahead and get the object out of storage if(isNetworkError(error)){ var loadedObjects = {}; // network error, load from local storage var byId = function(id,backup){ if(loadedObjects[id]){ return backup; } var result = dojo.fromJson(dojox.storage.get(getStorageKey(id),namespace)) || backup; loadedObjects[id] = result; for(var i in result){ var val = result[i]; // resolve references if we can if (val && val.$ref){ result[i] = byId(val.$ref,val); } } if (result instanceof Array){ //remove any deleted items for (i = 0;i