diff --git a/CHANGELOG.md b/CHANGELOG.md index 8719c9fb..67e796cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # offline-editor-js - Changelog +## Version 3.2.0 - May 11, 2016 + +No breaking changes. + +**Enhancements** +* Added CleanFeatureService.js util for deleting all features in a demo feature service + +**Bug Fixes** +* Closes #461 - proxyPath not respected in OfflineEditAdvanced. Also fixed proxyPath in OfflineEditBasic +* Closes #462 - getNextLowestTempId does not exist in editStorePOLS. +* Closes #463 - sample feature service no longer allows editing. Complete rewrite. + ## Version 3.1.0 - April 21, 2016 No breaking changes. diff --git a/README.md b/README.md index d72809ba..a15567cb 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,11 @@ This project is also available on npm: **[https://www.npmjs.com/package/esri-off This repo contains the following libraries in the `/dist` directory. The use of `basic` in the name indicates intermittent offline-only, and `advanced` indicates the library can be used for both intermittent and full offline. +Reference URLs are provided for developement only. It's recommended to use a CDN or host your own. + Use_Case | Name, Description and gh-pages URL --- | --- -Basic editing | **`offline-edit-basic-min.js`** Simple, lightweight *(14k minimized)* offline editing library that automatically caches adds, updates and deletes when the internet is temporarily interrupted.

[`http://esri.github.io/offline-editor-js/dist/offline-edit-basic-min.js`](http://esri.github.io/offline-editor-js/dist/offline-edit-basic-min.js) +Basic editing | **`offline-edit-basic-min.js`** Simple, lightweight *(15k minimized)* offline editing library that automatically caches adds, updates and deletes when the internet is temporarily interrupted.

[`http://esri.github.io/offline-editor-js/dist/offline-edit-basic-min.js`](http://esri.github.io/offline-editor-js/dist/offline-edit-basic-min.js) Advanced editing | **`offline-edit-advanced-min.js`** Used for intermittent and full offline editing workflows. Also includes limited support for attachments.

[`http://esri.github.io/offline-editor-js/dist/offline-edit-advanced-min.js`](http://esri.github.io/offline-editor-js/dist/offline-edit-advanced-min.js) Basic map tiles | **`offline-tiles-basic-min.js`** Caches map tiles for simple, intermittent-only offline workflows. Use this library with ArcGIS Online Web maps as well as with tiled map services.

[`http://esri.github.io/offline-editor-js/dist/offline-tiles-basic-min.js`](http://esri.github.io/offline-editor-js/dist/offline-tiles-basic-min.js) Advanced map tiles | **`offline-tiles-advanced-min.js`** Used for intermittent and full offline tile caching. Extends any ArcGIS Tiled Map Service. This library should be used in conjunction with an HTML5 Application Cache Manifest coding pattern.

[`http://esri.github.io/offline-editor-js/dist/offline-tiles-advanced-min.js`](http://esri.github.io/offline-editor-js/dist/offline-tiles-advanced-min.js) @@ -83,7 +85,7 @@ Go __[here](https://github.com/Esri/offline-editor-js/wiki/FAQ)__ for answers to ##Dependencies -* [ArcGIS API for JavaScript (v3.12+)](https://developers.arcgis.com/javascript/) +* [ArcGIS API for JavaScript (v3.14+)](https://developers.arcgis.com/javascript/) * [Offline.js](http://github.hubspot.com/offline/docs/welcome/) - it allows detection of the online/offline condition and provides events to hook callbacks on when this condition changes * Node.js required for building the source * [IndexedDBShim](https://github.com/axemclion/IndexedDBShim) - polyfill to simulate indexedDB functionality in browsers/platforms where it is not supported notably older versions desktop Safari and iOS Safari. diff --git a/dist/offline-edit-advanced-min.js b/dist/offline-edit-advanced-min.js index 8c23c456..3d554d7c 100644 --- a/dist/offline-edit-advanced-min.js +++ b/dist/offline-edit-advanced-min.js @@ -123,9 +123,9 @@ this._editStore.deletePhantomGraphic(c,function(a,b){a?g.resolve({success:!0,err if(b.length>0&&(e.forEach(b,function(a){a.hasOwnProperty("infoTemplate")&&delete a.infoTemplate},this),i="&adds="+JSON.stringify(b)),c.length>0&&(e.forEach(c,function(a){a.hasOwnProperty("infoTemplate")&&delete a.infoTemplate},this),j="&updates="+JSON.stringify(c)),d.length>0){var l=d[0].attributes[this.DB_UID] k="&deletes="+l}var m=h+i+j+k a.hasOwnProperty("credential")&&a.credential&&a.credential.hasOwnProperty("token")&&a.credential.token&&(m=m+"&token="+a.credential.token) -var n=new XMLHttpRequest -n.open("POST",a.url+"/applyEdits",!0),n.setRequestHeader("Content-type","application/x-www-form-urlencoded"),n.onload=function(){if(200===n.status&&""!==n.responseText)try{var a=JSON.parse(this.response) -f(a.addResults,a.updateResults,a.deleteResults)}catch(b){g("Unable to parse xhr response",n)}},n.onerror=function(a){g(a)},n.ontimeout=function(){g("xhr timeout error")},n.timeout=this._defaultXhrTimeout,n.send(m)},_parseResponsesArray:function(a){var c=new b,d=0 +var n=this.proxyPath?this.proxyPath+"?"+a.url:a.url,o=new XMLHttpRequest +o.open("POST",n+"/applyEdits",!0),o.setRequestHeader("Content-type","application/x-www-form-urlencoded"),o.onload=function(){if(200===o.status&&""!==o.responseText)try{var a=JSON.parse(this.response) +f(a.addResults,a.updateResults,a.deleteResults)}catch(b){g("Unable to parse xhr response",o)}},o.onerror=function(a){g(a)},o.ontimeout=function(){g("xhr timeout error")},o.timeout=this._defaultXhrTimeout,o.send(m)},_parseResponsesArray:function(a){var c=new b,d=0 for(var e in a)a.hasOwnProperty(e)&&(a[e].addResults.map(function(a){a.success||d++}),a[e].updateResults.map(function(a){a.success||d++}),a[e].deleteResults.map(function(a){a.success||d++})) return d>0?c.resolve(!1):c.resolve(!0),c.promise}})}),"undefined"!=typeof O?O.esri.Edit={}:(O={},O.esri={Edit:{}}),O.esri.Edit.EditStore=function(){"use strict" this._db=null,this._isDBInit=!1,this.dbName="features_store",this.objectStoreName="features",this.objectId="objectid",this.ADD="add",this.UPDATE="update",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="|@|",this.isSupported=function(){return!!window.indexedDB},this.pushEdit=function(a,b,c,d){var e={id:b+"/"+c.attributes[this.objectId],operation:a,layer:b,type:c.geometry.type,graphic:c.toJson()} diff --git a/dist/offline-edit-advanced-src.js b/dist/offline-edit-advanced-src.js index b42ed02b..039e003b 100644 --- a/dist/offline-edit-advanced-src.js +++ b/dist/offline-edit-advanced-src.js @@ -1,4 +1,4 @@ -/*! esri-offline-maps - v3.1.0 - 2016-04-21 +/*! esri-offline-maps - v3.2.0 - 2016-05-12 * Copyright (c) 2016 Environmental Systems Research Institute, Inc. * Apache License*/ // Configure offline/online detection @@ -2056,8 +2056,11 @@ define([ } } + // Respect the proxyPath if one has been set (Added at v3.2.0) + var url = this.proxyPath ? this.proxyPath + "?" + layer.url : layer.url; + var req = new XMLHttpRequest(); - req.open("POST", layer.url + "/applyEdits", true); + req.open("POST", url + "/applyEdits", true); req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); req.onload = function() { @@ -2068,7 +2071,7 @@ define([ callback(obj.addResults, obj.updateResults, obj.deleteResults); } catch(err) { - console.error("EDIT REQUEST REPONSE WAS NOT SUCCESSFUL:", req); + console.error("EDIT REQUEST RESPONSE WAS NOT SUCCESSFUL:", req); errback("Unable to parse xhr response", req); } } @@ -2735,7 +2738,7 @@ O.esri.Edit.EditStore = function () { else { callback(null, "no db"); } - }, + }; /** * Returns all the edits as a single Array via the callback diff --git a/dist/offline-edit-basic-min.js b/dist/offline-edit-basic-min.js index 764596be..819844f4 100644 --- a/dist/offline-edit-basic-min.js +++ b/dist/offline-edit-basic-min.js @@ -30,7 +30,7 @@ case g._editStore.UPDATE:d.operation==g._editStore.ADD&&(c.operation=g._editStor break case g._editStore.DELETE:var h=!0 d.operation==g._editStore.ADD&&a._deleteTemporaryFeature(c,function(a,b){a||(h=!1)}),f.resolve({success:h,graphic:c,operation:e})}else"Id not found"==d?f.resolve({success:!0,graphic:c,operation:e}):f.reject(c)}),f},a._deleteTemporaryFeature=function(b,c){g._editStore["delete"](a.url,b,function(a,b){c(a,b)})},a._getFilesFromForm=function(a){var b=[],c=e.filter(a.elements,function(a){return"file"===a.type}) -return c.forEach(function(a){b.push.apply(b,a.files)},this),b},this._editStore.getNextLowestTempId(a,function(b,c){"success"===c?a._nextTempId=b:a._nextTempId=-1}),a._getNextTempId=function(){return this._nextTempId--},c(f).then(function(a){g._autoOfflineDetect&&(Offline.on("up",function(){g.goOnline(function(a,b){})}),Offline.on("down",function(){g.goOffline()})),d(!0,null)})},goOffline:function(){this._onlineStatus=this.OFFLINE},goOnline:function(a){this._onlineStatus=this.RECONNECTING,this._replayStoredEdits(function(b,c){this._onlineStatus=this.ONLINE,a&&a(b,c)}.bind(this))},getOnlineStatus:function(){return this._onlineStatus},_initializeDB:function(a){var c=new b,d=this._editStore +return c.forEach(function(a){b.push.apply(b,a.files)},this),b},a._getNextTempId=function(){return this._nextTempId--},c(f).then(function(b){b[0].success?(g._editStore.getNextLowestTempId(a,function(b,c){"success"===c?a._nextTempId=b:a._nextTempId=-1}),g._autoOfflineDetect&&(Offline.on("up",function(){g.goOnline(function(a,b){})}),Offline.on("down",function(){g.goOffline()})),d(!0,null)):d(!1,b[0].error)})},goOffline:function(){this._onlineStatus=this.OFFLINE},goOnline:function(a){this._onlineStatus=this.RECONNECTING,this._replayStoredEdits(function(b,c){this._onlineStatus=this.ONLINE,a&&a(b,c)}.bind(this))},getOnlineStatus:function(){return this._onlineStatus},_initializeDB:function(a){var c=new b,d=this._editStore return d.dbName=this.DB_NAME,d.objectStoreName=this.DB_OBJECTSTORE_NAME,d.objectId=this.DB_UID,d.init(function(a,b){a?c.resolve({success:!0,error:null}):c.reject({success:!1,error:null})}),c},_replayStoredEdits:function(a){var b,d={},e=this,f=[],g=[],h=[],i=[],j=[],k=this._featureLayers,l=this._editStore this._editStore.getAllEditsArray(function(n,o){if(n.length>0){j=n for(var p=j.length,q=0;p>q;q++){b=k[j[q].layer],b.__onEditsComplete=b.onEditsComplete,b.onEditsComplete=function(){},f=[],g=[],h=[],i=[] @@ -56,9 +56,9 @@ return i.attributes={},i.attributes[this.DB_UID]=h,this._editStore["delete"](a.u if(b.length>0&&(e.forEach(b,function(a){a.hasOwnProperty("infoTemplate")&&delete a.infoTemplate},this),i="&adds="+JSON.stringify(b)),c.length>0&&(e.forEach(c,function(a){a.hasOwnProperty("infoTemplate")&&delete a.infoTemplate},this),j="&updates="+JSON.stringify(c)),d.length>0){var l=d[0].attributes[this.DB_UID] k="&deletes="+l}var m=h+i+j+k a.hasOwnProperty("credential")&&a.credential&&a.credential.hasOwnProperty("token")&&a.credential.token&&(m=m+"&token="+a.credential.token) -var n=new XMLHttpRequest -n.open("POST",a.url+"/applyEdits",!0),n.setRequestHeader("Content-type","application/x-www-form-urlencoded"),n.onload=function(){if(200===n.status&&""!==n.responseText)try{var a=JSON.parse(this.response) -f(a.addResults,a.updateResults,a.deleteResults)}catch(b){g("Unable to parse xhr response",n)}},n.onerror=function(a){g(a)},n.ontimeout=function(){g("xhr timeout error")},n.timeout=this._defaultXhrTimeout,n.send(m)},_parseResponsesArray:function(a,b){var c=0 +var n=this.proxyPath?this.proxyPath+"?"+a.url:a.url,o=new XMLHttpRequest +o.open("POST",n+"/applyEdits",!0),o.setRequestHeader("Content-type","application/x-www-form-urlencoded"),o.onload=function(){if(200===o.status&&""!==o.responseText)try{var a=JSON.parse(this.response) +f(a.addResults,a.updateResults,a.deleteResults)}catch(b){g("Unable to parse xhr response",o)}},o.onerror=function(a){g(a)},o.ontimeout=function(){g("xhr timeout error")},o.timeout=this._defaultXhrTimeout,o.send(m)},_parseResponsesArray:function(a,b){var c=0 for(var d in a)a.hasOwnProperty(d)&&(a[d].addResults.forEach(function(a){a.success||c++}),a[d].updateResults.forEach(function(a){a.success||c++}),a[d].deleteResults.forEach(function(a){a.success||c++})) b(!(c>0))}})}),"undefined"!=typeof O?O.esri.Edit={}:(O={},O.esri={Edit:{}}),O.esri.Edit.EditStorePOLS=function(){"use strict" this._db=null,this._isDBInit=!1,this.dbName="features_store",this.objectStoreName="features",this.objectId="objectid",this.ADD="add",this.UPDATE="update",this.DELETE="delete",this.FEATURE_LAYER_JSON_ID="feature-layer-object-1001",this.FEATURE_COLLECTION_ID="feature-collection-object-1001",this.isSupported=function(){return!!window.indexedDB},this.pushEdit=function(a,b,c,d){var e={id:b+"/"+c.attributes[this.objectId],operation:a,layer:b,type:c.geometry.type,graphic:c.toJson()} @@ -73,7 +73,13 @@ d.onsuccess=function(){var c=d.result c&&c.id==a?b(!0,c):b(!1,"Id not found")},d.onerror=function(a){b(!1,a)}},this.getAllEditsArray=function(a){var b=[] if(null!==this._db){var c=this.FEATURE_LAYER_JSON_ID,d=this.FEATURE_COLLECTION_ID,e=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor() e.onsuccess=function(e){var f=e.target.result -f&&f.value&&f.value.id?(f.value.id!==c&&f.value.id!==d&&b.push(f.value),f["continue"]()):a(b,"end")}.bind(this),e.onerror=function(b){a(null,b)}}else a(null,"no db")},this.updateExistingEdit=function(a,b,c,d){var e=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName),f=e.get(c.attributes[this.objectId]) +f&&f.value&&f.value.id?(f.value.id!==c&&f.value.id!==d&&b.push(f.value),f["continue"]()):a(b,"end")}.bind(this),e.onerror=function(b){a(null,b)}}else a(null,"no db")},this.getNextLowestTempId=function(a,b){var c=[],d=this +if(null!==this._db){var e=this.FEATURE_LAYER_JSON_ID,f=this.FEATURE_COLLECTION_ID,g=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor() +g.onsuccess=function(g){var h=g.target.result +if(h&&h.value&&h.value.id)h.value.id!==e&&h.value.id!==f&&h.value.layer===a.url&&"add"===h.value.operation&&c.push(h.value.graphic.attributes[d.objectId]),h["continue"]() +else if(0===c.length)b(-1,"success") +else{var i=c.filter(function(a){return!isNaN(a)}),j=Math.min.apply(Math,i) +b(j-1,"success")}}.bind(this),g.onerror=function(a){b(null,a)}}else b(null,"no db")},this.updateExistingEdit=function(a,b,c,d){var e=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName),f=e.get(c.attributes[this.objectId]) f.onsuccess=function(){f.result var g={id:b+"/"+c.attributes[this.objectId],operation:a,layer:b,graphic:c.toJson()},h=e.put(g) h.onsuccess=function(){d(!0)},h.onerror=function(a){d(!1,a)}}.bind(this)},this["delete"]=function(a,b,c){var d=this._db,e=null,f=this,g=a+"/"+b.attributes[this.objectId] diff --git a/dist/offline-edit-basic-src.js b/dist/offline-edit-basic-src.js index 206b5e7d..61a34a24 100644 --- a/dist/offline-edit-basic-src.js +++ b/dist/offline-edit-basic-src.js @@ -1,4 +1,4 @@ -/*! esri-offline-maps - v3.1.0 - 2016-04-21 +/*! esri-offline-maps - v3.2.0 - 2016-05-12 * Copyright (c) 2016 Environmental Systems Research Institute, Inc. * Apache License*/ // Configure offline/online detection @@ -501,21 +501,6 @@ define([ }, this); return files; }; - - // we need to identify ADDs before sending them to the server - // we assign temporary ids (using negative numbers to distinguish them from real ids) - // query the database first to find any existing offline adds, and find the next lowest integer to start with. - this._editStore.getNextLowestTempId(layer, function(value, status){ - if(status === "success"){ - console.log("_nextTempId:", value); - layer._nextTempId = value; - } - else{ - console.log("_nextTempId, not success:", value); - layer._nextTempId = -1; - console.debug(layer._nextTempId); - } - }); layer._getNextTempId = function () { return this._nextTempId--; @@ -524,20 +509,39 @@ define([ // We are currently only passing in a single deferred. all(extendPromises).then(function (r) { - if(self._autoOfflineDetect){ - Offline.on('up', function(){ // jshint ignore:line + if(r[0].success){ - self.goOnline(function(success,error){ // jshint ignore:line - console.log("GOING ONLINE"); - }); + // we need to identify ADDs before sending them to the server + // we assign temporary ids (using negative numbers to distinguish them from real ids) + // query the database first to find any existing offline adds, and find the next lowest integer to start with. + self._editStore.getNextLowestTempId(layer, function(value, status){ + if(status === "success"){ + layer._nextTempId = value; + } + else{ + console.log("Set _nextTempId not found: " + value + ", resetting to -1"); + layer._nextTempId = -1; + } }); - Offline.on('down', function(){ // jshint ignore:line - self.goOffline(); // jshint ignore:line - }); - } + if(self._autoOfflineDetect){ + Offline.on('up', function(){ // jshint ignore:line - callback(true, null); + self.goOnline(function(success,error){ // jshint ignore:line + console.log("GOING ONLINE"); + }); + }); + + Offline.on('down', function(){ // jshint ignore:line + self.goOffline(); // jshint ignore:line + }); + } + + callback(true, null); + } + else { + callback(false, r[0].error); + } }); }, // extend @@ -963,8 +967,11 @@ define([ } } + // Respect the proxyPath if one has been set (Added at v3.2.0) + var url = this.proxyPath ? this.proxyPath + "?" + layer.url : layer.url; + var req = new XMLHttpRequest(); - req.open("POST", layer.url + "/applyEdits", true); + req.open("POST", url + "/applyEdits", true); req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); req.onload = function() { @@ -1196,6 +1203,57 @@ O.esri.Edit.EditStorePOLS = function () { } }; + /* + * Query the database, looking for any existing Add temporary OIDs, and return the nextTempId to be used. + * @param feature - extended layer from offline edit advanced + * @param callback {int, messageString} or {null, messageString} + */ + this.getNextLowestTempId = function (feature, callback) { + var addOIDsArray = [], + self = this; + + if (this._db !== null) { + + var fLayerJSONId = this.FEATURE_LAYER_JSON_ID; + var fCollectionId = this.FEATURE_COLLECTION_ID; + + var transaction = this._db.transaction([this.objectStoreName]) + .objectStore(this.objectStoreName) + .openCursor(); + + transaction.onsuccess = function (event) { + var cursor = event.target.result; + 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 !== fCollectionId) { + if(cursor.value.layer === feature.url && cursor.value.operation === "add"){ // check to make sure the edit is for the feature we are looking for, and that the operation is an add. + addOIDsArray.push(cursor.value.graphic.attributes[self.objectId]); // add the temporary OID to the array + } + } + cursor.continue(); + } + else { + if(addOIDsArray.length === 0){ // if we didn't find anything, + callback(-1, "success"); // we'll start with -1 + } + else{ + var filteredOIDsArray = addOIDsArray.filter(function(val){ // filter out any non numbers from the array... + return !isNaN(val); // .. should anything have snuck in or returned a NaN + }); + var lowestTempId = Math.min.apply(Math, filteredOIDsArray); // then find the lowest number from the array + callback(lowestTempId-1, "success"); // and we'll start with one less than tat. + } + } + }.bind(this); + transaction.onerror = function (err) { + callback(null, err); + }; + } + else { + callback(null, "no db"); + } + }; + /** * Update an edit already exists in the database * @param operation add, update or delete diff --git a/dist/offline-tiles-advanced-src.js b/dist/offline-tiles-advanced-src.js index f47768f7..9e1b1f9b 100644 --- a/dist/offline-tiles-advanced-src.js +++ b/dist/offline-tiles-advanced-src.js @@ -1,4 +1,4 @@ -/*! esri-offline-maps - v3.1.0 - 2016-04-21 +/*! esri-offline-maps - v3.2.0 - 2016-05-12 * Copyright (c) 2016 Environmental Systems Research Institute, Inc. * Apache License*/ define([ diff --git a/dist/offline-tiles-basic-src.js b/dist/offline-tiles-basic-src.js index dc6f9335..d9653d29 100644 --- a/dist/offline-tiles-basic-src.js +++ b/dist/offline-tiles-basic-src.js @@ -1,4 +1,4 @@ -/*! esri-offline-maps - v3.1.0 - 2016-04-21 +/*! esri-offline-maps - v3.2.0 - 2016-05-12 * Copyright (c) 2016 Environmental Systems Research Institute, Inc. * Apache License*/ define([ diff --git a/dist/offline-tpk-src.js b/dist/offline-tpk-src.js index 846d722c..0e38d419 100644 --- a/dist/offline-tpk-src.js +++ b/dist/offline-tpk-src.js @@ -1,4 +1,4 @@ -/*! esri-offline-maps - v3.1.0 - 2016-04-21 +/*! esri-offline-maps - v3.2.0 - 2016-05-12 * Copyright (c) 2016 Environmental Systems Research Institute, Inc. * Apache License*/ /** diff --git a/lib/edit/OfflineEditAdvanced.js b/lib/edit/OfflineEditAdvanced.js index 99529c25..6ff78629 100644 --- a/lib/edit/OfflineEditAdvanced.js +++ b/lib/edit/OfflineEditAdvanced.js @@ -2040,8 +2040,11 @@ define([ } } + // Respect the proxyPath if one has been set (Added at v3.2.0) + var url = this.proxyPath ? this.proxyPath + "?" + layer.url : layer.url; + var req = new XMLHttpRequest(); - req.open("POST", layer.url + "/applyEdits", true); + req.open("POST", url + "/applyEdits", true); req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); req.onload = function() { @@ -2052,7 +2055,7 @@ define([ callback(obj.addResults, obj.updateResults, obj.deleteResults); } catch(err) { - console.error("EDIT REQUEST REPONSE WAS NOT SUCCESSFUL:", req); + console.error("EDIT REQUEST RESPONSE WAS NOT SUCCESSFUL:", req); errback("Unable to parse xhr response", req); } } diff --git a/lib/edit/OfflineEditBasic.js b/lib/edit/OfflineEditBasic.js index b264dc27..1b5dd0be 100644 --- a/lib/edit/OfflineEditBasic.js +++ b/lib/edit/OfflineEditBasic.js @@ -485,21 +485,6 @@ define([ }, this); return files; }; - - // we need to identify ADDs before sending them to the server - // we assign temporary ids (using negative numbers to distinguish them from real ids) - // query the database first to find any existing offline adds, and find the next lowest integer to start with. - this._editStore.getNextLowestTempId(layer, function(value, status){ - if(status === "success"){ - console.log("_nextTempId:", value); - layer._nextTempId = value; - } - else{ - console.log("_nextTempId, not success:", value); - layer._nextTempId = -1; - console.debug(layer._nextTempId); - } - }); layer._getNextTempId = function () { return this._nextTempId--; @@ -508,20 +493,39 @@ define([ // We are currently only passing in a single deferred. all(extendPromises).then(function (r) { - if(self._autoOfflineDetect){ - Offline.on('up', function(){ // jshint ignore:line + if(r[0].success){ - self.goOnline(function(success,error){ // jshint ignore:line - console.log("GOING ONLINE"); - }); + // we need to identify ADDs before sending them to the server + // we assign temporary ids (using negative numbers to distinguish them from real ids) + // query the database first to find any existing offline adds, and find the next lowest integer to start with. + self._editStore.getNextLowestTempId(layer, function(value, status){ + if(status === "success"){ + layer._nextTempId = value; + } + else{ + console.log("Set _nextTempId not found: " + value + ", resetting to -1"); + layer._nextTempId = -1; + } }); - Offline.on('down', function(){ // jshint ignore:line - self.goOffline(); // jshint ignore:line - }); - } + if(self._autoOfflineDetect){ + Offline.on('up', function(){ // jshint ignore:line + + self.goOnline(function(success,error){ // jshint ignore:line + console.log("GOING ONLINE"); + }); + }); + + Offline.on('down', function(){ // jshint ignore:line + self.goOffline(); // jshint ignore:line + }); + } - callback(true, null); + callback(true, null); + } + else { + callback(false, r[0].error); + } }); }, // extend @@ -947,8 +951,11 @@ define([ } } + // Respect the proxyPath if one has been set (Added at v3.2.0) + var url = this.proxyPath ? this.proxyPath + "?" + layer.url : layer.url; + var req = new XMLHttpRequest(); - req.open("POST", layer.url + "/applyEdits", true); + req.open("POST", url + "/applyEdits", true); req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); req.onload = function() { diff --git a/lib/edit/editStorePOLS.js b/lib/edit/editStorePOLS.js index f0b48c99..4f044452 100644 --- a/lib/edit/editStorePOLS.js +++ b/lib/edit/editStorePOLS.js @@ -147,6 +147,57 @@ O.esri.Edit.EditStorePOLS = function () { } }; + /* + * Query the database, looking for any existing Add temporary OIDs, and return the nextTempId to be used. + * @param feature - extended layer from offline edit advanced + * @param callback {int, messageString} or {null, messageString} + */ + this.getNextLowestTempId = function (feature, callback) { + var addOIDsArray = [], + self = this; + + if (this._db !== null) { + + var fLayerJSONId = this.FEATURE_LAYER_JSON_ID; + var fCollectionId = this.FEATURE_COLLECTION_ID; + + var transaction = this._db.transaction([this.objectStoreName]) + .objectStore(this.objectStoreName) + .openCursor(); + + transaction.onsuccess = function (event) { + var cursor = event.target.result; + 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 !== fCollectionId) { + if(cursor.value.layer === feature.url && cursor.value.operation === "add"){ // check to make sure the edit is for the feature we are looking for, and that the operation is an add. + addOIDsArray.push(cursor.value.graphic.attributes[self.objectId]); // add the temporary OID to the array + } + } + cursor.continue(); + } + else { + if(addOIDsArray.length === 0){ // if we didn't find anything, + callback(-1, "success"); // we'll start with -1 + } + else{ + var filteredOIDsArray = addOIDsArray.filter(function(val){ // filter out any non numbers from the array... + return !isNaN(val); // .. should anything have snuck in or returned a NaN + }); + var lowestTempId = Math.min.apply(Math, filteredOIDsArray); // then find the lowest number from the array + callback(lowestTempId-1, "success"); // and we'll start with one less than tat. + } + } + }.bind(this); + transaction.onerror = function (err) { + callback(null, err); + }; + } + else { + callback(null, "no db"); + } + }; + /** * Update an edit already exists in the database * @param operation add, update or delete diff --git a/lib/edit/editsStore.js b/lib/edit/editsStore.js index 8a5440ae..86ffb03c 100644 --- a/lib/edit/editsStore.js +++ b/lib/edit/editsStore.js @@ -590,7 +590,7 @@ O.esri.Edit.EditStore = function () { else { callback(null, "no db"); } - }, + }; /** * Returns all the edits as a single Array via the callback diff --git a/package.json b/package.json index aff2d1ab..4cf9c3dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "esri-offline-maps", - "version": "3.1.0", + "version": "3.2.0", "description": "Lightweight set of libraries for working offline with map tiles and editing with ArcGIS feature services", "author": "Andy Gup (http://blog.andygup.net)", "license": "Apache 2.0", diff --git a/samples/lib/CleanFeatureService.js b/samples/lib/CleanFeatureService.js new file mode 100644 index 00000000..25921e4d --- /dev/null +++ b/samples/lib/CleanFeatureService.js @@ -0,0 +1,36 @@ +"use strict"; + +/* + * Utility library for deleting all features in a feature layer. + * Use this to reset demo feature layers. + * WARNING: this will delete EVERYTHING! + */ + +function CleanFeatureLayer(featureLayer, callback) +{ + require(["esri/request"], function (esriRequest) { + esriRequest({ + url: featureLayer.url + "/deleteFeatures", + content: { f: 'json', where: '1=1'}, + handleAs: 'json' + },{usePost:true}).then( function(response) + { + callback && callback(true,response); + }, + function(error) + { + callback && callback(false,error); + }); + }); +} + +function InitCleanFeatureLayer(featureLayer){ + + CleanFeatureLayer(featureLayer, function(success){ + CleanFeatureLayer( featureLayer, function(success, response) + { + console.log("FeatureLayer cleaned: " + success); + featureLayer.refresh(); + }); + }); +} diff --git a/samples/package.json b/samples/package.json index 59da49be..bf6a96df 100644 --- a/samples/package.json +++ b/samples/package.json @@ -9,7 +9,7 @@ "appHomePage": "appcache-tiles.html", "optimizedApiURL": "../samples/jsolib", "arcGISBaseURL": "http://js.arcgis.com/3.14", - "version": "3.1.0", + "version": "3.2.0", "private": true, "description": "manifest generator project", "repository": { diff --git a/samples/simple-edit.html b/samples/simple-edit.html index b25ada4a..6523f578 100644 --- a/samples/simple-edit.html +++ b/samples/simple-edit.html @@ -2,129 +2,199 @@ - + + - Update Fire Perimeter + Simple Edit - - + + - - - + + + + +
+ +
+
+ + - - -
- -
-
+ var graphic = new Graphic(evt.geometry, symbol, {"name":"test2"}); + map.graphics.add(graphic); + + applyEdits(graphic); + } + + function applyEdits(graphic) { + lineFeatureLayer.applyEdits([graphic], null, null,function(addResults) + { + console.log("addResults length: " + addResults.length); + }, + function(error) + { + console.log("SetupFeatureService error: " + error.message); + }); + } + }); + \ No newline at end of file diff --git a/test/spec/offlineEditingBasicSpec.js b/test/spec/offlineEditingBasicSpec.js index 5d9181f8..5f76bbb4 100644 --- a/test/spec/offlineEditingBasicSpec.js +++ b/test/spec/offlineEditingBasicSpec.js @@ -79,6 +79,7 @@ describe("Normal online editing - Exercise the feature services", function() async.it("add test features", function(done) { expect(g_featureLayers[0].graphics.length).toBe(0); + expect(g_featureLayers[0]._nextTempId).toBe(-1); g1 = new g_modules.Graphic({"geometry":{"x":-105400,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"lat":0.0,"lng":0.0,"description":"g1"}}); g2 = new g_modules.Graphic({"geometry":{"x":-105600,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"lat":0.0,"lng":0.0,"description":"g2"}});