]> git.pond.sub.org Git - eow/blobdiff - static/dojo-release-1.1.1/dojox/off/sync.js
add Dojo 1.1.1
[eow] / static / dojo-release-1.1.1 / dojox / off / sync.js
diff --git a/static/dojo-release-1.1.1/dojox/off/sync.js b/static/dojo-release-1.1.1/dojox/off/sync.js
new file mode 100644 (file)
index 0000000..9927fbe
--- /dev/null
@@ -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();
+
+}