From 6de6572f7ae805c0dbeef3578b978b446a95fe31 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Tue, 26 May 2015 09:48:07 -0600 Subject: [PATCH 01/17] Remove deprecated methods --- lib/edit/editsStore.js | 58 ------------------------------------------ 1 file changed, 58 deletions(-) diff --git a/lib/edit/editsStore.js b/lib/edit/editsStore.js index 67a7cd60..13ea0d73 100644 --- a/lib/edit/editsStore.js +++ b/lib/edit/editsStore.js @@ -960,64 +960,6 @@ O.esri.Edit.EditStore = function () { callback(true, null); }.bind(this); }; - - /// - /// DEPRECATED @ v2.5 - /// Subject to complete removal at the next release. - /// Many of these were undocumented and for internal use. - /// - - /** - * Deprecated @ v2.5. Use pendingEditsCount(). - */ - this.hasPendingEdits = function () { - return "DEPRECATED at v2.5!"; - }; - - /** - * Deprecated @ v2.5. Use public function editExists() instead. - */ - this._isEditDuplicated = function (newEdit, edits) { - return "DEPRECATED at v2.5!"; - }; - - /** - * Deprecated @ v2.5. Use pushEdit() - */ - this._storeEditsQueue = function (edits) { - return "DEPRECATED at v2.5!"; - }; - - /** - * Deprecated @ v2.5. - */ - this._unpackArrayOfEdits = function (edits) { - return "DEPRECATED at v2.5!"; - }; - - /** - * Deprecated @ v2.5. Use getUsage(). - * @returns {string} - */ - this.getLocalStorageSizeBytes = function(){ - return "DEPRECATED at v2.5!"; - }; - - /** - * Deprecated @ v2.5. - * @returns {string} - */ - this.peekFirstEdit = function(){ - return "DEPRECATED at v2.5!"; - }; - - /** - * Deprecated @ v2.5. - * @returns {string} - */ - this.popFirstEdit = function(){ - return "DEPRECATED at v2.5!"; - }; }; From ada41c9959ffa98289d593d920e9e38edd95f858 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Tue, 26 May 2015 11:05:04 -0600 Subject: [PATCH 02/17] minor code cleanup --- test/SpecRunner.offlineFeaturesManager.html | 48 ++++++++++----------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/test/SpecRunner.offlineFeaturesManager.html b/test/SpecRunner.offlineFeaturesManager.html index 67af0bfb..a52c5667 100644 --- a/test/SpecRunner.offlineFeaturesManager.html +++ b/test/SpecRunner.offlineFeaturesManager.html @@ -115,30 +115,30 @@ try { - var jasmineEnv = jasmine.getEnv(); - jasmineEnv.updateInterval = 1000; - jasmineEnv.defaultTimeoutInterval = 10000; // 10 sec - var htmlReporter = new jasmine.HtmlReporter(); - - jasmineEnv.addReporter(htmlReporter); - - jasmineEnv.specFilter = function(spec) { - return htmlReporter.specFilter(spec); - }; - - /* - var currentWindowOnload = window.onload; - - window.onload = function() { - if (currentWindowOnload) { - currentWindowOnload(); + var jasmineEnv = jasmine.getEnv(); + jasmineEnv.updateInterval = 1000; + jasmineEnv.defaultTimeoutInterval = 10000; // 10 sec + var htmlReporter = new jasmine.HtmlReporter(); + + jasmineEnv.addReporter(htmlReporter); + + jasmineEnv.specFilter = function(spec) { + return htmlReporter.specFilter(spec); + }; + + /* + var currentWindowOnload = window.onload; + + window.onload = function() { + if (currentWindowOnload) { + currentWindowOnload(); + } + execJasmine(); + }; + */ + function execJasmine() { + jasmineEnv.execute(); } - execJasmine(); - }; - */ - function execJasmine() { - jasmineEnv.execute(); - } execJasmine(); } @@ -146,8 +146,6 @@ { console.log(err); } - - }; // test() }); // require() From 4a1d316637b9c57ccfd16e3de6a4e429c55282b1 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Tue, 26 May 2015 11:13:56 -0600 Subject: [PATCH 03/17] stubbed get and push featureCollections --- lib/edit/editsStore.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/lib/edit/editsStore.js b/lib/edit/editsStore.js index 13ea0d73..74ebb323 100644 --- a/lib/edit/editsStore.js +++ b/lib/edit/editsStore.js @@ -22,6 +22,7 @@ O.esri.Edit.EditStore = function () { this.DELETE = "delete"; this.FEATURE_LAYER_JSON_ID = "feature-layer-object-1001"; + this.FEATURE_COLLECTION_ID = "feature-collection-object-1001"; this.PHANTOM_GRAPHIC_PREFIX = "phantom-layer"; this._PHANTOM_PREFIX_TOKEN = "|@|"; @@ -69,6 +70,42 @@ O.esri.Edit.EditStore = function () { } }; + this._pushFeatureCollections = function(featureCollectionObject, callback){ + var transaction = this._db.transaction([this.objectStoreName], "readwrite"); + + transaction.oncomplete = function (event) { + callback(true); + }; + + transaction.onerror = function (event) { + callback(false, event.target.error.message); + }; + + var objectStore = transaction.objectStore(this.objectStoreName); + objectStore.put(featureCollectionObject); + }; + + this._getFeatureCollections = function(callback){ + var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); + + //Get the entry associated with the graphic + var objectStoreGraphicRequest = objectStore.get(this.FEATURE_COLLECTION_ID); + + objectStoreGraphicRequest.onsuccess = function () { + var object = objectStoreGraphicRequest.result; + if (typeof object != "undefined") { + callback(true, object); + } + else { + callback(false, "nothing found"); + } + }; + + objectStoreGraphicRequest.onerror = function (msg) { + callback(false, msg); + }; + }; + /** * Use this to store any static FeatureLayer or related JSON data related to your app that will assist in restoring * a FeatureLayer. From 5061c2e0844d5c07dd35fb8d9bd6042125f41cda Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Tue, 26 May 2015 11:14:31 -0600 Subject: [PATCH 04/17] removed unused deferred reference --- lib/edit/editsStore.js | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/lib/edit/editsStore.js b/lib/edit/editsStore.js index 74ebb323..a20936f6 100644 --- a/lib/edit/editsStore.js +++ b/lib/edit/editsStore.js @@ -585,30 +585,27 @@ O.esri.Edit.EditStore = function () { console.assert(this._db !== null, "indexeddb not initialized"); var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); - require(["dojo/Deferred"], function (Deferred) { - - if(typeof id === "undefined"){ - callback(false,"id is undefined."); - return; - } + if(typeof id === "undefined"){ + callback(false,"id is undefined."); + return; + } - //Get the entry associated with the graphic - var objectStoreGraphicRequest = objectStore.get(id); + //Get the entry associated with the graphic + var objectStoreGraphicRequest = objectStore.get(id); - objectStoreGraphicRequest.onsuccess = function () { - var graphic = objectStoreGraphicRequest.result; - if (graphic && (graphic.id == id)) { - callback(true,graphic); - } - else { - callback(false,"Id not found"); - } - }; + objectStoreGraphicRequest.onsuccess = function () { + var graphic = objectStoreGraphicRequest.result; + if (graphic && (graphic.id == id)) { + callback(true,graphic); + } + else { + callback(false,"Id not found"); + } + }; - objectStoreGraphicRequest.onerror = function (msg) { - callback(false,msg); - }; - }); + objectStoreGraphicRequest.onerror = function (msg) { + callback(false,msg); + }; }; /** From 0e3f57246356e35ca9fe36f923d5e6101c445c79 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Tue, 26 May 2015 15:52:22 -0600 Subject: [PATCH 05/17] update editsStoreSpec2 --- test/spec/editsStoreSpec2.js | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/spec/editsStoreSpec2.js b/test/spec/editsStoreSpec2.js index d9c3151d..a03f6a31 100644 --- a/test/spec/editsStoreSpec2.js +++ b/test/spec/editsStoreSpec2.js @@ -428,6 +428,53 @@ describe("Public Interface", function() }) }); }); + + describe("Handle built-in feature collection in database", function(){ + async.it("Get null featureCollection", function(done) { + g_editsStore._getFeatureCollections(function(success, result){ + expect(success).toBe(false); + expect(result).toBeNull(); + done(); + }); + }); + + async.it("Set featureCollection", function(done) { + var featureCollectionObject = { + // The id is required because the editsStore keypath + // uses it as a UID for all entries in the database + id: g_editsStore.FEATURE_COLLECTION_ID, + featureCollections: [ + { + featureLayerUrl: "TEST_URL1", + featureLayerCollection: "featureCollectionFeature" + } + ] + }; + + g_editsStore._pushFeatureCollections(featureCollectionObject, function(success) { + console.log("SUCCESS IS " + success); + expect(success).toBe(true); + done(); + }); + }); + + async.it("Get new featureCollection", function(done) { + g_editsStore._getFeatureCollections(function(success, result){ + expect(success).toBe(true); + expect(result.featureCollections[0].featureLayerUrl).toBe("TEST_URL1"); + done(); + }); + }); + + async.it("get size", function(done){ + g_editsStore.getUsage(function(success){ + expect(success).toEqual(jasmine.any(Object)); + expect(success.sizeBytes).toEqual(1695); + expect(success.editCount).toEqual(3); + done(); + }) + }); + }); }) }); From b752fcb3ef5fdf6d3fca8e0c7b9b17157b4f1b2b Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Tue, 26 May 2015 17:05:08 -0600 Subject: [PATCH 06/17] refactors get and push featureCollections --- lib/edit/editsStore.js | 98 +++++++++++++++++------------- lib/edit/offlineFeaturesManager.js | 70 +++++++++++++++++++++ 2 files changed, 125 insertions(+), 43 deletions(-) diff --git a/lib/edit/editsStore.js b/lib/edit/editsStore.js index a20936f6..4878e5b9 100644 --- a/lib/edit/editsStore.js +++ b/lib/edit/editsStore.js @@ -13,7 +13,7 @@ O.esri.Edit.EditStore = function () { this.objectStoreName = "features"; this.objectId = "objectid"; // set this depending on how your feature service is configured; - var _dbIndex = "featureId"; // @private + //var _dbIndex = "featureId"; // @private // ENUMs @@ -70,42 +70,6 @@ O.esri.Edit.EditStore = function () { } }; - this._pushFeatureCollections = function(featureCollectionObject, callback){ - var transaction = this._db.transaction([this.objectStoreName], "readwrite"); - - transaction.oncomplete = function (event) { - callback(true); - }; - - transaction.onerror = function (event) { - callback(false, event.target.error.message); - }; - - var objectStore = transaction.objectStore(this.objectStoreName); - objectStore.put(featureCollectionObject); - }; - - this._getFeatureCollections = function(callback){ - var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); - - //Get the entry associated with the graphic - var objectStoreGraphicRequest = objectStore.get(this.FEATURE_COLLECTION_ID); - - objectStoreGraphicRequest.onsuccess = function () { - var object = objectStoreGraphicRequest.result; - if (typeof object != "undefined") { - callback(true, object); - } - else { - callback(false, "nothing found"); - } - }; - - objectStoreGraphicRequest.onerror = function (msg) { - callback(false, msg); - }; - }; - /** * Use this to store any static FeatureLayer or related JSON data related to your app that will assist in restoring * a FeatureLayer. @@ -619,6 +583,7 @@ O.esri.Edit.EditStore = function () { if (this._db !== null) { var fLayerJSONId = this.FEATURE_LAYER_JSON_ID; + var fCollectionId = this.FEATURE_COLLECTION_ID; var phantomGraphicPrefix = this.PHANTOM_GRAPHIC_PREFIX; var transaction = this._db.transaction([this.objectStoreName]) @@ -630,7 +595,7 @@ O.esri.Edit.EditStore = function () { if (cursor && cursor.hasOwnProperty("value") && cursor.value.hasOwnProperty("id")) { // Make sure we are not return FeatureLayer JSON data or a Phantom Graphic - if (cursor.value.id !== fLayerJSONId && cursor.value.id.indexOf(phantomGraphicPrefix) == -1) { + if (cursor.value.id !== fLayerJSONId && cursor.value.id !== fCollectionId && cursor.value.id.indexOf(phantomGraphicPrefix) == -1) { callback(cursor.value, null); } cursor.continue(); @@ -660,6 +625,7 @@ O.esri.Edit.EditStore = function () { if (this._db !== null) { var fLayerJSONId = this.FEATURE_LAYER_JSON_ID; + var fCollectionId = this.FEATURE_COLLECTION_ID; var phantomGraphicPrefix = this.PHANTOM_GRAPHIC_PREFIX; var transaction = this._db.transaction([this.objectStoreName]) @@ -671,7 +637,7 @@ O.esri.Edit.EditStore = function () { if (cursor && cursor.value && cursor.value.id) { // Make sure we are not return FeatureLayer JSON data or a Phantom Graphic - if (cursor.value.id !== fLayerJSONId && cursor.value.id.indexOf(phantomGraphicPrefix) == -1) { + if (cursor.value.id !== fLayerJSONId && cursor.value.id !== fCollectionId && cursor.value.id.indexOf(phantomGraphicPrefix) == -1) { editsArray.push(cursor.value); } @@ -830,6 +796,7 @@ O.esri.Edit.EditStore = function () { var count = 0; var id = this.FEATURE_LAYER_JSON_ID; + var fCollectionId = this.FEATURE_COLLECTION_ID; var phantomGraphicPrefix = this.PHANTOM_GRAPHIC_PREFIX; var transaction = this._db.transaction([this.objectStoreName], "readwrite"); @@ -840,7 +807,7 @@ O.esri.Edit.EditStore = function () { // IMPORTANT: // Remember that we have feature layer JSON and Phantom Graphics in the same database if (cursor && cursor.value && cursor.value.id && cursor.value.id.indexOf(phantomGraphicPrefix) == -1) { - if (cursor.value.id !== id) { + if (cursor.value.id !== id && cursor.value.id !== fCollectionId) { count++; } cursor.continue(); @@ -899,6 +866,7 @@ O.esri.Edit.EditStore = function () { console.assert(this._db !== null, "indexeddb not initialized"); var id = this.FEATURE_LAYER_JSON_ID; + var fCollectionId = this.FEATURE_COLLECTION_ID; var phantomGraphicPrefix = this.PHANTOM_GRAPHIC_PREFIX; var usage = {sizeBytes: 0, editCount: 0}; @@ -916,7 +884,7 @@ O.esri.Edit.EditStore = function () { var json = JSON.stringify(storedObject); usage.sizeBytes += json.length; - if (cursor.value.id.indexOf(phantomGraphicPrefix) == -1 && cursor.value.id !== id) { + if (cursor.value.id.indexOf(phantomGraphicPrefix) == -1 && cursor.value.id !== id && cursor.value.id !== fCollectionId) { usage.editCount += 1; } @@ -931,7 +899,52 @@ O.esri.Edit.EditStore = function () { }; }; + // // internal methods + // + + /** + * The library automatically keeps a copy of the featureLayerCollection and its + * associated layer.url + * @param featureCollectionObject + * @param callback + * @private + */ + this._pushFeatureCollections = function(featureCollectionObject, callback){ + var transaction = this._db.transaction([this.objectStoreName], "readwrite"); + + transaction.oncomplete = function (event) { + callback(true); + }; + + transaction.onerror = function (event) { + callback(false, event.target.error.message); + }; + + var objectStore = transaction.objectStore(this.objectStoreName); + objectStore.put(featureCollectionObject); + }; + + this._getFeatureCollections = function(callback){ + var objectStore = this._db.transaction([this.objectStoreName], "readonly").objectStore(this.objectStoreName); + + //Get the entry associated with the graphic + var objectStoreGraphicRequest = objectStore.get(this.FEATURE_COLLECTION_ID); + + objectStoreGraphicRequest.onsuccess = function () { + var object = objectStoreGraphicRequest.result; + if (typeof object != "undefined") { + callback(true, object); + } + else { + callback(false, null); + } + }; + + objectStoreGraphicRequest.onerror = function (msg) { + callback(false, msg); + }; + }; /** * Save space in the database...don't need to store the entire Graphic object just its public properties! @@ -983,8 +996,7 @@ O.esri.Edit.EditStore = function () { db.deleteObjectStore(this.objectStoreName); } - var objectStore = db.createObjectStore(this.objectStoreName, {keyPath: "id"}); - objectStore.createIndex(_dbIndex, _dbIndex, {unique: false}); + db.createObjectStore(this.objectStoreName, {keyPath: "id"}); }.bind(this); request.onsuccess = function (event) { diff --git a/lib/edit/offlineFeaturesManager.js b/lib/edit/offlineFeaturesManager.js index 28220c21..8b0c4296 100644 --- a/lib/edit/offlineFeaturesManager.js +++ b/lib/edit/offlineFeaturesManager.js @@ -499,6 +499,48 @@ define([ } } + var featureCollectionObject = { + // The id is required because the editsStore keypath + // uses it as a UID for all entries in the database + id: self._editStore.FEATURE_COLLECTION_ID, + featureCollections: [ + { + featureLayerUrl: layer.url, + featureLayerCollection: JSON.stringify(this.toJson()) + } + ] + }; + + self._editStore._getFeatureCollections(function(success, result) { + if(success){ + var count = 0; + result.featureCollections.forEach(function(entry, i){ + if(entry.featureLayerUrl === layer.url) { + count++; + result.featureCollections[i] = featureCollectionObject; + } + }); + + if(count === 0) { + result.featureCollections.push(featureCollectionObject); + } + } + else if(!success && result === null) { + result = featureCollectionObject; + } + else { + console.error("There was a problem retrieving the featureCollections from editStore."); + } + + // Automatically update the featureCollection store in the database with every ADD, UPDATE + // and DELETE. It can be retrieved via OfflineFeaturesManager.getFeatureCollections(); + self._editStore._pushFeatureCollections(result, function(success, error) { + if(!success){ + console.error("There was a problem creating the featureCollectionObject: " + error); + } + }); + }); + // we already pushed the edits into the database, now we let the FeatureLayer to do the local updating of the layer graphics // EDITS_ENQUEUED = callback(true, edit), and EDITS_ENQUEUED_ERROR = callback(false, /*String */ error) this._editHandler(results, adds, updatesMap, callback, errback, deferred1); @@ -1149,6 +1191,34 @@ define([ } }, + /** + * Retrieves the feature collection object. Specifically used in offline browser restarts. + * This is an object created automatically by the library and is updated with every ADD, UPDATE and DELETE. + * Attachments are handled separately and not part of the feature collection created here. + * + * It has the following signature: {id: featureLayerCollections: [{ featureCollection: [Object], featureLayerUrl: String }]} + * @param callback + */ + getFeatureCollections: function(callback){ + if(!this._editStore._isDBInit){ + + this._initializeDB(null,null).then(function(result){ + if(result.success){ + this._editStore._getFeatureCollections(function(success,message){ + callback(success,message); + }); + } + }.bind(this), function(err){ + callback(false, err); + }); + } + else { + this._editStore._getFeatureCollections(function(success,message){ + callback(success,message); + }); + } + }, + /** * Retrieves the optional feature layer storage object * For use in full offline scenarios. From 32a63a3a1fd5ebc5d83e0d76a1bcdad67b3d7e1e Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Wed, 27 May 2015 10:43:05 -0600 Subject: [PATCH 07/17] add featureCollection validate to spec --- test/spec/offlineFeaturesManagerSpec.js | 30 ++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/test/spec/offlineFeaturesManagerSpec.js b/test/spec/offlineFeaturesManagerSpec.js index d9f64108..51710f2b 100644 --- a/test/spec/offlineFeaturesManagerSpec.js +++ b/test/spec/offlineFeaturesManagerSpec.js @@ -419,6 +419,14 @@ describe("Offline Editing", function() }); + async.it("Get empty featureCollections Object", function(done) { + g_offlineFeaturesManager.getFeatureCollections(function(success, result) { + expect(success).toBe(false); + expect(result).toBeNull(); + done(); + }); + }); + async.it("update existing features - points", function(done) { expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g3])); @@ -449,6 +457,16 @@ describe("Offline Editing", function() }); }); + async.it("Get featureCollections Object", function(done) { + g_offlineFeaturesManager.getFeatureCollections(function(success, result) { + expect(success).toBe(true); + expect(result.featureCollections.length).toBe(1); + expect(result.featureCollections[0].featureLayerCollection).toEqual(JSON.stringify(g_featureLayers[0].toJson())); + expect(result.featureCollections[0].featureLayerUrl).toEqual("http://services1.arcgis.com/M8KJPUwAXP8jhtnM/arcgis/rest/services/Simple_Point_Service/FeatureServer/0"); + done(); + }); + }); + // NOTE: We are only dealing with points! //async.it("update existing features - lines", function(done) //{ @@ -656,12 +674,22 @@ describe("Offline Editing", function() async.it("check db size", function(done){ g_featureLayers[0].getUsage(function(usage,error){ - expect(usage.sizeBytes).toBe(3847); + expect(usage.sizeBytes).toBe(20414); expect(usage.editCount).toBe(5); expect(error).toBe(null); done(); }) }); + + async.it("Validate featureCollections Object", function(done) { + g_offlineFeaturesManager.getFeatureCollections(function(success, result) { + expect(success).toBe(true); + expect(result.featureCollections.length).toBe(1); + expect(result.featureCollections[0].featureLayerCollection).toEqual(JSON.stringify(g_featureLayers[0].toJson())); + expect(result.featureCollections[0].featureLayerUrl).toEqual("http://services1.arcgis.com/M8KJPUwAXP8jhtnM/arcgis/rest/services/Simple_Point_Service/FeatureServer/0"); + done(); + }); + }); }); // TO-DO!! From 6e7b59a476ad19698e6450045dd3818e6693b4cf Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Wed, 27 May 2015 10:43:30 -0600 Subject: [PATCH 08/17] minor code comment update --- lib/edit/editsStore.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/edit/editsStore.js b/lib/edit/editsStore.js index 4878e5b9..47b84993 100644 --- a/lib/edit/editsStore.js +++ b/lib/edit/editsStore.js @@ -905,7 +905,9 @@ O.esri.Edit.EditStore = function () { /** * The library automatically keeps a copy of the featureLayerCollection and its - * associated layer.url + * associated layer.url. + * + * There should be only one featureLayerCollection Object per feature layer. * @param featureCollectionObject * @param callback * @private From fb6ab89934b5de26a83687fc3d5c928e1432c2dc Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Wed, 27 May 2015 10:43:57 -0600 Subject: [PATCH 09/17] tweak featureCollection creation --- lib/edit/offlineFeaturesManager.js | 47 ++++++++++++++++++------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/lib/edit/offlineFeaturesManager.js b/lib/edit/offlineFeaturesManager.js index 8b0c4296..f523bbe2 100644 --- a/lib/edit/offlineFeaturesManager.js +++ b/lib/edit/offlineFeaturesManager.js @@ -499,34 +499,45 @@ define([ } } - var featureCollectionObject = { - // The id is required because the editsStore keypath - // uses it as a UID for all entries in the database - id: self._editStore.FEATURE_COLLECTION_ID, - featureCollections: [ - { - featureLayerUrl: layer.url, - featureLayerCollection: JSON.stringify(this.toJson()) - } - ] - }; - self._editStore._getFeatureCollections(function(success, result) { + + var featureCollection = + { + featureLayerUrl: layer.url, + featureLayerCollection: JSON.stringify(layer.toJson()) + }; + + // An array of feature collections, of course :-) + var featureCollectionsArray = [ + featureCollection + ]; + + // An object for storing multiple feature collections + var featureCollectionsObject = { + // The id is required because the editsStore keypath + // uses it as a UID for all entries in the database + id: self._editStore.FEATURE_COLLECTION_ID, + featureCollections: featureCollectionsArray + }; + + // If the featureCollections object already exists if(success){ var count = 0; - result.featureCollections.forEach(function(entry, i){ - if(entry.featureLayerUrl === layer.url) { + for(var i = 0; i < result.featureCollections.length; i++) { + if(result.featureCollections[i].featureLayerUrl === layer.url) { count++; - result.featureCollections[i] = featureCollectionObject; + result.featureCollections[i] = featureCollection; } - }); + } + // If we have a new feature layer then add it to the featureCollections array if(count === 0) { - result.featureCollections.push(featureCollectionObject); + result.featureCollections.push(featureCollectionsArray); } } + // If empty then we need to add the new featureCollectionsObject else if(!success && result === null) { - result = featureCollectionObject; + result = featureCollectionsObject; } else { console.error("There was a problem retrieving the featureCollections from editStore."); From b251ea25cb7835dafa0572d5c82cc579ae6feaed Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Wed, 27 May 2015 13:38:04 -0600 Subject: [PATCH 10/17] Minor tweaks to spec --- test/spec/offlineFeaturesManagerSpec.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/test/spec/offlineFeaturesManagerSpec.js b/test/spec/offlineFeaturesManagerSpec.js index 51710f2b..3c507763 100644 --- a/test/spec/offlineFeaturesManagerSpec.js +++ b/test/spec/offlineFeaturesManagerSpec.js @@ -461,7 +461,7 @@ describe("Offline Editing", function() g_offlineFeaturesManager.getFeatureCollections(function(success, result) { expect(success).toBe(true); expect(result.featureCollections.length).toBe(1); - expect(result.featureCollections[0].featureLayerCollection).toEqual(JSON.stringify(g_featureLayers[0].toJson())); + expect(result.featureCollections[0].featureLayerCollection).toEqual(g_featureLayers[0].toJson()); expect(result.featureCollections[0].featureLayerUrl).toEqual("http://services1.arcgis.com/M8KJPUwAXP8jhtnM/arcgis/rest/services/Simple_Point_Service/FeatureServer/0"); done(); }); @@ -674,7 +674,7 @@ describe("Offline Editing", function() async.it("check db size", function(done){ g_featureLayers[0].getUsage(function(usage,error){ - expect(usage.sizeBytes).toBe(20414); + expect(usage.sizeBytes).toBe(9203); expect(usage.editCount).toBe(5); expect(error).toBe(null); done(); @@ -685,7 +685,7 @@ describe("Offline Editing", function() g_offlineFeaturesManager.getFeatureCollections(function(success, result) { expect(success).toBe(true); expect(result.featureCollections.length).toBe(1); - expect(result.featureCollections[0].featureLayerCollection).toEqual(JSON.stringify(g_featureLayers[0].toJson())); + expect(result.featureCollections[0].featureLayerCollection).toEqual(g_featureLayers[0].toJson()); expect(result.featureCollections[0].featureLayerUrl).toEqual("http://services1.arcgis.com/M8KJPUwAXP8jhtnM/arcgis/rest/services/Simple_Point_Service/FeatureServer/0"); done(); }); @@ -1123,6 +1123,16 @@ describe("Offline Editing", function() done(); }); }); + + async.it("Validate featureCollections Object", function(done) { + g_offlineFeaturesManager.getFeatureCollections(function(success, result) { + expect(success).toBe(true); + expect(result.featureCollections.length).toBe(1); + expect(result.featureCollections[0].featureLayerCollection).toEqual(g_featureLayers[0].toJson()); + expect(result.featureCollections[0].featureLayerUrl).toEqual("http://services1.arcgis.com/M8KJPUwAXP8jhtnM/arcgis/rest/services/Simple_Point_Service/FeatureServer/0"); + done(); + }); + }); }); describe("go Online", function() From b59c4964b75b97ebe86a60bc9664a2c378a0a41b Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Wed, 27 May 2015 13:38:48 -0600 Subject: [PATCH 11/17] doc updates --- README.md | 2 +- doc/howtouseeditlibrary.md | 19 ++++++++++++++++++- doc/offlinefeaturesmanager.md | 23 ++++++++++++----------- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 98cd91cb..4c659a79 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Offline-editor-js is an open source family of libraries for building offline cap Online samples and getting started tutorials are available here: **[http://esri.github.io/offline-editor-js/demo/](http://esri.github.io/offline-editor-js/demo/)** -*IMPORTANT:* This is an R&D project. If you need a fully integrated, robust offline solution then you should be using our ArcGIS Runtime SDKs for .NET, WPF, Java, iOS, Android and Qt. +*IMPORTANT:* If you need a fully integrated, robust offline solution then you should be using our ArcGIS Runtime SDKs for .NET, WPF, Java, iOS, Android and Qt. This repo contains the following libraries: diff --git a/doc/howtouseeditlibrary.md b/doc/howtouseeditlibrary.md index 62b748b7..4733052d 100644 --- a/doc/howtouseeditlibrary.md +++ b/doc/howtouseeditlibrary.md @@ -75,7 +75,24 @@ NOTE: You can also monitor standard ArcGIS API for JavaScript layer events using ``` -**Step 4** After the `layers-add-result` event fires extend the feature layer using the `extend()` method. Optionally, if you are building a fully offline app then you will also need to set the `dataStore` property in the constructor. +**Step 4** After the `layers-add-result` event fires extend the feature layer using the `extend()` method. + +Optionally, if you are building a fully offline app then you will also need to set the `dataStore` property in the constructor if you want full control of what is stored. You can also access an automatically created data store via the `getFeatureCollections()` method. If you use the `getFeatureCollections()` pattern you can simply ignore the `dataStore` property in the constructor. Here is an example of the Object returned in the `getFeatureCollections()` callback: + +```js + { + id: "feature-collection-object-1001", + featureLayerCollections: [ + { + featureLayerUrl: "http://...", + featureLayerCollection: { . . . } + } + ] + } + +``` + +The `featureLayerCollection` is equivalent to `featureLayer.toJson()`. Note: the `layer.extend()` callback only indicates that the edits database has been successfully initialized. diff --git a/doc/offlinefeaturesmanager.md b/doc/offlinefeaturesmanager.md index d2151ed2..395c803f 100644 --- a/doc/offlinefeaturesmanager.md +++ b/doc/offlinefeaturesmanager.md @@ -16,8 +16,8 @@ Property | Value | Description `DB_NAME` | "features_store" | Sets the database name. You can instantiate multiple databases within the same application by creating seperate instances of OfflineFeaturesManager. `DB_OBJECTSTORE_NAME` | "features" | Represents an object store that allows access to a set of data in the database. `DB_UID` | "objectid" | IMPORTANT!** This tells the database what id to use as a unique identifier. This depends on how your feature service was created. ArcGIS Online services may use something different such as `GlobalID`. -`ATTACHMENTS_DB_NAME` | "attachments_store" | **New @ v2.7** Sets the attachments database name. -`ATTACHMENTS_DB_OBJECTSTORE_NAME` | "attachments" | **New @ v2.7** Sets the attachments database object store name. +`ATTACHMENTS_DB_NAME` | "attachments_store" | (Added @ v2.7) Sets the attachments database name. +`ATTACHMENTS_DB_OBJECTSTORE_NAME` | "attachments" | (Added @ v2.7) Sets the attachments database object store name. `proxyPath` | null | Default is `null`. If you are using a Feature Service that is not CORS-enabled then you will need to set this path. `attachmentsStore` | null | Default is `null`. If you are using attachments, this property gives you access to the associated database. @@ -42,7 +42,8 @@ Methods | Returns | Description `goOffline()` | nothing | Forces library into an offline state. Any edits applied to extended FeatureLayers during this condition will be stored locally. `goOnline(callback)` | No attachments: `callback( {success: boolean, responses: Object } )`

With attachments: `callback( {success: boolean, responses: uploadedResponses, dbResponses: dbResponses })` | Forces library to return to an online state. If there are pending edits, an attempt will be made to sync them with the remote feature server. Callback function will be called when resync process is done.

Refer to the [How to use the edit library doc](howtouseeditlibrary.md) for addition information on the `results` object. `getOnlineStatus()` | `ONLINE`, `OFFLINE` or `RECONNECTING`| Determines the current state of the manager. Please, note that this library doesn't detect actual browser offline/online condition. You need to use the `offline.min.js` library included in `vendor\offline` directory to detect connection status and connect events to goOffline() and goOnline() methods. See `military-offline.html` sample. -`getFeatureLayerJSONDataStore( callback )` | `callback( boolean, Object)` | **New @ v2.7.1** Returns the feature layer's dataStore Object. +`getFeatureCollections( callback )` | `callback( boolean, Object)` | (Added @ v2.9) Returns and Object that contains the latest `featureLayerCollection` snapshot for each feature layer that is using the library. Each collection is updated automatically by the library when there is an associated `ADD`, `UPDATE` or `DELETE` operation.

This method should be used when working with pre-built Esri widgets such as the `AttributeInspector.` +`getFeatureLayerJSONDataStore( callback )` | `callback( boolean, Object)` | (Added @ v2.7.1) Returns the feature layer's dataStore Object that was created using the `offlineFeaturesManager()` constructor. Offers more control what is provided by `getFeatureCollections()`. `getReadableEdit()` | String | **DEPRECATED** @ v2.5. A string value representing human readable information on pending edits. Use `featureLayer.getAllEditsArray()`. @@ -90,12 +91,12 @@ Methods | Returns | Description --- | --- | --- `applyEdits(` `adds, updates, deletes,` `callback, errback)` | `deferred` | applyEdits() method is replaced by this library. It's behaviour depends upon online state of the manager. You need to pass the same arguments as to the original applyEdits() method and it returns a deferred object, that will be resolved in the same way as the original, as well as the callbacks will be called under the same conditions. This method looks the same as the original to calling code, the only difference is internal. Listen for `EDITS_ENQUEUED` or `EDITS_ENQUEUED_ERROR`. `addAttachment( objectId, formNode,` `callback,errback)` | `deferred` | Adds a single attachment. -`updateAttachment( objectId, attachmentId,` `formNode, callback, errback)` | `deferred` | **New @ v2.7** Updates an existing attachment. +`updateAttachment( objectId, attachmentId,` `formNode, callback, errback)` | `deferred` | (Added @ v2.7) Updates an existing attachment. `deleteAttachments( objectId, attachmentsIds,` `callback, errback)`| `deferred` | Deletes existing attachments as well as attachments that were created while offline. -`getAttachmentsUsage(callback)` | `callback(usageObject,error)` | **New @ v2.7** Returns the approximate size of the attachments database. The usage Object is {sizeBytes: number, attachmentCount: number}. -`resetAttachmentsDatabase( callback)` | `callback(boolean, error)` | **New @ v2.7** Resets the entire attachments database -- use with **caution**. -`convertGraphicLayerToJSON(` `features, updateEndEvent, callback)` | `callback( featureJSON, layerDefJSON)` | Not really needed @ v2.5 when you can store the entire feature layer's JSON using the `dataStore` property in the `OfflineFeatureManager` contructor. Used with offline browser restarts. In order to reconstitute the feature layer and map you'll need to store the featureJSON and layerDefJSON in local storage and then it read back upon an offline restart. The `updateEndEvent` is the Feature Layer's `update-end` event object. The appcache-features.html sample demonstrates this pattern. -`getFeatureDefinition(` `featureLayer, featuresArr` `geometryType, callback)` | `Object` | Used with offline browser restarts. Not really needed @ v2.5 when you can store the entire feature layer's JSON using the `dataStore` property in the `OfflineFeatureManager` contructor. Pass it a FeatureLayer instance, an array of features and specify the Esri geometry type. It will return a FeatureLayer Definition object that can be used to reconstitute a Feature Layer from scratch. The appcache-features.html sample demonstrates this pattern. Go here for more info on the ArcGIS REST API [layerDefinition](http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#//02r30000004v000000), and [Layer](http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#/Layer/02r30000004q000000/). +`getAttachmentsUsage(callback)` | `callback(usageObject,error)` | (Added @ v2.7) Returns the approximate size of the attachments database. The usage Object is {sizeBytes: number, attachmentCount: number}. +`resetAttachmentsDatabase( callback)` | `callback(boolean, error)` | (Added @ v2.7) Resets the entire attachments database -- use with **caution**. +`convertGraphicLayerToJSON(` `features, updateEndEvent, callback)` | `callback( featureJSON, layerDefJSON)` | You can also store the entire feature layer's JSON using the `dataStore` property in the `OfflineFeatureManager` contructor. Used with offline browser restarts. In order to reconstitute the feature layer and map you'll need to store the featureJSON and layerDefJSON in local storage and then it read back upon an offline restart. The `updateEndEvent` is the Feature Layer's `update-end` event object. The appcache-features.html sample demonstrates this pattern. +`getFeatureDefinition(` `featureLayer, featuresArr` `geometryType, callback)` | `Object` | Used with offline browser restarts. You can also store the entire feature layer's JSON using the `dataStore` property in the `OfflineFeatureManager` contructor. Pass it a FeatureLayer instance, an array of features and specify the Esri geometry type. It will return a FeatureLayer Definition object that can be used to reconstitute a Feature Layer from scratch. The appcache-features.html sample demonstrates this pattern. Go here for more info on the ArcGIS REST API [layerDefinition](http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#//02r30000004v000000), and [Layer](http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#/Layer/02r30000004q000000/). `setPhantomLayerGraphics( graphicsArray) ` | nothing | Used with offline browser restarts. Adds the graphics in the `graphicsArray` to the internal phantom graphics layer. This layer is designed to indicate to the user any graphic that has been modified while offline. The appcache-features.html sample demonstrates this pattern. `getPhantomLayerGraphics( callback) ` | `callback( graphicsLayerJSON)` | Used with offline browser restarts. Returns a JSON representation of the internal phantom graphics layer. This layer is designed to indicate to the user any graphic that has been modified while offline. The appcache-features.html sample demonstrates this pattern. `resetDatabase(callback)` | `callback( boolean, error)` | Full edits database reset -- use with **caution**. If some edits weren't successfully sent, then the record will still exist in the database. If you use this function then those pending records will also be deleted. @@ -164,9 +165,9 @@ Constructor | Description Property | Value | Description --- | --- | --- -`dbName` | "attachments_store" | **Updated @ v2.7** Represents a FeatureLayer.add() operation. -`objectStoreName` | "attachments" | **Updated @ v2.7** Represents a FeatureLayer.update() operation. -`TYPE` | "ADD", "UPDATE" or "DELETE" | **New @ v2.7** Specifies the type of operation against an attachment. +`dbName` | "attachments_store" | Represents a FeatureLayer.add() operation. +`objectStoreName` | "attachments" | Represents a FeatureLayer.update() operation. +`TYPE` | "ADD", "UPDATE" or "DELETE" | (Added @ v2.7) Specifies the type of operation against an attachment. ###Public Methods Methods | Returns | Description From 8ae98a45bfe92a4f28489c78a50d51f0240cafdb Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Wed, 27 May 2015 14:19:00 -0600 Subject: [PATCH 12/17] clarification on how to use --- doc/howtouseeditlibrary.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/doc/howtouseeditlibrary.md b/doc/howtouseeditlibrary.md index 4733052d..6270f5b7 100644 --- a/doc/howtouseeditlibrary.md +++ b/doc/howtouseeditlibrary.md @@ -177,6 +177,28 @@ You can then retrieve this data after an offline restart by using the following ``` +If you don't want to deal with creating and managing your own data store when working with offline browser restarts, then here's the pattern for using the built-in `featureLayerCollections`. This pattern is ideal if you are using Esri's pre-built widgets such as `AttributeInspector` and you don't have access to the necessary events for creating and updating the `dataStore`. + +```js + + offlinefeaturesManager.getFeatureCollections(function(success, collection) { + if(success) { + myFeatureLayer = new + FeatureLayer(collection.featureCollections[0].featureLayerCollection),{ + mode: FeatureLayer.MODE_SNAPSHOT, + outFields: ["GlobalID","BSID","ROUTES","STOPNAME"] + }); + + offlineFeaturesManager.extend(myFeatureLayer,function(result, error) { + if(result) { + console.log("Layer has been successfully rebuilt while offline!"); + } + } + } + }); + +``` + **Step 5** Once a layer has been extended the offline library will enable it with new methods. Here are a few examples that include code snippets of how to take advantage of some of the library's methods. You can also use a combination of methods from `editsStore` and `offlineFeaturesManager`. ####offlineFeaturesManager.proxyPath From 29fcc861826191b2024d36f03bf0656ec69e5ff2 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Wed, 27 May 2015 14:19:32 -0600 Subject: [PATCH 13/17] undo stringify. Use Json. --- lib/edit/offlineFeaturesManager.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/edit/offlineFeaturesManager.js b/lib/edit/offlineFeaturesManager.js index f523bbe2..70c170f1 100644 --- a/lib/edit/offlineFeaturesManager.js +++ b/lib/edit/offlineFeaturesManager.js @@ -504,7 +504,7 @@ define([ var featureCollection = { featureLayerUrl: layer.url, - featureLayerCollection: JSON.stringify(layer.toJson()) + featureLayerCollection: layer.toJson() }; // An array of feature collections, of course :-) @@ -524,6 +524,8 @@ define([ if(success){ var count = 0; for(var i = 0; i < result.featureCollections.length; i++) { + + // Update the current feature collection if(result.featureCollections[i].featureLayerUrl === layer.url) { count++; result.featureCollections[i] = featureCollection; @@ -1207,7 +1209,9 @@ define([ * This is an object created automatically by the library and is updated with every ADD, UPDATE and DELETE. * Attachments are handled separately and not part of the feature collection created here. * - * It has the following signature: {id: featureLayerCollections: [{ featureCollection: [Object], featureLayerUrl: String }]} + * It has the following signature: {id: "feature-collection-object-1001", + * featureLayerCollections: [{ featureCollection: [Object], featureLayerUrl: String }]} + * * @param callback */ getFeatureCollections: function(callback){ From f6da0cb5e828940b275047726abe18ac78db5e21 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Wed, 27 May 2015 16:42:09 -0600 Subject: [PATCH 14/17] update comments --- samples/appcache-features.html | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/samples/appcache-features.html b/samples/appcache-features.html index 14c79c57..a600d08d 100644 --- a/samples/appcache-features.html +++ b/samples/appcache-features.html @@ -9,16 +9,20 @@