aboutsummaryrefslogtreecommitdiff
path: root/includes/js/dojox/off/sync.js
diff options
context:
space:
mode:
Diffstat (limited to 'includes/js/dojox/off/sync.js')
-rw-r--r--includes/js/dojox/off/sync.js690
1 files changed, 690 insertions, 0 deletions
diff --git a/includes/js/dojox/off/sync.js b/includes/js/dojox/off/sync.js
new file mode 100644
index 0000000..9927fbe
--- /dev/null
+++ b/includes/js/dojox/off/sync.js
@@ -0,0 +1,690 @@
+if(!dojo._hasResource["dojox.off.sync"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
+dojo._hasResource["dojox.off.sync"] = true;
+dojo.provide("dojox.off.sync");
+
+dojo.require("dojox.storage.GearsStorageProvider");
+dojo.require("dojox.off._common");
+dojo.require("dojox.off.files");
+
+// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
+
+// summary:
+// Exposes syncing functionality to offline applications
+dojo.mixin(dojox.off.sync, {
+ // isSyncing: boolean
+ // Whether we are in the middle of a syncing session.
+ isSyncing: false,
+
+ // cancelled: boolean
+ // Whether we were cancelled during our last sync request or not. If
+ // we are cancelled, then successful will be false.
+ cancelled: false,
+
+ // successful: boolean
+ // Whether the last sync was successful or not. If false, an error
+ // occurred.
+ successful: true,
+
+ // details: String[]
+ // Details on the sync. If the sync was successful, this will carry
+ // any conflict or merging messages that might be available; if the
+ // sync was unsuccessful, this will have an error message. For both
+ // of these, this should be an array of Strings, where each string
+ // carries details on the sync.
+ // Example:
+ // dojox.off.sync.details = ["The document 'foobar' had conflicts - yours one",
+ // "The document 'hello world' was automatically merged"];
+ details: [],
+
+ // error: boolean
+ // Whether an error occurred during the syncing process.
+ error: false,
+
+ // actions: dojox.off.sync.ActionLog
+ // Our ActionLog that we store offline actions into for later
+ // replaying when we go online
+ actions: null,
+
+ // autoSync: boolean
+ // For advanced usage; most developers can ignore this.
+ // Whether we do automatically sync on page load or when we go online.
+ // If true we do, if false syncing must be manually initiated.
+ // Defaults to true.
+ autoSync: true,
+
+ // summary:
+ // An event handler that is called during the syncing process with
+ // the state of syncing. It is important that you connect to this
+ // method and respond to certain sync events, especially the
+ // "download" event.
+ // description:
+ // This event handler is called during the syncing process. You can
+ // do a dojo.connect to receive sync feedback:
+ //
+ // dojo.connect(dojox.off.sync, "onSync", someFunc);
+ //
+ // You will receive one argument, which is the type of the event
+ // and which can have the following values.
+ //
+ // The most common two types that you need to care about are "download"
+ // and "finished", especially if you are using the default
+ // Dojo Offline UI widget that does the hard work of informing
+ // the user through the UI about what is occuring during syncing.
+ //
+ // If you receive the "download" event, you should make a network call
+ // to retrieve and store your data somehow for offline access. The
+ // "finished" event indicates that syncing is done. An example:
+ //
+ // dojo.connect(dojox.off.sync, "onSync", function(type){
+ // if(type == "download"){
+ // // make a network call to download some data
+ // // for use offline
+ // dojo.xhrGet({
+ // url: "downloadData.php",
+ // handleAs: "javascript",
+ // error: function(err){
+ // dojox.off.sync.finishedDownloading(false, "Can't download data");
+ // },
+ // load: function(data){
+ // // store our data
+ // dojox.storage.put("myData", data);
+ //
+ // // indicate we are finished downloading
+ // dojox.off.sync.finishedDownloading(true);
+ // }
+ // });
+ // }else if(type == "finished"){
+ // // update UI somehow to indicate we are finished,
+ // // such as using the download data to change the
+ // // available data
+ // }
+ // })
+ //
+ // Here is the full list of event types if you want to do deep
+ // customization, such as updating your UI to display the progress
+ // of syncing (note that the default Dojo Offline UI widget does
+ // this for you if you choose to pull that in). Most of these
+ // are only appropriate for advanced usage and can be safely
+ // ignored:
+ //
+ // * "start"
+ // syncing has started
+ // * "refreshFiles"
+ // syncing will begin refreshing
+ // our offline file cache
+ // * "upload"
+ // syncing will begin uploading
+ // any local data changes we have on the client.
+ // This event is fired before we fire
+ // the dojox.off.sync.actions.onReplay event for
+ // each action to replay; use it to completely
+ // over-ride the replaying behavior and prevent
+ // it entirely, perhaps rolling your own sync
+ // protocol if needed.
+ // * "download"
+ // syncing will begin downloading any new data that is
+ // needed into persistent storage. Applications are required to
+ // implement this themselves, storing the required data into
+ // persistent local storage using Dojo Storage.
+ // * "finished"
+ // syncing is finished; this
+ // will be called whether an error ocurred or not; check
+ // dojox.off.sync.successful and dojox.off.sync.error for sync details
+ // * "cancel"
+ // Fired when canceling has been initiated; canceling will be
+ // attempted, followed by the sync event "finished".
+ onSync: function(/* String */ type){},
+
+ synchronize: function(){ /* void */
+ // summary: Starts synchronizing
+
+ //dojo.debug("synchronize");
+ if(this.isSyncing || dojox.off.goingOnline || (!dojox.off.isOnline)){
+ return;
+ }
+
+ this.isSyncing = true;
+ this.successful = false;
+ this.details = [];
+ this.cancelled = false;
+
+ this.start();
+ },
+
+ cancel: function(){ /* void */
+ // summary:
+ // Attempts to cancel this sync session
+
+ if(!this.isSyncing){ return; }
+
+ this.cancelled = true;
+ if(dojox.off.files.refreshing){
+ dojox.off.files.abortRefresh();
+ }
+
+ this.onSync("cancel");
+ },
+
+ finishedDownloading: function(successful /* boolean? */,
+ errorMessage /* String? */){
+ // summary:
+ // Applications call this method from their
+ // after getting a "download" event in
+ // dojox.off.sync.onSync to signal that
+ // they are finished downloading any data
+ // that should be available offline
+ // successful: boolean?
+ // Whether our downloading was successful or not.
+ // If not present, defaults to true.
+ // errorMessage: String?
+ // If unsuccessful, a message explaining why
+ if(typeof successful == "undefined"){
+ successful = true;
+ }
+
+ if(!successful){
+ this.successful = false;
+ this.details.push(errorMessage);
+ this.error = true;
+ }
+
+ this.finished();
+ },
+
+ start: function(){ /* void */
+ // summary:
+ // For advanced usage; most developers can ignore this.
+ // Called at the start of the syncing process. Advanced
+ // developers can over-ride this method to use their
+ // own sync mechanism to start syncing.
+
+ if(this.cancelled){
+ this.finished();
+ return;
+ }
+ this.onSync("start");
+ this.refreshFiles();
+ },
+
+ refreshFiles: function(){ /* void */
+ // summary:
+ // For advanced usage; most developers can ignore this.
+ // Called when we are going to refresh our list
+ // of offline files during syncing. Advanced developers
+ // can over-ride this method to do some advanced magic related to
+ // refreshing files.
+
+ //dojo.debug("refreshFiles");
+ if(this.cancelled){
+ this.finished();
+ return;
+ }
+
+ this.onSync("refreshFiles");
+
+ dojox.off.files.refresh(dojo.hitch(this, function(error, errorMessages){
+ if(error){
+ this.error = true;
+ this.successful = false;
+ for(var i = 0; i < errorMessages.length; i++){
+ this.details.push(errorMessages[i]);
+ }
+
+ // even if we get an error while syncing files,
+ // keep syncing so we can upload and download
+ // data
+ }
+
+ this.upload();
+ }));
+ },
+
+ upload: function(){ /* void */
+ // summary:
+ // For advanced usage; most developers can ignore this.
+ // Called when syncing wants to upload data. Advanced
+ // developers can over-ride this method to completely
+ // throw away the Action Log and replaying system
+ // and roll their own advanced sync mechanism if needed.
+
+ if(this.cancelled){
+ this.finished();
+ return;
+ }
+
+ this.onSync("upload");
+
+ // when we are done uploading start downloading
+ dojo.connect(this.actions, "onReplayFinished", this, this.download);
+
+ // replay the actions log
+ this.actions.replay();
+ },
+
+ download: function(){ /* void */
+ // summary:
+ // For advanced usage; most developers can ignore this.
+ // Called when syncing wants to download data. Advanced
+ // developers can over-ride this method to use their
+ // own sync mechanism.
+
+ if(this.cancelled){
+ this.finished();
+ return;
+ }
+
+ // apps should respond to the "download"
+ // event to download their data; when done
+ // they must call dojox.off.sync.finishedDownloading()
+ this.onSync("download");
+ },
+
+ finished: function(){ /* void */
+ // summary:
+ // For advanced usage; most developers can ignore this.
+ // Called when syncing is finished. Advanced
+ // developers can over-ride this method to clean
+ // up after finishing their own sync
+ // mechanism they might have rolled.
+ this.isSyncing = false;
+
+ this.successful = (!this.cancelled && !this.error);
+
+ this.onSync("finished");
+ },
+
+ _save: function(callback){
+ this.actions._save(function(){
+ callback();
+ });
+ },
+
+ _load: function(callback){
+ this.actions._load(function(){
+ callback();
+ });
+ }
+});
+
+
+// summary:
+// A class that records actions taken by a user when they are offline,
+// suitable for replaying when the network reappears.
+// description:
+// The basic idea behind this method is to record user actions that would
+// normally have to contact a server into an action log when we are
+// offline, so that later when we are online we can simply replay this log
+// in the order user actions happened so that they can be executed against
+// the server, causing synchronization to happen.
+//
+// When we replay, for each of the actions that were added, we call a
+// method named onReplay that applications should connect to and
+// which will be called over and over for each of our actions --
+// applications should take the offline action
+// information and use it to talk to a server to have this action
+// actually happen online, 'syncing' themselves with the server.
+//
+// For example, if the action was "update" with the item that was updated, we
+// might call some RESTian server API that exists for updating an item in
+// our application. The server could either then do sophisticated merging
+// and conflict resolution on the server side, for example, allowing you
+// to pop up a custom merge UI, or could do automatic merging or nothing
+// of the sort. When you are finished with this particular action, your
+// application is then required to call continueReplay() on the actionLog object
+// passed to onReplay() to continue replaying the action log, or haltReplay()
+// with the reason for halting to completely stop the syncing/replaying
+// process.
+//
+// For example, imagine that we have a web application that allows us to add
+// contacts. If we are offline, and we update a contact, we would add an action;
+// imagine that the user has to click an Update button after changing the values
+// for a given contact:
+//
+// dojox.off.whenOffline(dojo.byId("updateButton"), "onclick", function(evt){
+// // get the updated customer values
+// var customer = getCustomerValues();
+//
+// // we are offline -- just record this action
+// var action = {name: "update", customer: customer};
+// dojox.off.sync.actions.add(action)
+//
+// // persist this customer data into local storage as well
+// dojox.storage.put(customer.name, customer);
+// })
+//
+// Then, when we go back online, the dojox.off.sync.actions.onReplay event
+// will fire over and over, once for each action that was recorded while offline:
+//
+// dojo.connect(dojox.off.sync.actions, "onReplay", function(action, actionLog){
+// // called once for each action we added while offline, in the order
+// // they were added
+// if(action.name == "update"){
+// var customer = action.customer;
+//
+// // call some network service to update this customer
+// dojo.xhrPost({
+// url: "updateCustomer.php",
+// content: {customer: dojo.toJson(customer)},
+// error: function(err){
+// actionLog.haltReplay(err);
+// },
+// load: function(data){
+// actionLog.continueReplay();
+// }
+// })
+// }
+// })
+//
+// Note that the actions log is always automatically persisted locally while using it, so
+// that if the user closes the browser or it crashes the actions will safely be stored
+// for later replaying.
+dojo.declare("dojox.off.sync.ActionLog", null, {
+ // entries: Array
+ // An array of our action entries, where each one is simply a custom
+ // object literal that were passed to add() when this action entry
+ // was added.
+ entries: [],
+
+ // reasonHalted: String
+ // If we halted, the reason why
+ reasonHalted: null,
+
+ // isReplaying: boolean
+ // If true, we are in the middle of replaying a command log; if false,
+ // then we are not
+ isReplaying: false,
+
+ // autoSave: boolean
+ // Whether we automatically save the action log after each call to
+ // add(); defaults to true. For applications that are rapidly adding
+ // many action log entries in a short period of time, it can be
+ // useful to set this to false and simply call save() yourself when
+ // you are ready to persist your command log -- otherwise performance
+ // could be slow as the default action is to attempt to persist the
+ // actions log constantly with calls to add().
+ autoSave: true,
+
+ add: function(action /* Object */){ /* void */
+ // summary:
+ // Adds an action to our action log
+ // description:
+ // This method will add an action to our
+ // action log, later to be replayed when we
+ // go from offline to online. 'action'
+ // will be available when this action is
+ // replayed and will be passed to onReplay.
+ //
+ // Example usage:
+ //
+ // dojox.off.sync.log.add({actionName: "create", itemType: "document",
+ // {title: "Message", content: "Hello World"}});
+ //
+ // The object literal is simply a custom object appropriate
+ // for our application -- it can be anything that preserves the state
+ // of a user action that will be executed when we go back online
+ // and replay this log. In the above example,
+ // "create" is the name of this action; "documents" is the
+ // type of item this command is operating on, such as documents, contacts,
+ // tasks, etc.; and the final argument is the document that was created.
+
+ if(this.isReplaying){
+ throw "Programming error: you can not call "
+ + "dojox.off.sync.actions.add() while "
+ + "we are replaying an action log";
+ }
+
+ this.entries.push(action);
+
+ // save our updated state into persistent
+ // storage
+ if(this.autoSave){
+ this._save();
+ }
+ },
+
+ onReplay: function(action /* Object */,
+ actionLog /* dojox.off.sync.ActionLog */){ /* void */
+ // summary:
+ // Called when we replay our log, for each of our action
+ // entries.
+ // action: Object
+ // A custom object literal representing an action for this
+ // application, such as
+ // {actionName: "create", item: {title: "message", content: "hello world"}}
+ // actionLog: dojox.off.sync.ActionLog
+ // A reference to the dojox.off.sync.actions log so that developers
+ // can easily call actionLog.continueReplay() or actionLog.haltReplay().
+ // description:
+ // This callback should be connected to by applications so that
+ // they can sync themselves when we go back online:
+ //
+ // dojo.connect(dojox.off.sync.actions, "onReplay", function(action, actionLog){
+ // // do something
+ // })
+ //
+ // When we replay our action log, this callback is called for each
+ // of our action entries in the order they were added. The
+ // 'action' entry that was passed to add() for this action will
+ // also be passed in to onReplay, so that applications can use this information
+ // to do their syncing, such as contacting a server web-service
+ // to create a new item, for example.
+ //
+ // Inside the method you connected to onReplay, you should either call
+ // actionLog.haltReplay(reason) if an error occurred and you would like to halt
+ // action replaying or actionLog.continueReplay() to have the action log
+ // continue replaying its log and proceed to the next action;
+ // the reason you must call these is the action you execute inside of
+ // onAction will probably be asynchronous, since it will be talking on
+ // the network, and you should call one of these two methods based on
+ // the result of your network call.
+ },
+
+ length: function(){ /* Number */
+ // summary:
+ // Returns the length of this
+ // action log
+ return this.entries.length;
+ },
+
+ haltReplay: function(reason /* String */){ /* void */
+ // summary: Halts replaying this command log.
+ // reason: String
+ // The reason we halted.
+ // description:
+ // This method is called as we are replaying an action log; it
+ // can be called from dojox.off.sync.actions.onReplay, for
+ // example, for an application to indicate an error occurred
+ // while replaying this action, halting further processing of
+ // the action log. Note that any action log entries that
+ // were processed before have their effects retained (i.e.
+ // they are not rolled back), while the action entry that was
+ // halted stays in our list of actions to later be replayed.
+ if(!this.isReplaying){
+ return;
+ }
+
+ if(reason){
+ this.reasonHalted = reason.toString();
+ }
+
+ // save the state of our action log, then
+ // tell anyone who is interested that we are
+ // done when we are finished saving
+ if(this.autoSave){
+ var self = this;
+ this._save(function(){
+ self.isReplaying = false;
+ self.onReplayFinished();
+ });
+ }else{
+ this.isReplaying = false;
+ this.onReplayFinished();
+ }
+ },
+
+ continueReplay: function(){ /* void */
+ // summary:
+ // Indicates that we should continue processing out list of
+ // actions.
+ // description:
+ // This method is called by applications that have overridden
+ // dojox.off.sync.actions.onReplay() to continue replaying our
+ // action log after the application has finished handling the
+ // current action.
+ if(!this.isReplaying){
+ return;
+ }
+
+ // shift off the old action we just ran
+ this.entries.shift();
+
+ // are we done?
+ if(!this.entries.length){
+ // save the state of our action log, then
+ // tell anyone who is interested that we are
+ // done when we are finished saving
+ if(this.autoSave){
+ var self = this;
+ this._save(function(){
+ self.isReplaying = false;
+ self.onReplayFinished();
+ });
+ return;
+ }else{
+ this.isReplaying = false;
+ this.onReplayFinished();
+ return;
+ }
+ }
+
+ // get the next action
+ var nextAction = this.entries[0];
+ this.onReplay(nextAction, this);
+ },
+
+ clear: function(){ /* void */
+ // summary:
+ // Completely clears this action log of its entries
+
+ if(this.isReplaying){
+ return;
+ }
+
+ this.entries = [];
+
+ // save our updated state into persistent
+ // storage
+ if(this.autoSave){
+ this._save();
+ }
+ },
+
+ replay: function(){ /* void */
+ // summary:
+ // For advanced usage; most developers can ignore this.
+ // Replays all of the commands that have been
+ // cached in this command log when we go back online;
+ // onCommand will be called for each command we have
+
+ if(this.isReplaying){
+ return;
+ }
+
+ this.reasonHalted = null;
+
+ if(!this.entries.length){
+ this.onReplayFinished();
+ return;
+ }
+
+ this.isReplaying = true;
+
+ var nextAction = this.entries[0];
+ this.onReplay(nextAction, this);
+ },
+
+ // onReplayFinished: Function
+ // For advanced usage; most developers can ignore this.
+ // Called when we are finished replaying our commands;
+ // called if we have successfully exhausted all of our
+ // commands, or if an error occurred during replaying.
+ // The default implementation simply continues the
+ // synchronization process. Connect to this to register
+ // for the event:
+ //
+ // dojo.connect(dojox.off.sync.actions, "onReplayFinished",
+ // someFunc)
+ onReplayFinished: function(){
+ },
+
+ toString: function(){
+ var results = "";
+ results += "[";
+
+ for(var i = 0; i < this.entries.length; i++){
+ results += "{";
+ for(var j in this.entries[i]){
+ results += j + ": \"" + this.entries[i][j] + "\"";
+ results += ", ";
+ }
+ results += "}, ";
+ }
+
+ results += "]";
+
+ return results;
+ },
+
+ _save: function(callback){
+ if(!callback){
+ callback = function(){};
+ }
+
+ try{
+ var self = this;
+ var resultsHandler = function(status, key, message){
+ //console.debug("resultsHandler, status="+status+", key="+key+", message="+message);
+ if(status == dojox.storage.FAILED){
+ dojox.off.onFrameworkEvent("save",
+ {status: dojox.storage.FAILED,
+ isCoreSave: true,
+ key: key,
+ value: message,
+ namespace: dojox.off.STORAGE_NAMESPACE});
+ callback();
+ }else if(status == dojox.storage.SUCCESS){
+ callback();
+ }
+ };
+
+ dojox.storage.put("actionlog", this.entries, resultsHandler,
+ dojox.off.STORAGE_NAMESPACE);
+ }catch(exp){
+ console.debug("dojox.off.sync._save: " + exp.message||exp);
+ dojox.off.onFrameworkEvent("save",
+ {status: dojox.storage.FAILED,
+ isCoreSave: true,
+ key: "actionlog",
+ value: this.entries,
+ namespace: dojox.off.STORAGE_NAMESPACE});
+ callback();
+ }
+ },
+
+ _load: function(callback){
+ var entries = dojox.storage.get("actionlog", dojox.off.STORAGE_NAMESPACE);
+
+ if(!entries){
+ entries = [];
+ }
+
+ this.entries = entries;
+
+ callback();
+ }
+ }
+);
+
+dojox.off.sync.actions = new dojox.off.sync.ActionLog();
+
+}