diff --git a/lib/edit/offlineFeaturesManager.js b/lib/edit/offlineFeaturesManager.js index 152bde6..79d5384 100644 --- a/lib/edit/offlineFeaturesManager.js +++ b/lib/edit/offlineFeaturesManager.js @@ -283,45 +283,29 @@ define([ adds.forEach(function (addEdit) { var deferred = new Deferred(); - //self._validateFeature(addEdit).then(function(result){ - // console.log("HELLLLOOOO") - //}); - var objectId = this._getNextTempId(); addEdit.attributes[this.objectIdField] = objectId; var thisLayer = this; - self._editStore.pushEdit(self._editStore.ADD, this.url, addEdit, function (result, error) { - if(result == true){ - results.addResults.push({success: true, error: null, objectId: objectId}); + // We need to run some validation tests against each feature being added. + // If graphic doesn't exist return false and we will add a phantom graphic + // If graphic DOES exist then return true and we do not need to add a phantom graphic + this._validateFeature(addEdit,this.url,self._editStore.ADD).then(function(result){ + console.log("EDIT ADD IS BACK!!! " ); - var phantomAdd = new Graphic( - addEdit.geometry, - self._getPhantomSymbol(addEdit.geometry, self._editStore.ADD), - { - objectId: objectId - }); - - // Add phantom graphic to the layer - thisLayer._phantomLayer.add(phantomAdd); - - // Add phantom graphic to the database - self._editStore.pushPhantomGraphic(phantomAdd, function (result) { - if (!result)console.log("There was a problem adding phantom graphic id: " + objectId); - console.log("Phantom graphic " + objectId + " added to database as an add."); - }); - - domAttr.set(phantomAdd.getNode(), "stroke-dasharray", "10,4"); - domStyle.set(phantomAdd.getNode(), "pointer-events", "none"); - - deferred.resolve(result); + if(result.success){ + thisLayer._pushValidatedAddFeatureToDB(thisLayer,addEdit,result.operation,results,objectId,deferred); } else{ - // If we can't push edit to database then we don't create a phantom graphic - results.addResults.push({success: false, error: error, objectId: objectId}); - deferred.reject(error); + // If we get here then we deleted an edit that was added offline. + // We also have deleted the phantom graphic. + deferred.resolve(true); } + + },function(error){ + console.log("_validateFeature: Unable to validate!"); + deferred.reject(error); }); promises.push(deferred); @@ -336,35 +320,24 @@ define([ var thisLayer = this; - self._editStore.pushEdit(self._editStore.UPDATE, this.url, updateEdit, function (result, error) { + // We need to run some validation tests against each feature being added. + // If graphic doesn't exist return false and we will add a phantom graphic + // If graphic DOES exist then return true and we do not need to add a phantom graphic + this._validateFeature(updateEdit,this.url,self._editStore.UPDATE).then(function(result){ + console.log("EDIT UPDATE IS BACK!!! " ); - if(result == true){ - results.updateResults.push({success: true, error: null, objectId: objectId}); - - var phantomUpdate = new Graphic( - updateEdit.geometry, - self._getPhantomSymbol(updateEdit.geometry, self._editStore.UPDATE), - { - objectId: objectId - }); - thisLayer._phantomLayer.add(phantomUpdate); - - // Add phantom graphic to the database - self._editStore.pushPhantomGraphic(phantomUpdate, function (result) { - if (!result)console.log("There was a problem adding phantom graphic id: " + objectId); - console.log("Phantom graphic " + objectId + " added to database as an update."); - }); - - domAttr.set(phantomUpdate.getNode(), "stroke-dasharray", "5,2"); - domStyle.set(phantomUpdate.getNode(), "pointer-events", "none"); - - deferred.resolve(result); + if(result.success){ + thisLayer._pushValidatedUpdateFeatureToDB(thisLayer,updateEdit,result.operation,results,objectId,deferred); } else{ - // If we can't push edit to database then we don't create a phantom graphic - results.addResults.push({success: false, error: error, objectId: objectId}); - deferred.reject(error); + // If we get here then we deleted an edit that was added offline. + // We also have deleted the phantom graphic. + deferred.resolve(true); } + + },function(error){ + console.log("_validateFeature: Unable to validate!"); + deferred.reject(error); }); promises.push(deferred); @@ -642,6 +615,154 @@ define([ /* internal methods */ + /** + * Pushes an UPDATE request to the database after it's been validated + * @param layer + * @param updateEdit + * @param operation + * @param resultsArray + * @param objectId + * @param deferred + * @private + */ + layer._pushValidatedUpdateFeatureToDB = function(layer,updateEdit,operation,resultsArray,objectId,deferred){ + self._editStore.pushEdit(operation, layer.url, updateEdit, function (result, error) { + + if(result == true){ + resultsArray.updateResults.push({success: true, error: null, objectId: objectId}); + + var phantomUpdate = new Graphic( + updateEdit.geometry, + self._getPhantomSymbol(updateEdit.geometry, self._editStore.UPDATE), + { + objectId: objectId + }); + layer._phantomLayer.add(phantomUpdate); + + // Add phantom graphic to the database + self._editStore.pushPhantomGraphic(phantomUpdate, function (result) { + if (!result)console.log("There was a problem adding phantom graphic id: " + objectId); + console.log("Phantom graphic " + objectId + " added to database as an update."); + }); + + domAttr.set(phantomUpdate.getNode(), "stroke-dasharray", "5,2"); + domStyle.set(phantomUpdate.getNode(), "pointer-events", "none"); + + deferred.resolve(result); + } + else{ + // If we can't push edit to database then we don't create a phantom graphic + resultsArray.addResults.push({success: false, error: error, objectId: objectId}); + deferred.reject(error); + } + }); + }; + + /** + * Pushes an ADD request to the database after it's been validated + * @param layer + * @param addEdit + * @param operation + * @param resultsArray + * @param objectId + * @param deferred + * @private + */ + layer._pushValidatedAddFeatureToDB = function(layer,addEdit,operation,resultsArray,objectId,deferred){ + self._editStore.pushEdit(operation, layer.url, addEdit, function (result, error) { + if(result == true){ + resultsArray.addResults.push({success: true, error: null, objectId: objectId}); + + var phantomAdd = new Graphic( + addEdit.geometry, + self._getPhantomSymbol(addEdit.geometry, self._editStore.ADD), + { + objectId: objectId + }); + + // Add phantom graphic to the layer + layer._phantomLayer.add(phantomAdd); + + // Add phantom graphic to the database + self._editStore.pushPhantomGraphic(phantomAdd, function (result) { + if (!result)console.log("There was a problem adding phantom graphic id: " + objectId); + console.log("Phantom graphic " + objectId + " added to database as an add."); + }); + + domAttr.set(phantomAdd.getNode(), "stroke-dasharray", "10,4"); + domStyle.set(phantomAdd.getNode(), "pointer-events", "none"); + + deferred.resolve(result); + } + else{ + // If we can't push edit to database then we don't create a phantom graphic + resultsArray.addResults.push({success: false, error: error, objectId: objectId}); + deferred.reject(error); + } + }); + }; + + /** + * Validates duplicate entries. Last edit on same feature can overwite any previous values. + * Note: if an edit was already added offline and you delete it then we return success == false + * @param graphic esri.Graphic. + * @param layerUrl the URL of the feature service + * @param operation add, update or delete action on an edit + * @returns deferred {success:boolean,graphic:graphic,operation:add|update|delete} + * @private + */ + layer._validateFeature = function (graphic,layerUrl,operation) { + + var deferred = new Deferred(); + + var id = layerUrl + "/" + graphic.attributes.objectid; + + self._editStore.getEdit(id,function(success,result){ + if (success) { + switch( operation ) + { + case self._editStore.ADD: + // Not good - however we'll allow the new ADD to replace/overwrite existing edit + // and pass it through unmodified. Last ADD wins. + deferred.resolve({"success":true,"graphic":graphic,"operation":operation}); + break; + case self._editStore.UPDATE: + // If we are doing an update on a feature that has not been added to + // the server yet, then we need to maintain its operation as an ADD + // and not an UPDATE. This avoids the potential for an error if we submit + // an update operation on a feature that has not been added to the + // database yet. + if(result.operation = self._editStore.ADD){ + graphic.operation = self._editStore.ADD; + operation = self._editStore.ADD; + } + deferred.resolve({"success":true,"graphic":graphic,"operation":operation}); + break; + case self._editStore.DELETE: + + if(result.operation = this._editStore.ADD){ + // If we are deleting a new feature that has not been added to the + // server yet we need to delete it and its phantom graphic. + deferred.resolve({"success":false,"graphic":graphic,"operation":operation}); + } + deferred.resolve({"success":true,"graphic":graphic,"operation":operation}); + break; + } + } + else if(result == "Id not found"){ + // Let's simply pass the graphic back as good-to-go. + // No modifications needed because the graphic does not + // already exist in the database. + deferred.resolve({"success":true,"graphic":graphic,"operation":operation}); + } + else{ + deferred.reject(graphic); + } + }); + + return deferred; + }; + layer._getFilesFromForm = function (formNode) { var files = []; var inputNodes = array.filter(formNode.elements, function (node) { @@ -1316,52 +1437,6 @@ define([ return dfd.promise; }, - /** - * Validates duplicate entries. Last edit on same feature can overwite any previous values. - * @param edit valid internal editsStore edit object. - * @returns deferred - * @private - */ - _validateFeature: function (edit) { - - var deferred = new Deferred(); - this._editStore.getEdit(edit.id,function(success,result){ - if (success) { - switch( edit.operation ) - { - case this._editStore.ADD: - // Not good - however we'll allow the new ADD to replace existing edit - // and pass it through unmodified. Last ADD wins. - deferred.resolve(edit); - break; - case this._editStore.UPDATE: - // If we are doing an update on a feature that has not been added to - // the server yet, then we need to maintain its operation as an ADD - // and not an update. This avoids the potential for an error if we submit - // an update operation on a feature that has not been added to the - // database yet. - if(result.operation = this._editStore.ADD){ - edit.operation = this._editStore.ADD; - } - deferred.resolve(edit); - break; - case this._editStore.DELETE: - - if(result.operation = this._editStore.ADD){ - // If we are deleting a new feature that has not been added to the - // server yet we need to delete it and its phantom graphic. - } - deferred.resolve(edit); - break; - } - } - else{ - deferred.resolve(edit); - } - }); - return deferred; - }, - /** * Deprecated @ v2.5. Internal-use only * @returns {string} diff --git a/test/spec/offlineFeaturesManagerSpec.js b/test/spec/offlineFeaturesManagerSpec.js index 5c27e69..baf2339 100644 --- a/test/spec/offlineFeaturesManagerSpec.js +++ b/test/spec/offlineFeaturesManagerSpec.js @@ -409,9 +409,82 @@ describe("Offline Editing", function() }); }); + async.it("Update new feature offline - point", function(done){ + + // Let's make a change to g6 attributes + g6.attributes.additionalinformation = "TEST123"; + var updates = [g6]; + g_featureLayers[0].applyEdits(null,updates,null,function(addResults,updateResults,deleteResults) + { + expect(updateResults.length).toBe(1); + + g_editsStore.pendingEditsCount(function(result){ + + // Should be the exact same as previous test + // An update to a new feature should be a single entry in the database. + // We simply update the existing entry with the new information. + expect(result).toBe(9); + done(); + }); + }, + function(error) + { + expect(true).toBeFalsy(); + }); + }); + + async.it("validate non-existent feature - ADD", function(done){ + + var testGraphic = new g_modules.Graphic({"geometry":{"x":-109100,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Reference Point DLRP","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} ); + + g_featureLayers[0]._validateFeature(testGraphic,g_featureLayers[0].url,"add") + .then(function(result){ + expect(result.success).toBe(true); + expect(testGraphic).toEqual(result.graphic); + expect(result.operation).toEqual("add"); + done(); + },function(error){ + console.log("Validate feature error: " + error); + }); + }); + + async.it("validate existing feature - ADD", function(done){ + var id = getObjectIds([g6]).toString(); + expect(id).toEqual("-3"); + g_featureLayers[0]._validateFeature(g6,g_featureLayers[0].url,"add") + .then(function(result){ + expect(result.success).toBe(true); + expect(g6).toEqual(result.graphic); + expect(JSON.stringify(g6.toJson()) === JSON.stringify(result.graphic.toJson())).toBeTruthy(); + expect(result.operation).toEqual("add"); + done(); + },function(error){ + console.log("Validate feature error: " + error); + }); + }); + + async.it("validate existing feature - UPDATE", function(done){ + var id = getObjectIds([g6]).toString(); + expect(id).toEqual("-3"); + g_featureLayers[0]._validateFeature(g6,g_featureLayers[0].url,"update") + .then(function(result){ + expect(result.success).toBe(true); + expect(g6).toEqual(result.graphic); + + // we swap the operation type when updating an edit that hasn't + // been submitted to the server yet. + expect(result.operation).toBe("add"); + expect(JSON.stringify(g6.toJson()) === JSON.stringify(result.graphic.toJson())).toBeTruthy(); + expect(result.operation).toEqual("add"); + done(); + },function(error){ + console.log("Validate feature error: " + error); + }); + }); + async.it("check db size", function(done){ g_featureLayers[0].getUsage(function(usage,error){ - expect(usage.sizeBytes).toBe(7978); + expect(usage.sizeBytes).toBe(7982); expect(usage.editCount).toBe(9); expect(error).toBe(null); done();