more infrastructure to support rollback of an offline edit

This commit is contained in:
andygup 2014-01-12 22:31:00 -07:00
parent d9efc11592
commit 2aba3c7e03
3 changed files with 143 additions and 165 deletions

View File

@ -79,7 +79,6 @@ var OfflineStore = function(/* Map */ map) {
LOCAL_STORAGE_MAX_LIMIT : 4.75 /* MB */,
/* A unique token for tokenizing stringified localStorage values */
TOKEN : "|||",
EDIT_EVENT: "editEvent",
EDIT_EVENT_DUPLICATE: "duplicateEditEvent",
/**
* An event has been attempted during an offline condition
@ -130,7 +129,8 @@ var OfflineStore = function(/* Map */ map) {
}
/**
* Public method for retrieving all items in the localStore.
* Public method for retrieving all items in the temporary localStore.
* This request does not return the index information. For that use getLocalStoreIndex().
* @returns {Array} Graphics
*/
this.getStore = function(){
@ -214,6 +214,16 @@ var OfflineStore = function(/* Map */ map) {
}
}
this.getGraphicsLayerById = function(/* String */ id){
for(var layer in this.layers)
{
if(id == this.layers[layer].layerId){
return this.layers[layer];
break;
}
}
}
//////////////////////////
///
/// PRIVATE methods
@ -384,9 +394,7 @@ var OfflineStore = function(/* Map */ map) {
layer.add(graphic);
var obj = new this.pendingEventObject(graphic,layer.id,enumValue)
this._dispatchEvent(obj,this._localEnum().PENDING_EDIT_EVENT);
this._dispatchEvent(true,this._localEnum().PENDING_EDIT_EVENT);
}
this._startOfflineListener = function(){
@ -397,7 +405,7 @@ var OfflineStore = function(/* Map */ map) {
var arr = null;
try{var arr = this._getTempLocalStore()}catch(err){console.log("onlineStatusHandler: " + err.toString())};
if(arr != null){
this._reestablishedInternetCallbackHandler();
this._reestablishedInternet();
}
}
}
@ -435,22 +443,22 @@ var OfflineStore = function(/* Map */ map) {
}
}
/**
* Makes calls to _reestablishedInternet() and handles the callback properties
* @private
*/
this._reestablishedInternetCallbackHandler = function(){
this._reestablishedInternet(function(/* boolean */ success, /* boolean */ done,enumString,graphicsLayerId,objectId,error){
var obj = null;
if(success == true){
obj = new this._editResultObject(success,enumString,graphicsLayerId,objectId,error);
this._deleteTempLocalStore(); //all edits should have been pushed to server before this executes!
}
this._dispatchEvent(obj,this._localEnum().EDIT_EVENT);
}.bind(this));
}
// /**
// * Makes calls to _reestablishedInternet() and handles the callback properties
// * @private
// */
// this._reestablishedInternetCallbackHandler = function(){
// this._reestablishedInternet(function(/* boolean */ success, /* boolean */ done,enumString,graphicsLayerId,objectId,error){
// var obj = null;
// if(success == true){
// var date = JSON.stringify(this.utils.getDateMDYHMS());
// obj = new this._editResultObject(success,enumString,graphicsLayerId,objectId,date,error);
//
// this._deleteTempLocalStore(); //all edits should have been pushed to server before this executes!
// }
// this._dispatchEvent(obj,this._localEnum().EDIT_EVENT);
// }.bind(this));
// }
/**
* Creates a graphics array from localstorage and pushes all applicable edits to a
@ -459,61 +467,27 @@ var OfflineStore = function(/* Map */ map) {
* if this.getStore() returns null
* @private
*/
this._reestablishedInternet = function(callback){
this._reestablishedInternet = function(){
var graphicsArr = this.getStore();
if(graphicsArr != null && this.layers != null){
var check = [];
var errCnt = 0;
// var check = [];
// var errCnt = 0;
var length = graphicsArr.length;
for(var i = 0; i < length; i++){
var obj1 = graphicsArr[i];
var layer = this._getGraphicsLayerById(obj1.layer);
var layer = this.getGraphicsLayerById(obj1.layer);
this._layerEditManager(obj1.graphic,layer,obj1.enumValue,this.enum(),i,function(/* Number */ num, /* boolean */ success, /* String */ id,error){
check.push(num);
var date = new Date();
var indexObject = new this._indexObject(obj1.layer,id,obj1.enumValue,success,obj1.graphic.geometry.type,date) ;
var deleteTempItem = this._deleteItemInLocalStore(JSON.stringify(obj1));
this._setItemLocalStoreIndexObject(indexObject);
if(success == true && check.length == graphicsArr.length){
if(errCnt == 0){
callback(true,true,obj1.enumValue,obj1.layer,id);
}
else{ //this could cause a bug since it doesn't set item in local store. Not verified!
console.log("_reestablishedInternet: there were errors. LocalStore still available.");
callback(false,true,obj1.enumValue,obj1.layer,id);
}
}
//Don't return anything yet
else if(success == true && check.length < graphicsArr.length){
//Do nothing
}
else if(success == false && check.length == graphicsArr.length){
console.log("_reestablishedInternet: error sending edit on " + id);
callback(false,true,obj1.enumValue,obj1.layer,id,error);
}
//Don't return anything yet
else if(success == false && check.length < graphicsArr.length){
errCnt++;
console.log("_reestablishedInternet: error sending edit on " + id);
}
}.bind(this));
}
}
else{
callback(false,true,null,null,null);
}
}
this._getGraphicsLayerById = function(/* String */ id){
for(var layer in this.layers)
{
if(id == this.layers[layer].layerId){
return this.layers[layer];
break;
}
console.log("_reestablishedInternet: graphicsArray was null.");
}
}
@ -547,15 +521,15 @@ var OfflineStore = function(/* Map */ map) {
* The graphic will not have a UID because it has not been processed by the feature service.
* This poses a minor storage issue. So, in this version of the library we simply
* store the serialized graphic information and append it to any other pending edits.
* @param geometry
* @param serializedGraphic
* @returns {boolean} returns true if success, else false. Writes
* error stack to console.
*/
this._setTempLocalStore = function(/* Geometry */ geometry){
this._setTempLocalStore = function(/* String */ serializedGraphic){
var success = false;
try{
localStorage.setItem(this._localEnum().STORAGE_KEY,geometry);
localStorage.setItem(this._localEnum().STORAGE_KEY,serializedGraphic);
success = true;
}
catch(err){
@ -568,13 +542,13 @@ var OfflineStore = function(/* Map */ map) {
}
/**
* Deletes an item in local store.
* Deletes an item in temporary local store.
* REQUIRES: ObjectId field is present
* @param objectId
* @param callback
* @private
*/
this._deleteItemInLocalStore = function(/* String */ objectId,callback){
this._deleteObjectIdInLocalStore = function(/* String */ objectId,callback){
var success = false;
var localStore = localStorage.getItem(this._localEnum().STORAGE_KEY);
@ -588,6 +562,41 @@ var OfflineStore = function(/* Map */ map) {
if(q.hasOwnProperty("objectid") && q.objectid == objectId){
splitStore.splice(parseFloat(property),1);
var newArr = this._reserializeGraphicsArray(splitStore);
localStorage.removeItem(this._localEnum().STORAGE_KEY);
this._setTempLocalStore(newArr);
success = true;
break;
}
}
}
}
callback(success);
}
/**
* Deletes an item in temporary local store.
* REQUIRES: ObjectId field is present
* @param entry
* @param callback
* @private
*/
this._deleteItemInLocalStore = function(/* String */ entry,callback){
var success = false;
var localStore = localStorage.getItem(this._localEnum().STORAGE_KEY);
if(localStore != null){
var splitStore = localStore.split(this._localEnum().TOKEN);
for(var property in splitStore){
var test = splitStore[property];
if(typeof test !== "undefined" && test.length > 0 && test != null && Boolean(test) != false){
// var item = JSON.parse(splitStore[property]);
// var q = JSON.parse(item.attributes);
if(test == entry){
splitStore.splice(parseFloat(property),1);
var newArr = this._reserializeGraphicsArray(splitStore);
localStorage.removeItem(this._localEnum().STORAGE_KEY);
this._setTempLocalStore(newArr);
success = true;
break;
}
@ -721,6 +730,12 @@ var OfflineStore = function(/* Map */ map) {
}
/**
* Returns a graphic that has been reconstituted from localStorage.
* @param item
* @returns {{graphic: esri.Graphic, layer: *, enumValue: *}}
* @private
*/
this._deserializeGraphic = function(/* Graphic */ item){
var jsonItem = JSON.parse(item);
@ -728,6 +743,7 @@ var OfflineStore = function(/* Map */ map) {
var attributes = JSON.parse(jsonItem.attributes);
var enumValue = jsonItem.enumValue;
var layer = JSON.parse(jsonItem.layer);
var date = JSON.parse(jsonItem.date);
var finalGeom = null;
switch(geometry.type){
@ -750,7 +766,7 @@ var OfflineStore = function(/* Map */ map) {
var graphic = new esri.Graphic(finalGeom, null, attributes, null);
return {"graphic":graphic,"layer":layer,"enumValue":enumValue};
return {"graphic":graphic,"layer":layer,"enumValue":enumValue,"date":date};
}
/**
@ -766,6 +782,7 @@ var OfflineStore = function(/* Map */ map) {
json.layer = layer.layerId;
json.enumValue = enumValue;
json.geometry = JSON.stringify(graphic.geometry)
json.date = JSON.stringify(this.utils.getDateMDYHMS());
if(graphic.hasOwnProperty("attributes")){
if(graphic.attributes != null){
@ -788,18 +805,6 @@ var OfflineStore = function(/* Map */ map) {
///
//////////////////////////
/**
* Model for storing info related to a pending edit event.
* @param graphic
* @param layerId
* @param enumValue
*/
this.pendingEventObject = function(/* Graphic */ graphic, /* String */ layerId, /* String */ enumValue ){
this.graphic = graphic;
this.layerId = layerId;
this.enumValue = enumValue;
}
/**
* Model for storing serialized graphics
* @private
@ -809,6 +814,7 @@ var OfflineStore = function(/* Map */ map) {
this.enumValue = null;
this.geometry = null;
this.attributes = null;
this.date = null;
}
/**
@ -843,11 +849,12 @@ var OfflineStore = function(/* Map */ map) {
* @private
*/
this._editResultObject = function(/* boolean */ success, /* String */ enumString,
/* int */ graphicsLayerId, /* int */ objectId,error){
/* int */ graphicsLayerId, /* int */ objectId,/* String */ date, error){
this.success = success;
this.enumString = enumString;
this.graphicsLayerId = graphicsLayerId;
this.objectId = objectId;
this.date = date;
this.error = error;
}
@ -959,7 +966,7 @@ var OfflineStore = function(/* Map */ map) {
var arr = this._getTempLocalStore();
if(arr != null && Offline.state === 'up'){
this._reestablishedInternetCallbackHandler();
this._reestablishedInternet();
}
else if(arr != null && Offline.state !== 'up'){
this._startOfflineListener();

View File

@ -258,18 +258,6 @@ require([
var message = evt.detail.message;
switch(evt.detail.type){
case "editEvent":
if(message == null){
alert("There was a problem submitted the edit(s). See console for details.")
}
if(message.hasOwnProperty("success") && message.success == true){
console.log("editEvent: All edits successfully pushed");
}
else if(message.hasOwnProperty("success") && message.success == false){
alert("Not all edits were successfully pushed to the server");
}
headerDiv.innerHTML = getStorageInfo();
break;
case "internetStatusChangeEvent":
if(message == true){
document.getElementById("offlineImg").src = "../samples/img/32-Link.png";
@ -281,15 +269,17 @@ require([
}
break;
case "indexUpdateEvent":
headerDiv.innerHTML = getStorageInfo();
updateHistoryTextArea(evt);
break;
case "pendingEditEvent":
if(message != null){
addPendingTextArea(message);
addPendingTextArea();
}
headerDiv.innerHTML = getStorageInfo();
break;
case "duplicateEditEvent":
alert("Duplicate edit event");
console.log("duplicate event attempt");
break;
}
@ -310,8 +300,6 @@ require([
editToolbar.on("deactivate", function(evt) {
// updatePendingTextArea(evt.graphic,"Attempting to update",null);
if(updateFlag == true){
offlineStore.applyEdits(vertices.graphic,vertices.layer,offlineStore.enum().UPDATE,function(count,success,id){
headerDiv.innerHTML = getStorageInfo();
@ -352,8 +340,6 @@ require([
event.stop(evt);
if (evt.ctrlKey === true || evt.metaKey === true) { //delete feature if ctrl key is depressed
// updatePendingTextArea(evt.graphic,"Attempting to delete",null);
try{
offlineStore.applyEdits(evt.graphic,layer,offlineStore.enum().DELETE,function(count,success,id){
headerDiv.innerHTML = getStorageInfo();
@ -408,8 +394,6 @@ require([
var newGraphic = new Graphic(evt.geometry, null, newAttributes);
//selectedTemplate.featureLayer.applyEdits([newGraphic], null, null);
// updatePendingTextArea(newGraphic,"Attempting to Add",null);
offlineStore.applyEdits(newGraphic,selectedTemplate.featureLayer,offlineStore.enum().ADD,function(count,success,id){
headerDiv.innerHTML = "Storage Used: " + offlineStore.getlocalStorageUsed() + " MBs";
});
@ -433,52 +417,32 @@ function graphicInfoModel(/* Graphic */ graphic){
graphic.hasOwnProperty("geometry") ? this.geometryType = graphic.geometry.type : this.geometryType = null;
}
function addPendingTextArea(){
/**
* Sends pending changes to the update log
* @param graphic
* @param evt
* @param id
*/
function updatePendingTextArea(graphic,/* String */ evt,id){
var date = new Date();
var dateHMS = getDateHMS();
var attributes = graphic.attributes;
var objectid = attributes.objectid;
var geometryType = graphic.geometry.type;
var logText = "";
var graphicsArr = offlineStore.getStore();
if (typeof objectid == "undefined" && attributes.hasOwnProperty("symbolname") == true){
objectid = "new. Symbol name = " + attributes.symbolname;
}
else if (typeof objectid == "undefined" && attributes.hasOwnProperty("symbolname") == false){
objectid = "new. Symbol name = unknown";
}
else if(attributes != null || typeof attributes != "undefined" && attributes.hasOwnProperty("objectid")){
objectid = attributes.objectid;
}
else if (id != null || typeof id != "undefined"){
objectid = id;
if(graphicsArr != null){
var length = graphicsArr.length;
for(var i = 0; i < length; i++){
var symbolname = null;
var graphic = graphicsArr[i].graphic;
var layer = graphicsArr[i].layer;
var enumValue = graphicsArr[i].enumValue;
var date = graphicsArr[i].date;
var geometryType = graphic.geometry.type;
if(graphic.hasOwnProperty("attributes") && graphic.attributes.hasOwnProperty("objectid")){
objectid = graphic.attributes.objectid;
graphic.attributes.hasOwnProperty("symbolname") == true ? symbolname = graphic.attributes.symbolname : symbolname = null;
}
else{
}
logText += date + " " + enumValue + " " + geometryType + " " + objectid + ", symbolname: " + symbolname + ", date:" + date + "\n\r";
}
}
_logValue += dateHMS + " " + evt + " " + geometryType + " " + objectid + "\n\r";
document.getElementById("pendingTextArea").innerHTML = _logValue;
}
function addPendingTextArea(message){
var objectid = null;
var layer = message.layerId;
var enumValue = message.enumValue;
var date = offlineUtils.getDateMDYHMS();
var graphic = message.graphic;
var geometryType = graphic.geometry.type;
if(graphic.hasOwnProperty("attributes") && graphic.attributes.hasOwnProperty("objectid")){
objectid = graphic.attributes.objectid;
}
_logValue += date + " " + enumValue + " " + geometryType + " " + objectid + "\n\r";
document.getElementById("pendingTextArea").innerHTML = _logValue;
// console.log(date + ", " + objectid + ", " + layer + ", " + enumValue);
document.getElementById("pendingTextArea").innerHTML = logText;
}
/**
@ -486,25 +450,17 @@ function addPendingTextArea(message){
* @param evt
*/
function updateHistoryTextArea(evt){
var dateHMS = getDateHMS();
var evtObjMsg = evt.detail.message;
var logText = "";
var index = offlineStore.getLocalStoreIndex();
var evtType;
switch(evtObjMsg.type){
case "delete":
evtType = "Delete success = " + evtObjMsg.success;
break;
case "add":
evtType = "Add success = " + evtObjMsg.success;
break;
case "update":
evtType = "Update success = " + evtObjMsg.success;
break;
if(index != null){
for(var property in index){
var item = index[property];
logText += item + "\n\r";
}
}
_logValue += dateHMS + " " + evtType + " for " + evtObjMsg.geometryType + " id: " + evtObjMsg.id + "\n\r";
document.getElementById("historyTextArea").innerHTML = _logValue;
document.getElementById("historyTextArea").innerHTML = logText;
}
function getDateHMS(){

View File

@ -5,7 +5,7 @@ describe("Initialize Offline Library", function() {
})
it("get graphics layer by id", function(){
var layer = offlineStore._getGraphicsLayerById(6);
var layer = offlineStore.getGraphicsLayerById(6);
expect(layer).toEqual(jasmine.any(Object));
})
@ -98,7 +98,7 @@ describe("Serialize/Deserialize Graphic - simple Point Graphic", function(){
it("delete one graphic from local storage", function(){
var value = null;
var attempt = offlineStore._deleteItemInLocalStore("42749",function(evt){
var attempt = offlineStore._deleteObjectIdInLocalStore("42749",function(evt){
value = evt;
}.bind(this))
expect(value).toEqual(true);
@ -484,13 +484,28 @@ describe("Validate local storage index functionality",function(){
})
describe("Reestablish internet", function(){
it("set item in local storage", function(){
offlineStore._deleteLocalStoreIndex();
var json = offlineStore._serializeGraphic(complexPolygonGraphic,landusePointLayer,offlineStore.enum().ADD);
var setItem = offlineStore._setTempLocalStore(json);
expect(setItem).toEqual(true);
})
it("reestablish internet handler with empty store", function(){
var validate = null;
offlineStore._reestablishedInternet(function(evt){
validate = evt;
});
expect(validate).toEqual(false);
offlineStore._reestablishedInternet();
var item = offlineStore.getLocalStoreIndex();
expect(item).toEqual(null);
})
it("get item from local storage using getStore()", function(){
var data = offlineStore.getStore();
var type = Object.prototype.toString.call( data ); // === '[object Array]';
expect(type).toEqual('[object Array]');
})
})
describe("Test custom event handling", function(){