From e29b520ea86396bc7c26bf8fa61c47caaf42f1bc Mon Sep 17 00:00:00 2001 From: andygup Date: Mon, 2 Dec 2013 16:32:24 -0700 Subject: [PATCH] Added callback to applyEdits() and custom event to indicate success/failure of updates, adds, deletes --- index.html | 41 ++++++++++++++++--- src/OfflineStore.js | 98 ++++++++++++++++++++++++++++++++++++--------- 2 files changed, 114 insertions(+), 25 deletions(-) diff --git a/index.html b/index.html index 6bf65ff..4904e83 100644 --- a/index.html +++ b/index.html @@ -16,14 +16,14 @@ padding: 0; overflow:hidden; } - #header { + #header, #info { border:solid 2px #462d44; background:#fff; color:#444; -moz-border-radius: 4px; border-radius: 4px; font-family: sans-serif; - font-size: 1.1em + font-size: 1.1em; padding-left:20px; } #map { @@ -98,6 +98,8 @@ map.on("layers-add-result", initEditing); + var headerDiv = document.getElementById("info"); + var landusePointLayer = new FeatureLayer("http://sampleserver6.arcgisonline.com/arcgis/rest/services/Military/FeatureServer/6", { mode: FeatureLayer.MODE_SNAPSHOT, outFields: ["*"] @@ -127,17 +129,39 @@ ////////// offlineStore = new OfflineStore(map); console.log("Storage used : " + offlineStore.getlocalStorageUsed()); + headerDiv.innerHTML = "Storage Used: " + offlineStore.getlocalStorageUsed() + " MBs"; + try{ + document.addEventListener("editEvent",function(evt){ + if(evt.type == "editEvent" && evt.detail.message == true){ + console.log("editEvent: All edits successfully pushed"); + } + else if(evt.type == "editEvent" && evt.detail.message == false){ + alert("Not all edits were successfully pushed to the server"); + } + + headerDiv.innerHTML = "Storage Used: " + offlineStore.getlocalStorageUsed() + " MBs"; + }, + false); + } + catch(err){ + console.log("test that event object is valid: " + err); + } + ////////// var editToolbar = new Edit(map); editToolbar.on("deactivate", function(evt) { if(updateFlag == true){ - offlineStore.applyEdits(vertices.graphic,vertices.layer,offlineStore.enum().UPDATE); + offlineStore.applyEdits(vertices.graphic,vertices.layer,offlineStore.enum().UPDATE,function(count,success,id){ + headerDiv.innerHTML = "Storage Used: " + offlineStore.getlocalStorageUsed() + " MBs"; + }); updateFlag = false; } else{ - offlineStore.applyEdits(evt.graphic,currentLayer,offlineStore.enum().UPDATE); + offlineStore.applyEdits(evt.graphic,currentLayer,offlineStore.enum().UPDATE,function(count,success,id){ + headerDiv.innerHTML = "Storage Used: " + offlineStore.getlocalStorageUsed() + " MBs"; + }); } }); @@ -168,7 +192,9 @@ event.stop(evt); if (evt.ctrlKey === true || evt.metaKey === true) { //delete feature if ctrl key is depressed try{ - offlineStore.applyEdits(evt.graphic,layer,offlineStore.enum().DELETE); + offlineStore.applyEdits(evt.graphic,layer,offlineStore.enum().DELETE,function(count,success,id){ + headerDiv.innerHTML = "Storage Used: " + offlineStore.getlocalStorageUsed() + " MBs"; + }); } catch(err){ console.log("error " + err.stack); @@ -218,7 +244,9 @@ var newAttributes = lang.mixin({}, selectedTemplate.template.prototype.attributes); var newGraphic = new Graphic(evt.geometry, null, newAttributes); //selectedTemplate.featureLayer.applyEdits([newGraphic], null, null); - offlineStore.applyEdits(newGraphic,selectedTemplate.featureLayer,offlineStore.enum().ADD); + offlineStore.applyEdits(newGraphic,selectedTemplate.featureLayer,offlineStore.enum().ADD,function(count,success,id){ + headerDiv.innerHTML = "Storage Used: " + offlineStore.getlocalStorageUsed() + " MBs"; + }); }); } @@ -253,6 +281,7 @@
+
Storage used: 0 MBs
diff --git a/src/OfflineStore.js b/src/OfflineStore.js index 7e01990..bfd2164 100755 --- a/src/OfflineStore.js +++ b/src/OfflineStore.js @@ -55,14 +55,24 @@ var OfflineStore = function(/* Map */ map) { */ this._localEnum = (function(){ var values = { - VALIDATION_URL : "http://localhost/offline/test.html", /* Change this to a remote server for testing! */ - TIMER_URL : "./src/Timer.js", /* For use within a child process only */ - STORAGE_KEY : "___EsriOfflineStore___", /* Unique key for setting/retrieving values from localStorage */ - INDEX_KEY : "___EsriOfflineIndex___", /* Index for tracking each action (add, delete, update) in local store */ - VALIDATION_TIMEOUT : 10 * 1000, /* HTTP timeout when trying to validate internet on/off */ - LOCAL_STORAGE_MAX_LIMIT : 4.75 /* MB */, /* Most browsers offer default storage of ~5MB */ - TOKEN : "|||", /* A unique token for tokenizing stringified localStorage values */ + /* Change this to a remote server for testing! */ + VALIDATION_URL : "http://localhost/offline/test.html", + /* For use within a child process only */ + TIMER_URL : "./src/Timer.js", + /* Unique key for setting/retrieving values from localStorage */ + STORAGE_KEY : "___EsriOfflineStore___", + /* Index for tracking each action (add, delete, update) in local store */ + INDEX_KEY : "___EsriOfflineIndex___", + /* HTTP timeout when trying to validate internet on/off */ + VALIDATION_TIMEOUT : 10 * 1000, + /* Most browsers offer default storage of ~5MB */ + LOCAL_STORAGE_MAX_LIMIT : 4.75 /* MB */, + /* A unique token for tokenizing stringified localStorage values */ + TOKEN : "|||", TIMER_TICK_INTERVAL : 10 * 1000 /* ms */, + EDIT_EVENT: "editEvent", + EDIT_EVENT_SUCCESS: true, + EDIT_EVENT_FAILED: false, REQUIRED_LIBS : [ "./src/Hydrate.js", "./src/Poller.js" @@ -102,10 +112,12 @@ var OfflineStore = function(/* Map */ map) { * @param graphic Required * @param layer Required * @param enumValue Required + * @param callback Recommended. Returns true if offline condition detected otherwise returns false. */ - this.applyEdits = function(/* Graphic */ graphic,/* FeatureLayer */ layer, /* String */ enumValue){ + this.applyEdits = function(/* Graphic */ graphic,/* FeatureLayer */ layer, /* String */ enumValue, callback){ var internet = this._checkInternet(); - this._applyEdits(internet,graphic,layer,enumValue); + this._applyEdits(internet,graphic,layer,enumValue, callback); + //this._sendEvent("Halllooo","test"); } /** @@ -145,6 +157,8 @@ var OfflineStore = function(/* Map */ map) { /** * Determines total storage used for this domain. + * NOTE: The index does take up storage space. Even if the offlineStore + * is deleted, you will still see some space taken up by the index. * @returns Number MB's */ this.getlocalStorageUsed = function(){ @@ -152,20 +166,32 @@ var OfflineStore = function(/* Map */ map) { //IE hack if(window.localStorage.hasOwnProperty("remainingspace")){ //http://msdn.microsoft.com/en-us/library/ie/cc197016(v=vs.85).aspx - return window.localStorage.remainingSpace/1024/1024; + return (window.localStorage.remainingSpace/1024/1024).round(4); } else{ var mb = 0; for(var x in localStorage){ //Uncomment out console.log to see *all* items in local storage - console.log(x+"="+((localStorage[x].length * 2)/1024/1024)+" MB"); + //console.log(x+"="+((localStorage[x].length * 2)/1024/1024).toFixed(4)+" MB"); mb += localStorage[x].length } - return Math.round(((mb * 2)/1024/1024) * 100)/100; + //return Math.round(((mb * 2)/1024/1024) * 100)/100; + return ((mb *2)/1024/1024).round(4); } } + /** + * A Global prototype that provides rounding capabilities. + * TODO reevaluate if this should be local in scope or global. + * @param places + * @returns {number} + */ + Number.prototype.round = function(places){ + places = Math.pow(10, places); + return Math.round(this * places)/places; + } + ////////////////////////// /// /// PRIVATE methods @@ -180,20 +206,23 @@ var OfflineStore = function(/* Map */ map) { * @param graphic * @param layer * @param enumValue + * @param callback Returns true if offline condition detected otherwise returns false. + * Format: {count, success, id} * @private */ - this._applyEdits = function(/* Boolean */ internet, /* Graphic */ graphic,/* FeatureLayer */ layer, /* String */ enumValue){ + this._applyEdits = function(/* Boolean */ internet, /* Graphic */ graphic,/* FeatureLayer */ layer, /* String */ enumValue, callback){ //TODO Need to add code to determine size of incoming graphic var mb = this.getlocalStorageUsed(); console.log("getlocalStorageUsed = " + mb + " MBs"); if(mb > this._localEnum().LOCAL_STORAGE_MAX_LIMIT /* MB */){ alert("You are almost over the local storage limit. No more data can be added.") + callback(0,false,0); return; } if(internet === false){ - this._addToLocalStore(graphic,layer,enumValue); + this._addToLocalStore(graphic,layer,enumValue,callback); if(this.isTimer == null){ this._startTimer(function(err){ throw ("unable to start background timer. Offline edits won't work. " + err.stack); @@ -202,11 +231,12 @@ var OfflineStore = function(/* Map */ map) { } else if(internet == null || typeof internet == "undefined"){ console.log("applyEdits: possible error."); + callback(0,false,0); } else{ //No need for a callback because this is an online request and it's immediately //pushed to Feature Service. The only thing updated in the library is the Index. - this._layerEditManager(graphic,layer,enumValue,this.enum(),null,null); + this._layerEditManager(graphic,layer,enumValue,this.enum(),0,callback); } } @@ -305,17 +335,20 @@ console.log(localStore.toString()); } } - this._addToLocalStore = function(/* Graphic */ graphic, /* FeatureLayer */ layer, /* String */ enumValue){ + this._addToLocalStore = function(/* Graphic */ graphic, /* FeatureLayer */ layer, /* String */ enumValue,callback){ var arr = this._getLocalStorage(); var geom = this._serializeGraphic(graphic,layer,enumValue); + var setItem = null; + //If localStorage does NOT exist if(arr === null){ - - this._setItemInLocalStore(geom); + setItem = this._setItemInLocalStore(geom); + callback(0,setItem,0); } else{ - this._updateExistingLocalStore(geom); + setItem = this._updateExistingLocalStore(geom); + callback(0,setItem,0); } layer.add(graphic); @@ -365,6 +398,10 @@ console.log(localStore.toString()); if(evt == true){ this._stopTimer(); this._deleteStore(); + this._sendEvent(true,this._localEnum().EDIT_EVENT); + } + else{ + this._sendEvent(false,this._localEnum().EDIT_EVENT); } }.bind(this)); } @@ -395,6 +432,23 @@ console.log(localStore.toString()); } } + this._sendEvent = function(msg,event){ + //this.preventDefault(); + + if (msg && window.CustomEvent) { + var event = new CustomEvent(event, { + detail: { + message: msg, + time: new Date() + }, + bubbles: true, + cancelable: true + }); + + document.dispatchEvent(event); + } + } + this._handleRestablishedInternet = function(callback){ var graphicsArr = this.getStore(); @@ -418,6 +472,7 @@ console.log(localStore.toString()); else{ console.log("_handleRestablishedInternet: there were errors. LocalStore still available."); this._stopTimer(); + callback(false); } } else if(success == true && check.length < graphicsArr.length){ @@ -427,6 +482,7 @@ console.log(localStore.toString()); this._setItemLocalStoreIndex(obj1.layer,objectId,obj1.enumValue,false); console.log("_handleRestablishedInternet: error sending edit on " + objectId); this._stopTimer(); + callback(false); } else if(success == false && check.length < graphicsArr.length){ this._setItemLocalStoreIndex(obj1.layer,objectId,obj1.enumValue,false); @@ -761,6 +817,10 @@ console.log(localStore.toString()); if(evt == true){ this._stopTimer(); this._deleteStore(); + this._sendEvent(true,this._localEnum().EDIT_EVENT); + } + else{ + this._sendEvent(false,this._localEnum().EDIT_EVENT); } }.bind(this)); }