From f0913c0f24f0ce63f82abb043ea692d967091d7c Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Tue, 1 Nov 2016 09:37:10 -0600 Subject: [PATCH 1/2] Closes #491, #492 --- lib/edit/OfflineEditAdvanced.js | 36 ++++++++++++++++++++++++++++--- lib/edit/OfflineEditBasic.js | 38 ++++++++++++++++++++++++++++++--- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/lib/edit/OfflineEditAdvanced.js b/lib/edit/OfflineEditAdvanced.js index b9e5c11b..d1458f5a 100644 --- a/lib/edit/OfflineEditAdvanced.js +++ b/lib/edit/OfflineEditAdvanced.js @@ -25,7 +25,8 @@ define([ _featureLayers: {}, _featureCollectionUsageFlag: false, // if a feature collection was used to create the feature layer. _editStore: new O.esri.Edit.EditStore(), - _defaultXhrTimeout: 15000, // ms + _defaultXhrTimeout: 15000, // ms + _esriFieldTypeOID: "", // Determines the correct casing for objectid. Some feature layers use different casing ONLINE: "online", // all edits will directly go to the server OFFLINE: "offline", // edits will be enqueued @@ -121,6 +122,14 @@ define([ // library will break and we'll have to re-architect how it manages UIDs. layer.objectIdField = this.DB_UID; + // NOTE: set the casing for the feature layers objectid. + for(var i = 0; i < layer.fields.length; i++){ + if(layer.fields[i].type === "esriFieldTypeOID"){ + this._esriFieldTypeOID = layer.fields[i].name; + break; + } + } + var url = null; // There have been reproducible use cases showing when a browser is restarted offline that @@ -1896,7 +1905,28 @@ define([ }); } + // addResults present a special case for handling objectid if(addResults.length > 0) { + var objectid = ""; + + if(addResults[0].hasOwnProperty("objectid")){ + objectid = "objectid"; + } + + if(addResults[0].hasOwnProperty("objectId")){ + objectid = "objectId"; + } + + if(addResults[0].hasOwnProperty("OBJECTID")){ + objectid = "OBJECTID"; + } + + // ??? These are the most common objectid values. I may have missed some! + + // Some feature layers will return different casing such as: 'objectid', 'objectId' and 'OBJECTID' + // Normalize these values to the feature type OID so that we don't break other aspects + // of the JS API. + adds[0].attributes[that._esriFieldTypeOID] = addResults[0][objectid]; var graphic = new Graphic(adds[0].geometry,null,adds[0].attributes); layer.add(graphic); } @@ -2051,11 +2081,11 @@ define([ if( req.status === 200 && req.responseText !== "") { try { - var obj = JSON.parse(this.response); + var obj = JSON.parse(this.responseText); callback(obj.addResults, obj.updateResults, obj.deleteResults); } catch(err) { - console.error("EDIT REQUEST RESPONSE WAS NOT SUCCESSFUL:", req); + console.error("FAILED TO PARSE EDIT REQUEST RESPONSE:", req); errback("Unable to parse xhr response", req); } } diff --git a/lib/edit/OfflineEditBasic.js b/lib/edit/OfflineEditBasic.js index af20c0e8..c13c30ae 100644 --- a/lib/edit/OfflineEditBasic.js +++ b/lib/edit/OfflineEditBasic.js @@ -24,8 +24,9 @@ define([ _onlineStatus: "online", _featureLayers: {}, _editStore: new O.esri.Edit.EditStorePOLS(), - _defaultXhrTimeout: 15000, // ms + _defaultXhrTimeout: 15000, // ms _autoOfflineDetect: true, + _esriFieldTypeOID: "", // Determines the correct casing for objectid. Some feature layers use different casing ONLINE: "online", // all edits will directly go to the server OFFLINE: "offline", // edits will be enqueued @@ -76,6 +77,14 @@ define([ // library will break and we'll have to re-architect how it manages UIDs. layer.objectIdField = this.DB_UID; + // NOTE: set the casing for the feature layers objectid. + for(var i = 0; i < layer.fields.length; i++){ + if(layer.fields[i].type === "esriFieldTypeOID"){ + this._esriFieldTypeOID = layer.fields[i].name; + break; + } + } + var url = null; // There have been reproducible use cases showing when a browser is restarted offline that @@ -820,7 +829,29 @@ define([ this._makeEditRequest(layer, adds, updates, deletes, function (addResults, updateResults, deleteResults) { + // addResults present a special case for handling objectid if(addResults.length > 0) { + + var objectid = ""; + + if(addResults[0].hasOwnProperty("objectid")){ + objectid = "objectid"; + } + + if(addResults[0].hasOwnProperty("objectId")){ + objectid = "objectId"; + } + + if(addResults[0].hasOwnProperty("OBJECTID")){ + objectid = "OBJECTID"; + } + + // ??? These are the most common objectid values. I may have missed some! + + // Some feature layers will return different casing such as: 'objectid', 'objectId' and 'OBJECTID' + // Normalize these values to the feature type OID so that we don't break other aspects + // of the JS API. + adds[0].attributes[that._esriFieldTypeOID] = addResults[0][objectid]; var graphic = new Graphic(adds[0].geometry,null,adds[0].attributes); layer.add(graphic); } @@ -962,11 +993,12 @@ define([ if( req.status === 200 && req.responseText !== "") { try { - var obj = JSON.parse(this.response); + // var b = this.responseText.replace(/"/g, "'"); // jshint ignore:line + var obj = JSON.parse(this.responseText); callback(obj.addResults, obj.updateResults, obj.deleteResults); } catch(err) { - console.error("EDIT REQUEST REPONSE WAS NOT SUCCESSFUL:", req); + console.error("FAILED TO PARSE EDIT REQUEST RESPONSE:", req); errback("Unable to parse xhr response", req); } } From ca31f01f50ee15ee624882cf7bf92c9af2a38501 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Tue, 1 Nov 2016 09:38:19 -0600 Subject: [PATCH 2/2] v3.7.0 --- CHANGELOG.md | 9 + dist/offline-edit-advanced-min.js | 17 +- dist/offline-edit-advanced-src.js | 38 +- dist/offline-edit-basic-min.js | 15 +- dist/offline-edit-basic-src.js | 40 +- dist/offline-tiles-advanced-src.js | 2 +- dist/offline-tiles-basic-src.js | 2 +- dist/offline-tpk-src.js | 1116 ++++++++++++++-------------- package.json | 2 +- samples/package.json | 2 +- 10 files changed, 660 insertions(+), 583 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33bf7053..16dabc95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # offline-editor-js - Changelog +## Version 3.7.0 - Nov 1, 2016 + +No breaking changes. + +**Enhancements** +* Closes #491 - better handling of OBJECTID when returning online +* Closes #492 - it's working again, no clue why. No fix. Could be how JSON is being returned from AGOL. + + ## Version 3.6.0 - October 17, 2016 No breaking changes. diff --git a/dist/offline-edit-advanced-min.js b/dist/offline-edit-advanced-min.js index de046a06..3ab9f1c1 100644 --- a/dist/offline-edit-advanced-min.js +++ b/dist/offline-edit-advanced-min.js @@ -1,10 +1,11 @@ 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") +return d("O.esri.Edit.OfflineEditAdvanced",[a],{_onlineStatus:"online",_featureLayers:{},_featureCollectionUsageFlag:!1,_editStore:new O.esri.Edit.EditStore,_defaultXhrTimeout:15e3,_esriFieldTypeOID:"",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") try{if(this.attachmentsStore=new O.esri.Edit.AttachmentsStore,this.attachmentsStore.dbName=this.ATTACHMENTS_DB_NAME,this.attachmentsStore.objectStoreName=this.ATTACHMENTS_DB_OBJECTSTORE_NAME,!this.attachmentsStore.isSupported())return a(!1,"indexedDB not supported") this.attachmentsStore.init(a)}catch(b){}},extend:function(a,d,i){function l(){try{a._phantomLayer=new j({opacity:.8}),a._map.addLayer(a._phantomLayer)}catch(b){}}var m=[],n=this a.offlineExtended=!0,!a.loaded,a.objectIdField=this.DB_UID -var o=null -a.url&&(o=a.url,this._featureLayers[a.url]=a),a._mode.featureLayer.hasOwnProperty("_collection")&&(this._featureCollectionUsageFlag=!0),this._editStore._isDBInit||m.push(this._initializeDB(i,o)),a._applyEdits=a.applyEdits,a._addAttachment=a.addAttachment,a._queryAttachmentInfos=a.queryAttachmentInfos,a._deleteAttachments=a.deleteAttachments,a._updateAttachment=a.updateAttachment,a.queryAttachmentInfos=function(a,c,d){if(n.getOnlineStatus()===n.ONLINE){var e=this._queryAttachmentInfos(a,function(){n.emit(n.events.ATTACHMENTS_INFO,arguments),c&&c.apply(this,arguments)},d) +for(var o=0;od;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} +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&&p?this.ENABLE_FEATURECOLLECTION?a._pushFeatureCollections(function(a){a?d(!0,null):d(!1,null)}):d(!0,null):b[0].success&&!p?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) @@ -114,8 +115,10 @@ v.then(function(b){this._parseResponsesArray(b).then(function(c){c?this.emit(thi return d.attributes={},d.attributes[this.DB_UID]=a.id,this._editStore["delete"](a.layer,d,function(a,b){a?c.resolve({success:!0,error:null}):c.reject({success:!1,error:b})}.bind(this)),c.promise},getFeatureLayerJSON:function(a,b){require(["esri/request"],function(c){var d=c({url:a,content:{f:"json"},handleAs:"json",callbackParamName:"callback"}) d.then(function(a){b(!0,a)},function(a){b(!1,a.message)})})},_internalApplyEditsAll:function(a,c,d,e,f,g){var h=this,i=new b return this._makeEditRequest(a,e,f,g,function(b,f,g){if(a._phantomLayer.clear(),null!=a._attachmentsStore&&a.hasAttachments&&d.length>0){var j=b.map(function(a){return a.objectId}) -a._replaceFeatureIds(d,j,function(a){})}if(b.length>0){var l=new k(e[0].geometry,null,e[0].attributes) -a.add(l)}h._cleanDatabase(a,d,b,f,g).then(function(e){i.resolve({id:c,layer:a.url,tempId:d,addResults:b,updateResults:f,deleteResults:g,databaseResults:e,databaseErrors:null,syncError:null})},function(e){i.resolve({id:c,layer:a.url,tempId:d,addResults:b,updateResults:f,deleteResults:g,databaseResults:null,databaseErrors:e,syncError:e})})},function(b){a.onEditsComplete=a.__onEditsComplete,delete a.__onEditsComplete,i.reject(b)}),i.promise},_cleanDatabase:function(a,c,d,e,f){var g=new b,h=null +a._replaceFeatureIds(d,j,function(a){})}if(b.length>0){var l="" +b[0].hasOwnProperty("objectid")&&(l="objectid"),b[0].hasOwnProperty("objectId")&&(l="objectId"),b[0].hasOwnProperty("OBJECTID")&&(l="OBJECTID"),e[0].attributes[h._esriFieldTypeOID]=b[0][l] +var m=new k(e[0].geometry,null,e[0].attributes) +a.add(m)}h._cleanDatabase(a,d,b,f,g).then(function(e){i.resolve({id:c,layer:a.url,tempId:d,addResults:b,updateResults:f,deleteResults:g,databaseResults:e,databaseErrors:null,syncError:null})},function(e){i.resolve({id:c,layer:a.url,tempId:d,addResults:b,updateResults:f,deleteResults:g,databaseResults:null,databaseErrors:e,syncError:e})})},function(b){a.onEditsComplete=a.__onEditsComplete,delete a.__onEditsComplete,i.reject(b)}),i.promise},_cleanDatabase:function(a,c,d,e,f){var g=new b,h=null e.length>0&&e[0].success&&(h=e[0].objectId),f.length>0&&f[0].success&&(h=f[0].objectId),d.length>0&&d[0].success&&(h=c) var i={} return i.attributes={},i.attributes[this.DB_UID]=h,this._editStore["delete"](a.url,i,function(a,b){if(a){var c=this._editStore.PHANTOM_GRAPHIC_PREFIX+this._editStore._PHANTOM_PREFIX_TOKEN+i.attributes[this.DB_UID] @@ -124,7 +127,7 @@ if(b.length>0&&(e.forEach(b,function(a){a.hasOwnProperty("infoTemplate")&&delete 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=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) +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.responseText) 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" diff --git a/dist/offline-edit-advanced-src.js b/dist/offline-edit-advanced-src.js index e8333e39..98d87172 100644 --- a/dist/offline-edit-advanced-src.js +++ b/dist/offline-edit-advanced-src.js @@ -1,4 +1,4 @@ -/*! esri-offline-maps - v3.6.0 - 2016-10-17 +/*! esri-offline-maps - v3.7.0 - 2016-11-01 * Copyright (c) 2016 Environmental Systems Research Institute, Inc. * Apache License*/ // Configure offline/online detection @@ -41,7 +41,8 @@ define([ _featureLayers: {}, _featureCollectionUsageFlag: false, // if a feature collection was used to create the feature layer. _editStore: new O.esri.Edit.EditStore(), - _defaultXhrTimeout: 15000, // ms + _defaultXhrTimeout: 15000, // ms + _esriFieldTypeOID: "", // Determines the correct casing for objectid. Some feature layers use different casing ONLINE: "online", // all edits will directly go to the server OFFLINE: "offline", // edits will be enqueued @@ -137,6 +138,14 @@ define([ // library will break and we'll have to re-architect how it manages UIDs. layer.objectIdField = this.DB_UID; + // NOTE: set the casing for the feature layers objectid. + for(var i = 0; i < layer.fields.length; i++){ + if(layer.fields[i].type === "esriFieldTypeOID"){ + this._esriFieldTypeOID = layer.fields[i].name; + break; + } + } + var url = null; // There have been reproducible use cases showing when a browser is restarted offline that @@ -1912,7 +1921,28 @@ define([ }); } + // addResults present a special case for handling objectid if(addResults.length > 0) { + var objectid = ""; + + if(addResults[0].hasOwnProperty("objectid")){ + objectid = "objectid"; + } + + if(addResults[0].hasOwnProperty("objectId")){ + objectid = "objectId"; + } + + if(addResults[0].hasOwnProperty("OBJECTID")){ + objectid = "OBJECTID"; + } + + // ??? These are the most common objectid values. I may have missed some! + + // Some feature layers will return different casing such as: 'objectid', 'objectId' and 'OBJECTID' + // Normalize these values to the feature type OID so that we don't break other aspects + // of the JS API. + adds[0].attributes[that._esriFieldTypeOID] = addResults[0][objectid]; var graphic = new Graphic(adds[0].geometry,null,adds[0].attributes); layer.add(graphic); } @@ -2067,11 +2097,11 @@ define([ if( req.status === 200 && req.responseText !== "") { try { - var obj = JSON.parse(this.response); + var obj = JSON.parse(this.responseText); callback(obj.addResults, obj.updateResults, obj.deleteResults); } catch(err) { - console.error("EDIT REQUEST RESPONSE WAS NOT SUCCESSFUL:", req); + console.error("FAILED TO PARSE EDIT REQUEST RESPONSE:", req); errback("Unable to parse xhr response", req); } } diff --git a/dist/offline-edit-basic-min.js b/dist/offline-edit-basic-min.js index 10819e73..a92050f4 100644 --- a/dist/offline-edit-basic-min.js +++ b/dist/offline-edit-basic-min.js @@ -1,8 +1,9 @@ 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 +return d("O.esri.Edit.OfflineEditBasic",[a],{_onlineStatus:"online",_featureLayers:{},_editStore:new O.esri.Edit.EditStorePOLS,_defaultXhrTimeout:15e3,_autoOfflineDetect:!0,_esriFieldTypeOID:"",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 a.offlineExtended=!0,!a.loaded||null===a._url,a.objectIdField=this.DB_UID -var h=null -a.url&&(h=a.url,this._featureLayers[a.url]=a),this._editStore._isDBInit||f.push(this._initializeDB(h)),a._applyEdits=a.applyEdits,a.applyEdits=function(d,e,f,h,i){var j=[] +for(var h=0;h0&&(g.updateResults[0].success?(h.layer=g.layer,h.id=g.up var l=c(i) l.then(function(a){e.length>0?b(!1,a):b(!0,a)},function(a){b(!1,a)})}else b(!0,{})},_updateDatabase:function(a){var c=new b,d={} return d.attributes={},d.attributes[this.DB_UID]=a.id,this._editStore["delete"](a.layer,d,function(a,b){a?c.resolve({success:!0,error:null}):c.reject({success:!1,error:b})}.bind(this)),c.promise},_internalApplyEditsAll:function(a,c,d,e,f,g){var h=this,i=new b -return this._makeEditRequest(a,e,f,g,function(b,f,g){if(b.length>0){var j=new m(e[0].geometry,null,e[0].attributes) -a.add(j)}h._cleanDatabase(a,d,b,f,g).then(function(e){i.resolve({id:c,layer:a.url,tempId:d,addResults:b,updateResults:f,deleteResults:g,databaseResults:e,databaseErrors:null,syncError:null})},function(e){i.resolve({id:c,layer:a.url,tempId:d,addResults:b,updateResults:f,deleteResults:g,databaseResults:null,databaseErrors:e,syncError:e})})},function(b){a.onEditsComplete=a.__onEditsComplete,delete a.__onEditsComplete,i.reject(b)}),i.promise},_cleanDatabase:function(a,c,d,e,f){var g=new b,h=null +return this._makeEditRequest(a,e,f,g,function(b,f,g){if(b.length>0){var j="" +b[0].hasOwnProperty("objectid")&&(j="objectid"),b[0].hasOwnProperty("objectId")&&(j="objectId"),b[0].hasOwnProperty("OBJECTID")&&(j="OBJECTID"),e[0].attributes[h._esriFieldTypeOID]=b[0][j] +var k=new m(e[0].geometry,null,e[0].attributes) +a.add(k)}h._cleanDatabase(a,d,b,f,g).then(function(e){i.resolve({id:c,layer:a.url,tempId:d,addResults:b,updateResults:f,deleteResults:g,databaseResults:e,databaseErrors:null,syncError:null})},function(e){i.resolve({id:c,layer:a.url,tempId:d,addResults:b,updateResults:f,deleteResults:g,databaseResults:null,databaseErrors:e,syncError:e})})},function(b){a.onEditsComplete=a.__onEditsComplete,delete a.__onEditsComplete,i.reject(b)}),i.promise},_cleanDatabase:function(a,c,d,e,f){var g=new b,h=null e.length>0&&e[0].success&&(h=e[0].objectId),f.length>0&&f[0].success&&(h=f[0].objectId),d.length>0&&d[0].success&&(h=c) var i={} return i.attributes={},i.attributes[this.DB_UID]=h,this._editStore["delete"](a.url,i,function(a,b){a?g.resolve({success:!0,error:null,id:h}):g.reject({success:!1,error:b,id:h})}),g.promise},_makeEditRequest:function(a,b,c,d,f,g){var h="f=json",i="",j="",k="" @@ -57,7 +60,7 @@ if(b.length>0&&(e.forEach(b,function(a){a.hasOwnProperty("infoTemplate")&&delete 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=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) +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.responseText) 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" diff --git a/dist/offline-edit-basic-src.js b/dist/offline-edit-basic-src.js index 358601c0..9d0fe116 100644 --- a/dist/offline-edit-basic-src.js +++ b/dist/offline-edit-basic-src.js @@ -1,4 +1,4 @@ -/*! esri-offline-maps - v3.6.0 - 2016-10-17 +/*! esri-offline-maps - v3.7.0 - 2016-11-01 * Copyright (c) 2016 Environmental Systems Research Institute, Inc. * Apache License*/ // Configure offline/online detection @@ -40,8 +40,9 @@ define([ _onlineStatus: "online", _featureLayers: {}, _editStore: new O.esri.Edit.EditStorePOLS(), - _defaultXhrTimeout: 15000, // ms + _defaultXhrTimeout: 15000, // ms _autoOfflineDetect: true, + _esriFieldTypeOID: "", // Determines the correct casing for objectid. Some feature layers use different casing ONLINE: "online", // all edits will directly go to the server OFFLINE: "offline", // edits will be enqueued @@ -92,6 +93,14 @@ define([ // library will break and we'll have to re-architect how it manages UIDs. layer.objectIdField = this.DB_UID; + // NOTE: set the casing for the feature layers objectid. + for(var i = 0; i < layer.fields.length; i++){ + if(layer.fields[i].type === "esriFieldTypeOID"){ + this._esriFieldTypeOID = layer.fields[i].name; + break; + } + } + var url = null; // There have been reproducible use cases showing when a browser is restarted offline that @@ -836,7 +845,29 @@ define([ this._makeEditRequest(layer, adds, updates, deletes, function (addResults, updateResults, deleteResults) { + // addResults present a special case for handling objectid if(addResults.length > 0) { + + var objectid = ""; + + if(addResults[0].hasOwnProperty("objectid")){ + objectid = "objectid"; + } + + if(addResults[0].hasOwnProperty("objectId")){ + objectid = "objectId"; + } + + if(addResults[0].hasOwnProperty("OBJECTID")){ + objectid = "OBJECTID"; + } + + // ??? These are the most common objectid values. I may have missed some! + + // Some feature layers will return different casing such as: 'objectid', 'objectId' and 'OBJECTID' + // Normalize these values to the feature type OID so that we don't break other aspects + // of the JS API. + adds[0].attributes[that._esriFieldTypeOID] = addResults[0][objectid]; var graphic = new Graphic(adds[0].geometry,null,adds[0].attributes); layer.add(graphic); } @@ -978,11 +1009,12 @@ define([ if( req.status === 200 && req.responseText !== "") { try { - var obj = JSON.parse(this.response); + // var b = this.responseText.replace(/"/g, "'"); // jshint ignore:line + var obj = JSON.parse(this.responseText); callback(obj.addResults, obj.updateResults, obj.deleteResults); } catch(err) { - console.error("EDIT REQUEST REPONSE WAS NOT SUCCESSFUL:", req); + console.error("FAILED TO PARSE EDIT REQUEST RESPONSE:", req); errback("Unable to parse xhr response", req); } } diff --git a/dist/offline-tiles-advanced-src.js b/dist/offline-tiles-advanced-src.js index a2951767..24cfde7e 100644 --- a/dist/offline-tiles-advanced-src.js +++ b/dist/offline-tiles-advanced-src.js @@ -1,4 +1,4 @@ -/*! esri-offline-maps - v3.6.0 - 2016-10-17 +/*! esri-offline-maps - v3.7.0 - 2016-11-01 * 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 d06bb144..3d4397b1 100644 --- a/dist/offline-tiles-basic-src.js +++ b/dist/offline-tiles-basic-src.js @@ -1,4 +1,4 @@ -/*! esri-offline-maps - v3.6.0 - 2016-10-17 +/*! esri-offline-maps - v3.7.0 - 2016-11-01 * 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 9bf37d31..df213bcc 100644 --- a/dist/offline-tpk-src.js +++ b/dist/offline-tpk-src.js @@ -1,4 +1,4 @@ -/*! esri-offline-maps - v3.6.0 - 2016-10-17 +/*! esri-offline-maps - v3.7.0 - 2016-11-01 * Copyright (c) 2016 Environmental Systems Research Institute, Inc. * Apache License*/ /** @@ -815,7 +815,7 @@ O.esri.Tiles.TilesStore = function(){ * @param url * @param callback */ - this.retrieve = function(url,callback) + this.retrieve = function(/* String */ url,callback) { if(this._db !== null) { @@ -4916,560 +4916,560 @@ 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 - Original sources are available at https://code.google.com/p/x2js/ - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -O.esri.TPK.X2JS = function(config){ - 'use strict'; - - var VERSION = "1.1.5"; - - var config = config || {}; - initConfigDefaults(); - initRequiredPolyfills(); - - function initConfigDefaults() { - if (config.escapeMode === undefined) { - config.escapeMode = true; - } - config.attributePrefix = config.attributePrefix || "_"; - config.arrayAccessForm = config.arrayAccessForm || "none"; - config.emptyNodeForm = config.emptyNodeForm || "text"; - if (config.enableToStringFunc === undefined) { - config.enableToStringFunc = true; - } - config.arrayAccessFormPaths = config.arrayAccessFormPaths || []; - if (config.skipEmptyTextNodesForObj === undefined) { - config.skipEmptyTextNodesForObj = true; - } - if (config.stripWhitespaces === undefined) { - config.stripWhitespaces = true; - } - config.datetimeAccessFormPaths = config.datetimeAccessFormPaths || []; - } - - var DOMNodeTypes = { - ELEMENT_NODE: 1, - TEXT_NODE: 3, - CDATA_SECTION_NODE: 4, - COMMENT_NODE: 8, - DOCUMENT_NODE: 9 - }; - - function initRequiredPolyfills() { - function pad(number) { - var r = String(number); - if (r.length === 1) { - r = '0' + r; - } - return r; - } - - // Hello IE8- - if (typeof String.prototype.trim !== 'function') { - String.prototype.trim = function () { - return this.replace(/^\s+|^\n+|(\s|\n)+$/g, ''); - } - } - if (typeof Date.prototype.toISOString !== 'function') { - // Implementation from http://stackoverflow.com/questions/2573521/how-do-i-output-an-iso-8601-formatted-string-in-javascript - Date.prototype.toISOString = function () { - return this.getUTCFullYear() - + '-' + pad(this.getUTCMonth() + 1) - + '-' + pad(this.getUTCDate()) - + 'T' + pad(this.getUTCHours()) - + ':' + pad(this.getUTCMinutes()) - + ':' + pad(this.getUTCSeconds()) - + '.' + String((this.getUTCMilliseconds() / 1000).toFixed(3)).slice(2, 5) - + 'Z'; - }; - } - } - - function getNodeLocalName(node) { - var nodeLocalName = node.localName; - if (nodeLocalName == null) // Yeah, this is IE!! - nodeLocalName = node.baseName; - if (nodeLocalName == null || nodeLocalName == "") // =="" is IE too - nodeLocalName = node.nodeName; - return nodeLocalName; - } - - function getNodePrefix(node) { - return node.prefix; - } - - function escapeXmlChars(str) { - if (typeof(str) == "string") - return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g, '/'); - else - return str; - } - - function unescapeXmlChars(str) { - return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'").replace(///g, '\/'); - } - - function toArrayAccessForm(obj, childName, path) { - switch (config.arrayAccessForm) { - case "property": - if (!(obj[childName] instanceof Array)) - obj[childName + "_asArray"] = [obj[childName]]; - else - obj[childName + "_asArray"] = obj[childName]; - break; - /*case "none": - break;*/ - } - - if (!(obj[childName] instanceof Array) && config.arrayAccessFormPaths.length > 0) { - var idx = 0; - for (; idx < config.arrayAccessFormPaths.length; idx++) { - var arrayPath = config.arrayAccessFormPaths[idx]; - if (typeof arrayPath === "string") { - if (arrayPath == path) - break; - } - else if (arrayPath instanceof RegExp) { - if (arrayPath.test(path)) - break; - } - else if (typeof arrayPath === "function") { - if (arrayPath(obj, childName, path)) - break; - } - } - if (idx != config.arrayAccessFormPaths.length) { - obj[childName] = [obj[childName]]; - } - } - } - - function fromXmlDateTime(prop) { - // Implementation based up on http://stackoverflow.com/questions/8178598/xml-datetime-to-javascript-date-object - // Improved to support full spec and optional parts - var bits = prop.split(/[-T:+Z]/g); - - var d = new Date(bits[0], bits[1] - 1, bits[2]); - var secondBits = bits[5].split("\."); - d.setHours(bits[3], bits[4], secondBits[0]); - if (secondBits.length > 1) - d.setMilliseconds(secondBits[1]); - - // Get supplied time zone offset in minutes - if (bits[6] && bits[7]) { - var offsetMinutes = bits[6] * 60 + Number(bits[7]); - var sign = /\d\d-\d\d:\d\d$/.test(prop) ? '-' : '+'; - - // Apply the sign - offsetMinutes = 0 + (sign == '-' ? -1 * offsetMinutes : offsetMinutes); - - // Apply offset and local timezone - d.setMinutes(d.getMinutes() - offsetMinutes - d.getTimezoneOffset()) - } - else if (prop.indexOf("Z", prop.length - 1) !== -1) { - d = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds())); - } - - // d is now a local time equivalent to the supplied time - return d; - } - - function checkFromXmlDateTimePaths(value, childName, fullPath) { - if (config.datetimeAccessFormPaths.length > 0) { - var path = fullPath.split("\.#")[0]; - var idx = 0; - for (; idx < config.datetimeAccessFormPaths.length; idx++) { - var dtPath = config.datetimeAccessFormPaths[idx]; - if (typeof dtPath === "string") { - if (dtPath == path) - break; - } - else if (dtPath instanceof RegExp) { - if (dtPath.test(path)) - break; - } - else if (typeof dtPath === "function") { - if (dtPath(obj, childName, path)) - break; - } - } - if (idx != config.datetimeAccessFormPaths.length) { - return fromXmlDateTime(value); - } - else - return value; - } - else - return value; - } - - function parseDOMChildren(node, path) { - if (node.nodeType == DOMNodeTypes.DOCUMENT_NODE) { - var result = new Object; - var nodeChildren = node.childNodes; - // Alternative for firstElementChild which is not supported in some environments - for (var cidx = 0; cidx < nodeChildren.length; cidx++) { - var child = nodeChildren.item(cidx); - if (child.nodeType == DOMNodeTypes.ELEMENT_NODE) { - var childName = getNodeLocalName(child); - result[childName] = parseDOMChildren(child, childName); - } - } - return result; - } - else if (node.nodeType == DOMNodeTypes.ELEMENT_NODE) { - var result = new Object; - result.__cnt = 0; - - var nodeChildren = node.childNodes; - - // Children nodes - for (var cidx = 0; cidx < nodeChildren.length; cidx++) { - var child = nodeChildren.item(cidx); // nodeChildren[cidx]; - var childName = getNodeLocalName(child); - - if (child.nodeType != DOMNodeTypes.COMMENT_NODE) { - result.__cnt++; - if (result[childName] == null) { - result[childName] = parseDOMChildren(child, path + "." + childName); - toArrayAccessForm(result, childName, path + "." + childName); - } - else { - if (result[childName] != null) { - if (!(result[childName] instanceof Array)) { - result[childName] = [result[childName]]; - toArrayAccessForm(result, childName, path + "." + childName); - } - } - (result[childName])[result[childName].length] = parseDOMChildren(child, path + "." + childName); - } - } - } - - // Attributes - for (var aidx = 0; aidx < node.attributes.length; aidx++) { - var attr = node.attributes.item(aidx); // [aidx]; - result.__cnt++; - result[config.attributePrefix + attr.name] = attr.value; - } - - // Node namespace prefix - var nodePrefix = getNodePrefix(node); - if (nodePrefix != null && nodePrefix != "") { - result.__cnt++; - result.__prefix = nodePrefix; - } - - if (result["#text"] != null) { - result.__text = result["#text"]; - if (result.__text instanceof Array) { - result.__text = result.__text.join("\n"); - } - if (config.escapeMode) - result.__text = unescapeXmlChars(result.__text); - if (config.stripWhitespaces) - result.__text = result.__text.trim(); - delete result["#text"]; - if (config.arrayAccessForm == "property") - delete result["#text_asArray"]; - result.__text = checkFromXmlDateTimePaths(result.__text, childName, path + "." + childName); - } - if (result["#cdata-section"] != null) { - result.__cdata = result["#cdata-section"]; - delete result["#cdata-section"]; - if (config.arrayAccessForm == "property") - delete result["#cdata-section_asArray"]; - } - - if (result.__cnt == 1 && result.__text != null) { - result = result.__text; - } - else if (result.__cnt == 0 && config.emptyNodeForm == "text") { - result = ''; - } - else if (result.__cnt > 1 && result.__text != null && config.skipEmptyTextNodesForObj) { - if ((config.stripWhitespaces && result.__text == "") || (result.__text.trim() == "")) { - delete result.__text; - } - } - delete result.__cnt; - - if (config.enableToStringFunc && (result.__text != null || result.__cdata != null )) { - result.toString = function () { - return (this.__text != null ? this.__text : '') + ( this.__cdata != null ? this.__cdata : ''); - }; - } - - return result; - } - else if (node.nodeType == DOMNodeTypes.TEXT_NODE || node.nodeType == DOMNodeTypes.CDATA_SECTION_NODE) { - return node.nodeValue; - } - } - - function startTag(jsonObj, element, attrList, closed) { - var resultStr = "<" + ( (jsonObj != null && jsonObj.__prefix != null) ? (jsonObj.__prefix + ":") : "") + element; - if (attrList != null) { - for (var aidx = 0; aidx < attrList.length; aidx++) { - var attrName = attrList[aidx]; - var attrVal = jsonObj[attrName]; - if (config.escapeMode) - attrVal = escapeXmlChars(attrVal); - resultStr += " " + attrName.substr(config.attributePrefix.length) + "='" + attrVal + "'"; - } - } - if (!closed) - resultStr += ">"; - else - resultStr += "/>"; - return resultStr; - } - - function endTag(jsonObj, elementName) { - return ""; - } - - function endsWith(str, suffix) { - return str.indexOf(suffix, str.length - suffix.length) !== -1; - } - - function jsonXmlSpecialElem(jsonObj, jsonObjField) { - if ((config.arrayAccessForm == "property" && endsWith(jsonObjField.toString(), ("_asArray"))) - || jsonObjField.toString().indexOf(config.attributePrefix) == 0 - || jsonObjField.toString().indexOf("__") == 0 - || (jsonObj[jsonObjField] instanceof Function)) - return true; - else - return false; - } - - function jsonXmlElemCount(jsonObj) { - var elementsCnt = 0; - if (jsonObj instanceof Object) { - for (var it in jsonObj) { - if (jsonXmlSpecialElem(jsonObj, it)) - continue; - elementsCnt++; - } - } - return elementsCnt; - } - - function parseJSONAttributes(jsonObj) { - var attrList = []; - if (jsonObj instanceof Object) { - for (var ait in jsonObj) { - if (ait.toString().indexOf("__") == -1 && ait.toString().indexOf(config.attributePrefix) == 0) { - attrList.push(ait); - } - } - } - return attrList; - } - - function parseJSONTextAttrs(jsonTxtObj) { - var result = ""; - - if (jsonTxtObj.__cdata != null) { - result += ""; - } - - if (jsonTxtObj.__text != null) { - if (config.escapeMode) - result += escapeXmlChars(jsonTxtObj.__text); - else - result += jsonTxtObj.__text; - } - return result; - } - - function parseJSONTextObject(jsonTxtObj) { - var result = ""; - - if (jsonTxtObj instanceof Object) { - result += parseJSONTextAttrs(jsonTxtObj); - } - else if (jsonTxtObj != null) { - if (config.escapeMode) - result += escapeXmlChars(jsonTxtObj); - else - result += jsonTxtObj; - } - - return result; - } - - function parseJSONArray(jsonArrRoot, jsonArrObj, attrList) { - var result = ""; - if (jsonArrRoot.length == 0) { - result += startTag(jsonArrRoot, jsonArrObj, attrList, true); - } - else { - for (var arIdx = 0; arIdx < jsonArrRoot.length; arIdx++) { - result += startTag(jsonArrRoot[arIdx], jsonArrObj, parseJSONAttributes(jsonArrRoot[arIdx]), false); - result += parseJSONObject(jsonArrRoot[arIdx]); - result += endTag(jsonArrRoot[arIdx], jsonArrObj); - } - } - return result; - } - - function parseJSONObject(jsonObj) { - var result = ""; - - var elementsCnt = jsonXmlElemCount(jsonObj); - - if (elementsCnt > 0) { - for (var it in jsonObj) { - - if (jsonXmlSpecialElem(jsonObj, it)) - continue; - - var subObj = jsonObj[it]; - - var attrList = parseJSONAttributes(subObj) - - if (subObj == null || subObj == undefined) { - result += startTag(subObj, it, attrList, true); - } - else if (subObj instanceof Object) { - - if (subObj instanceof Array) { - result += parseJSONArray(subObj, it, attrList); - } - else if (subObj instanceof Date) { - result += startTag(subObj, it, attrList, false); - result += subObj.toISOString(); - result += endTag(subObj, it); - } - else { - var subObjElementsCnt = jsonXmlElemCount(subObj); - if (subObjElementsCnt > 0 || subObj.__text != null || subObj.__cdata != null) { - result += startTag(subObj, it, attrList, false); - result += parseJSONObject(subObj); - result += endTag(subObj, it); - } - else { - result += startTag(subObj, it, attrList, true); - } - } - } - else { - result += startTag(subObj, it, attrList, false); - result += parseJSONTextObject(subObj); - result += endTag(subObj, it); - } - } - } - result += parseJSONTextObject(jsonObj); - - return result; - } - - this.parseXmlString = function (xmlDocStr) { - var isIEParser = window.ActiveXObject || "ActiveXObject" in window; - if (xmlDocStr === undefined) { - return null; - } - var xmlDoc; - if (window.DOMParser) { - var parser = new window.DOMParser(); - var parsererrorNS = null; - // IE9+ now is here - if (!isIEParser) { - try { - parsererrorNS = parser.parseFromString("INVALID", "text/xml").childNodes[0].namespaceURI; - } - catch (err) { - parsererrorNS = null; - } - } - try { - xmlDoc = parser.parseFromString(xmlDocStr, "text/xml"); - if (parsererrorNS != null && xmlDoc.getElementsByTagNameNS(parsererrorNS, "parsererror").length > 0) { - //throw new Error('Error parsing XML: '+xmlDocStr); - xmlDoc = null; - } - } - catch (err) { - xmlDoc = null; - } - } - else { - // IE :( - if (xmlDocStr.indexOf("") + 2); - } - xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); - xmlDoc.async = "false"; - xmlDoc.loadXML(xmlDocStr); - } - return xmlDoc; - }; - - this.asArray = function (prop) { - if (prop instanceof Array) - return prop; - else - return [prop]; - }; - - this.toXmlDateTime = function (dt) { - if (dt instanceof Date) - return dt.toISOString(); - else if (typeof(dt) === 'number') - return new Date(dt).toISOString(); - else - return null; - }; - - this.asDateTime = function (prop) { - if (typeof(prop) == "string") { - return fromXmlDateTime(prop); - } - else - return prop; - }; - - this.xml2json = function (xmlDoc) { - return parseDOMChildren(xmlDoc); - }; - - this.xml_str2json = function(xmlDocStr) { - var xmlDoc = this.parseXmlString(xmlDocStr); - if (xmlDoc != null) - return this.xml2json(xmlDoc); - else - return null; - }; - - this.json2xml_str = function (jsonObj) { - return parseJSONObject(jsonObj); - }; - - this.json2xml = function (jsonObj) { - var xmlDocStr = this.json2xml_str(jsonObj); - return this.parseXmlString(xmlDocStr); - }; - - this.getVersion = function () { - return VERSION; - }; - +//https://github.com/abdmob/x2js/blob/master/xml2json.js +/* + Copyright 2011-2013 Abdulla Abdurakhmanov + Original sources are available at https://code.google.com/p/x2js/ + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +O.esri.TPK.X2JS = function(config){ + 'use strict'; + + var VERSION = "1.1.5"; + + var config = config || {}; + initConfigDefaults(); + initRequiredPolyfills(); + + function initConfigDefaults() { + if (config.escapeMode === undefined) { + config.escapeMode = true; + } + config.attributePrefix = config.attributePrefix || "_"; + config.arrayAccessForm = config.arrayAccessForm || "none"; + config.emptyNodeForm = config.emptyNodeForm || "text"; + if (config.enableToStringFunc === undefined) { + config.enableToStringFunc = true; + } + config.arrayAccessFormPaths = config.arrayAccessFormPaths || []; + if (config.skipEmptyTextNodesForObj === undefined) { + config.skipEmptyTextNodesForObj = true; + } + if (config.stripWhitespaces === undefined) { + config.stripWhitespaces = true; + } + config.datetimeAccessFormPaths = config.datetimeAccessFormPaths || []; + } + + var DOMNodeTypes = { + ELEMENT_NODE: 1, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9 + }; + + function initRequiredPolyfills() { + function pad(number) { + var r = String(number); + if (r.length === 1) { + r = '0' + r; + } + return r; + } + + // Hello IE8- + if (typeof String.prototype.trim !== 'function') { + String.prototype.trim = function () { + return this.replace(/^\s+|^\n+|(\s|\n)+$/g, ''); + } + } + if (typeof Date.prototype.toISOString !== 'function') { + // Implementation from http://stackoverflow.com/questions/2573521/how-do-i-output-an-iso-8601-formatted-string-in-javascript + Date.prototype.toISOString = function () { + return this.getUTCFullYear() + + '-' + pad(this.getUTCMonth() + 1) + + '-' + pad(this.getUTCDate()) + + 'T' + pad(this.getUTCHours()) + + ':' + pad(this.getUTCMinutes()) + + ':' + pad(this.getUTCSeconds()) + + '.' + String((this.getUTCMilliseconds() / 1000).toFixed(3)).slice(2, 5) + + 'Z'; + }; + } + } + + function getNodeLocalName(node) { + var nodeLocalName = node.localName; + if (nodeLocalName == null) // Yeah, this is IE!! + nodeLocalName = node.baseName; + if (nodeLocalName == null || nodeLocalName == "") // =="" is IE too + nodeLocalName = node.nodeName; + return nodeLocalName; + } + + function getNodePrefix(node) { + return node.prefix; + } + + function escapeXmlChars(str) { + if (typeof(str) == "string") + return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g, '/'); + else + return str; + } + + function unescapeXmlChars(str) { + return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'").replace(///g, '\/'); + } + + function toArrayAccessForm(obj, childName, path) { + switch (config.arrayAccessForm) { + case "property": + if (!(obj[childName] instanceof Array)) + obj[childName + "_asArray"] = [obj[childName]]; + else + obj[childName + "_asArray"] = obj[childName]; + break; + /*case "none": + break;*/ + } + + if (!(obj[childName] instanceof Array) && config.arrayAccessFormPaths.length > 0) { + var idx = 0; + for (; idx < config.arrayAccessFormPaths.length; idx++) { + var arrayPath = config.arrayAccessFormPaths[idx]; + if (typeof arrayPath === "string") { + if (arrayPath == path) + break; + } + else if (arrayPath instanceof RegExp) { + if (arrayPath.test(path)) + break; + } + else if (typeof arrayPath === "function") { + if (arrayPath(obj, childName, path)) + break; + } + } + if (idx != config.arrayAccessFormPaths.length) { + obj[childName] = [obj[childName]]; + } + } + } + + function fromXmlDateTime(prop) { + // Implementation based up on http://stackoverflow.com/questions/8178598/xml-datetime-to-javascript-date-object + // Improved to support full spec and optional parts + var bits = prop.split(/[-T:+Z]/g); + + var d = new Date(bits[0], bits[1] - 1, bits[2]); + var secondBits = bits[5].split("\."); + d.setHours(bits[3], bits[4], secondBits[0]); + if (secondBits.length > 1) + d.setMilliseconds(secondBits[1]); + + // Get supplied time zone offset in minutes + if (bits[6] && bits[7]) { + var offsetMinutes = bits[6] * 60 + Number(bits[7]); + var sign = /\d\d-\d\d:\d\d$/.test(prop) ? '-' : '+'; + + // Apply the sign + offsetMinutes = 0 + (sign == '-' ? -1 * offsetMinutes : offsetMinutes); + + // Apply offset and local timezone + d.setMinutes(d.getMinutes() - offsetMinutes - d.getTimezoneOffset()) + } + else if (prop.indexOf("Z", prop.length - 1) !== -1) { + d = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds())); + } + + // d is now a local time equivalent to the supplied time + return d; + } + + function checkFromXmlDateTimePaths(value, childName, fullPath) { + if (config.datetimeAccessFormPaths.length > 0) { + var path = fullPath.split("\.#")[0]; + var idx = 0; + for (; idx < config.datetimeAccessFormPaths.length; idx++) { + var dtPath = config.datetimeAccessFormPaths[idx]; + if (typeof dtPath === "string") { + if (dtPath == path) + break; + } + else if (dtPath instanceof RegExp) { + if (dtPath.test(path)) + break; + } + else if (typeof dtPath === "function") { + if (dtPath(obj, childName, path)) + break; + } + } + if (idx != config.datetimeAccessFormPaths.length) { + return fromXmlDateTime(value); + } + else + return value; + } + else + return value; + } + + function parseDOMChildren(node, path) { + if (node.nodeType == DOMNodeTypes.DOCUMENT_NODE) { + var result = new Object; + var nodeChildren = node.childNodes; + // Alternative for firstElementChild which is not supported in some environments + for (var cidx = 0; cidx < nodeChildren.length; cidx++) { + var child = nodeChildren.item(cidx); + if (child.nodeType == DOMNodeTypes.ELEMENT_NODE) { + var childName = getNodeLocalName(child); + result[childName] = parseDOMChildren(child, childName); + } + } + return result; + } + else if (node.nodeType == DOMNodeTypes.ELEMENT_NODE) { + var result = new Object; + result.__cnt = 0; + + var nodeChildren = node.childNodes; + + // Children nodes + for (var cidx = 0; cidx < nodeChildren.length; cidx++) { + var child = nodeChildren.item(cidx); // nodeChildren[cidx]; + var childName = getNodeLocalName(child); + + if (child.nodeType != DOMNodeTypes.COMMENT_NODE) { + result.__cnt++; + if (result[childName] == null) { + result[childName] = parseDOMChildren(child, path + "." + childName); + toArrayAccessForm(result, childName, path + "." + childName); + } + else { + if (result[childName] != null) { + if (!(result[childName] instanceof Array)) { + result[childName] = [result[childName]]; + toArrayAccessForm(result, childName, path + "." + childName); + } + } + (result[childName])[result[childName].length] = parseDOMChildren(child, path + "." + childName); + } + } + } + + // Attributes + for (var aidx = 0; aidx < node.attributes.length; aidx++) { + var attr = node.attributes.item(aidx); // [aidx]; + result.__cnt++; + result[config.attributePrefix + attr.name] = attr.value; + } + + // Node namespace prefix + var nodePrefix = getNodePrefix(node); + if (nodePrefix != null && nodePrefix != "") { + result.__cnt++; + result.__prefix = nodePrefix; + } + + if (result["#text"] != null) { + result.__text = result["#text"]; + if (result.__text instanceof Array) { + result.__text = result.__text.join("\n"); + } + if (config.escapeMode) + result.__text = unescapeXmlChars(result.__text); + if (config.stripWhitespaces) + result.__text = result.__text.trim(); + delete result["#text"]; + if (config.arrayAccessForm == "property") + delete result["#text_asArray"]; + result.__text = checkFromXmlDateTimePaths(result.__text, childName, path + "." + childName); + } + if (result["#cdata-section"] != null) { + result.__cdata = result["#cdata-section"]; + delete result["#cdata-section"]; + if (config.arrayAccessForm == "property") + delete result["#cdata-section_asArray"]; + } + + if (result.__cnt == 1 && result.__text != null) { + result = result.__text; + } + else if (result.__cnt == 0 && config.emptyNodeForm == "text") { + result = ''; + } + else if (result.__cnt > 1 && result.__text != null && config.skipEmptyTextNodesForObj) { + if ((config.stripWhitespaces && result.__text == "") || (result.__text.trim() == "")) { + delete result.__text; + } + } + delete result.__cnt; + + if (config.enableToStringFunc && (result.__text != null || result.__cdata != null )) { + result.toString = function () { + return (this.__text != null ? this.__text : '') + ( this.__cdata != null ? this.__cdata : ''); + }; + } + + return result; + } + else if (node.nodeType == DOMNodeTypes.TEXT_NODE || node.nodeType == DOMNodeTypes.CDATA_SECTION_NODE) { + return node.nodeValue; + } + } + + function startTag(jsonObj, element, attrList, closed) { + var resultStr = "<" + ( (jsonObj != null && jsonObj.__prefix != null) ? (jsonObj.__prefix + ":") : "") + element; + if (attrList != null) { + for (var aidx = 0; aidx < attrList.length; aidx++) { + var attrName = attrList[aidx]; + var attrVal = jsonObj[attrName]; + if (config.escapeMode) + attrVal = escapeXmlChars(attrVal); + resultStr += " " + attrName.substr(config.attributePrefix.length) + "='" + attrVal + "'"; + } + } + if (!closed) + resultStr += ">"; + else + resultStr += "/>"; + return resultStr; + } + + function endTag(jsonObj, elementName) { + return ""; + } + + function endsWith(str, suffix) { + return str.indexOf(suffix, str.length - suffix.length) !== -1; + } + + function jsonXmlSpecialElem(jsonObj, jsonObjField) { + if ((config.arrayAccessForm == "property" && endsWith(jsonObjField.toString(), ("_asArray"))) + || jsonObjField.toString().indexOf(config.attributePrefix) == 0 + || jsonObjField.toString().indexOf("__") == 0 + || (jsonObj[jsonObjField] instanceof Function)) + return true; + else + return false; + } + + function jsonXmlElemCount(jsonObj) { + var elementsCnt = 0; + if (jsonObj instanceof Object) { + for (var it in jsonObj) { + if (jsonXmlSpecialElem(jsonObj, it)) + continue; + elementsCnt++; + } + } + return elementsCnt; + } + + function parseJSONAttributes(jsonObj) { + var attrList = []; + if (jsonObj instanceof Object) { + for (var ait in jsonObj) { + if (ait.toString().indexOf("__") == -1 && ait.toString().indexOf(config.attributePrefix) == 0) { + attrList.push(ait); + } + } + } + return attrList; + } + + function parseJSONTextAttrs(jsonTxtObj) { + var result = ""; + + if (jsonTxtObj.__cdata != null) { + result += ""; + } + + if (jsonTxtObj.__text != null) { + if (config.escapeMode) + result += escapeXmlChars(jsonTxtObj.__text); + else + result += jsonTxtObj.__text; + } + return result; + } + + function parseJSONTextObject(jsonTxtObj) { + var result = ""; + + if (jsonTxtObj instanceof Object) { + result += parseJSONTextAttrs(jsonTxtObj); + } + else if (jsonTxtObj != null) { + if (config.escapeMode) + result += escapeXmlChars(jsonTxtObj); + else + result += jsonTxtObj; + } + + return result; + } + + function parseJSONArray(jsonArrRoot, jsonArrObj, attrList) { + var result = ""; + if (jsonArrRoot.length == 0) { + result += startTag(jsonArrRoot, jsonArrObj, attrList, true); + } + else { + for (var arIdx = 0; arIdx < jsonArrRoot.length; arIdx++) { + result += startTag(jsonArrRoot[arIdx], jsonArrObj, parseJSONAttributes(jsonArrRoot[arIdx]), false); + result += parseJSONObject(jsonArrRoot[arIdx]); + result += endTag(jsonArrRoot[arIdx], jsonArrObj); + } + } + return result; + } + + function parseJSONObject(jsonObj) { + var result = ""; + + var elementsCnt = jsonXmlElemCount(jsonObj); + + if (elementsCnt > 0) { + for (var it in jsonObj) { + + if (jsonXmlSpecialElem(jsonObj, it)) + continue; + + var subObj = jsonObj[it]; + + var attrList = parseJSONAttributes(subObj) + + if (subObj == null || subObj == undefined) { + result += startTag(subObj, it, attrList, true); + } + else if (subObj instanceof Object) { + + if (subObj instanceof Array) { + result += parseJSONArray(subObj, it, attrList); + } + else if (subObj instanceof Date) { + result += startTag(subObj, it, attrList, false); + result += subObj.toISOString(); + result += endTag(subObj, it); + } + else { + var subObjElementsCnt = jsonXmlElemCount(subObj); + if (subObjElementsCnt > 0 || subObj.__text != null || subObj.__cdata != null) { + result += startTag(subObj, it, attrList, false); + result += parseJSONObject(subObj); + result += endTag(subObj, it); + } + else { + result += startTag(subObj, it, attrList, true); + } + } + } + else { + result += startTag(subObj, it, attrList, false); + result += parseJSONTextObject(subObj); + result += endTag(subObj, it); + } + } + } + result += parseJSONTextObject(jsonObj); + + return result; + } + + this.parseXmlString = function (xmlDocStr) { + var isIEParser = window.ActiveXObject || "ActiveXObject" in window; + if (xmlDocStr === undefined) { + return null; + } + var xmlDoc; + if (window.DOMParser) { + var parser = new window.DOMParser(); + var parsererrorNS = null; + // IE9+ now is here + if (!isIEParser) { + try { + parsererrorNS = parser.parseFromString("INVALID", "text/xml").childNodes[0].namespaceURI; + } + catch (err) { + parsererrorNS = null; + } + } + try { + xmlDoc = parser.parseFromString(xmlDocStr, "text/xml"); + if (parsererrorNS != null && xmlDoc.getElementsByTagNameNS(parsererrorNS, "parsererror").length > 0) { + //throw new Error('Error parsing XML: '+xmlDocStr); + xmlDoc = null; + } + } + catch (err) { + xmlDoc = null; + } + } + else { + // IE :( + if (xmlDocStr.indexOf("") + 2); + } + xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); + xmlDoc.async = "false"; + xmlDoc.loadXML(xmlDocStr); + } + return xmlDoc; + }; + + this.asArray = function (prop) { + if (prop instanceof Array) + return prop; + else + return [prop]; + }; + + this.toXmlDateTime = function (dt) { + if (dt instanceof Date) + return dt.toISOString(); + else if (typeof(dt) === 'number') + return new Date(dt).toISOString(); + else + return null; + }; + + this.asDateTime = function (prop) { + if (typeof(prop) == "string") { + return fromXmlDateTime(prop); + } + else + return prop; + }; + + this.xml2json = function (xmlDoc) { + return parseDOMChildren(xmlDoc); + }; + + this.xml_str2json = function(xmlDocStr) { + var xmlDoc = this.parseXmlString(xmlDocStr); + if (xmlDoc != null) + return this.xml2json(xmlDoc); + else + return null; + }; + + this.json2xml_str = function (jsonObj) { + return parseJSONObject(jsonObj); + }; + + this.json2xml = function (jsonObj) { + var xmlDocStr = this.json2xml_str(jsonObj); + return this.parseXmlString(xmlDocStr); + }; + + this.getVersion = function () { + return VERSION; + }; + } \ No newline at end of file diff --git a/package.json b/package.json index 0cf4dc85..3d67a8cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "esri-offline-maps", - "version": "3.6.0", + "version": "3.7.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/package.json b/samples/package.json index 48dba47e..d47d60fe 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.6.0", + "version": "3.7.0", "private": true, "description": "manifest generator project", "repository": {