diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b397a57..80a2f19c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # offline-editor-js - Changelog +## Version 3.0.6 - March 30, 2016 + +No breaking changes + +**Bug Fixes** +* Closes #448 - OfflineEditAdvanced - after multiple offline restarts, UID begins at -1 again for Adds. + +**Enhancements** +* Closes #451 - OfflineTiles - Option for offline_id_manager localStorage key name +* Updates to howtousetiles.md documentation for the offlineIdManager parameter. + ## Version 3.0.5 - Feb 2, 2016 No breaking changes. Documentation updates only. diff --git a/Gruntfile.js b/Gruntfile.js index c906e075..505a392e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -117,7 +117,7 @@ module.exports = function(grunt) { beautify: { semicolons: false //Required: prevents dojo parser errors w/ minified files in this project }, - preserveComments: 'some', + preserveComments: /^!/, wrap: false // mangle: { // except: ['O'] diff --git a/dist/offline-edit-advanced-min.js b/dist/offline-edit-advanced-min.js index 68baa293..219753bc 100644 --- a/dist/offline-edit-advanced-min.js +++ b/dist/offline-edit-advanced-min.js @@ -1,5 +1,5 @@ -/*! esri-offline-maps - v3.0.3 - 2015-11-30 -* Copyright (c) 2015 Environmental Systems Research Institute, Inc. +/*! esri-offline-maps - v3.0.6 - 2016-03-30 +* Copyright (c) 2016 Environmental Systems Research Institute, Inc. * Apache License*/ Offline.options={checks:{image:{url:function(){return"http://esri.github.io/offline-editor-js/tiny-image.png?_="+Math.floor(1e9*Math.random())}},active:"image"}},define(["dojo/Evented","dojo/_base/Deferred","dojo/promise/all","dojo/_base/declare","dojo/_base/array","dojo/dom-attr","dojo/dom-style","dojo/query","esri/config","esri/layers/GraphicsLayer","esri/graphic","esri/request","esri/symbols/SimpleMarkerSymbol","esri/symbols/SimpleLineSymbol","esri/symbols/SimpleFillSymbol","esri/urlUtils"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p){"use strict" return d("O.esri.Edit.OfflineEditAdvanced",[a],{_onlineStatus:"online",_featureLayers:{},_featureCollectionUsageFlag:!1,_editStore:new O.esri.Edit.EditStore,_defaultXhrTimeout:15e3,ONLINE:"online",OFFLINE:"offline",RECONNECTING:"reconnecting",attachmentsStore:null,proxyPath:null,ENABLE_FEATURECOLLECTION:!1,DB_NAME:"features_store",DB_OBJECTSTORE_NAME:"features",DB_UID:"objectid",ATTACHMENTS_DB_NAME:"attachments_store",ATTACHMENTS_DB_OBJECTSTORE_NAME:"attachments",events:{EDITS_SENT:"edits-sent",EDITS_ENQUEUED:"edits-enqueued",EDITS_ENQUEUED_ERROR:"edits-enqueued-error",EDITS_SENT_ERROR:"edits-sent-error",ALL_EDITS_SENT:"all-edits-sent",ATTACHMENT_ENQUEUED:"attachment-enqueued",ATTACHMENTS_SENT:"attachments-sent"},initAttachments:function(a){if(a=a||function(a){},!this._checkFileAPIs())return a(!1,"File APIs not supported") @@ -82,7 +82,7 @@ return n._editStore.deletePhantomGraphic(h,function(b){b?a.resolve(!0):a.resolve c([f(),g()]).then(function(a){e(a)})},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},a._replaceFeatureIds=function(a,b,c){a.length||c(0) var d,e=a.length,f=e,g=0 -for(d=0;e>d;d++)n.attachmentsStore.replaceFeatureId(this.url,a[d],b[d],function(a){--f,g+=a?1:0,0===f&&c(g)}.bind(this))},a._nextTempId=-1,a._getNextTempId=function(){return this._nextTempId--},l(),c(m).then(function(b){0===b.length&&o?this.ENABLE_FEATURECOLLECTION?a._pushFeatureCollections(function(a){a?d(!0,null):d(!1,null)}):d(!0,null):b[0].success&&!o?this._editStore.getFeatureLayerJSON(function(b,c){b?(this._featureLayers[c.__featureLayerURL]=a,a.url=c.__featureLayerURL,this.ENABLE_FEATURECOLLECTION?a._pushFeatureCollections(function(a){a?d(!0,null):d(!1,null)}):d(!0,null)):d(!1,c)}.bind(this)):b[0].success&&(this.ENABLE_FEATURECOLLECTION?a._pushFeatureCollections(function(a){a?d(!0,null):d(!1,null)}):d(!0,null))}.bind(this))},goOffline:function(){this._onlineStatus=this.OFFLINE},goOnline:function(a){this._onlineStatus=this.RECONNECTING,this._replayStoredEdits(function(b,c){var d={success:b,responses:c} +for(d=0;e>d;d++)n.attachmentsStore.replaceFeatureId(this.url,a[d],b[d],function(a){--f,g+=a?1:0,0===f&&c(g)}.bind(this))},this._editStore.getNextLowestTempId(a,function(b,c){"success"===c?a._nextTempId=b:a._nextTempId=-1}),a._getNextTempId=function(){return this._nextTempId--},l(),c(m).then(function(b){0===b.length&&o?this.ENABLE_FEATURECOLLECTION?a._pushFeatureCollections(function(a){a?d(!0,null):d(!1,null)}):d(!0,null):b[0].success&&!o?this._editStore.getFeatureLayerJSON(function(b,c){b?(this._featureLayers[c.__featureLayerURL]=a,a.url=c.__featureLayerURL,this.ENABLE_FEATURECOLLECTION?a._pushFeatureCollections(function(a){a?d(!0,null):d(!1,null)}):d(!0,null)):d(!1,c)}.bind(this)):b[0].success&&(this.ENABLE_FEATURECOLLECTION?a._pushFeatureCollections(function(a){a?d(!0,null):d(!1,null)}):d(!0,null))}.bind(this))},goOffline:function(){this._onlineStatus=this.OFFLINE},goOnline:function(a){this._onlineStatus=this.RECONNECTING,this._replayStoredEdits(function(b,c){var d={success:b,responses:c} this._onlineStatus=this.ONLINE,null!=this.attachmentsStore?this._sendStoredAttachments(function(b,c,e){d.attachments={success:b,responses:c,dbResponses:e},a&&a(d)}.bind(this)):a&&a(d)}.bind(this))},getOnlineStatus:function(){return this._onlineStatus},serializeFeatureGraphicsArray:function(a,b){for(var c=a.length,d=[],e=0;c>e;e++){var f=a[e].toJson() if(d.push(f),e==c-1){var g=JSON.stringify(d) b(g) @@ -131,7 +131,7 @@ n.open("POST",a.url+"/applyEdits",!0),n.setRequestHeader("Content-type","applica 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 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?!0:!1},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()} +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()} if("undefined"==typeof c.attributes[this.objectId])d(!1,"editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineEdit.DB_UID? "+JSON.stringify(c.attributes)) else{var f=this._db.transaction([this.objectStoreName],"readwrite") f.oncomplete=function(a){d(!0)},f.onerror=function(a){d(!1,a.target.error.message)} @@ -169,7 +169,13 @@ var d=c.get(a) 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.getAllEdits=function(a){if(null!==this._db){var b=this.FEATURE_LAYER_JSON_ID,c=this.FEATURE_COLLECTION_ID,d=this.PHANTOM_GRAPHIC_PREFIX,e=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor() e.onsuccess=function(e){var f=e.target.result -f&&f.hasOwnProperty("value")&&f.value.hasOwnProperty("id")?(f.value.id!==b&&f.value.id!==c&&-1==f.value.id.indexOf(d)&&a(f.value,null),f["continue"]()):a(null,"end")}.bind(this),e.onerror=function(b){a(null,b)}}else a(null,"no db")},this.getAllEditsArray=function(a){var b=[] +f&&f.hasOwnProperty("value")&&f.value.hasOwnProperty("id")?(f.value.id!==b&&f.value.id!==c&&-1==f.value.id.indexOf(d)&&a(f.value,null),f["continue"]()):a(null,"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.PHANTOM_GRAPHIC_PREFIX,h=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor() +h.onsuccess=function(h){var i=h.target.result +if(i&&i.value&&i.value.id)i.value.id!==e&&i.value.id!==f&&-1==i.value.id.indexOf(g)&&i.value.layer===a.url&&"add"===i.value.operation&&c.push(i.value.graphic.attributes[d.objectId]),i["continue"]() +else if(0===c.length)b(-1,"success") +else{var j=c.filter(function(a){return!isNaN(a)}),k=Math.min.apply(Math,j) +b(k-1,"success")}}.bind(this),h.onerror=function(a){b(null,a)}}else b(null,"no db")},this.getAllEditsArray=function(a){var b=[] if(null!==this._db){var c=this.FEATURE_LAYER_JSON_ID,d=this.FEATURE_COLLECTION_ID,e=this.PHANTOM_GRAPHIC_PREFIX,f=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor() f.onsuccess=function(f){var g=f.target.result g&&g.value&&g.value.id?(g.value.id!==c&&g.value.id!==d&&-1==g.value.id.indexOf(e)&&b.push(g.value),g["continue"]()):a(b,"end")}.bind(this),f.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]) @@ -196,7 +202,7 @@ c.onsuccess=function(){var b=c.result "undefined"!=typeof b?a(!0,b):a(!1,null)},c.onerror=function(b){a(!1,b)}},this.init=function(a){var b=indexedDB.open(this.dbName,11) a=a||function(a){}.bind(this),b.onerror=function(b){a(!1,b.target.errorCode)}.bind(this),b.onupgradeneeded=function(a){var b=a.target.result b.objectStoreNames.contains(this.objectStoreName)&&b.deleteObjectStore(this.objectStoreName),b.createObjectStore(this.objectStoreName,{keyPath:"id"})}.bind(this),b.onsuccess=function(b){this._db=b.target.result,this._isDBInit=!0,a(!0,null)}.bind(this)}},O.esri.Edit.AttachmentsStore=function(){"use strict" -this._db=null,this.dbName="attachments_store",this.objectStoreName="attachments",this.TYPE={ADD:"add",UPDATE:"update",DELETE:"delete"},this.isSupported=function(){return window.indexedDB?!0:!1},this.store=function(a,b,c,d,e,f){try{e==this.TYPE.ADD||e==this.TYPE.UPDATE||e==this.TYPE.DELETE?this._readFile(d,function(g,h){if(g){var i={id:b,objectId:c,type:e,featureId:a+"/"+c,contentType:d.type,name:d.name,size:d.size,featureLayerUrl:a,content:h,file:d},j=this._db.transaction([this.objectStoreName],"readwrite") +this._db=null,this.dbName="attachments_store",this.objectStoreName="attachments",this.TYPE={ADD:"add",UPDATE:"update",DELETE:"delete"},this.isSupported=function(){return!!window.indexedDB},this.store=function(a,b,c,d,e,f){try{e==this.TYPE.ADD||e==this.TYPE.UPDATE||e==this.TYPE.DELETE?this._readFile(d,function(g,h){if(g){var i={id:b,objectId:c,type:e,featureId:a+"/"+c,contentType:d.type,name:d.name,size:d.size,featureLayerUrl:a,content:h,file:d},j=this._db.transaction([this.objectStoreName],"readwrite") j.oncomplete=function(a){f(!0,i)},j.onerror=function(a){f(!1,a.target.error.message)} try{j.objectStore(this.objectStoreName).put(i)}catch(k){f(!1,k)}}else f(!1,h)}.bind(this)):f(!1,"attachmentsStore.store() Invalid type in the constructor!")}catch(g){f(!1,g.stack)}},this.retrieve=function(a,b){var c=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName),d=c.get(a) d.onsuccess=function(a){var c=a.target.result diff --git a/dist/offline-edit-advanced-src.js b/dist/offline-edit-advanced-src.js index db393422..1792708c 100644 --- a/dist/offline-edit-advanced-src.js +++ b/dist/offline-edit-advanced-src.js @@ -1,5 +1,5 @@ -/*! esri-offline-maps - v3.0.3 - 2015-11-30 -* Copyright (c) 2015 Environmental Systems Research Institute, Inc. +/*! esri-offline-maps - v3.0.6 - 2016-03-30 +* Copyright (c) 2016 Environmental Systems Research Institute, Inc. * Apache License*/ // Configure offline/online detection // Requires: http://github.hubspot.com/offline/docs/welcome/ @@ -1166,7 +1166,19 @@ define([ // we need to identify ADDs before sending them to the server // we assign temporary ids (using negative numbers to distinguish them from real ids) - layer._nextTempId = -1; + // 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--; }; @@ -2673,6 +2685,58 @@ O.esri.Edit.EditStore = 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 phantomGraphicPrefix = this.PHANTOM_GRAPHIC_PREFIX; + + 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 && cursor.value.id.indexOf(phantomGraphicPrefix) == -1) { + 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"); + } + }, + /** * Returns all the edits as a single Array via the callback * @param callback {array, messageString} or {null, messageString} diff --git a/dist/offline-edit-basic-min.js b/dist/offline-edit-basic-min.js index 1a134316..6ecb206a 100644 --- a/dist/offline-edit-basic-min.js +++ b/dist/offline-edit-basic-min.js @@ -1,5 +1,5 @@ -/*! esri-offline-maps - v3.0.3 - 2015-11-30 -* Copyright (c) 2015 Environmental Systems Research Institute, Inc. +/*! esri-offline-maps - v3.0.6 - 2016-03-30 +* Copyright (c) 2016 Environmental Systems Research Institute, Inc. * Apache License*/ Offline.options={checks:{image:{url:function(){return"http://esri.github.io/offline-editor-js/tiny-image.png?_="+Math.floor(1e9*Math.random())}},active:"image"}},define(["dojo/Evented","dojo/_base/Deferred","dojo/promise/all","dojo/_base/declare","dojo/_base/array","dojo/dom-attr","dojo/dom-style","dojo/query","dojo/on","esri/config","esri/layers/GraphicsLayer","esri/layers/FeatureLayer","esri/graphic"],function(a,b,c,d,e,f,g,h,i,j,k,l,m){"use strict" return d("O.esri.Edit.OfflineEditBasic",[a],{_onlineStatus:"online",_featureLayers:{},_editStore:new O.esri.Edit.EditStorePOLS,_defaultXhrTimeout:15e3,_autoOfflineDetect:!0,ONLINE:"online",OFFLINE:"offline",RECONNECTING:"reconnecting",proxyPath:null,DB_NAME:"features_store",DB_OBJECTSTORE_NAME:"features",DB_UID:"objectid",events:{EDITS_SENT:"edits-sent",EDITS_ENQUEUED:"edits-enqueued",EDITS_ENQUEUED_ERROR:"edits-enqueued-error"},constructor:function(a){a&&a.hasOwnProperty("autoDetect")&&(this._autoOfflineDetect=a.autoDetect)},extend:function(a,d){var f=[],g=this @@ -33,7 +33,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},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},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 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=[] @@ -63,8 +63,8 @@ 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 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?!1:!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?!0:!1},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()} +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()} if("undefined"==typeof c.attributes[this.objectId])d(!1,"editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineEdit.DB_UID? "+JSON.stringify(c.attributes)) else{var f=this._db.transaction([this.objectStoreName],"readwrite") f.oncomplete=function(a){d(!0)},f.onerror=function(a){d(!1,a.target.error.message)} diff --git a/dist/offline-edit-basic-src.js b/dist/offline-edit-basic-src.js index ab98a754..0d9aca04 100644 --- a/dist/offline-edit-basic-src.js +++ b/dist/offline-edit-basic-src.js @@ -1,5 +1,5 @@ -/*! esri-offline-maps - v3.0.3 - 2015-11-30 -* Copyright (c) 2015 Environmental Systems Research Institute, Inc. +/*! esri-offline-maps - v3.0.6 - 2016-03-30 +* Copyright (c) 2016 Environmental Systems Research Institute, Inc. * Apache License*/ // Configure offline/online detection // Requires: http://github.hubspot.com/offline/docs/welcome/ @@ -504,7 +504,19 @@ define([ // we need to identify ADDs before sending them to the server // we assign temporary ids (using negative numbers to distinguish them from real ids) - layer._nextTempId = -1; + // 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--; }; diff --git a/dist/offline-tiles-advanced-min.js b/dist/offline-tiles-advanced-min.js index 35aa0183..e917efe2 100644 --- a/dist/offline-tiles-advanced-min.js +++ b/dist/offline-tiles-advanced-min.js @@ -1,11 +1,11 @@ -/*! esri-offline-maps - v3.0.3 - 2015-11-30 -* Copyright (c) 2015 Environmental Systems Research Institute, Inc. +/*! esri-offline-maps - v3.0.6 - 2016-03-30 +* Copyright (c) 2016 Environmental Systems Research Institute, Inc. * Apache License*/ define(["dojo/query","dojo/request","dojo/_base/declare","esri/layers/LOD","esri/geometry/Point","esri/geometry/Extent","esri/layers/TileInfo","esri/SpatialReference","esri/geometry/Polygon","esri/layers/TiledMapServiceLayer"],function(a,b,c,d,e,f,g,h,i,j){"use strict" -return c("O.esri.Tiles.OfflineTilesAdvanced",[j],{tileInfo:null,_imageType:"",_level:null,_minZoom:null,_maxZoom:null,_tilesCore:null,_secure:!1,constructor:function(a,b,c,d){this._isLocalStorage()===!1?(alert("OfflineTiles Library not supported on this browser."),b(!1)):window.localStorage.offline_id_manager="",void 0===d||null===d?(this.DB_NAME="offline_tile_store",this.DB_OBJECTSTORE_NAME="tilepath"):(this.DB_NAME=d.dbName,this.DB_OBJECTSTORE_NAME=d.objectStoreName),this._tilesCore=new O.esri.Tiles.TilesCore,Array.prototype.sortNumber=function(){return this.sort(function(a,b){return a-b})},this._self=this,this._lastTileUrl="",this._imageType="",this._getTileUrl=this.getTileUrl +return c("O.esri.Tiles.OfflineTilesAdvanced",[j],{tileInfo:null,_imageType:"",_level:null,_minZoom:null,_maxZoom:null,_tilesCore:null,_secure:!1,constructor:function(a,b,c,d){this._isLocalStorage()===!1&&(alert("OfflineTiles Library not supported on this browser."),b(!1)),void 0===d||null===d?(this.DB_NAME="offline_tile_store",this.DB_OBJECTSTORE_NAME="tilepath",this.offline_id_manager="offline_id_manager"):(this.DB_NAME=d.dbName,this.DB_OBJECTSTORE_NAME=d.objectStoreName,void 0===d.offlineIdManager||null===d.offlineIdManger?this.offline_id_manager="offline_id_manager":this.offline_id_manager=d.offlineIdManager),this._tilesCore=new O.esri.Tiles.TilesCore,Array.prototype.sortNumber=function(){return this.sort(function(a,b){return a-b})},this._self=this,this._lastTileUrl="",this._imageType="",this._getTileUrl=this.getTileUrl var e=!0 -return("undefined"!=typeof c||null!=c)&&(e=c),this.showBlankTiles=!0,this.offline={online:e,store:new O.esri.Tiles.TilesStore,proxyPath:null},this.offline.store.isSupported()?(this.offline.store.dbName=this.DB_NAME,this.offline.store.objectStoreName=this.DB_OBJECTSTORE_NAME,this.offline.store.init(function(c){c&&this._getTileInfoPrivate(a,function(a){b(a)})}.bind(this._self)),void 0):b(!1,"indexedDB not supported")},getTileUrl:function(b,c,d){this._level=b -var e,f=this,g=window.localStorage.offline_id_manager +return"undefined"==typeof c&&null==c||(e=c),this.showBlankTiles=!0,this.offline={online:e,store:new O.esri.Tiles.TilesStore,proxyPath:null},this.offline.store.isSupported()?(this.offline.store.dbName=this.DB_NAME,this.offline.store.objectStoreName=this.DB_OBJECTSTORE_NAME,this.offline.store.init(function(c){c&&this._getTileInfoPrivate(a,function(a){b(a)})}.bind(this._self)),void 0):b(!1,"indexedDB not supported")},getTileUrl:function(b,c,d){this._level=b +var e,f=this,g=window.localStorage[this.offline_id_manager] if(void 0===g||""===g)e="" else{var h=JSON.parse(g) h.credentials.forEach(function(a){-1!==f.url.indexOf(a.server)&&(e="?token="+a.token)})}var i=this.url+"/tile/"+b+"/"+c+"/"+d+e @@ -17,18 +17,18 @@ return a.getLayer(b)},getLevelEstimation:function(a,b,c){var d=new O.esri.Tiles. return f},getLevel:function(){return this._level},getMaxZoom:function(a){null==this._maxZoom&&(this._maxZoom=this.tileInfo.lods[this.tileInfo.lods.length-1].level),a(this._maxZoom)},getMinZoom:function(a){null==this._minZoom&&(this._minZoom=this.tileInfo.lods[0].level),a(this._minZoom)},getMinMaxLOD:function(a,b){var c={},d=this.getMap(),e=d.getLevel()-Math.abs(a),f=d.getLevel()+b return null!=this._maxZoom&&null!=this._minZoom?(c.max=Math.min(this._maxZoom,f),c.min=Math.max(this._minZoom,e)):(this.getMinZoom(function(a){c.min=Math.max(a,e)}),this.getMaxZoom(function(a){c.max=Math.min(a,f)})),c},prepareForOffline:function(a,b,c,d){this._tilesCore._createCellsForOffline(this,a,b,c,function(a){this._doNextTile(0,a,d)}.bind(this))},goOffline:function(){this.offline.online=!1},goOnline:function(){this.offline.online=!0,this.refresh()},isOnline:function(){return this.offline.online},deleteAllTiles:function(a){var b=this.offline.store b.deleteAll(a)},getOfflineUsage:function(a){var b=this.offline.store -b.usedSpace(a)},getTilePolygons:function(a){this._tilesCore._getTilePolygons(this.offline.store,this.url,this,a)},saveToFile:function(a,b){this._tilesCore._saveToFile(a,this.offline.store,b)},loadFromFile:function(a,b){this._tilesCore._loadFromFile(a,this.offline.store,b)},estimateTileSize:function(a){this._tilesCore._estimateTileSize(b,this._lastTileUrl,this.offline.proxyPath,a)},getExtentBuffer:function(a,b){return b.xmin-=a,b.ymin-=a,b.xmax+=a,b.ymax+=a,b},getTileUrlsByExtent:function(a,b){var c=new O.esri.Tiles.TilingScheme(this),d=c.getAllCellIdsInExtent(a,b),e=[] +b.usedSpace(a)},getTilePolygons:function(a){this._tilesCore._getTilePolygons(this.offline.store,this.url,this,a)},saveToFile:function(a,b){this._tilesCore._saveToFile(a,this.offline.store,b)},loadFromFile:function(a,b){this._tilesCore._loadFromFile(a,this.offline.store,b)},estimateTileSize:function(a){this._tilesCore._estimateTileSize(b,this._lastTileUrl,this.offline.proxyPath,this.offline_id_manager,a)},getExtentBuffer:function(a,b){return b.xmin-=a,b.ymin-=a,b.xmax+=a,b.ymax+=a,b},getTileUrlsByExtent:function(a,b){var c=new O.esri.Tiles.TilingScheme(this),d=c.getAllCellIdsInExtent(a,b),e=[] return d.forEach(function(a){e.push(this.url+"/"+b+"/"+a[1]+"/"+a[0])}.bind(this)),e},_doNextTile:function(a,b,c){var d=b[a],e=this._getTileUrl(d.level,d.row,d.col) this._tilesCore._storeTile(e,this.offline.proxyPath,this.offline.store,function(e,f){e||(f={cell:d,msg:f}) var g=c({countNow:a,countMax:b.length,cell:d,error:f,finishedDownloading:!1}) g||a===b.length-1?c({finishedDownloading:!0,cancelRequested:g}):this._doNextTile(a+1,b,c)}.bind(this))},_isLocalStorage:function(){var a="test" -try{return localStorage.setItem(a,a),localStorage.removeItem(a),!0}catch(b){return!1}},_parseTileInfo:function(a,b,c){b.offline.online===!1&&a===!1&&void 0!==localStorage.__offlineTileInfo?a=localStorage.__offlineTileInfo:b.offline.online===!1&&a===!1&&void 0===localStorage.__offlineTileInfo&&alert("There was a problem retrieving tiled map info in OfflineTilesEnablerLayer."),b._tilesCore._parseGetTileInfo(a,function(a){b.layerInfos=a.resultObj.layers,b.minScale=a.resultObj.minScale,b.maxScale=a.resultObj.maxScale,b.tileInfo=a.tileInfo,b._imageType=b.tileInfo.format.toLowerCase(),b.fullExtent=a.fullExtent,b.spatialReference=b.tileInfo.spatialReference,b.initialExtent=a.initExtent,b.loaded=!0,b.onLoad(b),c(!0)})},_getTileInfoPrivate:function(a,b){var c,d=this,e=new XMLHttpRequest,f=window.localStorage.offline_id_manager +try{return localStorage.setItem(a,a),localStorage.removeItem(a),!0}catch(b){return!1}},_parseTileInfo:function(a,b,c){b.offline.online===!1&&a===!1&&void 0!==localStorage.__offlineTileInfo?a=localStorage.__offlineTileInfo:b.offline.online===!1&&a===!1&&void 0===localStorage.__offlineTileInfo&&alert("There was a problem retrieving tiled map info in OfflineTilesEnablerLayer."),b._tilesCore._parseGetTileInfo(a,function(a){b.layerInfos=a.resultObj.layers,b.minScale=a.resultObj.minScale,b.maxScale=a.resultObj.maxScale,b.tileInfo=a.tileInfo,b._imageType=b.tileInfo.format.toLowerCase(),b.fullExtent=a.fullExtent,b.spatialReference=b.tileInfo.spatialReference,b.initialExtent=a.initExtent,b.loaded=!0,b.onLoad(b),c(!0)})},_getTileInfoPrivate:function(a,b){var c,d=this,e=new XMLHttpRequest,f=window.localStorage[this.offline_id_manager] if(void 0===f||""===f)c="" else{var g=JSON.parse(f) g.credentials.forEach(function(b){-1!==a.indexOf(b.server)&&(c="&token="+b.token)})}var h=null!=d.offline.proxyPath?d.offline.proxyPath+"?"+a+"?f=pjson"+c:a+"?f=pjson"+c e.open("GET",h,!0),e.onload=function(){if(200===e.status&&""!==e.responseText){var c=this.response,f=this.response.replace(/\\'/g,"'"),g=JSON.parse(f) -"error"in g?"code"in g.error&&(499==g.error.code||498==g.error.code)&&require(["esri/IdentityManager"],function(c){var e=c.findCredential(a) -void 0===e?c.getCredential(a).then(function(){d._secure=!0,window.localStorage.offline_id_manager=JSON.stringify(c.toJson()),d._getTileInfoPrivate(a,b)}):d._getTileInfoPrivate(a,b)}):d._parseTileInfo(c,d,b)}else b(!1)},e.onerror=function(a){b(!1)},e.send(null)}})}),"undefined"!=typeof O?O.esri.Tiles={}:(O={},O.esri={Tiles:{}}),O.esri.Tiles.Base64Utils={},O.esri.Tiles.Base64Utils.outputTypes={Base64:0,Hex:1,String:2,Raw:3},O.esri.Tiles.Base64Utils.addWords=function(a,b){var c=(65535&a)+(65535&b),d=(a>>16)+(b>>16)+(c>>16) +"error"in g?"code"in g.error&&(499!=g.error.code&&498!=g.error.code||require(["esri/IdentityManager"],function(c){var e=c.findCredential(a) +void 0===e?c.getCredential(a).then(function(){d._secure=!0,window.localStorage[d.offline_id_manager]=JSON.stringify(c.toJson()),d._getTileInfoPrivate(a,b)}):d._getTileInfoPrivate(a,b)})):d._parseTileInfo(c,d,b)}else b(!1)},e.onerror=function(a){b(!1)},e.send(null)}})}),"undefined"!=typeof O?O.esri.Tiles={}:(O={},O.esri={Tiles:{}}),O.esri.Tiles.Base64Utils={},O.esri.Tiles.Base64Utils.outputTypes={Base64:0,Hex:1,String:2,Raw:3},O.esri.Tiles.Base64Utils.addWords=function(a,b){var c=(65535&a)+(65535&b),d=(a>>16)+(b>>16)+(c>>16) return d<<16|65535&c},O.esri.Tiles.Base64Utils.stringToWord=function(a){for(var b=8,c=(1<e;e+=b)d[e>>5]|=(a.charCodeAt(e/b)&c)<e;e+=b)d.push(String.fromCharCode(a[e>>5]>>>e%32&c)) return d.join("")},O.esri.Tiles.Base64Utils.wordToHex=function(a){for(var b="0123456789abcdef",c=[],d=0,e=4*a.length;e>d;d++)c.push(b.charAt(a[d>>2]>>d%4*8+4&15)+b.charAt(a[d>>2]>>d%4*8&15)) @@ -39,7 +39,7 @@ var b=a.document,c=function(){return a.URL||a.webkitURL||a},d=a.URL||a.webkitURL "string"==typeof b?d.revokeObjectURL(b):b.remove()}l.length=0},n=function(a,b,c){b=[].concat(b) for(var d=b.length;d--;){var e=a["on"+b[d]] if("function"==typeof e)try{e.call(a,c||a)}catch(f){i(f)}}},o=function(d,i){var m,o,p,q=this,r=d.type,s=!1,t=function(){var a=c().createObjectURL(d) -return l.push(a),a},u=function(){n(q,"writestart progress write writeend".split(" "))},v=function(){(s||!m)&&(m=t(d)),o?o.location.href=m:window.open(m,"_blank"),q.readyState=q.DONE,u()},w=function(a){return function(){return q.readyState!==q.DONE?a.apply(this,arguments):void 0}},x={create:!0,exclusive:!1} +return l.push(a),a},u=function(){n(q,"writestart progress write writeend".split(" "))},v=function(){!s&&m||(m=t(d)),o?o.location.href=m:window.open(m,"_blank"),q.readyState=q.DONE,u()},w=function(a){return function(){return q.readyState!==q.DONE?a.apply(this,arguments):void 0}},x={create:!0,exclusive:!1} if(q.readyState=q.INIT,i||(i="download"),f){m=t(d),b=a.document,e=b.createElementNS("http://www.w3.org/1999/xhtml","a"),e.href=m,e.download=i var y=b.createEvent("MouseEvents") return y.initMouseEvent("click",!0,!1,a,0,0,0,0,0,!1,!1,!1,!1,0,null),e.dispatchEvent(y),q.readyState=q.DONE,void u()}return a.chrome&&r&&r!==j&&(p=d.slice||d.webkitSlice,d=p.call(d,0,d.size,j),s=!0),g&&"download"!==i&&(i+=".download"),(r===j||g)&&(o=a),h?(k+=d.size,void h(a.TEMPORARY,k,w(function(a){a.root.getDirectory("saved",x,w(function(a){var b=function(){a.getFile(i,x,w(function(a){a.createWriter(w(function(b){b.onwriteend=function(b){o.location.href=a.toURL(),l.push(a),q.readyState=q.DONE,n(q,"writeend",b)},b.onerror=function(){var a=b.error @@ -55,12 +55,13 @@ c.store(g,d)}else d(!1,f.status+" "+f.statusText+": "+f.response+" when download if(i.forEach(function(a){g.push({level:h,row:a[1],col:a[0]})}),g.length>5e3&&h!==c)break}e(g)},this._saveToFile=function(a,b,c){var d=[] d.push("url,img"),b.getAllTiles(function(b,e,f){if("end"===f){var g=new Blob([d.join("\r\n")],{type:"text/plain;charset=utf-8"}),h=O.esri.Tiles.saveAs(g,a) if(h.readyState===h.DONE)return h.error?c(!1,"Error saving file "+a):c(!0,"Saved "+(d.length-1)+" tiles ("+Math.floor(g.size/1024/1024*100)/100+" Mb) into "+a) -h.onerror=function(){c(!1,"Error saving file "+a)},h.onwriteend=function(){c(!0,"Saved "+(d.length-1)+" tiles ("+Math.floor(g.size/1024/1024*100)/100+" Mb) into "+a)}}else d.push(b+","+e)})},this._estimateTileSize=function(a,b,c,d){if(b){var e,f=window.localStorage.offline_id_manager -if(void 0===f||""===f)e="" -else{var g=JSON.parse(f) -g.credentials.forEach(function(a){-1!==b.indexOf(a.server)&&(e="?token="+a.token)})}var h=c?c+"?"+b+e:b+e -a.get(h,{handleAs:"text/plain; charset=x-user-defined",headers:{"X-Requested-With":""},timeout:2e3}).then(function(a){var b=O.esri.Tiles.Base64Utils.wordToBase64(O.esri.Tiles.Base64Utils.stringToWord(a)) -d(b.length+h.length,null)},function(a){d(null,a)})}else d(NaN)},this._loadFromFile=function(a,b,c){if(window.File&&window.FileReader&&window.FileList&&window.Blob){var d=new FileReader +h.onerror=function(){c(!1,"Error saving file "+a)},h.onwriteend=function(){c(!0,"Saved "+(d.length-1)+" tiles ("+Math.floor(g.size/1024/1024*100)/100+" Mb) into "+a)}}else d.push(b+","+e)})},this._estimateTileSize=function(a,b,c,d,e){if(b){var f +if(""!==d)var g=window.localStorage[d] +if(void 0===g||""===g)f="" +else{var h=JSON.parse(g) +h.credentials.forEach(function(a){-1!==b.indexOf(a.server)&&(f="?token="+a.token)})}var i=c?c+"?"+b+f:b+f +a.get(i,{handleAs:"text/plain; charset=x-user-defined",headers:{"X-Requested-With":""},timeout:2e3}).then(function(a){var b=O.esri.Tiles.Base64Utils.wordToBase64(O.esri.Tiles.Base64Utils.stringToWord(a)) +e(b.length+i.length,null)},function(a){e(null,a)})}else e(NaN)},this._loadFromFile=function(a,b,c){if(window.File&&window.FileReader&&window.FileList&&window.Blob){var d=new FileReader d.onload=function(d){var e,f,g=d.target.result,h=g.split("\r\n"),i=0 if("url,img"!==h[0])return c(!1,"File "+a.name+" doesn't contain tiles that can be loaded") for(var j=1;j5e3&&h!==c)break}e(g)},this._saveToFile=function(a,b,c){var d=[] d.push("url,img"),b.getAllTiles(function(b,e,f){if("end"===f){var g=new Blob([d.join("\r\n")],{type:"text/plain;charset=utf-8"}),h=O.esri.Tiles.saveAs(g,a) if(h.readyState===h.DONE)return h.error?c(!1,"Error saving file "+a):c(!0,"Saved "+(d.length-1)+" tiles ("+Math.floor(g.size/1024/1024*100)/100+" Mb) into "+a) -h.onerror=function(){c(!1,"Error saving file "+a)},h.onwriteend=function(){c(!0,"Saved "+(d.length-1)+" tiles ("+Math.floor(g.size/1024/1024*100)/100+" Mb) into "+a)}}else d.push(b+","+e)})},this._estimateTileSize=function(a,b,c,d){if(b){var e,f=window.localStorage.offline_id_manager -if(void 0===f||""===f)e="" -else{var g=JSON.parse(f) -g.credentials.forEach(function(a){-1!==b.indexOf(a.server)&&(e="?token="+a.token)})}var h=c?c+"?"+b+e:b+e -a.get(h,{handleAs:"text/plain; charset=x-user-defined",headers:{"X-Requested-With":""},timeout:2e3}).then(function(a){var b=O.esri.Tiles.Base64Utils.wordToBase64(O.esri.Tiles.Base64Utils.stringToWord(a)) -d(b.length+h.length,null)},function(a){d(null,a)})}else d(NaN)},this._loadFromFile=function(a,b,c){if(window.File&&window.FileReader&&window.FileList&&window.Blob){var d=new FileReader +h.onerror=function(){c(!1,"Error saving file "+a)},h.onwriteend=function(){c(!0,"Saved "+(d.length-1)+" tiles ("+Math.floor(g.size/1024/1024*100)/100+" Mb) into "+a)}}else d.push(b+","+e)})},this._estimateTileSize=function(a,b,c,d,e){if(b){var f +if(""!==d)var g=window.localStorage[d] +if(void 0===g||""===g)f="" +else{var h=JSON.parse(g) +h.credentials.forEach(function(a){-1!==b.indexOf(a.server)&&(f="?token="+a.token)})}var i=c?c+"?"+b+f:b+f +a.get(i,{handleAs:"text/plain; charset=x-user-defined",headers:{"X-Requested-With":""},timeout:2e3}).then(function(a){var b=O.esri.Tiles.Base64Utils.wordToBase64(O.esri.Tiles.Base64Utils.stringToWord(a)) +e(b.length+i.length,null)},function(a){e(null,a)})}else e(NaN)},this._loadFromFile=function(a,b,c){if(window.File&&window.FileReader&&window.FileList&&window.Blob){var d=new FileReader d.onload=function(d){var e,f,g=d.target.result,h=g.split("\r\n"),i=0 if("url,img"!==h[0])return c(!1,"File "+a.name+" doesn't contain tiles that can be loaded") for(var j=1;j>2,c=(3&f)<<4,g+=h[b]+h[c]+"=="):2==k&&(f=i[l]<<8| c(g)},_int2HexString:function(a){var b=a.toString(16).toUpperCase() return 1===b.length?"000"+b:2===b.length?"00"+b:3===b.length?"0"+b:b.substr(0,b.length)},_getOffset:function(a,b,c,d,e){var f=128*(c-e)+(b-d) return 16+5*f},_getCacheFilePath:function(a,b,c,d){var e=[] -return e.push(a),e.push("/"),e.push("L"),e.push(10>b?"0"+b:b),e.push("/"),e.push("R"),e.push(this._int2HexString(c)),e.push("C"),e.push(this._int2HexString(d)),e.join("")},_bytes2MBs:function(a){return(a>>>20)+"."+(2046&a)}})}),"undefined"!=typeof O?O.esri.TPK={}:(O={},O.esri={TPK:{},Tiles:{}}),O.esri.Tiles.TilesStore=function(){this._db=null,this.dbName="offline_tile_store",this.objectStoreName="tilepath",this.isSupported=function(){return window.indexedDB||window.openDatabase?!0:!1},this.store=function(a,b){try{var c=this._db.transaction([this.objectStoreName],"readwrite") +return e.push(a),e.push("/"),e.push("L"),e.push(10>b?"0"+b:b),e.push("/"),e.push("R"),e.push(this._int2HexString(c)),e.push("C"),e.push(this._int2HexString(d)),e.join("")},_bytes2MBs:function(a){return(a>>>20)+"."+(2046&a)}})}),"undefined"!=typeof O?O.esri.TPK={}:(O={},O.esri={TPK:{},Tiles:{}}),O.esri.Tiles.TilesStore=function(){this._db=null,this.dbName="offline_tile_store",this.objectStoreName="tilepath",this.isSupported=function(){return!(!window.indexedDB&&!window.openDatabase)},this.store=function(a,b){try{var c=this._db.transaction([this.objectStoreName],"readwrite") c.oncomplete=function(){b(!0)},c.onerror=function(a){b(!1,a.target.error.message)} var d=c.objectStore(this.objectStoreName),e=d.put(a) e.onsuccess=function(){}}catch(f){b(!1,f.stack)}},this.retrieve=function(a,b){if(null!==this._db){var c=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName),d=c.get(a) @@ -149,7 +149,7 @@ for(b=0;B+1>b;b++)e[b]=0 for(b=0;3>b;b++)f[b]=0 g.set(e.subarray(0,B),0),h.set(e.subarray(0,B+1),0)}var c,d,e,f,g,h,j=this j.inflate_trees_bits=function(e,f,g,h,i){var j -return b(19),c[0]=0,j=a(e,0,19,19,null,null,g,f,h,c,d),j==m?i.msg="oversubscribed dynamic bit lengths tree":(j==o||0===f[0])&&(i.msg="incomplete dynamic bit lengths tree",j=m),j},j.inflate_trees_dynamic=function(e,f,g,h,j,k,l,p,q){var r +return b(19),c[0]=0,j=a(e,0,19,19,null,null,g,f,h,c,d),j==m?i.msg="oversubscribed dynamic bit lengths tree":j!=o&&0!==f[0]||(i.msg="incomplete dynamic bit lengths tree",j=m),j},j.inflate_trees_dynamic=function(e,f,g,h,j,k,l,p,q){var r return b(288),c[0]=0,r=a(g,0,e,257,x,y,k,h,p,c,d),r!=i||0===h[0]?(r==m?q.msg="oversubscribed literal/length tree":r!=n&&(q.msg="incomplete literal/length tree",r=m),r):(b(288),r=a(g,e,f,0,z,A,l,j,p,c,d),r!=i||0===j[0]&&e>257?(r==m?q.msg="oversubscribed distance tree":r==o?(q.msg="incomplete distance tree",r=m):r!=n&&(q.msg="empty distance tree with lengths",r=m),r):i)}}function c(){function a(a,b,c,d,e,f,g,h){var k,l,n,o,q,r,s,t,u,v,w,x,y,z,A,B s=h.next_in_index,t=h.avail_in,q=g.bitb,r=g.bitk,u=g.write,v=ur;)t--,q|=(255&h.read_byte(s++))</g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/"):a}function g(a){return a.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(///g,"/")}function h(b,c,d){switch(a.arrayAccessForm){case"property":b[c]instanceof Array?b[c+"_asArray"]=b[c]:b[c+"_asArray"]=[b[c]]}if(!(b[c]instanceof Array)&&a.arrayAccessFormPaths.length>0){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/"):a}function g(a){return a.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(///g,"/")}function h(b,c,d){switch(a.arrayAccessForm){case"property":b[c]instanceof Array?b[c+"_asArray"]=b[c]:b[c+"_asArray"]=[b[c]]}if(!(b[c]instanceof Array)&&a.arrayAccessFormPaths.length>0){for(var e=0;e1&&c.setMilliseconds(d[1]),b[6]&&b[7]){var e=60*b[6]+Number(b[7]),f=/\d\d-\d\d:\d\d$/.test(a)?"-":"+" e=0+("-"==f?-1*e:e),c.setMinutes(c.getMinutes()-e-c.getTimezoneOffset())}else-1!==a.indexOf("Z",a.length-1)&&(c=new Date(Date.UTC(c.getFullYear(),c.getMonth(),c.getDate(),c.getHours(),c.getMinutes(),c.getSeconds(),c.getMilliseconds()))) @@ -314,7 +314,7 @@ m.nodeType!=w.COMMENT_NODE&&(f.__cnt++,null==f[n]?(f[n]=k(m,c+"."+n),h(f,n,c+"." f.__cnt++,f[a.attributePrefix+p.name]=p.value}var q=e(b) return null!=q&&""!=q&&(f.__cnt++,f.__prefix=q),null!=f["#text"]&&(f.__text=f["#text"],f.__text instanceof Array&&(f.__text=f.__text.join("\n")),a.escapeMode&&(f.__text=g(f.__text)),a.stripWhitespaces&&(f.__text=f.__text.trim()),delete f["#text"],"property"==a.arrayAccessForm&&delete f["#text_asArray"],f.__text=j(f.__text,n,c+"."+n)),null!=f["#cdata-section"]&&(f.__cdata=f["#cdata-section"],delete f["#cdata-section"],"property"==a.arrayAccessForm&&delete f["#cdata-section_asArray"]),1==f.__cnt&&null!=f.__text?f=f.__text:0==f.__cnt&&"text"==a.emptyNodeForm?f="":f.__cnt>1&&null!=f.__text&&a.skipEmptyTextNodesForObj&&(a.stripWhitespaces&&""==f.__text||""==f.__text.trim())&&delete f.__text,delete f.__cnt,!a.enableToStringFunc||null==f.__text&&null==f.__cdata||(f.toString=function(){return(null!=this.__text?this.__text:"")+(null!=this.__cdata?this.__cdata:"")}),f}return b.nodeType==w.TEXT_NODE||b.nodeType==w.CDATA_SECTION_NODE?b.nodeValue:void 0}function l(b,c,d,e){var g="<"+(null!=b&&null!=b.__prefix?b.__prefix+":":"")+c if(null!=d)for(var h=0;h":">"}function m(a,b){return""}function n(a,b){return-1!==a.indexOf(b,a.length-b.length)}function o(b,c){return"property"==a.arrayAccessForm&&n(c.toString(),"_asArray")||0==c.toString().indexOf(a.attributePrefix)||0==c.toString().indexOf("__")||b[c]instanceof Function?!0:!1}function p(a){var b=0 +a.escapeMode&&(j=f(j)),g+=" "+i.substr(a.attributePrefix.length)+"='"+j+"'"}return g+=e?"/>":">"}function m(a,b){return""}function n(a,b){return-1!==a.indexOf(b,a.length-b.length)}function o(b,c){return!!("property"==a.arrayAccessForm&&n(c.toString(),"_asArray")||0==c.toString().indexOf(a.attributePrefix)||0==c.toString().indexOf("__")||b[c]instanceof Function)}function p(a){var b=0 if(a instanceof Object)for(var c in a)o(a,c)||b++ return b}function q(b){var c=[] if(b instanceof Object)for(var d in b)-1==d.toString().indexOf("__")&&0==d.toString().indexOf(a.attributePrefix)&&c.push(d) diff --git a/dist/offline-tpk-src.js b/dist/offline-tpk-src.js index 3d27b4c2..e4ffaf98 100644 --- a/dist/offline-tpk-src.js +++ b/dist/offline-tpk-src.js @@ -1,6 +1,6 @@ -/*! esri-offline-maps - v3.0.3 - 2015-11-30 -* Copyright (c) 2015 Environmental Systems Research Institute, Inc. -* Apache License*/ +/*! esri-offline-maps - v3.0.6 - 2016-03-30 +* Copyright (c) 2016 Environmental Systems Research Institute, Inc. +* Apache License*/ /** * Library for reading an ArcGIS Tile Package (.tpk) file and displaying the tiles * as a map that can be used both online and offline. @@ -720,7 +720,7 @@ define([ } }); } -); +); /** * Creates a namespace for the non-AMD libraries in this directory */ @@ -738,7 +738,7 @@ else{ } //"use strict"; - + /*global indexedDB */ /** @@ -1000,7 +1000,7 @@ O.esri.Tiles.TilesStore = function(){ }; }; - + //https://github.com/gildas-lormeau/zip.js/blob/master/WebContent/zip.js /* Copyright (c) 2013 Gildas Lormeau. All rights reserved. @@ -1807,7 +1807,7 @@ O.esri.Tiles.TilesStore = function(){ useWebWorkers : true }; -}(O.esri)); +}(O.esri)); /** * This library assists with autoCenter the map upon orientation change * IMPORTANT: There are Esri dependencies in this library including @@ -1941,7 +1941,7 @@ O.esri.TPK.autoCenterMap = function(/* Map */ map,/* int */ delay){ var centerPt = map.extent.getCenter(); _setCenterPt(centerPt.x,centerPt.y,map.spatialReference.wkid); }; -}; +}; /* Copyright (c) 2013 Gildas Lormeau. All rights reserved. @@ -4120,7 +4120,7 @@ O.esri.TPK.___blobURL = URL.createObjectURL( ) O.esri.zip.workerScriptsPath = O.esri.TPK.___blobURL; - + //https://github.com/abdmob/x2js/blob/master/xml2json.js /* Copyright 2011-2013 Abdulla Abdurakhmanov diff --git a/doc/howtousetiles.md b/doc/howtousetiles.md index b01be919..a0f3bd31 100644 --- a/doc/howtousetiles.md +++ b/doc/howtousetiles.md @@ -7,7 +7,7 @@ The `tiles` library allows a developer to extend a tiled layer with offline supp There are two primary approaches to using this set of libraries. The first approaches is for intermittent offline use cases where you only expect occasional and temporary internet outages and you don't need to worry about restarting the application while offline. The first approach works with both an `ArcGISTiledMapServiceLayer` and ArcGIS.com Web maps. -The second approach is if you need to be able to restart or reload your application offline and only works with `ArcGISTiledMapServiceLayer`. +The second approach is if you need to be able to restart or reload your application offline and only works with `ArcGISTiledMapServiceLayer`. *You must use this approach if your tiled layer uses token-based security.* For detecting whether the browser is online, offline or listen for connection changes we recommend the [Offline.js](http://github.hubspot.com/offline/docs/welcome/) library. @@ -188,9 +188,9 @@ To get the current extent you will need to monitor the `zoom-end` and `pan-end` ``` -## Specifying a custom database and dataStore name +## Specifying a custom database, dataStore name, and Offline Tiles Id Manager name -Both `OfflineTilesAdvanced` and `OfflineTilesBasic` have an optional property that allows you to specify your own database name and dataStore name. +Both `OfflineTilesAdvanced` and `OfflineTilesBasic` have an optional property that allows you to specify your own database name, dataStore name, and offline tile ID manager name. For OfflineTilesBasic you can use the following pattern within the `extend()` method: @@ -212,13 +212,14 @@ For OfflineTilesBasic you can use the following pattern within the `extend()` me ``` -For OfflineTilesAdvanced use this pattern in the constructor: +For OfflineTilesAdvanced use this pattern in the constructor. Note that the Advanced approach also supports token-based security, so an optional ```offlineIdManager``` can also be specified. ```js var dbConfig = { dbName : "TILES_TEST", - objectStoreName : "TILES" + objectStoreName : "TILES", + offlineIdManager: "TILES_ID_MANAGER" } tileLayer = new O.esri.Tiles.OfflineTilesAdvanced("http://xyz",function(evt){ @@ -236,7 +237,7 @@ In the constructor for `OfflineTilesAdvanced` and in the `extend()` method for ` If you are using a secure tiled map service then you'll need to use the `OfflineTilesAdvanced` library. There isn't anything special you need to do, the library should automatically recognize you are using a secure service and it will trigger `esri/IdentityManager` if it cannot find valid credentials. -The library manually stores credential information using the following localStorage pattern: `window.localStorage.offline_id_manager`. +The library manually stores credential information using the following localStorage pattern: ```window.localStorage[offlineIdManager```. If you do not specify the ```offlineIdManager``` parameter in the ```dbConfig``` constructor parameter, a default value of `window.localStorage.offline_id_manager` will be assigned. If you are using an optimized version of the ArcGIS API for JavaScript make sure you include the `esri/IdentityManager` module. diff --git a/lib/edit/OfflineEditAdvanced.js b/lib/edit/OfflineEditAdvanced.js index 67ec436f..99529c25 100644 --- a/lib/edit/OfflineEditAdvanced.js +++ b/lib/edit/OfflineEditAdvanced.js @@ -1150,7 +1150,19 @@ define([ // we need to identify ADDs before sending them to the server // we assign temporary ids (using negative numbers to distinguish them from real ids) - layer._nextTempId = -1; + // 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--; }; diff --git a/lib/edit/OfflineEditBasic.js b/lib/edit/OfflineEditBasic.js index a12d801f..b264dc27 100644 --- a/lib/edit/OfflineEditBasic.js +++ b/lib/edit/OfflineEditBasic.js @@ -488,7 +488,19 @@ define([ // we need to identify ADDs before sending them to the server // we assign temporary ids (using negative numbers to distinguish them from real ids) - layer._nextTempId = -1; + // 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--; }; diff --git a/lib/edit/editsStore.js b/lib/edit/editsStore.js index 888106bd..8a5440ae 100644 --- a/lib/edit/editsStore.js +++ b/lib/edit/editsStore.js @@ -540,6 +540,58 @@ O.esri.Edit.EditStore = 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 phantomGraphicPrefix = this.PHANTOM_GRAPHIC_PREFIX; + + 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 && cursor.value.id.indexOf(phantomGraphicPrefix) == -1) { + 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"); + } + }, + /** * Returns all the edits as a single Array via the callback * @param callback {array, messageString} or {null, messageString} diff --git a/lib/tiles/OfflineTilesAdvanced.js b/lib/tiles/OfflineTilesAdvanced.js index 2814d965..74d1fb23 100644 --- a/lib/tiles/OfflineTilesAdvanced.js +++ b/lib/tiles/OfflineTilesAdvanced.js @@ -28,18 +28,22 @@ define([ alert("OfflineTiles Library not supported on this browser."); callback(false); } - else { - window.localStorage.offline_id_manager = ""; //this is where we will store secure service info - } if( dbConfig === undefined || dbConfig === null){ // Database properties this.DB_NAME = "offline_tile_store"; // Sets the database name. this.DB_OBJECTSTORE_NAME = "tilepath"; // Represents an object store that allows access to a set of data in the IndexedDB database + this.offline_id_manager = "offline_id_manager"; } else { this.DB_NAME = dbConfig.dbName; this.DB_OBJECTSTORE_NAME = dbConfig.objectStoreName; + if( dbConfig.offlineIdManager === undefined || dbConfig.offlineIdManger === null ){ + this.offline_id_manager = "offline_id_manager"; + } + else{ + this.offline_id_manager = dbConfig.offlineIdManager; + } } this._tilesCore = new O.esri.Tiles.TilesCore(); @@ -125,7 +129,7 @@ define([ // code then there will be a problem because the library won't be able to retrieve // secure tiles without appending the token to the URL var token; - var secureInfo = window.localStorage.offline_id_manager; + var secureInfo = window.localStorage[this.offline_id_manager]; if(secureInfo === undefined || secureInfo === ""){ token = ""; @@ -367,7 +371,7 @@ define([ */ estimateTileSize : function(callback) { - this._tilesCore._estimateTileSize(request,this._lastTileUrl,this.offline.proxyPath,callback); + this._tilesCore._estimateTileSize(request,this._lastTileUrl,this.offline.proxyPath,this.offline_id_manager,callback); }, /** @@ -487,7 +491,7 @@ define([ var self = this; var req = new XMLHttpRequest(); var token; - var secureInfo = window.localStorage.offline_id_manager; + var secureInfo = window.localStorage[this.offline_id_manager]; if(secureInfo === undefined || secureInfo === ""){ token = ""; @@ -528,7 +532,7 @@ define([ //https://developers.arcgis.com/javascript/jssamples/widget_identitymanager_client_side.html esriId.getCredential(url).then(function () { self._secure = true; - window.localStorage.offline_id_manager = JSON.stringify(esriId.toJson()); + window.localStorage[self.offline_id_manager] = JSON.stringify(esriId.toJson()); self._getTileInfoPrivate(url, callback); }); } diff --git a/lib/tiles/OfflineTilesBasic.js b/lib/tiles/OfflineTilesBasic.js index e19cd479..46be8e82 100644 --- a/lib/tiles/OfflineTilesBasic.js +++ b/lib/tiles/OfflineTilesBasic.js @@ -328,7 +328,7 @@ define([ */ layer.estimateTileSize = function(callback) { - layer._tilesCore._estimateTileSize(request,this._lastTileUrl,this.offline.proxyPath,callback); + layer._tilesCore._estimateTileSize(request,this._lastTileUrl,this.offline.proxyPath,"",callback); }; /** diff --git a/lib/tiles/TilesCore.js b/lib/tiles/TilesCore.js index 13a9a8c8..a9f88b92 100644 --- a/lib/tiles/TilesCore.js +++ b/lib/tiles/TilesCore.js @@ -174,7 +174,7 @@ O.esri.Tiles.TilesCore = function(){ * @param callback * @returns {Number} Returns NaN if there was a problem retrieving the tile */ - this._estimateTileSize = function(request,lastTileUrl,proxyPath,callback) + this._estimateTileSize = function(request,lastTileUrl,proxyPath,offline_id_manager,callback) { if(lastTileUrl) { @@ -183,7 +183,9 @@ O.esri.Tiles.TilesCore = function(){ // code then there will be a problem because the library won't be able to retrieve // secure tiles without appending the token to the URL var token; - var secureInfo = window.localStorage.offline_id_manager; + if(offline_id_manager !== ""){ // this will be blank if coming in from OfflineTilesBasic, and will remain undefined, + var secureInfo = window.localStorage[offline_id_manager]; // but if coming from OfflineTilesAdvanced, we need to find the value from localStorage. + } if(secureInfo === undefined || secureInfo === ""){ token = ""; diff --git a/package.json b/package.json index f4b00309..ff58f6a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "esri-offline-maps", - "version": "3.0.5", + "version": "3.0.6", "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/test/SpecRunner.offlineTilesAdvanced.TokenBased.html b/test/SpecRunner.offlineTilesAdvanced.TokenBased.html index a15b3b83..7e539b34 100644 --- a/test/SpecRunner.offlineTilesAdvanced.TokenBased.html +++ b/test/SpecRunner.offlineTilesAdvanced.TokenBased.html @@ -49,8 +49,9 @@ var dbConfig = { dbName: "TILES_TEST", - objectStoreName: "TILES" - } + objectStoreName: "TILES", + offlineIdManager: "OFFLINE_ID_MGR" // This parameteter is optional. If not provided, it will default to "offline_id_manager" + }; g_basemapLayer = new O.esri.Tiles.OfflineTilesAdvanced(g_url,function(evt){ console.log("Tile Layer Loaded.");