From 4bc476a57a1e2d4e1cc22abb9ef59c198c997d7f Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Thu, 19 Nov 2015 17:48:03 -0700 Subject: [PATCH 01/48] simple edit library initial commit --- lib/edit/OfflineFeatureLayer.js | 1003 +++++++++++++++++++++++++++++++ lib/edit/editStorePOLS.js | 420 +++++++++++++ lib/edit/offlineJSOptions.js | 13 + 3 files changed, 1436 insertions(+) create mode 100644 lib/edit/OfflineFeatureLayer.js create mode 100644 lib/edit/editStorePOLS.js create mode 100644 lib/edit/offlineJSOptions.js diff --git a/lib/edit/OfflineFeatureLayer.js b/lib/edit/OfflineFeatureLayer.js new file mode 100644 index 00000000..6973bfa2 --- /dev/null +++ b/lib/edit/OfflineFeatureLayer.js @@ -0,0 +1,1003 @@ +/*jshint -W030 */ +/** + * This library is optimized for Partial Offline Support ONLY + */ +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 (Evented, Deferred, all, declare, array, domAttr, domStyle, query, on, + esriConfig, GraphicsLayer, FeatureLayer, Graphic) { + "use strict"; + return declare("O.esri.Edit.OfflineFeatureLayer", [Evented], + { + _onlineStatus: "online", + _featureLayers: {}, + _editStore: new O.esri.Edit.EditStorePOLS(), + _defaultXhrTimeout: 15000, // ms + _autoOfflineDetect: false, + + ONLINE: "online", // all edits will directly go to the server + OFFLINE: "offline", // edits will be enqueued + RECONNECTING: "reconnecting", // sending stored edits to the server + proxyPath: null, // by default we use CORS and therefore proxyPath is null + + // Database properties + DB_NAME: "features_store", // Sets the database name. + DB_OBJECTSTORE_NAME: "features",// Represents an object store that allows access to a set of data in the IndexedDB database + DB_UID: "objectid", // Set this based on the unique identifier is set up in the feature service + + // manager emits event when... + events: { + EDITS_SENT: "edits-sent", // ...whenever any edit is actually sent to the server + EDITS_ENQUEUED: "edits-enqueued", // ...when an edit is enqueued (and not sent to the server) + EDITS_ENQUEUED_ERROR: "edits-enqueued-error", // ...when there is an error during the queing process + }, + + constructor: function(options){ + if(options && options.hasOwnProperty("autoDetect")){ + this._autoOfflineDetect = options.autoDetect; + } + }, + + /** + * Overrides a feature layer. Call this AFTER the FeatureLayer's 'update-end' event. + * objects such as [esri.Graphic] will need to be serialized or you will get an IndexedDB error. + * @param layer + * @param updateEndEvent The FeatureLayer's update-end event object + * @param callback {true, null} or {false, errorString} Traps whether or not the database initialized + * @returns deferred + */ + extend: function (layer, callback) { + + var extendPromises = []; // deferred promises related to initializing this method + + var self = this; + layer.offlineExtended = true; // to identify layer has been extended + + if(!layer.loaded || layer._url === null) { + console.error("Make sure to initialize OfflineFeaturesManager after layer loaded and feature layer update-end event."); + } + + // NOTE: At v2.6.1 we've discovered that not all feature layers support objectIdField. + // However, to try to be consistent here with how the library is managing Ids + // we force the layer.objectIdField to DB_UID. This should be consistent with + // how esri.Graphics assign a unique ID to a graphic. If it is not, then this + // library will break and we'll have to re-architect how it manages UIDs. + layer.objectIdField = this.DB_UID; + + var url = null; + + // There have been reproducible use cases showing when a browser is restarted offline that + // for some reason the layer.url may be undefined. + // This is an attempt to minimize the possibility of that situation causing errors. + if(layer.url) { + url = layer.url; + // we keep track of the FeatureLayer object + this._featureLayers[layer.url] = layer; + } + + // Initialize the database as well as set offline data. + if(!this._editStore._isDBInit) { + extendPromises.push(this._initializeDB(url)); + } + + if(this._autoOfflineDetect){ + Offline.on('up', self.goOnline(function(success,error){ // jshint ignore:line + + })); + + Offline.on('down', self.goOffline()); // jshint ignore:line + } + + // replace the applyEdits() method + layer._applyEdits = layer.applyEdits; + + /** + * Overrides the ArcGIS API for JavaSccript applyEdits() method. + * @param adds Creates a new edit entry. + * @param updates Updates an existing entry. + * @param deletes Deletes an existing entry. + * @param callback Called when the operation is complete. + * @param errback An error object is returned if an error occurs + * @returns {*} deferred + * @event EDITS_ENQUEUED boolean if all edits successfully stored while offline + * @event EDITS_ENQUEUED_ERROR string message if there was an error while storing an edit while offline + */ + layer.applyEdits = function (adds, updates, deletes, callback, errback) { + // inside this method, 'this' will be the FeatureLayer + // and 'self' will be the offlineFeatureLayer object + var promises = []; + + if (self.getOnlineStatus() === self.ONLINE) { + var def = layer._applyEdits(adds, updates, deletes, + function () { + self.emit(self.events.EDITS_SENT, arguments); + callback && callback.apply(this, arguments); + }, + errback); + return def; + } + + var deferred1 = new Deferred(); + var results = {addResults: [], updateResults: [], deleteResults: []}; + var updatesMap = {}; + + var _adds = adds || []; + _adds.forEach(function (addEdit) { + var deferred = new Deferred(); + + var objectId = this._getNextTempId(); + + addEdit.attributes[this.objectIdField] = objectId; + + var thisLayer = this; + + // We need to run some validation tests against each feature being added. + // Adding the same feature multiple times results in the last edit wins. LIFO. + this._validateFeature(addEdit,this.url,self._editStore.ADD).then(function(result){ + console.log("EDIT ADD IS BACK!!! " ); + + if(result.success){ + thisLayer._pushValidatedAddFeatureToDB(thisLayer,addEdit,result.operation,results,objectId,deferred); + } + else{ + // If we get here then we deleted an edit that was added offline. + deferred.resolve(true); + } + + },function(error){ + console.log("_validateFeature: Unable to validate!"); + deferred.reject(error); + }); + + promises.push(deferred); + }, this); + + updates = updates || []; + updates.forEach(function (updateEdit) { + var deferred = new Deferred(); + + var objectId = updateEdit.attributes[this.objectIdField]; + updatesMap[objectId] = updateEdit; + + var thisLayer = this; + + // We need to run some validation tests against each feature being updated. + // If we have added a feature and we need to update it then we change it's operation type to "add" + // and the last edits wins. LIFO. + this._validateFeature(updateEdit,this.url,self._editStore.UPDATE).then(function(result){ + console.log("EDIT UPDATE IS BACK!!! " ); + + if(result.success){ + thisLayer._pushValidatedUpdateFeatureToDB(thisLayer,updateEdit,result.operation,results,objectId,deferred); + } + else{ + // If we get here then we deleted an edit that was added offline. + deferred.resolve(true); + } + + },function(error){ + console.log("_validateFeature: Unable to validate!"); + deferred.reject(error); + }); + + promises.push(deferred); + }, this); + + deletes = deletes || []; + deletes.forEach(function (deleteEdit) { + var deferred = new Deferred(); + + var objectId = deleteEdit.attributes[this.objectIdField]; + + var thisLayer = this; + + // We need to run some validation tests against each feature being deleted. + // If we have added a feature and then deleted it in the app + this._validateFeature(deleteEdit,this.url,self._editStore.DELETE).then(function(result){ + console.log("EDIT DELETE IS BACK!!! " ); + + if(result.success){ + thisLayer._pushValidatedDeleteFeatureToDB(thisLayer,deleteEdit,result.operation,results,objectId,deferred); + } + else{ + // If we get here then we deleted an edit that was added offline. + deferred.resolve(true); + } + + },function(error){ + console.log("_validateFeature: Unable to validate!"); + deferred.reject(error); + }); + + promises.push(deferred); + }, this); + + all(promises).then(function (r) { + // Make sure all edits were successful. If not throw an error. + var promisesSuccess = true; + for (var v = 0; v < r.length; v++) { + if (r[v] === false) { + promisesSuccess = false; + } + } + + promisesSuccess === true ? self.emit(self.events.EDITS_ENQUEUED, results) : self.emit(self.events.EDITS_ENQUEUED_ERROR, results); + // EDITS_ENQUEUED = callback(true, edit), and EDITS_ENQUEUED_ERROR = callback(false, /*String */ error) + this._editHandler(results, _adds, updatesMap, callback, errback, deferred1); + }.bind(this)); + + return deferred1; + + }; // layer.applyEdits() + + /** + * Returns the approximate size of the edits database in bytes + * @param callback callback({usage}, error) Whereas, the usage Object is {sizeBytes: number, editCount: number} + */ + layer.getUsage = function(callback){ + self._editStore.getUsage(function(usage,error){ + callback(usage,error); + }); + }; + + /** + * Full edits database reset. + * CAUTION! If some edits weren't successfully sent, then their record + * will still exist in the database. If you use this function you + * will also delete those records. + * @param callback (boolean, error) + */ + layer.resetDatabase = function(callback){ + self._editStore.resetEditsQueue(function(result,error){ + callback(result,error); + }); + }; + + /** + * Returns the number of edits pending in the database. + * @param callback callback( int ) + */ + layer.pendingEditsCount = function(callback){ + self._editStore.pendingEditsCount(function(count){ + callback(count); + }); + }; + + /** + * Create a featureDefinition + * @param featureLayer + * @param featuresArr + * @param geometryType + * @param callback + */ + layer.getFeatureDefinition = function (/* Object */ featureLayer, /* Array */ featuresArr, /* String */ geometryType, callback) { + + var featureDefinition = { + "layerDefinition": featureLayer, + "featureSet": { + "features": featuresArr, + "geometryType": geometryType + } + + }; + + callback(featureDefinition); + }; + + /** + * Returns an iterable array of all edits stored in the database + * Each item in the array is an object and contains: + * { + * id: "internal ID", + * operation: "add, update or delete", + * layer: "layerURL", + * type: "esri Geometry Type", + * graphic: "esri.Graphic converted to JSON then serialized" + * } + * @param callback (true, array) or (false, errorString) + */ + layer.getAllEditsArray = function(callback){ + self._editStore.getAllEditsArray(function(array,message){ + if(message == "end"){ + callback(true,array); + } + else{ + callback(false,message); + } + }); + }; + + /* internal methods */ + + /** + * Pushes a DELETE request to the database after it's been validated + * @param layer + * @param deleteEdit + * @param operation + * @param resultsArray + * @param objectId + * @param deferred + * @private + */ + layer._pushValidatedDeleteFeatureToDB = function(layer,deleteEdit,operation,resultsArray,objectId,deferred){ + self._editStore.pushEdit(operation, layer.url, deleteEdit, function (result, error) { + + if(result){ + resultsArray.deleteResults.push({success: true, error: null, objectId: objectId}); + + // Use the correct key as set by self.DB_UID + var tempIdObject = {}; + tempIdObject[self.DB_UID] = objectId; + } + else{ + resultsArray.deleteResults.push({success: false, error: error, objectId: objectId}); + } + + deferred.resolve(result); + }); + }; + + /** + * Pushes an UPDATE request to the database after it's been validated + * @param layer + * @param updateEdit + * @param operation + * @param resultsArray + * @param objectId + * @param deferred + * @private + */ + layer._pushValidatedUpdateFeatureToDB = function(layer,updateEdit,operation,resultsArray,objectId,deferred){ + self._editStore.pushEdit(operation, layer.url, updateEdit, function (result, error) { + + if(result){ + resultsArray.updateResults.push({success: true, error: null, objectId: objectId}); + + // Use the correct key as set by self.DB_UID + var tempIdObject = {}; + tempIdObject[self.DB_UID] = objectId; + } + else{ + resultsArray.updateResults.push({success: false, error: error, objectId: objectId}); + } + + deferred.resolve(result); + }); + }; + + /** + * Pushes an ADD request to the database after it's been validated + * @param layer + * @param addEdit + * @param operation + * @param resultsArray + * @param objectId + * @param deferred + * @private + */ + layer._pushValidatedAddFeatureToDB = function(layer,addEdit,operation,resultsArray,objectId,deferred){ + self._editStore.pushEdit(operation, layer.url, addEdit, function (result, error) { + if(result){ + resultsArray.addResults.push({success: true, error: null, objectId: objectId}); + + // Use the correct key as set by self.DB_UID + var tempIdObject = {}; + tempIdObject[self.DB_UID] = objectId; + } + else{ + resultsArray.addResults.push({success: false, error: error, objectId: objectId}); + } + + deferred.resolve(result); + }); + }; + + /** + * Validates duplicate entries. Last edit on same feature can overwite any previous values. + * Note: if an edit was already added offline and you delete it then we return success == false + * @param graphic esri.Graphic. + * @param layerUrl the URL of the feature service + * @param operation add, update or delete action on an edit + * @returns deferred {success:boolean,graphic:graphic,operation:add|update|delete} + * @private + */ + layer._validateFeature = function (graphic,layerUrl,operation) { + + var deferred = new Deferred(); + + var id = layerUrl + "/" + graphic.attributes[self.DB_UID]; + + self._editStore.getEdit(id,function(success,result){ + if (success) { + switch( operation ) + { + case self._editStore.ADD: + // Not good - however we'll allow the new ADD to replace/overwrite existing edit + // and pass it through unmodified. Last ADD wins. + deferred.resolve({"success":true,"graphic":graphic,"operation":operation}); + break; + case self._editStore.UPDATE: + // If we are doing an update on a feature that has not been added to + // the server yet, then we need to maintain its operation as an ADD + // and not an UPDATE. This avoids the potential for an error if we submit + // an update operation on a feature that has not been added to the + // database yet. + if(result.operation == self._editStore.ADD){ + graphic.operation = self._editStore.ADD; + operation = self._editStore.ADD; + } + deferred.resolve({"success":true,"graphic":graphic,"operation":operation}); + break; + case self._editStore.DELETE: + + var resolved = true; + + if(result.operation == self._editStore.ADD){ + // If we are deleting a new feature that has not been added to the + // server yet we need to delete it + layer._deleteTemporaryFeature(graphic,function(success, error){ + if(!success){ + resolved = false; + console.log("Unable to delete feature: " + JSON.stringify(error)); + } + }); + } + deferred.resolve({"success":resolved,"graphic":graphic,"operation":operation}); + break; + } + } + else if(result == "Id not found"){ + // Let's simply pass the graphic back as good-to-go. + // No modifications needed because the graphic does not + // already exist in the database. + deferred.resolve({"success":true,"graphic":graphic,"operation":operation}); + } + else{ + deferred.reject(graphic); + } + }); + + return deferred; + }; + + /** + * Delete a graphic that has been added while offline. + * @param graphic + * @param callback + * @private + */ + layer._deleteTemporaryFeature = function(graphic,callback){ + self._editStore.delete(layer.url,graphic,function(success,error){ + callback(success, error); + }); + }; + + layer._getFilesFromForm = function (formNode) { + var files = []; + var inputNodes = array.filter(formNode.elements, function (node) { + return node.type === "file"; + }); + inputNodes.forEach(function (inputNode) { + files.push.apply(files, inputNode.files); + }, this); + return files; + }; + + // we need to identify ADDs before sending them to the server + // we assign temporary ids (using negative numbers to distinguish them from real ids) + layer._nextTempId = -1; + layer._getNextTempId = function () { + return this._nextTempId--; + }; + + // We are currently only passing in a single deferred. + all(extendPromises).then(function (r) { + callback(true, null); + }); + + }, // extend + + /** + * Forces library into an offline state. Any edits applied during this condition will be stored locally + */ + goOffline: function () { + console.log("offlineFeatureManager going offline"); + this._onlineStatus = this.OFFLINE; + }, + + /** + * Forces library to return to an online state. If there are pending edits, + * an attempt will be made to sync them with the remote feature server + * @param callback callback( boolean, errors ) + */ + goOnline: function (callback) { + console.log("offlineFeaturesManager going online"); + this._onlineStatus = this.RECONNECTING; + this._replayStoredEdits(function (success, responses) { + //var result = {success: success, responses: responses}; + this._onlineStatus = this.ONLINE; + + //this._onlineStatus = this.ONLINE; + callback && callback(success,responses); + + }.bind(this)); + }, + + /** + * Determines if offline or online condition exists + * @returns {string} ONLINE or OFFLINE + */ + getOnlineStatus: function () { + return this._onlineStatus; + }, + + /* internal methods */ + + /** + * Initialize the database and push featureLayer JSON to DB if required. + * @param url Feature Layer's url. This is used by this library for internal feature identification. + * @return deferred + * @private + */ + _initializeDB: function(url){ + var deferred = new Deferred(); + + var editStore = this._editStore; + + // Configure the database + editStore.dbName = this.DB_NAME; + editStore.objectStoreName = this.DB_OBJECTSTORE_NAME; + editStore.objectId = this.DB_UID; + + // Attempt to initialize the database + editStore.init(function (result, error) { + + if(result){ + deferred.resolve({success:true, error: null}); + } + else{ + deferred.reject({success:false, error: null}); + } + }); + + return deferred; + }, + + // + // methods to send features back to the server + // + + /** + * Attempts to send any edits in the database. Monitor events for success or failure. + * @param callback + * @event ALL_EDITS_SENT when all edits have been successfully sent. Contains {[addResults],[updateResults],[deleteResults]} + * @event EDITS_SENT_ERROR some edits were not sent successfully. Contains {msg: error} + * @private + */ + _replayStoredEdits: function (callback) { + var promises = {}; + var that = this; + + // + // send edits for each of the layers + // + var layer; + var adds = [], updates = [], deletes = []; + var tempObjectIds = []; + var tempArray = []; + var featureLayers = this._featureLayers; + + var editStore = this._editStore; + + this._editStore.getAllEditsArray(function (result, err) { + if (result.length > 0) { + tempArray = result; + + var length = tempArray.length; + + for (var n = 0; n < length; n++) { + layer = featureLayers[tempArray[n].layer]; + layer.__onEditsComplete = layer.onEditsComplete; + layer.onEditsComplete = function () { + console.log("intercepting events onEditsComplete"); + }; + + // Let's zero everything out + adds = [], updates = [], deletes = [], tempObjectIds = []; + + // IMPORTANT: reconstitute the graphic JSON into an actual esri.Graphic object + // NOTE: we are only sending one Graphic per loop! + var graphic = new Graphic(tempArray[n].graphic); + + switch (tempArray[n].operation) { + case editStore.ADD: + for (var i = 0; i < layer.graphics.length; i++) { + var g = layer.graphics[i]; + if (g.attributes[layer.objectIdField] === graphic.attributes[layer.objectIdField]) { + layer.remove(g); + break; + } + } + tempObjectIds.push(graphic.attributes[layer.objectIdField]); + delete graphic.attributes[layer.objectIdField]; + adds.push(graphic); + break; + case editStore.UPDATE: + updates.push(graphic); + break; + case editStore.DELETE: + deletes.push(graphic); + break; + } + + // Note: when the feature layer is created with a feature collection we have to handle applyEdits() differently + // TO-DO rename this method. + promises[n] = that._internalApplyEditsAll(layer, tempArray[n].id, tempObjectIds, adds, updates, deletes); + } + + // wait for all requests to finish + // responses contain {id,layer,tempId,addResults,updateResults,deleteResults} + var allPromises = all(promises); + allPromises.then( + function (responses) { + console.log("OfflineFeaturesManager sync - all responses are back"); + callback(true, responses); + }, + function (errors) { + console.log("OfflineFeaturesManager._replayStoredEdits - ERROR!!"); + callback(false, errors); + } + ); + + } + else{ + // No edits were found + callback(true,[]); + } + }); + }, + + /** + * DEPRECATED as of v2.11 - + * TO-DO remove in next release + * Only delete items from database that were verified as successfully updated on the server. + * @param responses Object + * @param callback callback(true, responses) or callback(false, responses) + * @private + */ + _cleanSuccessfulEditsDatabaseRecords: function (responses, callback) { + if (Object.keys(responses).length !== 0) { + + var editsArray = []; + var editsFailedArray = []; + + for (var key in responses) { + if (responses.hasOwnProperty(key)) { + + var edit = responses[key]; + var tempResult = {}; + + if (edit.updateResults.length > 0) { + if (edit.updateResults[0].success) { + tempResult.layer = edit.layer; + tempResult.id = edit.updateResults[0].objectId; + editsArray.push(tempResult); + } + else { + editsFailedArray.push(edit); + } + } + if (edit.deleteResults.length > 0) { + if (edit.deleteResults[0].success) { + tempResult.layer = edit.layer; + tempResult.id = edit.deleteResults[0].objectId; + editsArray.push(tempResult); + } + else { + editsFailedArray.push(edit); + } + } + if (edit.addResults.length > 0) { + if (edit.addResults[0].success) { + tempResult.layer = edit.layer; + tempResult.id = edit.tempId; + editsArray.push(tempResult); + } + else { + editsFailedArray.push(edit); + } + } + } + } + + var promises = {}; + var length = editsArray.length; + for (var i = 0; i < length; i++) { + promises[i] = this._updateDatabase(editsArray[i]); + } + //console.log("EDIT LIST " + JSON.stringify(editsArray)); + + // wait for all requests to finish + // + var allPromises = all(promises); + allPromises.then( + function (responses) { + editsFailedArray.length > 0 ? callback(false, responses) : callback(true, responses); + }, + function (errors) { + callback(false, errors); + } + ); + } + else { + callback(true, {}); + } + }, + + /** + * Deletes edits from database. + * @param edit + * @returns {l.Deferred.promise|*|c.promise|q.promise|promise} + * @private + */ + _updateDatabase: function (edit) { + var dfd = new Deferred(); + var fakeGraphic = {}; + fakeGraphic.attributes = {}; + + // Use the correct attributes key! + fakeGraphic.attributes[this.DB_UID] = edit.id; + + this._editStore.delete(edit.layer, fakeGraphic, function (success, error) { + if (success) { + dfd.resolve({success: true, error: null}); + } + else { + dfd.reject({success: false, error: error}); + } + }.bind(this)); + + return dfd.promise; + + }, + + /** + * Applies edits. This works with both standard feature layers and when a feature layer is created + * using a feature collection. + * + * This works around specific behaviors in esri.layers.FeatureLayer when using the pattern + * new FeatureLayer(featureCollectionObject). + * + * Details on the specific behaviors can be found here: + * https://developers.arcgis.com/javascript/jsapi/featurelayer-amd.html#featurelayer2 + * + * @param layer + * @param id + * @param tempObjectIds + * @param adds + * @param updates + * @param deletes + * @returns {*|r} + * @private + */ + _internalApplyEditsAll: function (layer, id, tempObjectIds, adds, updates, deletes) { + var that = this; + var dfd = new Deferred(); + + this._makeEditRequest(layer, adds, updates, deletes, + function (addResults, updateResults, deleteResults) { + + if(addResults.length > 0) { + var graphic = new Graphic(adds[0].geometry,null,adds[0].attributes); + layer.add(graphic); + } + + that._cleanDatabase(layer, tempObjectIds, addResults, updateResults, deleteResults).then(function(results){ + dfd.resolve({ + id: id, + layer: layer.url, + tempId: tempObjectIds, // let's us internally match an ADD to it's new ObjectId + addResults: addResults, + updateResults: updateResults, + deleteResults: deleteResults, + databaseResults: results, + databaseErrors: null, + syncError: null + }); + }, function(error) { + dfd.resolve({ + id: id, + layer: layer.url, + tempId: tempObjectIds, // let's us internally match an ADD to it's new ObjectId + addResults: addResults, + updateResults: updateResults, + deleteResults: deleteResults, + databaseResults: null, + databaseErrors: error, + syncError: error + }); + }); + + }, + function (error) { + layer.onEditsComplete = layer.__onEditsComplete; + delete layer.__onEditsComplete; + + dfd.reject(error); + } + ); + return dfd.promise; + }, + + _cleanDatabase: function(layer, tempId, addResults, updateResults, deleteResults) { + + var dfd = new Deferred(); + var id = null; + + if (updateResults.length > 0) { + if (updateResults[0].success) { + id = updateResults[0].objectId; + } + } + if (deleteResults.length > 0) { + if (deleteResults[0].success) { + id = deleteResults[0].objectId; + } + } + if (addResults.length > 0) { + if (addResults[0].success) { + id = tempId; + } + } + + var fakeGraphic = {}; + fakeGraphic.attributes = {}; + + // Use the correct attributes key! + fakeGraphic.attributes[this.DB_UID] = id; + + // Delete the edit from the database + this._editStore.delete(layer.url, fakeGraphic, function (success, error) { + if (success) { + dfd.resolve({success: true, error: null, id: id}); + } + else { + dfd.reject({success: false, error: error, id: id}); + } + }); + + return dfd.promise; + }, + + /** + * Used when a feature layer is created with a feature collection. + * + * In the current version of the ArcGIS JSAPI 3.12+ the applyEdit() method doesn't send requests + * to the server when a feature layer is created with a feature collection. + * + * The use case for using this is: clean start app > go offline and make edits > offline restart browser > + * go online. + * + * @param layer + * @param adds + * @param updates + * @param deletes + * @returns {*|r} + * @private + */ + _makeEditRequest: function(layer,adds, updates, deletes, callback, errback) { + + var f = "f=json", a = "", u = "", d = ""; + + if(adds.length > 0) { + array.forEach(adds, function(add){ + if(add.hasOwnProperty("infoTemplate")){ // if the add has an infoTemplate attached, + delete add.infoTemplate; // delete it to reduce payload size. + } + }, this); + a = "&adds=" + JSON.stringify((adds)); + } + if(updates.length > 0) { + array.forEach(updates, function(update){ + if(update.hasOwnProperty("infoTemplate")){ // if the update has an infoTemplate attached, + delete update.infoTemplate; // delete it to reduce payload size. + } + }, this); + u = "&updates=" + JSON.stringify(updates); + } + if(deletes.length > 0) { + var id = deletes[0].attributes[this.DB_UID]; + d = "&deletes=" + id; + } + + var params = f + a + u + d; + + if(layer.hasOwnProperty("credential") && layer.credential){ + if(layer.credential.hasOwnProperty("token") && layer.credential.token){ + params = params + "&token=" + layer.credential.token; + } + } + + var req = new XMLHttpRequest(); + req.open("POST", layer.url + "/applyEdits", true); + req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + req.onload = function() + { + if( req.status === 200 && req.responseText !== "") + { + try { + var obj = JSON.parse(this.response); + callback(obj.addResults, obj.updateResults, obj.deleteResults); + } + catch(err) { + console.error("EDIT REQUEST REPONSE WAS NOT SUCCESSFUL:", req); + errback("Unable to parse xhr response", req); + } + } + + }; + req.onerror = function(e) + { + console.error("_makeEditRequest failed: " + e); + errback(e); + }; + req.ontimeout = function() { + errback("xhr timeout error"); + }; + req.timeout = this._defaultXhrTimeout; + req.send(params); + }, + + /** + * Parses the respones related to going back online and cleaning up the database. + * @param responses + * @returns {promise} True means all was successful. False indicates there was a problem. + * @private + */ + _parseResponsesArray: function(responses,callback) { + + var err = 0; + + for (var key in responses) { + if (responses.hasOwnProperty(key)) { + responses[key].addResults.forEach(function(result){ + if(!result.success) { + err++; + } + }); + + responses[key].updateResults.forEach(function(result){ + if(!result.success) { + err++; + } + }); + + responses[key].deleteResults.forEach(function(result){ + if(!result.success) { + err++; + } + }); + } + } + + if(err > 0){ + callback(false); + } + else { + callback(true); + } + } + }); // declare + }); // define \ No newline at end of file diff --git a/lib/edit/editStorePOLS.js b/lib/edit/editStorePOLS.js new file mode 100644 index 00000000..4018d775 --- /dev/null +++ b/lib/edit/editStorePOLS.js @@ -0,0 +1,420 @@ +/*global indexedDB */ +/*jshint -W030 */ +/** + * This library is optimized for Partial Offline Support ONLY + * @constructor + */ +O.esri.Edit.EditStorePOLS = function () { + + "use strict"; + + this._db = null; + this._isDBInit = false; + + // Public properties + + this.dbName = "features_store"; + this.objectStoreName = "features"; + this.objectId = "objectid"; // set this depending on how your feature service is configured; + + //var _dbIndex = "featureId"; // @private + + // ENUMs + + 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 () { + if (!window.indexedDB) { + return false; + } + return true; + }; + + /** + * Commit an edit to the database + * @param operation add, update or delete + * @param layerUrl the URL of the feature layer + * @param graphic esri/graphic. The method will serialize to JSON + * @param callback callback(true, edit) or callback(false, error) + */ + this.pushEdit = function (operation, layerUrl, graphic, callback) { + + var edit = { + id: layerUrl + "/" + graphic.attributes[this.objectId], + operation: operation, + layer: layerUrl, + type: graphic.geometry.type, + graphic: graphic.toJson() + }; + + if(typeof graphic.attributes[this.objectId] === "undefined") { + console.error("editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineFeaturesManager.DB_UID? " + JSON.stringify(graphic.attributes)); + callback(false,"editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineFeaturesManager.DB_UID? " + JSON.stringify(graphic.attributes)); + } + else{ + var transaction = this._db.transaction([this.objectStoreName], "readwrite"); + + transaction.oncomplete = function (event) { + callback(true); + }; + + transaction.onerror = function (event) { + callback(false, event.target.error.message); + }; + + var objectStore = transaction.objectStore(this.objectStoreName); + objectStore.put(edit); + } + }; + + /** + * Retrieve an edit by its internal ID + * @param id String identifier + * @param callback callback(true,graphic) or callback(false, error) + */ + this.getEdit = function(id,callback){ + + console.assert(this._db !== null, "indexeddb not initialized"); + var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); + + if(typeof id === "undefined"){ + callback(false,"id is undefined."); + return; + } + + //Get the entry associated with the graphic + var objectStoreGraphicRequest = objectStore.get(id); + + objectStoreGraphicRequest.onsuccess = function () { + var graphic = objectStoreGraphicRequest.result; + if (graphic && (graphic.id == id)) { + callback(true,graphic); + } + else { + callback(false,"Id not found"); + } + }; + + objectStoreGraphicRequest.onerror = function (msg) { + callback(false,msg); + }; + }; + + /** + * Returns all the edits as a single Array via the callback + * @param callback {array, messageString} or {null, messageString} + */ + this.getAllEditsArray = function (callback) { + + console.assert(this._db !== null, "indexeddb not initialized"); + var editsArray = []; + + if (this._db !== null) { + + var fLayerJSONId = this.FEATURE_LAYER_JSON_ID; + var fCollectionId = this.FEATURE_COLLECTION_ID; + + var transaction = this._db.transaction([this.objectStoreName]) + .objectStore(this.objectStoreName) + .openCursor(); + + transaction.onsuccess = function (event) { + var cursor = event.target.result; + if (cursor && cursor.value && cursor.value.id) { + + // Make sure we are not return FeatureLayer JSON data + if (cursor.value.id !== fLayerJSONId && cursor.value.id !== fCollectionId) { + editsArray.push(cursor.value); + + } + cursor.continue(); + } + else { + callback(editsArray, "end"); + } + }.bind(this); + transaction.onerror = function (err) { + callback(null, err); + }; + } + else { + callback(null, "no db"); + } + }; + + /** + * Update an edit already exists in the database + * @param operation add, update or delete + * @param layer the URL of the feature layer + * @param graphic esri/graphic. The method will serialize to JSON + * @param callback {true, edit} or {false, error} + */ + this.updateExistingEdit = function (operation, layer, graphic, callback) { + + console.assert(this._db !== null, "indexeddb not initialized"); + + var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); + + //Let's get the entry associated with the graphic + var objectStoreGraphicRequest = objectStore.get(graphic.attributes[this.objectId]); + objectStoreGraphicRequest.onsuccess = function () { + + //Grab the data object returned as a result + // TO-DO Do we keep this?? + objectStoreGraphicRequest.result; + + //Create a new update object + var update = { + id: layer + "/" + graphic.attributes[this.objectId], + operation: operation, + layer: layer, + graphic: graphic.toJson() + }; + + // Insert the update into the database + var updateGraphicRequest = objectStore.put(update); + + updateGraphicRequest.onsuccess = function () { + callback(true); + }; + + updateGraphicRequest.onerror = function (err) { + callback(false, err); + }; + }.bind(this); + }; + + /** + * Delete a pending edit's record from the database. + * IMPORTANT: Be aware of false negatives. See Step 4 in this function. + * + * @param layerUrl + * @param graphic Graphic + * @param callback {boolean, error} + */ + this.delete = function (layerUrl, graphic, callback) { + + // NOTE: the implementation of the IndexedDB spec has a design fault with respect to + // handling deletes. The result of a delete operation is always designated as undefined. + // What this means is that there is no way to tell if an operation was successful or not. + // And, it will always return 'true.' + // + // In order to get around this we have to verify if after the attempted deletion operation + // if the record is or is not in the database. Kinda dumb, but that's how IndexedDB works. + //http://stackoverflow.com/questions/17137879/is-there-a-way-to-get-information-on-deleted-record-when-calling-indexeddbs-obj + + var db = this._db; + var deferred = null; + var self = this; + + var id = layerUrl + "/" + graphic.attributes[this.objectId]; + + require(["dojo/Deferred"], function (Deferred) { + deferred = new Deferred(); + + // Step 1 - lets see if record exits. If it does then return callback. + self.editExists(id).then(function (result) { + + // Step 4 - Then we check to see if the record actually exists or not. + deferred.then(function (result) { + + // IF the delete was successful, then the record should return 'false' because it doesn't exist. + self.editExists(id).then(function (results) { + callback(false); + }, + function (err) { + callback(true); //because we want this test to throw an error. That means item deleted. + }); + }, + // There was a problem with the delete operation on the database + function (err) { + callback(false, err); + }); + + var objectStore = db.transaction([self.objectStoreName], "readwrite").objectStore(self.objectStoreName); + + // Step 2 - go ahead and delete graphic + var objectStoreDeleteRequest = objectStore.delete(id); + + // Step 3 - We know that the onsuccess will always fire unless something serious goes wrong. + // So we go ahead and resolve the deferred here. + objectStoreDeleteRequest.onsuccess = function () { + deferred.resolve(true); + }; + + objectStoreDeleteRequest.onerror = function (msg) { + deferred.reject({success: false, error: msg}); + }; + + }, + // If there is an error in editExists() + function (err) { + callback(false, err); + }); + }); + }; + + /** + * Full database reset. + * CAUTION! If some edits weren't successfully sent, then their record + * will still exist in the database. If you use this function you + * will also delete those records. + * @param callback boolean + */ + this.resetEditsQueue = function (callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + var request = this._db.transaction([this.objectStoreName], "readwrite") + .objectStore(this.objectStoreName) + .clear(); + request.onsuccess = function (event) { + setTimeout(function () { + callback(true); + }, 0); + }; + request.onerror = function (err) { + callback(false, err); + }; + }; + + this.pendingEditsCount = function (callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + var count = 0; + var id = this.FEATURE_LAYER_JSON_ID; + var fCollectionId = this.FEATURE_COLLECTION_ID; + + var transaction = this._db.transaction([this.objectStoreName], "readwrite"); + var objectStore = transaction.objectStore(this.objectStoreName); + objectStore.openCursor().onsuccess = function (evt) { + var cursor = evt.target.result; + if (cursor && cursor.value && cursor.value.id) { + if (cursor.value.id !== id && cursor.value.id !== fCollectionId) { + count++; + } + cursor.continue(); + } + else { + callback(count); + } + }; + }; + + /** + * Verify is an edit already exists in the database. Checks the objectId. + * @param id + * @returns {deferred} {success: boolean, error: message} + * @private + */ + this.editExists = function (id) { + + var db = this._db; + var deferred = null; + var self = this; + + require(["dojo/Deferred"], function (Deferred) { + deferred = new Deferred(); + + var objectStore = db.transaction([self.objectStoreName], "readwrite").objectStore(self.objectStoreName); + + //Get the entry associated with the graphic + var objectStoreGraphicRequest = objectStore.get(id); + + objectStoreGraphicRequest.onsuccess = function () { + var graphic = objectStoreGraphicRequest.result; + if (graphic && (graphic.id == id)) { + deferred.resolve({success: true, error: null}); + } + else { + deferred.reject({success: false, error: "objectId is not a match."}); + } + }; + + objectStoreGraphicRequest.onerror = function (msg) { + deferred.reject({success: false, error: msg}); + }; + }); + + //We return a deferred object so that when calling this function you can chain it with a then() statement. + return deferred; + }; + + /** + * Returns the approximate size of the database in bytes + * IMPORTANT: Currently requires all data be serialized! + * @param callback callback({usage}, error) Whereas, the usage Object is {sizeBytes: number, editCount: number} + */ + this.getUsage = function (callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + var id = this.FEATURE_LAYER_JSON_ID; + var fCollectionId = this.FEATURE_COLLECTION_ID; + + var usage = {sizeBytes: 0, editCount: 0}; + + var transaction = this._db.transaction([this.objectStoreName]) + .objectStore(this.objectStoreName) + .openCursor(); + + console.log("dumping keys"); + + transaction.onsuccess = function (event) { + var cursor = event.target.result; + if (cursor && cursor.value && cursor.value.id) { + var storedObject = cursor.value; + var json = JSON.stringify(storedObject); + usage.sizeBytes += json.length; + + if (cursor.value.id !== id && cursor.value.id !== fCollectionId) { + usage.editCount += 1; + } + + cursor.continue(); + } + else { + callback(usage, null); + } + }; + transaction.onerror = function (err) { + callback(null, err); + }; + }; + + this.init = function (callback) { + console.log("init editsStore.js"); + + var request = indexedDB.open(this.dbName, 11); + callback = callback || function (success) { + console.log("EditsStore::init() success:", success); + }.bind(this); + + request.onerror = function (event) { + console.log("indexedDB error: " + event.target.errorCode); + callback(false, event.target.errorCode); + }.bind(this); + + request.onupgradeneeded = function (event) { + var db = event.target.result; + + if (db.objectStoreNames.contains(this.objectStoreName)) { + db.deleteObjectStore(this.objectStoreName); + } + + db.createObjectStore(this.objectStoreName, {keyPath: "id"}); + }.bind(this); + + request.onsuccess = function (event) { + this._db = event.target.result; + this._isDBInit = true; + console.log("database opened successfully"); + callback(true, null); + }.bind(this); + }; +}; + + diff --git a/lib/edit/offlineJSOptions.js b/lib/edit/offlineJSOptions.js new file mode 100644 index 00000000..f56f62d8 --- /dev/null +++ b/lib/edit/offlineJSOptions.js @@ -0,0 +1,13 @@ +// Configure offline/online detection +// Requires: http://github.hubspot.com/offline/docs/welcome/ + +Offline.options = { // jshint ignore:line + checks: { + image: { + url: function() { + return 'http://esri.github.io/offline-editor-js/tiny-image.png?_=' + (Math.floor(Math.random() * 1000000000)); + } + }, + active: 'image' + } +}; \ No newline at end of file From 017b6e764be75713c6fb9dd299814cadaad4d69d Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Thu, 19 Nov 2015 17:48:31 -0700 Subject: [PATCH 02/48] simple edit specs --- test/SpecRunner.offlineEditingLight.html | 177 +++++++ test/spec/offlineEditingLightSpec.js | 614 +++++++++++++++++++++++ 2 files changed, 791 insertions(+) create mode 100644 test/SpecRunner.offlineEditingLight.html create mode 100644 test/spec/offlineEditingLightSpec.js diff --git a/test/SpecRunner.offlineEditingLight.html b/test/SpecRunner.offlineEditingLight.html new file mode 100644 index 00000000..6785cc0d --- /dev/null +++ b/test/SpecRunner.offlineEditingLight.html @@ -0,0 +1,177 @@ + + + + Jasmine Spec Runner - offlineFeaturesManager + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/test/spec/offlineEditingLightSpec.js b/test/spec/offlineEditingLightSpec.js new file mode 100644 index 00000000..4b64c9b5 --- /dev/null +++ b/test/spec/offlineEditingLightSpec.js @@ -0,0 +1,614 @@ +"use strict"; + +/* + * helper functions + */ + +function clearFeatureLayer(featureLayer, cb) +{ + g_modules.esriRequest({ + url: featureLayer.url + "/deleteFeatures", + content: { f: 'json', where: '1=1'}, + handleAs: 'json' + },{usePost:true}).then( function(response) + { + cb && cb(true,response); + }, + function(error) + { + cb && cb(false,error); + }); +} + +function countFeatures(featureLayer, cb) +{ + g_modules.esriRequest({ + url: featureLayer.url + "/query", + content: { f: 'json', where: '1=1', returnCountOnly:true}, + handleAs: 'json' + },{usePost:true}).then( function(response) + { + cb && cb(true,response); + }, + function(error) + { + cb && cb(false,error); + }); +} + +function getObjectIds(graphics) +{ + return graphics.map( function(g) { return g.attributes[g_offlineFeaturesManager.DB_UID]; }); +} + +/* + * tests begin here + */ +var async = new AsyncSpec(this); + +describe("Normal online editing - Exercise the feature services", function() +{ + var g1,g2,g3; + + describe("Original applyEdits method", function() + { + async.it("clears the feature layers", function(done) + { + var count = 0; + function completedOne() + { + count += 1; + if(count==g_layersIds.length){ + console.log("Before running tests graphic count: " + g_featureLayers[0].graphics.length); + done(); + } + + } + + // Run clear twice because of current bug in ArcGIS Online related to clearing feature services with attachments. + clearFeatureLayer(g_featureLayers[0], function(success){ + clearFeatureLayer( g_featureLayers[0], function(success,response) + { + expect(success).toBeTruthy(); + var listener = g_featureLayers[0].on('update-end', function(){ listener.remove(); completedOne();}) + g_featureLayers[0].refresh(); + }); + }); + }); + + async.it("add test features", function(done) + { + expect(g_featureLayers[0].graphics.length).toBe(0); + + g1 = new g_modules.Graphic({"geometry":{"x":-105400,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"lat":0.0,"lng":0.0,"description":"g1"}}); + g2 = new g_modules.Graphic({"geometry":{"x":-105600,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"lat":0.0,"lng":0.0,"description":"g2"}}); + g3 = new g_modules.Graphic({"geometry":{"x":-105800,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"lat":0.0,"lng":0.0,"description":"g3"}}); + + var adds = [g1,g2,g3]; + g_featureLayers[0].applyEdits(adds,null,null,function(addResults,updateResults,deleteResults) + { + expect(addResults.length).toBe(3); + expect(addResults[0].success).toBeTruthy(); + expect(addResults[1].success).toBeTruthy(); + expect(addResults[2].success).toBeTruthy(); + //g1.attributes.objectid = addResults[0].objectId; + //g2.attributes.objectid = addResults[1].objectId; + //g3.attributes.objectid = addResults[2].objectId; + expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g3])); + expect(g_featureLayers[0].graphics.length).toBe(3); + countFeatures(g_featureLayers[0], function(success,result) + { + expect(success).toBeTruthy(); + expect(result.count).toBe(3); + done(); + }); + }, + function(error) + { + expect(true).toBeFalsy(); + done(); + }); + }); + + async.it("update test features", function(done) + { + expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g3])); + expect(g_featureLayers[0].graphics.length).toBe(3); + + g1.geometry.y += 300; + g2.geometry.y += 100; + g3.geometry.y -= 200; + var updates = [g1,g2,g3]; + g_featureLayers[0].applyEdits(null,updates,null,function(addResults,updateResults,deleteResults) + { + expect(updateResults.length).toBe(3); + expect(updateResults[0].success).toBeTruthy(); + expect(updateResults[1].success).toBeTruthy(); + expect(updateResults[2].success).toBeTruthy(); + expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g3])); + expect(g_featureLayers[0].graphics.length).toBe(3); + done(); + }, + function(error) + { + expect(true).toBeFalsy(); + done(); + }); + }); + + async.it("delete one test feature", function(done) { + expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1, g2, g3])); + expect(g_featureLayers[0].graphics.length).toBe(3); + + var deletes = [g3]; + g_featureLayers[0].applyEdits(null, null, deletes, function (addResults, updateResults, deleteResults) { + expect(deleteResults.length).toBe(1); + expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1, g2])); + expect(g_featureLayers[0].graphics.length).toBe(2); + done(); + }, + function (error) { + expect(true).toBeFalsy(); + done(); + }); + }); + }); +}); + +describe("Offline Editing", function() +{ + + var g1,g2,g3; + var g4,g5,g6; + + describe("Prep db and feature service", function() + { + async.it("detect IndexedDB support", function (done) { + expect(g_editsStore.isSupported()).toBeTruthy(); + done(); + }); + + async.it("initialize database", function (done) { + g_editsStore.init(function (success) { + expect(success).toEqual(true); + done(); + }) + }); + + async.it("Prepare feature service. Clear database",function(done) + { + g_featureLayers[0].resetDatabase(function (result) { + expect(result).toEqual(true); + + g_featureLayers[0].pendingEditsCount(function (count) { + expect(count).toBe(0); + done(); + }); + + }); + }); + + async.it("Prepare feature service. Clear feature Layers - points - lines", function(done) + { + var count = 0; + function completedOne() + { + count += 1; + + if(count==g_layersIds.length){ + console.log("Before running tests graphic count 2: " + g_featureLayers[0].graphics.length); + done(); + } + } + clearFeatureLayer( g_featureLayers[0], function(success,response) + { + expect(success).toBeTruthy(); + var listener = g_featureLayers[0].on('update-end', function(){ listener.remove(); completedOne();}) + g_featureLayers[0].refresh(); + }); + }); + + async.it("Prepare feature service. Add some features online - points", function(done) + { + expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE); + + g1 = new g_modules.Graphic({"geometry":{"x":-105400,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":1,"lat":0.0,"lng":0.0,"description":"g1"}}); + g2 = new g_modules.Graphic({"geometry":{"x":-105600,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":2,"lat":0.0,"lng":0.0,"description":"g2"}}); + g3 = new g_modules.Graphic({"geometry":{"x":-105800,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":3,"lat":0.0,"lng":0.0,"description":"g3"}}); + + var adds = [g1,g2,g3]; + g_featureLayers[0].applyEdits(adds,null,null,function(addResults,updateResults,deleteResults) + { + expect(addResults.length).toBe(3); + console.log("OBJECT IDs Before going offline: " + JSON.stringify(getObjectIds(g_featureLayers[0].graphics))); + expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g3])); + expect(g_featureLayers[0].graphics.length).toBe(3); + countFeatures(g_featureLayers[0], function(success,result) + { + expect(success).toBeTruthy(); + expect(result.count).toBe(3); + + done(); + }); + }, + function(error) + { + expect(true).toBeFalsy(); + }); + }); + }); + + describe("Go offline", function() + { + async.it("go Offline", function(done) + { + expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE); + g_offlineFeaturesManager.goOffline(); + expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.OFFLINE); + done(); + }); + + async.it("update existing features - points", function(done) + { + + var listener = jasmine.createSpy('event listener edits enqueued'); + + g_offlineFeaturesManager.on(g_offlineFeaturesManager.events.EDITS_ENQUEUED,listener); + + + expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g3])); + expect(g_featureLayers[0].graphics.length).toBe(3); + expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.OFFLINE); + + g1.geometry.y += 300; + g2.geometry.y += 100; + g3.geometry.y -= 200; + var updates = [g1,g2,g3]; + g_featureLayers[0].applyEdits(null,updates,null,function(addResults,updateResults,deleteResults) + { console.log("update existing features - points: " + JSON.stringify(updateResults)) + expect(updateResults.length).toBe(3); + expect(updateResults[0].success).toBeTruthy(); + expect(updateResults[1].success).toBeTruthy(); + expect(updateResults[2].success).toBeTruthy(); + expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g3])); + expect(g_featureLayers[0].graphics.length).toBe(3); + expect(listener).toHaveBeenCalled(); + g_editsStore.pendingEditsCount(function(result){ + expect(result).toBe(3); + done(); + }); + }, + function(error) + { + expect(true).toBeFalsy(); + done(); + }); + }); + + async.it("delete existing features - points", function(done) + { + expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g3])); + expect(g_featureLayers[0].graphics.length).toBe(3); + + var deletes = [g3]; console.log("Graphic " + JSON.stringify(g3.toJson())); + g_featureLayers[0].applyEdits(null,null,deletes,function(addResults,updateResults,deleteResults) + { + expect(deleteResults.length).toBe(1); + expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2])); + expect(g_featureLayers[0].graphics.length).toBe(2); + g_editsStore.pendingEditsCount(function(result){ + + // Expected count will stil be 6! This record is the database gets overwritten + // with the latest edit request. Last edit wins. + expect(result).toBe(3); + done(); + }); + }, + function(error) + { + expect(true).toBeFalsy(); + done(); + }); + }); + + async.it("add new features offline - points", function(done) + { + expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2])); + expect(g_featureLayers[0].graphics.length).toBe(2); + expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.OFFLINE); + + //g4 = new g_modules.Graphic({"geometry":{"x":-109100,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Reference Point DLRP","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} ); + //g5 = new g_modules.Graphic({"geometry":{"x":-109500,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Reference Point DLRP","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} ); + //g6 = new g_modules.Graphic({"geometry":{"x":-109900,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Reference Point DLRP","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} ); + + g4 = new g_modules.Graphic({"geometry":{"x":-109100,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":4,"lat":0.0,"lng":0.0,"description":"g4"}}); + g5 = new g_modules.Graphic({"geometry":{"x":-109500,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":5,"lat":0.0,"lng":0.0,"description":"g5"}}); + g6 = new g_modules.Graphic({"geometry":{"x":-109900,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":6,"lat":0.0,"lng":0.0,"description":"g6"}}); + + var adds = [g4,g5,g6]; + g_featureLayers[0].applyEdits(adds,null,null,function(addResults,updateResults,deleteResults) + { + expect(addResults.length).toBe(3); + + g_editsStore.pendingEditsCount(function(result){ + + // Should be 9 since we added 3 three edits and we had 6 edits in the previous test + expect(result).toBe(6); + done(); + }); + + expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g4,g5,g6])); + expect(g_featureLayers[0].graphics.length).toBe(5); + //g4.attributes.objectid = addResults[0].objectId; + //g5.attributes.objectid = addResults[1].objectId; + //g6.attributes.objectid = addResults[2].objectId; + expect(addResults[0].objectId).toBeLessThan(0); + expect(addResults[1].objectId).toEqual(-2); + expect(addResults[2].objectId).toEqual(-3); + }, + function(error) + { + expect(true).toBeFalsy(); + }); + }); + + async.it("Update new feature offline - point", function(done){ + + // Let's make a change to g6 attributes + g6.attributes.additionalinformation = null; + var updates = [g6]; + g_featureLayers[0].applyEdits(null,updates,null,function(addResults,updateResults,deleteResults) + { + expect(updateResults.length).toBe(1); + + console.log("OBJECT IDs after updating new offline feature: " + JSON.stringify(getObjectIds(g_featureLayers[0].graphics))); + expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g4,g5,g6])); + + g_editsStore.pendingEditsCount(function(result){ + + // Should be the exact same as previous test + // An update to a new feature should be a single entry in the database. + // We simply update the existing entry with the new information. + expect(result).toBe(6); + done(); + }); + }, + function(error) + { + expect(true).toBeFalsy(); + }); + }); + + async.it("validate non-existent feature - ADD", function(done){ + + var testGraphic = new g_modules.Graphic({"geometry":{"x":-109100,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Reference Point DLRP","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} ); + + g_featureLayers[0]._validateFeature(testGraphic,g_featureLayers[0].url,"add") + .then(function(result){ + expect(result.success).toBe(true); + expect(testGraphic).toEqual(result.graphic); + expect(result.operation).toEqual("add"); + done(); + },function(error){ + console.log("Validate feature error: " + error); + }); + }); + + async.it("validate feature that exists in db - ADD", function(done){ + var id = getObjectIds([g6]).toString(); + expect(id).toEqual("-3"); + g_featureLayers[0]._validateFeature(g6,g_featureLayers[0].url,"add") + .then(function(result){ + expect(result.success).toBe(true); + expect(g6).toEqual(result.graphic); + expect(JSON.stringify(g6.toJson()) === JSON.stringify(result.graphic.toJson())).toBeTruthy(); + expect(result.operation).toEqual("add"); + done(); + },function(error){ + console.log("Validate feature error: " + error); + }); + }); + + // This UPDATE should be converted in an ADD + async.it("validate feature that exists in db - UPDATE", function(done){ + var id = getObjectIds([g6]).toString(); + expect(id).toEqual("-3"); + g_featureLayers[0]._validateFeature(g6,g_featureLayers[0].url,"update") + .then(function(result){ + expect(result.success).toBe(true); + expect(g6).toEqual(result.graphic); + + // we swap the operation type when updating an edit that hasn't + // been submitted to the server yet. + expect(result.operation).toBe("add"); + expect(JSON.stringify(g6.toJson()) === JSON.stringify(result.graphic.toJson())).toBeTruthy(); + expect(result.operation).toEqual("add"); + done(); + },function(error){ + console.log("Validate feature error: " + error); + }); + }); + + // Checking for errors and error handling + async.it("delete a non-existing feature directly from db", function(done){ + + var fakeGraphic = new g_modules.Graphic({"geometry":{"x":-10910,"y":513700,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Reference Point 1234","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} ); + g_editsStore.delete(g_featureLayers[0].url,fakeGraphic,function(success,error){ + expect(success).toBe(false); + done(); + }); + }); + + // Checking for errors and error handling + async.it("delete a non-existing feature using extended feature layer", function(done){ + + var fakeGraphic = new g_modules.Graphic({"geometry":{"x":-10910,"y":513700,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Reference Point 1234","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} ); + g_featureLayers[0]._deleteTemporaryFeature(fakeGraphic,function(results){ + expect(results).toBe(false); + done(); + }); + }); + + async.it("check db size before offline delete", function(done){ + g_featureLayers[0].getUsage(function(usage,error){ + expect(usage.sizeBytes).toBe(2498); + expect(usage.editCount).toBe(6); + expect(error).toBe(null); + done(); + }) + }); + + // This private function deletes a temporary graphic and it's associated phantom graphic + async.it("delete an existing feature from db using extended feature layer", function(done){ + var id = getObjectIds([g5]).toString(); + expect(id).toEqual("-2"); + g_featureLayers[0]._deleteTemporaryFeature(g5,function(results){ + + // We only deleted data from database NOT from the featurelayer! + console.log("OBJECT IDs after deleting offline feature: " + JSON.stringify(getObjectIds(g_featureLayers[0].graphics))); + expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g4,g5,g6])); + + expect(results).toBe(true); + done(); + }); + }); + + async.it("check db size after offline delete", function(done){ + g_featureLayers[0].getUsage(function(usage,error){ + expect(usage.sizeBytes).toBe(2090); + expect(usage.editCount).toBe(5); + expect(error).toBe(null); + done(); + }) + }); + }); + + describe("Before going online", function(){ + async.it("Before going online validate graphic layer properties", function(done){ + // Remember we deleted g3! So our total count is 8 not 9. HOWEVER, there should be 9 records in the database! + expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g4,g5,g6])); + //expect(getObjectIds(g_featureLayers[1].graphics)).toEqual(getObjectIds([l1,l2,l3])); + expect(g_featureLayers[0].graphics.length).toBe(5); + //expect(g_featureLayers[1].graphics.length).toBe(3); + //expect(g_featureLayers[2].graphics.length).toBe(0); + done(); + }); + + // Here's a list of what we should have for pending edits: + // -1 = add + // -3 = add + // update + // update + // delete + async.it("Retrieve edits array from the layer", function(done){ + g_featureLayers[0].getAllEditsArray(function(success,array){ + expect(success).toBe(true); console.log("Pending edits prior to going back online: " + JSON.stringify(array)) + expect(array.length).toBe(5); + done(); + }); + }); + + async.it("Verify feature layer graphic counts",function(done){ + // all of them are positive + console.log("OBJECT IDs Before Online: " + JSON.stringify(getObjectIds(g_featureLayers[0].graphics))); + expect(getObjectIds(g_featureLayers[0].graphics).filter(function(id){ return id<0; })).toEqual([-1,-2,-3]); + expect(g_featureLayers[0].graphics.length).toBe(5); + done(); + }); + + async.it("Verify feature count from the feature layer's REST endpoint",function(done){ + countFeatures(g_featureLayers[0], function(success,result) + { + expect(success).toBeTruthy(); + expect(result.count).toBe(3); + done(); + }); + }); + }); + + describe("go Online", function() + { + async.it("go Online", function(done) + { + + var listener = jasmine.createSpy('event listener all edits sent'); + + //g_offlineFeaturesManager.on(g_offlineFeaturesManager.events.ALL_EDITS_SENT,listener); + + g_offlineFeaturesManager.goOnline(function(success,results) { + console.log("Library is now back online"); + expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE); + //expect(listener).toHaveBeenCalled(); + expect(success).toBeTruthy(); + + //console.log("RESPONSES " + JSON.stringify(responses) + ", " + JSON.stringify(results)) + + //expect(Object.keys(results.responses).length).toBe(5); + for (var key in results) { + + var response = results[key]; + + console.log("RESPONSE " + JSON.stringify(response)) + + //var layerId = response.layer.substring(response.layer.lastIndexOf('/') + 1); + // + //expect(typeof response.tempId).toBe("object"); + //expect(typeof response.updateResults).toBe("object"); + //expect(typeof response.deleteResults).toBe("object"); + //expect(typeof response.addResults).toBe("object"); + //expect(typeof response.id).toBe("string"); + //expect(typeof response.layer).toBe("string"); + // + //if(response.updateResults.length > 0){ + // expect(response.updateResults[0].success).toBe(true); + // expect(response.updateResults[0].objectId).toBeGreaterThan(0); + //} + //if(response.deleteResults.length > 0){ + // expect(response.deleteResults[0].success).toBe(true); + // expect(response.deleteResults[0].objectId).toBeGreaterThan(0); + //} + //if(response.addResults.length > 0){ + // expect(response.addResults[0].success).toBe(true); + // expect(response.addResults[0].objectId).toBeGreaterThan(0); + //} + } + done(); + }); + }); + }); + + describe("After online", function(){ + async.it("After online - verify feature layer graphic counts",function(done){ + console.log("After Online feature layers graphic count before clear: " + g_featureLayers[0].graphics.length); + + countFeatures(g_featureLayers[0], function(success,result) + { + expect(success).toBeTruthy(); + expect(result.count).toBe(4); + done(); + }); + + //done(); + }); + + async.it("Retrieve edits array from the layer", function(done){ + g_featureLayers[0].getAllEditsArray(function(success,array){ + expect(success).toBe(true); console.log("ARRAY should be empty " + JSON.stringify(array)) + expect(array.length).toBe(0); + done(); + }); + }); + + async.it("After online - verify online status",function(done){ + expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE); + done(); + }); + + async.it("After online - check pending edits count 0",function(done){ + g_editsStore.pendingEditsCount(function(result){ + expect(result).toBe(0); + done(); + }); + }); + }); +}); \ No newline at end of file From ee93dae15b94aef20a05c40c764fd0baa9187967 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Thu, 19 Nov 2015 17:49:35 -0700 Subject: [PATCH 03/48] grunt mods for creating simple edit lib --- Gruntfile.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Gruntfile.js b/Gruntfile.js index 7456cd5d..37b13446 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -58,6 +58,15 @@ module.exports = function(grunt) { ], dest: 'dist/offline-edit-src.js' }, + editLight: { + src: [ + 'lib/edit/offlineJSOptions.js', + 'lib/edit/OfflineFeatureLayer.js', + 'lib/edit/OfflineEditNS.js', + 'lib/edit/editStorePOLS.js' + ], + dest: 'dist/offline-editlight-src.js' + }, /* Tiles basic is for use with WebMaps. Cannot be reloaded or restarted while offline */ tilesBasic: { src: [ @@ -113,6 +122,7 @@ module.exports = function(grunt) { dist: { files: { 'dist/offline-edit-min.js': ['dist/offline-edit-src.js'], + 'dist/offline-editlight-min.js': ['dist/offline-editlight-src.js'], 'dist/offline-tiles-basic-min.js': ['dist/offline-tiles-basic-src.js'], 'dist/offline-tiles-advanced-min.js': ['dist/offline-tiles-advanced-src.js'], 'dist/offline-tpk-min.js': ['dist/offline-tpk-src.js'] From 44cd61f11aed6a985527c1b64896186ab203ef0e Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Thu, 19 Nov 2015 17:57:40 -0700 Subject: [PATCH 04/48] move auto-detect --- lib/edit/OfflineFeatureLayer.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/edit/OfflineFeatureLayer.js b/lib/edit/OfflineFeatureLayer.js index 6973bfa2..11c94f9b 100644 --- a/lib/edit/OfflineFeatureLayer.js +++ b/lib/edit/OfflineFeatureLayer.js @@ -25,7 +25,7 @@ define([ _featureLayers: {}, _editStore: new O.esri.Edit.EditStorePOLS(), _defaultXhrTimeout: 15000, // ms - _autoOfflineDetect: false, + _autoOfflineDetect: true, ONLINE: "online", // all edits will directly go to the server OFFLINE: "offline", // edits will be enqueued @@ -92,14 +92,6 @@ define([ extendPromises.push(this._initializeDB(url)); } - if(this._autoOfflineDetect){ - Offline.on('up', self.goOnline(function(success,error){ // jshint ignore:line - - })); - - Offline.on('down', self.goOffline()); // jshint ignore:line - } - // replace the applyEdits() method layer._applyEdits = layer.applyEdits; @@ -504,7 +496,16 @@ define([ // We are currently only passing in a single deferred. all(extendPromises).then(function (r) { - callback(true, null); + + if(self._autoOfflineDetect){ + Offline.on('up', self.goOnline(function(success,error){ // jshint ignore:line + + })); + + Offline.on('down', self.goOffline()); // jshint ignore:line + } + + callback(true, null); }); }, // extend From cc18951e2b4ffa4fae1484e48ad5e50e3cc646cb Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Thu, 19 Nov 2015 17:58:25 -0700 Subject: [PATCH 05/48] add simple edit sample --- samples/simple-edit.html | 127 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 samples/simple-edit.html diff --git a/samples/simple-edit.html b/samples/simple-edit.html new file mode 100644 index 00000000..983155b2 --- /dev/null +++ b/samples/simple-edit.html @@ -0,0 +1,127 @@ + + + + + + + Update Fire Perimeter + + + + + + + + + + +
+ +
+
+ + + \ No newline at end of file From 4d5bb1ccafb9da85cad3023e7bbe4271967c4e74 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Thu, 19 Nov 2015 17:58:58 -0700 Subject: [PATCH 06/48] demo sample - multiple layers full offline --- samples/appcache-twofeatureslayer-noedit.html | 723 ++++++++++++++++++ 1 file changed, 723 insertions(+) create mode 100644 samples/appcache-twofeatureslayer-noedit.html diff --git a/samples/appcache-twofeatureslayer-noedit.html b/samples/appcache-twofeatureslayer-noedit.html new file mode 100644 index 00000000..b144d98e --- /dev/null +++ b/samples/appcache-twofeatureslayer-noedit.html @@ -0,0 +1,723 @@ + + + + + + + + + + + AppCache Tiles and Features + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+ + +
+
+
+
+
+
+
+
+ + + + + + + + + \ No newline at end of file From b55ff84740dd3c826f957051a1d72ce437274f94 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Thu, 19 Nov 2015 18:08:47 -0700 Subject: [PATCH 07/48] fix online/offline switching --- lib/edit/OfflineFeatureLayer.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/edit/OfflineFeatureLayer.js b/lib/edit/OfflineFeatureLayer.js index 11c94f9b..abf33c79 100644 --- a/lib/edit/OfflineFeatureLayer.js +++ b/lib/edit/OfflineFeatureLayer.js @@ -498,11 +498,16 @@ define([ all(extendPromises).then(function (r) { if(self._autoOfflineDetect){ - Offline.on('up', self.goOnline(function(success,error){ // jshint ignore:line + Offline.on('up', function(){ // jshint ignore:line - })); + self.goOnline(function(success,error){ // jshint ignore:line + console.log("GOING ONLINE"); + }); + }); - Offline.on('down', self.goOffline()); // jshint ignore:line + Offline.on('down', function(){ // jshint ignore:line + self.goOffline(); // jshint ignore:line + }); } callback(true, null); From 7b177855d96d383f2ae8eb5f83a8fee662f81547 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Fri, 20 Nov 2015 13:54:04 -0700 Subject: [PATCH 08/48] Rename libraries --- dist/offline-edit-min.js | 5 - dist/offline-edit-src.js | 3663 ----------------- ...ecRunner.offlineFeaturesManagerBasic.html} | 0 3 files changed, 3668 deletions(-) delete mode 100644 dist/offline-edit-min.js delete mode 100644 dist/offline-edit-src.js rename test/{SpecRunner.offlineEditingLight.html => SpecRunner.offlineFeaturesManagerBasic.html} (100%) diff --git a/dist/offline-edit-min.js b/dist/offline-edit-min.js deleted file mode 100644 index d622a4a3..00000000 --- a/dist/offline-edit-min.js +++ /dev/null @@ -1,5 +0,0 @@ -/*! esri-offline-maps - v2.16.0 - 2015-10-29 -* Copyright (c) 2015 Environmental Systems Research Institute, Inc. -* Apache License*/ -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.OfflineFeaturesManager",[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",EXTEND_COMPLETE:"extend-complete"},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||null===a._url,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);return e}if(n.attachmentsStore){var f=new b;return n.attachmentsStore.getAttachmentsByFeatureId(this.url,a,function(a){c&&c(a),f.resolve(a)}),f}},a.addAttachment=function(a,c,d,e){if(n.getOnlineStatus()===n.ONLINE)return this._addAttachment(a,c,function(){n.emit(n.events.ATTACHMENTS_SENT,arguments),d&&d.apply(this,arguments)},function(a){e&&e.apply(this,arguments)});if(n.attachmentsStore){var f=this._getFilesFromForm(c),g=f[0],i=new b,j=this._getNextTempId();return n.attachmentsStore.store(this.url,j,a,g,n.attachmentsStore.TYPE.ADD,function(b,c){var f={attachmentId:j,objectId:a,success:b};if(b){n.emit(n.events.ATTACHMENT_ENQUEUED,f),d&&d(f),i.resolve(f);var g=this._url.path+"/"+a+"/attachments/"+j,k=h("[href="+g+"]");k.attr("href",c.url)}else f.error="can't store attachment",e&&e(f),i.reject(f)}.bind(this)),i}},a.updateAttachment=function(a,c,d,e,f){if(n.getOnlineStatus()===n.ONLINE)return this._updateAttachment(a,c,d,function(){e&&e.apply(this,arguments)},function(a){f&&f.apply(this,arguments)});if(n.attachmentsStore){var g=this._getFilesFromForm(d),i=g[0],j=n.attachmentsStore.TYPE.UPDATE,k=new b;return 0>c&&(j=n.attachmentsStore.TYPE.ADD),n.attachmentsStore.store(this.url,c,a,i,j,function(b,d){var g={attachmentId:c,objectId:a,success:b};if(b){n.emit(n.events.ATTACHMENT_ENQUEUED,g),e&&e(g),k.resolve(g);var i=this._url.path+"/"+a+"/attachments/"+c,j=h("[href="+i+"]");j.attr("href",d.url)}else g.error="layer.updateAttachment::attachmentStore can't store attachment",f&&f(g),k.reject(g)}.bind(this)),k}},a.deleteAttachments=function(a,d,e,f){if(n.getOnlineStatus()===n.ONLINE){var g=this._deleteAttachments(a,d,function(){e&&e.apply(this,arguments)},function(a){f&&f.apply(this,arguments)});return g}if(n.attachmentsStore){var h=[];d.forEach(function(c){c=parseInt(c,10);var d=new b;if(0>c)n.attachmentsStore["delete"](c,function(b){var e={objectId:a,attachmentId:c,success:b};d.resolve(e)});else{var e=new Blob([],{type:"image/png"});n.attachmentsStore.store(this.url,c,a,e,n.attachmentsStore.TYPE.DELETE,function(b,e){var f={attachmentId:c,objectId:a,success:b};b?d.resolve(f):d.reject(f)}.bind(this))}h.push(d)},this);var i=c(h);return i.then(function(a){e&&e(a)}),i}},a.applyEdits=function(d,e,f,g,h){var i=[];if(n.getOnlineStatus()===n.ONLINE){var j=this._applyEdits(d,e,f,function(){n.emit(n.events.EDITS_SENT,arguments),g&&g.apply(this,arguments)},h);return j}var k=new b,l={addResults:[],updateResults:[],deleteResults:[]},m={},o=d||[];return o.forEach(function(a){var c=new b,d=this._getNextTempId();a.attributes[this.objectIdField]=d;var e=this;this._validateFeature(a,this.url,n._editStore.ADD).then(function(b){b.success?e._pushValidatedAddFeatureToDB(e,a,b.operation,l,d,c):c.resolve(!0)},function(a){c.reject(a)}),i.push(c)},this),e=e||[],e.forEach(function(a){var c=new b,d=a.attributes[this.objectIdField];m[d]=a;var e=this;this._validateFeature(a,this.url,n._editStore.UPDATE).then(function(b){b.success?e._pushValidatedUpdateFeatureToDB(e,a,b.operation,l,d,c):c.resolve(!0)},function(a){c.reject(a)}),i.push(c)},this),f=f||[],f.forEach(function(a){var c=new b,d=a.attributes[this.objectIdField],e=this;this._validateFeature(a,this.url,n._editStore.DELETE).then(function(b){b.success?e._pushValidatedDeleteFeatureToDB(e,a,b.operation,l,d,c):c.resolve(!0)},function(a){c.reject(a)}),i.push(c)},this),c(i).then(function(b){for(var c=!0,d=0;dg;g++){var h=a[g].toJson();if(f.push(h),g==e-1){var i=JSON.stringify(f),j=JSON.stringify(d);c(i,j);break}}},a.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)})})},a.setFeatureLayerJSONDataStore=function(a,b){n._editStore.pushFeatureLayerJSON(a,function(a,c){b(a,c)})},a.getFeatureLayerJSONDataStore=function(a){n._editStore.getFeatureLayerJSON(function(b,c){a(b,c)})},a.setPhantomLayerGraphics=function(a){var b=a.length;if(b>0)for(var c=0;b>c;c++){var d=new k(a[c]);this._phantomLayer.add(d)}},a.getPhantomLayerGraphics=function(b){for(var c=a._phantomLayer.graphics,d=a._phantomLayer.graphics.length,e=[],f=0;d>f;f++){var g=c[f].toJson();if(e.push(g),f==d-1){var h=JSON.stringify(e);b(h);break}}},a.getPhantomGraphicsArray=function(a){n._editStore.getPhantomGraphicsArray(function(b,c){"end"==c?a(!0,b):a(!1,c)})},a.getAttachmentsUsage=function(a){n.attachmentsStore.getUsage(function(b,c){a(b,c)})},a.resetAttachmentsDatabase=function(a){n.attachmentsStore.resetAttachmentsQueue(function(b,c){a(b,c)})},a.getUsage=function(a){n._editStore.getUsage(function(b,c){a(b,c)})},a.resetDatabase=function(a){n._editStore.resetEditsQueue(function(b,c){a(b,c)})},a.pendingEditsCount=function(a){n._editStore.pendingEditsCount(function(b){a(b)})},a.getFeatureDefinition=function(a,b,c,d){var e={layerDefinition:a,featureSet:{features:b,geometryType:c}};d(e)},a.getAllEditsArray=function(a){n._editStore.getAllEditsArray(function(b,c){"end"==c?a(!0,b):a(!1,c)})},a._pushFeatureCollections=function(b){n._editStore._getFeatureCollections(function(c,d){var e={featureLayerUrl:a.url,featureLayerCollection:a.toJson()},f=[e],g={id:n._editStore.FEATURE_COLLECTION_ID,featureCollections:f};if(a.hasAttachments=e.featureLayerCollection.layerDefinition.hasAttachments,c){for(var h=0,i=0;id;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};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);break}}},getFeatureCollections:function(a){this._editStore._isDBInit?this._editStore._getFeatureCollections(function(b,c){a(b,c)}):this._initializeDB(null,null).then(function(b){b.success&&this._editStore._getFeatureCollections(function(b,c){a(b,c)})}.bind(this),function(b){a(!1,b)})},getFeatureLayerJSONDataStore:function(a){this._editStore._isDBInit?this._editStore.getFeatureLayerJSON(function(b,c){a(b,c)}):this._initializeDB(null,null).then(function(b){b.success&&this._editStore.getFeatureLayerJSON(function(b,c){a(b,c)})}.bind(this),function(b){a(!1,b)})},_initializeDB:function(a,c){var d=new b,e=this._editStore;return e.dbName=this.DB_NAME,e.objectStoreName=this.DB_OBJECTSTORE_NAME,e.objectId=this.DB_UID,e.init(function(b,f){"object"==typeof a&&b===!0&&void 0!==a&&null!==a?(c&&(a.__featureLayerURL=c),e.pushFeatureLayerJSON(a,function(a,b){a?d.resolve({success:!0,error:null}):d.reject({success:!1,error:b})})):b?d.resolve({success:!0,error:null}):d.reject({success:!1,error:null})}),d},_checkFileAPIs:function(){return window.File&&window.FileReader&&window.FileList&&window.Blob?(XMLHttpRequest.prototype.sendAsBinary||(XMLHttpRequest.prototype.sendAsBinary=function(a){function b(a){return 255&a.charCodeAt(0)}var c=Array.prototype.map.call(a,b),d=new Uint8Array(c);this.send(d.buffer)}),!0):!1},_extendAjaxReq:function(a){a.sendAsBinary=XMLHttpRequest.prototype.sendAsBinary},_phantomSymbols:[],_getPhantomSymbol:function(a,b){if(0===this._phantomSymbols.length){var c=[0,255,0,255],d=1.5;this._phantomSymbols.point=[],this._phantomSymbols.point[this._editStore.ADD]=new m({type:"esriSMS",style:"esriSMSCross",xoffset:10,yoffset:10,color:[255,255,255,0],size:15,outline:{color:c,width:d,type:"esriSLS",style:"esriSLSSolid"}}),this._phantomSymbols.point[this._editStore.UPDATE]=new m({type:"esriSMS",style:"esriSMSCircle",xoffset:0,yoffset:0,color:[255,255,255,0],size:15,outline:{color:c,width:d,type:"esriSLS",style:"esriSLSSolid"}}),this._phantomSymbols.point[this._editStore.DELETE]=new m({type:"esriSMS",style:"esriSMSX",xoffset:0,yoffset:0,color:[255,255,255,0],size:15,outline:{color:c,width:d,type:"esriSLS",style:"esriSLSSolid"}}),this._phantomSymbols.multipoint=null,this._phantomSymbols.polyline=[],this._phantomSymbols.polyline[this._editStore.ADD]=new n({type:"esriSLS",style:"esriSLSSolid",color:c,width:d}),this._phantomSymbols.polyline[this._editStore.UPDATE]=new n({type:"esriSLS",style:"esriSLSSolid",color:c,width:d}),this._phantomSymbols.polyline[this._editStore.DELETE]=new n({type:"esriSLS",style:"esriSLSSolid",color:c,width:d}),this._phantomSymbols.polygon=[],this._phantomSymbols.polygon[this._editStore.ADD]=new o({type:"esriSFS",style:"esriSFSSolid",color:[255,255,255,0],outline:{type:"esriSLS",style:"esriSLSSolid",color:c,width:d}}),this._phantomSymbols.polygon[this._editStore.UPDATE]=new o({type:"esriSFS",style:"esriSFSSolid",color:[255,255,255,0],outline:{type:"esriSLS",style:"esriSLSDash",color:c,width:d}}),this._phantomSymbols.polygon[this._editStore.DELETE]=new o({type:"esriSFS",style:"esriSFSSolid",color:[255,255,255,0],outline:{type:"esriSLS",style:"esriSLSDot",color:c,width:d}})}return this._phantomSymbols[a.type][b]},_uploadAttachment:function(a){var c=new b,d=this._featureLayers[a.featureLayerUrl],e=new FormData;switch(e.append("attachment",a.file),a.type){case this.attachmentsStore.TYPE.ADD:d.addAttachment(a.objectId,e,function(b){c.resolve({attachmentResult:b,id:a.id})},function(a){c.reject(a)});break;case this.attachmentsStore.TYPE.UPDATE:e.append("attachmentId",a.id),d._sendAttachment("update",a.objectId,e,function(b){c.resolve({attachmentResult:b,id:a.id})},function(a){c.reject(a)});break;case this.attachmentsStore.TYPE.DELETE:d.deleteAttachments(a.objectId,[a.id],function(b){c.resolve({attachmentResult:b,id:a.id})},function(a){c.reject(a)})}return c.promise},_deleteAttachmentFromDB:function(a,c){var d=new b;return this.attachmentsStore["delete"](a,function(a){d.resolve({success:a,result:c})}),d},_cleanAttachmentsDB:function(a,b){var d=this,e=[],f=0;a.forEach(function(a){"object"==typeof a.attachmentResult&&a.attachmentResult.success?e.push(d._deleteAttachmentFromDB(a.id,null)):a.attachmentResult instanceof Array?a.attachmentResult.forEach(function(b){b.success?e.push(d._deleteAttachmentFromDB(a.id,null)):f++}):f++});var g=c(e);g.then(function(c){b(f>0?{errors:!0,attachmentsDBResults:c,uploadResults:a}:{errors:!1,attachmentsDBResults:c,uploadResults:a})})},_sendStoredAttachments:function(a){this.attachmentsStore.getAllAttachments(function(b){var d=this,e=[];b.forEach(function(a){var b=this._uploadAttachment(a);e.push(b)},this);var f=c(e);f.then(function(b){d._cleanAttachmentsDB(b,function(c){c.errors?a&&a(!1,b,c):a&&a(!0,b,c)})},function(b){a&&a(!1,b)})}.bind(this))},_replayStoredEdits:function(a){var b,d={},e=this,f=[],g=[],h=[],i=[],j=[],l=this._featureLayers,m=this.attachmentsStore,n=this._editStore;this._editStore.getAllEditsArray(function(o,p){if(o.length>0){j=o;for(var q=j.length,r=0;q>r;r++){b=l[j[r].layer],null==m&&b.hasAttachments,b._attachmentsStore=m,b.__onEditsComplete=b.onEditsComplete,b.onEditsComplete=function(){},f=[],g=[],h=[],i=[];var s=new k(j[r].graphic);switch(j[r].operation){case n.ADD:for(var t=0;t0&&(g.updateResults[0].success?(h.layer=g.layer,h.id=g.updateResults[0].objectId,d.push(h)):e.push(g)),g.deleteResults.length>0&&(g.deleteResults[0].success?(h.layer=g.layer,h.id=g.deleteResults[0].objectId,d.push(h)):e.push(g)),g.addResults.length>0&&(g.addResults[0].success?(h.layer=g.layer,h.id=g.tempId,d.push(h)):e.push(g))}for(var i={},j=d.length,k=0;j>k;k++)i[k]=this._updateDatabase(d[k]);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},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)})})},_internalApplyEdits:function(a,c,d,e,f,g){var h=this,i=new b;return a._applyEdits(e,f,g,function(b,e,f){if(a._phantomLayer.clear(),null!=a._attachmentsStore&&a.hasAttachments&&d.length>0){var g=b.map(function(a){return a.objectId});a._replaceFeatureIds(d,g,function(a){})}h._cleanDatabase(a,d,b,e,f).then(function(g){i.resolve({id:c,layer:a.url,tempId:d,addResults:b,updateResults:e,deleteResults:f,databaseResults:g,databaseErrors:null})},function(g){i.resolve({id:c,layer:a.url,tempId:d,addResults:b,updateResults:e,deleteResults:f,databaseResults:null,databaseErrors:g})})},function(b){a.onEditsComplete=a.__onEditsComplete,delete a.__onEditsComplete,i.reject(b)}),i.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(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;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];this._editStore.deletePhantomGraphic(c,function(a,b){a?g.resolve({success:!0,error:null,id:c}):g.reject({success:!1,error:b,id:c})})}else g.reject({success:!1,error:b,id:c})}.bind(this)),g.promise},_makeEditRequest:function(a,b,c,d,f,g){var h="f=json",i="",j="",k="";if(b.length>0&&(e.forEach(b,function(a){a.hasOwnProperty("infoTemplate")&&delete a.infoTemplate},this),i="&adds="+JSON.stringify(b)),c.length>0&&(e.forEach(c,function(a){a.hasOwnProperty("infoTemplate")&&delete a.infoTemplate},this),j="&updates="+JSON.stringify(c)),d.length>0){var l=d[0].attributes[this.DB_UID];k="&deletes="+l}var m=h+i+j+k;a.hasOwnProperty("credential")&&a.credential&&a.credential.hasOwnProperty("token")&&a.credential.token&&(m=m+"&token="+a.credential.token);var n=new XMLHttpRequest;n.open("POST",a.url+"/applyEdits",!0),n.setRequestHeader("Content-type","application/x-www-form-urlencoded"),n.onload=function(){if(200===n.status&&""!==n.responseText)try{var a=JSON.parse(this.response);f(a.addResults,a.updateResults,a.deleteResults)}catch(b){g("Unable to parse xhr response",n)}},n.onerror=function(a){g(a)},n.ontimeout=function(){g("xhr timeout error")},n.timeout=this._defaultXhrTimeout,n.send(m)},_parseResponsesArray:function(a){var c=new b,d=0;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()};if("undefined"==typeof c.attributes[this.objectId])d(!1,"editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineFeaturesManager.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)};var g=f.objectStore(this.objectStoreName);g.put(e)}},this.pushFeatureLayerJSON=function(a,b){"object"!=typeof a&&b(!1,"dataObject type is not an object.");var c=this._db;a.id=this.FEATURE_LAYER_JSON_ID,this.getFeatureLayerJSON(function(d,e){var f;if(d&&"undefined"!=typeof e){f=c.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName);for(var g in a)a.hasOwnProperty(g)&&(e[g]=a[g]);var h=f.put(e);h.onsuccess=function(){b(!0,null)},h.onerror=function(a){b(!1,a)}}else{var i=c.transaction([this.objectStoreName],"readwrite");i.oncomplete=function(a){b(!0,null)},i.onerror=function(a){b(!1,a.target.error.message)},f=i.objectStore(this.objectStoreName);try{f.put(a)}catch(j){b(!1,JSON.stringify(j))}}}.bind(this))},this.getFeatureLayerJSON=function(a){var b=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName),c=b.get(this.FEATURE_LAYER_JSON_ID);c.onsuccess=function(){var b=c.result;"undefined"!=typeof b?a(!0,b):a(!1,"nothing found")},c.onerror=function(b){a(!1,b)}},this.deleteFeatureLayerJSON=function(a){var b=this._db,c=null,d=this,e=this.FEATURE_LAYER_JSON_ID;require(["dojo/Deferred"],function(f){c=new f,c.then(function(b){d.editExists(e).then(function(b){a(!1,{message:"object was not deleted."})},function(b){a(!0,{message:"id does not exist"})})},function(b){a(!1,{message:"id does not exist"})}),d.editExists(e).then(function(a){var f=b.transaction([d.objectStoreName],"readwrite").objectStore(d.objectStoreName),g=f["delete"](e);g.onsuccess=function(){c.resolve(!0)},g.onerror=function(a){c.reject({success:!1,error:a})}},function(a){c.reject({success:!1,message:a})}.bind(this))})},this.pushPhantomGraphic=function(a,b){var c=this._db,d=this.PHANTOM_GRAPHIC_PREFIX+this._PHANTOM_PREFIX_TOKEN+a.attributes[this.objectId],e={id:d,graphic:a.toJson()},f=c.transaction([this.objectStoreName],"readwrite");f.oncomplete=function(a){b(!0,null)},f.onerror=function(a){b(!1,a.target.error.message)};var g=f.objectStore(this.objectStoreName);g.put(e)},this.getPhantomGraphicsArray=function(a){var b=[];if(null!==this._db){var c=this.PHANTOM_GRAPHIC_PREFIX,d=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor();d.onsuccess=function(d){var e=d.target.result;e&&e.value&&e.value.id?(-1!=e.value.id.indexOf(c)&&b.push(e.value),e["continue"]()):a(b,"end")}.bind(this),d.onerror=function(b){a(null,b)}}else a(null,"no db")},this._getPhantomGraphicsArraySimple=function(a){var b=[];if(null!==this._db){var c=this.PHANTOM_GRAPHIC_PREFIX,d=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor();d.onsuccess=function(d){var e=d.target.result;e&&e.value&&e.value.id?(-1!=e.value.id.indexOf(c)&&b.push(e.value.id),e["continue"]()):a(b,"end")}.bind(this),d.onerror=function(b){a(null,b)}}else a(null,"no db")},this.deletePhantomGraphic=function(a,b){var c=this._db,d=null,e=this;require(["dojo/Deferred"],function(f){d=new f,e.editExists(a).then(function(f){d.then(function(c){e.editExists(a).then(function(a){b(!1,"item was not deleted")},function(a){b(!0,"item successfully deleted")})},function(a){b(!1,a)});var g=c.transaction([e.objectStoreName],"readwrite").objectStore(e.objectStoreName),h=g["delete"](a);h.onsuccess=function(){d.resolve(!0)},h.onerror=function(a){d.reject({success:!1,error:a})}},function(a){b(!1,"item doesn't exist in db")})})},this.resetLimitedPhantomGraphicsQueue=function(a,b){if(Object.keys(a).length>0){var c=this._db,d=0,e=c.transaction([this.objectStoreName],"readwrite"),f=e.objectStore(this.objectStoreName);f.onerror=function(){d++},e.oncomplete=function(){b(0===d?!0:!1)};for(var g in a)if(a.hasOwnProperty(g)){var h=a[g],i=this.PHANTOM_GRAPHIC_PREFIX+this._PHANTOM_PREFIX_TOKEN+h.id;h.updateResults.length>0&&h.updateResults[0].success&&f["delete"](i),h.deleteResults.length>0&&h.deleteResults[0].success&&f["delete"](i),h.addResults.length>0&&h.addResults[0].success&&f["delete"](i)}}else b(!0)},this.resetPhantomGraphicsQueue=function(a){var b=this._db;this._getPhantomGraphicsArraySimple(function(c){if(c!=[]){var d=0,e=b.transaction([this.objectStoreName],"readwrite"),f=e.objectStore(this.objectStoreName);f.onerror=function(){d++},e.oncomplete=function(){a(0===d?!0:!1)};for(var g=c.length,h=0;g>h;h++)f["delete"](c[h])}else a(!0)}.bind(this))},this.getEdit=function(a,b){var c=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName);if("undefined"==typeof a)return void b(!1,"id is undefined.");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=[];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]);f.onsuccess=function(){f.result;var g={id:b+"/"+c.attributes[this.objectId],operation:a,layer:b,graphic:c.toJson()},h=e.put(g);h.onsuccess=function(){d(!0)},h.onerror=function(a){d(!1,a)}}.bind(this)},this["delete"]=function(a,b,c){var d=this._db,e=null,f=this,g=a+"/"+b.attributes[this.objectId];require(["dojo/Deferred"],function(a){e=new a,f.editExists(g).then(function(a){e.then(function(a){f.editExists(g).then(function(a){c(!1)},function(a){c(!0)})},function(a){c(!1,a)});var b=d.transaction([f.objectStoreName],"readwrite").objectStore(f.objectStoreName),h=b["delete"](g);h.onsuccess=function(){e.resolve(!0)},h.onerror=function(a){e.reject({success:!1,error:a})}},function(a){c(!1)})})},this.resetEditsQueue=function(a){var b=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName).clear();b.onsuccess=function(b){setTimeout(function(){a(!0)},0)},b.onerror=function(b){a(!1,b)}},this.pendingEditsCount=function(a){var b=0,c=this.FEATURE_LAYER_JSON_ID,d=this.FEATURE_COLLECTION_ID,e=this.PHANTOM_GRAPHIC_PREFIX,f=this._db.transaction([this.objectStoreName],"readwrite"),g=f.objectStore(this.objectStoreName);g.openCursor().onsuccess=function(f){var g=f.target.result;g&&g.value&&g.value.id&&-1==g.value.id.indexOf(e)?(g.value.id!==c&&g.value.id!==d&&b++,g["continue"]()):a(b)}},this.editExists=function(a){var b=this._db,c=null,d=this;return require(["dojo/Deferred"],function(e){c=new e;var f=b.transaction([d.objectStoreName],"readwrite").objectStore(d.objectStoreName),g=f.get(a);g.onsuccess=function(){var b=g.result;b&&b.id==a?c.resolve({success:!0,error:null}):c.reject({success:!1,error:"objectId is not a match."})},g.onerror=function(a){c.reject({success:!1,error:a})}}),c},this.getUsage=function(a){var b=this.FEATURE_LAYER_JSON_ID,c=this.FEATURE_COLLECTION_ID,d=this.PHANTOM_GRAPHIC_PREFIX,e={sizeBytes:0,editCount:0},f=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor();f.onsuccess=function(f){var g=f.target.result;if(g&&g.value&&g.value.id){var h=g.value,i=JSON.stringify(h);e.sizeBytes+=i.length,-1==g.value.id.indexOf(d)&&g.value.id!==b&&g.value.id!==c&&(e.editCount+=1), -g["continue"]()}else a(e,null)},f.onerror=function(b){a(null,b)}},this._pushFeatureCollections=function(a,b){var c=this._db.transaction([this.objectStoreName],"readwrite");c.oncomplete=function(a){b(!0)},c.onerror=function(a){b(!1,a.target.error.message)};var d=c.objectStore(this.objectStoreName);d.put(a)},this._getFeatureCollections=function(a){var b=this._db.transaction([this.objectStoreName],"readonly").objectStore(this.objectStoreName),c=b.get(this.FEATURE_COLLECTION_ID);c.onsuccess=function(){var b=c.result;"undefined"!=typeof b?a(!0,b):a(!1,null)},c.onerror=function(b){a(!1,b)}},this._serialize=function(a){var b=a.toJson(),c={attributes:b.attributes,geometry:b.geometry,infoTemplate:b.infoTemplate,symbol:b.symbol};return JSON.stringify(c)},this._deserialize=function(a){var b;return require(["esri/graphic"],function(c){b=new c(JSON.parse(a))}),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");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;c?b(!0,c):b(!1,"not found")},d.onerror=function(a){b(!1,a)}},this.getAttachmentsByFeatureId=function(a,b,c){var d=a+"/"+b,e=[],f=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName),g=f.index("featureId"),h=IDBKeyRange.only(d);g.openCursor(h).onsuccess=function(a){var b=a.target.result;b?(e.push(b.value),b["continue"]()):c(e)}},this.getAttachmentsByFeatureLayer=function(a,b){var c=[],d=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName),e=d.index("featureLayerUrl"),f=IDBKeyRange.only(a);e.openCursor(f).onsuccess=function(a){var d=a.target.result;d?(c.push(d.value),d["continue"]()):b(c)}},this.getAllAttachments=function(a){var b=[],c=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName);c.openCursor().onsuccess=function(c){var d=c.target.result;d?(b.push(d.value),d["continue"]()):a(b)}},this.deleteAttachmentsByFeatureId=function(a,b,c){var d=a+"/"+b,e=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName),f=e.index("featureId"),g=IDBKeyRange.only(d),h=0;f.openCursor(g).onsuccess=function(a){var b=a.target.result;b?(e["delete"](b.primaryKey),h++,b["continue"]()):setTimeout(function(){c(h)},0)}.bind(this)},this["delete"]=function(a,b){this.retrieve(a,function(c,d){if(!c)return void b(!1,"attachment "+a+" not found");var e=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName)["delete"](a);e.onsuccess=function(a){setTimeout(function(){b(!0)},0)},e.onerror=function(a){b(!1,a)}}.bind(this))},this.deleteAll=function(a){this.getAllAttachments(function(b){var c=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName).clear();c.onsuccess=function(b){setTimeout(function(){a(!0)},0)},c.onerror=function(b){a(!1,b)}}.bind(this))},this.replaceFeatureId=function(a,b,c,d){var e=a+"/"+b,f=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName),g=f.index("featureId"),h=IDBKeyRange.only(e),i=0;g.openCursor(h).onsuccess=function(b){var e=b.target.result;if(e){var g=a+"/"+c,h=e.value;h.featureId=g,h.objectId=c,f.put(h),i++,e["continue"]()}else setTimeout(function(){d(i)},1)}},this.getUsage=function(a){var b={sizeBytes:0,attachmentCount:0},c=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor();c.onsuccess=function(c){var d=c.target.result;if(d){var e=d.value,f=JSON.stringify(e);b.sizeBytes+=f.length,b.attachmentCount+=1,d["continue"]()}else a(b,null)}.bind(this),c.onerror=function(b){a(null,b)}},this.resetAttachmentsQueue=function(a){var b=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName).clear();b.onsuccess=function(b){setTimeout(function(){a(!0)},0)},b.onerror=function(b){a(!1,b)}},this._readFile=function(a,b){var c=new FileReader;c.onload=function(a){b(!0,a.target.result)},c.onerror=function(a){b(!1,a.target.result)},c.readAsBinaryString(a)},this.init=function(a){var b=indexedDB.open(this.dbName,12);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);var c=b.createObjectStore(this.objectStoreName,{keyPath:"id"});c.createIndex("featureId","featureId",{unique:!1}),c.createIndex("featureLayerUrl","featureLayerUrl",{unique:!1})}.bind(this),b.onsuccess=function(b){this._db=b.target.result,a(!0)}.bind(this)}}; \ No newline at end of file diff --git a/dist/offline-edit-src.js b/dist/offline-edit-src.js deleted file mode 100644 index 1b084d4f..00000000 --- a/dist/offline-edit-src.js +++ /dev/null @@ -1,3663 +0,0 @@ -/*! esri-offline-maps - v2.16.0 - 2015-10-29 -* Copyright (c) 2015 Environmental Systems Research Institute, Inc. -* Apache License*/ -/*jshint -W030 */ -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 (Evented, Deferred, all, declare, array, domAttr, domStyle, query, - esriConfig, GraphicsLayer, Graphic, esriRequest, SimpleMarkerSymbol, SimpleLineSymbol, SimpleFillSymbol, urlUtils) { - "use strict"; - return declare("O.esri.Edit.OfflineFeaturesManager", [Evented], - { - _onlineStatus: "online", - _featureLayers: {}, - _featureCollectionUsageFlag: false, // if a feature collection was used to create the feature layer. - _editStore: new O.esri.Edit.EditStore(), - _defaultXhrTimeout: 15000, // ms - - ONLINE: "online", // all edits will directly go to the server - OFFLINE: "offline", // edits will be enqueued - RECONNECTING: "reconnecting", // sending stored edits to the server - attachmentsStore: null, // indexedDB for storing attachments - proxyPath: null, // by default we use CORS and therefore proxyPath is null - - ENABLE_FEATURECOLLECTION: false, // Set this to true for full offline use if you want to use the - // getFeatureCollections() pattern of reconstituting a feature layer. - - // Database properties - DB_NAME: "features_store", // Sets the database name. - DB_OBJECTSTORE_NAME: "features",// Represents an object store that allows access to a set of data in the IndexedDB database - DB_UID: "objectid", // Set this based on the unique identifier is set up in the feature service - - ATTACHMENTS_DB_NAME: "attachments_store", //Sets attachments database name - ATTACHMENTS_DB_OBJECTSTORE_NAME: "attachments", - // NOTE: attachments don't have the same issues as Graphics as related to UIDs (e.g. the need for DB_UID). - // You can manually create a graphic, but it would be very rare for someone to - // manually create an attachment. So, we don't provide a public property for - // the attachments database UID. - - // manager emits event when... - events: { - EDITS_SENT: "edits-sent", // ...whenever any edit is actually sent to the server - EDITS_ENQUEUED: "edits-enqueued", // ...when an edit is enqueued (and not sent to the server) - EDITS_ENQUEUED_ERROR: "edits-enqueued-error", // ...when there is an error during the queing process - EDITS_SENT_ERROR: "edits-sent-error", // ...there was a problem with one or more edits! - ALL_EDITS_SENT: "all-edits-sent", // ...after going online and there are no pending edits in the queue - ATTACHMENT_ENQUEUED: "attachment-enqueued", - ATTACHMENTS_SENT: "attachments-sent", - EXTEND_COMPLETE: "extend-complete" // ...when the libary has completed its initialization - }, - - /** - * Need to call this method only if you want to support offline attachments - * it is optional - * @param callback(success) - * @returns void - */ - initAttachments: function (callback) { - callback = callback || function (success) { - console.log("attachments inited ", success ? "ok" : "failed"); - }; - - if (!this._checkFileAPIs()) { - return callback(false, "File APIs not supported"); - } - - try { - this.attachmentsStore = new O.esri.Edit.AttachmentsStore(); - this.attachmentsStore.dbName = this.ATTACHMENTS_DB_NAME; - this.attachmentsStore.objectStoreName = this.ATTACHMENTS_DB_OBJECTSTORE_NAME; - - if (/*false &&*/ this.attachmentsStore.isSupported()) { - this.attachmentsStore.init(callback); - } - else { - return callback(false, "indexedDB not supported"); - } - } - catch (err) { - console.log("problem! " + err.toString()); - } - }, - - /** - * Overrides a feature layer. Call this AFTER the FeatureLayer's 'update-end' event. - * IMPORTANT: If dataStore is specified it will be saved to the database. Any complex - * objects such as [esri.Graphic] will need to be serialized or you will get an IndexedDB error. - * @param layer - * @param updateEndEvent The FeatureLayer's update-end event object - * @param callback {true, null} or {false, errorString} Traps whether or not the database initialized - * @param dataStore Optional configuration Object. Added @ v2.5. There is only one reserved object key and that is "id". - * Use this option to store featureLayerJSON and any other configuration information you'll need access to after - * a full offline browser restart. - * @returns deferred - */ - extend: function (layer, callback, dataStore) { - - var extendPromises = []; // deferred promises related to initializing this method - - var self = this; - layer.offlineExtended = true; // to identify layer has been extended - - if(!layer.loaded || layer._url === null) { - console.error("Make sure to initialize OfflineFeaturesManager after layer loaded and feature layer update-end event."); - } - - // NOTE: At v2.6.1 we've discovered that not all feature layers support objectIdField. - // However, to try to be consistent here with how the library is managing Ids - // we force the layer.objectIdField to DB_UID. This should be consistent with - // how esri.Graphics assign a unique ID to a graphic. If it is not, then this - // library will break and we'll have to re-architect how it manages UIDs. - layer.objectIdField = this.DB_UID; - - var url = null; - - // There have been reproducible use cases showing when a browser is restarted offline that - // for some reason the layer.url may be undefined. - // This is an attempt to minimize the possibility of that situation causing errors. - if(layer.url) { - url = layer.url; - // we keep track of the FeatureLayer object - this._featureLayers[layer.url] = layer; - } - - // This is a potentially brittle solution to detecting if a feature layer collection - // was used to create the feature layer. - // Is there a better way?? - if(layer._mode.featureLayer.hasOwnProperty("_collection")){ - // This means a feature collection was used to create the feature layer and it will - // require different handling when running applyEdit() - this._featureCollectionUsageFlag = true; - } - - // Initialize the database as well as set offline data. - if(!this._editStore._isDBInit) { - extendPromises.push(this._initializeDB(dataStore, url)); - } - - // replace the applyEdits() method - layer._applyEdits = layer.applyEdits; - - - // attachments - layer._addAttachment = layer.addAttachment; - layer._queryAttachmentInfos = layer.queryAttachmentInfos; - layer._deleteAttachments = layer.deleteAttachments; - layer._updateAttachment = layer.updateAttachment; - - /* - operations supported offline: - 1. add a new attachment to an existing feature (DONE) - 2. add a new attachment to a new feature (DONE) - 3. remove an attachment that is already in the server... (DONE) - 4. remove an attachment that is not in the server yet (DONE) - 5. update an existing attachment to an existing feature (DONE) - 6. update a new attachment (DONE) - - concerns: - - manage the relationship between offline features and attachments: what if the user wants to add - an attachment to a feature that is still offline? we need to keep track of objectids so that when - the feature is sent to the server and receives a final objectid we replace the temporary negative id - by its final objectid (DONE) - - what if the user deletes an offline feature that had offline attachments? we need to discard the attachment (DONE) - */ - - // - // attachments - // - layer.queryAttachmentInfos = function (objectId, callback, errback) { - if (self.getOnlineStatus() === self.ONLINE) { - var def = this._queryAttachmentInfos(objectId, - function () { - console.log(arguments); - self.emit(self.events.ATTACHMENTS_INFO, arguments); - callback && callback.apply(this, arguments); - }, - errback); - return def; - } - - if (!self.attachmentsStore) { - console.log("in order to support attachments you need to call initAttachments() method of offlineFeaturesManager"); - return; - } - - // will only return LOCAL attachments - var deferred = new Deferred(); - self.attachmentsStore.getAttachmentsByFeatureId(this.url, objectId, function (attachments) { - callback && callback(attachments); - deferred.resolve(attachments); - }); - return deferred; - }; - - layer.addAttachment = function (objectId, formNode, callback, errback) { - - if (self.getOnlineStatus() === self.ONLINE) { - return this._addAttachment(objectId, formNode, - function () { - console.log(arguments); - self.emit(self.events.ATTACHMENTS_SENT, arguments); - callback && callback.apply(this, arguments); - }, - function (err) { - console.log("addAttachment: " + err); - errback && errback.apply(this, arguments); - } - ); - } - - if (!self.attachmentsStore) { - console.error("in order to support attachments you need to call initAttachments() method of offlineFeaturesManager"); - return; - } - - var files = this._getFilesFromForm(formNode); - var file = files[0]; // addAttachment can only add one file, so the rest -if any- are ignored - - var deferred = new Deferred(); - var attachmentId = this._getNextTempId(); - self.attachmentsStore.store(this.url, attachmentId, objectId, file,self.attachmentsStore.TYPE.ADD, function (success, newAttachment) { - var returnValue = {attachmentId: attachmentId, objectId: objectId, success: success}; - if (success) { - self.emit(self.events.ATTACHMENT_ENQUEUED, returnValue); - callback && callback(returnValue); - deferred.resolve(returnValue); - - // replace the default URL that is set by attachmentEditor with the local file URL - var attachmentUrl = this._url.path + "/" + objectId + "/attachments/" + attachmentId; - var attachmentElement = query("[href=" + attachmentUrl + "]"); - attachmentElement.attr("href", newAttachment.url); - } - else { - returnValue.error = "can't store attachment"; - errback && errback(returnValue); - deferred.reject(returnValue); - } - }.bind(this)); - - return deferred; - }; - - layer.updateAttachment = function(objectId, attachmentId, formNode, callback, errback) { - if (self.getOnlineStatus() === self.ONLINE) { - return this._updateAttachment(objectId, attachmentId, formNode, - function () { - callback && callback.apply(this, arguments); - }, - function (err) { - console.log("updateAttachment: " + err); - errback && errback.apply(this, arguments); - }); - //return def; - } - - if (!self.attachmentsStore) { - console.error("in order to support attachments you need to call initAttachments() method of offlineFeaturesManager"); - return; - } - - var files = this._getFilesFromForm(formNode); - var file = files[0]; // addAttachment can only add one file, so the rest -if any- are ignored - var action = self.attachmentsStore.TYPE.UPDATE; // Is this an ADD or an UPDATE? - - var deferred = new Deferred(); - - // If the attachment has a temporary ID we want to keep it's action as an ADD. - // Otherwise we'll get an error when we try to UPDATE an ObjectId that doesn't exist in ArcGIS Online or Server. - if(attachmentId < 0) { - action = self.attachmentsStore.TYPE.ADD; - } - - self.attachmentsStore.store(this.url, attachmentId, objectId, file, action, function (success, newAttachment) { - var returnValue = {attachmentId: attachmentId, objectId: objectId, success: success}; - if (success) { - self.emit(self.events.ATTACHMENT_ENQUEUED, returnValue); - callback && callback(returnValue); - deferred.resolve(returnValue); - - // replace the default URL that is set by attachmentEditor with the local file URL - var attachmentUrl = this._url.path + "/" + objectId + "/attachments/" + attachmentId; - var attachmentElement = query("[href=" + attachmentUrl + "]"); - attachmentElement.attr("href", newAttachment.url); - } - else { - returnValue.error = "layer.updateAttachment::attachmentStore can't store attachment"; - errback && errback(returnValue); - deferred.reject(returnValue); - } - }.bind(this)); - - return deferred; - }; - - layer.deleteAttachments = function (objectId, attachmentsIds, callback, errback) { - if (self.getOnlineStatus() === self.ONLINE) { - var def = this._deleteAttachments(objectId, attachmentsIds, - function () { - callback && callback.apply(this, arguments); - }, - function (err) { - console.log("deleteAttachments: " + err); - errback && errback.apply(this, arguments); - }); - return def; - } - - if (!self.attachmentsStore) { - console.error("in order to support attachments you need to call initAttachments() method of offlineFeaturesManager"); - return; - } - - // case 1.- it is a new attachment - // case 2.- it is an already existing attachment - - // asynchronously delete each of the attachments - var promises = []; - attachmentsIds.forEach(function (attachmentId) { - attachmentId = parseInt(attachmentId, 10); // to number - - var deferred = new Deferred(); - - // IMPORTANT: If attachmentId < 0 then it's a local/new attachment - // and we can simply delete it from the attachmentsStore. - // However, if the attachmentId > 0 then we need to store the DELETE - // so that it can be processed and sync'd correctly during _uploadAttachments(). - if(attachmentId < 0) { - self.attachmentsStore.delete(attachmentId, function (success) { - var result = {objectId: objectId, attachmentId: attachmentId, success: success}; - deferred.resolve(result); - }); - } - else { - var dummyBlob = new Blob([],{type: "image/png"}); //TO-DO just a placeholder. Need to consider add a null check. - self.attachmentsStore.store(this.url, attachmentId, objectId, dummyBlob,self.attachmentsStore.TYPE.DELETE, function (success, newAttachment) { - var returnValue = {attachmentId: attachmentId, objectId: objectId, success: success}; - if (success) { - deferred.resolve(returnValue); - } - else { - deferred.reject(returnValue); - } - }.bind(this)); - } - //console.assert(attachmentId < 0, "we only support deleting local attachments"); - promises.push(deferred); - }, this); - - // call callback once all deletes have finished - // IMPORTANT: This returns an array!!! - var allPromises = all(promises); - allPromises.then(function (results) { - callback && callback(results); - }); - - return allPromises; - }; - - // - // other functions - // - - /** - * Overrides the ArcGIS API for JavaSccript applyEdits() method. - * @param adds Creates a new edit entry. - * @param updates Updates an existing entry. - * @param deletes Deletes an existing entry. - * @param callback Called when the operation is complete. - * @param errback An error object is returned if an error occurs - * @returns {*} deferred - * @event EDITS_ENQUEUED boolean if all edits successfully stored while offline - * @event EDITS_ENQUEUED_ERROR string message if there was an error while storing an edit while offline - */ - layer.applyEdits = function (adds, updates, deletes, callback, errback) { - // inside this method, 'this' will be the FeatureLayer - // and 'self' will be the offlineFeatureLayer object - var promises = []; - - if (self.getOnlineStatus() === self.ONLINE) { - var def = this._applyEdits(adds, updates, deletes, - function () { - self.emit(self.events.EDITS_SENT, arguments); - callback && callback.apply(this, arguments); - }, - errback); - return def; - } - - var deferred1 = new Deferred(); - var results = {addResults: [], updateResults: [], deleteResults: []}; - var updatesMap = {}; - - var _adds = adds || []; - _adds.forEach(function (addEdit) { - var deferred = new Deferred(); - - var objectId = this._getNextTempId(); - - addEdit.attributes[this.objectIdField] = objectId; - - var thisLayer = this; - - // We need to run some validation tests against each feature being added. - // Adding the same feature multiple times results in the last edit wins. LIFO. - this._validateFeature(addEdit,this.url,self._editStore.ADD).then(function(result){ - console.log("EDIT ADD IS BACK!!! " ); - - if(result.success){ - thisLayer._pushValidatedAddFeatureToDB(thisLayer,addEdit,result.operation,results,objectId,deferred); - } - else{ - // If we get here then we deleted an edit that was added offline. - // We also have deleted the phantom graphic. - deferred.resolve(true); - } - - },function(error){ - console.log("_validateFeature: Unable to validate!"); - deferred.reject(error); - }); - - promises.push(deferred); - }, this); - - updates = updates || []; - updates.forEach(function (updateEdit) { - var deferred = new Deferred(); - - var objectId = updateEdit.attributes[this.objectIdField]; - updatesMap[objectId] = updateEdit; - - var thisLayer = this; - - // We need to run some validation tests against each feature being updated. - // If we have added a feature and we need to update it then we change it's operation type to "add" - // and the last edits wins. LIFO. - this._validateFeature(updateEdit,this.url,self._editStore.UPDATE).then(function(result){ - console.log("EDIT UPDATE IS BACK!!! " ); - - if(result.success){ - thisLayer._pushValidatedUpdateFeatureToDB(thisLayer,updateEdit,result.operation,results,objectId,deferred); - } - else{ - // If we get here then we deleted an edit that was added offline. - // We also have deleted the phantom graphic. - deferred.resolve(true); - } - - },function(error){ - console.log("_validateFeature: Unable to validate!"); - deferred.reject(error); - }); - - promises.push(deferred); - }, this); - - deletes = deletes || []; - deletes.forEach(function (deleteEdit) { - var deferred = new Deferred(); - - var objectId = deleteEdit.attributes[this.objectIdField]; - - var thisLayer = this; - - // We need to run some validation tests against each feature being deleted. - // If we have added a feature and then deleted it in the app then we go ahead - // and delete it and its phantom graphic from the database. - // NOTE: at this time we don't handle attachments automatically - this._validateFeature(deleteEdit,this.url,self._editStore.DELETE).then(function(result){ - console.log("EDIT DELETE IS BACK!!! " ); - - if(result.success){ - thisLayer._pushValidatedDeleteFeatureToDB(thisLayer,deleteEdit,result.operation,results,objectId,deferred); - } - else{ - // If we get here then we deleted an edit that was added offline. - // We also have deleted the phantom graphic. - deferred.resolve(true); - } - - },function(error){ - console.log("_validateFeature: Unable to validate!"); - deferred.reject(error); - }); - - promises.push(deferred); - }, this); - - all(promises).then(function (r) { - // Make sure all edits were successful. If not throw an error. - var promisesSuccess = true; - for (var v = 0; v < r.length; v++) { - if (r[v] === false) { - promisesSuccess = false; - } - } - - layer._pushFeatureCollections(function(success){ - console.log("All edits done"); - - if(success && promisesSuccess){ - self.emit(self.events.EDITS_ENQUEUED, results); - } - else { - if(!success){ - console.log("applyEdits() there was a problem with _pushFeatureCollections."); - } - self.emit(self.events.EDITS_ENQUEUED_ERROR, results); - } - - //promisesSuccess === true ? self.emit(self.events.EDITS_ENQUEUED, results) : self.emit(self.events.EDITS_ENQUEUED_ERROR, results); - - // we already pushed the edits into the database, now we let the FeatureLayer to do the local updating of the layer graphics - this._editHandler(results, _adds, updatesMap, callback, errback, deferred1); - }.bind(this)); - - //success === true ? self.emit(self.events.EDITS_ENQUEUED, results) : self.emit(self.events.EDITS_ENQUEUED_ERROR, results); - // EDITS_ENQUEUED = callback(true, edit), and EDITS_ENQUEUED_ERROR = callback(false, /*String */ error) - //this._editHandler(results, _adds, updatesMap, callback, errback, deferred1); - }.bind(this)); - - return deferred1; - - }; // layer.applyEdits() - - /** - * Converts an array of graphics/features into JSON - * @param features - * @param updateEndEvent The layer's 'update-end' event - * @param callback - */ - layer.convertGraphicLayerToJSON = function (features, updateEndEvent, callback) { - var layerDefinition = {}; - - // We want to be consistent, but we've discovered that not all feature layers have an objectIdField - if(updateEndEvent.target.hasOwnProperty("objectIdField")) - { - layerDefinition.objectIdFieldName = updateEndEvent.target.objectIdField; - }else { - layerDefinition.objectIdFieldName = this.objectIdField; - } - - layerDefinition.globalIdFieldName = updateEndEvent.target.globalIdField; - layerDefinition.geometryType = updateEndEvent.target.geometryType; - layerDefinition.spatialReference = updateEndEvent.target.spatialReference; - layerDefinition.fields = updateEndEvent.target.fields; - - var length = features.length; - var jsonArray = []; - for (var i = 0; i < length; i++) { - var jsonGraphic = features[i].toJson(); - jsonArray.push(jsonGraphic); - if (i == (length - 1)) { - var featureJSON = JSON.stringify(jsonArray); - var layerDefJSON = JSON.stringify(layerDefinition); - callback(featureJSON, layerDefJSON); - break; - } - } - }; - - /** - * Retrieves f=json from the feature layer - * @param url FeatureLayer's URL - * @param callback - * @private - */ - layer.getFeatureLayerJSON = function (url, callback) { - require(["esri/request"], function (esriRequest) { - var request = esriRequest({ - url: url, - content: {f: "json"}, - handleAs: "json", - callbackParamName: "callback" - }); - - request.then(function (response) { - console.log("Success: ", response); - callback(true, response); - }, function (error) { - console.log("Error: ", error.message); - callback(false, error.message); - }); - }); - }; - - /** - * Sets the optional feature layer storage object - * @param jsonObject - * @param callback - */ - layer.setFeatureLayerJSONDataStore = function(jsonObject, callback){ - self._editStore.pushFeatureLayerJSON(jsonObject,function(success,error){ - callback(success,error); - }); - }; - - /** - * Retrieves the optional feature layer storage object - * @param callback callback(true, object) || callback(false, error) - */ - layer.getFeatureLayerJSONDataStore = function(callback){ - self._editStore.getFeatureLayerJSON(function(success,message){ - callback(success,message); - }); - }; - - /** - * Sets the phantom layer with new features. - * Used to restore PhantomGraphicsLayer after offline restart - * @param graphicsArray an array of Graphics - */ - layer.setPhantomLayerGraphics = function (graphicsArray) { - var length = graphicsArray.length; - - if (length > 0) { - for (var i = 0; i < length; i++) { - var graphic = new Graphic(graphicsArray[i]); - this._phantomLayer.add(graphic); - } - } - }; - - /** - * Returns the array of graphics from the phantom graphics layer. - * This layer identifies features that have been modified - * while offline. - * @returns {array} - */ - layer.getPhantomLayerGraphics = function (callback) { - //return layer._phantomLayer.graphics; - var graphics = layer._phantomLayer.graphics; - var length = layer._phantomLayer.graphics.length; - var jsonArray = []; - for (var i = 0; i < length; i++) { - var jsonGraphic = graphics[i].toJson(); - jsonArray.push(jsonGraphic); - if (i == (length - 1)) { - var graphicsJSON = JSON.stringify(jsonArray); - callback(graphicsJSON); - break; - } - } - }; - - /** - * Returns an array of phantom graphics from the database. - * @param callback callback (true, array) or (false, errorString) - */ - layer.getPhantomGraphicsArray = function(callback){ - self._editStore.getPhantomGraphicsArray(function(array,message){ - if(message == "end"){ - callback(true,array); - } - else{ - callback(false,message); - } - }); - }; - - /** - * Returns the approximate size of the attachments database in bytes - * @param callback callback({usage}, error) Whereas, the usage Object is {sizeBytes: number, attachmentCount: number} - */ - layer.getAttachmentsUsage = function(callback) { - self.attachmentsStore.getUsage(function(usage,error){ - callback(usage,error); - }); - }; - - /** - * Full attachments database reset. - * CAUTION! If some attachments weren't successfully sent, then their record - * will still exist in the database. If you use this function you - * will also delete those records. - * @param callback (boolean, error) - */ - layer.resetAttachmentsDatabase = function(callback){ - self.attachmentsStore.resetAttachmentsQueue(function(result,error){ - callback(result,error); - }); - }; - - /** - * Returns the approximate size of the edits database in bytes - * @param callback callback({usage}, error) Whereas, the usage Object is {sizeBytes: number, editCount: number} - */ - layer.getUsage = function(callback){ - self._editStore.getUsage(function(usage,error){ - callback(usage,error); - }); - }; - - /** - * Full edits database reset. - * CAUTION! If some edits weren't successfully sent, then their record - * will still exist in the database. If you use this function you - * will also delete those records. - * @param callback (boolean, error) - */ - layer.resetDatabase = function(callback){ - self._editStore.resetEditsQueue(function(result,error){ - callback(result,error); - }); - }; - - /** - * Returns the number of edits pending in the database. - * @param callback callback( int ) - */ - layer.pendingEditsCount = function(callback){ - self._editStore.pendingEditsCount(function(count){ - callback(count); - }); - }; - - /** - * Create a featureDefinition - * @param featureLayer - * @param featuresArr - * @param geometryType - * @param callback - */ - layer.getFeatureDefinition = function (/* Object */ featureLayer, /* Array */ featuresArr, /* String */ geometryType, callback) { - - var featureDefinition = { - "layerDefinition": featureLayer, - "featureSet": { - "features": featuresArr, - "geometryType": geometryType - } - - }; - - callback(featureDefinition); - }; - - /** - * Returns an iterable array of all edits stored in the database - * Each item in the array is an object and contains: - * { - * id: "internal ID", - * operation: "add, update or delete", - * layer: "layerURL", - * type: "esri Geometry Type", - * graphic: "esri.Graphic converted to JSON then serialized" - * } - * @param callback (true, array) or (false, errorString) - */ - layer.getAllEditsArray = function(callback){ - self._editStore.getAllEditsArray(function(array,message){ - if(message == "end"){ - callback(true,array); - } - else{ - callback(false,message); - } - }); - }; - - /* internal methods */ - - /** - * Automatically creates a set of featureLayerCollections. This is specifically for - * use with offline browser restarts. You can retrieve the collections and use them - * to reconstitute a featureLayer and then redisplay all the associated features. - * - * To retrieve use OfflineFeaturesManager.getFeatureCollections(). - * @param callback (boolean) - * @private - */ - layer._pushFeatureCollections = function(callback){ - - // First let's see if any collections exists - self._editStore._getFeatureCollections(function(success, result) { - - var featureCollection = - { - featureLayerUrl: layer.url, - featureLayerCollection: layer.toJson() - }; - - // An array of feature collections, of course :-) - var featureCollectionsArray = [ - featureCollection - ]; - - // An object for storing multiple feature collections - var featureCollectionsObject = { - // The id is required because the editsStore keypath - // uses it as a UID for all entries in the database - id: self._editStore.FEATURE_COLLECTION_ID, - featureCollections: featureCollectionsArray - }; - - // THIS IS A HACK. - // There is a bug in JS API 3.11+ when you create a feature layer from a featureCollectionObject - // the hasAttachments property does not get properly repopulated. - layer.hasAttachments = featureCollection.featureLayerCollection.layerDefinition.hasAttachments; - - // If the featureCollectionsObject already exists - if(success){ - var count = 0; - for(var i = 0; i < result.featureCollections.length; i++) { - - // Update the current feature collection - if(result.featureCollections[i].featureLayerUrl === layer.url) { - count++; - result.featureCollections[i] = featureCollection; - } - } - - // If we have a new feature layer then add it to the featureCollections array - if(count === 0) { - result.featureCollections.push(featureCollection); - } - } - // If it does not exist then we need to add a featureCollectionsObject - else if(!success && result === null) { - result = featureCollectionsObject; - } - else { - console.error("There was a problem retrieving the featureCollections from editStore."); - } - - // Automatically update the featureCollectionsObject in the database with every ADD, UPDATE - // and DELETE. It can be retrieved via OfflineFeaturesManager.getFeatureCollections(); - self._editStore._pushFeatureCollections(result, function(success, error) { - if(!success){ - console.error("There was a problem creating the featureCollectionObject: " + error); - callback(false); - } - else { - callback(true); - } - }.bind(this)); - }); - }; - - /** - * Pushes a DELETE request to the database after it's been validated - * @param layer - * @param deleteEdit - * @param operation - * @param resultsArray - * @param objectId - * @param deferred - * @private - */ - layer._pushValidatedDeleteFeatureToDB = function(layer,deleteEdit,operation,resultsArray,objectId,deferred){ - self._editStore.pushEdit(operation, layer.url, deleteEdit, function (result, error) { - - if(result){ - resultsArray.deleteResults.push({success: true, error: null, objectId: objectId}); - - // Use the correct key as set by self.DB_UID - var tempIdObject = {}; - tempIdObject[self.DB_UID] = objectId; - - var phantomDelete = new Graphic( - deleteEdit.geometry, - self._getPhantomSymbol(deleteEdit.geometry, self._editStore.DELETE), - tempIdObject - ); - - layer._phantomLayer.add(phantomDelete); - - // Add phantom graphic to the database - self._editStore.pushPhantomGraphic(phantomDelete, function (result) { - if (!result) { - console.log("There was a problem adding phantom graphic id: " + objectId); - } - else{ - console.log("Phantom graphic " + objectId + " added to database as a deletion."); - domAttr.set(phantomDelete.getNode(), "stroke-dasharray", "4,4"); - domStyle.set(phantomDelete.getNode(), "pointer-events", "none"); - } - }); - - if (self.attachmentsStore) { - // delete local attachments of this feature, if any... we just launch the delete and don't wait for it to complete - self.attachmentsStore.deleteAttachmentsByFeatureId(layer.url, objectId, function (deletedCount) { - console.log("deleted", deletedCount, "attachments of feature", objectId); - }); - } - } - else{ - // If we can't push edit to database then we don't create a phantom graphic - resultsArray.deleteResults.push({success: false, error: error, objectId: objectId}); - } - - deferred.resolve(result); - }); - }; - - /** - * Pushes an UPDATE request to the database after it's been validated - * @param layer - * @param updateEdit - * @param operation - * @param resultsArray - * @param objectId - * @param deferred - * @private - */ - layer._pushValidatedUpdateFeatureToDB = function(layer,updateEdit,operation,resultsArray,objectId,deferred){ - self._editStore.pushEdit(operation, layer.url, updateEdit, function (result, error) { - - if(result){ - resultsArray.updateResults.push({success: true, error: null, objectId: objectId}); - - // Use the correct key as set by self.DB_UID - var tempIdObject = {}; - tempIdObject[self.DB_UID] = objectId; - - var phantomUpdate = new Graphic( - updateEdit.geometry, - self._getPhantomSymbol(updateEdit.geometry, self._editStore.UPDATE), - tempIdObject - ); - - layer._phantomLayer.add(phantomUpdate); - - // Add phantom graphic to the database - self._editStore.pushPhantomGraphic(phantomUpdate, function (result) { - if (!result) { - console.log("There was a problem adding phantom graphic id: " + objectId); - } - else{ - console.log("Phantom graphic " + objectId + " added to database as an update."); - domAttr.set(phantomUpdate.getNode(), "stroke-dasharray", "5,2"); - domStyle.set(phantomUpdate.getNode(), "pointer-events", "none"); - } - }); - - } - else{ - // If we can't push edit to database then we don't create a phantom graphic - resultsArray.updateResults.push({success: false, error: error, objectId: objectId}); - } - - deferred.resolve(result); - }); - }; - - /** - * Pushes an ADD request to the database after it's been validated - * @param layer - * @param addEdit - * @param operation - * @param resultsArray - * @param objectId - * @param deferred - * @private - */ - layer._pushValidatedAddFeatureToDB = function(layer,addEdit,operation,resultsArray,objectId,deferred){ - self._editStore.pushEdit(operation, layer.url, addEdit, function (result, error) { - if(result){ - resultsArray.addResults.push({success: true, error: null, objectId: objectId}); - - // Use the correct key as set by self.DB_UID - var tempIdObject = {}; - tempIdObject[self.DB_UID] = objectId; - - var phantomAdd = new Graphic( - addEdit.geometry, - self._getPhantomSymbol(addEdit.geometry, self._editStore.ADD), - tempIdObject - ); - - // Add phantom graphic to the layer - layer._phantomLayer.add(phantomAdd); - - // Add phantom graphic to the database - self._editStore.pushPhantomGraphic(phantomAdd, function (result) { - if (!result) { - console.log("There was a problem adding phantom graphic id: " + objectId); - } - else{ - console.log("Phantom graphic " + objectId + " added to database as an add."); - domAttr.set(phantomAdd.getNode(), "stroke-dasharray", "10,4"); - domStyle.set(phantomAdd.getNode(), "pointer-events", "none"); - } - - }); - } - else{ - // If we can't push edit to database then we don't create a phantom graphic - resultsArray.addResults.push({success: false, error: error, objectId: objectId}); - } - - deferred.resolve(result); - }); - }; - - /** - * Validates duplicate entries. Last edit on same feature can overwite any previous values. - * Note: if an edit was already added offline and you delete it then we return success == false - * @param graphic esri.Graphic. - * @param layerUrl the URL of the feature service - * @param operation add, update or delete action on an edit - * @returns deferred {success:boolean,graphic:graphic,operation:add|update|delete} - * @private - */ - layer._validateFeature = function (graphic,layerUrl,operation) { - - var deferred = new Deferred(); - - var id = layerUrl + "/" + graphic.attributes[self.DB_UID]; - - self._editStore.getEdit(id,function(success,result){ - if (success) { - switch( operation ) - { - case self._editStore.ADD: - // Not good - however we'll allow the new ADD to replace/overwrite existing edit - // and pass it through unmodified. Last ADD wins. - deferred.resolve({"success":true,"graphic":graphic,"operation":operation}); - break; - case self._editStore.UPDATE: - // If we are doing an update on a feature that has not been added to - // the server yet, then we need to maintain its operation as an ADD - // and not an UPDATE. This avoids the potential for an error if we submit - // an update operation on a feature that has not been added to the - // database yet. - if(result.operation == self._editStore.ADD){ - graphic.operation = self._editStore.ADD; - operation = self._editStore.ADD; - } - deferred.resolve({"success":true,"graphic":graphic,"operation":operation}); - break; - case self._editStore.DELETE: - - var resolved = true; - - if(result.operation == self._editStore.ADD){ - // If we are deleting a new feature that has not been added to the - // server yet we need to delete it and its phantom graphic. - layer._deleteTemporaryFeature(graphic,function(success){ - if(!success){ - resolved = false; - } - }); - } - deferred.resolve({"success":resolved,"graphic":graphic,"operation":operation}); - break; - } - } - else if(result == "Id not found"){ - // Let's simply pass the graphic back as good-to-go. - // No modifications needed because the graphic does not - // already exist in the database. - deferred.resolve({"success":true,"graphic":graphic,"operation":operation}); - } - else{ - deferred.reject(graphic); - } - }); - - return deferred; - }; - - /** - * Delete a graphic and its associated phantom graphic that has been added while offline. - * @param graphic - * @param callback - * @private - */ - layer._deleteTemporaryFeature = function(graphic,callback){ - - var phantomGraphicId = self._editStore.PHANTOM_GRAPHIC_PREFIX + self._editStore._PHANTOM_PREFIX_TOKEN + graphic.attributes[self.DB_UID]; - - function _deleteGraphic(){ - var deferred = new Deferred(); - self._editStore.delete(layer.url,graphic,function(success,error){ - if(success){ - deferred.resolve(true); - } - else{ - deferred.resolve(false); - } - }); - return deferred.promise; - } - - function _deletePhantomGraphic(){ - var deferred = new Deferred(); - self._editStore.deletePhantomGraphic(phantomGraphicId,function(success){ - if(success) { - deferred.resolve(true); - } - else { - deferred.resolve(false); - } - }, function(error) { - deferred.resolve(false); - }); - return deferred.promise; - } - - all([_deleteGraphic(),_deletePhantomGraphic()]).then(function (results) { - callback(results); - }); - - }; - - layer._getFilesFromForm = function (formNode) { - var files = []; - var inputNodes = array.filter(formNode.elements, function (node) { - return node.type === "file"; - }); - inputNodes.forEach(function (inputNode) { - files.push.apply(files, inputNode.files); - }, this); - return files; - }; - - layer._replaceFeatureIds = function (tempObjectIds, newObjectIds, callback) { - console.log("replacing ids of attachments", tempObjectIds, newObjectIds); - console.assert(tempObjectIds.length === newObjectIds.length, "arrays must be the same length"); - - if (!tempObjectIds.length) { - console.log("no ids to replace!"); - callback(0); - } - - var i, n = tempObjectIds.length; - var count = n; - var successCount = 0; - for (i = 0; i < n; i++) { - self.attachmentsStore.replaceFeatureId(this.url, tempObjectIds[i], newObjectIds[i], function (success) { - --count; - successCount += (success ? 1 : 0); - if (count === 0) { - callback(successCount); - } - }.bind(this)); - } - }; - - // 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; - layer._getNextTempId = function () { - return this._nextTempId--; - }; - - function _initPhantomLayer() { - try { - layer._phantomLayer = new GraphicsLayer({opacity: 0.8}); - layer._map.addLayer(layer._phantomLayer); - } - catch (err) { - console.log("Unable to init PhantomLayer: " + err.message); - } - } - - _initPhantomLayer(); - - // We are currently only passing in a single deferred. - all(extendPromises).then(function (r) { - - // DB already initialized - if(r.length === 0 && url){ - // Initialize the internal featureLayerCollectionObject - if(this.ENABLE_FEATURECOLLECTION) { - layer._pushFeatureCollections(function(success){ - if(success){ - callback(true, null); - } - else { - callback(false, null); - } - }); - } - else { - callback(true, null); - } - } - else if(r[0].success && !url){ - - // This functionality is specifically for offline restarts - // and attempts to retrieve a feature layer url. - // It's a hack because layer.toJson() doesn't convert layer.url. - this._editStore.getFeatureLayerJSON(function(success,message){ - if(success) { - this._featureLayers[message.__featureLayerURL] = layer; - layer.url = message.__featureLayerURL; - - // Initialize the internal featureLayerCollectionObject - if(this.ENABLE_FEATURECOLLECTION) { - layer._pushFeatureCollections(function(success){ - if(success){ - callback(true, null); - } - else { - callback(false, null); - } - }); - } - else { - callback(true, null); - } - } - else { - // NOTE: We have to have a valid feature layer URL in order to initialize the featureLayerCollectionObject - console.error("getFeatureLayerJSON() failed and unable to create featureLayerCollectionObject."); - callback(false, message); - } - }.bind(this)); - } - else if(r[0].success){ - - if(this.ENABLE_FEATURECOLLECTION) { - layer._pushFeatureCollections(function(success){ - if(success){ - callback(true, null); - } - else { - callback(false, null); - } - }); - } - else { - callback(true, null); - } - } - }.bind(this)); - - }, // extend - - /** - * Forces library into an offline state. Any edits applied during this condition will be stored locally - */ - goOffline: function () { - console.log("offlineFeatureManager going offline"); - this._onlineStatus = this.OFFLINE; - }, - - /** - * Forces library to return to an online state. If there are pending edits, - * an attempt will be made to sync them with the remote feature server - * @param callback callback( boolean, errors ) - */ - goOnline: function (callback) { - console.log("offlineFeaturesManager going online"); - this._onlineStatus = this.RECONNECTING; - this._replayStoredEdits(function (success, responses) { - var result = {success: success, responses: responses}; - this._onlineStatus = this.ONLINE; - if (this.attachmentsStore != null) { - console.log("sending attachments"); - this._sendStoredAttachments(function (success, uploadedResponses, dbResponses) { - //this._onlineStatus = this.ONLINE; - result.attachments = {success: success, responses: uploadedResponses, dbResponses: dbResponses}; - callback && callback(result); - }.bind(this)); - } - else { - //this._onlineStatus = this.ONLINE; - callback && callback(result); - } - }.bind(this)); - }, - - /** - * Determines if offline or online condition exists - * @returns {string} ONLINE or OFFLINE - */ - getOnlineStatus: function () { - return this._onlineStatus; - }, - - /** - * Serialize the feature layer graphics - * @param features Array of features - * @param callback Returns a JSON string - */ - serializeFeatureGraphicsArray: function (features, callback) { - var length = features.length; - var jsonArray = []; - for (var i = 0; i < length; i++) { - var jsonGraphic = features[i].toJson(); - jsonArray.push(jsonGraphic); - if (i == (length - 1)) { - var featureJSON = JSON.stringify(jsonArray); - callback(featureJSON); - break; - } - } - }, - - /** - * Retrieves the feature collection object. Specifically used in offline browser restarts. - * This is an object created automatically by the library and is updated with every ADD, UPDATE and DELETE. - * Attachments are handled separately and not part of the feature collection created here. - * - * It has the following signature: {id: "feature-collection-object-1001", - * featureLayerCollections: [{ featureCollection: [Object], featureLayerUrl: String }]} - * - * @param callback - */ - getFeatureCollections: function(callback){ - if(!this._editStore._isDBInit){ - - this._initializeDB(null,null).then(function(result){ - if(result.success){ - this._editStore._getFeatureCollections(function(success,message){ - callback(success,message); - }); - } - }.bind(this), function(err){ - callback(false, err); - }); - } - else { - this._editStore._getFeatureCollections(function(success,message){ - callback(success,message); - }); - } - }, - - /** - * Retrieves the optional feature layer storage object - * For use in full offline scenarios. - * @param callback callback(true, object) || callback(false, error) - */ - getFeatureLayerJSONDataStore: function(callback){ - if(!this._editStore._isDBInit){ - - this._initializeDB(null,null).then(function(result){ - if(result.success){ - this._editStore.getFeatureLayerJSON(function(success,message){ - callback(success,message); - }); - } - }.bind(this), function(err){ - callback(false, err); - }); - } - else { - this._editStore.getFeatureLayerJSON(function(success,message){ - callback(success,message); - }); - } - }, - - /* internal methods */ - - /** - * Initialize the database and push featureLayer JSON to DB if required. - * NOTE: also stores feature layer url in hidden dataStore property dataStore.__featureLayerURL. - * @param dataStore Object - * @param url Feature Layer's url. This is used by this library for internal feature identification. - * @param callback - * @private - */ - //_initializeDB: function(dataStore,url,callback){ - _initializeDB: function(dataStore,url){ - var deferred = new Deferred(); - - var editStore = this._editStore; - - // Configure the database - editStore.dbName = this.DB_NAME; - editStore.objectStoreName = this.DB_OBJECTSTORE_NAME; - editStore.objectId = this.DB_UID; - - // Attempt to initialize the database - editStore.init(function (result, error) { - - //////////////////////////////////////////////////// - // OFFLINE RESTART CONFIGURATION - // Added @ v2.5 - // - // Configure database for offline restart - // dataStore object allows you to store data that you'll - // use after an offline browser restart. - // - // If dataStore Object is not defined then do nothing. - // - //////////////////////////////////////////////////// - - if (typeof dataStore === "object" && result === true && (dataStore !== undefined) && (dataStore !== null)) { - - // Add a hidden property to hold the feature layer's url - // When converting a feature layer to json (layer.toJson()) we lose this information. - // This library needs to know the feature layer url. - if(url) { - dataStore.__featureLayerURL = url; - } - - editStore.pushFeatureLayerJSON(dataStore, function (success, err) { - if (success) { - deferred.resolve({success:true, error: null}); - } - else { - deferred.reject({success:false, error: err}); - } - }); - } - else if(result){ - deferred.resolve({success:true, error: null}); - } - else{ - deferred.reject({success:false, error: null}); - } - }); - - return deferred; - }, - - /** - * internal method that checks if this browser supports everything that is needed to handle offline attachments - * it also extends XMLHttpRequest with sendAsBinary() method, needed in Chrome - */ - _checkFileAPIs: function () { - if (window.File && window.FileReader && window.FileList && window.Blob) { - console.log("All APIs supported!"); - - if (!XMLHttpRequest.prototype.sendAsBinary) { - // https://code.google.com/p/chromium/issues/detail?id=35705#c40 - XMLHttpRequest.prototype.sendAsBinary = function (datastr) { - function byteValue(x) { - return x.charCodeAt(0) & 0xff; // jshint ignore:line - } - - var ords = Array.prototype.map.call(datastr, byteValue); - var ui8a = new Uint8Array(ords); - this.send(ui8a.buffer); - }; - console.log("extending XMLHttpRequest"); - } - return true; - } - console.log("The File APIs are not fully supported in this browser."); - return false; - }, - - /** - * internal method that extends an object with sendAsBinary() method - * sometimes extending XMLHttpRequest.prototype is not enough, as ArcGIS JS lib seems to mess with this object too - * @param oAjaxReq object to extend - */ - _extendAjaxReq: function (oAjaxReq) { - oAjaxReq.sendAsBinary = XMLHttpRequest.prototype.sendAsBinary; - console.log("extending XMLHttpRequest"); - }, - - // - // phantom symbols - // - - _phantomSymbols: [], - - _getPhantomSymbol: function (geometry, operation) { - if (this._phantomSymbols.length === 0) { - var color = [0, 255, 0, 255]; - var width = 1.5; - - this._phantomSymbols.point = []; - this._phantomSymbols.point[this._editStore.ADD] = new SimpleMarkerSymbol({ - "type": "esriSMS", "style": "esriSMSCross", - "xoffset": 10, "yoffset": 10, - "color": [255, 255, 255, 0], "size": 15, - "outline": {"color": color, "width": width, "type": "esriSLS", "style": "esriSLSSolid"} - }); - this._phantomSymbols.point[this._editStore.UPDATE] = new SimpleMarkerSymbol({ - "type": "esriSMS", "style": "esriSMSCircle", - "xoffset": 0, "yoffset": 0, - "color": [255, 255, 255, 0], "size": 15, - "outline": {"color": color, "width": width, "type": "esriSLS", "style": "esriSLSSolid"} - }); - this._phantomSymbols.point[this._editStore.DELETE] = new SimpleMarkerSymbol({ - "type": "esriSMS", "style": "esriSMSX", - "xoffset": 0, "yoffset": 0, - "color": [255, 255, 255, 0], "size": 15, - "outline": {"color": color, "width": width, "type": "esriSLS", "style": "esriSLSSolid"} - }); - this._phantomSymbols.multipoint = null; - - this._phantomSymbols.polyline = []; - this._phantomSymbols.polyline[this._editStore.ADD] = new SimpleLineSymbol({ - "type": "esriSLS", "style": "esriSLSSolid", - "color": color, "width": width - }); - this._phantomSymbols.polyline[this._editStore.UPDATE] = new SimpleLineSymbol({ - "type": "esriSLS", "style": "esriSLSSolid", - "color": color, "width": width - }); - this._phantomSymbols.polyline[this._editStore.DELETE] = new SimpleLineSymbol({ - "type": "esriSLS", "style": "esriSLSSolid", - "color": color, "width": width - }); - - this._phantomSymbols.polygon = []; - this._phantomSymbols.polygon[this._editStore.ADD] = new SimpleFillSymbol({ - "type": "esriSFS", - "style": "esriSFSSolid", - "color": [255, 255, 255, 0], - "outline": {"type": "esriSLS", "style": "esriSLSSolid", "color": color, "width": width} - }); - this._phantomSymbols.polygon[this._editStore.UPDATE] = new SimpleFillSymbol({ - "type": "esriSFS", - "style": "esriSFSSolid", - "color": [255, 255, 255, 0], - "outline": {"type": "esriSLS", "style": "esriSLSDash", "color": color, "width": width} - }); - this._phantomSymbols.polygon[this._editStore.DELETE] = new SimpleFillSymbol({ - "type": "esriSFS", - "style": "esriSFSSolid", - "color": [255, 255, 255, 0], - "outline": {"type": "esriSLS", "style": "esriSLSDot", "color": color, "width": width} - }); - } - - return this._phantomSymbols[geometry.type][operation]; - }, - - // - // methods to handle attachment uploads - // - - _uploadAttachment: function (attachment) { - var dfd = new Deferred(); - - var layer = this._featureLayers[attachment.featureLayerUrl]; - - var formData = new FormData(); - formData.append("attachment",attachment.file); - - switch(attachment.type){ - case this.attachmentsStore.TYPE.ADD: - layer.addAttachment(attachment.objectId,formData,function(evt){ - dfd.resolve({attachmentResult:evt,id:attachment.id}); - },function(err){ - dfd.reject(err); - }); - break; - case this.attachmentsStore.TYPE.UPDATE: - formData.append("attachmentId", attachment.id); - - // NOTE: - // We need to handle updates different from ADDS and DELETES because of how the JS API - // parses the DOM formNode property. - layer._sendAttachment("update",/* objectid */attachment.objectId, formData,function(evt){ - dfd.resolve({attachmentResult:evt,id:attachment.id}); - },function(err){ - dfd.reject(err); - }); - - break; - case this.attachmentsStore.TYPE.DELETE: - // IMPORTANT: This method returns attachmentResult as an Array. Whereas ADD and UPDATE do not!! - layer.deleteAttachments(attachment.objectId,[attachment.id],function(evt){ - dfd.resolve({attachmentResult:evt,id:attachment.id}); - },function(err){ - dfd.reject(err); - }); - break; - } - - return dfd.promise; - }, - - _deleteAttachmentFromDB: function (attachmentId, uploadResult) { - var dfd = new Deferred(); - - console.log("upload complete", uploadResult, attachmentId); - this.attachmentsStore.delete(attachmentId, function (success) { - console.assert(success === true, "can't delete attachment already uploaded"); - console.log("delete complete", success); - dfd.resolve({success:success,result:uploadResult}); - }); - - return dfd; - }, - - /** - * Removes attachments from DB if they were successfully uploaded - * @param results promises.results - * @callback callback callback( {errors: boolean, attachmentsDBResults: results, uploadResults: results} ) - * @private - */ - _cleanAttachmentsDB: function(results,callback){ - - var self = this; - var promises = []; - var count = 0; - - results.forEach(function(value){ - - if(typeof value.attachmentResult == "object" && value.attachmentResult.success){ - // Delete an attachment from the database if it was successfully - // submitted to the server. - promises.push(self._deleteAttachmentFromDB(value.id,null)); - } - // NOTE: layer.deleteAttachments returns an array rather than an object - else if(value.attachmentResult instanceof Array){ - - // Because we get an array we have to cycle thru it to verify all results - value.attachmentResult.forEach(function(deleteValue){ - if(deleteValue.success){ - // Delete an attachment from the database if it was successfully - // submitted to the server. - promises.push(self._deleteAttachmentFromDB(value.id,null)); - } - else { - count++; - } - }); - } - else{ - // Do nothing. Don't delete attachments from DB if we can't upload them - count++; - } - }); - - var allPromises = all(promises); - allPromises.then(function(dbResults){ - if(count > 0){ - // If count is greater than zero then we have errors and need to set errors to true - callback({errors: true, attachmentsDBResults: dbResults, uploadResults: results}); - } - else{ - callback({errors: false, attachmentsDBResults: dbResults, uploadResults: results}); - } - }); - }, - - /** - * Attempts to upload stored attachments when the library goes back on line. - * @param callback callback({success: boolean, uploadResults: results, dbResults: results}) - * @private - */ - _sendStoredAttachments: function (callback) { - this.attachmentsStore.getAllAttachments(function (attachments) { - - var self = this; - - console.log("we have", attachments.length, "attachments to upload"); - - var promises = []; - attachments.forEach(function (attachment) { - console.log("sending attachment", attachment.id, "to feature", attachment.featureId); - - var uploadAttachmentComplete = this._uploadAttachment(attachment); - promises.push(uploadAttachmentComplete); - }, this); - console.log("promises", promises.length); - var allPromises = all(promises); - allPromises.then(function (uploadResults) { - console.log(uploadResults); - self._cleanAttachmentsDB(uploadResults,function(dbResults){ - if(dbResults.errors){ - callback && callback(false, uploadResults,dbResults); - } - else{ - callback && callback(true, uploadResults,dbResults); - } - }); - }, - function (err) { - console.log("error!", err); - callback && callback(false, err); - }); - }.bind(this)); - }, - - // - // methods to send features back to the server - // - - /** - * Attempts to send any edits in the database. Monitor events for success or failure. - * @param callback - * @event ALL_EDITS_SENT when all edits have been successfully sent. Contains {[addResults],[updateResults],[deleteResults]} - * @event EDITS_SENT_ERROR some edits were not sent successfully. Contains {msg: error} - * @private - */ - _replayStoredEdits: function (callback) { - var promises = {}; - var that = this; - - // - // send edits for each of the layers - // - var layer; - var adds = [], updates = [], deletes = []; - var tempObjectIds = []; - var tempArray = []; - var featureLayers = this._featureLayers; - var attachmentsStore = this.attachmentsStore; - var editStore = this._editStore; - - this._editStore.getAllEditsArray(function (result, err) { - if (result.length > 0) { - tempArray = result; - - var length = tempArray.length; - - for (var n = 0; n < length; n++) { - layer = featureLayers[tempArray[n].layer]; - - // If the layer has attachments then check to see if the attachmentsStore has been initialized - if (attachmentsStore == null && layer.hasAttachments) { - console.log("NOTICE: you may need to run OfflineFeaturesManager.initAttachments(). Check the Attachments doc for more info. Layer id: " + layer.id + " accepts attachments"); - } - - // Assign the attachmentsStore to the layer as a private var so we can access it from - // the promises applyEdits() method. - layer._attachmentsStore = attachmentsStore; - - layer.__onEditsComplete = layer.onEditsComplete; - layer.onEditsComplete = function () { - console.log("intercepting events onEditsComplete"); - }; - - // Let's zero everything out - adds = [], updates = [], deletes = [], tempObjectIds = []; - - // IMPORTANT: reconstitute the graphic JSON into an actual esri.Graphic object - // NOTE: we are only sending one Graphic per loop! - var graphic = new Graphic(tempArray[n].graphic); - - switch (tempArray[n].operation) { - case editStore.ADD: - for (var i = 0; i < layer.graphics.length; i++) { - var g = layer.graphics[i]; - if (g.attributes[layer.objectIdField] === graphic.attributes[layer.objectIdField]) { - layer.remove(g); - break; - } - } - tempObjectIds.push(graphic.attributes[layer.objectIdField]); - delete graphic.attributes[layer.objectIdField]; - adds.push(graphic); - break; - case editStore.UPDATE: - updates.push(graphic); - break; - case editStore.DELETE: - deletes.push(graphic); - break; - } - - //if(that._featureCollectionUsageFlag){ - // Note: when the feature layer is created with a feature collection we have to handle applyEdits() differently - // TO-DO rename this method. - promises[n] = that._internalApplyEditsAll(layer, tempArray[n].id, tempObjectIds, adds, updates, deletes); - } - - // wait for all requests to finish - // responses contain {id,layer,tempId,addResults,updateResults,deleteResults} - var allPromises = all(promises); - allPromises.then( - function (responses) { - console.log("OfflineFeaturesManager sync - all responses are back"); - - this._parseResponsesArray(responses).then(function(result) { - if(result) { - this.emit(this.events.ALL_EDITS_SENT,responses); - } - else { - this.emit(this.events.EDITS_SENT_ERROR, {msg: "Not all edits synced", respones: responses}); - } - callback && callback(true, responses); - }.bind(this)); - }.bind(that), - function (errors) { - console.log("OfflineFeaturesManager._replayStoredEdits - ERROR!!"); - console.log(errors); - callback && callback(false, errors); - }.bind(that) - ); - - } - else{ - // No edits were found - callback(true,[]); - } - }); - }, - - /** - * DEPRECATED as of v2.11 - - * TO-DO remove in next release - * Only delete items from database that were verified as successfully updated on the server. - * @param responses Object - * @param callback callback(true, responses) or callback(false, responses) - * @private - */ - _cleanSuccessfulEditsDatabaseRecords: function (responses, callback) { - if (Object.keys(responses).length !== 0) { - - var editsArray = []; - var editsFailedArray = []; - - for (var key in responses) { - if (responses.hasOwnProperty(key)) { - - var edit = responses[key]; - var tempResult = {}; - - if (edit.updateResults.length > 0) { - if (edit.updateResults[0].success) { - tempResult.layer = edit.layer; - tempResult.id = edit.updateResults[0].objectId; - editsArray.push(tempResult); - } - else { - editsFailedArray.push(edit); - } - } - if (edit.deleteResults.length > 0) { - if (edit.deleteResults[0].success) { - tempResult.layer = edit.layer; - tempResult.id = edit.deleteResults[0].objectId; - editsArray.push(tempResult); - } - else { - editsFailedArray.push(edit); - } - } - if (edit.addResults.length > 0) { - if (edit.addResults[0].success) { - tempResult.layer = edit.layer; - tempResult.id = edit.tempId; - editsArray.push(tempResult); - } - else { - editsFailedArray.push(edit); - } - } - } - } - - var promises = {}; - var length = editsArray.length; - for (var i = 0; i < length; i++) { - promises[i] = this._updateDatabase(editsArray[i]); - } - //console.log("EDIT LIST " + JSON.stringify(editsArray)); - - // wait for all requests to finish - // - var allPromises = all(promises); - allPromises.then( - function (responses) { - editsFailedArray.length > 0 ? callback(false, responses) : callback(true, responses); - }, - function (errors) { - callback(false, errors); - } - ); - } - else { - callback(true, {}); - } - }, - - /** - * Deletes edits from database. - * This does not handle phantom graphics! - * @param edit - * @returns {l.Deferred.promise|*|c.promise|q.promise|promise} - * @private - */ - _updateDatabase: function (edit) { - var dfd = new Deferred(); - var fakeGraphic = {}; - fakeGraphic.attributes = {}; - - // Use the correct attributes key! - fakeGraphic.attributes[this.DB_UID] = edit.id; - - this._editStore.delete(edit.layer, fakeGraphic, function (success, error) { - if (success) { - dfd.resolve({success: true, error: null}); - } - else { - dfd.reject({success: false, error: error}); - } - }.bind(this)); - - return dfd.promise; - - }, - - /** - * Retrieves f=json from the feature layer - * @param url FeatureLayer's URL - * @param callback - * @private - */ - getFeatureLayerJSON: function (url, callback) { - require(["esri/request"], function (esriRequest) { - var request = esriRequest({ - url: url, - content: {f: "json"}, - handleAs: "json", - callbackParamName: "callback" - }); - - request.then(function (response) { - console.log("Success: ", response); - callback(true, response); - }, function (error) { - console.log("Error: ", error.message); - callback(false, error.message); - }); - }); - }, - - /** - * DEPRECATED at v2.11 - * Executes the _applyEdits() method when a feature layer is created using a REST endpoint - * @param layer - * @param id the unique id that identifies the Graphic in the database - * @param tempObjectIds - * @param adds - * @param updates - * @param deletes - * @returns {l.Deferred.promise} contains {id,layer,tempId,addResults,updateResults,deleteResults} - * @private - */ - _internalApplyEdits: function (layer, id, tempObjectIds, adds, updates, deletes) { - var that = this; - var dfd = new Deferred(); - - layer._applyEdits(adds, updates, deletes, - function (addResults, updateResults, deleteResults) { - layer._phantomLayer.clear(); - - // We use a different pattern if the attachmentsStore is valid and the layer has attachments - if (layer._attachmentsStore != null && layer.hasAttachments && tempObjectIds.length > 0) { - - var newObjectIds = addResults.map(function (r) { - return r.objectId; - }); - - layer._replaceFeatureIds(tempObjectIds, newObjectIds, function (count) { - console.log("Done replacing feature ids. Total count = " + count); - }); - } - - that._cleanDatabase(layer, tempObjectIds, addResults, updateResults, deleteResults).then(function(results){ - dfd.resolve({ - id: id, - layer: layer.url, - tempId: tempObjectIds, // let's us internally match an ADD to it's new ObjectId - addResults: addResults, - updateResults: updateResults, - deleteResults: deleteResults, - databaseResults: results, - databaseErrors: null - }); // wrap three arguments in a single object - }, function(error) { - dfd.resolve({ - id: id, - layer: layer.url, - tempId: tempObjectIds, // let's us internally match an ADD to it's new ObjectId - addResults: addResults, - updateResults: updateResults, - deleteResults: deleteResults, - databaseResults: null, - databaseErrors: error - }); // wrap three arguments in a single object - }); - - }, - function (error) { - layer.onEditsComplete = layer.__onEditsComplete; - delete layer.__onEditsComplete; - - dfd.reject(error); - } - ); - return dfd.promise; - }, - - /** - * Applies edits. This works with both standard feature layers and when a feature layer is created - * using a feature collection. - * - * This works around specific behaviors in esri.layers.FeatureLayer when using the pattern - * new FeatureLayer(featureCollectionObject). - * - * Details on the specific behaviors can be found here: - * https://developers.arcgis.com/javascript/jsapi/featurelayer-amd.html#featurelayer2 - * - * @param layer - * @param id - * @param tempObjectIds - * @param adds - * @param updates - * @param deletes - * @returns {*|r} - * @private - */ - _internalApplyEditsAll: function (layer, id, tempObjectIds, adds, updates, deletes) { - var that = this; - var dfd = new Deferred(); - - this._makeEditRequest(layer, adds, updates, deletes, - function (addResults, updateResults, deleteResults) { - layer._phantomLayer.clear(); - - // We use a different pattern if the attachmentsStore is valid and the layer has attachments - if (layer._attachmentsStore != null && layer.hasAttachments && tempObjectIds.length > 0) { - - var newObjectIds = addResults.map(function (r) { - return r.objectId; - }); - - layer._replaceFeatureIds(tempObjectIds, newObjectIds, function (count) { - console.log("Done replacing feature ids. Total count = " + count); - }); - } - - if(addResults.length > 0) { - var graphic = new Graphic(adds[0].geometry,null,adds[0].attributes); - layer.add(graphic); - } - - that._cleanDatabase(layer, tempObjectIds, addResults, updateResults, deleteResults).then(function(results){ - dfd.resolve({ - id: id, - layer: layer.url, - tempId: tempObjectIds, // let's us internally match an ADD to it's new ObjectId - addResults: addResults, - updateResults: updateResults, - deleteResults: deleteResults, - databaseResults: results, - databaseErrors: null, - syncError: null - }); - }, function(error) { - dfd.resolve({ - id: id, - layer: layer.url, - tempId: tempObjectIds, // let's us internally match an ADD to it's new ObjectId - addResults: addResults, - updateResults: updateResults, - deleteResults: deleteResults, - databaseResults: null, - databaseErrors: error, - syncError: error - }); - }); - - }, - function (error) { - layer.onEditsComplete = layer.__onEditsComplete; - delete layer.__onEditsComplete; - - dfd.reject(error); - } - ); - return dfd.promise; - }, - - _cleanDatabase: function(layer, tempId, addResults, updateResults, deleteResults) { - - var dfd = new Deferred(); - var id = null; - - if (updateResults.length > 0) { - if (updateResults[0].success) { - id = updateResults[0].objectId; - } - } - if (deleteResults.length > 0) { - if (deleteResults[0].success) { - id = deleteResults[0].objectId; - } - } - if (addResults.length > 0) { - if (addResults[0].success) { - id = tempId; - } - } - - var fakeGraphic = {}; - fakeGraphic.attributes = {}; - - // Use the correct attributes key! - fakeGraphic.attributes[this.DB_UID] = id; - - // Delete the edit from the database - this._editStore.delete(layer.url, fakeGraphic, function (success, error) { - if (success) { - - var id = this._editStore.PHANTOM_GRAPHIC_PREFIX + this._editStore._PHANTOM_PREFIX_TOKEN + fakeGraphic.attributes[this.DB_UID]; - - // Delete the phantom graphic associated with the dit - this._editStore.deletePhantomGraphic(id, function(success,error){ - if(!success) { - console.log("_cleanDatabase delete phantom graphic error: " + error); - dfd.reject({success: false, error: error, id: id}); - } - else { - console.log("_cleanDatabase success: " + id); - dfd.resolve({success: true, error: null, id: id}); - } - }); - } - else { - dfd.reject({success: false, error: error, id: id}); - } - }.bind(this)); - - return dfd.promise; - }, - - /** - * Used when a feature layer is created with a feature collection. - * - * In the current version of the ArcGIS JSAPI 3.12+ the applyEdit() method doesn't send requests - * to the server when a feature layer is created with a feature collection. - * - * The use case for using this is: clean start app > go offline and make edits > offline restart browser > - * go online. - * - * @param layer - * @param adds - * @param updates - * @param deletes - * @returns {*|r} - * @private - */ - _makeEditRequest: function(layer,adds, updates, deletes, callback, errback) { - - var f = "f=json", a = "", u = "", d = ""; - - if(adds.length > 0) { - array.forEach(adds, function(add){ - if(add.hasOwnProperty("infoTemplate")){ // if the add has an infoTemplate attached, - delete add.infoTemplate; // delete it to reduce payload size. - } - }, this); - a = "&adds=" + JSON.stringify((adds)); - } - if(updates.length > 0) { - array.forEach(updates, function(update){ - if(update.hasOwnProperty("infoTemplate")){ // if the update has an infoTemplate attached, - delete update.infoTemplate; // delete it to reduce payload size. - } - }, this); - u = "&updates=" + JSON.stringify(updates); - } - if(deletes.length > 0) { - var id = deletes[0].attributes[this.DB_UID]; - d = "&deletes=" + id; - } - - var params = f + a + u + d; - - if(layer.hasOwnProperty("credential") && layer.credential){ - if(layer.credential.hasOwnProperty("token") && layer.credential.token){ - params = params + "&token=" + layer.credential.token; - } - } - - var req = new XMLHttpRequest(); - req.open("POST", layer.url + "/applyEdits", true); - req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - req.onload = function() - { - if( req.status === 200 && req.responseText !== "") - { - try { - var obj = JSON.parse(this.response); - callback(obj.addResults, obj.updateResults, obj.deleteResults); - } - catch(err) { - console.error("EDIT REQUEST REPONSE WAS NOT SUCCESSFUL:", req); - errback("Unable to parse xhr response", req); - } - } - - }; - req.onerror = function(e) - { - console.error("_makeEditRequest failed: " + e); - errback(e); - }; - req.ontimeout = function() { - errback("xhr timeout error"); - }; - req.timeout = this._defaultXhrTimeout; - req.send(params); - }, - - /** - * Parses the respones related to going back online and cleaning up the database. - * @param responses - * @returns {promise} True means all was successful. False indicates there was a problem. - * @private - */ - _parseResponsesArray: function(responses) { - - var dfd = new Deferred(); - var err = 0; - - for (var key in responses) { - if (responses.hasOwnProperty(key)) { - responses[key].addResults.map(function(result){ - if(!result.success) { - err++; - } - }); - - responses[key].updateResults.map(function(result){ - if(!result.success) { - err++; - } - }); - - responses[key].deleteResults.map(function(result){ - if(!result.success) { - err++; - } - }); - } - } - - if(err > 0){ - dfd.resolve(false); - } - else { - dfd.resolve(true); - } - - return dfd.promise; - } - }); // declare - }); // define - -/** - * Creates a namespace for the non-AMD libraries in this directory - */ -/*jshint -W020 */ -if(typeof O != "undefined"){ - O.esri.Edit = {}; -} -else{ - O = {}; - O.esri = { - Edit: {} - }; -} -/*global indexedDB */ -/*jshint -W030 */ -O.esri.Edit.EditStore = function () { - - "use strict"; - - this._db = null; - this._isDBInit = false; - - // Public properties - - this.dbName = "features_store"; - this.objectStoreName = "features"; - this.objectId = "objectid"; // set this depending on how your feature service is configured; - - //var _dbIndex = "featureId"; // @private - - // ENUMs - - 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 () { - if (!window.indexedDB) { - return false; - } - return true; - }; - - /** - * Commit an edit to the database - * @param operation add, update or delete - * @param layerUrl the URL of the feature layer - * @param graphic esri/graphic. The method will serialize to JSON - * @param callback callback(true, edit) or callback(false, error) - */ - this.pushEdit = function (operation, layerUrl, graphic, callback) { - - var edit = { - id: layerUrl + "/" + graphic.attributes[this.objectId], - operation: operation, - layer: layerUrl, - type: graphic.geometry.type, - graphic: graphic.toJson() - }; - - if(typeof graphic.attributes[this.objectId] === "undefined") { - console.error("editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineFeaturesManager.DB_UID? " + JSON.stringify(graphic.attributes)); - callback(false,"editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineFeaturesManager.DB_UID? " + JSON.stringify(graphic.attributes)); - } - else{ - var transaction = this._db.transaction([this.objectStoreName], "readwrite"); - - transaction.oncomplete = function (event) { - callback(true); - }; - - transaction.onerror = function (event) { - callback(false, event.target.error.message); - }; - - var objectStore = transaction.objectStore(this.objectStoreName); - objectStore.put(edit); - } - }; - - /** - * Use this to store any static FeatureLayer or related JSON data related to your app that will assist in restoring - * a FeatureLayer. - * - * Handles both adds and updates. It copies any object properties, so it will not, by default, overwrite the entire object. - * - * Example 1: If you just submit {featureLayerRenderer: {someJSON}} it will only update the featureLayerRenderer property - * Example 2: This is a full example - * { - * featureLayerJSON: ..., - * graphics: ..., // Serialized Feature Layer graphics. Must be serialized! - * renderer: ..., - * opacity: ..., - * outfields: ..., - * mode: ..., - * extent: ..., - * zoom: 7, - * lastEdit: ... - * } - * - * NOTE: "dataObject.id" is a reserved property. If you use "id" in your object this method will break. - * @param dataStore Object - * @param callback callback(true, null) or callback(false, error) - */ - this.pushFeatureLayerJSON = function (dataStore /*Object*/, callback) { - - console.assert(this._db !== null, "indexeddb not initialized"); - if (typeof dataStore != "object") { - callback(false, "dataObject type is not an object."); - } - - var db = this._db; - dataStore.id = this.FEATURE_LAYER_JSON_ID; - - this.getFeatureLayerJSON(function (success, result) { - - var objectStore; - - if (success && typeof result !== "undefined") { - - objectStore = db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); - - // Make a copy of the object - for (var key in dataStore) { - if (dataStore.hasOwnProperty(key)) { - result[key] = dataStore[key]; - } - } - - // Insert the update into the database - var updateFeatureLayerDataRequest = objectStore.put(result); - - updateFeatureLayerDataRequest.onsuccess = function () { - callback(true, null); - }; - - updateFeatureLayerDataRequest.onerror = function (err) { - callback(false, err); - }; - } - else { - - var transaction = db.transaction([this.objectStoreName], "readwrite"); - - transaction.oncomplete = function (event) { - callback(true, null); - }; - - transaction.onerror = function (event) { - callback(false, event.target.error.message); - }; - - objectStore = transaction.objectStore(this.objectStoreName); - - // Protect against data cloning errors since we don't validate the input object - // Example: if you attempt to use an esri.Graphic in its native form you'll get a data clone error - try { - objectStore.put(dataStore); - } - catch (err) { - callback(false, JSON.stringify(err)); - } - } - }.bind(this)); - }; - - /** - * Retrieve the FeatureLayer data object - * @param callback callback(true, object) || callback(false, error) - */ - this.getFeatureLayerJSON = function (callback) { - - console.assert(this._db !== null, "indexeddb not initialized"); - - var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); - - //Get the entry associated with the graphic - var objectStoreGraphicRequest = objectStore.get(this.FEATURE_LAYER_JSON_ID); - - objectStoreGraphicRequest.onsuccess = function () { - var object = objectStoreGraphicRequest.result; - if (typeof object != "undefined") { - callback(true, object); - } - else { - callback(false, "nothing found"); - } - }; - - objectStoreGraphicRequest.onerror = function (msg) { - callback(false, msg); - }; - }; - - /** - * Safe delete. Checks if id exists, then reverifies. - * @param callback callback(boolean, {message: String}) - */ - this.deleteFeatureLayerJSON = function (callback) { - // NOTE: the implementation of the IndexedDB spec has a design fault with respect to - // handling deletes. The result of a delete operation is always designated as undefined. - // What this means is that there is no way to tell if an operation was successful or not. - // And, it will always return 'true.' - // - // In order to get around this we have to verify if after the attempted deletion operation - // if the record is or is not in the database. Kinda dumb, but that's how IndexedDB works. - //http://stackoverflow.com/questions/17137879/is-there-a-way-to-get-information-on-deleted-record-when-calling-indexeddbs-obj - - var db = this._db; - var deferred = null; - var self = this; - - var id = this.FEATURE_LAYER_JSON_ID; - - require(["dojo/Deferred"], function (Deferred) { - deferred = new Deferred(); - - // Step 4 - Then we check to see if the record actually exists or not. - deferred.then(function (result) { - // IF the delete was successful, then the record should return an error because it doesn't exist. - // We aren't 100% sure how all platforms will perform so we also trap the promise for return results. - self.editExists(id).then(function (results) { - // If edit does exist then we have not been successful in deleting the object. - callback(false, {message: "object was not deleted."}); - }, - function (err) { - // If the result is false then in theory the id no longer exists - // and we should return 'true' to indicate a successful delete operation. - callback(true, {message: "id does not exist"}); //because we want this test to throw an error. That means item deleted. - }); - }, - // There was a problem with the delete operation on the database - // This error message will come from editExists(); - function (err) { - callback(false, {message: "id does not exist"}); - }); - - // Step 1 - lets see if record exits. If it does not then return callback. Otherwise, - // continue on with the deferred. - self.editExists(id).then(function (result) { - - var objectStore = db.transaction([self.objectStoreName], "readwrite").objectStore(self.objectStoreName); - - // Step 2 - go ahead and delete graphic - var objectStoreDeleteRequest = objectStore.delete(id); - - // Step 3 - We know that the onsuccess will always fire unless something serious goes wrong. - // So we go ahead and resolve the deferred here. - objectStoreDeleteRequest.onsuccess = function () { - deferred.resolve(true); - }; - - objectStoreDeleteRequest.onerror = function (msg) { - deferred.reject({success: false, error: msg}); - }; - - }, - // If there is an error in editExists() - function (err) { - deferred.reject({success: false, message: err}); - }.bind(this)); - }); - }; - - /** - * Add a phantom graphic to the store. - * IMPORTANT! Requires graphic to have an objectId - * @param graphic - * @param callback - */ - this.pushPhantomGraphic = function (graphic, callback) { - console.assert(this._db !== null, "indexeddb not initialized"); - - var db = this._db; - var id = this.PHANTOM_GRAPHIC_PREFIX + this._PHANTOM_PREFIX_TOKEN + graphic.attributes[this.objectId]; - - var object = { - id: id, - graphic: graphic.toJson() - }; - - var transaction = db.transaction([this.objectStoreName], "readwrite"); - - transaction.oncomplete = function (event) { - callback(true, null); - }; - - transaction.onerror = function (event) { - callback(false, event.target.error.message); - }; - - var objectStore = transaction.objectStore(this.objectStoreName); - objectStore.put(object); - - }; - - /** - * Return an array of phantom graphics - * @param callback - */ - this.getPhantomGraphicsArray = function (callback) { - console.assert(this._db !== null, "indexeddb not initialized"); - var editsArray = []; - - if (this._db !== null) { - - 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.indexOf(phantomGraphicPrefix) != -1) { - editsArray.push(cursor.value); - - } - cursor.continue(); - } - else { - callback(editsArray, "end"); - } - }.bind(this); - transaction.onerror = function (err) { - callback(null, err); - }; - } - else { - callback(null, "no db"); - } - }; - - /** - * Internal method that returns an array of id's only - * @param callback - * @private - */ - this._getPhantomGraphicsArraySimple = function (callback) { - console.assert(this._db !== null, "indexeddb not initialized"); - var editsArray = []; - - if (this._db !== null) { - - 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.indexOf(phantomGraphicPrefix) != -1) { - editsArray.push(cursor.value.id); - - } - cursor.continue(); - } - else { - callback(editsArray, "end"); - } - }.bind(this); - transaction.onerror = function (err) { - callback(null, err); - }; - } - else { - callback(null, "no db"); - } - }; - - /** - * Deletes an individual graphic from the phantom layer - * @param id Internal ID - * @param callback callback(boolean, message) - */ - this.deletePhantomGraphic = function (id, callback) { - // NOTE: the implementation of the IndexedDB spec has a design fault with respect to - // handling deletes. The result of a delete operation is always designated as undefined. - // What this means is that there is no way to tell if an operation was successful or not. - // And, it will always return 'true.' - // - // In order to get around this we have to verify if after the attempted deletion operation - // if the record is or is not in the database. Kinda dumb, but that's how IndexedDB works. - //http://stackoverflow.com/questions/17137879/is-there-a-way-to-get-information-on-deleted-record-when-calling-indexeddbs-obj - - var db = this._db; - var deferred = null; - var self = this; - - require(["dojo/Deferred"], function (Deferred) { - deferred = new Deferred(); - - // Step 1 - lets see if record exits. If it does then return callback. - self.editExists(id).then(function (result) { - - // Step 4 - Then we check to see if the record actually exists or not. - deferred.then(function (result) { - - // IF the delete was successful, then the record should return 'false' because it doesn't exist. - self.editExists(id).then(function (results) { - callback(false, "item was not deleted"); // item is still in the database!! - }, - function (err) { - callback(true, "item successfully deleted"); //because we want this test to throw an error. That means item deleted. - }); - }, - // There was a problem with the delete operation on the database - function (err) { - callback(false, err); - }); - - var objectStore = db.transaction([self.objectStoreName], "readwrite").objectStore(self.objectStoreName); - - // Step 2 - go ahead and delete graphic - var objectStoreDeleteRequest = objectStore.delete(id); - - // Step 3 - We know that the onsuccess will always fire unless something serious goes wrong. - // So we go ahead and resolve the deferred here. - objectStoreDeleteRequest.onsuccess = function () { - deferred.resolve(true); - }; - - objectStoreDeleteRequest.onerror = function (msg) { - deferred.reject({success: false, error: msg}); - }; - }, - // If there is an error in editExists() - function (err) { - callback(false, "item doesn't exist in db"); - }); - }); - }; - - /** - * DEPRECATED at v2.11 - * TO-DO remove in the next release - * - * Removes some phantom graphics from database. - * The responseObject contains {id,layer,tempId,addResults,updateResults,deleteResults}. - * IF there are no results.success then nothing will be deleted. - * - * WARNING: Can generate false positives. IndexedDB will always return success - * even if you attempt to delete a non-existent id. - * - * CAUTION: This should always be used in conjunction with deleting the phantom graphic's - * associated edit entry in the database. - * - * @param responseObject - * @param callback boolean - */ - this.resetLimitedPhantomGraphicsQueue = function (responseObject, callback) { - - if (Object.keys(responseObject).length > 0) { - var db = this._db; - - var errors = 0; - var tx = db.transaction([this.objectStoreName], "readwrite"); - var objectStore = tx.objectStore(this.objectStoreName); - - objectStore.onerror = function () { - errors++; - console.log("PHANTOM GRAPHIC ERROR"); - }; - - tx.oncomplete = function () { - errors === 0 ? callback(true) : callback(false); - }; - - for (var key in responseObject) { - if (responseObject.hasOwnProperty(key)) { - var edit = responseObject[key]; - var id = this.PHANTOM_GRAPHIC_PREFIX + this._PHANTOM_PREFIX_TOKEN + edit.id; - - // CAUTION: - // TO-DO we do NOT match the edit.id with edit's objectId - - // If we have an add, update or delete success then delete the entry, otherwise we skip it. - if(edit.updateResults.length > 0){ - if (edit.updateResults[0].success){ - objectStore.delete(id); - } - } - - if(edit.deleteResults.length > 0){ - if (edit.deleteResults[0].success){ - objectStore.delete(id); - } - } - - if(edit.addResults.length > 0){ - if (edit.addResults[0].success){ - objectStore.delete(id); - } - } - } - } - } - else { - callback(true); - } - }; - - - /** - * Removes all phantom graphics from database - * @param callback boolean - */ - this.resetPhantomGraphicsQueue = function (callback) { - - var db = this._db; - - // First we need to get the array of graphics that are stored in the database - // so that we can cycle thru them. - this._getPhantomGraphicsArraySimple(function (array) { - if (array != []) { - - var errors = 0; - var tx = db.transaction([this.objectStoreName], "readwrite"); - var objectStore = tx.objectStore(this.objectStoreName); - - objectStore.onerror = function () { - errors++; - }; - - tx.oncomplete = function () { - errors === 0 ? callback(true) : callback(false); - }; - - var length = array.length; - for (var i = 0; i < length; i++) { - objectStore.delete(array[i]); - } - } - else { - callback(true); - } - }.bind(this)); - }; - - /** - * Retrieve an edit by its internal ID - * @param id String identifier - * @param callback callback(true,graphic) or callback(false, error) - */ - this.getEdit = function(id,callback){ - - console.assert(this._db !== null, "indexeddb not initialized"); - var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); - - if(typeof id === "undefined"){ - callback(false,"id is undefined."); - return; - } - - //Get the entry associated with the graphic - var objectStoreGraphicRequest = objectStore.get(id); - - objectStoreGraphicRequest.onsuccess = function () { - var graphic = objectStoreGraphicRequest.result; - if (graphic && (graphic.id == id)) { - callback(true,graphic); - } - else { - callback(false,"Id not found"); - } - }; - - objectStoreGraphicRequest.onerror = function (msg) { - callback(false,msg); - }; - }; - - /** - * Returns all the edits recursively via the callback - * @param callback {value, message} - */ - this.getAllEdits = function (callback) { - - console.assert(this._db !== null, "indexeddb not initialized"); - - 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.hasOwnProperty("value") && cursor.value.hasOwnProperty("id")) { - - // Make sure we are not return FeatureLayer JSON data or a Phantom Graphic - if (cursor.value.id !== fLayerJSONId && cursor.value.id !== fCollectionId && cursor.value.id.indexOf(phantomGraphicPrefix) == -1) { - callback(cursor.value, null); - } - cursor.continue(); - } - else { - callback(null, "end"); - } - }.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} - */ - this.getAllEditsArray = function (callback) { - - console.assert(this._db !== null, "indexeddb not initialized"); - var editsArray = []; - - 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) { - editsArray.push(cursor.value); - - } - cursor.continue(); - } - else { - callback(editsArray, "end"); - } - }.bind(this); - transaction.onerror = function (err) { - callback(null, err); - }; - } - else { - callback(null, "no db"); - } - }; - - /** - * Update an edit already exists in the database - * @param operation add, update or delete - * @param layer the URL of the feature layer - * @param graphic esri/graphic. The method will serialize to JSON - * @param callback {true, edit} or {false, error} - */ - this.updateExistingEdit = function (operation, layer, graphic, callback) { - - console.assert(this._db !== null, "indexeddb not initialized"); - - var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); - - //Let's get the entry associated with the graphic - var objectStoreGraphicRequest = objectStore.get(graphic.attributes[this.objectId]); - objectStoreGraphicRequest.onsuccess = function () { - - //Grab the data object returned as a result - // TO-DO Do we keep this?? - objectStoreGraphicRequest.result; - - //Create a new update object - var update = { - id: layer + "/" + graphic.attributes[this.objectId], - operation: operation, - layer: layer, - graphic: graphic.toJson() - }; - - // Insert the update into the database - var updateGraphicRequest = objectStore.put(update); - - updateGraphicRequest.onsuccess = function () { - callback(true); - }; - - updateGraphicRequest.onerror = function (err) { - callback(false, err); - }; - }.bind(this); - }; - - /** - * Delete a pending edit's record from the database. - * IMPORTANT: Be aware of false negatives. See Step 4 in this function. - * - * @param layerUrl - * @param graphic Graphic - * @param callback {boolean, error} - */ - this.delete = function (layerUrl, graphic, callback) { - - // NOTE: the implementation of the IndexedDB spec has a design fault with respect to - // handling deletes. The result of a delete operation is always designated as undefined. - // What this means is that there is no way to tell if an operation was successful or not. - // And, it will always return 'true.' - // - // In order to get around this we have to verify if after the attempted deletion operation - // if the record is or is not in the database. Kinda dumb, but that's how IndexedDB works. - //http://stackoverflow.com/questions/17137879/is-there-a-way-to-get-information-on-deleted-record-when-calling-indexeddbs-obj - - var db = this._db; - var deferred = null; - var self = this; - - var id = layerUrl + "/" + graphic.attributes[this.objectId]; - - require(["dojo/Deferred"], function (Deferred) { - deferred = new Deferred(); - - // Step 1 - lets see if record exits. If it does then return callback. - self.editExists(id).then(function (result) { - - // Step 4 - Then we check to see if the record actually exists or not. - deferred.then(function (result) { - - // IF the delete was successful, then the record should return 'false' because it doesn't exist. - self.editExists(id).then(function (results) { - callback(false); - }, - function (err) { - callback(true); //because we want this test to throw an error. That means item deleted. - }); - }, - // There was a problem with the delete operation on the database - function (err) { - callback(false, err); - }); - - var objectStore = db.transaction([self.objectStoreName], "readwrite").objectStore(self.objectStoreName); - - // Step 2 - go ahead and delete graphic - var objectStoreDeleteRequest = objectStore.delete(id); - - // Step 3 - We know that the onsuccess will always fire unless something serious goes wrong. - // So we go ahead and resolve the deferred here. - objectStoreDeleteRequest.onsuccess = function () { - deferred.resolve(true); - }; - - objectStoreDeleteRequest.onerror = function (msg) { - deferred.reject({success: false, error: msg}); - }; - - }, - // If there is an error in editExists() - function (err) { - callback(false); - }); - }); - }; - - /** - * Full database reset. - * CAUTION! If some edits weren't successfully sent, then their record - * will still exist in the database. If you use this function you - * will also delete those records. - * @param callback boolean - */ - this.resetEditsQueue = function (callback) { - console.assert(this._db !== null, "indexeddb not initialized"); - - var request = this._db.transaction([this.objectStoreName], "readwrite") - .objectStore(this.objectStoreName) - .clear(); - request.onsuccess = function (event) { - setTimeout(function () { - callback(true); - }, 0); - }; - request.onerror = function (err) { - callback(false, err); - }; - }; - - this.pendingEditsCount = function (callback) { - console.assert(this._db !== null, "indexeddb not initialized"); - - var count = 0; - var id = this.FEATURE_LAYER_JSON_ID; - var fCollectionId = this.FEATURE_COLLECTION_ID; - var phantomGraphicPrefix = this.PHANTOM_GRAPHIC_PREFIX; - - var transaction = this._db.transaction([this.objectStoreName], "readwrite"); - var objectStore = transaction.objectStore(this.objectStoreName); - objectStore.openCursor().onsuccess = function (evt) { - var cursor = evt.target.result; - - // IMPORTANT: - // Remember that we have feature layer JSON and Phantom Graphics in the same database - if (cursor && cursor.value && cursor.value.id && cursor.value.id.indexOf(phantomGraphicPrefix) == -1) { - if (cursor.value.id !== id && cursor.value.id !== fCollectionId) { - count++; - } - cursor.continue(); - } - else { - callback(count); - } - }; - }; - - /** - * Verify is an edit already exists in the database. Checks the objectId. - * @param id - * @returns {deferred} {success: boolean, error: message} - * @private - */ - this.editExists = function (id) { - - var db = this._db; - var deferred = null; - var self = this; - - require(["dojo/Deferred"], function (Deferred) { - deferred = new Deferred(); - - var objectStore = db.transaction([self.objectStoreName], "readwrite").objectStore(self.objectStoreName); - - //Get the entry associated with the graphic - var objectStoreGraphicRequest = objectStore.get(id); - - objectStoreGraphicRequest.onsuccess = function () { - var graphic = objectStoreGraphicRequest.result; - if (graphic && (graphic.id == id)) { - deferred.resolve({success: true, error: null}); - } - else { - deferred.reject({success: false, error: "objectId is not a match."}); - } - }; - - objectStoreGraphicRequest.onerror = function (msg) { - deferred.reject({success: false, error: msg}); - }; - }); - - //We return a deferred object so that when calling this function you can chain it with a then() statement. - return deferred; - }; - - /** - * Returns the approximate size of the database in bytes - * IMPORTANT: Currently requires all data be serialized! - * @param callback callback({usage}, error) Whereas, the usage Object is {sizeBytes: number, editCount: number} - */ - this.getUsage = function (callback) { - console.assert(this._db !== null, "indexeddb not initialized"); - - var id = this.FEATURE_LAYER_JSON_ID; - var fCollectionId = this.FEATURE_COLLECTION_ID; - var phantomGraphicPrefix = this.PHANTOM_GRAPHIC_PREFIX; - - var usage = {sizeBytes: 0, editCount: 0}; - - var transaction = this._db.transaction([this.objectStoreName]) - .objectStore(this.objectStoreName) - .openCursor(); - - console.log("dumping keys"); - - transaction.onsuccess = function (event) { - var cursor = event.target.result; - if (cursor && cursor.value && cursor.value.id) { - var storedObject = cursor.value; - var json = JSON.stringify(storedObject); - usage.sizeBytes += json.length; - - if (cursor.value.id.indexOf(phantomGraphicPrefix) == -1 && cursor.value.id !== id && cursor.value.id !== fCollectionId) { - usage.editCount += 1; - } - - cursor.continue(); - } - else { - callback(usage, null); - } - }; - transaction.onerror = function (err) { - callback(null, err); - }; - }; - - // - // internal methods - // - - /** - * The library automatically keeps a copy of the featureLayerCollection and its - * associated layer.url. - * - * There should be only one featureLayerCollection Object per feature layer. - * @param featureCollectionObject - * @param callback - * @private - */ - this._pushFeatureCollections = function(featureCollectionObject, callback){ - var transaction = this._db.transaction([this.objectStoreName], "readwrite"); - - transaction.oncomplete = function (event) { - callback(true); - }; - - transaction.onerror = function (event) { - callback(false, event.target.error.message); - }; - - var objectStore = transaction.objectStore(this.objectStoreName); - objectStore.put(featureCollectionObject); - }; - - this._getFeatureCollections = function(callback){ - var objectStore = this._db.transaction([this.objectStoreName], "readonly").objectStore(this.objectStoreName); - - //Get the entry associated with the graphic - var objectStoreGraphicRequest = objectStore.get(this.FEATURE_COLLECTION_ID); - - objectStoreGraphicRequest.onsuccess = function () { - var object = objectStoreGraphicRequest.result; - if (typeof object != "undefined") { - callback(true, object); - } - else { - callback(false, null); - } - }; - - objectStoreGraphicRequest.onerror = function (msg) { - callback(false, msg); - }; - }; - - /** - * Save space in the database...don't need to store the entire Graphic object just its public properties! - * @param graphic - * @returns {*} - * @private - */ - this._serialize = function (graphic) { - // see http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#/Apply_Edits_Feature_Service_Layer/02r3000000r6000000/ - // use graphic's built-in serializing method - var json = graphic.toJson(); - var jsonClean = - { - attributes: json.attributes, - geometry: json.geometry, - infoTemplate: json.infoTemplate, - symbol: json.symbol - }; - return JSON.stringify(jsonClean); - }; - - this._deserialize = function (json) { - var graphic; - - require(["esri/graphic"], function (Graphic) { - graphic = new Graphic(JSON.parse(json)); - }); - - return graphic; - }; - - this.init = function (callback) { - console.log("init editsStore.js"); - - var request = indexedDB.open(this.dbName, 11); - callback = callback || function (success) { - console.log("EditsStore::init() success:", success); - }.bind(this); - - request.onerror = function (event) { - console.log("indexedDB error: " + event.target.errorCode); - callback(false, event.target.errorCode); - }.bind(this); - - request.onupgradeneeded = function (event) { - var db = event.target.result; - - if (db.objectStoreNames.contains(this.objectStoreName)) { - db.deleteObjectStore(this.objectStoreName); - } - - db.createObjectStore(this.objectStoreName, {keyPath: "id"}); - }.bind(this); - - request.onsuccess = function (event) { - this._db = event.target.result; - this._isDBInit = true; - console.log("database opened successfully"); - callback(true, null); - }.bind(this); - }; -}; - - - -/*global IDBKeyRange,indexedDB */ - -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 () { - if (!window.indexedDB) { - return false; - } - return true; - }; - - /** - * Stores an attachment in the database. - * In theory, this abides by the query-attachment-infos-complete Object which can be found here: - * https://developers.arcgis.com/javascript/jsapi/featurelayer-amd.html#event-query-attachment-infos-complete - * @param featureLayerUrl - * @param attachmentId The temporary or actual attachmentId issued by the feature service - * @param objectId The actual ObjectId issues by the feature service - * @param attachmentFile - * @param type Type of operation: "add", "update" or "delete" - * @param callback - */ - this.store = function (featureLayerUrl, attachmentId, objectId, attachmentFile, type, callback) { - try { - // Avoid allowing the wrong type to be stored - if(type == this.TYPE.ADD || type == this.TYPE.UPDATE || type == this.TYPE.DELETE) { - - // first of all, read file content - this._readFile(attachmentFile, function (success, fileContent) { - - if (success) { - // now, store it in the db - var newAttachment = - { - id: attachmentId, - objectId: objectId, - type: type, - - // Unique ID - don't use the ObjectId - // multiple features services could have an a feature with the same ObjectId - featureId: featureLayerUrl + "/" + objectId, - contentType: attachmentFile.type, - name: attachmentFile.name, - size: attachmentFile.size, - featureLayerUrl: featureLayerUrl, - content: fileContent, - file: attachmentFile - }; - - var transaction = this._db.transaction([this.objectStoreName], "readwrite"); - - transaction.oncomplete = function (event) { - callback(true, newAttachment); - }; - - transaction.onerror = function (event) { - callback(false, event.target.error.message); - }; - - try { - transaction.objectStore(this.objectStoreName).put(newAttachment); - } - catch(err) { - callback(false, err); - } - } - else { - callback(false, fileContent); - } - }.bind(this)); - } - else{ - console.error("attachmentsStore.store() Invalid type in the constructor!"); - callback(false,"attachmentsStore.store() Invalid type in the constructor!"); - } - } - catch (err) { - console.log("AttachmentsStore: " + err.stack); - callback(false, err.stack); - } - }; - - this.retrieve = function (attachmentId, callback) { - console.assert(this._db !== null, "indexeddb not initialized"); - - var objectStore = this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName); - var request = objectStore.get(attachmentId); - request.onsuccess = function (event) { - var result = event.target.result; - if (!result) { - callback(false, "not found"); - } - else { - callback(true, result); - } - }; - request.onerror = function (err) { - console.log(err); - callback(false, err); - }; - }; - - this.getAttachmentsByFeatureId = function (featureLayerUrl, objectId, callback) { - console.assert(this._db !== null, "indexeddb not initialized"); - - var featureId = featureLayerUrl + "/" + objectId; - var attachments = []; - - var objectStore = this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName); - var index = objectStore.index("featureId"); - var keyRange = IDBKeyRange.only(featureId); - index.openCursor(keyRange).onsuccess = function (evt) { - var cursor = evt.target.result; - if (cursor) { - attachments.push(cursor.value); - cursor.continue(); - } - else { - callback(attachments); - } - }; - }; - - this.getAttachmentsByFeatureLayer = function (featureLayerUrl, callback) { - console.assert(this._db !== null, "indexeddb not initialized"); - - var attachments = []; - - var objectStore = this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName); - var index = objectStore.index("featureLayerUrl"); - var keyRange = IDBKeyRange.only(featureLayerUrl); - index.openCursor(keyRange).onsuccess = function (evt) { - var cursor = evt.target.result; - if (cursor) { - attachments.push(cursor.value); - cursor.continue(); - } - else { - callback(attachments); - } - }; - }; - - this.getAllAttachments = function (callback) { - console.assert(this._db !== null, "indexeddb not initialized"); - - var attachments = []; - - var objectStore = this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName); - objectStore.openCursor().onsuccess = function (evt) { - var cursor = evt.target.result; - if (cursor) { - attachments.push(cursor.value); - cursor.continue(); - } - else { - callback(attachments); - } - }; - }; - - this.deleteAttachmentsByFeatureId = function (featureLayerUrl, objectId, callback) { - console.assert(this._db !== null, "indexeddb not initialized"); - - var featureId = featureLayerUrl + "/" + objectId; - - var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); - var index = objectStore.index("featureId"); - var keyRange = IDBKeyRange.only(featureId); - var deletedCount = 0; - index.openCursor(keyRange).onsuccess = function (evt) { - var cursor = evt.target.result; - if (cursor) { - //var attachment = cursor.value; - //this._revokeLocalURL(attachment); - objectStore.delete(cursor.primaryKey); - deletedCount++; - cursor.continue(); - } - else { - setTimeout(function () { - callback(deletedCount); - }, 0); - } - }.bind(this); - }; - - this.delete = function (attachmentId, callback) { - console.assert(this._db !== null, "indexeddb not initialized"); - - // before deleting an attachment we must revoke the blob URL that it contains - // in order to free memory in the browser - this.retrieve(attachmentId, function (success, attachment) { - if (!success) { - callback(false, "attachment " + attachmentId + " not found"); - return; - } - - //this._revokeLocalURL(attachment); - - var request = this._db.transaction([this.objectStoreName], "readwrite") - .objectStore(this.objectStoreName) - .delete(attachmentId); - request.onsuccess = function (event) { - setTimeout(function () { - callback(true); - }, 0); - }; - request.onerror = function (err) { - callback(false, err); - }; - }.bind(this)); - }; - - this.deleteAll = function (callback) { - console.assert(this._db !== null, "indexeddb not initialized"); - - this.getAllAttachments(function (attachments) { - //attachments.forEach(function (attachment) { - // this._revokeLocalURL(attachment); - //}, this); - - var request = this._db.transaction([this.objectStoreName], "readwrite") - .objectStore(this.objectStoreName) - .clear(); - request.onsuccess = function (event) { - setTimeout(function () { - callback(true); - }, 0); - }; - request.onerror = function (err) { - callback(false, err); - }; - }.bind(this)); - }; - - this.replaceFeatureId = function (featureLayerUrl, oldId, newId, callback) { - console.assert(this._db !== null, "indexeddb not initialized"); - - var featureId = featureLayerUrl + "/" + oldId; - - var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); - var index = objectStore.index("featureId"); - var keyRange = IDBKeyRange.only(featureId); - var replacedCount = 0; - index.openCursor(keyRange).onsuccess = function (evt) { - var cursor = evt.target.result; - if (cursor) { - var newFeatureId = featureLayerUrl + "/" + newId; - var updated = cursor.value; - updated.featureId = newFeatureId; - updated.objectId = newId; - objectStore.put(updated); - replacedCount++; - cursor.continue(); - } - else { - // If no records match then evt.target.result = null - // allow time for all changes to persist... - setTimeout(function () { - callback(replacedCount); - }, 1); - } - }; - }; - - this.getUsage = function (callback) { - console.assert(this._db !== null, "indexeddb not initialized"); - - var usage = {sizeBytes: 0, attachmentCount: 0}; - - var transaction = this._db.transaction([this.objectStoreName]) - .objectStore(this.objectStoreName) - .openCursor(); - - console.log("dumping keys"); - - transaction.onsuccess = function (event) { - var cursor = event.target.result; - if (cursor) { - console.log(cursor.value.id, cursor.value.featureId, cursor.value.objectId); - var storedObject = cursor.value; - var json = JSON.stringify(storedObject); - usage.sizeBytes += json.length; - usage.attachmentCount += 1; - cursor.continue(); - } - else { - callback(usage, null); - } - }.bind(this); - transaction.onerror = function (err) { - callback(null, err); - }; - }; - - /** - * Full attachments database reset. - * CAUTION! If some attachments weren't successfully sent, then their record - * will still exist in the database. If you use this function you - * will also delete those records. - * @param callback boolean - */ - this.resetAttachmentsQueue = function (callback) { - console.assert(this._db !== null, "indexeddb not initialized"); - - var request = this._db.transaction([this.objectStoreName], "readwrite") - .objectStore(this.objectStoreName) - .clear(); - request.onsuccess = function (event) { - setTimeout(function () { - callback(true); - }, 0); - }; - request.onerror = function (err) { - callback(false, err); - }; - }; - - // internal methods - - this._readFile = function (attachmentFile, callback) { - var reader = new FileReader(); - reader.onload = function (evt) { - callback(true,evt.target.result); - }; - reader.onerror = function (evt) { - callback(false,evt.target.result); - }; - reader.readAsBinaryString(attachmentFile); - }; - - // Deprecated @ v2.7 - //this._createLocalURL = function (attachmentFile) { - // return window.URL.createObjectURL(attachmentFile); - //}; - - //this._revokeLocalURL = function (attachment) { - // window.URL.revokeObjectURL(attachment.url); - //}; - - this.init = function (callback) { - console.log("init AttachmentStore"); - - var request = indexedDB.open(this.dbName, 12); - callback = callback || function (success) { - console.log("AttachmentsStore::init() success:", success); - }.bind(this); - - request.onerror = function (event) { - console.log("indexedDB error: " + event.target.errorCode); - callback(false, event.target.errorCode); - }.bind(this); - - request.onupgradeneeded = function (event) { - var db = event.target.result; - - if (db.objectStoreNames.contains(this.objectStoreName)) { - db.deleteObjectStore(this.objectStoreName); - } - - var objectStore = db.createObjectStore(this.objectStoreName, {keyPath: "id"}); - objectStore.createIndex("featureId", "featureId", {unique: false}); - objectStore.createIndex("featureLayerUrl", "featureLayerUrl", {unique: false}); - }.bind(this); - - request.onsuccess = function (event) { - this._db = event.target.result; - console.log("database opened successfully"); - callback(true); - }.bind(this); - }; -}; - diff --git a/test/SpecRunner.offlineEditingLight.html b/test/SpecRunner.offlineFeaturesManagerBasic.html similarity index 100% rename from test/SpecRunner.offlineEditingLight.html rename to test/SpecRunner.offlineFeaturesManagerBasic.html From 97d7554d6c70d05d57e69c42f699fa5e89c746a0 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Fri, 20 Nov 2015 14:01:24 -0700 Subject: [PATCH 09/48] renamed and updated edit advanced doc --- ...r.md => offlinefeaturesmanageradvanced.md} | 99 +++---------------- 1 file changed, 13 insertions(+), 86 deletions(-) rename doc/{offlinefeaturesmanager.md => offlinefeaturesmanageradvanced.md} (60%) diff --git a/doc/offlinefeaturesmanager.md b/doc/offlinefeaturesmanageradvanced.md similarity index 60% rename from doc/offlinefeaturesmanager.md rename to doc/offlinefeaturesmanageradvanced.md index e61d97a5..7066b84a 100644 --- a/doc/offlinefeaturesmanager.md +++ b/doc/offlinefeaturesmanageradvanced.md @@ -1,19 +1,19 @@ -API OfflineFeaturesManager -========================== +API OfflineFeaturesManagerAdvanced +================================== -##O.esri.Edit.OfflineFeaturesManager -The `offline-edit-min.js` library provides the following tools for working with esri.layers.FeatureLayer objects while partially or fully offline. +##O.esri.Edit.OfflineFeaturesManagerAdvanced +The `offline-edit-advanced-min.js` library provides the following tools for working with esri.layers.FeatureLayer objects while intermittently and fully offline. ###Constructor Constructor | Description --- | --- -`O.esri.Edit.OfflineFeaturesManager()` | Creates an instance of the OfflineFeaturesManager class. This library allows you to extend FeatureLayer objects with offline editing capabilities and manage the online/offline resynchronization process. +`O.esri.Edit.OfflineFeaturesManagerAdvanced()` | Creates an instance of the OfflineFeaturesManagerAdvanced class. This library allows you to extend FeatureLayer objects with offline editing capabilities and manage the online/offline resynchronization process. ###Properties Property | Value | Description --- | --- | --- -`DB_NAME` | "features_store" | Sets the database name. You can instantiate multiple databases within the same application by creating seperate instances of OfflineFeaturesManager. +`DB_NAME` | "features_store" | Sets the database name. You can instantiate multiple databases within the same application by creating seperate instances of OfflineFeaturesManagerAdvanced. `DB_OBJECTSTORE_NAME` | "features" | Represents an object store that allows access to a set of data in the database. `DB_UID` | "objectid" | IMPORTANT!** This tells the database what id to use as a unique identifier. This depends on how your feature service was created. ArcGIS Online services may use something different such as `GlobalID`. `ATTACHMENTS_DB_NAME` | "attachments_store" | (Added @ v2.7) Sets the attachments database name. @@ -33,7 +33,7 @@ Property | Value | Description ###Methods -OfflineFeaturesManager provides the following functionality. +OfflineFeaturesManagerAdvanced provides the following functionality. **IMPORTANT:** The library currently only works offline when the feature layer's `mode` is set to `FeatureLayer.MODE_SNAPSHOT`. @@ -44,17 +44,17 @@ Methods | Returns | Description `goOnline(callback)` | No attachments: `callback( {success: boolean, responses: Object } )`

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

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

This method should be used when working with pre-built Esri widgets such as the `AttributeInspector.` -`getFeatureLayerJSONDataStore( callback )` | `callback( boolean, Object)` | (Added @ v2.7.1) Returns the feature layer's dataStore Object that was created using the `offlineFeaturesManager()` constructor. Offers more control what is provided by `getFeatureCollections()`. +`getFeatureLayerJSONDataStore( callback )` | `callback( boolean, Object)` | (Added @ v2.7.1) Returns the feature layer's dataStore Object that was created using the `offlineFeaturesManagerAdvanced()` constructor. Offers more control what is provided by `getFeatureCollections()`. `getReadableEdit()` | String | **DEPRECATED** @ v2.5. A string value representing human readable information on pending edits. Use `featureLayer.getAllEditsArray()`. ###Events -Application code can subscribe to offlineFeaturesManager events to be notified of different conditions. +Application code can subscribe to offlineFeaturesManagerAdvanced events to be notified of different conditions. ```js - offlineFeaturesManager.on( - offlineFeaturesManager.events.ALL_EDITS_SENT, + offlineFeaturesManagerAdvanced.on( + offlineFeaturesManagerAdvanced.events.ALL_EDITS_SENT, function(edits) { ... @@ -73,7 +73,7 @@ Event | Value | Returns | Description ###FeatureLayer -A FeatureLayer that has been extended using OfflineFeaturesManager.extend() will gain access to the following additional functionality. Example usage: +A FeatureLayer that has been extended using OfflineFeaturesManagerAdvanced.extend() will gain access to the following additional functionality. Example usage: ```js @@ -108,77 +108,4 @@ Methods | Returns | Description `getFeatureLayerJSON(url,callback)` | `callback( boolean, JSON )` | Helper function that retrieves the feature layer's JSON using `f=json` parameter. `setFeatureLayerJSONDataStore( jsonObject, callback)` | `callback( boolean, error)` | Sets the optional feature layer storage object. Can be used instead of the `OfflineFeatureManager` constructor's `dataStore` property or to update it. `jsonObject` can be any Object. However, they key name `id` is reserved. This data store object is used for full offline browser restarts. `getFeatureLayerJSONDataStore(callback)` | `callback( true, object )` or `callback( false, errorString)` | Retrieves the optional feature layer storage object. This data store object is used for full offline browser restarts. -`convertFeatureGraphicsToJSON(` `[features],callback)` | `callback( jsonString )` | Helper function that converts an array of feature layer graphics to a JSON string. - -##O.esri.Edit.EditStore - -Provides a number of public methods that are used by `OfflineFeaturesManager` library for storing edits in the browser. Instiantiate this library using a `new` statement. - -__NOTE:__ Use with caution as most of the methods are RESERVED for internal library use-only. All common use functions should be accessed directly through the feature layer after it has been extended by the offlineFeaturesManager. - -###Constructor -Constructor | Description ---- | --- -`O.esri.Edit.EditStore()` | Creates an instance of the EditStore class. This library is responsible for managing the storage, reading, writing, serialization, deserialization of geometric features. - -###ENUMs - -Property | Value | Description ---- | --- | --- -`ADD` | "add" | Represents a FeatureLayer.add() operation. -`UPDATE` | "update" | Represents a FeatureLayer.update() operation. -`DELETE` | "delete" | Represents a FeatureLayer.delete() operation. - -###Public Properties - -Property | Value | Description ---- | --- | --- -`dbName` | "features_store" | Defines the database name. You can have multiple databases within the same application. -`objectStoreName` | "features" | Represents an object store that allows access to a set of data in the IndexedDB database, looked up via primary key. - -###Public Methods -Methods | Returns | Description ---- | --- | --- -`isSupported()` | boolean | Determines if local storage is available. If it is not available then the storage cache will not work. It's a best practice to verify this before attempting to write to the local cache. -`pushEdit(` `operation, layer, graphic, callback)` | `callback(` `true, edit)` or `callback(` `false, message)`| Pushes an edit into storage. Operation is the corresponding enum. Layer is a reference to the feature layer, and the graphic is the graphic object associated with the edit. -`resetEditsQueue(callback)` | `callback( boolean, error)` | Use with **caution**, initiates a complete database reset. If some edits weren't sent when your app goes online, then you will delete those records as well. -`pendingEditsCount( callback )` | `callback( int )` | The total number of edits that are queued in the database. -`getAllEditsArray( callback)` | `callback()` | Returns all edits in an iterable array. -`getFeatureLayerJSON( callback)` | `callback( boolean, Object)` | Returns the feature layer JSON object. -`deleteFeatureLayerJSON( callback)` | `callback( boolean, {message:String)` | Delete the feature layer JSON object from the database. -`pushFeatureLayerJSON( dataObject, callback)` | `callback( boolean, error)` | Use this to store any static FeatureLayer or related JSON data related to your app that will assist in restoring the layer after an offline restart. Supports adds and updates, will not overwrite entire object. -`getUsage( callback)` | `callback( int, errorString)` | Returns the approximate size of the database in bytes. -`hasPendingEdits()` | boolean | Determines if there are any queued edits in the local cache. Use `pendingEditsCount()` instead. -`retrieveEditsQueue()` | Array | Returns an array of all pending edits. -`getEditsStoreSizeBytes()` | Number | Returns the total size of all pending edits in bytes. Use `getUsage()` instead. -`getLocalStorageSizeBytes()` | Number | Returns the total size in bytes of all items for local storage cached using the current domain name. Use `getUsage()` instead. - -##O.esri.Edit.AttachmentsStore - -Provides a number of public methods that are used by `OfflineFeaturesManager` library for storing attachments in the browser. Instiantiate this library using a `new` statement. Instiantiate this library using a `new` statement. In general, you shouldn't be adding, updating or deleting data directly. You should be using functionality extended thru the feature layer. - -###Constructor -Constructor | Description ---- | --- -`O.esri.Edit.AttachmentsStore()` | Creates an instance of the AttachmentsStore class. This library is responsible for managing the storage of attachments. - -###Properties - -Property | Value | Description ---- | --- | --- -`dbName` | "attachments_store" | Represents a FeatureLayer.add() operation. -`objectStoreName` | "attachments" | Represents a FeatureLayer.update() operation. -`TYPE` | "ADD", "UPDATE" or "DELETE" | (Added @ v2.7) Specifies the type of operation against an attachment. - -###Public Methods -Methods | Returns | Description ---- | --- | --- -`store(` `featureLayerUrl, attachmentId,` `objectId, attachmentFile, callback)` | `callback(` `true, edit)` or `callback(` `false, message)` | Stores attachment. AttachmentId is temporary. For more information on `objectId` see the [FeatureLayer.addAttachments()](https://developers.arcgis.com/javascript/jsapi/featurelayer-amd.html#addattachment) doc. -`retrieve(attachmentId, callback)` | `callback(` `true, result)` or `callback(` `false, message)` | Retrieves an attachment by its unique `attachmentId`. -`getAttachmentsByFeatureId(featureLayerUrl,` `objectId, callback)` | `callback([attachments])` | Retrieves all attachments having the unique `objectId`. For more information on `objectId` see the [FeatureLayer.addAttachments()](https://developers.arcgis.com/javascript/jsapi/featurelayer-amd.html#addattachment) doc -`getAttachmentsByFeatureLayer(featureLayerUrl, callback)` | `callback([attachments])` | Retrieves all attachments in the specified feature layer. -`getAllAttachments(callback)` | `callback([attachments])` | Retrieves all attachments in the database. -`deleteAttachmentsByFeatureId(featureLayerUrl,` `objectId, callback)` | `callback(int)` | Deletes all attachments having the unique `objectId`. Callback provides the number of attachments deleted. -`deleteAll(callback)` | `callback(` `true)` or `callback(` `false, message)` | Deletes all attachments in the database. -`replaceFeatureId(featureLayerUrl,` `oldId, newId, callback)` | `callback(int)` | Gives an attachment a new objectId. Returns the number of attachments that were updated. -`getUsage(callback)` | `callback({sizeBytes: int,` `attachmentCount: int})` | Returns an approximation of how much data is stored in the database. \ No newline at end of file +`convertFeatureGraphicsToJSON(` `[features],callback)` | `callback( jsonString )` | Helper function that converts an array of feature layer graphics to a JSON string. \ No newline at end of file From ce741565f832b1d90c02c2e83198bd8fbc1812f5 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Fri, 20 Nov 2015 17:01:31 -0700 Subject: [PATCH 10/48] 1st pass at updating api and how to use docs --- ...brary.md => howtouseofmadvancedlibrary.md} | 48 ++--- doc/howtouseofmbasic.md | 204 ++++++++++++++++++ doc/offlinefeaturesmanageradvanced.md | 11 +- doc/offlinefeaturesmanagerbasic.md | 84 ++++++++ 4 files changed, 317 insertions(+), 30 deletions(-) rename doc/{howtouseeditlibrary.md => howtouseofmadvancedlibrary.md} (87%) create mode 100644 doc/howtouseofmbasic.md create mode 100644 doc/offlinefeaturesmanagerbasic.md diff --git a/doc/howtouseeditlibrary.md b/doc/howtouseofmadvancedlibrary.md similarity index 87% rename from doc/howtouseeditlibrary.md rename to doc/howtouseofmadvancedlibrary.md index fcef3df9..b233a33f 100644 --- a/doc/howtouseeditlibrary.md +++ b/doc/howtouseofmadvancedlibrary.md @@ -1,15 +1,15 @@ -How to use the edit library -=========================== +How to use the advanced edit library +==================================== -##`edit` library +##`OfflineFeaturesManagerAdvanced` library -The `edit` library allows a developer to extend a feature layer with offline editing support. You can combine this functionality with offline tiles. For a complete list of features consult the [OfflineFeaturesManager API doc](offlinefeaturesmanager.md). +This library allows a developer to extend a feature layer with intermittent and full offline editing support. You can combine this functionality with offline tiles. For a complete list of features consult the [OfflineFeaturesManagerAdvanced API doc](offlinefeaturesmanageradvanced.md). -**IMPORTANT:** Only use a single instance of OfflineFeaturesManager per application. With this single instance you can extend offline capabilities to multiple feature layers. This single instance contains all edits for all feature layers initialized via `offlineFeaturesManager.extend().` Multiple feature layers share a single database. The database maintains the relationship between each edit and its' respective feature layer via a UUID. +**IMPORTANT:** Only use a single instance of OfflineFeaturesManagerAdvanced per application. With this single instance you can extend offline capabilities to multiple feature layers. This single instance contains all edits for all feature layers initialized via `offlineFeaturesManagerAdvanced.extend().` Multiple feature layers share a single database. The database maintains the relationship between each edit and its' respective feature layer via a UUID. -**Step 1** Include `offline.min.js`, `offline-tiles-basic-min.js` and `offline-edit-min.js` in your app's require contstructor. Be sure to include `ofline.mins.js` which is a 3rd party library for detecting if the browser is online or offline. +**Step 1** Include `offline.min.js`, `offline-tiles-basic-min.js` and `offline-edit-advanced-min.js` in your app's require contstructor. Be sure to include `ofline.mins.js` which is a 3rd party library for detecting if the browser is online or offline. -The pattern for how we include the tiles and edit library within the `require` statement is called generic script injection. Note that we do assign any of the editing or tile libraries an alias name. For example, we specified the mobile path "esri/map" and we gave it an alias called "Map." But, we did not do the equivalent for `offline-tiles-based-min.js` or `offline-edit-min.js`. +The pattern for how we include the tiles and edit library within the `require` statement is called generic script injection. Note that we do assign any of the editing or tile libraries an alias name. For example, we specified the mobile path "esri/map" and we gave it an alias called "Map." But, we did not do the equivalent for `offline-tiles-basic-min.js` or `offline-edit-advanced-min.js`. ```html @@ -17,7 +17,7 @@ The pattern for how we include the tiles and edit library within the `require` s require([ "esri/map", "..dist/offline-tiles-basic-min.js", - "..dist/offline-edit-min.js", + "..dist/offline-edit-advanced-min.js", function(Map) { ... @@ -28,17 +28,17 @@ You can also refer to the offline-editor-js within a `define` statement using th ```js - define(["..dist/offline-edit-min"],function(){ + define(["..dist/offline-edit-advanced-min"],function(){ ... }) ``` -**Step 2** Once your map is created (either using new Map() or using esriUtils.createMap(webmapid,...), you create a new OfflineFeaturesManager instance and starting assigning events listeners to tie the library into your user interface: +**Step 2** Once your map is created (either using new Map() or using esriUtils.createMap(webmapid,...), you create a new OfflineFeaturesManagerAdvanced instance and starting assigning events listeners to tie the library into your user interface: ```js - var offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManager(); + var offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManagerAdvanced(); // OPTIONAL - you can change the name of the database // offlineFeaturesManager.DBNAME = "FIELD_SURVEY3"; // OPTIONAL - you can change the name of the unique identifier used by the feature service. Default is "objectid". @@ -291,9 +291,9 @@ There are two ways to get the dataStore. You can get it from the instance of Off * `featureLayer.getFeatureLayerJSONDataStore(callback)` -**Step 5** Once a layer has been extended the offline library will enable it with new methods. Here are a few examples that include code snippets of how to take advantage of some of the library's methods. You can also use a combination of methods from `editsStore` and `offlineFeaturesManager`. +**Step 5** Once a layer has been extended the offline library will enable it with new methods. Here are a few examples that include code snippets of how to take advantage of some of the library's methods. -####offlineFeaturesManager.proxyPath +####OfflineFeaturesManagerAdvanced.proxyPath By default, the library assumes you are using a CORS-enabled Feature Service. All ArcGIS Online Feature Services are CORS-enabled. If you are hosting your own service and it is not CORS-enabled, then you will need to set this path. More information on downloading and using ArcGIS proxies can be found here: [https://developers.arcgis.com/en/javascript/jshelp/ags_proxy.html](https://developers.arcgis.com/en/javascript/jshelp/ags_proxy.html) Here's one example: @@ -304,18 +304,18 @@ Here's one example: ``` -####offlineFeaturesManager.goOffline() +####OfflineFeaturesManagerAdvanced.goOffline() Force the library to go offline. Once this condition is set, then any offline edits will be cached locally. ```js function goOffline() { - offlineFeaturesManager.goOffline(); - //TO-DO + offlineFeaturesManager.goOffline() }); + //TO-DO } ``` -####offlineFeaturesManager.goOnline() +####OfflineFeaturesManagerAdvanced.goOnline() Force the library to return to an online condition. If there are pending edits, the library will attempt to sync them. ```js @@ -352,7 +352,7 @@ Typically you should only need to call this method once for each online/offline If there was a an failure and/or errors, it's a good idea to reevaluate the edits that remain in the database because some edits may have been synced and others may still be pending. Only then, and depending on the error message, should the app try to `goOnline()` again. -####offlineFeaturesManager.getOnlineStatus() +####OfflineFeaturesManagerAdvanced.getOnlineStatus() Within your application you can manually check online status and then update your user interface. By using a switch/case statement you can check against three enums that indicate if the library thinks it is offline, online or in the process of reconnecting. ```js @@ -375,18 +375,18 @@ Within your application you can manually check online status and then update you ``` -####featureLayer.pendingEditsCount(callback) -You can check if there are any edits pending. If there are edits then you can iterate `editsStore.retrieveEditsQueue()` and convert the edits to a readable format via `offlineFeaturesManager.getReadableEdit(edit)`. +####OfflineFeaturesManagerAdvanced.pendingEditsCount(callback) +You can check if there are any edits pending. ```js // Simply get a count - featureLayer.pendingEditsCount(function(count){ + offlineFeaturesManager.pendingEditsCount(function(count){ console.log("There are " + count + " edits pending"); }) // Or retrieve all pending edits - featureLayer.getAllEditsArray(function(success,editsArray){ + offlineFeaturesManager.getAllEditsArray(function(success,editsArray){ if(success && editsArray.length > 0){ editsArray.forEach(function(edit){ console.log("Pending edit: " + JSON.stringify(edit)); @@ -398,7 +398,7 @@ You can check if there are any edits pending. If there are edits then you can it ### How to empty the edits database during testing? -Some browsers, like Firefox, make it difficult or impossible to delete data that's in an IndexedDB database. And, there may be times during testing were you are stuck with bad or old data in the database and you need to delete it. Fortunately there is a pattern using `editsStore.resetEditsQueue()` for helping you out with this situation. +Some browsers, like Firefox, make it difficult or impossible to delete data that's in an IndexedDB database. And, there may be times during testing were you are stuck with bad or old data in the database and you need to delete it. You can run the reset code seperately or you can run the app with this pattern. If you do use the pattern below be sure to comment out the reset code and then re-run the app. You should be good to go again with a completely empty database. @@ -407,7 +407,7 @@ You can run the reset code seperately or you can run the app with this pattern. offlineFeaturesManager.extend(myFeatureLayer,function(result, error) { if(result) { console.log("offlineFeaturesManager initialized."); - offlineFeaturesManager._editStore.resetEditsQueue(function(success){ + offlineFeaturesManager.resetDatabase(function(success,error){ console.log("DATABASE DELETED"); }); . . . diff --git a/doc/howtouseofmbasic.md b/doc/howtouseofmbasic.md new file mode 100644 index 00000000..0692bc8f --- /dev/null +++ b/doc/howtouseofmbasic.md @@ -0,0 +1,204 @@ +How to use the basic edit library +==================================== + +##`OfflineFeaturesManagerBasic` library + +This library allows a developer to extend a feature layer with intermittent offline editing support. You can combine this functionality with offline tiles. For a complete list of features consult the [OfflineFeaturesManagerBasic API doc](offlinefeaturesmanagerbasic.md). + +**IMPORTANT:** Only use a single instance of OfflineFeaturesManagerBasic per application. With this single instance you can extend offline capabilities to multiple feature layers. This single instance contains all edits for all feature layers initialized via `OfflineFeaturesManagerBasic.extend().` Multiple feature layers share a single database. The database maintains the relationship between each edit and its' respective feature layer via a UUID. + +**Step 1** Include `offline.min.js`, `offline-tiles-basic-min.js` and `offline-edit-min.js` in your app's require contstructor. Be sure to include `ofline.mins.js` which is a 3rd party library for detecting if the browser is online or offline. + +The pattern for how we include the tiles and edit library within the `require` statement is called generic script injection. Note that we do assign any of the editing or tile libraries an alias name. For example, we specified the mobile path "esri/map" and we gave it an alias called "Map." But, we did not do the equivalent for `offline-tiles-basic-min.js` or `offline-edit-basic-min.js`. + +```html + + + diff --git a/test/SpecRunner.editsStore.html b/test/SpecRunner.editsStore.html index 9c092884..61b019d0 100644 --- a/test/SpecRunner.editsStore.html +++ b/test/SpecRunner.editsStore.html @@ -20,6 +20,7 @@ + diff --git a/test/SpecRunner.offlineAttachments.html b/test/SpecRunner.offlineAttachments.html index 03f7c62b..72607033 100644 --- a/test/SpecRunner.offlineAttachments.html +++ b/test/SpecRunner.offlineAttachments.html @@ -22,6 +22,7 @@ + diff --git a/test/SpecRunner.offlineFeaturesManager.TokenBased.html b/test/SpecRunner.offlineFeaturesManager.TokenBased.html index 5d13c61d..898bf086 100644 --- a/test/SpecRunner.offlineFeaturesManager.TokenBased.html +++ b/test/SpecRunner.offlineFeaturesManager.TokenBased.html @@ -22,6 +22,7 @@ + diff --git a/test/SpecRunner.offlineFeaturesManager.oAuth.html b/test/SpecRunner.offlineFeaturesManager.oAuth.html index d2bde803..8c91420c 100644 --- a/test/SpecRunner.offlineFeaturesManager.oAuth.html +++ b/test/SpecRunner.offlineFeaturesManager.oAuth.html @@ -22,6 +22,7 @@ + diff --git a/test/SpecRunner.offlineFeaturesManagerAdvanced.html b/test/SpecRunner.offlineFeaturesManagerAdvanced.html index 619a03f9..38d3a2a8 100644 --- a/test/SpecRunner.offlineFeaturesManagerAdvanced.html +++ b/test/SpecRunner.offlineFeaturesManagerAdvanced.html @@ -21,6 +21,7 @@ + diff --git a/test/SpecRunner.offlineFeaturesManagerBasic.html b/test/SpecRunner.offlineFeaturesManagerBasic.html index 8a349549..11f9be31 100644 --- a/test/SpecRunner.offlineFeaturesManagerBasic.html +++ b/test/SpecRunner.offlineFeaturesManagerBasic.html @@ -25,7 +25,7 @@ - + + - - + + diff --git a/samples/attachments-editor-secure.html b/samples/attachments-editor-secure.html index 2c6f3cd3..38e3d578 100644 --- a/samples/attachments-editor-secure.html +++ b/samples/attachments-editor-secure.html @@ -19,7 +19,7 @@ - + - - - + + + - - + + + - - + + +
@@ -135,10 +124,8 @@ "dojo/_base/array", "dojo/parser", "dojo/keys", "dojo/dom", "dojo/on", "dojo/dom-construct", "dojo/dom-class", "esri/domUtils", - "esri/graphic", - "dijit/layout/BorderContainer", "dijit/layout/ContentPane", - "../dist/offline-edit-src.js", + "../dist/offline-edit-basic-src.js", "dojo/domReady!" ], function( Map, GeometryService, Edit, @@ -148,7 +135,7 @@ esriConfig, jsapiBundle, Deferred, all, arrayUtils, parser, keys, - dom, on, domConstruct, domClass, domUtils,Graphic + dom, on, domConstruct, domClass, domUtils ) { var editor; @@ -161,7 +148,7 @@ on(dom.byId('go-offline-btn'),'click', goOffline); on(dom.byId('go-online-btn'),'click', goOnline); on(dom.byId('refresh-feature-layers-btn'),'click', refreshFeatureLayers); - var offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManager(); + var offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManagerBasic(); // IMPORTANT!!! // A proxy page may be required to upload attachments. @@ -291,8 +278,7 @@ // Things to do when all the promises complete function resolveOfflinePromises(){ offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_ENQUEUED, updateStatus); - offlineFeaturesManager.on(offlineFeaturesManager.events.ALL_EDITS_SENT, updateStatusAllEditsSent); - offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_SENT_ERROR, handleEditsSentError); + offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_SENT, updateStatusAllEditsSent); offlineInitializedLayers[0].on("edits-complete", handleEditsComplete); offlineInitializedLayers[1].on("edits-complete", handleEditsComplete); diff --git a/samples/package.json b/samples/package.json index 1d1be34d..a120c79c 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": "2.16.0", + "version": "3.0.0", "private": true, "description": "manifest generator project", "repository": { @@ -24,6 +24,6 @@ "dependencies": {}, "devDependencies": { "grunt": "~0.4.5", - "grunt-manifest": "^0.4.0" + "grunt-manifest": "^0.4.4" } } diff --git a/samples/simple-edit.html b/samples/simple-edit.html index 983155b2..786a82d7 100644 --- a/samples/simple-edit.html +++ b/samples/simple-edit.html @@ -48,7 +48,7 @@ "dijit/layout/BorderContainer", "dijit/layout/ContentPane", - "../dist/offline-editlight-src.js", + "../dist/offline-edit-basic-src.js", "dojo/domReady!" ], function( Map, Edit, FeatureLayer, Query, esriConfig, @@ -59,7 +59,7 @@ // refer to "Using the Proxy Page" for more information: https://developers.arcgis.com/javascript/jshelp/ags_proxy.html esriConfig.defaults.io.proxyUrl = "/proxy/"; - var offlineFeatureLayer = new O.esri.Edit.OfflineFeatureLayer(); + var offlineFeatureLayer = new O.esri.Edit.OfflineFeaturesManagerBasic(); map = new Map("map", { diff --git a/samples/tiles-indexed-db.html b/samples/tiles-indexed-db.html index e9feefe5..53c7bcdc 100644 --- a/samples/tiles-indexed-db.html +++ b/samples/tiles-indexed-db.html @@ -7,12 +7,12 @@ - + - + - + - - - - - - - - - -
-
-

Getting Started with ArcGIS.com

-

Basic steps for working with ArcGIS.com base maps for offline.

- -
-
- -
- -
-
- - - -
-
-

Step 1: Fill in the basics

-

Add the basic library references. Then test to make sure map loads.

-
-
-                        
-<!DOCTYPE html>
-<html>
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
-    <title>Offline ArcGIS.com</title>
-
-    <!-- Bootstrap core CSS -->
-    <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
-
-    <link rel="stylesheet" href="http://js.arcgis.com/3.11/esri/css/esri.css"">
-    <link rel="stylesheet" type="text/css" href="http://esri.github.io/bootstrap-map-js/src/css/bootstrapmap.css">
-    <style>
-        #mapDiv {
-            min-height: 500px;
-            max-height: 1000px;
-        }
-        body {
-            background-color: #ffffff;
-            overflow: hidden;
-            font-family: "Trebuchet MS";
-        }
-        .floatRight {float: right;}
-        .container { padding: 20px;}
-    </style>
-
-    <!-- Include a reference to offline.js which detects online/offline conditions -->
-    <script src="../vendor/offline/offline.min.js"></script>
-    <script>
-        // Set the online/offline detection options.
-        // More info at: http://github.hubspot.com/offline/docs/welcome/
-        Offline.options = {
-            checks: {
-                image: {
-                    url: function() {
-                        return 'http://esri.github.io/offline-editor-js/tiny-image.png?_=' +
-                                (Math.floor(Math.random() * 1000000000));
-                    }
-                },
-                active: 'image'
-            }
-        }
-    </script>
-
-    <!-- Include a reference to IndexedDBShim for library to work on Safari 7.x -->
-    <script src="../vendor/IndexedDBShim/dist/IndexedDBShim.js"></script>
-
-    <script src="http://js.arcgis.com/3.11/"></script>
-</head>
-
-<body>
-
-<div class="container">
-    <div class="row">
-        <div class="col-xs-12">
-            <div id="mapDiv"></div>
-        </div>
-    </div>
-</div>
-
-<script>
-
-    // Make sure to reference the tiles library within the require statement!
-    require([
-        "esri/map",
-        "dojo/on",
-        "esri/arcgis/utils",
-        "//esri.github.com/bootstrap-map-js/src/js/bootstrapmap.js",
-        "../dist/offline-tiles-basic-src.js",
-        "dojo/domReady!"],
-        function(Map,on,arcgisUtils,BootstrapMap) {
-
-            var map;
-
-            // Load the map
-            arcgisUtils.createMap("bbc1a04a3eca4430be144d7a08b43a17","mapDiv").then(function(response){
-                var map = response.map;
-
-                map = response.map;
-
-                // Initialize BootstrapMap to make the map responsive
-                BootstrapMap.create(map);
-
-            });
-        });
-</script>
-
-
-<!-- Bootstrap core JavaScript
-================================================== -->
-<!-- Placed at the end of the document so the pages load faster -->
-<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
-<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
-
-</body>
-</html>
-
-                        
-                    
-
NOTE: Replace paths with your references. Or build your app in the /demo directory
-
-
-

Step 2: Extend basemap using the offline library

-

This initializes the OfflineTilesEnabler library and tells it which tiled map service layer to use for offline. Test to make sure map loads.

-
- -
-                        
-<!DOCTYPE html>
-<html>
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
-    <title>Offline ArcGIS.com</title>
-
-    <!-- Bootstrap core CSS -->
-    <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
-
-    <link rel="stylesheet" href="http://js.arcgis.com/3.11/esri/css/esri.css">
-    <link rel="stylesheet" type="text/css" href="http://esri.github.io/bootstrap-map-js/src/css/bootstrapmap.css">
-    <style>
-        html, body, #map {
-            height: 100%;
-            width: 100%;
-            margin: 0;
-            padding: 0;
-        }
-        body {
-            background-color: #000000;
-            overflow: hidden;
-            font-family: "Trebuchet MS";
-        }
-        .floatRight {float: right;}
-        .container { padding: 20px;}
-    </style>
-
-    <!-- Include a reference to offline.js which detects online/offline conditions -->
-    <script src="../vendor/offline/offline.min.js"></script>
-    <script>
-        // Set the online/offline detection options.
-        // More info at: http://github.hubspot.com/offline/docs/welcome/
-        Offline.options = {
-            checks: {
-                image: {
-                    url: function() {
-                        return 'http://esri.github.io/offline-editor-js/tiny-image.png?_=' +
-                                (Math.floor(Math.random() * 1000000000));
-                    }
-                },
-                active: 'image'
-            }
-        }
-    </script>
-
-    <!-- Include a reference to IndexedDBShim for library to work on Safari 7.x -->
-    <script src="../vendor/IndexedDBShim/dist/IndexedDBShim.js"></script>
-
-    <script src="http://js.arcgis.com/3.11/"></script>
-</head>
-
-<body>
-
-<div class="row">
-    <div class="col-xs-12">
-        <div id="mapDiv"></div>
-    </div>
-</div>
-
-<script>
-
-    // Make sure to reference the tiles library within the require statement!
-    require([
-        "esri/map",
-        "dojo/on",
-        "esri/arcgis/utils",
-        "//esri.github.com/bootstrap-map-js/src/js/bootstrapmap.js",
-        "../dist/offline-tiles-basic-src.js",
-        "dojo/domReady!"],
-        function(Map,on,arcgisUtils,BootstrapMap) {
-
-            var map, basemapLayer;
-            var offlineTilesEnabler = new O.esri.Tiles.OfflineTilesEnabler();
-
-            showMap();
-
-
-            function showMap(){
-                // Load the map
-                arcgisUtils.createMap("bbc1a04a3eca4430be144d7a08b43a17","mapDiv").then(function(response){
-                    map = response.map;
-
-                    // Initialize BootstrapMap to make the map responsive
-                    BootstrapMap.create(map);
-
-                    // Get the ArcGIS.com basemap that we want to use offline.
-                    // And then extend it for offline use.
-                    if(map.loaded)
-                    {
-                        basemapLayer = map.getLayer( map.layerIds[0] );
-                        initializeOfflineTiles();
-
-                    }
-                    else
-                    {
-                        map.on("load",function()
-                        {
-                            basemapLayer = map.getLayer( map.layerIds[0] );
-                            initializeOfflineTiles();
-                        });
-                    }
-
-                });
-            }
-
-            function initializeOfflineTiles(){
-                offlineTilesEnabler.extend(basemapLayer,function(success) {
-                    if (success) {
-                        console.log("ArcGIS.com map extended for offline!")
-                    }
-                })
-            }
-        });
-</script>
-
-
-<!-- Bootstrap core JavaScript
-================================================== -->
-<!-- Placed at the end of the document so the pages load faster -->
-<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
-<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
-
-</body>
-</html>
-
-                        
-                    
-
NOTE: Replace paths with your references. Or build your app in the /demo directory
- -
- -
-

Step 3: Configure tiles download.

-

Enable the ability to download tiles as well the ability to toggle online and offline.

-
- - - -
- - -
-
-
-                            
-
-<!DOCTYPE html>
-<html>
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
-    <title>Offline ArcGIS.com</title>
-
-    <!-- Bootstrap core CSS -->
-    <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
-
-    <link rel="stylesheet" href="http://js.arcgis.com/3.11/esri/css/esri.css">
-    <link rel="stylesheet" type="text/css" href="http://esri.github.io/bootstrap-map-js/src/css/bootstrapmap.css">
-    <style>
-        #mapDiv {
-            min-height: 500px;
-            max-height: 1000px;
-        }
-        body {
-            background-color: #ffffff;
-            overflow: hidden;
-            font-family: "Trebuchet MS";
-        }
-
-        .floatRight {float: right;}
-        .container { padding: 20px;}
-    </style>
-
-    <!-- Include a reference to offline.js which detects online/offline conditions -->
-    <script src="../vendor/offline/offline.min.js"></script>
-    <script>
-        // Set the online/offline detection options.
-        // More info at: http://github.hubspot.com/offline/docs/welcome/
-        Offline.options = {
-            checks: {
-                image: {
-                    url: function() {
-                        return 'http://esri.github.io/offline-editor-js/tiny-image.png?_=' +
-                                (Math.floor(Math.random() * 1000000000));
-                    }
-                },
-                active: 'image'
-            }
-        }
-    </script>
-
-    <!-- Include a reference to IndexedDBShim for library to work on Safari 7.x -->
-    <script src="../vendor/IndexedDBShim/dist/IndexedDBShim.js"></script>
-
-    <script src="http://js.arcgis.com/3.11/"></script>
-</head>
-
-<body>
-
-<div class="container">
-    <div class="row">
-        <div class="col-xs-10">
-            <div class="form form-group btn-group" data-toggle="buttons">
-                <button class="btn btn-success" id="btn-get-tiles">1. Download Tiles</button>
-                <button class="btn btn-success" disabled id="btn-online-offline">2. Go Offline</button>
-                <button class="btn btn-success" disabled id="btn-pan-left">3. Pan left</button>
-            </div>
-        </div>
-        <div class="col-xs-2">
-            <!-- this indicates whether app is offline (down) or online (up) -->
-            <button id="btn-state" class="btn btn-success btn-large floatRight">
-                <span id="state-span" class="glyphicon glyphicon-link"> Up</span>
-            </button>
-        </div>
-    </div>
-    <div class="row">
-        <div class="col-xs-12">
-            <div id="mapDiv"></div>
-        </div>
-    </div>
-</div>
-
-<script>
-
-    // Make sure to reference the tiles library within the require statement!
-    require([
-        "esri/map",
-        "dojo/on",
-        "esri/arcgis/utils",
-        "//esri.github.com/bootstrap-map-js/src/js/bootstrapmap.js",
-        "../dist/offline-tiles-basic-src.js",
-        "dojo/domReady!"],
-        function(Map,on,arcgisUtils,BootstrapMap) {
-
-            var map, basemapLayer;
-            var offlineTilesEnabler = new O.esri.Tiles.OfflineTilesEnabler();
-
-            // Check if browser state is online or offline
-            Offline.check();
-            Offline.on('up down', updateState );
-
-            // For cancelling the download of tiles
-            var _wantToCancel;
-            var _downloadState = "downloaded";
-
-            // Set up min and max boundaries for retrieving tiles
-            var minZoomAdjust = -1, maxZoomAdjust = 1, mMinZoom, mMaxZoom;
-
-            // Set up button click listeners.
-            var btnGetTiles = document.getElementById("btn-get-tiles");
-            var btnOnlineOffline = document.getElementById("btn-online-offline");
-            var btnPanLeft = document.getElementById("btn-pan-left");
-
-            var imgOfflineIndicator = document.getElementById("state-span");
-            var btnState = document.getElementById("btn-state");
-
-            on(btnGetTiles,"click",downloadTiles);
-            on(btnOnlineOffline,"click",goOnlineOffline);
-            on(btnPanLeft,"click",panLeft);
-
-            showMap();
-
-
-            function showMap(){
-                // Load the map
-                arcgisUtils.createMap("bbc1a04a3eca4430be144d7a08b43a17","mapDiv").then(function(response){
-                    map = response.map;
-
-                    // Initialize BootstrapMap to make the map responsive
-                    BootstrapMap.create(map);
-
-                    // Get the ArcGIS.com basemap that we want to use offline.
-                    // And then extend it for offline use.
-                    if(map.loaded)
-                    {
-                        basemapLayer = map.getLayer( map.layerIds[0] );
-                        initializeOfflineTiles();
-
-                    }
-                    else
-                    {
-                        map.on("load",function()
-                        {
-                            basemapLayer = map.getLayer( map.layerIds[0] );
-                            initializeOfflineTiles();
-                        });
-                    }
-
-                });
-            }
-
-            function initializeOfflineTiles(){
-                offlineTilesEnabler.extend(basemapLayer,function(success) {
-                    if (success) {
-                        console.log("ArcGIS.com map extended for offline!")
-                    }
-                })
-            }
-
-            function downloadTiles(){
-
-                if(_downloadState == "downloading"){
-                    _wantToCancel = true;
-                }
-                else{
-                    _wantToCancel = false;
-
-                    // First delete any existing tiles from database
-                    basemapLayer.deleteAllTiles(function(success,err){
-                        var zoom = basemapLayer.getMinMaxLOD(minZoomAdjust,maxZoomAdjust);
-
-                        // Now download tiles
-                        basemapLayer.prepareForOffline(zoom.min, zoom.max, map.extent, function(progress){
-                            console.log("downloading tiles...");
-
-                            if(progress.hasOwnProperty("countNow")){
-                                var percent = Math.floor(progress.countNow / progress.countMax * 100);
-                                btnGetTiles.innerHTML = 'Saving to phone ' + percent + "% - Tap to Cancel";
-                            }
-
-                            if( progress.finishedDownloading )
-                            {
-                                btnGetTiles.innerHTML = "Saving to phone 100% - Tap to Cancel";
-
-                                if( progress.cancelRequested )
-                                {
-                                    alert("Tile download was cancelled");
-                                    _downloadState = "cancelled";
-                                }
-                                else
-                                {
-                                    alert("Tile download complete");
-                                    _downloadState = "downloaded";
-                                    btnOnlineOffline.disabled = false;
-                                }
-
-                                btnGetTiles.innerHTML = '1. Download Tiles';
-                            }
-                            return _wantToCancel; //determines if a cancel request has been issued
-                        });
-
-                        _downloadState = "downloading";
-
-                    });
-                }
-            }
-
-            // Force the tileLayer between online and offline
-            function goOnlineOffline(){
-
-                btnPanLeft.disabled = false;
-
-                if(btnOnlineOffline.innerHTML == "2. Go Offline"){
-                    toggleStateUp(false);
-                    console.log("Map is offline");
-                }
-                else{
-                    toggleStateUp(true);
-                    console.log("Map is online");
-                }
-            }
-
-            function toggleStateUp(state){
-                if(state){
-                    btnOnlineOffline.innerHTML = "2. Go Offline";
-                    basemapLayer.goOnline();
-                    imgOfflineIndicator.className = "glyphicon glyphicon-link";
-                    imgOfflineIndicator.innerHTML = " Up";
-                    btnState.className = "btn btn-success btn-large floatRight";
-                }
-                else{
-                    btnOnlineOffline.innerHTML = "2. Go Online";
-                    basemapLayer.goOffline();
-                    imgOfflineIndicator.className = "glyphicon glyphicon-thumbs-down";
-                    imgOfflineIndicator.innerHTML = " Down";
-                    btnState.className = "btn btn-danger btn-large floatRight";
-                }
-            }
-
-            // Set the ArcGIS.com map online or offline.
-            // When set offline it will look for tiles in the tiles database
-            function updateState(){
-                if(Offline.state === 'up'){
-                    if(typeof basemapLayer != "undefined") basemapLayer.goOnline();
-                    toggleStateUp(true);
-                }
-                else{
-                    if(typeof basemapLayer != "undefined") basemapLayer.goOffline();
-                    toggleStateUp(false);
-                }
-            }
-
-            // Pan left when "offline" to view only tiles that have been stored locally
-            function panLeft(){
-                map.panLeft();
-            }
-        });
-</script>
-
-
-<!-- Bootstrap core JavaScript
-================================================== -->
-<!-- Placed at the end of the document so the pages load faster -->
-<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
-<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
-
-</body>
-</html>
-
-                            
-                        
-
NOTE: Replace paths with your references. Or build your app in the /demo directory
-
- -
-
-
-
- - - -
-
-
- - -
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/demo/getstarted-editing.html b/demo/getstarted-editing.html deleted file mode 100644 index b7dbd779..00000000 --- a/demo/getstarted-editing.html +++ /dev/null @@ -1,1208 +0,0 @@ - - - - - - - - - Get Started Editing - - - - - - - - - - - - - - - - - - - - - - - -
-
-

Getting Started with Editing

-

Basic steps for working with Editing geographic features for offline.

- -
-
- -
- -
-
- - - -
-
-

Step 1: Fill in the basics

-

Add library references and build basic layout. Then test to make sure map loads. - A fully working and editable version of this step is included in /demo/samples/editing-step1.html.

-
-
-                        
-<!DOCTYPE html>
-<html>
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
-    <title>Offline Features</title>
-    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
-    <link rel="stylesheet" href="//js.arcgis.com/3.13/esri/css/esri.css">
-    <link rel="stylesheet" href="../widgets/modal/css/modal-popup.css">
-    <link rel="stylesheet" type="text/css" href="http://esri.github.io/bootstrap-map-js/src/css/bootstrapmap.css">
-    <style>
-        #mapDiv {
-            min-height: 500px;
-            max-height: 1000px;
-        }
-
-        #img-offline-indicator {
-            padding: 8px;
-            position: relative; float: right;
-        }
-
-        /* Override mod-popup default */
-        .mod-popup-stop-input {color: black;}
-        .span-pending {color: blue; padding-left: 1em;}
-        .voffset20px { margin-top: 20px; }
-        .floatRight { float: right}
-    </style>
-
-    <!-- Include a reference to offline.js which detects online/offline conditions -->
-    <script src="../../vendor/offline/offline.min.js"></script>
-    <script>
-        // Set how we pull in custom AMD modules
-        var path = location.pathname.replace(/[^\/]+$/, '');
-        var dojoConfig =
-        {
-            debug: true,
-            packages:[
-                {
-                    name: "widgets",
-                    location: path + "../widgets/modal/"
-                }]
-        }
-
-        // Set the online/offline detection options.
-        // More info at: http://github.hubspot.com/offline/docs/welcome/
-        Offline.options = {
-            checks: {
-                image: {
-                    url: function() {
-                        return 'http://esri.github.io/offline-editor-js/tiny-image.png?_=' +
-                                (Math.floor(Math.random() * 1000000000));
-                    }
-                },
-                active: 'image'
-            }
-        }
-    </script>
-
-    <script src="//js.arcgis.com/3.13/"></script>
-</head>
-
-<body>
-
-<div class="container voffset20px">
-    <div class="row">
-        <div class="col-xs-8">
-            <button class="btn btn-success" id="btn-online-offline">Go Offline</button>
-            <span class="span-pending">Pending Edits <span id="span-pending-edits" class="badge">0</span></span>
-        </div>
-        <div class="col-xs-4">
-            <button id="btn-state" class="btn btn-success btn-large floatRight">
-                <span id="state-span" class="glyphicon glyphicon-link"> Up</span>
-            </button>
-        </div>
-    </div>
-    <div class="row voffset20px">
-        <div class="col-xs-12">
-            <div id="mapDiv"></div>
-        </div>
-    </div>
-</div>
-<!-- Stub for modal popup -->
-<div id="modal-popup"></div>
-
-<script>
-
-    require([
-                "esri/map","esri/tasks/query",
-                "dojo/on","dojo/parser", "esri/renderers/SimpleRenderer",
-                "esri/symbols/SimpleMarkerSymbol","esri/Color",
-                "widgets/popup","esri/layers/FeatureLayer",
-                "//esri.github.com/bootstrap-map-js/src/js/bootstrapmap.js",
-                "../../dist/offline-edit-src.js",
-                "dojo/domReady!"],
-            function(Map,Query,on,parser,SimpleRenderer,SimpleMarkerSymbol,
-                     Color,ModalPopup,FeatureLayer,BootstrapMap){
-
-                var map, popup, editsStore;
-                var defaultSymbol;
-                var offlineFeaturesManager;
-                var btnOnlineOffline, btnState, pendingEdits;
-                var imgOfflineIndicator;
-                var closeBtn,saveBtn,deleteBtn,stopMainID,stopID,stopRoutes,stopNames;
-
-                initMap();
-
-                function initMap(){
-
-                    map = BootstrapMap.create("mapDiv",{
-                        basemap: "topo",
-                        center: [-104.99,39.75], // longitude, latitude
-                        zoom: 15
-                    });
-                }
-            }
-    );
-
-</script>
-<!-- Bootstrap core JavaScript
-================================================== -->
-<!-- Placed at the end of the document so the pages load faster -->
-<script src="//code.jquery.com/jquery-2.1.3.min.js"></script>
-<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
-</body>
-</html>
-
-                        
-                    
-
NOTE: If you aren't building this in the offline repo directory, replace paths with your references.
-
-
-

Step 2: Configure modal popup

-

This initializes and configures the modal popup. Test to make sure map loads and popup displays. - You'll notice the buttons on the popup don't work yet. We'll fix that in the Step 3. - A full working version of this step is included in /demo/samples/editing-step2.html.

-
- -
-                        
-<!DOCTYPE html>
-<html>
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
-    <title>Offline Features</title>
-    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
-    <link rel="stylesheet" href="//js.arcgis.com/3.13/esri/css/esri.css">
-    <link rel="stylesheet" href="../widgets/modal/css/modal-popup.css">
-    <link rel="stylesheet" type="text/css" href="http://esri.github.io/bootstrap-map-js/src/css/bootstrapmap.css">
-    <style>
-        #mapDiv {
-            min-height: 500px;
-            max-height: 1000px;
-        }
-
-        #img-offline-indicator {
-            padding: 8px;
-            position: relative; float: right;
-        }
-
-        /* Override mod-popup default */
-        .mod-popup-stop-input {color: black;}
-        .span-pending {color: blue; padding-left: 1em;}
-        .voffset20px { margin-top: 20px; }
-        .floatRight { float: right}
-    </style>
-
-    <!-- Include a reference to offline.js which detects online/offline conditions -->
-    <script src="../../vendor/offline/offline.min.js"></script>
-    <script>
-        // Set how we pull in custom AMD modules
-        var path = location.pathname.replace(/[^\/]+$/, '');
-        var dojoConfig =
-        {
-            debug: true,
-            packages:[
-                {
-                    name: "widgets",
-                    location: path + "../widgets/modal/"
-                }]
-        }
-
-        // Set the online/offline detection options.
-        // More info at: http://github.hubspot.com/offline/docs/welcome/
-        Offline.options = {
-            checks: {
-                image: {
-                    url: function() {
-                        return 'http://esri.github.io/offline-editor-js/tiny-image.png?_=' +
-                                (Math.floor(Math.random() * 1000000000));
-                    }
-                },
-                active: 'image'
-            }
-        }
-    </script>
-
-    <script src="//js.arcgis.com/3.13/"></script>
-</head>
-
-<body>
-
-<div class="container voffset20px">
-    <div class="row">
-        <div class="col-xs-8">
-            <button class="btn btn-success" id="btn-online-offline">Go Offline</button>
-            <span class="span-pending">Pending Edits <span id="span-pending-edits" class="badge">0</span></span>
-        </div>
-        <div class="col-xs-4">
-            <button id="btn-state" class="btn btn-success btn-large floatRight">
-                <span id="state-span" class="glyphicon glyphicon-link"> Up</span>
-            </button>
-        </div>
-    </div>
-    <div class="row voffset20px">
-        <div class="col-xs-12">
-            <div id="mapDiv"></div>
-        </div>
-    </div>
-</div>
-<!-- Stub for modal popup -->
-<div id="modal-popup"></div>
-
-<script>
-
-    require([
-                "esri/map","esri/tasks/query",
-                "dojo/on","dojo/parser", "esri/renderers/SimpleRenderer",
-                "esri/symbols/SimpleMarkerSymbol","esri/Color",
-                "widgets/popup","esri/layers/FeatureLayer",
-                "//esri.github.com/bootstrap-map-js/src/js/bootstrapmap.js",
-                "../../dist/offline-edit-src.js",
-                "dojo/domReady!"],
-            function(Map,Query,on,parser,SimpleRenderer,SimpleMarkerSymbol,
-                     Color,ModalPopup,FeatureLayer,BootstrapMap){
-
-                var map, popup, editsStore;
-                var defaultSymbol;
-                var offlineFeaturesManager;
-                var btnOnlineOffline, btnState, pendingEdits;
-                var imgOfflineIndicator;
-                var closeBtn,saveBtn,deleteBtn,stopMainID,stopID,stopRoutes,stopNames;
-
-                initVars();
-                initMap();
-
-                function initMap(){
-
-                    map = BootstrapMap.create("mapDiv",{
-                        basemap: "topo",
-                        center: [-104.99,39.75], // longitude, latitude
-                        zoom: 15
-                    });
-
-                    map.on("load",function(evt){
-                        console.log("Map is loaded. Loading popup...")
-                        window.setTimeout(function(){
-                            popup.show();
-                        },2000);
-                    });
-                }
-
-                function initVars(){
-
-                    editsStore = new O.esri.Edit.EditStore();
-                    popup = new ModalPopup({animation: true, animationDuration: 1},"modal-popup");
-                    popup.startup();
-
-                    parser.parse();
-
-                    // Check if browser state is online or offline
-                    Offline.check();
-                    Offline.on('up down', updateState );
-
-
-                    btnOnlineOffline = document.getElementById("btn-online-offline");
-                    imgOfflineIndicator = document.getElementById("state-span");
-                    btnState = document.getElementById("btn-state");
-                    pendingEdits = document.getElementById("span-pending-edits");
-
-                    // Modify symbol size based on screen size.
-                    // Bigger screens get smaller symbols. Smaller screens get larger symbols.
-                    var width = window.innerWidth
-                            || document.documentElement.clientWidth
-                            || document.body.clientWidth;
-
-                    var height = window.innerHeight
-                            || document.documentElement.clientHeight
-                            || document.body.clientHeight;
-
-                    if (height >= 768 || width >= 1024) {
-                        defaultSymbol= new SimpleMarkerSymbol().setStyle(
-                                SimpleMarkerSymbol.STYLE_DIAMOND).setColor(
-                                new Color([255,0,0,0.5])).setSize(20);
-                    }
-                    else{
-                        defaultSymbol= new SimpleMarkerSymbol().setStyle(
-                                SimpleMarkerSymbol.STYLE_DIAMOND).setColor(
-                                new Color([255,0,0,0.5])).setSize(35);
-                    }
-
-                    // Variables for modal popup
-                    closeBtn = document.getElementById("mod-popup-close-btn");
-                    saveBtn = document.getElementById("mod-popup-save-btn");
-                    deleteBtn = document.getElementById("mod-popup-delete-btn");
-                    stopMainID = document.getElementById("stop-main-id");
-                    stopID = document.getElementById("stop-id");
-                    stopRoutes = document.getElementById("stop-routes");
-                    stopNames = document.getElementById("stop-names");
-
-                }
-
-                function setModalPopupClickListeners(){
-                    closeBtn.onclick = function(evt){
-                        hideModalPopup();
-                    }
-
-                    saveBtn.onclick = function(evt) {
-                        //TO-DO
-                    }
-
-                }
-
-                function showModalPopup(graphic){
-                    popup.graphic = graphic; // assign graphic to modPopup as a property.
-                    popup.show();
-                }
-
-                function hideModalPopup(){
-                    popup.hide();
-                }
-
-                // Force feature service offline
-                function goOnlineOffline(){
-
-                    if(btnOnlineOffline.innerHTML == "Go Offline"){
-                        toggleStateUp(false);
-                        console.log("Map is offline");
-                    }
-                    else{
-                        toggleStateUp(true);
-                        console.log("Map is online");
-                    }
-                }
-
-                // Set the UX to online or offline state
-                function toggleStateUp(state){
-                    if(state){
-                        btnOnlineOffline.innerHTML = "Go Offline";
-                        offlineFeaturesManager.goOnline();
-                        imgOfflineIndicator.className = "glyphicon glyphicon-link";
-                        imgOfflineIndicator.innerHTML = " Up";
-                        btnState.className = "btn btn-success btn-large floatRight";
-                    }
-                    else{
-                        btnOnlineOffline.innerHTML = "Go Online";
-                        offlineFeaturesManager.goOffline();
-                        imgOfflineIndicator.className = "glyphicon glyphicon-thumbs-down";
-                        imgOfflineIndicator.innerHTML = " Down";
-                        btnState.className = "btn btn-danger btn-large floatRight";
-                    }
-                }
-
-                // Automatically set the feature service online or offline.
-                function updateState(){
-                    if(Offline.state === 'up'){
-                        toggleStateUp(true);
-                    }
-                    else{
-                        toggleStateUp(false);
-                    }
-                }
-            }
-    );
-
-</script>
-<!-- Bootstrap core JavaScript
-================================================== -->
-<!-- Placed at the end of the document so the pages load faster -->
-<script src="//code.jquery.com/jquery-2.1.3.min.js"></script>
-<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
-</body>
-</html>
-
-                        
-                    
-
NOTE: If you aren't building this in the offline repo directory, replace paths with your references.
- -
- -
-

Step 3: Configure offline editing.

-

Enable the ability to store features (points, lines and polygons) while offline, and resync features when internet is restored. - A full working version of this step is included in /demo/samples/editing-step3.html.

-
- - - -
- - -
-
-
-                            
-
-<!DOCTYPE html>
-<html>
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
-    <title>Offline Features</title>
-    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
-    <link rel="stylesheet" href="//js.arcgis.com/3.13/esri/css/esri.css">
-    <link rel="stylesheet" href="//esri.github.io/bootstrap-map-js/src/css/bootstrapmap.css">
-    <link rel="stylesheet" href="../widgets/modal/css/modal-popup.css">
-    <style>
-        #mapDiv {
-            min-height: 500px;
-            max-height: 1000px;
-        }
-
-        #img-offline-indicator {
-            padding: 8px;
-            position: relative;
-            float: right;
-        }
-
-        /* Override mod-popup default */
-        .mod-popup-stop-input {
-            color: black;
-        }
-
-        .span-pending {
-            color: blue;
-            padding-left: 1em;
-        }
-
-        .voffset20px {
-            margin-top: 20px;
-        }
-
-        .floatRight {
-            float: right
-        }
-    </style>
-
-    <!-- Include a reference to offline.js which detects online/offline conditions -->
-    <script src="../../vendor/offline/offline.min.js"></script>
-    <script>
-        // Set how we pull in custom AMD modules
-        var path = location.pathname.replace(/[^\/]+$/, '');
-        var dojoConfig =
-        {
-            debug: true,
-            packages: [
-                {
-                    name: "widgets",
-                    location: path + "../widgets/modal/"
-                }]
-        }
-
-        // Set the online/offline detection options.
-        // More info at: http://github.hubspot.com/offline/docs/welcome/
-        Offline.options = {
-            checks: {
-                image: {
-                    url: function () {
-                        return 'http://esri.github.io/offline-editor-js/tiny-image.png?_=' +
-                                (Math.floor(Math.random() * 1000000000));
-                    }
-                },
-                active: 'image'
-            }
-        }
-    </script>
-
-    <script src="//js.arcgis.com/3.13/"></script>
-</head>
-
-<body>
-
-<div class="container voffset20px">
-    <div class="row">
-        <div class="col-xs-8">
-            <button class="btn btn-success" id="btn-online-offline">Go Offline</button>
-            <span class="span-pending">Pending Edits <span id="span-pending-edits" class="badge">0</span></span>
-        </div>
-        <div class="col-xs-4">
-            <button id="btn-state" class="btn btn-success btn-large floatRight">
-                <span id="state-span" class="glyphicon glyphicon-link"> Up</span>
-            </button>
-        </div>
-    </div>
-    <div class="row voffset20px">
-        <div class="col-xs-12">
-            <div id="mapDiv"></div>
-        </div>
-    </div>
-</div>
-<!-- Stub for modal popup -->
-<div id="modal-popup"></div>
-
-<script>
-
-    require([
-                "esri/map", "esri/tasks/query",
-                "dojo/on", "dojo/parser", "esri/renderers/SimpleRenderer",
-                "esri/symbols/SimpleMarkerSymbol", "esri/Color",
-                "widgets/popup", "esri/layers/FeatureLayer",
-                "//esri.github.com/bootstrap-map-js/src/js/bootstrapmap.js",
-                "../../dist/offline-edit-src.js",
-                "dojo/domReady!"],
-            function (Map, Query, on, parser, SimpleRenderer, SimpleMarkerSymbol,
-                      Color, ModalPopup, FeatureLayer, BootstrapMap) {
-
-                var map, popup, editsStore;
-                var defaultSymbol;
-                var offlineFeaturesManager;
-                var btnOnlineOffline, btnState, pendingEdits;
-                var imgOfflineIndicator;
-                var closeBtn, saveBtn, deleteBtn, stopMainID, stopID, stopRoutes, stopNames;
-                var busStopFeatureLayer;
-
-                initVars();
-                initMap();
-
-                function initMap() {
-
-                    map = BootstrapMap.create("mapDiv", {
-                        basemap: "topo",
-                        center: [-104.99, 39.75], // longitude, latitude
-                        zoom: 15
-                    });
-
-                    busStopFeatureLayer = new FeatureLayer("http://services1.arcgis.com/M8KJPUwAXP8jhtnM/arcgis/rest/services/Denver_Bus_Stops/FeatureServer/0", {
-                        mode: FeatureLayer.MODE_SNAPSHOT,
-                        outFields: ["OBJECTID", "BSID", "ROUTES", "STOPNAME"]
-                    });
-
-                    //Set the graphics to red boxes to make it easy to click on them
-                    //on a mobile device.
-                    busStopFeatureLayer.setRenderer(new SimpleRenderer(defaultSymbol));
-                    busStopFeatureLayer.on("update-end", function (evt) {
-
-                        // Now we can enable the button click listener
-                        on(btnOnlineOffline, "click", goOnlineOffline);
-
-                        initOfflineFeaturesMgr();
-                        setFeatureLayerClickHandler();
-                        setModalPopupClickListeners();
-                    });
-
-                    map.addLayer(busStopFeatureLayer);
-                }
-
-                function initVars() {
-
-                    editsStore = new O.esri.Edit.EditStore();
-                    popup = new ModalPopup({animation: true, animationDuration: 1}, "modal-popup");
-                    popup.startup();
-
-                    parser.parse();
-
-                    // Check if browser state is online or offline
-                    Offline.check();
-                    Offline.on('up down', updateState);
-
-
-                    btnOnlineOffline = document.getElementById("btn-online-offline");
-                    imgOfflineIndicator = document.getElementById("state-span");
-                    btnState = document.getElementById("btn-state");
-                    pendingEdits = document.getElementById("span-pending-edits");
-
-                    // Modify symbol size based on screen size.
-                    // Bigger screens get smaller symbols. Smaller screens get larger symbols.
-                    var width = window.innerWidth
-                            || document.documentElement.clientWidth
-                            || document.body.clientWidth;
-
-                    var height = window.innerHeight
-                            || document.documentElement.clientHeight
-                            || document.body.clientHeight;
-
-                    if (height >= 768 || width >= 1024) {
-                        defaultSymbol = new SimpleMarkerSymbol().setStyle(
-                                SimpleMarkerSymbol.STYLE_DIAMOND).setColor(
-                                new Color([255, 0, 0, 0.5])).setSize(20);
-                    }
-                    else {
-                        defaultSymbol = new SimpleMarkerSymbol().setStyle(
-                                SimpleMarkerSymbol.STYLE_DIAMOND).setColor(
-                                new Color([255, 0, 0, 0.5])).setSize(35);
-                    }
-
-                    // Variables for modal popup
-                    closeBtn = document.getElementById("mod-popup-close-btn");
-                    saveBtn = document.getElementById("mod-popup-save-btn");
-                    deleteBtn = document.getElementById("mod-popup-delete-btn");
-                    stopMainID = document.getElementById("stop-main-id");
-                    stopID = document.getElementById("stop-id");
-                    stopRoutes = document.getElementById("stop-routes");
-                    stopNames = document.getElementById("stop-names");
-
-                }
-
-                /**
-                 * ************************************
-                 * OFFLINE FEATURE SERVICE HANDLER CODE
-                 * ************************************
-                 */
-
-                function initOfflineFeaturesMgr() {
-                    offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManager();
-
-                    // IMPORTANT!!!
-                    // A proxy page may be required to upload attachments.
-                    // If you are using a CORS enabled server you may be able to set the proxyPath to null.
-                    // Refer to "Using the Proxy Page" for more information:
-                    //https://developers.arcgis.com/en/javascript/jshelp/ags_proxy.html
-                    offlineFeaturesManager.proxyPath = null;
-
-                    // IMPORTANT!!!
-                    // This tells the database which graphic.attribute property to use as a unique identifier
-                    // You can lok this information up in your feature service directory under the "Fields" category.
-                    // Example: http://services1.arcgis.com/M8KJPUwAXP8jhtnM/arcgis/rest/services/Denver_Bus_Stops/FeatureServer/0
-                    offlineFeaturesManager.DB_UID = "FID";
-
-                    offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_ENQUEUED, updatePendingEditStatus);
-                    offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_SENT, updatePendingEditStatus);
-                    offlineFeaturesManager.on(offlineFeaturesManager.events.ALL_EDITS_SENT, updatePendingEditStatus);
-
-                    offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_ENQUEUED_ERROR, function(errorsArray){
-                        alert("There was an error attempting to write to the database: " + JSON.stringify(errorsArray));
-                    });
-
-                    offlineFeaturesManager.extend(busStopFeatureLayer, function (success, error) {
-                        if (success) {
-                            console.log("offlineFeaturesManager initialized.");
-
-                            Offline.check();
-                            Offline.on('up down', updateState);
-                        }
-                        else {
-                            alert("Unable to enable feature layer for offline usage. " + error);
-                        }
-                    });
-                }
-
-                // Display modal popup when someone clicks on a feature
-                // and load the fields with data from the feature service.
-                function setFeatureLayerClickHandler() {
-                    busStopFeatureLayer.on("click", function (evt) {
-
-                        showModalPopup(evt.graphic);
-                        var atts = evt.graphic.attributes;
-                        stopID.value = atts.BSID;
-                        stopMainID.value = atts.FID;
-                        stopNames.value = atts.STOPNAME;
-                        stopRoutes.value = atts.ROUTES;
-
-                    }.bind(this));
-                }
-
-                function updatePendingEditStatus() {
-                    busStopFeatureLayer.pendingEditsCount(function(count){
-                        pendingEdits.innerHTML = count;
-                    });
-                }
-
-
-                function setModalPopupClickListeners() {
-                    closeBtn.onclick = function (evt) {
-                        hideModalPopup();
-                    };
-
-                    saveBtn.onclick = function (evt) {
-                        popup.graphic.attributes.ROUTES = stopRoutes.value;
-                        popup.graphic.attributes.STOPNAME = stopNames.value;
-
-                        busStopFeatureLayer.applyEdits(null, [popup.graphic], null, function (result) {
-                                    console.log("Successfully saved changes to: " +
-                                    popup.graphic.attributes.STOPNAME);
-                                    hideModalPopup();
-                                },
-                                function (err) {
-                                    alert("There was a problem while trying to save: " +
-                                    popup.graphic.attributes.STOPNAME);
-                                })
-                    };
-
-                    deleteBtn.onclick = function (evt) {
-                        busStopFeatureLayer.applyEdits(null, null, [popup.graphic], function (result) {
-                                    console.log("Successfully deleted: " + popup.graphic.attributes.STOPNAME);
-                                    hideModalPopup();
-                                },
-                                function (err) {
-                                    alert("There was a problem while trying to delete: " +
-                                    popup.graphic.attributes.STOPNAME);
-                                })
-                    }
-
-                }
-
-                function showModalPopup(graphic) {
-                    popup.graphic = graphic; // assign graphic to modPopup as a property.
-                    popup.show();
-                }
-
-                function hideModalPopup() {
-                    popup.hide();
-                }
-
-                /**
-                 * ************************************
-                 * GO OFFLINE/ONLINE HANDLER CODE
-                 * ************************************
-                 */
-
-                // Force feature service offline
-                function goOnlineOffline() {
-
-                    if (btnOnlineOffline.innerHTML == "Go Offline") {
-                        toggleStateUp(false);
-                        console.log("Map is offline");
-                    }
-                    else {
-                        toggleStateUp(true);
-                        console.log("Map is online");
-                    }
-                }
-
-                // Set the UX to online or offline state
-                function toggleStateUp(state) {
-                    if (state) {
-                        offlineFeaturesManager.goOnline();
-                        btnOnlineOffline.innerHTML = "Go Offline";
-                        imgOfflineIndicator.className = "glyphicon glyphicon-link";
-                        imgOfflineIndicator.innerHTML = " Up";
-                        btnState.className = "btn btn-success btn-large floatRight";
-                    }
-                    else {
-                        offlineFeaturesManager.goOffline();
-                        btnOnlineOffline.innerHTML = "Go Online";
-                        imgOfflineIndicator.className = "glyphicon glyphicon-thumbs-down";
-                        imgOfflineIndicator.innerHTML = " Down";
-                        btnState.className = "btn btn-danger btn-large floatRight";
-                    }
-                }
-
-                // Automatically set the feature service online or offline.
-                function updateState() {
-                    if (Offline.state === 'up') {
-                        toggleStateUp(true);
-                    }
-                    else {
-                        toggleStateUp(false);
-                    }
-                }
-            }
-    );
-
-</script>
-<!-- Bootstrap core JavaScript
-================================================== -->
-<!-- Placed at the end of the document so the pages load faster -->
-<script src="//code.jquery.com/jquery-2.1.3.min.js"></script>
-<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
-</body>
-</html>
-
-                            
-                        
-
NOTE: If you aren't building this in the offline repo directory, replace paths with your references.
-
- -
-
-
- - Pending Edits 0 -
-
- -
-
-
-
-
-
-
- -
-
-
-
- -
-
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/demo/getstarted-tiles.html b/demo/getstarted-tiles.html deleted file mode 100644 index 5ba871b6..00000000 --- a/demo/getstarted-tiles.html +++ /dev/null @@ -1,785 +0,0 @@ - - - - - - - - - Offline-editor-js - - - - - - - - - - - - - - - - - - - - - - - -
-
-

Getting Started with Tiles

-

Basic steps for working with tiled base map services for offline.

- -
-
- -
- -
-
- - - -
-
-

Step 1: Fill in the basics

-

Add the basic library references. Then test to make sure map loads.

-
-
-                        
-<!DOCTYPE html>
-<html>
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
-    <title>Offline Tiles</title>
-
-    <!-- Bootstrap core CSS -->
-    <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
-
-    <link rel="stylesheet" href="http://js.arcgis.com/3.11/esri/css/esri.css">
-    <link rel="stylesheet" type="text/css" href="http://esri.github.io/bootstrap-map-js/src/css/bootstrapmap.css">
-    <style>
-        #mapDiv {
-            min-height: 500px;
-            max-height: 1000px;
-        }
-
-        body {
-            background-color: #ffffff;
-            overflow: hidden;
-            font-family: "Trebuchet MS";
-        }
-
-        .floatRight {float: right;}
-        .container { padding: 20px;}
-    </style>
-
-    <!-- Include a reference to offline.js which detects online/offline conditions -->
-    <script src="../vendor/offline/offline.min.js"></script>
-    <script>
-        // Set the online/offline detection options.
-        // More info at: http://github.hubspot.com/offline/docs/welcome/
-        Offline.options = {
-            checks: {
-                image: {
-                    url: function() {
-                        return 'http://esri.github.io/offline-editor-js/tiny-image.png?_=' +
-                                (Math.floor(Math.random() * 1000000000));
-                    }
-                },
-                active: 'image'
-            }
-        }
-    </script>
-
-    <!-- Include a reference to IndexedDBShim for library to work on Safari 7.x -->
-    <script src="../vendor/IndexedDBShim/dist/IndexedDBShim.js"></script>
-
-    <script src="http://js.arcgis.com/3.11/"></script>
-</head>
-
-<body>
-<div class="row">
-    <div class="col-xs-12">
-        <div id="mapDiv"></div>
-    </div>
-</div>
-
-<script>
-
-    // Make sure to reference the tiles library within the require statement!
-    require(["esri/map","dojo/on","//esri.github.com/bootstrap-map-js/src/js/bootstrapmap.js","../dist/offline-tiles-advanced-min.js", "dojo/domReady!"],
-        function(Map,on,Bootstrapmap) {
-
-        // Initialize our map to be responsive
-        var map = Bootstrapmap.create("mapDiv",{
-            basemap: "topo",
-            center: [-122.45, 37.75], // longitude, latitude
-            zoom: 15
-        });
-
-    });
-</script>
-
-<!-- Bootstrap core JavaScript
-================================================== -->
-<!-- Placed at the end of the document so the pages load faster -->
-<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
-<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
-</body>
-</html>
-
-                        
-                    
-
NOTE: Replace paths with your references. Or build your app in the /demo directory
-
-
-

Step 2: Configure tiled basemap to work offline

-

This initializes the offline-editor-js library. Test to make sure map loads.

-
- -
-                        
-<!DOCTYPE html>
-<html>
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
-    <title>Offline Tiles</title>
-
-    <!-- Bootstrap core CSS -->
-    <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
-
-    <link rel="stylesheet" href="http://js.arcgis.com/3.11/esri/css/esri.css">
-    <link rel="stylesheet" type="text/css" href="http://esri.github.io/bootstrap-map-js/src/css/bootstrapmap.css">
-    <style>
-        #mapDiv {
-            min-height: 500px;
-            max-height: 1000px;
-        }
-
-        body {
-            background-color: #ffffff;
-            overflow: hidden;
-            font-family: "Trebuchet MS";
-        }
-
-       .floatRight {float: right;}
-        .container { padding: 20px;}
-    </style>
-
-    <!-- Include a reference to offline.js which detects online/offline conditions -->
-    <script src="../vendor/offline/offline.min.js"></script>
-    <script>
-        // Set the online/offline detection options.
-        // More info at: http://github.hubspot.com/offline/docs/welcome/
-        Offline.options = {
-            checks: {
-                image: {
-                    url: function() {
-                        return 'http://esri.github.io/offline-editor-js/tiny-image.png?_=' +
-                                (Math.floor(Math.random() * 1000000000));
-                    }
-                },
-                active: 'image'
-            }
-        }
-    </script>
-
-    <!-- Include a reference to IndexedDBShim for library to work on Safari 7.x -->
-    <script src="../vendor/IndexedDBShim/dist/IndexedDBShim.js"></script>
-
-    <script src="http://js.arcgis.com/3.11/"></script>
-</head>
-
-<body>
-<div class="row">
-    <div class="col-xs-12">
-        <div id="mapDiv"></div>
-    </div>
-</div>
-
-<script>
-
-    // Make sure to reference the tiles library within the require statement!
-    require(["esri/map","dojo/on","//esri.github.com/bootstrap-map-js/src/js/bootstrapmap.js","../dist/offline-tiles-advanced-min.js", "dojo/domReady!"],
-        function(Map,on,Bootstrapmap) {
-
-        // Check if browser state is online or offline
-        Offline.check();
-        Offline.on('up down', updateState );
-
-        // Initialize our map to be responsive
-        var map = Bootstrapmap.create("mapDiv",{
-            //basemap: "topo", // comment out this basemap!
-            center: [-122.45, 37.75], // longitude, latitude
-            zoom: 15
-        });
-
-        // Now we initialize a topo tiled basemap service to be offline-enabled.
-        var tileLayer = O.esri.Tiles.OfflineTileEnablerLayer(
-                "http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer",
-                function(evt){
-                    console.log("Offline tile lib enabled. App is: " + Offline.state);
-                },
-                true);
-
-        // Add our offline tile layer to the map instead of using the default basemap!
-        map.addLayer(tileLayer);
-
-        // Set the tileLayer online or offline.
-        // When set to offline, the map will look for tiles in the local tiles database
-        function updateState(){
-            if(Offline.state === 'up'){
-                if(typeof tileLayer != "undefined") tileLayer.goOnline();
-            }
-            else{
-                if(typeof tileLayer != "undefined") tileLayer.goOffline();
-            }
-        }
-
-    });
-</script>
-
-<!-- Bootstrap core JavaScript
-================================================== -->
-<!-- Placed at the end of the document so the pages load faster -->
-<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
-<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
-</body>
-</html>
-
-                        
-                    
-
NOTE: Replace paths with your references. Or build your app in the /demo directory
- -
- -
-

Step 3: Configure tiles download.

-

Enable the ability to download tiles as well the ability to toggle online and offline.

-
- - - -
- - -
-
-
-                            
-
-<!DOCTYPE html>
-<html>
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
-    <title>Offline Tiles</title>
-
-    <!-- Bootstrap core CSS -->
-    <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
-
-    <link rel="stylesheet" href="http://js.arcgis.com/3.11/esri/css/esri.css">
-    <link rel="stylesheet" type="text/css" href="http://esri.github.io/bootstrap-map-js/src/css/bootstrapmap.css">
-    <style>
-        #mapDiv {
-            min-height: 500px;
-            max-height: 1000px;
-        }
-
-        body {
-            background-color: #ffffff;
-            overflow: hidden;
-            font-family: "Trebuchet MS";
-        }
-
-        .floatRight { float: right;}
-        .container { padding: 20px;}
-    </style>
-
-    <!-- Include a reference to offline.js which detects online/offline conditions -->
-    <script src="../vendor/offline/offline.min.js"></script>
-    <script>
-        // Set the online/offline detection options.
-        // More info at: http://github.hubspot.com/offline/docs/welcome/
-        Offline.options = {
-            checks: {
-                image: {
-                    url: function() {
-                        return 'http://esri.github.io/offline-editor-js/tiny-image.png?_=' +
-                                (Math.floor(Math.random() * 1000000000));
-                    }
-                },
-                active: 'image'
-            }
-        }
-    </script>
-
-    <!-- Include a reference to IndexedDBShim for library to work on Safari 7.x -->
-    <script src="../vendor/IndexedDBShim/dist/IndexedDBShim.js"></script>
-
-    <script src="http://js.arcgis.com/3.11/"></script>
-</head>
-
-<body>
-
-<!-- Our buttons and online/offline indicator -->
-<div class="container">
-    <div class="row">
-        <div class="col-xs-10">
-            <div class="form form-group btn-group" data-toggle="buttons">
-                <button class="btn btn-success" id="btn-get-tiles">1. Download Tiles</button>
-                <button class="btn btn-success" disabled id="btn-online-offline">2. Go Offline</button>
-                <button class="btn btn-success" disabled id="btn-pan-left">3. Pan left</button>
-            </div>
-        </div>
-        <div class="col-xs-2">
-            <!-- this indicates whether app is offline (down) or online (up) -->
-            <button id="btn-state" class="btn btn-success btn-large floatRight">
-                <span id="state-span" class="glyphicon glyphicon-link"> Up</span>
-            </button>
-        </div>
-    </div>
-    <div class="row">
-        <div class="col-xs-12">
-            <div id="mapDiv"></div>
-        </div>
-    </div>
-</div>
-
-<script>
-
-    // Make sure to reference the tiles library within the require statement!
-    require(["esri/map","dojo/on","//esri.github.com/bootstrap-map-js/src/js/bootstrapmap.js",
-        "../dist/offline-tiles-advanced-min.js", "dojo/domReady!"],
-        function(Map,on,Bootstrapmap){
-
-            var map,basemapLayer;
-
-            // Check if browser state is online or offline
-            Offline.check();
-            Offline.on('up down', updateState );
-
-            // For cancelling the download of tiles
-            var _wantToCancel = false;
-            var _downloadState = "downloaded";
-
-            // Set up min and max boundaries for retrieving tiles
-            var minZoomAdjust = -1, maxZoomAdjust = 1;
-
-            // Set up button click listeners.
-            var btnGetTiles = document.getElementById("btn-get-tiles");
-            var btnOnlineOffline = document.getElementById("btn-online-offline");
-            var btnPanLeft = document.getElementById("btn-pan-left");
-
-            on(btnGetTiles,"click",downloadTiles);
-            on(btnOnlineOffline,"click",goOnlineOffline);
-            on(btnPanLeft,"click",panLeft);
-
-            var imgOfflineIndicator = document.getElementById("state-span");
-            var btnState = document.getElementById("btn-state");
-
-            showMap();
-
-            function showMap(){
-
-                // Initialize our map to be responsive
-                map = Bootstrapmap.create("mapDiv",{
-                    //basemap: "topo", // comment out this basemap!
-                    center: [-122.45, 37.75], // longitude, latitude
-                    zoom: 15
-                });
-
-                // Now we initialize the basemap to be offline-enabled. This is out new basemap.
-                basemapLayer = O.esri.Tiles.OfflineTileEnablerLayer(
-                        "http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer",
-                        function(evt){
-                            console.log("Offline tile lib enabled. App is: " + Offline.state);
-                        },
-                        true);
-
-                // Add our offline tile layer to the map instead of using the default basemap!
-                map.addLayer(basemapLayer);
-            }
-
-            function downloadTiles(){
-
-                if(_downloadState == "downloading"){
-                    _wantToCancel = true;
-                }
-                else{
-                    _wantToCancel = false;
-
-                    // First delete any existing tiles from database
-                    basemapLayer.deleteAllTiles(function(success,err){
-                        var zoom = basemapLayer.getMinMaxLOD(minZoomAdjust,maxZoomAdjust);
-
-                        // Now download tiles
-                        basemapLayer.prepareForOffline(zoom.min, zoom.max, map.extent, function(progress){
-                            console.log("downloading tiles...");
-
-                            if(progress.hasOwnProperty("countNow")){
-                                var percent = Math.floor(progress.countNow / progress.countMax * 100);
-                                btnGetTiles.innerHTML = 'Saving to phone ' + percent + "% - Tap to Cancel";
-                            }
-
-                            if( progress.finishedDownloading )
-                            {
-                                btnGetTiles.innerHTML = "Saving to phone 100% - Tap to Cancel";
-
-                                if( progress.cancelRequested )
-                                {
-                                    alert("Tile download was cancelled");
-                                    _downloadState = "cancelled";
-                                }
-                                else
-                                {
-                                    alert("Tile download complete");
-                                    _downloadState = "downloaded";
-                                    btnOnlineOffline.disabled = false;
-                                }
-
-                                btnGetTiles.innerHTML = '1. Download Tiles';
-                            }
-                            return _wantToCancel; //determines if a cancel request has been issued
-                        });
-
-                        _downloadState = "downloading";
-
-                    });
-                }
-            }
-
-            // Force the tileLayer between online and offline
-            function goOnlineOffline(){
-
-                btnPanLeft.disabled = false;
-
-                if(btnOnlineOffline.innerHTML == "2. Go Offline"){
-                    toggleStateUp(false);
-                    console.log("Map is offline");
-                }
-                else{
-                    toggleStateUp(true);
-                    console.log("Map is online");
-                }
-            }
-
-            function toggleStateUp(state){
-                if(state){
-                    btnOnlineOffline.innerHTML = "2. Go Offline";
-                    basemapLayer.goOnline();
-                    imgOfflineIndicator.className = "glyphicon glyphicon-link";
-                    imgOfflineIndicator.innerHTML = " Up";
-                    btnState.className = "btn btn-success btn-large floatRight";
-                }
-                else{
-                    btnOnlineOffline.innerHTML = "2. Go Online";
-                    basemapLayer.goOffline();
-                    imgOfflineIndicator.className = "glyphicon glyphicon-thumbs-down";
-                    imgOfflineIndicator.innerHTML = " Down";
-                    btnState.className = "btn btn-danger btn-large floatRight";
-                }
-            }
-
-            // Set the ArcGIS.com map online or offline.
-            // When set offline it will look for tiles in the tiles database
-            function updateState(){
-                if(Offline.state === 'up'){
-                    if(typeof basemapLayer != "undefined") basemapLayer.goOnline();
-                    toggleStateUp(true);
-                }
-                else{
-                    if(typeof basemapLayer != "undefined") basemapLayer.goOffline();
-                    toggleStateUp(false);
-                }
-            }
-
-            // Pan left when "offline" to view only tiles that have been stored locally
-            function panLeft(){
-                map.panLeft();
-            }
-
-    });
-</script>
-
-<!-- Bootstrap core JavaScript
-================================================== -->
-<!-- Placed at the end of the document so the pages load faster -->
-<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
-<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
-</body>
-</html>
-
-                            
-                        
-
NOTE: Replace paths with your references. Or build your app in the /demo directory
-
- -
-
-
-
- - - -
-
-
- - -
-
-
-
-
-
-
- -
-
-
-
- -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/demo/getstarted-tpk.html b/demo/getstarted-tpk.html deleted file mode 100644 index c86507fe..00000000 --- a/demo/getstarted-tpk.html +++ /dev/null @@ -1,574 +0,0 @@ - - - - - - - - - Offline-editor-js - - - - - - - - - - - - - - - - - - - - - - - -
-
-

Getting Started with TPKs

-

Basic steps for working with TPK packages for offline.

- -
-
- -
- -
-
- - - -
-
-

Step 1: Fill in the basics

-

Add the basic library references and CSS. Then test to make sure application loads. - There won't be a map to display just yet, you'll only see the header bar.

-
-
-                        
-<!DOCTYPE html>
-<html>
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
-    <title>Offline TPK</title>
-    <link href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet" media="screen">
-    <link rel="stylesheet" href="http://js.arcgis.com/3.11/esri/css/esri.css"">
-    <link rel="stylesheet" type="text/css" href="http://esri.github.io/bootstrap-map-js/src/css/bootstrapmap.css">
-    <style>
-        #mapDiv {
-            min-height: 300px;
-            max-height: 1000px;
-        }
-        .container { padding: 20px;}
-    </style>
-
-    <!-- Include a reference to IndexedDBShim for library to work on Safari 7.x -->
-    <script src="../vendor/IndexedDBShim/dist/IndexedDBShim.js"></script>
-
-    <script src="http://js.arcgis.com/3.11/"></script>
-</head>
-
-<body>
-
-<div class="container">
-    <div class="row">
-        <div class="col-lg-12">
-            <div class="form form-group input-group">
-                <input id="url-input" type="text" class="form-control"
-                            value="../samples/tpks/Beirut.zip">
-            <span class="input-group-btn">
-                <button id="url-btn" class="btn btn-success" type="button">Go!</button>
-            </span>
-            </div>
-        </div>
-    </div>
-    <div class="row">
-        <div class="col-xs-12">
-            <div id="mapDiv"></div>
-        </div>
-    </div>
-</div>
-
-<script>
-
-    // Make sure to reference the tpk library within the require statement!
-    require([
-        "esri/map",
-        "dojo/on",
-        "//esri.github.com/bootstrap-map-js/src/js/bootstrapmap.js",
-        "../dist/offline-tpk-src.js", "dojo/domReady!"],
-            function(Map,on,BootstrapMap) {
-
-                var map, tpkLayer;
-
-                var url = document.getElementById("url-input");
-                var urlInputBtn = document.getElementById("url-btn");
-
-            }
-    );
-</script>
-<!-- Bootstrap core JavaScript
-================================================== -->
-<!-- Placed at the end of the document so the pages load faster -->
-<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
-<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
-</body>
-</html>
-
-                        
-                    
-
NOTE: Replace paths with your references.
-
-
-

Step 2: Retrieve TPK.

-

Download and unzip the TPK. You should get an alert when TPK is fully downloaded.

-

NOTE: If you have a TPK file you will have to change its type to .zip for the browser to recognize it.

-
- -
-                        
-<!DOCTYPE html>
-<html>
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
-    <title>Offline TPK</title>
-    <link href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet" media="screen">
-    <link rel="stylesheet" href="http://js.arcgis.com/3.11/esri/css/esri.css">
-    <link rel="stylesheet" type="text/css" href="http://esri.github.io/bootstrap-map-js/src/css/bootstrapmap.css">
-    <style>
-        #mapDiv {
-            min-height: 300px;
-            max-height: 1000px;
-        }
-        .container { padding: 20px;}
-    </style>
-
-    <!-- Include a reference to IndexedDBShim for library to work on Safari 7.x -->
-    <script src="../vendor/IndexedDBShim/dist/IndexedDBShim.js"></script>
-
-    <script src="http://js.arcgis.com/3.11/"></script>
-</head>
-
-<body>
-
-<div class="container">
-    <div class="row">
-        <div class="col-lg-12">
-            <div class="form form-group input-group">
-                <input id="url-input" type="text" class="form-control"
-                       value="../samples/tpks/Beirut.zip">
-            <span class="input-group-btn">
-                <button id="url-btn" class="btn btn-success" type="button">Go!</button>
-            </span>
-            </div>
-        </div>
-    </div>
-    <div class="row">
-        <div class="col-xs-12">
-            <div id="mapDiv"></div>
-        </div>
-    </div>
-</div>
-
-<script>
-
-    // Make sure to reference the tpk library within the require statement!
-    require([
-        "esri/map",
-        "dojo/on",
-        "//esri.github.com/bootstrap-map-js/src/js/bootstrapmap.js",
-        "../dist/offline-tpk-src.js", "dojo/domReady!"],
-            function(Map,on,BootstrapMap) {
-
-                var map, tpkLayer;
-
-                var url = document.getElementById("url-input");
-                var urlInputBtn = document.getElementById("url-btn");
-                urlInputBtn.onclick = function(){
-                    getTPK();
-                };
-
-                // Retrieve the TPK file via an HTTP request
-                function getTPK(){
-                    urlInputBtn.innerHTML = "Get file via url";
-
-                    var xhrRequest = new XMLHttpRequest();
-                    xhrRequest.open("GET", url.value, true);
-                    xhrRequest.responseType = "blob";
-                    xhrRequest.onprogress = function(evt){
-                        var percent = (parseFloat(evt.loaded / evt.totalSize) * 100).toFixed(0);
-                        urlInputBtn.innerHTML = "Get file via url " + percent + "%";
-                        console.log("Begin downloading remote tpk file...")
-                    }
-
-                    xhrRequest.error = function(err){console.log("ERROR")}
-
-                    xhrRequest.onload = function(oEvent) {
-                        if(this.status == 200) {
-                            console.log("Remote tpk download finished.")
-                            zipParser(this.response);
-                        }
-                        else{
-                            alert("There was a problem loading the file. " + this.status + ": " + this.statusText )
-                        }
-                    };
-
-                    xhrRequest.send();
-                }
-
-                // Parse the zip file contents into a zip.Entries object
-                function zipParser(blob){
-
-                    O.esri.zip.createReader(new O.esri.zip.BlobReader(blob), function (zipReader) {
-                        zipReader.getEntries(function (entries) {
-                            if(entries) alert("TPK downloaded and unzipped!");
-                            zipReader.close(function(evt){
-                                console.log("Done reading zip file.")
-                            })
-                        }, function (err) {
-                            alert("There was a problem reading the file!: " + err);
-                        })
-                    })
-                }
-
-            }
-    );
-</script>
-<!-- Bootstrap core JavaScript
-================================================== -->
-<!-- Placed at the end of the document so the pages load faster -->
-<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
-<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
-</body>
-</html>
-
-                        
-                    
-
NOTE: Replace paths with your references.
- -
- -
-

Step 3: Display TPK tiles.

-

In this step we hand the zip file entries over to TPKLayer to inflate the map.

-
- - - -
- - -
-
-
-                            
-
-<!DOCTYPE html>
-<html>
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
-    <title>Offline TPK</title>
-    <link href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet" media="screen">
-    <link rel="stylesheet" href="http://js.arcgis.com/3.11/esri/css/esri.css">
-    <link rel="stylesheet" type="text/css" href="http://esri.github.io/bootstrap-map-js/src/css/bootstrapmap.css">
-    <style>
-        #mapDiv {
-            min-height: 300px;
-            max-height: 1000px;
-        }
-        .container { padding: 20px;}
-    </style>
-
-    <!-- Include a reference to IndexedDBShim for library to work on Safari 7.x -->
-    <script src="../vendor/IndexedDBShim/dist/IndexedDBShim.js"></script>
-
-    <script src="http://js.arcgis.com/3.11/"></script>
-</head>
-
-<body>
-
-<div class="container">
-    <div class="row">
-        <div class="col-lg-12">
-            <div class="form form-group input-group">
-                <input id="url-input" type="text" class="form-control"
-                       value="../samples/tpks/Beirut.zip">
-            <span class="input-group-btn">
-                <button id="url-btn" class="btn btn-success" type="button">Go!</button>
-            </span>
-            </div>
-        </div>
-    </div>
-    <div class="row">
-        <div class="col-xs-12">
-            <div id="mapDiv"></div>
-        </div>
-    </div>
-</div>
-
-<script>
-
-    // Make sure to reference the tpk library within the require statement!
-    require([
-        "esri/map",
-        "dojo/on",
-        "//esri.github.com/bootstrap-map-js/src/js/bootstrapmap.js",
-        "../dist/offline-tpk-src.js", "dojo/domReady!"],
-            function(Map,on,BootstrapMap) {
-
-                var map, tpkLayer;
-
-                var url = document.getElementById("url-input");
-                var urlInputBtn = document.getElementById("url-btn");
-                urlInputBtn.onclick = function(){
-                    getTPK();
-                };
-
-                // Retrieve the TPK file via an HTTP request
-                function getTPK(){
-                    urlInputBtn.innerHTML = "Get file via url";
-
-                    var xhrRequest = new XMLHttpRequest();
-                    xhrRequest.open("GET", url.value, true);
-                    xhrRequest.responseType = "blob";
-                    xhrRequest.onprogress = function(evt){
-                        var percent = (parseFloat(evt.loaded / evt.totalSize) * 100).toFixed(0);
-                        urlInputBtn.innerHTML = "Get file via url " + percent + "%";
-                        console.log("Begin downloading remote tpk file...")
-                    }
-
-                    xhrRequest.error = function(err){console.log("ERROR")}
-
-                    xhrRequest.onload = function(oEvent) {
-                        if(this.status == 200) {
-                            console.log("Remote tpk download finished.")
-                            zipParser(this.response);
-                        }
-                        else{
-                            alert("There was a problem loading the file. " + this.status + ": " + this.statusText )
-                        }
-                    };
-
-                    xhrRequest.send();
-                }
-
-                // Parse the zip file contents into a zip.Entries object
-                function zipParser(blob){
-
-                    O.esri.zip.createReader(new O.esri.zip.BlobReader(blob), function (zipReader) {
-                        zipReader.getEntries(function (entries) {
-                            initMap(entries);
-                            //if(entries)alert("TPK downloaded and unzipped!");
-                            zipReader.close(function(evt){
-                                console.log("Done reading zip file.")
-                            })
-                        }, function (err) {
-                            alert("There was a problem reading the file!: " + err);
-                        })
-                    })
-                }
-
-                // Initialize the Map and the TPKLayer
-                function initMap(entries){
-
-                    map = BootstrapMap.create("mapDiv",{});
-
-                    tpkLayer = new O.esri.TPK.TPKLayer();
-                    tpkLayer.on("progress", function (evt) {
-                        console.log("TPK loading...");
-                    })
-                    tpkLayer.extend(entries);
-
-                    map.addLayer(tpkLayer);
-                }
-
-            }
-    );
-</script>
-<!-- Bootstrap core JavaScript
-================================================== -->
-<!-- Placed at the end of the document so the pages load faster -->
-<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
-<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
-</body>
-</html>
-
-                            
-                        
-
NOTE: Replace paths with your references. This sample may look different than our live sample.
-
- -
-
-
-
- - - - -
-
-
-
-
-
-
-
- -
- -
-
- -
-
-
-
-
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/demo/getstarted.html b/demo/getstarted.html deleted file mode 100644 index 90c57436..00000000 --- a/demo/getstarted.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - Offline-editor-js - - - - - - - - - - - - - - - - - - - - - - - -
-
-

Getting Started Samples

-

Basic responsive samples on how to work with the ArcGIS API for JavaScript while offline.

- -
-
- -
- - - -
-
-
-

Fork or clone offline-editor-js

-
-
-

Here are the important directories to know:

-
    -
  • \dist - minified library files and concatenated source (for debugging).
  • -
  • \samples - more examples that demonstrate the library's functionality.
  • -
  • \vendor - contains IndexedDBShim and offline.js helper libraries
  • -
-
- -
-
-
- -
- - - - - - - - - - - - - \ No newline at end of file From 11a540471f868a2929d0232a23ae30db27d89b45 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Fri, 20 Nov 2015 19:17:19 -0700 Subject: [PATCH 15/48] update how to use --- doc/howtouseofmadvancedlibrary.md | 2 +- doc/howtouseofmbasic.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/howtouseofmadvancedlibrary.md b/doc/howtouseofmadvancedlibrary.md index b233a33f..8f4c981f 100644 --- a/doc/howtouseofmadvancedlibrary.md +++ b/doc/howtouseofmadvancedlibrary.md @@ -12,7 +12,7 @@ This library allows a developer to extend a feature layer with intermittent and The pattern for how we include the tiles and edit library within the `require` statement is called generic script injection. Note that we do assign any of the editing or tile libraries an alias name. For example, we specified the mobile path "esri/map" and we gave it an alias called "Map." But, we did not do the equivalent for `offline-tiles-basic-min.js` or `offline-edit-advanced-min.js`. ```html - + + - - - - - - - -
-
-
- - Pending Edits 0 -
-
- -
-
-
-
-
-
-
-
- - - - - - - - - - \ No newline at end of file diff --git a/demo/samples/editing-step2.html b/demo/samples/editing-step2.html deleted file mode 100644 index 79f12ec8..00000000 --- a/demo/samples/editing-step2.html +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - Offline Features - - - - - - - - - - - - - - - -
-
-
- - Pending Edits 0 -
-
- -
-
-
-
-
-
-
-
- - - - - - - - - - \ No newline at end of file diff --git a/demo/samples/editing-step3.html b/demo/samples/editing-step3.html deleted file mode 100644 index 12258972..00000000 --- a/demo/samples/editing-step3.html +++ /dev/null @@ -1,363 +0,0 @@ - - - - - - Offline Features - - - - - - - - - - - - - - - -
-
-
- - Pending Edits 0 -
-
- -
-
-
-
-
-
-
-
- - - - - - - - - - \ No newline at end of file From 8a169847b94f266ea5634b1ddaba7ffb81357e84 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Mon, 23 Nov 2015 10:29:53 -0700 Subject: [PATCH 18/48] delete/rename old libraries --- ...turesManager.js => OfflineEditAdvanced.js} | 151 +----------------- ...ineFeatureLayer.js => OfflineEditBasic.js} | 3 +- 2 files changed, 4 insertions(+), 150 deletions(-) rename lib/edit/{offlineFeaturesManager.js => OfflineEditAdvanced.js} (93%) rename lib/edit/{OfflineFeatureLayer.js => OfflineEditBasic.js} (99%) diff --git a/lib/edit/offlineFeaturesManager.js b/lib/edit/OfflineEditAdvanced.js similarity index 93% rename from lib/edit/offlineFeaturesManager.js rename to lib/edit/OfflineEditAdvanced.js index 7acb1603..6a5f7e64 100644 --- a/lib/edit/offlineFeaturesManager.js +++ b/lib/edit/OfflineEditAdvanced.js @@ -19,7 +19,7 @@ define([ function (Evented, Deferred, all, declare, array, domAttr, domStyle, query, esriConfig, GraphicsLayer, Graphic, esriRequest, SimpleMarkerSymbol, SimpleLineSymbol, SimpleFillSymbol, urlUtils) { "use strict"; - return declare("O.esri.Edit.OfflineFeaturesManager", [Evented], + return declare("O.esri.Edit.OfflineEditAdvanced", [Evented], { _onlineStatus: "online", _featureLayers: {}, @@ -57,7 +57,6 @@ define([ ALL_EDITS_SENT: "all-edits-sent", // ...after going online and there are no pending edits in the queue ATTACHMENT_ENQUEUED: "attachment-enqueued", ATTACHMENTS_SENT: "attachments-sent", - EXTEND_COMPLETE: "extend-complete" // ...when the libary has completed its initialization }, /** @@ -111,7 +110,7 @@ define([ var self = this; layer.offlineExtended = true; // to identify layer has been extended - if(!layer.loaded || layer._url === null) { + if(!layer.loaded) { console.error("Make sure to initialize OfflineFeaturesManager after layer loaded and feature layer update-end event."); } @@ -776,7 +775,7 @@ define([ * use with offline browser restarts. You can retrieve the collections and use them * to reconstitute a featureLayer and then redisplay all the associated features. * - * To retrieve use OfflineFeaturesManager.getFeatureCollections(). + * To retrieve use OfflineEditAdvanced.getFeatureCollections(). * @param callback (boolean) * @private */ @@ -1793,83 +1792,6 @@ define([ }); }, - /** - * DEPRECATED as of v2.11 - - * TO-DO remove in next release - * Only delete items from database that were verified as successfully updated on the server. - * @param responses Object - * @param callback callback(true, responses) or callback(false, responses) - * @private - */ - _cleanSuccessfulEditsDatabaseRecords: function (responses, callback) { - if (Object.keys(responses).length !== 0) { - - var editsArray = []; - var editsFailedArray = []; - - for (var key in responses) { - if (responses.hasOwnProperty(key)) { - - var edit = responses[key]; - var tempResult = {}; - - if (edit.updateResults.length > 0) { - if (edit.updateResults[0].success) { - tempResult.layer = edit.layer; - tempResult.id = edit.updateResults[0].objectId; - editsArray.push(tempResult); - } - else { - editsFailedArray.push(edit); - } - } - if (edit.deleteResults.length > 0) { - if (edit.deleteResults[0].success) { - tempResult.layer = edit.layer; - tempResult.id = edit.deleteResults[0].objectId; - editsArray.push(tempResult); - } - else { - editsFailedArray.push(edit); - } - } - if (edit.addResults.length > 0) { - if (edit.addResults[0].success) { - tempResult.layer = edit.layer; - tempResult.id = edit.tempId; - editsArray.push(tempResult); - } - else { - editsFailedArray.push(edit); - } - } - } - } - - var promises = {}; - var length = editsArray.length; - for (var i = 0; i < length; i++) { - promises[i] = this._updateDatabase(editsArray[i]); - } - //console.log("EDIT LIST " + JSON.stringify(editsArray)); - - // wait for all requests to finish - // - var allPromises = all(promises); - allPromises.then( - function (responses) { - editsFailedArray.length > 0 ? callback(false, responses) : callback(true, responses); - }, - function (errors) { - callback(false, errors); - } - ); - } - else { - callback(true, {}); - } - }, - /** * Deletes edits from database. * This does not handle phantom graphics! @@ -1923,73 +1845,6 @@ define([ }); }, - /** - * DEPRECATED at v2.11 - * Executes the _applyEdits() method when a feature layer is created using a REST endpoint - * @param layer - * @param id the unique id that identifies the Graphic in the database - * @param tempObjectIds - * @param adds - * @param updates - * @param deletes - * @returns {l.Deferred.promise} contains {id,layer,tempId,addResults,updateResults,deleteResults} - * @private - */ - _internalApplyEdits: function (layer, id, tempObjectIds, adds, updates, deletes) { - var that = this; - var dfd = new Deferred(); - - layer._applyEdits(adds, updates, deletes, - function (addResults, updateResults, deleteResults) { - layer._phantomLayer.clear(); - - // We use a different pattern if the attachmentsStore is valid and the layer has attachments - if (layer._attachmentsStore != null && layer.hasAttachments && tempObjectIds.length > 0) { - - var newObjectIds = addResults.map(function (r) { - return r.objectId; - }); - - layer._replaceFeatureIds(tempObjectIds, newObjectIds, function (count) { - console.log("Done replacing feature ids. Total count = " + count); - }); - } - - that._cleanDatabase(layer, tempObjectIds, addResults, updateResults, deleteResults).then(function(results){ - dfd.resolve({ - id: id, - layer: layer.url, - tempId: tempObjectIds, // let's us internally match an ADD to it's new ObjectId - addResults: addResults, - updateResults: updateResults, - deleteResults: deleteResults, - databaseResults: results, - databaseErrors: null - }); // wrap three arguments in a single object - }, function(error) { - dfd.resolve({ - id: id, - layer: layer.url, - tempId: tempObjectIds, // let's us internally match an ADD to it's new ObjectId - addResults: addResults, - updateResults: updateResults, - deleteResults: deleteResults, - databaseResults: null, - databaseErrors: error - }); // wrap three arguments in a single object - }); - - }, - function (error) { - layer.onEditsComplete = layer.__onEditsComplete; - delete layer.__onEditsComplete; - - dfd.reject(error); - } - ); - return dfd.promise; - }, - /** * Applies edits. This works with both standard feature layers and when a feature layer is created * using a feature collection. diff --git a/lib/edit/OfflineFeatureLayer.js b/lib/edit/OfflineEditBasic.js similarity index 99% rename from lib/edit/OfflineFeatureLayer.js rename to lib/edit/OfflineEditBasic.js index abf33c79..6b767374 100644 --- a/lib/edit/OfflineFeatureLayer.js +++ b/lib/edit/OfflineEditBasic.js @@ -19,7 +19,7 @@ define([ function (Evented, Deferred, all, declare, array, domAttr, domStyle, query, on, esriConfig, GraphicsLayer, FeatureLayer, Graphic) { "use strict"; - return declare("O.esri.Edit.OfflineFeatureLayer", [Evented], + return declare("O.esri.Edit.OfflineEditBasic", [Evented], { _onlineStatus: "online", _featureLayers: {}, @@ -226,7 +226,6 @@ define([ } promisesSuccess === true ? self.emit(self.events.EDITS_ENQUEUED, results) : self.emit(self.events.EDITS_ENQUEUED_ERROR, results); - // EDITS_ENQUEUED = callback(true, edit), and EDITS_ENQUEUED_ERROR = callback(false, /*String */ error) this._editHandler(results, _adds, updatesMap, callback, errback, deferred1); }.bind(this)); From 93b4b27d2de09a05a69173e9f982788d6f3c6b8c Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Mon, 23 Nov 2015 10:30:07 -0700 Subject: [PATCH 19/48] remove Offline.js from submodules --- .gitmodules | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index 682efeec..d7406779 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "vendor/offline"] - path = vendor/offline - url = https://github.com/HubSpot/offline.git [submodule "vendor/IndexedDBShim"] path = vendor/IndexedDBShim url = https://github.com/axemclion/IndexedDBShim.git From 0b1df1d9fc1cce348e8fd375625d97b53e1f52e7 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Mon, 23 Nov 2015 10:31:12 -0700 Subject: [PATCH 20/48] udpate samples --- samples/appcache-features.html | 48 +++++++++---------- samples/appcache-tiles.appcache | 2 +- samples/appcache-twofeatureslayer-noedit.html | 42 ++++++++-------- samples/attachments-editor-secure.html | 30 ++++++------ samples/attachments-editor.html | 30 ++++++------ samples/draw-pointlinepoly-offline.html | 24 +++++----- samples/simple-edit.html | 4 +- 7 files changed, 90 insertions(+), 90 deletions(-) diff --git a/samples/appcache-features.html b/samples/appcache-features.html index de0bd5b5..96750b7e 100644 --- a/samples/appcache-features.html +++ b/samples/appcache-features.html @@ -194,7 +194,7 @@ var _listener; // Variables for editing handling - var currentFeature, busStopFeatureLayer = null, offlineFeaturesManager = null; + var currentFeature, busStopFeatureLayer = null, offlineEdit = null; var pendingEdits = document.getElementById("span-pending-edits"); var imgOfflineIndicator = document.getElementById("state-span"); var btnState = document.getElementById("btn-state"); @@ -337,7 +337,7 @@ // Extend the feature layer with offline capabilities. // - initOfflineFeaturesMgr(); + initOfflineEdits(); // If app is online then we ONLY need to extend the feature layer. if(_isOnline){ @@ -357,7 +357,7 @@ } }); } - // If the app is offline then we need to retrieve the dataStore from OfflineFeaturesManager + // If the app is offline then we need to retrieve the dataStore from OfflineEditAdvanced // and then extend the feature layer using that information. else { loadFeatureLayerOffline(function(success) { @@ -403,7 +403,7 @@ * Load the feature while offline using information stored in database */ function loadFeatureLayerOffline(callback) { - offlineFeaturesManager.getFeatureLayerJSONDataStore(function(success,dataStore) { + offlineEdit.getFeatureLayerJSONDataStore(function(success,dataStore) { if(success){ // Use the feature layer returns from getFeatureDefinition() to reconstitute the layer @@ -446,26 +446,26 @@ * ********************************************** */ - function initOfflineFeaturesMgr(callback) { - offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManagerAdvanced(); + function initOfflineEdits() { + offlineEdit = new O.esri.Edit.OfflineEditAdvanced(); // IMPORTANT!!! // This tells the database which graphic.attribute property to use as a unique identifier // You can look this information up in your feature service directory under the "Fields" category. // Example: http://services1.arcgis.com/M8KJPUwAXP8jhtnM/arcgis/rest/services/Denver_Bus_Stops/FeatureServer/0 - offlineFeaturesManager.DB_UID = "FID"; + offlineEdit.DB_UID = "FID"; // IMPORTANT!!! // A proxy page may be required to upload attachments. // If you are using a CORS enabled feature service you can ignore this. // If your feature service is not CORS-enabled then you will need to configure this. // Refer to "Using the Proxy Page" for more information: https://developers.arcgis.com/en/javascript/jshelp/ags_proxy.html - offlineFeaturesManager.proxyPath = null; + offlineEdit.proxyPath = null; - offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_ENQUEUED, updateStatus); - offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_SENT, updateStatus); - offlineFeaturesManager.on(offlineFeaturesManager.events.ALL_EDITS_SENT, updateStatus); - offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_SENT_ERROR, editsError); + offlineEdit.on(offlineEdit.events.EDITS_ENQUEUED, updateStatus); + offlineEdit.on(offlineEdit.events.EDITS_SENT, updateStatus); + offlineEdit.on(offlineEdit.events.ALL_EDITS_SENT, updateStatus); + offlineEdit.on(offlineEdit.events.EDITS_SENT_ERROR, editsError); } function extendFeatureLayer(online,callback){ @@ -484,19 +484,19 @@ } // NOTE: if app is offline then we want the dataStore object to be null - offlineFeaturesManager.extend(busStopFeatureLayer,function(result, error) { + offlineEdit.extend(busStopFeatureLayer,function(result, error) { if(result) { - console.log("offlineFeaturesManager initialized."); + console.log("OfflineEditAdvanced initialized."); // This sets listeners to detect if the app goes online or offline. Offline.on('up', goOnline); Offline.on('down', goOffline); - // If the app is online then force offlineFeaturesManager to its online state + // If the app is online then force OfflineEditAdvanced to its online state // This will force the library to check for pending edits and attempt to // resend them to the Feature Service. if(_isOnline){ - offlineFeaturesManager.goOnline(function(result){ + offlineEdit.goOnline(function(result){ if(!result.success){ alert("There was a problem when attempting to go back online."); } @@ -506,7 +506,7 @@ }); } else { - offlineFeaturesManager.goOffline(); + offlineEdit.goOffline(); updateStatus(); } @@ -672,7 +672,7 @@ */ /** - * Forces offlineFeaturesManager to go online. + * Forces OfflineEditAdvanced to go online. * When invoking featureLayer.goOnline() we force library to sync any edits that were * stored while offline. */ @@ -685,10 +685,10 @@ } - offlineFeaturesManager.goOnline(function(success,error) { + offlineEdit.goOnline(function(success,error) { if(error === undefined) { setUIOnline(); - console.log("offlineFeatureManager is online."); + console.log("OfflineEditAdvanced is online."); } else { alert("There was a problem syncing offline edits: " + JSON.stringify(error)); @@ -702,20 +702,20 @@ } /** - * Forces offlineFeaturesManager offline + * Forces OfflineEditAdvanced offline */ function goOffline() { console.log("Going offline..."); setUIOffline(); - offlineFeaturesManager.goOffline(); + offlineEdit.goOffline(); if(typeof tileLayer != "undefined") tileLayer.goOffline(); } /** - * Toggles offlineFeaturesManager online/offline + * Toggles offlineEdit online/offline */ function goOnlineOffline() { - if(offlineFeaturesManager.getOnlineStatus() == offlineFeaturesManager.ONLINE){ + if(offlineEdit.getOnlineStatus() == offlineEdit.ONLINE){ goOffline(); } else{ diff --git a/samples/appcache-tiles.appcache b/samples/appcache-tiles.appcache index 686860b8..0f61101b 100644 --- a/samples/appcache-tiles.appcache +++ b/samples/appcache-tiles.appcache @@ -1,6 +1,6 @@ CACHE MANIFEST # This manifest was generated by grunt-manifest HTML5 Cache Manifest Generator -# Time: Fri Nov 20 2015 18:10:55 GMT-0700 (MST) +# Time: Mon Nov 23 2015 09:22:04 GMT-0700 (MST) CACHE: #manifest-generator,version:3.0.0 diff --git a/samples/appcache-twofeatureslayer-noedit.html b/samples/appcache-twofeatureslayer-noedit.html index b144d98e..c603c057 100644 --- a/samples/appcache-twofeatureslayer-noedit.html +++ b/samples/appcache-twofeatureslayer-noedit.html @@ -182,7 +182,7 @@ "esri/graphic","esri/geometry/Extent", "esri/SpatialReference", "//esri.github.io/bootstrap-map-js/src/js/bootstrapmap.js", "../dist/offline-tiles-advanced-src.js", - "../dist/offline-edit-src.js", + "../dist/offline-edit-advanced-src.js", "dojo/domReady!"], function(Map,FeatureLayer, SimpleRenderer,SimpleMarkerSymbol,Color,Query, @@ -193,7 +193,7 @@ var defaultSymbol, bStationSymbol; // Variables for editing handling - var busStopFeatureLayer = null, offlineFeaturesManager = {}; + var busStopFeatureLayer = null, offlineEdit = {}; var bicycleStationFeatureLayer = null; var imgOfflineIndicator = document.getElementById("state-span"); var btnState = document.getElementById("btn-state"); @@ -309,11 +309,11 @@ listener1 = busStopFeatureLayer.on("update-end",function(event){ listener1.remove(); - if(!offlineFeaturesManager.hasOwnProperty("ONLINE")){ - initOfflineFeaturesMgr(); + if(!offlineEdit.hasOwnProperty("ONLINE")){ + initOfflineEdits(); } - offlineFeaturesManager.extend(busStopFeatureLayer,function(result, error) { + offlineEdit.extend(busStopFeatureLayer,function(result, error) { if(result){ deferred1.resolve(true); map.addLayer(bicycleStationFeatureLayer); @@ -335,11 +335,11 @@ listener2 = bicycleStationFeatureLayer.on("update-end",function(event){ listener2.remove(); - if(!offlineFeaturesManager.hasOwnProperty("ONLINE")){ - initOfflineFeaturesMgr(); + if(!offlineEdit.hasOwnProperty("ONLINE")){ + initOfflineEdits(); } - offlineFeaturesManager.extend(bicycleStationFeatureLayer,function(result, error) { + offlineEdit.extend(bicycleStationFeatureLayer,function(result, error) { if(result){ deferred2.resolve(true); } @@ -366,7 +366,7 @@ // This is just an example of what you would do if you had stored edits! console.log("Application is online...attempting to send any stored edits."); - offlineFeaturesManager.goOnline(function(result){ + offlineEdit.goOnline(function(result){ if(!result.success){ alert("There was a problem when attempting to go back online."); } @@ -376,7 +376,7 @@ }); } else { - offlineFeaturesManager.goOffline(); + offlineEdit.goOffline(); } callback(); @@ -424,11 +424,11 @@ */ function loadFeatureLayerOffline(callback) { - initOfflineFeaturesMgr(); + initOfflineEdits(); var promises = []; - offlineFeaturesManager.getFeatureCollections(function(success,dataStore) { + offlineEdit.getFeatureCollections(function(success,dataStore) { if(success){ var deferred1 = new Deferred(); @@ -459,7 +459,7 @@ _listener1 = busStopFeatureLayer.on("update",function(evt){ _listener1.remove(); - offlineFeaturesManager.extend(busStopFeatureLayer,function(success, error){ + offlineEdit.extend(busStopFeatureLayer,function(success, error){ if(success){ console.log("busStopFeatureLayer successfully extended offline"); deferred1.resolve(true); @@ -495,7 +495,7 @@ _listener2 = bicycleStationFeatureLayer.on("update",function(evt){ _listener2.remove(); - offlineFeaturesManager.extend(bicycleStationFeatureLayer,function(success, error){ + offlineEdit.extend(bicycleStationFeatureLayer,function(success, error){ if(success){ console.log("bicycleStationFeatureLayer successfully extended offline"); deferred2.resolve(true); @@ -533,25 +533,25 @@ * ********************************************** */ - function initOfflineFeaturesMgr() { - console.log("Initializing OfflineFeaturesManager"); - offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManager(); + function initOfflineEdits() { + console.log("Initializing OfflineEditAdvanced"); + offlineEdit = new O.esri.Edit.OfflineEditAdvanced(); // IMPORTANT!!! // This tells the database which graphic.attribute property to use as a unique identifier // You can look this information up in your feature service directory under the "Fields" category. // Example: http://services1.arcgis.com/M8KJPUwAXP8jhtnM/arcgis/rest/services/Denver_Bus_Stops/FeatureServer/0 - offlineFeaturesManager.DB_UID = "FID"; + offlineEdit.DB_UID = "FID"; // We want the library to automatically create the offline featureCollection for each layer - offlineFeaturesManager.ENABLE_FEATURECOLLECTION = true; + offlineEdit.ENABLE_FEATURECOLLECTION = true; // IMPORTANT!!! // A proxy page may be required to upload attachments. // If you are using a CORS enabled feature service you can ignore this. // If your feature service is not CORS-enabled then you will need to configure this. // Refer to "Using the Proxy Page" for more information: https://developers.arcgis.com/en/javascript/jshelp/ags_proxy.html - offlineFeaturesManager.proxyPath = null; + offlineEdit.proxyPath = null; } /** @@ -650,7 +650,7 @@ * Toggles offlineFeaturesManager online/offline */ function goOnlineOffline() { - if(offlineFeaturesManager.getOnlineStatus() == offlineFeaturesManager.ONLINE){ + if(offlineEdit.getOnlineStatus() == offlineEdit.ONLINE){ goOffline(); } else{ diff --git a/samples/attachments-editor-secure.html b/samples/attachments-editor-secure.html index 38e3d578..a976a1e5 100644 --- a/samples/attachments-editor-secure.html +++ b/samples/attachments-editor-secure.html @@ -100,15 +100,15 @@ // window.proxyPath = null; //can be set to null when using a CORS-enabled server // esriConfig.defaults.io.proxyUrl = null; //can be set to null when using a CORS-enabled server - var offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManagerAdvanced(); + var offlineEdit = new O.esri.Edit.OfflineEditAdvanced(); // IMPORTANT!!! // A proxy page may be required to upload attachments. // If you are using a CORS enabled server you may be ablew to set the proxyPath to null. // Refer to "Using the Proxy Page" for more information: https://developers.arcgis.com/en/javascript/jshelp/ags_proxy.html - offlineFeaturesManager.proxyPath = null; + offlineEdit.proxyPath = null; - offlineFeaturesManager.initAttachments(function(success) + offlineEdit.initAttachments(function(success) { attachmentsInited = success; updateStatus(); @@ -127,8 +127,8 @@ map.on("delete-attachments-complete", updateStatus); map.on("query-attachment-infos-complete", updateStatus); - offlineFeaturesManager.on(offlineFeaturesManager.events.ATTACHMENT_ENQUEUED, updateStatus); - offlineFeaturesManager.on(offlineFeaturesManager.events.ATTACHMENTS_SENT, updateStatus); + offlineEdit.on(offlineEdit.events.ATTACHMENT_ENQUEUED, updateStatus); + offlineEdit.on(offlineEdit.events.ATTACHMENTS_SENT, updateStatus); function mapLoaded() { @@ -178,7 +178,7 @@ { var layer = evt.layer; - offlineFeaturesManager.extend(featureLayer,function(success, error){ + offlineEdit.extend(featureLayer,function(success, error){ if(success == false) alert("There was a problem initiating the database. " + error); }); } @@ -191,7 +191,7 @@ function goOnline() { - offlineFeaturesManager.goOnline(function() + offlineEdit.goOnline(function() { updateConnectivityIndicator(); updateStatus(); @@ -201,13 +201,13 @@ function goOffline() { - offlineFeaturesManager.goOffline(); + offlineEdit.goOffline(); updateConnectivityIndicator(); } function clearLocalAttachments() { - offlineFeaturesManager.attachmentsStore.deleteAll(function(success) + offlineEdit.attachmentsStore.deleteAll(function(success) { updateStatus(); }); @@ -230,7 +230,7 @@ return; domConstruct.empty('local-attachments-list'); - offlineFeaturesManager.attachmentsStore.getAllAttachments(function(attachments) + offlineEdit.attachmentsStore.getAllAttachments(function(attachments) { var li; if( attachments.length ) @@ -257,17 +257,17 @@ { var node = dom.byId('connectivity-indicator'); domClass.remove(node, "online offline reconnecting"); - switch( offlineFeaturesManager.getOnlineStatus() ) + switch( offlineEdit.getOnlineStatus() ) { - case offlineFeaturesManager.OFFLINE: + case offlineEdit.OFFLINE: node.innerHTML = " offline"; domClass.add(node, "offline"); break; - case offlineFeaturesManager.ONLINE: + case offlineEdit.ONLINE: node.innerHTML = " online"; domClass.add(node, "online"); break; - case offlineFeaturesManager.RECONNECTING: + case offlineEdit.RECONNECTING: node.innerHTML = " reconnecting"; domClass.add(node, "reconnecting"); break; @@ -293,7 +293,7 @@ if(!attachmentsInited) return; - offlineFeaturesManager.attachmentsStore.getUsage(function(usage) + offlineEdit.attachmentsStore.getUsage(function(usage) { var str = "attachment" + ((usage.attachmentCount === 1)? "" : "s"); var info = usage.attachmentCount + " " + str + " (" + formatSize( usage.sizeBytes) + ")"; diff --git a/samples/attachments-editor.html b/samples/attachments-editor.html index 6c6172ec..7a2b4c90 100644 --- a/samples/attachments-editor.html +++ b/samples/attachments-editor.html @@ -80,15 +80,15 @@ // window.proxyPath = null; //can be set to null when using a CORS-enabled server // esriConfig.defaults.io.proxyUrl = null; //can be set to null when using a CORS-enabled server - var offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManagerAdvanced(); + var offlineEdit = new O.esri.Edit.OfflineEditAdvanced(); // IMPORTANT!!! // A proxy page may be required to upload attachments. // If you are using a CORS enabled server you may be ablew to set the proxyPath to null. // Refer to "Using the Proxy Page" for more information: https://developers.arcgis.com/en/javascript/jshelp/ags_proxy.html - offlineFeaturesManager.proxyPath = null; + offlineEdit.proxyPath = null; - offlineFeaturesManager.initAttachments(function(success) + offlineEdit.initAttachments(function(success) { attachmentsInited = success; updateStatus(); @@ -107,8 +107,8 @@ map.on("delete-attachments-complete", updateStatus); map.on("query-attachment-infos-complete", updateStatus); - offlineFeaturesManager.on(offlineFeaturesManager.events.ATTACHMENT_ENQUEUED, updateStatus); - offlineFeaturesManager.on(offlineFeaturesManager.events.ATTACHMENTS_SENT, updateStatus); + offlineEdit.on(offlineEdit.events.ATTACHMENT_ENQUEUED, updateStatus); + offlineEdit.on(offlineEdit.events.ATTACHMENTS_SENT, updateStatus); function mapLoaded() { @@ -158,7 +158,7 @@ { var layer = evt.layer; - offlineFeaturesManager.extend(featureLayer,function(success, error){ + offlineEdit.extend(featureLayer,function(success, error){ if(success == false) alert("There was a problem initiating the database. " + error); }); } @@ -171,7 +171,7 @@ function goOnline() { - offlineFeaturesManager.goOnline(function() + offlineEdit.goOnline(function() { updateConnectivityIndicator(); updateStatus(); @@ -181,13 +181,13 @@ function goOffline() { - offlineFeaturesManager.goOffline(); + offlineEdit.goOffline(); updateConnectivityIndicator(); } function clearLocalAttachments() { - offlineFeaturesManager.attachmentsStore.deleteAll(function(success) + offlineEdit.attachmentsStore.deleteAll(function(success) { updateStatus(); }); @@ -210,7 +210,7 @@ return; domConstruct.empty('local-attachments-list'); - offlineFeaturesManager.attachmentsStore.getAllAttachments(function(attachments) + offlineEdit.attachmentsStore.getAllAttachments(function(attachments) { var li; if( attachments.length ) @@ -237,17 +237,17 @@ { var node = dom.byId('connectivity-indicator'); domClass.remove(node, "online offline reconnecting"); - switch( offlineFeaturesManager.getOnlineStatus() ) + switch( offlineEdit.getOnlineStatus() ) { - case offlineFeaturesManager.OFFLINE: + case offlineEdit.OFFLINE: node.innerHTML = " offline"; domClass.add(node, "offline"); break; - case offlineFeaturesManager.ONLINE: + case offlineEdit.ONLINE: node.innerHTML = " online"; domClass.add(node, "online"); break; - case offlineFeaturesManager.RECONNECTING: + case offlineEdit.RECONNECTING: node.innerHTML = " reconnecting"; domClass.add(node, "reconnecting"); break; @@ -273,7 +273,7 @@ if(!attachmentsInited) return; - offlineFeaturesManager.attachmentsStore.getUsage(function(usage) + offlineEdit.attachmentsStore.getUsage(function(usage) { var str = "attachment" + ((usage.attachmentCount === 1)? "" : "s"); var info = usage.attachmentCount + " " + str + " (" + formatSize( usage.sizeBytes) + ")"; diff --git a/samples/draw-pointlinepoly-offline.html b/samples/draw-pointlinepoly-offline.html index 7106b8d0..1eeb816e 100644 --- a/samples/draw-pointlinepoly-offline.html +++ b/samples/draw-pointlinepoly-offline.html @@ -148,13 +148,13 @@ on(dom.byId('go-offline-btn'),'click', goOffline); on(dom.byId('go-online-btn'),'click', goOnline); on(dom.byId('refresh-feature-layers-btn'),'click', refreshFeatureLayers); - var offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManagerBasic(); + var offlineEdit = new O.esri.Edit.OfflineEditBasic(); // IMPORTANT!!! // A proxy page may be required to upload attachments. // If you are using a CORS enabled server you may be ablew to set the proxyPath to null. // Refer to "Using the Proxy Page" for more information: https://developers.arcgis.com/en/javascript/jshelp/ags_proxy.html - offlineFeaturesManager.proxyPath = null; + offlineEdit.proxyPath = null; map = new Map("map", { basemap: "satellite", @@ -247,7 +247,7 @@ var layer = result.layer; offlineInitializedLayers.push(layer); - offlineFeaturesManager.extend(layer,function(success,error){ + offlineEdit.extend(layer,function(success,error){ if(success){ deferred.resolve(true); } @@ -277,15 +277,15 @@ // Things to do when all the promises complete function resolveOfflinePromises(){ - offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_ENQUEUED, updateStatus); - offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_SENT, updateStatusAllEditsSent); + offlineEdit.on(offlineEdit.events.EDITS_ENQUEUED, updateStatus); + offlineEdit.on(offlineEdit.events.EDITS_SENT, updateStatusAllEditsSent); offlineInitializedLayers[0].on("edits-complete", handleEditsComplete); offlineInitializedLayers[1].on("edits-complete", handleEditsComplete); offlineInitializedLayers[2].on("edits-complete", handleEditsComplete); // handle errors that happen while storing offline edits - offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_ENQUEUED_ERROR, function(errors) + offlineEdit.on(offlineEdit.events.EDITS_ENQUEUED_ERROR, function(errors) { if( errors.length ) { @@ -428,7 +428,7 @@ function goOnline() { domUtils.show(editor.progressBar.domNode); - offlineFeaturesManager.goOnline(function() + offlineEdit.goOnline(function() { domUtils.hide(editor.progressBar.domNode); updateConnectivityIndicator(); @@ -438,7 +438,7 @@ function goOffline() { - offlineFeaturesManager.goOffline(); + offlineEdit.goOffline(); updateConnectivityIndicator(); } @@ -446,17 +446,17 @@ { var node = dom.byId('connectivityIndicator'); domClass.remove(node, "online offline reconnecting"); - switch( offlineFeaturesManager.getOnlineStatus() ) + switch( offlineEdit.getOnlineStatus() ) { - case offlineFeaturesManager.OFFLINE: + case offlineEdit.OFFLINE: node.innerHTML = " offline"; domClass.add(node, "offline"); break; - case offlineFeaturesManager.ONLINE: + case offlineEdit.ONLINE: node.innerHTML = " online"; domClass.add(node, "online"); break; - case offlineFeaturesManager.RECONNECTING: + case offlineEdit.RECONNECTING: node.innerHTML = " reconnecting"; domClass.add(node, "reconnecting"); break; diff --git a/samples/simple-edit.html b/samples/simple-edit.html index 786a82d7..0f75a5b4 100644 --- a/samples/simple-edit.html +++ b/samples/simple-edit.html @@ -59,7 +59,7 @@ // refer to "Using the Proxy Page" for more information: https://developers.arcgis.com/javascript/jshelp/ags_proxy.html esriConfig.defaults.io.proxyUrl = "/proxy/"; - var offlineFeatureLayer = new O.esri.Edit.OfflineFeaturesManagerBasic(); + var offlineEdit = new O.esri.Edit.OfflineEditBasic(); map = new Map("map", { @@ -79,7 +79,7 @@ function initEditing(evt) { - offlineFeatureLayer.extend(firePerimeterFL,function(success,error){ + offlineEdit.extend(firePerimeterFL,function(success,error){ if(success){ var editToolbar = new Edit(map); editToolbar.on("deactivate", function(evt) { From 02d2d2310166b8bdef6824b669dc7b2113b8e099 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Mon, 23 Nov 2015 10:31:34 -0700 Subject: [PATCH 21/48] remove deprecated function --- lib/edit/editsStore.js | 100 ------------------------ test/spec/offlineFeaturesManagerSpec.js | 52 ------------ 2 files changed, 152 deletions(-) diff --git a/lib/edit/editsStore.js b/lib/edit/editsStore.js index 9e0d0227..dc68d415 100644 --- a/lib/edit/editsStore.js +++ b/lib/edit/editsStore.js @@ -430,76 +430,6 @@ O.esri.Edit.EditStore = function () { }); }; - /** - * DEPRECATED at v2.11 - * TO-DO remove in the next release - * - * Removes some phantom graphics from database. - * The responseObject contains {id,layer,tempId,addResults,updateResults,deleteResults}. - * IF there are no results.success then nothing will be deleted. - * - * WARNING: Can generate false positives. IndexedDB will always return success - * even if you attempt to delete a non-existent id. - * - * CAUTION: This should always be used in conjunction with deleting the phantom graphic's - * associated edit entry in the database. - * - * @param responseObject - * @param callback boolean - */ - this.resetLimitedPhantomGraphicsQueue = function (responseObject, callback) { - - if (Object.keys(responseObject).length > 0) { - var db = this._db; - - var errors = 0; - var tx = db.transaction([this.objectStoreName], "readwrite"); - var objectStore = tx.objectStore(this.objectStoreName); - - objectStore.onerror = function () { - errors++; - console.log("PHANTOM GRAPHIC ERROR"); - }; - - tx.oncomplete = function () { - errors === 0 ? callback(true) : callback(false); - }; - - for (var key in responseObject) { - if (responseObject.hasOwnProperty(key)) { - var edit = responseObject[key]; - var id = this.PHANTOM_GRAPHIC_PREFIX + this._PHANTOM_PREFIX_TOKEN + edit.id; - - // CAUTION: - // TO-DO we do NOT match the edit.id with edit's objectId - - // If we have an add, update or delete success then delete the entry, otherwise we skip it. - if(edit.updateResults.length > 0){ - if (edit.updateResults[0].success){ - objectStore.delete(id); - } - } - - if(edit.deleteResults.length > 0){ - if (edit.deleteResults[0].success){ - objectStore.delete(id); - } - } - - if(edit.addResults.length > 0){ - if (edit.addResults[0].success){ - objectStore.delete(id); - } - } - } - } - } - else { - callback(true); - } - }; - - /** * Removes all phantom graphics from database * @param callback boolean @@ -945,36 +875,6 @@ O.esri.Edit.EditStore = function () { }; }; - /** - * Save space in the database...don't need to store the entire Graphic object just its public properties! - * @param graphic - * @returns {*} - * @private - */ - this._serialize = function (graphic) { - // see http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#/Apply_Edits_Feature_Service_Layer/02r3000000r6000000/ - // use graphic's built-in serializing method - var json = graphic.toJson(); - var jsonClean = - { - attributes: json.attributes, - geometry: json.geometry, - infoTemplate: json.infoTemplate, - symbol: json.symbol - }; - return JSON.stringify(jsonClean); - }; - - this._deserialize = function (json) { - var graphic; - - require(["esri/graphic"], function (Graphic) { - graphic = new Graphic(JSON.parse(json)); - }); - - return graphic; - }; - this.init = function (callback) { console.log("init editsStore.js"); diff --git a/test/spec/offlineFeaturesManagerSpec.js b/test/spec/offlineFeaturesManagerSpec.js index 84d6d74d..e3c0d336 100644 --- a/test/spec/offlineFeaturesManagerSpec.js +++ b/test/spec/offlineFeaturesManagerSpec.js @@ -923,58 +923,6 @@ describe("Offline Editing", function() }) }); - //async.it("delete temporary feature", function(done){ - // g_featureLayers[0]._deleteTemporaryFeature(g6,function(results){ - // expect(results[0]).toBe(true); - // expect(results[1]).toBe(true); - // done(); - // }); - //}); - - async.it("Delete one of the phantom graphics using resetLimitedPhantomGraphicsQueue()", function(done){ - var responseObject = { - 0:{ - "id":-1, - "updateResults":[ - { - "success":false - } - ], - "addResults":[ - { - "success":true - } - ], - "deleteResults":[ - { - "success":false - } - ] - } - }; - - g_editsStore.resetLimitedPhantomGraphicsQueue(responseObject,function(success){ - expect(success).toBe(true); - done(); - }); - }); - - async.it("Reverify total number of phantom graphics", function(done){ - g_editsStore._getPhantomGraphicsArraySimple(function(results,errors){ - - // We remove one graphic from the previous result and should now be @ 9 - expect(results.length).toBe(6); - expect(results[0]).toBe("phantom-layer|@|-3"); - expect((results[1]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0); - expect((results[2]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0); - expect((results[3]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0); - expect(results[4]).toBe("phantom-layer|@|test002"); - expect(results[5]).toBe("phantom-layer|@|test003"); - expect(errors).toBe("end"); - done(); - }) - }); - async.it("Delete all PhantomLayerGraphics", function(done){ g_editsStore.resetPhantomGraphicsQueue(function(result){ From c739703a1043e641932a486c1074afa32d519560 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Mon, 23 Nov 2015 11:35:35 -0700 Subject: [PATCH 22/48] refactor old names to new --- demo/api-doc.html | 6 +- doc/attachments.md | 20 ++--- doc/howtouseofmadvancedlibrary.md | 86 +++++++++---------- doc/howtouseofmbasic.md | 52 +++++------ doc/offlinefeaturesmanageradvanced.md | 24 +++--- doc/offlinefeaturesmanagerbasic.md | 22 ++--- lib/edit/OfflineEditAdvanced.js | 20 ++--- lib/edit/OfflineEditBasic.js | 8 +- lib/edit/editStorePOLS.js | 4 +- lib/edit/editsStore.js | 4 +- samples/appcache-twofeatureslayer-noedit.html | 10 +-- test/SpecRunner.offlineAttachments.html | 14 +-- ...ner.offlineFeaturesManager.TokenBased.html | 23 +++-- ...ecRunner.offlineFeaturesManager.oAuth.html | 23 +++-- ...Runner.offlineFeaturesManagerAdvanced.html | 23 +++-- ...pecRunner.offlineFeaturesManagerBasic.html | 25 ++---- test/spec/offlineAttachmentsSpec.js | 70 +++++++-------- ...rSpec.js => offlineEditingAdvancedSpec.js} | 44 +++++----- ...ightSpec.js => offlineEditingBasicSpec.js} | 24 +++--- 19 files changed, 244 insertions(+), 258 deletions(-) rename test/spec/{offlineFeaturesManagerSpec.js => offlineEditingAdvancedSpec.js} (96%) rename test/spec/{offlineEditingLightSpec.js => offlineEditingBasicSpec.js} (96%) diff --git a/demo/api-doc.html b/demo/api-doc.html index dfea6b5d..2503c66d 100644 --- a/demo/api-doc.html +++ b/demo/api-doc.html @@ -78,7 +78,7 @@

Offline Editing - Basic

  • - O.esri.Edit.OfflineFeaturesManagerBasic + O.esri.Edit.OfflineEditBasic
  • How to use. @@ -96,7 +96,7 @@

    Offline Editing - Advanced

    • - O.esri.Edit.OfflineFeaturesManagerAdvanced + O.esri.Edit.OfflineEditAdvanced
    • How to use. @@ -152,7 +152,7 @@

      Offline Attachments

      • - O.esri.Edit.OfflineFeaturesManagerAdvanced + O.esri.Edit.OfflineEditAdvanced
      • How to use. diff --git a/doc/attachments.md b/doc/attachments.md index 7c2f3953..57495a12 100644 --- a/doc/attachments.md +++ b/doc/attachments.md @@ -15,14 +15,14 @@ You can either use the ArcGIS FeatureLayer API _(esri.layers.FeatureLayer)_ dire The only differences in your code are: -* create an offlineFeaturesManager enabled for attachment support. Make sure you initialize the attachments database: +* create an OfflineEditAdvanced instance that is enabled for attachment support. Make sure you initialize the attachments database: - var offlineFeaturesManager = new esri.OfflineFeaturesManager(); - offlineFeaturesManager.initAttachments(); + var offlineEdit = new O.esri.Edit.OfflineEditAdvanced(); + offlineEdit.initAttachments(); * extend your featureLayers with offline editing functionality: - offlineFeaturesManager.extend(featureLayer, function(success, error) + offlineEdit.extend(featureLayer, function(success, error) { console.log("layer extended", success? "success" : "failed"); }); @@ -30,14 +30,14 @@ The only differences in your code are: You can also modified the database's name and object store name. This functionality is typically used for advanced users that have a requirement to run multiple databases: - var offlineFeaturesManager = new esri.OfflineFeaturesManager(); - offlineFeaturesManager.ATTACHMENTS_DB_NAME = "attachment-store-two"; - offlineFeaturesManager.ATTACHMENTS_DB_OBJECTSTORE_NAME = "attachments-two"; + var offlineEdit = new O.esri.Edit.OfflineEditAdvanced(); + offlineEdit.ATTACHMENTS_DB_NAME = "attachment-store-two"; + offlineEdit.ATTACHMENTS_DB_OBJECTSTORE_NAME = "attachments-two"; - offlineFeaturesManager.initAttachments(); + offlineEdit.initAttachments(); ###Using the FeatureLayer API -The FeatureLayer API for handling attachments consists primarily of four methods. In general you should let `OfflineFeaturesManager` +The FeatureLayer API for handling attachments consists primarily of four methods. In general you should let `OfflineEditAdvanced` handle interactions with attachments and it's not recommended to interact with the attachments database directly. * `layer.queryAttachmentInfos(objectId,callback,errback)` [doc](https://developers.arcgis.com/javascript/jsapi/featurelayer.html#queryattachmentinfos) @@ -45,7 +45,7 @@ handle interactions with attachments and it's not recommended to interact with t * `layer.updateAttachment(objectId, attachmentId, formNode, callback, errback)` - as of April 2015 the ArcGIS API for JavaScript document has this functionality but it's not documented. That should hopefully be fixed in the next release of the JS API. * `layer.deleteAttachments(objectId, attachmentIds, callback, errback)` [doc](https://developers.arcgis.com/javascript/jsapi/featurelayer.html#deleteattachments) -They work the same both in ONLINE and OFFLINE mode. In OFFLINE mode, attachments will be kept in the local database (indexeddb) and sent back to the server when you call `offlineFeaturesManager.goOnline()` +They work the same both in ONLINE and OFFLINE mode. In OFFLINE mode, attachments will be kept in the local database (indexeddb) and sent back to the server when you call `offlineEdit.goOnline()` ##Getting database usage Once a feature layer is extended you can find out how big the database and how many attachments are stored by using the following pattern: diff --git a/doc/howtouseofmadvancedlibrary.md b/doc/howtouseofmadvancedlibrary.md index 8f4c981f..caa7057c 100644 --- a/doc/howtouseofmadvancedlibrary.md +++ b/doc/howtouseofmadvancedlibrary.md @@ -1,11 +1,11 @@ How to use the advanced edit library ==================================== -##`OfflineFeaturesManagerAdvanced` library +##`OfflineEditAdvanced` library -This library allows a developer to extend a feature layer with intermittent and full offline editing support. You can combine this functionality with offline tiles. For a complete list of features consult the [OfflineFeaturesManagerAdvanced API doc](offlinefeaturesmanageradvanced.md). +This library allows a developer to extend a feature layer with intermittent and full offline editing support. You can combine this functionality with offline tiles. For a complete list of features consult the [OfflineEditAdvanced API doc](offlinefeaturesmanageradvanced.md). -**IMPORTANT:** Only use a single instance of OfflineFeaturesManagerAdvanced per application. With this single instance you can extend offline capabilities to multiple feature layers. This single instance contains all edits for all feature layers initialized via `offlineFeaturesManagerAdvanced.extend().` Multiple feature layers share a single database. The database maintains the relationship between each edit and its' respective feature layer via a UUID. +**IMPORTANT:** Only use a single instance of OfflineEditAdvanced per application. With this single instance you can extend offline capabilities to multiple feature layers. This single instance contains all edits for all feature layers initialized via `OfflineEditAdvanced.extend().` Multiple feature layers share a single database. The database maintains the relationship between each edit and its' respective feature layer via a UUID. **Step 1** Include `offline.min.js`, `offline-tiles-basic-min.js` and `offline-edit-advanced-min.js` in your app's require contstructor. Be sure to include `ofline.mins.js` which is a 3rd party library for detecting if the browser is online or offline. @@ -34,19 +34,19 @@ You can also refer to the offline-editor-js within a `define` statement using th ``` -**Step 2** Once your map is created (either using new Map() or using esriUtils.createMap(webmapid,...), you create a new OfflineFeaturesManagerAdvanced instance and starting assigning events listeners to tie the library into your user interface: +**Step 2** Once your map is created (either using new Map() or using esriUtils.createMap(webmapid,...), you create a new OfflineEditAdvanced instance and starting assigning events listeners to tie the library into your user interface: ```js - var offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManagerAdvanced(); + var offlineEdit = new O.esri.Edit.OfflineEditAdvanced(); // OPTIONAL - you can change the name of the database - // offlineFeaturesManager.DBNAME = "FIELD_SURVEY3"; + // OfflineEdit.DBNAME = "FIELD_SURVEY3"; // OPTIONAL - you can change the name of the unique identifier used by the feature service. Default is "objectid". - // offlineFeaturesManager.UID = "GlobalID"; - offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_ENQUEUED, updateStatus); + // offlineEdit.UID = "GlobalID"; + offlineEdit.on(offlineEdit.events.EDITS_ENQUEUED, updateStatus); updateStatus); - offlineFeaturesManager.on(offlineFeaturesManager.events.ALL_EDITS_SENT, updateStatus); - offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_SENT_ERROR, handleEditsSentError); + offlineEdit.on(offlineEdit.events.ALL_EDITS_SENT, updateStatus); + offlineEdit.on(offlineEdit.events.EDITS_SENT_ERROR, handleEditsSentError); ``` @@ -54,7 +54,7 @@ NOTE: You can also monitor standard ArcGIS API for JavaScript layer events using ```js - offlineFeatureLayer.on("edits-complete", handleEditsComplete); + offlineEdit.on("edits-complete", handleEditsComplete); ``` @@ -93,7 +93,7 @@ Here is an example of initializing the library for partial offline use. Note tha function initEditor(evt) { - offlineFeaturesManager.extend(layer1,function(success, error){ + offlineEdit.extend(layer1,function(success, error){ if(success){ console.log("Layer has been extended for offline use."); } @@ -113,7 +113,7 @@ For full offline use, the pattern would look like this where we are creating a ` dataStore.featureLayerJSON = layer1.toJson(); dataStore.zoom = map.getZoom(); - offlineFeaturesManager.extend(layer1,function(success, error){ + offlineEdit.extend(layer1,function(success, error){ if(success){ console.log("Layer has been extended for offline use."); } @@ -129,13 +129,13 @@ The workflow for this coding pattern is you start out online > offline > browser ```js - offlineFeaturesManager.extend(layer1, function(success, error) { + offlineEdit.extend(layer1, function(success, error) { if(success) { - // If the app is online then force offlineFeaturesManager to its online state + // If the app is online then force offlineEdit to its online state // This will force the library to check for pending edits and attempt to // resend them to the Feature Service. if(_isOnline){ // Check if app is online or offline - offlineFeaturesManager.goOnline(function(result){ + offlineEdit.goOnline(function(result){ if(!result.success){ alert("There was a problem when attempting to go back online."); } @@ -145,7 +145,7 @@ The workflow for this coding pattern is you start out online > offline > browser }); } else { - offlineFeaturesManager.goOffline(); + offlineEdit.goOffline(); } } }); @@ -157,7 +157,7 @@ There are two approaches to using the dataStore: * **Approach 1** involves you manually creating the dataStore for greater control over what goes into the Data Store Object and then inserting that Object into the offlineFeatureManager's constructor. -* **Approach 2**, you can let the library manage it automatically upon an ADD, UPDATE or DELETE. This is accomplished by not inserting a manual Data Store Object into offlineFeatureManager constructor and instead setting offlineFeaturesManager.ENABLE_FEATURECOLLECTION = true. +* **Approach 2**, you can let the library manage it automatically upon an ADD, UPDATE or DELETE. This is accomplished by not inserting a manual Data Store Object into OfflineEditAdvanced constructor and instead setting OfflineEditAdvanced.ENABLE_FEATURECOLLECTION = true. #### Approach 1 - manually create dataStore @@ -184,7 +184,7 @@ Here's one approach for using a recursive function for loading the feature layer var count = 0; function extendFeatureLayers(){ if(count <= featureLayerArray.length){ - offlineFeaturesManager.extend(featureLayerArray[count], + offlineEdit.extend(featureLayerArray[count], function(success, error){ if(success){ count++; @@ -208,7 +208,7 @@ You can then retrieve this data after an offline restart by using the following ```js - offlineFeaturesManager.getFeatureLayerJSONDataStore(function(success, dataStore){ + offlineEdit.getFeatureLayerJSONDataStore(function(success, dataStore){ if(success){ myFeatureLayer = new FeatureLayer(dataStore.featureLayerJSON,{ @@ -216,7 +216,7 @@ You can then retrieve this data after an offline restart by using the following outFields: ["GlobalID","BSID","ROUTES","STOPNAME"] }); - offlineFeaturesManager.extend(myFeatureLayer,function(result, error) { + offlineEdit.extend(myFeatureLayer,function(result, error) { if(result) { console.log("Layer has been successfully rebuilt while offline!"); } @@ -238,9 +238,9 @@ Once you set `ENABLED_FEATURECOLLECTION` to `true` the library will automaticall // Tell the library to automatically create and store a snapshot of the // of the feature layer. - offlineFeaturesManager.ENABLE_FEATURECOLLECTION = true + offlineEdit.ENABLE_FEATURECOLLECTION = true - offlineFeaturesManager.extend(layer1,function(success, error){ + offlineEdit.extend(layer1,function(success, error){ if(success){ console.log("layer1 has been extended for offline use."); } @@ -252,7 +252,7 @@ Now you can use this pattern to reconstitute the layer after an offline browser ```js - offlinefeaturesManager.getFeatureCollections(function(success, collection) { + offlineEdit.getFeatureCollections(function(success, collection) { if(success) { myFeatureLayer = new FeatureLayer(collection.featureCollections[0].featureLayerCollection),{ @@ -260,7 +260,7 @@ Now you can use this pattern to reconstitute the layer after an offline browser outFields: ["GlobalID","BSID","ROUTES","STOPNAME"] }); - offlineFeaturesManager.extend(myFeatureLayer,function(result, error) { + offlineEdit.extend(myFeatureLayer,function(result, error) { if(result) { console.log("Layer has been successfully rebuilt while offline!"); } @@ -287,41 +287,41 @@ Here is an example of the Object returned in the `getFeatureCollections()` callb There are two ways to get the dataStore. You can get it from the instance of Offline Features Manager or from the feature layer, itself: -* `offlineFeaturesManager.getFeatureLayerJSONDataStore( callback )` +* `offlineEdit.getFeatureLayerJSONDataStore( callback )` * `featureLayer.getFeatureLayerJSONDataStore(callback)` **Step 5** Once a layer has been extended the offline library will enable it with new methods. Here are a few examples that include code snippets of how to take advantage of some of the library's methods. -####OfflineFeaturesManagerAdvanced.proxyPath +####OfflineEditAdvanced.proxyPath By default, the library assumes you are using a CORS-enabled Feature Service. All ArcGIS Online Feature Services are CORS-enabled. If you are hosting your own service and it is not CORS-enabled, then you will need to set this path. More information on downloading and using ArcGIS proxies can be found here: [https://developers.arcgis.com/en/javascript/jshelp/ags_proxy.html](https://developers.arcgis.com/en/javascript/jshelp/ags_proxy.html) Here's one example: ```js - offlineFeaturesManager.proxyPath = "../your-local-proxy-directory/proxy.php"; + offlineEdit.proxyPath = "../your-local-proxy-directory/proxy.php"; ``` -####OfflineFeaturesManagerAdvanced.goOffline() +####OfflineEditAdvanced.goOffline() Force the library to go offline. Once this condition is set, then any offline edits will be cached locally. ```js function goOffline() { - offlineFeaturesManager.goOffline() }); + offlineEdit.goOffline() }); //TO-DO } ``` -####OfflineFeaturesManagerAdvanced.goOnline() +####OfflineEditAdvanced.goOnline() Force the library to return to an online condition. If there are pending edits, the library will attempt to sync them. ```js function goOnline() { - offlineFeaturesManager.goOnline(function(result) + offlineEdit.goOnline(function(result) { if(result.success){ //Modify user inteface depending on success/failure @@ -352,22 +352,22 @@ Typically you should only need to call this method once for each online/offline If there was a an failure and/or errors, it's a good idea to reevaluate the edits that remain in the database because some edits may have been synced and others may still be pending. Only then, and depending on the error message, should the app try to `goOnline()` again. -####OfflineFeaturesManagerAdvanced.getOnlineStatus() +####OfflineEditAdvanced.getOnlineStatus() Within your application you can manually check online status and then update your user interface. By using a switch/case statement you can check against three enums that indicate if the library thinks it is offline, online or in the process of reconnecting. ```js - switch( offlineFeaturesManager.getOnlineStatus() ) + switch( offlineEdit.getOnlineStatus() ) { - case offlineFeaturesManager.OFFLINE: + case offlineEdit.OFFLINE: node.innerHTML = " offline"; domClass.add(node, "offline"); break; - case offlineFeaturesManager.ONLINE: + case offlineEdit.ONLINE: node.innerHTML = " online"; domClass.add(node, "online"); break; - case offlineFeaturesManager.RECONNECTING: + case offlineEdit.RECONNECTING: node.innerHTML = " reconnecting"; domClass.add(node, "reconnecting"); break; @@ -375,18 +375,18 @@ Within your application you can manually check online status and then update you ``` -####OfflineFeaturesManagerAdvanced.pendingEditsCount(callback) +####OfflineEditAdvanced.pendingEditsCount(callback) You can check if there are any edits pending. ```js // Simply get a count - offlineFeaturesManager.pendingEditsCount(function(count){ + offlineEdit.pendingEditsCount(function(count){ console.log("There are " + count + " edits pending"); }) // Or retrieve all pending edits - offlineFeaturesManager.getAllEditsArray(function(success,editsArray){ + offlineEdit.getAllEditsArray(function(success,editsArray){ if(success && editsArray.length > 0){ editsArray.forEach(function(edit){ console.log("Pending edit: " + JSON.stringify(edit)); @@ -404,10 +404,10 @@ You can run the reset code seperately or you can run the app with this pattern. ```js -offlineFeaturesManager.extend(myFeatureLayer,function(result, error) { +offlineEdit.extend(myFeatureLayer,function(result, error) { if(result) { - console.log("offlineFeaturesManager initialized."); - offlineFeaturesManager.resetDatabase(function(success,error){ + console.log("OfflineEditAdvanced initialized."); + offlineEdit.resetDatabase(function(success,error){ console.log("DATABASE DELETED"); }); . . . diff --git a/doc/howtouseofmbasic.md b/doc/howtouseofmbasic.md index 877f6794..947b74c1 100644 --- a/doc/howtouseofmbasic.md +++ b/doc/howtouseofmbasic.md @@ -1,11 +1,11 @@ How to use the basic edit library ==================================== -##`OfflineFeaturesManagerBasic` library +##`OfflineEditBasic` library -This library allows a developer to extend a feature layer with intermittent offline editing support. You can combine this functionality with offline tiles. For a complete list of features consult the [OfflineFeaturesManagerBasic API doc](offlinefeaturesmanagerbasic.md). +This library allows a developer to extend a feature layer with intermittent offline editing support. You can combine this functionality with offline tiles. For a complete list of features consult the [OfflineEditBasic API doc](offlinefeaturesmanagerbasic.md). -**IMPORTANT:** Only use a single instance of OfflineFeaturesManagerBasic per application. With this single instance you can extend offline capabilities to multiple feature layers. This single instance contains all edits for all feature layers initialized via `OfflineFeaturesManagerBasic.extend().` Multiple feature layers share a single database. The database maintains the relationship between each edit and its' respective feature layer via a UUID. +**IMPORTANT:** Only use a single instance of OfflineEditBasic per application. With this single instance you can extend offline capabilities to multiple feature layers. This single instance contains all edits for all feature layers initialized via `OfflineEditBasic.extend().` Multiple feature layers share a single database. The database maintains the relationship between each edit and its' respective feature layer via a UUID. **Step 1** Include `offline.min.js`, `offline-tiles-basic-min.js` and `offline-edit-min.js` in your app's require contstructor. Be sure to include `ofline.mins.js` which is a 3rd party library for detecting if the browser is online or offline. @@ -34,18 +34,18 @@ You can also refer to the offline-editor-js within a `define` statement using th ``` -**Step 2** Once your map is created (either using `new Map()` or using `esriUtils.createMap(webmapid,...)`, you create a new OfflineFeaturesManagerBasic instance and starting assigning events listeners to tie the library into your user interface: +**Step 2** Once your map is created (either using `new Map()` or using `esriUtils.createMap(webmapid,...)`, you create a new OfflineEditBasic instance and starting assigning events listeners to tie the library into your user interface: ```js - var offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManagerBasic(); + var offlineEdit = new O.esri.Edit.OfflineEditBasic(); // OPTIONAL - you can change the name of the database - // offlineFeaturesManager.DBNAME = "FIELD_SURVEY3"; + // offlineEdit.DBNAME = "FIELD_SURVEY3"; // OPTIONAL - you can change the name of the unique identifier used by the feature service. Default is "objectid". - // offlineFeaturesManager.UID = "GlobalID"; - offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_ENQUEUED, updateStatus); + // offlineEdit.UID = "GlobalID"; + offlineEdit.on(offlineEdit.events.EDITS_ENQUEUED, updateStatus); updateStatus); - offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_SENT, updateStatus); + offlineEdit.on(offlineEdit.events.EDITS_SENT, updateStatus); ``` @@ -80,35 +80,35 @@ NOTE: You can also monitor standard ArcGIS API for JavaScript layer events using **Step 4** Once a layer has been extended the offline library will enable it with new methods. Here are a few examples that include code snippets of how to take advantage of some of the library's methods. -####OfflineFeaturesManagerBasic.proxyPath +####OfflineEditBasic.proxyPath By default, the library assumes you are using a CORS-enabled Feature Service. All ArcGIS Online Feature Services are CORS-enabled. If you are hosting your own service and it is not CORS-enabled, then you will need to set this path. More information on downloading and using ArcGIS proxies can be found here: [https://developers.arcgis.com/en/javascript/jshelp/ags_proxy.html](https://developers.arcgis.com/en/javascript/jshelp/ags_proxy.html) Here's one example: ```js - offlineFeaturesManager.proxyPath = "../your-local-proxy-directory/proxy.php"; + offlineEdit.proxyPath = "../your-local-proxy-directory/proxy.php"; ``` -####OfflineFeaturesManagerBasic.goOffline() +####OfflineEditBasic.goOffline() Force the library to go offline. Once this condition is set, then any offline edits will be cached locally. ```js function goOffline() { - offlineFeaturesManager.goOffline() }); + offlineEdit.goOffline() }); //TO-DO } ``` -####OfflineFeaturesManagerBasic.goOnline() +####OfflineEditBasic.goOnline() Force the library to return to an online condition. If there are pending edits, the library will attempt to sync them. ```js function goOnline() { - offlineFeaturesManager.goOnline(function(result) + offlineEdit.goOnline(function(result) { if(result.success){ //Modify user inteface depending on success/failure @@ -139,22 +139,22 @@ Typically you should only need to call this method once for each online/offline If there was a an failure and/or errors, it's a good idea to reevaluate the edits that remain in the database because some edits may have been synced and others may still be pending. Only then, and depending on the error message, should the app try to `goOnline()` again. -####OfflineFeaturesManagerBasic.getOnlineStatus() +####OfflineEditBasic.getOnlineStatus() Within your application you can manually check online status and then update your user interface. By using a switch/case statement you can check against three enums that indicate if the library thinks it is offline, online or in the process of reconnecting. ```js - switch( offlineFeaturesManager.getOnlineStatus() ) + switch( offlineEdit.getOnlineStatus() ) { - case offlineFeaturesManager.OFFLINE: + case offlineEdit.OFFLINE: node.innerHTML = " offline"; domClass.add(node, "offline"); break; - case offlineFeaturesManager.ONLINE: + case offlineEdit.ONLINE: node.innerHTML = " online"; domClass.add(node, "online"); break; - case offlineFeaturesManager.RECONNECTING: + case offlineEdit.RECONNECTING: node.innerHTML = " reconnecting"; domClass.add(node, "reconnecting"); break; @@ -162,18 +162,18 @@ Within your application you can manually check online status and then update you ``` -####OfflineFeaturesManagerBasic.pendingEditsCount(callback) +####OfflineEditBasic.pendingEditsCount(callback) You can check if there are any edits pending. ```js // Simply get a count - offlineFeaturesManager.pendingEditsCount(function(count){ + offlineEdit.pendingEditsCount(function(count){ console.log("There are " + count + " edits pending"); }) // Or retrieve all pending edits - offlineFeaturesManager.getAllEditsArray(function(success,editsArray){ + offlineEdit.getAllEditsArray(function(success,editsArray){ if(success && editsArray.length > 0){ editsArray.forEach(function(edit){ console.log("Pending edit: " + JSON.stringify(edit)); @@ -191,10 +191,10 @@ You can run the reset code seperately or you can run the app with this pattern. ```js -offlineFeaturesManager.extend(myFeatureLayer,function(result, error) { +offlineEdit.extend(myFeatureLayer,function(result, error) { if(result) { - console.log("offlineFeaturesManager initialized."); - offlineFeaturesManager.resetDatabase(function(success,error){ + console.log("OfflineEditBasic initialized."); + offlineEdit.resetDatabase(function(success,error){ console.log("DATABASE DELETED"); }); . . . diff --git a/doc/offlinefeaturesmanageradvanced.md b/doc/offlinefeaturesmanageradvanced.md index 6cc3d79f..48953e20 100644 --- a/doc/offlinefeaturesmanageradvanced.md +++ b/doc/offlinefeaturesmanageradvanced.md @@ -1,19 +1,19 @@ -API OfflineFeaturesManagerAdvanced +API OfflineEditAdvanced ================================== -##O.esri.Edit.OfflineFeaturesManagerAdvanced +##O.esri.Edit.OfflineEditAdvanced The `offline-edit-advanced-min.js` library provides the following tools for working with esri.layers.FeatureLayer objects while intermittently and fully offline. ###Constructor Constructor | Description --- | --- -`O.esri.Edit.OfflineFeaturesManagerAdvanced()` | Creates an instance of the OfflineFeaturesManagerAdvanced class. This library allows you to extend FeatureLayer objects with offline editing capabilities and manage the online/offline resynchronization process. +`O.esri.Edit.OfflineEditAdvanced()` | Creates an instance of the OfflineEditAdvanced class. This library allows you to extend FeatureLayer objects with offline editing capabilities and manage the online/offline resynchronization process. ###Properties Property | Value | Description --- | --- | --- -`DB_NAME` | "features_store" | Sets the database name. You can instantiate multiple databases within the same application by creating seperate instances of OfflineFeaturesManagerAdvanced. +`DB_NAME` | "features_store" | Sets the database name. You can instantiate multiple databases within the same application by creating seperate instances of OfflineEditAdvanced. `DB_OBJECTSTORE_NAME` | "features" | Represents an object store that allows access to a set of data in the database. `DB_UID` | "objectid" | IMPORTANT!** This tells the database what id to use as a unique identifier. This depends on how your feature service was created. ArcGIS Online services may use something different such as `GlobalID`. `ATTACHMENTS_DB_NAME` | "attachments_store" | (Added @ v2.7) Sets the attachments database name. @@ -33,7 +33,7 @@ Property | Value | Description ###Methods -OfflineFeaturesManagerAdvanced provides the following functionality. +OfflineEditAdvanced provides the following functionality. **IMPORTANT:** The library currently only works offline when the feature layer's `mode` is set to `FeatureLayer.MODE_SNAPSHOT`. @@ -44,16 +44,16 @@ Methods | Returns | Description `goOnline(callback)` | No attachments: `callback( {success: boolean, responses: Object } )`

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

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

        This method should be used when working with pre-built Esri widgets such as the `AttributeInspector.` -`getFeatureLayerJSONDataStore( callback )` | `callback( boolean, Object)` | (Added @ v2.7.1) Returns the feature layer's dataStore Object that was created using the `offlineFeaturesManagerAdvanced()` constructor. Offers more control what is provided by `getFeatureCollections()`. +`getFeatureLayerJSONDataStore( callback )` | `callback( boolean, Object)` | (Added @ v2.7.1) Returns the feature layer's dataStore Object that was created using the `OfflineEditAdvanced()` constructor. Offers more control what is provided by `getFeatureCollections()`. ###Events -Application code can subscribe to offlineFeaturesManagerAdvanced events to be notified of different conditions. +Application code can subscribe to OfflineEditAdvanced events to be notified of different conditions. ```js - offlineFeaturesManager.on( - OfflineFeaturesManagerAdvanced.events.ALL_EDITS_SENT, + offlineEdit.on( + offlineEdit.events.ALL_EDITS_SENT, function(edits) { ... @@ -72,14 +72,14 @@ Event | Value | Returns | Description ###FeatureLayer -A FeatureLayer that has been extended using OfflineFeaturesManagerAdvanced.extend() will gain access to the following additional functionality. Example usage: +A FeatureLayer that has been extended using OfflineEditAdvancedoff.extend() will gain access to the following additional functionality. Example usage: ```js // Extend the FeatureLayer - var offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManager(); - offlineFeaturesManager.extend(myCustomFeatureLayer); + var offlineEdit = new O.esri.Edit.OfflineEditAdvanced(); + offlineEdit.extend(myCustomFeatureLayer); // Access additional functionality myCustomFeatureLayer.getPhantomGraphicsLayer(function(json){...}); diff --git a/doc/offlinefeaturesmanagerbasic.md b/doc/offlinefeaturesmanagerbasic.md index 6d712d03..7610d97f 100644 --- a/doc/offlinefeaturesmanagerbasic.md +++ b/doc/offlinefeaturesmanagerbasic.md @@ -1,19 +1,19 @@ -API OfflineFeaturesManagerBasic +API OfflineEditBasic ================================== -##O.esri.Edit.OfflineFeaturesManagerBasic +##O.esri.Edit.OfflineEditBasic The `offline-edit-basic-min.js` library provides the following tools for working with esri.layers.FeatureLayer objects while intermittently offline. ###Constructor Constructor | Description --- | --- -`O.esri.Edit.OfflineFeaturesManagerBasic()` | Creates an instance of the OfflineFeaturesManagerBasic class. This library allows you to extend FeatureLayer objects with offline editing capabilities and manage the online/offline resynchronization process. +`O.esri.Edit.OfflineEditBasic()` | Creates an instance of the OfflineEditBasic class. This library allows you to extend FeatureLayer objects with offline editing capabilities and manage the online/offline resynchronization process. ###Properties Property | Value | Description --- | --- | --- -`DB_NAME` | "features_store" | Sets the database name. You can instantiate multiple databases within the same application by creating seperate instances of OfflineFeaturesManagerAdvanced. +`DB_NAME` | "features_store" | Sets the database name. You can instantiate multiple databases within the same application by creating seperate instances of OfflineEditAdvanced. `DB_OBJECTSTORE_NAME` | "features" | Represents an object store that allows access to a set of data in the database. `DB_UID` | "objectid" | IMPORTANT!** This tells the database what id to use as a unique identifier. This depends on how your feature service was created. ArcGIS Online services may use something different such as `GlobalID`. `proxyPath` | null | Default is `null`. If you are using a Feature Service that is not CORS-enabled then you will need to set this path. @@ -29,7 +29,7 @@ Property | Value | Description ###Methods -OfflineFeaturesManagerBasic provides the following functionality. +OfflineEditBasic provides the following functionality. **IMPORTANT:** The library currently only works offline when the feature layer's `mode` is set to `FeatureLayer.MODE_SNAPSHOT`. @@ -42,12 +42,12 @@ Methods | Returns | Description ###Events -Application code can subscribe to offlineFeaturesManagerBasic events to be notified of different conditions. +Application code can subscribe to OfflineEditBasic events to be notified of different conditions. ```js - offlineFeaturesManager.on( - OfflineFeaturesManagerBasic.events.ALL_EDITS_SENT, + offlineEdit.on( + offlineEdit.events.ALL_EDITS_SENT, function(edits) { ... @@ -63,14 +63,14 @@ Event | Value | Returns | Description ###FeatureLayer -A FeatureLayer that has been extended using OfflineFeaturesManagerAdvanced.extend() will gain access to the following additional functionality. Example usage: +A FeatureLayer that has been extended using OfflineEditBasic.extend() will gain access to the following additional functionality. Example usage: ```js // Extend the FeatureLayer - var offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManagerBasic(); - offlineFeaturesManager.extend(myCustomFeatureLayer); + var offlineEdit = new O.esri.Edit.OfflineEditBasic(); + offlineEdit.extend(myCustomFeatureLayer); ``` diff --git a/lib/edit/OfflineEditAdvanced.js b/lib/edit/OfflineEditAdvanced.js index 6a5f7e64..67ec436f 100644 --- a/lib/edit/OfflineEditAdvanced.js +++ b/lib/edit/OfflineEditAdvanced.js @@ -111,7 +111,7 @@ define([ layer.offlineExtended = true; // to identify layer has been extended if(!layer.loaded) { - console.error("Make sure to initialize OfflineFeaturesManager after layer loaded and feature layer update-end event."); + console.error("Make sure to initialize OfflineEditAdvanced after layer loaded and feature layer update-end event."); } // NOTE: At v2.6.1 we've discovered that not all feature layers support objectIdField. @@ -189,7 +189,7 @@ define([ } if (!self.attachmentsStore) { - console.log("in order to support attachments you need to call initAttachments() method of offlineFeaturesManager"); + console.log("in order to support attachments you need to call initAttachments() method of OfflineEditAdvanced"); return; } @@ -219,7 +219,7 @@ define([ } if (!self.attachmentsStore) { - console.error("in order to support attachments you need to call initAttachments() method of offlineFeaturesManager"); + console.error("in order to support attachments you need to call initAttachments() method of OfflineEditAdvanced"); return; } @@ -264,7 +264,7 @@ define([ } if (!self.attachmentsStore) { - console.error("in order to support attachments you need to call initAttachments() method of offlineFeaturesManager"); + console.error("in order to support attachments you need to call initAttachments() method of OfflineEditAdvanced"); return; } @@ -316,7 +316,7 @@ define([ } if (!self.attachmentsStore) { - console.error("in order to support attachments you need to call initAttachments() method of offlineFeaturesManager"); + console.error("in order to support attachments you need to call initAttachments() method of OfflineEditAdvanced"); return; } @@ -834,7 +834,7 @@ define([ } // Automatically update the featureCollectionsObject in the database with every ADD, UPDATE - // and DELETE. It can be retrieved via OfflineFeaturesManager.getFeatureCollections(); + // and DELETE. It can be retrieved via OfflineEditAdvanced.getFeatureCollections(); self._editStore._pushFeatureCollections(result, function(success, error) { if(!success){ console.error("There was a problem creating the featureCollectionObject: " + error); @@ -1253,7 +1253,7 @@ define([ * @param callback callback( boolean, errors ) */ goOnline: function (callback) { - console.log("offlineFeaturesManager going online"); + console.log("OfflineEditAdvanced going online"); this._onlineStatus = this.RECONNECTING; this._replayStoredEdits(function (success, responses) { var result = {success: success, responses: responses}; @@ -1714,7 +1714,7 @@ define([ // If the layer has attachments then check to see if the attachmentsStore has been initialized if (attachmentsStore == null && layer.hasAttachments) { - console.log("NOTICE: you may need to run OfflineFeaturesManager.initAttachments(). Check the Attachments doc for more info. Layer id: " + layer.id + " accepts attachments"); + console.log("NOTICE: you may need to run OfflineEditAdvanced.initAttachments(). Check the Attachments doc for more info. Layer id: " + layer.id + " accepts attachments"); } // Assign the attachmentsStore to the layer as a private var so we can access it from @@ -1765,7 +1765,7 @@ define([ var allPromises = all(promises); allPromises.then( function (responses) { - console.log("OfflineFeaturesManager sync - all responses are back"); + console.log("OfflineEditAdvanced sync - all responses are back"); this._parseResponsesArray(responses).then(function(result) { if(result) { @@ -1778,7 +1778,7 @@ define([ }.bind(this)); }.bind(that), function (errors) { - console.log("OfflineFeaturesManager._replayStoredEdits - ERROR!!"); + console.log("OfflineEditAdvanced._replayStoredEdits - ERROR!!"); console.log(errors); callback && callback(false, errors); }.bind(that) diff --git a/lib/edit/OfflineEditBasic.js b/lib/edit/OfflineEditBasic.js index 6b767374..a12d801f 100644 --- a/lib/edit/OfflineEditBasic.js +++ b/lib/edit/OfflineEditBasic.js @@ -66,7 +66,7 @@ define([ layer.offlineExtended = true; // to identify layer has been extended if(!layer.loaded || layer._url === null) { - console.error("Make sure to initialize OfflineFeaturesManager after layer loaded and feature layer update-end event."); + console.error("Make sure to initialize OfflineEditBasic after layer loaded and feature layer update-end event."); } // NOTE: At v2.6.1 we've discovered that not all feature layers support objectIdField. @@ -528,7 +528,7 @@ define([ * @param callback callback( boolean, errors ) */ goOnline: function (callback) { - console.log("offlineFeaturesManager going online"); + console.log("OfflineEditBasic going online"); this._onlineStatus = this.RECONNECTING; this._replayStoredEdits(function (success, responses) { //var result = {success: success, responses: responses}; @@ -657,11 +657,11 @@ define([ var allPromises = all(promises); allPromises.then( function (responses) { - console.log("OfflineFeaturesManager sync - all responses are back"); + console.log("OfflineEditBasic sync - all responses are back"); callback(true, responses); }, function (errors) { - console.log("OfflineFeaturesManager._replayStoredEdits - ERROR!!"); + console.log("OfflineEditBasic._replayStoredEdits - ERROR!!"); callback(false, errors); } ); diff --git a/lib/edit/editStorePOLS.js b/lib/edit/editStorePOLS.js index 4018d775..f0b48c99 100644 --- a/lib/edit/editStorePOLS.js +++ b/lib/edit/editStorePOLS.js @@ -53,8 +53,8 @@ O.esri.Edit.EditStorePOLS = function () { }; if(typeof graphic.attributes[this.objectId] === "undefined") { - console.error("editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineFeaturesManager.DB_UID? " + JSON.stringify(graphic.attributes)); - callback(false,"editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineFeaturesManager.DB_UID? " + JSON.stringify(graphic.attributes)); + console.error("editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineEdit.DB_UID? " + JSON.stringify(graphic.attributes)); + callback(false,"editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineEdit.DB_UID? " + JSON.stringify(graphic.attributes)); } else{ var transaction = this._db.transaction([this.objectStoreName], "readwrite"); diff --git a/lib/edit/editsStore.js b/lib/edit/editsStore.js index dc68d415..888106bd 100644 --- a/lib/edit/editsStore.js +++ b/lib/edit/editsStore.js @@ -51,8 +51,8 @@ O.esri.Edit.EditStore = function () { }; if(typeof graphic.attributes[this.objectId] === "undefined") { - console.error("editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineFeaturesManager.DB_UID? " + JSON.stringify(graphic.attributes)); - callback(false,"editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineFeaturesManager.DB_UID? " + JSON.stringify(graphic.attributes)); + console.error("editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineEdit.DB_UID? " + JSON.stringify(graphic.attributes)); + callback(false,"editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineEdit.DB_UID? " + JSON.stringify(graphic.attributes)); } else{ var transaction = this._db.transaction([this.objectStoreName], "readwrite"); diff --git a/samples/appcache-twofeatureslayer-noedit.html b/samples/appcache-twofeatureslayer-noedit.html index c603c057..6b73469b 100644 --- a/samples/appcache-twofeatureslayer-noedit.html +++ b/samples/appcache-twofeatureslayer-noedit.html @@ -371,7 +371,7 @@ alert("There was a problem when attempting to go back online."); } else { - console.log("OfflineFeaturesManager is online"); + console.log("OfflineEditAdvanced is online"); } }); } @@ -404,7 +404,7 @@ if(_isOnline){ initializeFeaturesLayersOnline(); } - // If the app is offline then we need to retrieve the dataStore from OfflineFeaturesManager + // If the app is offline then we need to retrieve the dataStore from OfflineEditAdvanced // and then extend the feature layer using that information. else { loadFeatureLayerOffline(function(success) { @@ -627,7 +627,7 @@ */ /** - * Forces offlineFeaturesManager to go online. + * Forces OfflineEditAdvanced to go online. * When invoking featureLayer.goOnline() we force library to sync any edits that were * stored while offline. */ @@ -638,7 +638,7 @@ } /** - * Forces offlineFeaturesManager offline + * Forces OfflineEditAdvanced offline */ function goOffline() { console.log("Going offline..."); @@ -647,7 +647,7 @@ } /** - * Toggles offlineFeaturesManager online/offline + * Toggles OfflineEditAdvanced online/offline */ function goOnlineOffline() { if(offlineEdit.getOnlineStatus() == offlineEdit.ONLINE){ diff --git a/test/SpecRunner.offlineAttachments.html b/test/SpecRunner.offlineAttachments.html index 72607033..00a89d3b 100644 --- a/test/SpecRunner.offlineAttachments.html +++ b/test/SpecRunner.offlineAttachments.html @@ -2,7 +2,7 @@ "http://www.w3.org/TR/html4/loose.dtd"> - Jasmine Spec Runner - offlineFeaturesManager + Jasmine Spec Runner - Offline Attachments @@ -32,7 +32,7 @@ var g_map; var g_featureLayers = []; var g_featureLayer = null; - var g_offlineFeaturesManager; + var g_offlineEdit; var g_modules = {}; var g_editsStore; var g_formNode, g_formData, g_formData2, g_formNode2; @@ -52,12 +52,12 @@ { g_modules.esriRequest = esriRequest; g_modules.Graphic = Graphic; - g_offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManagerAdvanced(); - g_offlineFeaturesManager.DB_UID = "OBJECTID"; + g_offlineEdit = new O.esri.Edit.OfflineEditAdvanced(); + g_offlineEdit.DB_UID = "OBJECTID"; g_editsStore = new O.esri.Edit.EditStore(); - g_offlineFeaturesManager.initAttachments(function(success) + g_offlineEdit.initAttachments(function(success) { console.log("attachments inited", success); }); @@ -92,7 +92,7 @@ function test() { - g_offlineFeaturesManager.extend(g_featureLayer,function(success,message){ + g_offlineEdit.extend(g_featureLayer,function(success,message){ if(!success){ alert("There was a problem extending the layer: " + layer); } @@ -148,7 +148,7 @@ files.push(file); - // Fake a form node for our custom parser in offlineFeaturesManager.js + // Fake a form node for our custom parser in OfflineEditAdvanced.js g_formNode = { elements:[ {type:"file", diff --git a/test/SpecRunner.offlineFeaturesManager.TokenBased.html b/test/SpecRunner.offlineFeaturesManager.TokenBased.html index 898bf086..1b9501d6 100644 --- a/test/SpecRunner.offlineFeaturesManager.TokenBased.html +++ b/test/SpecRunner.offlineFeaturesManager.TokenBased.html @@ -2,7 +2,7 @@ "https://www.w3.org/TR/html4/loose.dtd"> - Jasmine Spec Runner - offlineFeaturesManagerAdvanced + Jasmine Spec Runner - OfflineEditAdvanced @@ -31,8 +31,8 @@ var g_map; var g_featureLayers = []; - var g_offlineFeaturesManager; - var g_offlineFeaturesManagerAttach; + var g_offlineEdit; + var g_offlineEditAttach; var g_modules = {}; var g_editsStore; var g_layersIds = []; @@ -48,18 +48,17 @@ GraphicsLayer, Graphic, FeatureLayer, geometry, esriRequest, dom, on, query, - OfflineFeaturesManager,editsStore, domConstruct) { g_modules.esriRequest = esriRequest; g_modules.Graphic = Graphic; - g_offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManagerAdvanced(); - g_offlineFeaturesManager.DB_NAME = "FEATURES_TEST"; - g_offlineFeaturesManager.DB_UID = "OBJECTID"; //VERY IMPORTANT! We get this from the Service Directory "Fields" section. + g_offlineEdit = new O.esri.Edit.OfflineEditAdvanced(); + g_offlineEdit.DB_NAME = "FEATURES_TEST"; + g_offlineEdit.DB_UID = "OBJECTID"; //VERY IMPORTANT! We get this from the Service Directory "Fields" section. // For testing attachments - g_offlineFeaturesManagerAttach = new O.esri.Edit.OfflineFeaturesManagerAdvanced(); - g_offlineFeaturesManagerAttach.DB_NAME = "ATTACH_TEST"; + g_offlineEditAttach = new O.esri.Edit.OfflineEditAdvanced(); + g_offlineEditAttach.DB_NAME = "ATTACH_TEST"; // We are also validating the OfflineFeatureManager directly against the database g_editsStore = new O.esri.Edit.EditStore(); @@ -74,7 +73,7 @@ // TODO: UPDATE THE BELOW URL with your own secured Simple_Point_Service. The IdentityManager will prompt you for credentials before the test starts. // TODO: This feature should use the same schema as found at: "http://services1.arcgis.com/M8KJPUwAXP8jhtnM/arcgis/rest/services/Simple_Point_Service/FeatureServer/" - // TODO: Also replace the URL found in the spec/offlineFeaturesManagerSpec.js with your own URL. + // TODO: Also replace the URL found in the spec/offlineEditAdvancedSpec.js with your own URL. var fsUrlAttachmentsEnabled = ""; // Layer 1 = points // Layer 2 = lines @@ -108,8 +107,8 @@ options.zoom = g_map.getZoom(); // WE will use both the featureLayerCollection and dataStore objects for testing purposes - g_offlineFeaturesManager.ENABLE_FEATURECOLLECTION = true; - g_offlineFeaturesManager.extend(layer,function(result){ + g_offlineEdit.ENABLE_FEATURECOLLECTION = true; + g_offlineEdit.extend(layer,function(result){ if(result == false) console.error("WARNING: Unable to initialize database"); },options); }); diff --git a/test/SpecRunner.offlineFeaturesManager.oAuth.html b/test/SpecRunner.offlineFeaturesManager.oAuth.html index 8c91420c..6b2c48f5 100644 --- a/test/SpecRunner.offlineFeaturesManager.oAuth.html +++ b/test/SpecRunner.offlineFeaturesManager.oAuth.html @@ -2,7 +2,7 @@ "https://www.w3.org/TR/html4/loose.dtd"> - Jasmine Spec Runner - offlineFeaturesManager + Jasmine Spec Runner - Offline Edit Advanced OAuth @@ -31,8 +31,8 @@ var g_map; var g_featureLayers = []; - var g_offlineFeaturesManager; - var g_offlineFeaturesManagerAttach; + var g_offlineEdit; + var g_offlineEditAttach; var g_modules = {}; var g_editsStore; var g_layersIds = []; @@ -48,7 +48,6 @@ GraphicsLayer, Graphic, FeatureLayer, geometry, esriRequest, dom, domStyle, on, query, - OfflineFeaturesManager,editsStore, domConstruct) { // TODO: UPDATE THE BELOW appId WITH YOUR OWN REGISTERED APP. @@ -82,13 +81,13 @@ g_modules.esriRequest = esriRequest; g_modules.Graphic = Graphic; - g_offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManagerAdvanced(); - g_offlineFeaturesManager.DB_NAME = "FEATURES_TEST"; - g_offlineFeaturesManager.DB_UID = "OBJECTID"; //VERY IMPORTANT! We get this from the Service Directory "Fields" section. + g_offlineEdit = new O.esri.Edit.OfflineEditAdvanced(); + g_offlineEdit.DB_NAME = "FEATURES_TEST"; + g_offlineEdit.DB_UID = "OBJECTID"; //VERY IMPORTANT! We get this from the Service Directory "Fields" section. // For testing attachments - g_offlineFeaturesManagerAttach = new O.esri.Edit.OfflineFeaturesManagerAdvanced(); - g_offlineFeaturesManagerAttach.DB_NAME = "ATTACH_TEST"; + g_offlineEditAttach = new O.esri.Edit.OfflineEditAdvanced(); + g_offlineEditAttach.DB_NAME = "ATTACH_TEST"; // We are also validating the OfflineFeatureManager directly against the database g_editsStore = new O.esri.Edit.EditStore(); @@ -103,7 +102,7 @@ // TODO: UPDATE THE BELOW URL with your own secured Simple_Point_Service that is added to your secured AGOL app. // TODO: This feature should use the same schema as found at: "http://services1.arcgis.com/M8KJPUwAXP8jhtnM/arcgis/rest/services/Simple_Point_Service/FeatureServer/" - // TODO: Also replace the URL found in the spec/offlineFeaturesManagerSpec.js with your own URL. + // TODO: Also replace the URL found in the spec/OfflineEditAdvancedSpec.js with your own URL. var fsUrlAttachmentsEnabled = ""; // Layer 1 = points // Layer 2 = lines @@ -137,8 +136,8 @@ options.zoom = g_map.getZoom(); // WE will use both the featureLayerCollection and dataStore objects for testing purposes - g_offlineFeaturesManager.ENABLE_FEATURECOLLECTION = true; - g_offlineFeaturesManager.extend(layer,function(result){ + g_offlineEdit.ENABLE_FEATURECOLLECTION = true; + g_offlineEdit.extend(layer,function(result){ if(result == false) console.error("WARNING: Unable to initialize database"); },options); }); diff --git a/test/SpecRunner.offlineFeaturesManagerAdvanced.html b/test/SpecRunner.offlineFeaturesManagerAdvanced.html index 38d3a2a8..0737de44 100644 --- a/test/SpecRunner.offlineFeaturesManagerAdvanced.html +++ b/test/SpecRunner.offlineFeaturesManagerAdvanced.html @@ -2,7 +2,7 @@ "http://www.w3.org/TR/html4/loose.dtd"> - Jasmine Spec Runner - offlineFeaturesManagerAdvanced + Jasmine Spec Runner - Offline Edit Advanced @@ -24,14 +24,14 @@ - + - + @@ -31,7 +31,7 @@ var g_map; var g_featureLayers = []; - var g_offlineFeaturesManager; + var g_offlineEdit; var g_modules = {}; var g_editsStore; var g_layersIds = []; @@ -48,7 +48,6 @@ GraphicsLayer, Graphic, FeatureLayer, geometry, esriRequest, dom, on, Query, - OfflineFeaturesManager,editsStore, domConstruct) { @@ -62,9 +61,9 @@ g_modules.esriRequest = esriRequest; g_modules.Graphic = Graphic; - g_offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManagerBasic(); - g_offlineFeaturesManager.DB_NAME = "FEATURES_TEST"; - g_offlineFeaturesManager.DB_UID = "OBJECTID"; //VERY IMPORTANT! We get this from the Service Directory "Fields" section. + g_offlineEdit = new O.esri.Edit.OfflineEditBasic(); + g_offlineEdit.DB_NAME = "FEATURES_TEST"; + g_offlineEdit.DB_UID = "OBJECTID"; //VERY IMPORTANT! We get this from the Service Directory "Fields" section. // We are also validating the OfflineFeatureManager directly against the database @@ -90,14 +89,6 @@ g_map.addLayers(g_featureLayers); -// g_map.addLayer(g_offlineFeaturesManager); - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - // To test what happens when you extend library before layers-add-result - // comment this out below and just run test() -// updateEndListener = g_offlineFeaturesManager.on('update-end', test); - //test() g_map.on('layers-add-result', test); function test(evt) @@ -107,14 +98,12 @@ { g_featureLayers.forEach(function(layer) { - g_offlineFeaturesManager.extend(layer,function(result){ + g_offlineEdit.extend(layer,function(result){ if(result == false) console.error("WARNING: Unable to initialize database"); try { -// g_featureLayers[0] = g_offlineFeaturesManager.featureLayer; -// var test = g_offlineFeaturesManager; var jasmineEnv = jasmine.getEnv(); jasmineEnv.updateInterval = 1000; jasmineEnv.defaultTimeoutInterval = 10000; // 10 sec diff --git a/test/spec/offlineAttachmentsSpec.js b/test/spec/offlineAttachmentsSpec.js index cfd6975c..39e17099 100644 --- a/test/spec/offlineAttachmentsSpec.js +++ b/test/spec/offlineAttachmentsSpec.js @@ -38,7 +38,7 @@ function countFeatures(featureLayer, cb) function getObjectIds(graphics) { - return graphics.map( function(g) { return g_offlineFeaturesManager.DB_UID; }); + return graphics.map( function(g) { return g_offlineEdit.DB_UID; }); } /* @@ -69,14 +69,14 @@ describe("Attachments", function() async.it("delete all local attachments", function(done) { - expect(g_offlineFeaturesManager.attachmentsStore).not.toBeUndefined(); + expect(g_offlineEdit.attachmentsStore).not.toBeUndefined(); - g_offlineFeaturesManager.attachmentsStore.deleteAll(function(success) + g_offlineEdit.attachmentsStore.deleteAll(function(success) { expect(success).toBeTruthy(); setTimeout(function() { - g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage) + g_offlineEdit.attachmentsStore.getUsage(function(usage) { expect(usage.attachmentCount).toBe(0); done(); @@ -181,9 +181,9 @@ describe("Attachments", function() async.it("go offline", function(done) { - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE); - g_offlineFeaturesManager.goOffline(); - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.OFFLINE); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.ONLINE); + g_offlineEdit.goOffline(); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.OFFLINE); done(); }); @@ -232,7 +232,7 @@ describe("Attachments", function() }); async.it("Verify Attachment DB usage as zero", function(done){ - g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage) + g_offlineEdit.attachmentsStore.getUsage(function(usage) { expect(usage.attachmentCount).toBe(0); done(); @@ -275,8 +275,8 @@ describe("Attachments", function() async.it("add attachment to (online) feature", function(done) { expect(g_featureLayer.graphics.length).toBe(4); - expect(g_offlineFeaturesManager.attachmentsStore).not.toBeUndefined(); - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.OFFLINE); + expect(g_offlineEdit.attachmentsStore).not.toBeUndefined(); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.OFFLINE); expect(g1_online.attributes.objectid).toBeGreaterThan(0); g_featureLayer.addAttachment( g1_online.attributes.objectid, g_formNode, @@ -295,10 +295,10 @@ describe("Attachments", function() }); async.it("Verify Attachment DB usage", function(done){ - g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage) + g_offlineEdit.attachmentsStore.getUsage(function(usage) { expect(usage.attachmentCount).toBe(1); - g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g1_online.attributes.objectid, function(attachments) + g_offlineEdit.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g1_online.attributes.objectid, function(attachments) { expect(attachments.length).toBe(1); console.log("attached file:", attachments[0]); @@ -315,7 +315,7 @@ describe("Attachments", function() async.it("add attachment to (offline) feature g2_offline", function(done) { expect(g_featureLayer.graphics.length).toBe(4); - expect(g_offlineFeaturesManager.attachmentsStore).not.toBeUndefined(); + expect(g_offlineEdit.attachmentsStore).not.toBeUndefined(); expect(g2_offline.attributes.objectid).toBeLessThan(0); @@ -336,10 +336,10 @@ describe("Attachments", function() }); async.it("Verify attachment g2_offline exists in DB", function(done){ - g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage) + g_offlineEdit.attachmentsStore.getUsage(function(usage) { expect(usage.attachmentCount).toBe(2); - g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g2_offline.attributes.objectid, function(attachments) + g_offlineEdit.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g2_offline.attributes.objectid, function(attachments) { expect(attachments.length).toBe(1); console.log("attached file:", attachments[0]); @@ -351,7 +351,7 @@ describe("Attachments", function() async.it("add attachment to (offline) feature g3_offline (to be deleted)", function(done) { expect(g_featureLayer.graphics.length).toBe(4); - expect(g_offlineFeaturesManager.attachmentsStore).not.toBeUndefined(); + expect(g_offlineEdit.attachmentsStore).not.toBeUndefined(); expect(g3_offline.attributes.objectid).toBeLessThan(0); @@ -372,7 +372,7 @@ describe("Attachments", function() }); async.it("Verify attachment g3_offline", function(done) { - g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g3_offline.attributes.objectid, function(attachments) + g_offlineEdit.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g3_offline.attributes.objectid, function(attachments) { expect(attachments.length).toBe(1); console.log("attached file:", attachments[0]); @@ -384,7 +384,7 @@ describe("Attachments", function() async.it("add attachment to (offline) feature g4_offline (to be deleted)", function(done) { expect(g_featureLayer.graphics.length).toBe(4); - expect(g_offlineFeaturesManager.attachmentsStore).not.toBeUndefined(); + expect(g_offlineEdit.attachmentsStore).not.toBeUndefined(); expect(g4_offline.attributes.objectid).toBeLessThan(0); @@ -408,7 +408,7 @@ describe("Attachments", function() }); async.it("Verify attachment g4_offline", function(done) { - g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g4_offline.attributes.objectid, function(attachments) + g_offlineEdit.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g4_offline.attributes.objectid, function(attachments) { expect(attachments.length).toBe(1); console.log("attached file:", attachments[0]); @@ -417,7 +417,7 @@ describe("Attachments", function() }); async.it("Verify Attachment DB usage", function(done){ - g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage) + g_offlineEdit.attachmentsStore.getUsage(function(usage) { expect(usage.attachmentCount).toBe(4); done(); @@ -426,7 +426,7 @@ describe("Attachments", function() async.it("query offline attachments of layer", function(done) { - g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureLayer(g_featureLayer.url, function(attachments) + g_offlineEdit.attachmentsStore.getAttachmentsByFeatureLayer(g_featureLayer.url, function(attachments) { expect(attachments.length).toBe(4); var objectIds = attachments.map(function(a){ return a.objectId; }).sort(); @@ -507,10 +507,10 @@ describe("Attachments", function() }); async.it("Verify Attachment DB usage", function(done){ - g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage) + g_offlineEdit.attachmentsStore.getUsage(function(usage) { expect(usage.attachmentCount).toBe(3); - g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g3_offline.attributes.objectid, function(attachments) + g_offlineEdit.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g3_offline.attributes.objectid, function(attachments) { expect(attachments.length).toBe(0); console.log("attached file:", attachments[0]); @@ -527,7 +527,7 @@ describe("Attachments", function() async.it("add attachment", function(done) { expect(g_featureLayer.graphics.length).toBe(3); - expect(g_offlineFeaturesManager.attachmentsStore).not.toBeUndefined(); + expect(g_offlineEdit.attachmentsStore).not.toBeUndefined(); expect(g2_offline.attributes.objectid).toBeLessThan(0); @@ -551,10 +551,10 @@ describe("Attachments", function() }); async.it("Verify Attachment DB usage",function(done){ - g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage) + g_offlineEdit.attachmentsStore.getUsage(function(usage) { expect(usage.attachmentCount).toBe(4); - g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g2_offline.attributes.objectid, function(attachments) + g_offlineEdit.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g2_offline.attributes.objectid, function(attachments) { expect(attachments.length).toBe(2); done(); @@ -565,7 +565,7 @@ describe("Attachments", function() async.it("delete attachment", function(done) { expect(g_featureLayer.graphics.length).toBe(3); - expect(g_offlineFeaturesManager.attachmentsStore).not.toBeUndefined(); + expect(g_offlineEdit.attachmentsStore).not.toBeUndefined(); g_featureLayer.deleteAttachments( g2_offline.attributes.objectid, [attachmentId], function(result) @@ -583,7 +583,7 @@ describe("Attachments", function() }); async.it("Verify Attachment DB usage", function(done){ - g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage) + g_offlineEdit.attachmentsStore.getUsage(function(usage) { expect(usage.attachmentCount).toBe(3); done(); @@ -591,7 +591,7 @@ describe("Attachments", function() }); async.it("Verify attachment g2_offline", function(done){ - g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g2_offline.attributes.objectid, function(attachments) + g_offlineEdit.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g2_offline.attributes.objectid, function(attachments) { expect(attachments.length).toBe(1); done(); @@ -674,7 +674,7 @@ describe("Attachments", function() { async.it("query offline attachments of layer", function(done) { - g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureLayer(g_featureLayer.url, function(attachments) + g_offlineEdit.attachmentsStore.getAttachmentsByFeatureLayer(g_featureLayer.url, function(attachments) { // This should be 3 because we are doing a delete existing attachment operation // which means that DELETE will be queued in the database and will not be @@ -691,12 +691,12 @@ describe("Attachments", function() expect(g_featureLayer.graphics.length).toBe(3); var listener = jasmine.createSpy('event listener'); - g_offlineFeaturesManager.on(g_offlineFeaturesManager.events.ALL_EDITS_SENT, listener); + g_offlineEdit.on(g_offlineEdit.events.ALL_EDITS_SENT, listener); - g_offlineFeaturesManager.goOnline(function(result) + g_offlineEdit.goOnline(function(result) { console.log("went online"); - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.ONLINE); expect(listener).toHaveBeenCalled(); expect(result.success).toBeTruthy(); expect(result.attachments.success).toBeTruthy(); @@ -724,7 +724,7 @@ describe("Attachments", function() expect(g_featureLayer.graphics.length).toBe(3); done(); }); - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.RECONNECTING); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.RECONNECTING); }); }); @@ -749,7 +749,7 @@ describe("Attachments", function() async.it("Get attachments database usage - check directly via attachmentsStore", function(done) { - g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage) + g_offlineEdit.attachmentsStore.getUsage(function(usage) { expect(usage.attachmentCount).toBe(0); done(); diff --git a/test/spec/offlineFeaturesManagerSpec.js b/test/spec/offlineEditingAdvancedSpec.js similarity index 96% rename from test/spec/offlineFeaturesManagerSpec.js rename to test/spec/offlineEditingAdvancedSpec.js index e3c0d336..df495b14 100644 --- a/test/spec/offlineFeaturesManagerSpec.js +++ b/test/spec/offlineEditingAdvancedSpec.js @@ -38,7 +38,7 @@ function countFeatures(featureLayer, cb) function getObjectIds(graphics) { - return graphics.map( function(g) { return g.attributes[g_offlineFeaturesManager.DB_UID]; }); + return graphics.map( function(g) { return g.attributes[g_offlineEdit.DB_UID]; }); } /* @@ -190,7 +190,7 @@ describe("Normal online editing - Exercise the feature services", function() // ] // }; // -// g_offlineFeaturesManager.initAttachments(function(success){ +// g_offlineEdit.initAttachments(function(success){ // expect(success).toBe(true); // if(success){ // @@ -306,7 +306,7 @@ describe("Offline Editing", function() async.it("Prepare feature service. Add some features online - points", function(done) { - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.ONLINE); g1 = new g_modules.Graphic({"geometry":{"x":-105400,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":1,"lat":0.0,"lng":0.0,"description":"g1"}}); g2 = new g_modules.Graphic({"geometry":{"x":-105600,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":2,"lat":0.0,"lng":0.0,"description":"g2"}}); @@ -334,7 +334,7 @@ describe("Offline Editing", function() // Temporarily comment out. We have switched to a Point-based service only that accepts attachments //async.it("Prepare feature service. Add some features online - lines", function(done) //{ - // expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE); + // expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.ONLINE); // // l1 = new g_modules.Graphic({"geometry":{"paths":[[[-101300,5136900],[-108400,5136900]]],"spatialReference":{"wkid":102100}},"attributes":{"ruleid":40,"zmax":null,"additionalinformation":null,"eny":null,"uniquedesignation":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"echelon":null,"x":null,"y":null,"z":null,"zmin":null}}); // l2 = new g_modules.Graphic({"geometry":{"paths":[[[-101300,5136800],[-108400,5136800]]],"spatialReference":{"wkid":102100}},"attributes":{"ruleid":40,"zmax":null,"additionalinformation":null,"eny":null,"uniquedesignation":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"echelon":null,"x":null,"y":null,"z":null,"zmin":null}}); @@ -364,16 +364,16 @@ describe("Offline Editing", function() { async.it("go Offline", function(done) { - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE); - g_offlineFeaturesManager.goOffline(); - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.OFFLINE); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.ONLINE); + g_offlineEdit.goOffline(); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.OFFLINE); done(); }); async.it("Convert feature array into JSON", function(done){ var adds = [g1,g2,g3]; - g_offlineFeaturesManager.serializeFeatureGraphicsArray(adds,function(JSONString){ + g_offlineEdit.serializeFeatureGraphicsArray(adds,function(JSONString){ expect(typeof JSONString).toBe("string"); var object = JSON.parse(JSONString); expect(typeof object).toBe("object"); @@ -386,7 +386,7 @@ describe("Offline Editing", function() }); async.it("Get empty featureCollections Object", function(done) { - g_offlineFeaturesManager.getFeatureCollections(function(success, result) { + g_offlineEdit.getFeatureCollections(function(success, result) { expect(success).toBe(false); expect(result).toBeNull(); done(); @@ -398,12 +398,12 @@ describe("Offline Editing", function() var listener = jasmine.createSpy('event listener edits enqueued'); - g_offlineFeaturesManager.on(g_offlineFeaturesManager.events.EDITS_ENQUEUED,listener); + g_offlineEdit.on(g_offlineEdit.events.EDITS_ENQUEUED,listener); expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g3])); expect(g_featureLayers[0].graphics.length).toBe(3); - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.OFFLINE); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.OFFLINE); g1.geometry.y += 300; g2.geometry.y += 100; @@ -431,7 +431,7 @@ describe("Offline Editing", function() }); async.it("Get featureCollections Object", function(done) { - g_offlineFeaturesManager.getFeatureCollections(function(success, result) { + g_offlineEdit.getFeatureCollections(function(success, result) { expect(success).toBe(true); expect(result.featureCollections.length).toBe(1); expect(result.featureCollections[0].featureLayerCollection).toEqual(g_featureLayers[0].toJson()); @@ -445,7 +445,7 @@ describe("Offline Editing", function() //{ // expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([l1,l2,l3])); // expect(g_featureLayers[1].graphics.length).toBe(3); - // expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.OFFLINE); + // expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.OFFLINE); // // // l1.geometry.y += 300; // jabadia: change @@ -503,7 +503,7 @@ describe("Offline Editing", function() { expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2])); expect(g_featureLayers[0].graphics.length).toBe(2); - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.OFFLINE); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.OFFLINE); //g4 = new g_modules.Graphic({"geometry":{"x":-109100,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Reference Point DLRP","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} ); //g5 = new g_modules.Graphic({"geometry":{"x":-109500,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Reference Point DLRP","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} ); @@ -656,7 +656,7 @@ describe("Offline Editing", function() }); async.it("Validate featureCollections Object", function(done) { - g_offlineFeaturesManager.getFeatureCollections(function(success, result) { + g_offlineEdit.getFeatureCollections(function(success, result) { expect(success).toBe(true); expect(result.featureCollections.length).toBe(1); expect(result.featureCollections[0].featureLayerCollection).toEqual(g_featureLayers[0].toJson()); @@ -704,7 +704,7 @@ describe("Offline Editing", function() ] }; - g_offlineFeaturesManager.initAttachments(function(success){ + g_offlineEdit.initAttachments(function(success){ expect(success).toBe(true); if(success){ @@ -826,7 +826,7 @@ describe("Offline Editing", function() }) }); - // offlineFeaturesManager results should be the same as getting results directly from database + // OfflineEditAdvanced results should be the same as getting results directly from database async.it("Get PhantomLayerGraphics via the layer", function(done){ g_featureLayers[0].getPhantomGraphicsArray(function(result,array){ expect(result).toBe(true); @@ -1031,7 +1031,7 @@ describe("Offline Editing", function() }); async.it("Validate featureCollections Object", function(done) { - g_offlineFeaturesManager.getFeatureCollections(function(success, result) { + g_offlineEdit.getFeatureCollections(function(success, result) { expect(success).toBe(true); expect(result.featureCollections.length).toBe(1); expect(result.featureCollections[0].featureLayerCollection).toEqual(g_featureLayers[0].toJson()); @@ -1065,11 +1065,11 @@ describe("Offline Editing", function() var listener = jasmine.createSpy('event listener all edits sent'); - g_offlineFeaturesManager.on(g_offlineFeaturesManager.events.ALL_EDITS_SENT,listener); + g_offlineEdit.on(g_offlineEdit.events.ALL_EDITS_SENT,listener); - g_offlineFeaturesManager.goOnline(function(results) { + g_offlineEdit.goOnline(function(results) { console.log("Library is now back online"); - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.ONLINE); expect(listener).toHaveBeenCalled(); expect(results.success).toBeTruthy(); @@ -1136,7 +1136,7 @@ describe("Offline Editing", function() }); async.it("After online - verify online status",function(done){ - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.ONLINE); done(); }); diff --git a/test/spec/offlineEditingLightSpec.js b/test/spec/offlineEditingBasicSpec.js similarity index 96% rename from test/spec/offlineEditingLightSpec.js rename to test/spec/offlineEditingBasicSpec.js index 4b64c9b5..5d9181f8 100644 --- a/test/spec/offlineEditingLightSpec.js +++ b/test/spec/offlineEditingBasicSpec.js @@ -38,7 +38,7 @@ function countFeatures(featureLayer, cb) function getObjectIds(graphics) { - return graphics.map( function(g) { return g.attributes[g_offlineFeaturesManager.DB_UID]; }); + return graphics.map( function(g) { return g.attributes[g_offlineEdit.DB_UID]; }); } /* @@ -210,7 +210,7 @@ describe("Offline Editing", function() async.it("Prepare feature service. Add some features online - points", function(done) { - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.ONLINE); g1 = new g_modules.Graphic({"geometry":{"x":-105400,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":1,"lat":0.0,"lng":0.0,"description":"g1"}}); g2 = new g_modules.Graphic({"geometry":{"x":-105600,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":2,"lat":0.0,"lng":0.0,"description":"g2"}}); @@ -242,9 +242,9 @@ describe("Offline Editing", function() { async.it("go Offline", function(done) { - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE); - g_offlineFeaturesManager.goOffline(); - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.OFFLINE); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.ONLINE); + g_offlineEdit.goOffline(); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.OFFLINE); done(); }); @@ -253,12 +253,12 @@ describe("Offline Editing", function() var listener = jasmine.createSpy('event listener edits enqueued'); - g_offlineFeaturesManager.on(g_offlineFeaturesManager.events.EDITS_ENQUEUED,listener); + g_offlineEdit.on(g_offlineEdit.events.EDITS_ENQUEUED,listener); expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g3])); expect(g_featureLayers[0].graphics.length).toBe(3); - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.OFFLINE); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.OFFLINE); g1.geometry.y += 300; g2.geometry.y += 100; @@ -315,7 +315,7 @@ describe("Offline Editing", function() { expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2])); expect(g_featureLayers[0].graphics.length).toBe(2); - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.OFFLINE); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.OFFLINE); //g4 = new g_modules.Graphic({"geometry":{"x":-109100,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Reference Point DLRP","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} ); //g5 = new g_modules.Graphic({"geometry":{"x":-109500,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Reference Point DLRP","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} ); @@ -533,11 +533,11 @@ describe("Offline Editing", function() var listener = jasmine.createSpy('event listener all edits sent'); - //g_offlineFeaturesManager.on(g_offlineFeaturesManager.events.ALL_EDITS_SENT,listener); + //g_offlineEdit.on(g_offlineEdit.events.ALL_EDITS_SENT,listener); - g_offlineFeaturesManager.goOnline(function(success,results) { + g_offlineEdit.goOnline(function(success,results) { console.log("Library is now back online"); - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.ONLINE); //expect(listener).toHaveBeenCalled(); expect(success).toBeTruthy(); @@ -600,7 +600,7 @@ describe("Offline Editing", function() }); async.it("After online - verify online status",function(done){ - expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE); + expect(g_offlineEdit.getOnlineStatus()).toBe(g_offlineEdit.ONLINE); done(); }); From 70e09012a26c507850840215588d1648f3a38c3c Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Mon, 23 Nov 2015 11:35:53 -0700 Subject: [PATCH 23/48] add new thumbnail --- demo/images/simple-edit-thumb.png | Bin 0 -> 58962 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 demo/images/simple-edit-thumb.png diff --git a/demo/images/simple-edit-thumb.png b/demo/images/simple-edit-thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..224cb9f710f9df9ede7ce5b3a7c5c58aa65fbc8e GIT binary patch literal 58962 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 TGRplY00000NkvXXu0mjf8JBNl literal 0 HcmV?d00001 From a9d8eef0bf9a2534ba9237738f93e66a0793a58b Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Mon, 23 Nov 2015 11:38:07 -0700 Subject: [PATCH 24/48] Refactor spec file names --- ...nBased.html => SpecRunner.offlineEditAdvanced.TokenBased.html} | 0 ...esManagerAdvanced.html => SpecRunner.offlineEditAdvanced.html} | 0 ...nager.oAuth.html => SpecRunner.offlineEditAdvanced.oAuth.html} | 0 ...FeaturesManagerBasic.html => SpecRunner.offlineEditBasic.html} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename test/{SpecRunner.offlineFeaturesManager.TokenBased.html => SpecRunner.offlineEditAdvanced.TokenBased.html} (100%) rename test/{SpecRunner.offlineFeaturesManagerAdvanced.html => SpecRunner.offlineEditAdvanced.html} (100%) rename test/{SpecRunner.offlineFeaturesManager.oAuth.html => SpecRunner.offlineEditAdvanced.oAuth.html} (100%) rename test/{SpecRunner.offlineFeaturesManagerBasic.html => SpecRunner.offlineEditBasic.html} (100%) diff --git a/test/SpecRunner.offlineFeaturesManager.TokenBased.html b/test/SpecRunner.offlineEditAdvanced.TokenBased.html similarity index 100% rename from test/SpecRunner.offlineFeaturesManager.TokenBased.html rename to test/SpecRunner.offlineEditAdvanced.TokenBased.html diff --git a/test/SpecRunner.offlineFeaturesManagerAdvanced.html b/test/SpecRunner.offlineEditAdvanced.html similarity index 100% rename from test/SpecRunner.offlineFeaturesManagerAdvanced.html rename to test/SpecRunner.offlineEditAdvanced.html diff --git a/test/SpecRunner.offlineFeaturesManager.oAuth.html b/test/SpecRunner.offlineEditAdvanced.oAuth.html similarity index 100% rename from test/SpecRunner.offlineFeaturesManager.oAuth.html rename to test/SpecRunner.offlineEditAdvanced.oAuth.html diff --git a/test/SpecRunner.offlineFeaturesManagerBasic.html b/test/SpecRunner.offlineEditBasic.html similarity index 100% rename from test/SpecRunner.offlineFeaturesManagerBasic.html rename to test/SpecRunner.offlineEditBasic.html From f346eaa21f7c0e41f0dd1621c177d1b4e3cfbd76 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Mon, 23 Nov 2015 11:41:52 -0700 Subject: [PATCH 25/48] Refactor anme offlineTilesEnabler to OfflineTilesBasic --- lib/tiles/{offlineTilesEnabler.js => OfflineTilesBasic.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/tiles/{offlineTilesEnabler.js => OfflineTilesBasic.js} (100%) diff --git a/lib/tiles/offlineTilesEnabler.js b/lib/tiles/OfflineTilesBasic.js similarity index 100% rename from lib/tiles/offlineTilesEnabler.js rename to lib/tiles/OfflineTilesBasic.js From aa007435a32f61e1c21ae611168cd8cf577714cd Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Mon, 23 Nov 2015 13:03:41 -0700 Subject: [PATCH 26/48] rename docs --- ...flinefeaturesmanageradvanced.md => offlineeditadvanced.md} | 0 doc/{offlinefeaturesmanagerbasic.md => offlineeditbasic.md} | 0 doc/{offlinetilesenablerlayer.md => offlinetilesadvanced.md} | 0 doc/{offlinetilesenabler.md => offlinetilesbasic.md} | 0 .../{OfflineTilesEnablerLayer.js => OfflineTilesAdvanced.js} | 0 ...d.html => SpecRunner.offlineTilesAdvanced.TokenBased.html} | 4 ++-- ...EnablerLayer.html => SpecRunner.offlineTilesAdvanced.html} | 4 ++-- ...ineTilesEnabler.html => SpecRunner.offlineTilesBasic.html} | 0 ...neTilesEnablerLayerSpec.js => offlineTilesAdvancedSpec.js} | 0 ...blerLayerTokenSpec.js => offlineTilesAdvancedTokenSpec.js} | 0 .../{offlineTilesEnablerSpec.js => offlineTilesBasicSpec.js} | 0 11 files changed, 4 insertions(+), 4 deletions(-) rename doc/{offlinefeaturesmanageradvanced.md => offlineeditadvanced.md} (100%) rename doc/{offlinefeaturesmanagerbasic.md => offlineeditbasic.md} (100%) rename doc/{offlinetilesenablerlayer.md => offlinetilesadvanced.md} (100%) rename doc/{offlinetilesenabler.md => offlinetilesbasic.md} (100%) rename lib/tiles/{OfflineTilesEnablerLayer.js => OfflineTilesAdvanced.js} (100%) rename test/{SpecRunner.offlineTilesEnablerLayer.TokenBased.html => SpecRunner.offlineTilesAdvanced.TokenBased.html} (97%) rename test/{SpecRunner.offlineTilesEnablerLayer.html => SpecRunner.offlineTilesAdvanced.html} (97%) rename test/{SpecRunner.offlineTilesEnabler.html => SpecRunner.offlineTilesBasic.html} (100%) rename test/spec/{offlineTilesEnablerLayerSpec.js => offlineTilesAdvancedSpec.js} (100%) rename test/spec/{offlineTilesEnablerLayerTokenSpec.js => offlineTilesAdvancedTokenSpec.js} (100%) rename test/spec/{offlineTilesEnablerSpec.js => offlineTilesBasicSpec.js} (100%) diff --git a/doc/offlinefeaturesmanageradvanced.md b/doc/offlineeditadvanced.md similarity index 100% rename from doc/offlinefeaturesmanageradvanced.md rename to doc/offlineeditadvanced.md diff --git a/doc/offlinefeaturesmanagerbasic.md b/doc/offlineeditbasic.md similarity index 100% rename from doc/offlinefeaturesmanagerbasic.md rename to doc/offlineeditbasic.md diff --git a/doc/offlinetilesenablerlayer.md b/doc/offlinetilesadvanced.md similarity index 100% rename from doc/offlinetilesenablerlayer.md rename to doc/offlinetilesadvanced.md diff --git a/doc/offlinetilesenabler.md b/doc/offlinetilesbasic.md similarity index 100% rename from doc/offlinetilesenabler.md rename to doc/offlinetilesbasic.md diff --git a/lib/tiles/OfflineTilesEnablerLayer.js b/lib/tiles/OfflineTilesAdvanced.js similarity index 100% rename from lib/tiles/OfflineTilesEnablerLayer.js rename to lib/tiles/OfflineTilesAdvanced.js diff --git a/test/SpecRunner.offlineTilesEnablerLayer.TokenBased.html b/test/SpecRunner.offlineTilesAdvanced.TokenBased.html similarity index 97% rename from test/SpecRunner.offlineTilesEnablerLayer.TokenBased.html rename to test/SpecRunner.offlineTilesAdvanced.TokenBased.html index 590b2b92..33566b53 100644 --- a/test/SpecRunner.offlineTilesEnablerLayer.TokenBased.html +++ b/test/SpecRunner.offlineTilesAdvanced.TokenBased.html @@ -43,7 +43,7 @@ dom, on, query, urlUtils, webMercatorUtils, LOD,Point,Extent,TileInfo,SpatialReference,Polygon, - OfflineTilesEnablerLayer, + OfflineTilesAdvanced, domConstruct) { @@ -52,7 +52,7 @@ objectStoreName: "TILES" } - g_basemapLayer = new O.esri.Tiles.OfflineTileEnablerLayer(g_url,function(evt){ + g_basemapLayer = new O.esri.Tiles.OfflineTilesAdvanced(g_url,function(evt){ console.log("Tile Layer Loaded."); g_basemapLayer.offline.proxyPath = null; },true,dbConfig); diff --git a/test/SpecRunner.offlineTilesEnablerLayer.html b/test/SpecRunner.offlineTilesAdvanced.html similarity index 97% rename from test/SpecRunner.offlineTilesEnablerLayer.html rename to test/SpecRunner.offlineTilesAdvanced.html index 407283be..572cee8f 100644 --- a/test/SpecRunner.offlineTilesEnablerLayer.html +++ b/test/SpecRunner.offlineTilesAdvanced.html @@ -42,7 +42,7 @@ dom, on, query, urlUtils, webMercatorUtils, LOD,Point,Extent,TileInfo,SpatialReference,Polygon, - OfflineTilesEnablerLayer, + OfflineTilesAdvanced, domConstruct) { @@ -51,7 +51,7 @@ objectStoreName: "TILES" } - g_basemapLayer = new O.esri.Tiles.OfflineTileEnablerLayer(g_url,function(evt){ + g_basemapLayer = new O.esri.Tiles.OfflineTilesAdvanced(g_url,function(evt){ console.log("Tile Layer Loaded."); g_basemapLayer.offline.proxyPath = null; },true,dbConfig); diff --git a/test/SpecRunner.offlineTilesEnabler.html b/test/SpecRunner.offlineTilesBasic.html similarity index 100% rename from test/SpecRunner.offlineTilesEnabler.html rename to test/SpecRunner.offlineTilesBasic.html diff --git a/test/spec/offlineTilesEnablerLayerSpec.js b/test/spec/offlineTilesAdvancedSpec.js similarity index 100% rename from test/spec/offlineTilesEnablerLayerSpec.js rename to test/spec/offlineTilesAdvancedSpec.js diff --git a/test/spec/offlineTilesEnablerLayerTokenSpec.js b/test/spec/offlineTilesAdvancedTokenSpec.js similarity index 100% rename from test/spec/offlineTilesEnablerLayerTokenSpec.js rename to test/spec/offlineTilesAdvancedTokenSpec.js diff --git a/test/spec/offlineTilesEnablerSpec.js b/test/spec/offlineTilesBasicSpec.js similarity index 100% rename from test/spec/offlineTilesEnablerSpec.js rename to test/spec/offlineTilesBasicSpec.js From 8b89e22869ef898b750fbdf61290b5c14b5f3ffa Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Mon, 23 Nov 2015 13:04:12 -0700 Subject: [PATCH 27/48] refactor grunt build, add beautifier --- Gruntfile.js | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 37b13446..bea3c248 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -28,8 +28,8 @@ module.exports = function(grunt) { 'Gruntfile.js', 'lib/edit/*.js', 'lib/tiles/base64utils.js', - 'lib/tiles/OfflineTilesEnabler.js', - 'lib/tiles/OfflineTilesEnablerLayer.js', + 'lib/tiles/OfflineTilesBasic.js', + 'lib/tiles/OfflineTilesAdvanced.js', 'lib/tiles/OfflineTilesNS.js', 'lib/tiles/TilesCore.js', 'lib/tiles/TilesStore.js', @@ -49,28 +49,29 @@ module.exports = function(grunt) { '*/\n' }, /* All feature editing capabilities: adds, updates and deletes */ - edit: { + editAdvanced: { src: [ - 'lib/edit/offlineFeaturesManager.js', + 'lib/edit/offlineJSOptions.js', + 'lib/edit/OfflineEditAdvanced.js', 'lib/edit/OfflineEditNS.js', 'lib/edit/editsStore.js', 'lib/edit/attachmentsStore.js' ], - dest: 'dist/offline-edit-src.js' + dest: 'dist/offline-edit-advanced-src.js' }, - editLight: { + editBasic: { src: [ 'lib/edit/offlineJSOptions.js', - 'lib/edit/OfflineFeatureLayer.js', + 'lib/edit/OfflineEditBasic.js', 'lib/edit/OfflineEditNS.js', 'lib/edit/editStorePOLS.js' ], - dest: 'dist/offline-editlight-src.js' + dest: 'dist/offline-edit-basic-src.js' }, /* Tiles basic is for use with WebMaps. Cannot be reloaded or restarted while offline */ tilesBasic: { src: [ - 'lib/tiles/offlineTilesEnabler.js', + 'lib/tiles/OfflineTilesBasic.js', 'lib/tiles/OfflineTilesNS.js', 'lib/tiles/base64utils.js', 'lib/tiles/FileSaver.js', @@ -83,7 +84,7 @@ module.exports = function(grunt) { /* Tiles advanced is for use with tiled map services. Works with reload or restart while offline */ tilesAdvanced: { src: [ - 'lib/tiles/offlineTilesEnablerLayer.js', + 'lib/tiles/OfflineTilesAdvanced.js', 'lib/tiles/OfflineTilesNS.js', 'lib/tiles/base64utils.js', 'lib/tiles/FileSaver.js', @@ -113,6 +114,9 @@ module.exports = function(grunt) { compress: { drop_console: true //remove console.log statements :) }, + beautify: { + semicolons: false + }, preserveComments: 'some', wrap: false // mangle: { @@ -121,8 +125,8 @@ module.exports = function(grunt) { }, dist: { files: { - 'dist/offline-edit-min.js': ['dist/offline-edit-src.js'], - 'dist/offline-editlight-min.js': ['dist/offline-editlight-src.js'], + 'dist/offline-edit-advanced-min.js': ['dist/offline-edit-advanced-src.js'], + 'dist/offline-edit-basic-min.js': ['dist/offline-edit-basic-src.js'], 'dist/offline-tiles-basic-min.js': ['dist/offline-tiles-basic-src.js'], 'dist/offline-tiles-advanced-min.js': ['dist/offline-tiles-advanced-src.js'], 'dist/offline-tpk-min.js': ['dist/offline-tpk-src.js'] From ed0b41301a3da2a71b00c19c41a48f519b284948 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Mon, 23 Nov 2015 13:04:36 -0700 Subject: [PATCH 28/48] addt'l refactoring of tests --- test/SpecRunner.offlineEditAdvanced.TokenBased.html | 2 +- test/SpecRunner.offlineEditAdvanced.oAuth.html | 2 +- test/SpecRunner.offlineTilesAdvanced.TokenBased.html | 2 +- test/SpecRunner.offlineTilesAdvanced.html | 2 +- test/SpecRunner.offlineTilesBasic.html | 8 ++++---- test/spec/offlineTilesAdvancedSpec.js | 2 +- test/spec/offlineTilesBasicSpec.js | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/test/SpecRunner.offlineEditAdvanced.TokenBased.html b/test/SpecRunner.offlineEditAdvanced.TokenBased.html index 1b9501d6..14e84457 100644 --- a/test/SpecRunner.offlineEditAdvanced.TokenBased.html +++ b/test/SpecRunner.offlineEditAdvanced.TokenBased.html @@ -25,7 +25,7 @@ - + - + - + - + - + - + Include a reference to IndexedDBShim for library to work on Safari 7.x. + May not be required for all Safari browsers. + --> + - + + + + diff --git a/samples/appcache-twofeatureslayer-noedit.html b/samples/appcache-twofeatureslayer-noedit.html index 6b73469b..7590456a 100644 --- a/samples/appcache-twofeatureslayer-noedit.html +++ b/samples/appcache-twofeatureslayer-noedit.html @@ -136,9 +136,10 @@ } - + Include a reference to IndexedDBShim for library to work on Safari 7.x. + May not be required for all Safari browsers. + --> + - + + From 1378b3757dde870b4b902268b9f378a407c7aa65 Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Mon, 23 Nov 2015 16:56:49 -0700 Subject: [PATCH 46/48] Removed indexeddbshim from submodules --- .gitmodules | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index d7406779..ac070090 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "vendor/IndexedDBShim"] - path = vendor/IndexedDBShim - url = https://github.com/axemclion/IndexedDBShim.git [submodule "vendor/jasmine.async"] path = vendor/jasmine.async url = https://github.com/derickbailey/jasmine.async.git From 663259225dde122dcd4b0cc57d1daedcf27ee86f Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Mon, 23 Nov 2015 16:57:11 -0700 Subject: [PATCH 47/48] Commented out image string to console --- lib/tpk/TPKLayer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tpk/TPKLayer.js b/lib/tpk/TPKLayer.js index 98d5d120..6f9f2b32 100644 --- a/lib/tpk/TPKLayer.js +++ b/lib/tpk/TPKLayer.js @@ -144,7 +144,7 @@ define([ // sets img visibility to 'hidden', so we need to show the image back once we have put the data:image img.style.visibility = "visible"; img.src = imgURL; - console.log("URL length " + imgURL.length + ", image: " + imgURL); + //console.log("URL length " + imgURL.length + ", image: " + imgURL); this.emit(this.PROGRESS_EVENT,this.PROGRESS_END); return ""; /* this result goes nowhere, seriously */ From d914d1b1603dbfdfc13c9b3bc56ea103a6364dfd Mon Sep 17 00:00:00 2001 From: Andy Gup Date: Mon, 23 Nov 2015 16:57:49 -0700 Subject: [PATCH 48/48] v3.0 --- CHANGELOG.md | 35 + README.md | 13 +- dist/offline-edit-advanced-min.js | 226 ++ dist/offline-edit-advanced-src.js | 3430 ++++++++++++++++++++++++++++ dist/offline-edit-basic-min.js | 97 + dist/offline-edit-basic-src.js | 1457 ++++++++++++ dist/offline-tiles-advanced-min.js | 93 +- dist/offline-tiles-advanced-src.js | 8 +- dist/offline-tiles-basic-min.js | 82 +- dist/offline-tiles-basic-src.js | 8 +- dist/offline-tpk-min.js | 343 ++- dist/offline-tpk-src.js | 6 +- package.json | 2 +- 13 files changed, 5772 insertions(+), 28 deletions(-) create mode 100644 dist/offline-edit-advanced-min.js create mode 100644 dist/offline-edit-advanced-src.js create mode 100644 dist/offline-edit-basic-min.js create mode 100644 dist/offline-edit-basic-src.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 331da73f..eeb05bb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ # offline-editor-js - Changelog +## Version 3.0 - Nov. 23, 2015 + +Has many breaking changes due to new naming conventions. + +This version adds a new, lightweight (14Kb) editing library aimed at intermittent offline-only workflows. And, there was a significant amount of refactoring aimed at fixing and simplifying the library's naming conventions. + +This version implements a consistent naming convention that uses "basic" and "advanced". If `basic` is in the name of the distribution library that means support for intermittent offline-only. `advanced` in the name means support for both intermittent and full offline usage including browser restarts while offline. + +In general, migrating from v2.x to 3.x should be a straightforward exercise in simple refactoring of library names. + +**Enhancements** +* Creates a new `OfflineEditBasic` library. This lightweight (14Kb) library is designed specifically for easy use and intermittent offline-only editing use cases. +* Created `offline-edit-basic` and `offline-edit-advanced` for both `src` and `min` versions. +* Updates documentation, samples and unit tests to reflect the name changes. + +**Refactored** +* `offlineFeaturesManager.js` renamed `OfflineEditAdvanced.js`. No other changes were made to this library. +* `offlineTilesEnabler.js` renamed `OfflineTilesBasic.js`. +* `OfflineTilesEnablerLayer.js` renamed `OfflineTilesAdvanced`. No other changes made. +* `tiles-indexed-db.html` renamed to `simple-tiles.html`. +* All samples have been updated to reflect the new library names. +* All test specs have been updated and refactored to reflect the new library names. + +**Deprecated** +* Deprecated `offline-edit-min.js` and `offline-edit-src.js`. +* In `OfflineEditAdvanced.js` removed deprecated functions `_cleanSuccessfulEditsDatabaseRecords()`, `_internalApplyEdits()`. +* In `editStore.js` removed deprecated function `resetLimitedPhantomGraphicsQueue()`. +* In `editStore.js` removed unused functions `_serialize()`, `_deserialize()`. +* Deleted `/demo/samples` directory. +* Removed Offline.js and IndexedDBShim from sub-module depencies. You'll need to reference these via their respective CDNs for gh-pages URL. + +**Bug Fixes** +* Minor - `OfflineEditAdvanced` no longer throws an error when reinitialized with a featureCollection. + + ## Version 2.16 - Oct. 29, 2015 No breaking changes. Recommended update. diff --git a/README.md b/README.md index ea5e961e..38b64183 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Online samples and getting started tutorials are available here: **[http://esri. # Libraries -This repo contains the following libraries in the `/dist` directory. +This repo contains the following libraries in the `/dist` directory. The use of `basic` in the name indicates intermittent offline-only, and `advanced` indicates the library can be used for both intermittent and full offline. Use_Case | Name, Description and gh-pages URL --- | --- @@ -22,7 +22,7 @@ Basic map tiles | **`offline-tiles-basic-min.js`** Caches map tiles for simple, Advanced map tiles | **`offline-tiles-advanced-min.js`** Used for intermittent and full offline tile caching. Extends any ArcGIS Tiled Map Service. This library should be used in conjunction with an HTML5 Application Cache Manifest coding pattern.

        [`http://esri.github.io/offline-editor-js/dist/offline-tiles-advanced-min.js`](http://esri.github.io/offline-editor-js/dist/offline-tiles-advanced-min.js) TPK files | **`offline-tpk-min.js`** Reads TPK files and displays and caches them as a tiled map layer.

        [`http://esri.github.io/offline-editor-js/dist/offline-tpk-min.js`](http://esri.github.io/offline-editor-js/dist/offline-tpk-min.js) -`src` files are for software development-only. The`min` versions are minified and should be used in production. The use of `basic` in the name indicates intermittent offline-only, and `advanced` indicates the library can be used for both intermittent and full offline. +`src` files are for software development-only. The`min` versions are minified and should be used in production. #Workflows Supported The following workflow is currently supported for both both features and tiles: @@ -70,6 +70,7 @@ Go __[here](https://github.com/Esri/offline-editor-js/wiki/FAQ)__ for answers to ##Limitations * Currently does not support related tables, domains or subtypes. The ArcGIS Runtime SDKs fully support these and more. +* There are browser limitations and technical dependencies. The offline capabilities in this toolkit depend on certain JavaScript capabilities being present in the browser. Go [here](doc/dependencies.md) for a detailed breakdown. * Attachments are supported with some limitations listed [here](./doc/attachments.md). * Browser storage space on mobile devices is a known limitation. This applies to stand-alone web applications and hybrid applications. @@ -79,17 +80,15 @@ Go __[here](https://github.com/Esri/offline-editor-js/wiki/FAQ)__ for answers to ##Dependencies -* ArcGIS API for JavaScript (v3.12+) +* [ArcGIS API for JavaScript (v3.12+)](https://developers.arcgis.com/javascript/) * [Offline.js](http://github.hubspot.com/offline/docs/welcome/) - it allows detection of the online/offline condition and provides events to hook callbacks on when this condition changes * Node.js required for building the source -* NOTE: browser limitations and technical dependencies. The offline capabilities in this toolkit depend on certain HTML5 capabilities being present in the browser. Go [here](doc/dependencies.md) for a detailed breakdown of the information. +* [IndexedDBShim](https://github.com/axemclion/IndexedDBShim) - polyfill to simulate indexedDB functionality in browsers/platforms where it is not supported notably older versions desktop Safari and iOS Safari. * Sub-modules (see `/vendor` directory) - * [IndexedDBShim](https://github.com/axemclion/IndexedDBShim) - polyfill to simulate indexedDB functionality in browsers/platforms where it is not supported (notably desktop Safari and iOS Safari) - - IMPORTANT: There are known [issues](https://github.com/axemclion/IndexedDBShim/issues/115) with IndexedDBShim on Safari. For Safari, the storage error workaround is to switch from using /dist/IndexedDBShim.min.js to just using IndexedDBShim.js and then search for and modify the line that defines the value for `DEFAULT_DB_SIZE`. Set this to more appropriate size that will meet all your storage needs, for example: ```var DEFAULT_DB_SIZE = 24 * 1024 * 1024``` * [jasmine.async](https://github.com/derickbailey/jasmine.async.git) - Used specifically for unit testing. -* Non sub-module based libraries +* Non sub-module based libraries that are used internally by this project * [FileSaver.js](https://github.com/Esri/offline-editor-js/blob/master/lib/tiles/README.md) - library to assist with uploading and downloading of files containing tile information. * [grunt-manifest](https://github.com/gunta/grunt-manifest) node.js library to assist with the creation of manifest files. * [zip](http://gildas-lormeau.github.io/zip.js/) A library for zipping and unzipping files. diff --git a/dist/offline-edit-advanced-min.js b/dist/offline-edit-advanced-min.js new file mode 100644 index 00000000..a105dd15 --- /dev/null +++ b/dist/offline-edit-advanced-min.js @@ -0,0 +1,226 @@ +/*! esri-offline-maps - v3.0.0 - 2015-11-23 +* Copyright (c) 2015 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") +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) +return e}if(n.attachmentsStore){var f=new b +return n.attachmentsStore.getAttachmentsByFeatureId(this.url,a,function(a){c&&c(a),f.resolve(a)}),f}},a.addAttachment=function(a,c,d,e){if(n.getOnlineStatus()===n.ONLINE)return this._addAttachment(a,c,function(){n.emit(n.events.ATTACHMENTS_SENT,arguments),d&&d.apply(this,arguments)},function(a){e&&e.apply(this,arguments)}) +if(n.attachmentsStore){var f=this._getFilesFromForm(c),g=f[0],i=new b,j=this._getNextTempId() +return n.attachmentsStore.store(this.url,j,a,g,n.attachmentsStore.TYPE.ADD,function(b,c){var f={attachmentId:j,objectId:a,success:b} +if(b){n.emit(n.events.ATTACHMENT_ENQUEUED,f),d&&d(f),i.resolve(f) +var g=this._url.path+"/"+a+"/attachments/"+j,k=h("[href="+g+"]") +k.attr("href",c.url)}else f.error="can't store attachment",e&&e(f),i.reject(f)}.bind(this)),i}},a.updateAttachment=function(a,c,d,e,f){if(n.getOnlineStatus()===n.ONLINE)return this._updateAttachment(a,c,d,function(){e&&e.apply(this,arguments)},function(a){f&&f.apply(this,arguments)}) +if(n.attachmentsStore){var g=this._getFilesFromForm(d),i=g[0],j=n.attachmentsStore.TYPE.UPDATE,k=new b +return 0>c&&(j=n.attachmentsStore.TYPE.ADD),n.attachmentsStore.store(this.url,c,a,i,j,function(b,d){var g={attachmentId:c,objectId:a,success:b} +if(b){n.emit(n.events.ATTACHMENT_ENQUEUED,g),e&&e(g),k.resolve(g) +var i=this._url.path+"/"+a+"/attachments/"+c,j=h("[href="+i+"]") +j.attr("href",d.url)}else g.error="layer.updateAttachment::attachmentStore can't store attachment",f&&f(g),k.reject(g)}.bind(this)),k}},a.deleteAttachments=function(a,d,e,f){if(n.getOnlineStatus()===n.ONLINE){var g=this._deleteAttachments(a,d,function(){e&&e.apply(this,arguments)},function(a){f&&f.apply(this,arguments)}) +return g}if(n.attachmentsStore){var h=[] +d.forEach(function(c){c=parseInt(c,10) +var d=new b +if(0>c)n.attachmentsStore["delete"](c,function(b){var e={objectId:a,attachmentId:c,success:b} +d.resolve(e)}) +else{var e=new Blob([],{type:"image/png"}) +n.attachmentsStore.store(this.url,c,a,e,n.attachmentsStore.TYPE.DELETE,function(b,e){var f={attachmentId:c,objectId:a,success:b} +b?d.resolve(f):d.reject(f)}.bind(this))}h.push(d)},this) +var i=c(h) +return i.then(function(a){e&&e(a)}),i}},a.applyEdits=function(d,e,f,g,h){var i=[] +if(n.getOnlineStatus()===n.ONLINE){var j=this._applyEdits(d,e,f,function(){n.emit(n.events.EDITS_SENT,arguments),g&&g.apply(this,arguments)},h) +return j}var k=new b,l={addResults:[],updateResults:[],deleteResults:[]},m={},o=d||[] +return o.forEach(function(a){var c=new b,d=this._getNextTempId() +a.attributes[this.objectIdField]=d +var e=this +this._validateFeature(a,this.url,n._editStore.ADD).then(function(b){b.success?e._pushValidatedAddFeatureToDB(e,a,b.operation,l,d,c):c.resolve(!0)},function(a){c.reject(a)}),i.push(c)},this),e=e||[],e.forEach(function(a){var c=new b,d=a.attributes[this.objectIdField] +m[d]=a +var e=this +this._validateFeature(a,this.url,n._editStore.UPDATE).then(function(b){b.success?e._pushValidatedUpdateFeatureToDB(e,a,b.operation,l,d,c):c.resolve(!0)},function(a){c.reject(a)}),i.push(c)},this),f=f||[],f.forEach(function(a){var c=new b,d=a.attributes[this.objectIdField],e=this +this._validateFeature(a,this.url,n._editStore.DELETE).then(function(b){b.success?e._pushValidatedDeleteFeatureToDB(e,a,b.operation,l,d,c):c.resolve(!0)},function(a){c.reject(a)}),i.push(c)},this),c(i).then(function(b){for(var c=!0,d=0;dg;g++){var h=a[g].toJson() +if(f.push(h),g==e-1){var i=JSON.stringify(f),j=JSON.stringify(d) +c(i,j) +break}}},a.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)})})},a.setFeatureLayerJSONDataStore=function(a,b){n._editStore.pushFeatureLayerJSON(a,function(a,c){b(a,c)})},a.getFeatureLayerJSONDataStore=function(a){n._editStore.getFeatureLayerJSON(function(b,c){a(b,c)})},a.setPhantomLayerGraphics=function(a){var b=a.length +if(b>0)for(var c=0;b>c;c++){var d=new k(a[c]) +this._phantomLayer.add(d)}},a.getPhantomLayerGraphics=function(b){for(var c=a._phantomLayer.graphics,d=a._phantomLayer.graphics.length,e=[],f=0;d>f;f++){var g=c[f].toJson() +if(e.push(g),f==d-1){var h=JSON.stringify(e) +b(h) +break}}},a.getPhantomGraphicsArray=function(a){n._editStore.getPhantomGraphicsArray(function(b,c){"end"==c?a(!0,b):a(!1,c)})},a.getAttachmentsUsage=function(a){n.attachmentsStore.getUsage(function(b,c){a(b,c)})},a.resetAttachmentsDatabase=function(a){n.attachmentsStore.resetAttachmentsQueue(function(b,c){a(b,c)})},a.getUsage=function(a){n._editStore.getUsage(function(b,c){a(b,c)})},a.resetDatabase=function(a){n._editStore.resetEditsQueue(function(b,c){a(b,c)})},a.pendingEditsCount=function(a){n._editStore.pendingEditsCount(function(b){a(b)})},a.getFeatureDefinition=function(a,b,c,d){var e={layerDefinition:a,featureSet:{features:b,geometryType:c}} +d(e)},a.getAllEditsArray=function(a){n._editStore.getAllEditsArray(function(b,c){"end"==c?a(!0,b):a(!1,c)})},a._pushFeatureCollections=function(b){n._editStore._getFeatureCollections(function(c,d){var e={featureLayerUrl:a.url,featureLayerCollection:a.toJson()},f=[e],g={id:n._editStore.FEATURE_COLLECTION_ID,featureCollections:f} +if(a.hasAttachments=e.featureLayerCollection.layerDefinition.hasAttachments,c){for(var h=0,i=0;id;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} +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) +break}}},getFeatureCollections:function(a){this._editStore._isDBInit?this._editStore._getFeatureCollections(function(b,c){a(b,c)}):this._initializeDB(null,null).then(function(b){b.success&&this._editStore._getFeatureCollections(function(b,c){a(b,c)})}.bind(this),function(b){a(!1,b)})},getFeatureLayerJSONDataStore:function(a){this._editStore._isDBInit?this._editStore.getFeatureLayerJSON(function(b,c){a(b,c)}):this._initializeDB(null,null).then(function(b){b.success&&this._editStore.getFeatureLayerJSON(function(b,c){a(b,c)})}.bind(this),function(b){a(!1,b)})},_initializeDB:function(a,c){var d=new b,e=this._editStore +return e.dbName=this.DB_NAME,e.objectStoreName=this.DB_OBJECTSTORE_NAME,e.objectId=this.DB_UID,e.init(function(b,f){"object"==typeof a&&b===!0&&void 0!==a&&null!==a?(c&&(a.__featureLayerURL=c),e.pushFeatureLayerJSON(a,function(a,b){a?d.resolve({success:!0,error:null}):d.reject({success:!1,error:b})})):b?d.resolve({success:!0,error:null}):d.reject({success:!1,error:null})}),d},_checkFileAPIs:function(){return window.File&&window.FileReader&&window.FileList&&window.Blob?(XMLHttpRequest.prototype.sendAsBinary||(XMLHttpRequest.prototype.sendAsBinary=function(a){function b(a){return 255&a.charCodeAt(0)}var c=Array.prototype.map.call(a,b),d=new Uint8Array(c) +this.send(d.buffer)}),!0):!1},_extendAjaxReq:function(a){a.sendAsBinary=XMLHttpRequest.prototype.sendAsBinary},_phantomSymbols:[],_getPhantomSymbol:function(a,b){if(0===this._phantomSymbols.length){var c=[0,255,0,255],d=1.5 +this._phantomSymbols.point=[],this._phantomSymbols.point[this._editStore.ADD]=new m({type:"esriSMS",style:"esriSMSCross",xoffset:10,yoffset:10,color:[255,255,255,0],size:15,outline:{color:c,width:d,type:"esriSLS",style:"esriSLSSolid"}}),this._phantomSymbols.point[this._editStore.UPDATE]=new m({type:"esriSMS",style:"esriSMSCircle",xoffset:0,yoffset:0,color:[255,255,255,0],size:15,outline:{color:c,width:d,type:"esriSLS",style:"esriSLSSolid"}}),this._phantomSymbols.point[this._editStore.DELETE]=new m({type:"esriSMS",style:"esriSMSX",xoffset:0,yoffset:0,color:[255,255,255,0],size:15,outline:{color:c,width:d,type:"esriSLS",style:"esriSLSSolid"}}),this._phantomSymbols.multipoint=null,this._phantomSymbols.polyline=[],this._phantomSymbols.polyline[this._editStore.ADD]=new n({type:"esriSLS",style:"esriSLSSolid",color:c,width:d}),this._phantomSymbols.polyline[this._editStore.UPDATE]=new n({type:"esriSLS",style:"esriSLSSolid",color:c,width:d}),this._phantomSymbols.polyline[this._editStore.DELETE]=new n({type:"esriSLS",style:"esriSLSSolid",color:c,width:d}),this._phantomSymbols.polygon=[],this._phantomSymbols.polygon[this._editStore.ADD]=new o({type:"esriSFS",style:"esriSFSSolid",color:[255,255,255,0],outline:{type:"esriSLS",style:"esriSLSSolid",color:c,width:d}}),this._phantomSymbols.polygon[this._editStore.UPDATE]=new o({type:"esriSFS",style:"esriSFSSolid",color:[255,255,255,0],outline:{type:"esriSLS",style:"esriSLSDash",color:c,width:d}}),this._phantomSymbols.polygon[this._editStore.DELETE]=new o({type:"esriSFS",style:"esriSFSSolid",color:[255,255,255,0],outline:{type:"esriSLS",style:"esriSLSDot",color:c,width:d}})}return this._phantomSymbols[a.type][b]},_uploadAttachment:function(a){var c=new b,d=this._featureLayers[a.featureLayerUrl],e=new FormData +switch(e.append("attachment",a.file),a.type){case this.attachmentsStore.TYPE.ADD:d.addAttachment(a.objectId,e,function(b){c.resolve({attachmentResult:b,id:a.id})},function(a){c.reject(a)}) +break +case this.attachmentsStore.TYPE.UPDATE:e.append("attachmentId",a.id),d._sendAttachment("update",a.objectId,e,function(b){c.resolve({attachmentResult:b,id:a.id})},function(a){c.reject(a)}) +break +case this.attachmentsStore.TYPE.DELETE:d.deleteAttachments(a.objectId,[a.id],function(b){c.resolve({attachmentResult:b,id:a.id})},function(a){c.reject(a)})}return c.promise},_deleteAttachmentFromDB:function(a,c){var d=new b +return this.attachmentsStore["delete"](a,function(a){d.resolve({success:a,result:c})}),d},_cleanAttachmentsDB:function(a,b){var d=this,e=[],f=0 +a.forEach(function(a){"object"==typeof a.attachmentResult&&a.attachmentResult.success?e.push(d._deleteAttachmentFromDB(a.id,null)):a.attachmentResult instanceof Array?a.attachmentResult.forEach(function(b){b.success?e.push(d._deleteAttachmentFromDB(a.id,null)):f++}):f++}) +var g=c(e) +g.then(function(c){b(f>0?{errors:!0,attachmentsDBResults:c,uploadResults:a}:{errors:!1,attachmentsDBResults:c,uploadResults:a})})},_sendStoredAttachments:function(a){this.attachmentsStore.getAllAttachments(function(b){var d=this,e=[] +b.forEach(function(a){var b=this._uploadAttachment(a) +e.push(b)},this) +var f=c(e) +f.then(function(b){d._cleanAttachmentsDB(b,function(c){c.errors?a&&a(!1,b,c):a&&a(!0,b,c)})},function(b){a&&a(!1,b)})}.bind(this))},_replayStoredEdits:function(a){var b,d={},e=this,f=[],g=[],h=[],i=[],j=[],l=this._featureLayers,m=this.attachmentsStore,n=this._editStore +this._editStore.getAllEditsArray(function(o,p){if(o.length>0){j=o +for(var q=j.length,r=0;q>r;r++){b=l[j[r].layer],null==m&&b.hasAttachments,b._attachmentsStore=m,b.__onEditsComplete=b.onEditsComplete,b.onEditsComplete=function(){},f=[],g=[],h=[],i=[] +var s=new k(j[r].graphic) +switch(j[r].operation){case n.ADD:for(var t=0;t0){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 +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] +this._editStore.deletePhantomGraphic(c,function(a,b){a?g.resolve({success:!0,error:null,id:c}):g.reject({success:!1,error:b,id:c})})}else g.reject({success:!1,error:b,id:c})}.bind(this)),g.promise},_makeEditRequest:function(a,b,c,d,f,g){var h="f=json",i="",j="",k="" +if(b.length>0&&(e.forEach(b,function(a){a.hasOwnProperty("infoTemplate")&&delete a.infoTemplate},this),i="&adds="+JSON.stringify(b)),c.length>0&&(e.forEach(c,function(a){a.hasOwnProperty("infoTemplate")&&delete a.infoTemplate},this),j="&updates="+JSON.stringify(c)),d.length>0){var l=d[0].attributes[this.DB_UID] +k="&deletes="+l}var m=h+i+j+k +a.hasOwnProperty("credential")&&a.credential&&a.credential.hasOwnProperty("token")&&a.credential.token&&(m=m+"&token="+a.credential.token) +var n=new XMLHttpRequest +n.open("POST",a.url+"/applyEdits",!0),n.setRequestHeader("Content-type","application/x-www-form-urlencoded"),n.onload=function(){if(200===n.status&&""!==n.responseText)try{var a=JSON.parse(this.response) +f(a.addResults,a.updateResults,a.deleteResults)}catch(b){g("Unable to parse xhr response",n)}},n.onerror=function(a){g(a)},n.ontimeout=function(){g("xhr timeout error")},n.timeout=this._defaultXhrTimeout,n.send(m)},_parseResponsesArray:function(a){var c=new b,d=0 +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()} +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)} +var g=f.objectStore(this.objectStoreName) +g.put(e)}},this.pushFeatureLayerJSON=function(a,b){"object"!=typeof a&&b(!1,"dataObject type is not an object.") +var c=this._db +a.id=this.FEATURE_LAYER_JSON_ID,this.getFeatureLayerJSON(function(d,e){var f +if(d&&"undefined"!=typeof e){f=c.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName) +for(var g in a)a.hasOwnProperty(g)&&(e[g]=a[g]) +var h=f.put(e) +h.onsuccess=function(){b(!0,null)},h.onerror=function(a){b(!1,a)}}else{var i=c.transaction([this.objectStoreName],"readwrite") +i.oncomplete=function(a){b(!0,null)},i.onerror=function(a){b(!1,a.target.error.message)},f=i.objectStore(this.objectStoreName) +try{f.put(a)}catch(j){b(!1,JSON.stringify(j))}}}.bind(this))},this.getFeatureLayerJSON=function(a){var b=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName),c=b.get(this.FEATURE_LAYER_JSON_ID) +c.onsuccess=function(){var b=c.result +"undefined"!=typeof b?a(!0,b):a(!1,"nothing found")},c.onerror=function(b){a(!1,b)}},this.deleteFeatureLayerJSON=function(a){var b=this._db,c=null,d=this,e=this.FEATURE_LAYER_JSON_ID +require(["dojo/Deferred"],function(f){c=new f,c.then(function(b){d.editExists(e).then(function(b){a(!1,{message:"object was not deleted."})},function(b){a(!0,{message:"id does not exist"})})},function(b){a(!1,{message:"id does not exist"})}),d.editExists(e).then(function(a){var f=b.transaction([d.objectStoreName],"readwrite").objectStore(d.objectStoreName),g=f["delete"](e) +g.onsuccess=function(){c.resolve(!0)},g.onerror=function(a){c.reject({success:!1,error:a})}},function(a){c.reject({success:!1,message:a})}.bind(this))})},this.pushPhantomGraphic=function(a,b){var c=this._db,d=this.PHANTOM_GRAPHIC_PREFIX+this._PHANTOM_PREFIX_TOKEN+a.attributes[this.objectId],e={id:d,graphic:a.toJson()},f=c.transaction([this.objectStoreName],"readwrite") +f.oncomplete=function(a){b(!0,null)},f.onerror=function(a){b(!1,a.target.error.message)} +var g=f.objectStore(this.objectStoreName) +g.put(e)},this.getPhantomGraphicsArray=function(a){var b=[] +if(null!==this._db){var c=this.PHANTOM_GRAPHIC_PREFIX,d=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor() +d.onsuccess=function(d){var e=d.target.result +e&&e.value&&e.value.id?(-1!=e.value.id.indexOf(c)&&b.push(e.value),e["continue"]()):a(b,"end")}.bind(this),d.onerror=function(b){a(null,b)}}else a(null,"no db")},this._getPhantomGraphicsArraySimple=function(a){var b=[] +if(null!==this._db){var c=this.PHANTOM_GRAPHIC_PREFIX,d=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor() +d.onsuccess=function(d){var e=d.target.result +e&&e.value&&e.value.id?(-1!=e.value.id.indexOf(c)&&b.push(e.value.id),e["continue"]()):a(b,"end")}.bind(this),d.onerror=function(b){a(null,b)}}else a(null,"no db")},this.deletePhantomGraphic=function(a,b){var c=this._db,d=null,e=this +require(["dojo/Deferred"],function(f){d=new f,e.editExists(a).then(function(f){d.then(function(c){e.editExists(a).then(function(a){b(!1,"item was not deleted")},function(a){b(!0,"item successfully deleted")})},function(a){b(!1,a)}) +var g=c.transaction([e.objectStoreName],"readwrite").objectStore(e.objectStoreName),h=g["delete"](a) +h.onsuccess=function(){d.resolve(!0)},h.onerror=function(a){d.reject({success:!1,error:a})}},function(a){b(!1,"item doesn't exist in db")})})},this.resetPhantomGraphicsQueue=function(a){var b=this._db +this._getPhantomGraphicsArraySimple(function(c){if(c!=[]){var d=0,e=b.transaction([this.objectStoreName],"readwrite"),f=e.objectStore(this.objectStoreName) +f.onerror=function(){d++},e.oncomplete=function(){a(0===d?!0:!1)} +for(var g=c.length,h=0;g>h;h++)f["delete"](c[h])}else a(!0)}.bind(this))},this.getEdit=function(a,b){var c=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName) +if("undefined"==typeof a)return void b(!1,"id is undefined.") +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=[] +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]) +f.onsuccess=function(){f.result +var g={id:b+"/"+c.attributes[this.objectId],operation:a,layer:b,graphic:c.toJson()},h=e.put(g) +h.onsuccess=function(){d(!0)},h.onerror=function(a){d(!1,a)}}.bind(this)},this["delete"]=function(a,b,c){var d=this._db,e=null,f=this,g=a+"/"+b.attributes[this.objectId] +require(["dojo/Deferred"],function(a){e=new a,f.editExists(g).then(function(a){e.then(function(a){f.editExists(g).then(function(a){c(!1)},function(a){c(!0)})},function(a){c(!1,a)}) +var b=d.transaction([f.objectStoreName],"readwrite").objectStore(f.objectStoreName),h=b["delete"](g) +h.onsuccess=function(){e.resolve(!0)},h.onerror=function(a){e.reject({success:!1,error:a})}},function(a){c(!1)})})},this.resetEditsQueue=function(a){var b=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName).clear() +b.onsuccess=function(b){setTimeout(function(){a(!0)},0)},b.onerror=function(b){a(!1,b)}},this.pendingEditsCount=function(a){var b=0,c=this.FEATURE_LAYER_JSON_ID,d=this.FEATURE_COLLECTION_ID,e=this.PHANTOM_GRAPHIC_PREFIX,f=this._db.transaction([this.objectStoreName],"readwrite"),g=f.objectStore(this.objectStoreName) +g.openCursor().onsuccess=function(f){var g=f.target.result +g&&g.value&&g.value.id&&-1==g.value.id.indexOf(e)?(g.value.id!==c&&g.value.id!==d&&b++,g["continue"]()):a(b)}},this.editExists=function(a){var b=this._db,c=null,d=this +return require(["dojo/Deferred"],function(e){c=new e +var f=b.transaction([d.objectStoreName],"readwrite").objectStore(d.objectStoreName),g=f.get(a) +g.onsuccess=function(){var b=g.result +b&&b.id==a?c.resolve({success:!0,error:null}):c.reject({success:!1,error:"objectId is not a match."})},g.onerror=function(a){c.reject({success:!1,error:a})}}),c},this.getUsage=function(a){var b=this.FEATURE_LAYER_JSON_ID,c=this.FEATURE_COLLECTION_ID,d=this.PHANTOM_GRAPHIC_PREFIX,e={sizeBytes:0,editCount:0},f=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor() +f.onsuccess=function(f){var g=f.target.result +if(g&&g.value&&g.value.id){var h=g.value,i=JSON.stringify(h) +e.sizeBytes+=i.length,-1==g.value.id.indexOf(d)&&g.value.id!==b&&g.value.id!==c&&(e.editCount+=1),g["continue"]()}else a(e,null)},f.onerror=function(b){a(null,b)}},this._pushFeatureCollections=function(a,b){var c=this._db.transaction([this.objectStoreName],"readwrite") +c.oncomplete=function(a){b(!0)},c.onerror=function(a){b(!1,a.target.error.message)} +var d=c.objectStore(this.objectStoreName) +d.put(a)},this._getFeatureCollections=function(a){var b=this._db.transaction([this.objectStoreName],"readonly").objectStore(this.objectStoreName),c=b.get(this.FEATURE_COLLECTION_ID) +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") +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 +c?b(!0,c):b(!1,"not found")},d.onerror=function(a){b(!1,a)}},this.getAttachmentsByFeatureId=function(a,b,c){var d=a+"/"+b,e=[],f=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName),g=f.index("featureId"),h=IDBKeyRange.only(d) +g.openCursor(h).onsuccess=function(a){var b=a.target.result +b?(e.push(b.value),b["continue"]()):c(e)}},this.getAttachmentsByFeatureLayer=function(a,b){var c=[],d=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName),e=d.index("featureLayerUrl"),f=IDBKeyRange.only(a) +e.openCursor(f).onsuccess=function(a){var d=a.target.result +d?(c.push(d.value),d["continue"]()):b(c)}},this.getAllAttachments=function(a){var b=[],c=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName) +c.openCursor().onsuccess=function(c){var d=c.target.result +d?(b.push(d.value),d["continue"]()):a(b)}},this.deleteAttachmentsByFeatureId=function(a,b,c){var d=a+"/"+b,e=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName),f=e.index("featureId"),g=IDBKeyRange.only(d),h=0 +f.openCursor(g).onsuccess=function(a){var b=a.target.result +b?(e["delete"](b.primaryKey),h++,b["continue"]()):setTimeout(function(){c(h)},0)}.bind(this)},this["delete"]=function(a,b){this.retrieve(a,function(c,d){if(!c)return void b(!1,"attachment "+a+" not found") +var e=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName)["delete"](a) +e.onsuccess=function(a){setTimeout(function(){b(!0)},0)},e.onerror=function(a){b(!1,a)}}.bind(this))},this.deleteAll=function(a){this.getAllAttachments(function(b){var c=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName).clear() +c.onsuccess=function(b){setTimeout(function(){a(!0)},0)},c.onerror=function(b){a(!1,b)}}.bind(this))},this.replaceFeatureId=function(a,b,c,d){var e=a+"/"+b,f=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName),g=f.index("featureId"),h=IDBKeyRange.only(e),i=0 +g.openCursor(h).onsuccess=function(b){var e=b.target.result +if(e){var g=a+"/"+c,h=e.value +h.featureId=g,h.objectId=c,f.put(h),i++,e["continue"]()}else setTimeout(function(){d(i)},1)}},this.getUsage=function(a){var b={sizeBytes:0,attachmentCount:0},c=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor() +c.onsuccess=function(c){var d=c.target.result +if(d){var e=d.value,f=JSON.stringify(e) +b.sizeBytes+=f.length,b.attachmentCount+=1,d["continue"]()}else a(b,null)}.bind(this),c.onerror=function(b){a(null,b)}},this.resetAttachmentsQueue=function(a){var b=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName).clear() +b.onsuccess=function(b){setTimeout(function(){a(!0)},0)},b.onerror=function(b){a(!1,b)}},this._readFile=function(a,b){var c=new FileReader +c.onload=function(a){b(!0,a.target.result)},c.onerror=function(a){b(!1,a.target.result)},c.readAsBinaryString(a)},this.init=function(a){var b=indexedDB.open(this.dbName,12) +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) +var c=b.createObjectStore(this.objectStoreName,{keyPath:"id"}) +c.createIndex("featureId","featureId",{unique:!1}),c.createIndex("featureLayerUrl","featureLayerUrl",{unique:!1})}.bind(this),b.onsuccess=function(b){this._db=b.target.result,a(!0)}.bind(this)}} diff --git a/dist/offline-edit-advanced-src.js b/dist/offline-edit-advanced-src.js new file mode 100644 index 00000000..841b4250 --- /dev/null +++ b/dist/offline-edit-advanced-src.js @@ -0,0 +1,3430 @@ +/*! esri-offline-maps - v3.0.0 - 2015-11-23 +* Copyright (c) 2015 Environmental Systems Research Institute, Inc. +* Apache License*/ +// Configure offline/online detection +// Requires: http://github.hubspot.com/offline/docs/welcome/ + +Offline.options = { // jshint ignore:line + checks: { + image: { + url: function() { + return 'http://esri.github.io/offline-editor-js/tiny-image.png?_=' + (Math.floor(Math.random() * 1000000000)); + } + }, + active: 'image' + } +}; +/*jshint -W030 */ +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 (Evented, Deferred, all, declare, array, domAttr, domStyle, query, + esriConfig, GraphicsLayer, Graphic, esriRequest, SimpleMarkerSymbol, SimpleLineSymbol, SimpleFillSymbol, urlUtils) { + "use strict"; + return declare("O.esri.Edit.OfflineEditAdvanced", [Evented], + { + _onlineStatus: "online", + _featureLayers: {}, + _featureCollectionUsageFlag: false, // if a feature collection was used to create the feature layer. + _editStore: new O.esri.Edit.EditStore(), + _defaultXhrTimeout: 15000, // ms + + ONLINE: "online", // all edits will directly go to the server + OFFLINE: "offline", // edits will be enqueued + RECONNECTING: "reconnecting", // sending stored edits to the server + attachmentsStore: null, // indexedDB for storing attachments + proxyPath: null, // by default we use CORS and therefore proxyPath is null + + ENABLE_FEATURECOLLECTION: false, // Set this to true for full offline use if you want to use the + // getFeatureCollections() pattern of reconstituting a feature layer. + + // Database properties + DB_NAME: "features_store", // Sets the database name. + DB_OBJECTSTORE_NAME: "features",// Represents an object store that allows access to a set of data in the IndexedDB database + DB_UID: "objectid", // Set this based on the unique identifier is set up in the feature service + + ATTACHMENTS_DB_NAME: "attachments_store", //Sets attachments database name + ATTACHMENTS_DB_OBJECTSTORE_NAME: "attachments", + // NOTE: attachments don't have the same issues as Graphics as related to UIDs (e.g. the need for DB_UID). + // You can manually create a graphic, but it would be very rare for someone to + // manually create an attachment. So, we don't provide a public property for + // the attachments database UID. + + // manager emits event when... + events: { + EDITS_SENT: "edits-sent", // ...whenever any edit is actually sent to the server + EDITS_ENQUEUED: "edits-enqueued", // ...when an edit is enqueued (and not sent to the server) + EDITS_ENQUEUED_ERROR: "edits-enqueued-error", // ...when there is an error during the queing process + EDITS_SENT_ERROR: "edits-sent-error", // ...there was a problem with one or more edits! + ALL_EDITS_SENT: "all-edits-sent", // ...after going online and there are no pending edits in the queue + ATTACHMENT_ENQUEUED: "attachment-enqueued", + ATTACHMENTS_SENT: "attachments-sent", + }, + + /** + * Need to call this method only if you want to support offline attachments + * it is optional + * @param callback(success) + * @returns void + */ + initAttachments: function (callback) { + callback = callback || function (success) { + console.log("attachments inited ", success ? "ok" : "failed"); + }; + + if (!this._checkFileAPIs()) { + return callback(false, "File APIs not supported"); + } + + try { + this.attachmentsStore = new O.esri.Edit.AttachmentsStore(); + this.attachmentsStore.dbName = this.ATTACHMENTS_DB_NAME; + this.attachmentsStore.objectStoreName = this.ATTACHMENTS_DB_OBJECTSTORE_NAME; + + if (/*false &&*/ this.attachmentsStore.isSupported()) { + this.attachmentsStore.init(callback); + } + else { + return callback(false, "indexedDB not supported"); + } + } + catch (err) { + console.log("problem! " + err.toString()); + } + }, + + /** + * Overrides a feature layer. Call this AFTER the FeatureLayer's 'update-end' event. + * IMPORTANT: If dataStore is specified it will be saved to the database. Any complex + * objects such as [esri.Graphic] will need to be serialized or you will get an IndexedDB error. + * @param layer + * @param updateEndEvent The FeatureLayer's update-end event object + * @param callback {true, null} or {false, errorString} Traps whether or not the database initialized + * @param dataStore Optional configuration Object. Added @ v2.5. There is only one reserved object key and that is "id". + * Use this option to store featureLayerJSON and any other configuration information you'll need access to after + * a full offline browser restart. + * @returns deferred + */ + extend: function (layer, callback, dataStore) { + + var extendPromises = []; // deferred promises related to initializing this method + + var self = this; + layer.offlineExtended = true; // to identify layer has been extended + + if(!layer.loaded) { + console.error("Make sure to initialize OfflineEditAdvanced after layer loaded and feature layer update-end event."); + } + + // NOTE: At v2.6.1 we've discovered that not all feature layers support objectIdField. + // However, to try to be consistent here with how the library is managing Ids + // we force the layer.objectIdField to DB_UID. This should be consistent with + // how esri.Graphics assign a unique ID to a graphic. If it is not, then this + // library will break and we'll have to re-architect how it manages UIDs. + layer.objectIdField = this.DB_UID; + + var url = null; + + // There have been reproducible use cases showing when a browser is restarted offline that + // for some reason the layer.url may be undefined. + // This is an attempt to minimize the possibility of that situation causing errors. + if(layer.url) { + url = layer.url; + // we keep track of the FeatureLayer object + this._featureLayers[layer.url] = layer; + } + + // This is a potentially brittle solution to detecting if a feature layer collection + // was used to create the feature layer. + // Is there a better way?? + if(layer._mode.featureLayer.hasOwnProperty("_collection")){ + // This means a feature collection was used to create the feature layer and it will + // require different handling when running applyEdit() + this._featureCollectionUsageFlag = true; + } + + // Initialize the database as well as set offline data. + if(!this._editStore._isDBInit) { + extendPromises.push(this._initializeDB(dataStore, url)); + } + + // replace the applyEdits() method + layer._applyEdits = layer.applyEdits; + + + // attachments + layer._addAttachment = layer.addAttachment; + layer._queryAttachmentInfos = layer.queryAttachmentInfos; + layer._deleteAttachments = layer.deleteAttachments; + layer._updateAttachment = layer.updateAttachment; + + /* + operations supported offline: + 1. add a new attachment to an existing feature (DONE) + 2. add a new attachment to a new feature (DONE) + 3. remove an attachment that is already in the server... (DONE) + 4. remove an attachment that is not in the server yet (DONE) + 5. update an existing attachment to an existing feature (DONE) + 6. update a new attachment (DONE) + + concerns: + - manage the relationship between offline features and attachments: what if the user wants to add + an attachment to a feature that is still offline? we need to keep track of objectids so that when + the feature is sent to the server and receives a final objectid we replace the temporary negative id + by its final objectid (DONE) + - what if the user deletes an offline feature that had offline attachments? we need to discard the attachment (DONE) + */ + + // + // attachments + // + layer.queryAttachmentInfos = function (objectId, callback, errback) { + if (self.getOnlineStatus() === self.ONLINE) { + var def = this._queryAttachmentInfos(objectId, + function () { + console.log(arguments); + self.emit(self.events.ATTACHMENTS_INFO, arguments); + callback && callback.apply(this, arguments); + }, + errback); + return def; + } + + if (!self.attachmentsStore) { + console.log("in order to support attachments you need to call initAttachments() method of OfflineEditAdvanced"); + return; + } + + // will only return LOCAL attachments + var deferred = new Deferred(); + self.attachmentsStore.getAttachmentsByFeatureId(this.url, objectId, function (attachments) { + callback && callback(attachments); + deferred.resolve(attachments); + }); + return deferred; + }; + + layer.addAttachment = function (objectId, formNode, callback, errback) { + + if (self.getOnlineStatus() === self.ONLINE) { + return this._addAttachment(objectId, formNode, + function () { + console.log(arguments); + self.emit(self.events.ATTACHMENTS_SENT, arguments); + callback && callback.apply(this, arguments); + }, + function (err) { + console.log("addAttachment: " + err); + errback && errback.apply(this, arguments); + } + ); + } + + if (!self.attachmentsStore) { + console.error("in order to support attachments you need to call initAttachments() method of OfflineEditAdvanced"); + return; + } + + var files = this._getFilesFromForm(formNode); + var file = files[0]; // addAttachment can only add one file, so the rest -if any- are ignored + + var deferred = new Deferred(); + var attachmentId = this._getNextTempId(); + self.attachmentsStore.store(this.url, attachmentId, objectId, file,self.attachmentsStore.TYPE.ADD, function (success, newAttachment) { + var returnValue = {attachmentId: attachmentId, objectId: objectId, success: success}; + if (success) { + self.emit(self.events.ATTACHMENT_ENQUEUED, returnValue); + callback && callback(returnValue); + deferred.resolve(returnValue); + + // replace the default URL that is set by attachmentEditor with the local file URL + var attachmentUrl = this._url.path + "/" + objectId + "/attachments/" + attachmentId; + var attachmentElement = query("[href=" + attachmentUrl + "]"); + attachmentElement.attr("href", newAttachment.url); + } + else { + returnValue.error = "can't store attachment"; + errback && errback(returnValue); + deferred.reject(returnValue); + } + }.bind(this)); + + return deferred; + }; + + layer.updateAttachment = function(objectId, attachmentId, formNode, callback, errback) { + if (self.getOnlineStatus() === self.ONLINE) { + return this._updateAttachment(objectId, attachmentId, formNode, + function () { + callback && callback.apply(this, arguments); + }, + function (err) { + console.log("updateAttachment: " + err); + errback && errback.apply(this, arguments); + }); + //return def; + } + + if (!self.attachmentsStore) { + console.error("in order to support attachments you need to call initAttachments() method of OfflineEditAdvanced"); + return; + } + + var files = this._getFilesFromForm(formNode); + var file = files[0]; // addAttachment can only add one file, so the rest -if any- are ignored + var action = self.attachmentsStore.TYPE.UPDATE; // Is this an ADD or an UPDATE? + + var deferred = new Deferred(); + + // If the attachment has a temporary ID we want to keep it's action as an ADD. + // Otherwise we'll get an error when we try to UPDATE an ObjectId that doesn't exist in ArcGIS Online or Server. + if(attachmentId < 0) { + action = self.attachmentsStore.TYPE.ADD; + } + + self.attachmentsStore.store(this.url, attachmentId, objectId, file, action, function (success, newAttachment) { + var returnValue = {attachmentId: attachmentId, objectId: objectId, success: success}; + if (success) { + self.emit(self.events.ATTACHMENT_ENQUEUED, returnValue); + callback && callback(returnValue); + deferred.resolve(returnValue); + + // replace the default URL that is set by attachmentEditor with the local file URL + var attachmentUrl = this._url.path + "/" + objectId + "/attachments/" + attachmentId; + var attachmentElement = query("[href=" + attachmentUrl + "]"); + attachmentElement.attr("href", newAttachment.url); + } + else { + returnValue.error = "layer.updateAttachment::attachmentStore can't store attachment"; + errback && errback(returnValue); + deferred.reject(returnValue); + } + }.bind(this)); + + return deferred; + }; + + layer.deleteAttachments = function (objectId, attachmentsIds, callback, errback) { + if (self.getOnlineStatus() === self.ONLINE) { + var def = this._deleteAttachments(objectId, attachmentsIds, + function () { + callback && callback.apply(this, arguments); + }, + function (err) { + console.log("deleteAttachments: " + err); + errback && errback.apply(this, arguments); + }); + return def; + } + + if (!self.attachmentsStore) { + console.error("in order to support attachments you need to call initAttachments() method of OfflineEditAdvanced"); + return; + } + + // case 1.- it is a new attachment + // case 2.- it is an already existing attachment + + // asynchronously delete each of the attachments + var promises = []; + attachmentsIds.forEach(function (attachmentId) { + attachmentId = parseInt(attachmentId, 10); // to number + + var deferred = new Deferred(); + + // IMPORTANT: If attachmentId < 0 then it's a local/new attachment + // and we can simply delete it from the attachmentsStore. + // However, if the attachmentId > 0 then we need to store the DELETE + // so that it can be processed and sync'd correctly during _uploadAttachments(). + if(attachmentId < 0) { + self.attachmentsStore.delete(attachmentId, function (success) { + var result = {objectId: objectId, attachmentId: attachmentId, success: success}; + deferred.resolve(result); + }); + } + else { + var dummyBlob = new Blob([],{type: "image/png"}); //TO-DO just a placeholder. Need to consider add a null check. + self.attachmentsStore.store(this.url, attachmentId, objectId, dummyBlob,self.attachmentsStore.TYPE.DELETE, function (success, newAttachment) { + var returnValue = {attachmentId: attachmentId, objectId: objectId, success: success}; + if (success) { + deferred.resolve(returnValue); + } + else { + deferred.reject(returnValue); + } + }.bind(this)); + } + //console.assert(attachmentId < 0, "we only support deleting local attachments"); + promises.push(deferred); + }, this); + + // call callback once all deletes have finished + // IMPORTANT: This returns an array!!! + var allPromises = all(promises); + allPromises.then(function (results) { + callback && callback(results); + }); + + return allPromises; + }; + + // + // other functions + // + + /** + * Overrides the ArcGIS API for JavaSccript applyEdits() method. + * @param adds Creates a new edit entry. + * @param updates Updates an existing entry. + * @param deletes Deletes an existing entry. + * @param callback Called when the operation is complete. + * @param errback An error object is returned if an error occurs + * @returns {*} deferred + * @event EDITS_ENQUEUED boolean if all edits successfully stored while offline + * @event EDITS_ENQUEUED_ERROR string message if there was an error while storing an edit while offline + */ + layer.applyEdits = function (adds, updates, deletes, callback, errback) { + // inside this method, 'this' will be the FeatureLayer + // and 'self' will be the offlineFeatureLayer object + var promises = []; + + if (self.getOnlineStatus() === self.ONLINE) { + var def = this._applyEdits(adds, updates, deletes, + function () { + self.emit(self.events.EDITS_SENT, arguments); + callback && callback.apply(this, arguments); + }, + errback); + return def; + } + + var deferred1 = new Deferred(); + var results = {addResults: [], updateResults: [], deleteResults: []}; + var updatesMap = {}; + + var _adds = adds || []; + _adds.forEach(function (addEdit) { + var deferred = new Deferred(); + + var objectId = this._getNextTempId(); + + addEdit.attributes[this.objectIdField] = objectId; + + var thisLayer = this; + + // We need to run some validation tests against each feature being added. + // Adding the same feature multiple times results in the last edit wins. LIFO. + this._validateFeature(addEdit,this.url,self._editStore.ADD).then(function(result){ + console.log("EDIT ADD IS BACK!!! " ); + + if(result.success){ + thisLayer._pushValidatedAddFeatureToDB(thisLayer,addEdit,result.operation,results,objectId,deferred); + } + else{ + // If we get here then we deleted an edit that was added offline. + // We also have deleted the phantom graphic. + deferred.resolve(true); + } + + },function(error){ + console.log("_validateFeature: Unable to validate!"); + deferred.reject(error); + }); + + promises.push(deferred); + }, this); + + updates = updates || []; + updates.forEach(function (updateEdit) { + var deferred = new Deferred(); + + var objectId = updateEdit.attributes[this.objectIdField]; + updatesMap[objectId] = updateEdit; + + var thisLayer = this; + + // We need to run some validation tests against each feature being updated. + // If we have added a feature and we need to update it then we change it's operation type to "add" + // and the last edits wins. LIFO. + this._validateFeature(updateEdit,this.url,self._editStore.UPDATE).then(function(result){ + console.log("EDIT UPDATE IS BACK!!! " ); + + if(result.success){ + thisLayer._pushValidatedUpdateFeatureToDB(thisLayer,updateEdit,result.operation,results,objectId,deferred); + } + else{ + // If we get here then we deleted an edit that was added offline. + // We also have deleted the phantom graphic. + deferred.resolve(true); + } + + },function(error){ + console.log("_validateFeature: Unable to validate!"); + deferred.reject(error); + }); + + promises.push(deferred); + }, this); + + deletes = deletes || []; + deletes.forEach(function (deleteEdit) { + var deferred = new Deferred(); + + var objectId = deleteEdit.attributes[this.objectIdField]; + + var thisLayer = this; + + // We need to run some validation tests against each feature being deleted. + // If we have added a feature and then deleted it in the app then we go ahead + // and delete it and its phantom graphic from the database. + // NOTE: at this time we don't handle attachments automatically + this._validateFeature(deleteEdit,this.url,self._editStore.DELETE).then(function(result){ + console.log("EDIT DELETE IS BACK!!! " ); + + if(result.success){ + thisLayer._pushValidatedDeleteFeatureToDB(thisLayer,deleteEdit,result.operation,results,objectId,deferred); + } + else{ + // If we get here then we deleted an edit that was added offline. + // We also have deleted the phantom graphic. + deferred.resolve(true); + } + + },function(error){ + console.log("_validateFeature: Unable to validate!"); + deferred.reject(error); + }); + + promises.push(deferred); + }, this); + + all(promises).then(function (r) { + // Make sure all edits were successful. If not throw an error. + var promisesSuccess = true; + for (var v = 0; v < r.length; v++) { + if (r[v] === false) { + promisesSuccess = false; + } + } + + layer._pushFeatureCollections(function(success){ + console.log("All edits done"); + + if(success && promisesSuccess){ + self.emit(self.events.EDITS_ENQUEUED, results); + } + else { + if(!success){ + console.log("applyEdits() there was a problem with _pushFeatureCollections."); + } + self.emit(self.events.EDITS_ENQUEUED_ERROR, results); + } + + //promisesSuccess === true ? self.emit(self.events.EDITS_ENQUEUED, results) : self.emit(self.events.EDITS_ENQUEUED_ERROR, results); + + // we already pushed the edits into the database, now we let the FeatureLayer to do the local updating of the layer graphics + this._editHandler(results, _adds, updatesMap, callback, errback, deferred1); + }.bind(this)); + + //success === true ? self.emit(self.events.EDITS_ENQUEUED, results) : self.emit(self.events.EDITS_ENQUEUED_ERROR, results); + // EDITS_ENQUEUED = callback(true, edit), and EDITS_ENQUEUED_ERROR = callback(false, /*String */ error) + //this._editHandler(results, _adds, updatesMap, callback, errback, deferred1); + }.bind(this)); + + return deferred1; + + }; // layer.applyEdits() + + /** + * Converts an array of graphics/features into JSON + * @param features + * @param updateEndEvent The layer's 'update-end' event + * @param callback + */ + layer.convertGraphicLayerToJSON = function (features, updateEndEvent, callback) { + var layerDefinition = {}; + + // We want to be consistent, but we've discovered that not all feature layers have an objectIdField + if(updateEndEvent.target.hasOwnProperty("objectIdField")) + { + layerDefinition.objectIdFieldName = updateEndEvent.target.objectIdField; + }else { + layerDefinition.objectIdFieldName = this.objectIdField; + } + + layerDefinition.globalIdFieldName = updateEndEvent.target.globalIdField; + layerDefinition.geometryType = updateEndEvent.target.geometryType; + layerDefinition.spatialReference = updateEndEvent.target.spatialReference; + layerDefinition.fields = updateEndEvent.target.fields; + + var length = features.length; + var jsonArray = []; + for (var i = 0; i < length; i++) { + var jsonGraphic = features[i].toJson(); + jsonArray.push(jsonGraphic); + if (i == (length - 1)) { + var featureJSON = JSON.stringify(jsonArray); + var layerDefJSON = JSON.stringify(layerDefinition); + callback(featureJSON, layerDefJSON); + break; + } + } + }; + + /** + * Retrieves f=json from the feature layer + * @param url FeatureLayer's URL + * @param callback + * @private + */ + layer.getFeatureLayerJSON = function (url, callback) { + require(["esri/request"], function (esriRequest) { + var request = esriRequest({ + url: url, + content: {f: "json"}, + handleAs: "json", + callbackParamName: "callback" + }); + + request.then(function (response) { + console.log("Success: ", response); + callback(true, response); + }, function (error) { + console.log("Error: ", error.message); + callback(false, error.message); + }); + }); + }; + + /** + * Sets the optional feature layer storage object + * @param jsonObject + * @param callback + */ + layer.setFeatureLayerJSONDataStore = function(jsonObject, callback){ + self._editStore.pushFeatureLayerJSON(jsonObject,function(success,error){ + callback(success,error); + }); + }; + + /** + * Retrieves the optional feature layer storage object + * @param callback callback(true, object) || callback(false, error) + */ + layer.getFeatureLayerJSONDataStore = function(callback){ + self._editStore.getFeatureLayerJSON(function(success,message){ + callback(success,message); + }); + }; + + /** + * Sets the phantom layer with new features. + * Used to restore PhantomGraphicsLayer after offline restart + * @param graphicsArray an array of Graphics + */ + layer.setPhantomLayerGraphics = function (graphicsArray) { + var length = graphicsArray.length; + + if (length > 0) { + for (var i = 0; i < length; i++) { + var graphic = new Graphic(graphicsArray[i]); + this._phantomLayer.add(graphic); + } + } + }; + + /** + * Returns the array of graphics from the phantom graphics layer. + * This layer identifies features that have been modified + * while offline. + * @returns {array} + */ + layer.getPhantomLayerGraphics = function (callback) { + //return layer._phantomLayer.graphics; + var graphics = layer._phantomLayer.graphics; + var length = layer._phantomLayer.graphics.length; + var jsonArray = []; + for (var i = 0; i < length; i++) { + var jsonGraphic = graphics[i].toJson(); + jsonArray.push(jsonGraphic); + if (i == (length - 1)) { + var graphicsJSON = JSON.stringify(jsonArray); + callback(graphicsJSON); + break; + } + } + }; + + /** + * Returns an array of phantom graphics from the database. + * @param callback callback (true, array) or (false, errorString) + */ + layer.getPhantomGraphicsArray = function(callback){ + self._editStore.getPhantomGraphicsArray(function(array,message){ + if(message == "end"){ + callback(true,array); + } + else{ + callback(false,message); + } + }); + }; + + /** + * Returns the approximate size of the attachments database in bytes + * @param callback callback({usage}, error) Whereas, the usage Object is {sizeBytes: number, attachmentCount: number} + */ + layer.getAttachmentsUsage = function(callback) { + self.attachmentsStore.getUsage(function(usage,error){ + callback(usage,error); + }); + }; + + /** + * Full attachments database reset. + * CAUTION! If some attachments weren't successfully sent, then their record + * will still exist in the database. If you use this function you + * will also delete those records. + * @param callback (boolean, error) + */ + layer.resetAttachmentsDatabase = function(callback){ + self.attachmentsStore.resetAttachmentsQueue(function(result,error){ + callback(result,error); + }); + }; + + /** + * Returns the approximate size of the edits database in bytes + * @param callback callback({usage}, error) Whereas, the usage Object is {sizeBytes: number, editCount: number} + */ + layer.getUsage = function(callback){ + self._editStore.getUsage(function(usage,error){ + callback(usage,error); + }); + }; + + /** + * Full edits database reset. + * CAUTION! If some edits weren't successfully sent, then their record + * will still exist in the database. If you use this function you + * will also delete those records. + * @param callback (boolean, error) + */ + layer.resetDatabase = function(callback){ + self._editStore.resetEditsQueue(function(result,error){ + callback(result,error); + }); + }; + + /** + * Returns the number of edits pending in the database. + * @param callback callback( int ) + */ + layer.pendingEditsCount = function(callback){ + self._editStore.pendingEditsCount(function(count){ + callback(count); + }); + }; + + /** + * Create a featureDefinition + * @param featureLayer + * @param featuresArr + * @param geometryType + * @param callback + */ + layer.getFeatureDefinition = function (/* Object */ featureLayer, /* Array */ featuresArr, /* String */ geometryType, callback) { + + var featureDefinition = { + "layerDefinition": featureLayer, + "featureSet": { + "features": featuresArr, + "geometryType": geometryType + } + + }; + + callback(featureDefinition); + }; + + /** + * Returns an iterable array of all edits stored in the database + * Each item in the array is an object and contains: + * { + * id: "internal ID", + * operation: "add, update or delete", + * layer: "layerURL", + * type: "esri Geometry Type", + * graphic: "esri.Graphic converted to JSON then serialized" + * } + * @param callback (true, array) or (false, errorString) + */ + layer.getAllEditsArray = function(callback){ + self._editStore.getAllEditsArray(function(array,message){ + if(message == "end"){ + callback(true,array); + } + else{ + callback(false,message); + } + }); + }; + + /* internal methods */ + + /** + * Automatically creates a set of featureLayerCollections. This is specifically for + * use with offline browser restarts. You can retrieve the collections and use them + * to reconstitute a featureLayer and then redisplay all the associated features. + * + * To retrieve use OfflineEditAdvanced.getFeatureCollections(). + * @param callback (boolean) + * @private + */ + layer._pushFeatureCollections = function(callback){ + + // First let's see if any collections exists + self._editStore._getFeatureCollections(function(success, result) { + + var featureCollection = + { + featureLayerUrl: layer.url, + featureLayerCollection: layer.toJson() + }; + + // An array of feature collections, of course :-) + var featureCollectionsArray = [ + featureCollection + ]; + + // An object for storing multiple feature collections + var featureCollectionsObject = { + // The id is required because the editsStore keypath + // uses it as a UID for all entries in the database + id: self._editStore.FEATURE_COLLECTION_ID, + featureCollections: featureCollectionsArray + }; + + // THIS IS A HACK. + // There is a bug in JS API 3.11+ when you create a feature layer from a featureCollectionObject + // the hasAttachments property does not get properly repopulated. + layer.hasAttachments = featureCollection.featureLayerCollection.layerDefinition.hasAttachments; + + // If the featureCollectionsObject already exists + if(success){ + var count = 0; + for(var i = 0; i < result.featureCollections.length; i++) { + + // Update the current feature collection + if(result.featureCollections[i].featureLayerUrl === layer.url) { + count++; + result.featureCollections[i] = featureCollection; + } + } + + // If we have a new feature layer then add it to the featureCollections array + if(count === 0) { + result.featureCollections.push(featureCollection); + } + } + // If it does not exist then we need to add a featureCollectionsObject + else if(!success && result === null) { + result = featureCollectionsObject; + } + else { + console.error("There was a problem retrieving the featureCollections from editStore."); + } + + // Automatically update the featureCollectionsObject in the database with every ADD, UPDATE + // and DELETE. It can be retrieved via OfflineEditAdvanced.getFeatureCollections(); + self._editStore._pushFeatureCollections(result, function(success, error) { + if(!success){ + console.error("There was a problem creating the featureCollectionObject: " + error); + callback(false); + } + else { + callback(true); + } + }.bind(this)); + }); + }; + + /** + * Pushes a DELETE request to the database after it's been validated + * @param layer + * @param deleteEdit + * @param operation + * @param resultsArray + * @param objectId + * @param deferred + * @private + */ + layer._pushValidatedDeleteFeatureToDB = function(layer,deleteEdit,operation,resultsArray,objectId,deferred){ + self._editStore.pushEdit(operation, layer.url, deleteEdit, function (result, error) { + + if(result){ + resultsArray.deleteResults.push({success: true, error: null, objectId: objectId}); + + // Use the correct key as set by self.DB_UID + var tempIdObject = {}; + tempIdObject[self.DB_UID] = objectId; + + var phantomDelete = new Graphic( + deleteEdit.geometry, + self._getPhantomSymbol(deleteEdit.geometry, self._editStore.DELETE), + tempIdObject + ); + + layer._phantomLayer.add(phantomDelete); + + // Add phantom graphic to the database + self._editStore.pushPhantomGraphic(phantomDelete, function (result) { + if (!result) { + console.log("There was a problem adding phantom graphic id: " + objectId); + } + else{ + console.log("Phantom graphic " + objectId + " added to database as a deletion."); + domAttr.set(phantomDelete.getNode(), "stroke-dasharray", "4,4"); + domStyle.set(phantomDelete.getNode(), "pointer-events", "none"); + } + }); + + if (self.attachmentsStore) { + // delete local attachments of this feature, if any... we just launch the delete and don't wait for it to complete + self.attachmentsStore.deleteAttachmentsByFeatureId(layer.url, objectId, function (deletedCount) { + console.log("deleted", deletedCount, "attachments of feature", objectId); + }); + } + } + else{ + // If we can't push edit to database then we don't create a phantom graphic + resultsArray.deleteResults.push({success: false, error: error, objectId: objectId}); + } + + deferred.resolve(result); + }); + }; + + /** + * Pushes an UPDATE request to the database after it's been validated + * @param layer + * @param updateEdit + * @param operation + * @param resultsArray + * @param objectId + * @param deferred + * @private + */ + layer._pushValidatedUpdateFeatureToDB = function(layer,updateEdit,operation,resultsArray,objectId,deferred){ + self._editStore.pushEdit(operation, layer.url, updateEdit, function (result, error) { + + if(result){ + resultsArray.updateResults.push({success: true, error: null, objectId: objectId}); + + // Use the correct key as set by self.DB_UID + var tempIdObject = {}; + tempIdObject[self.DB_UID] = objectId; + + var phantomUpdate = new Graphic( + updateEdit.geometry, + self._getPhantomSymbol(updateEdit.geometry, self._editStore.UPDATE), + tempIdObject + ); + + layer._phantomLayer.add(phantomUpdate); + + // Add phantom graphic to the database + self._editStore.pushPhantomGraphic(phantomUpdate, function (result) { + if (!result) { + console.log("There was a problem adding phantom graphic id: " + objectId); + } + else{ + console.log("Phantom graphic " + objectId + " added to database as an update."); + domAttr.set(phantomUpdate.getNode(), "stroke-dasharray", "5,2"); + domStyle.set(phantomUpdate.getNode(), "pointer-events", "none"); + } + }); + + } + else{ + // If we can't push edit to database then we don't create a phantom graphic + resultsArray.updateResults.push({success: false, error: error, objectId: objectId}); + } + + deferred.resolve(result); + }); + }; + + /** + * Pushes an ADD request to the database after it's been validated + * @param layer + * @param addEdit + * @param operation + * @param resultsArray + * @param objectId + * @param deferred + * @private + */ + layer._pushValidatedAddFeatureToDB = function(layer,addEdit,operation,resultsArray,objectId,deferred){ + self._editStore.pushEdit(operation, layer.url, addEdit, function (result, error) { + if(result){ + resultsArray.addResults.push({success: true, error: null, objectId: objectId}); + + // Use the correct key as set by self.DB_UID + var tempIdObject = {}; + tempIdObject[self.DB_UID] = objectId; + + var phantomAdd = new Graphic( + addEdit.geometry, + self._getPhantomSymbol(addEdit.geometry, self._editStore.ADD), + tempIdObject + ); + + // Add phantom graphic to the layer + layer._phantomLayer.add(phantomAdd); + + // Add phantom graphic to the database + self._editStore.pushPhantomGraphic(phantomAdd, function (result) { + if (!result) { + console.log("There was a problem adding phantom graphic id: " + objectId); + } + else{ + console.log("Phantom graphic " + objectId + " added to database as an add."); + domAttr.set(phantomAdd.getNode(), "stroke-dasharray", "10,4"); + domStyle.set(phantomAdd.getNode(), "pointer-events", "none"); + } + + }); + } + else{ + // If we can't push edit to database then we don't create a phantom graphic + resultsArray.addResults.push({success: false, error: error, objectId: objectId}); + } + + deferred.resolve(result); + }); + }; + + /** + * Validates duplicate entries. Last edit on same feature can overwite any previous values. + * Note: if an edit was already added offline and you delete it then we return success == false + * @param graphic esri.Graphic. + * @param layerUrl the URL of the feature service + * @param operation add, update or delete action on an edit + * @returns deferred {success:boolean,graphic:graphic,operation:add|update|delete} + * @private + */ + layer._validateFeature = function (graphic,layerUrl,operation) { + + var deferred = new Deferred(); + + var id = layerUrl + "/" + graphic.attributes[self.DB_UID]; + + self._editStore.getEdit(id,function(success,result){ + if (success) { + switch( operation ) + { + case self._editStore.ADD: + // Not good - however we'll allow the new ADD to replace/overwrite existing edit + // and pass it through unmodified. Last ADD wins. + deferred.resolve({"success":true,"graphic":graphic,"operation":operation}); + break; + case self._editStore.UPDATE: + // If we are doing an update on a feature that has not been added to + // the server yet, then we need to maintain its operation as an ADD + // and not an UPDATE. This avoids the potential for an error if we submit + // an update operation on a feature that has not been added to the + // database yet. + if(result.operation == self._editStore.ADD){ + graphic.operation = self._editStore.ADD; + operation = self._editStore.ADD; + } + deferred.resolve({"success":true,"graphic":graphic,"operation":operation}); + break; + case self._editStore.DELETE: + + var resolved = true; + + if(result.operation == self._editStore.ADD){ + // If we are deleting a new feature that has not been added to the + // server yet we need to delete it and its phantom graphic. + layer._deleteTemporaryFeature(graphic,function(success){ + if(!success){ + resolved = false; + } + }); + } + deferred.resolve({"success":resolved,"graphic":graphic,"operation":operation}); + break; + } + } + else if(result == "Id not found"){ + // Let's simply pass the graphic back as good-to-go. + // No modifications needed because the graphic does not + // already exist in the database. + deferred.resolve({"success":true,"graphic":graphic,"operation":operation}); + } + else{ + deferred.reject(graphic); + } + }); + + return deferred; + }; + + /** + * Delete a graphic and its associated phantom graphic that has been added while offline. + * @param graphic + * @param callback + * @private + */ + layer._deleteTemporaryFeature = function(graphic,callback){ + + var phantomGraphicId = self._editStore.PHANTOM_GRAPHIC_PREFIX + self._editStore._PHANTOM_PREFIX_TOKEN + graphic.attributes[self.DB_UID]; + + function _deleteGraphic(){ + var deferred = new Deferred(); + self._editStore.delete(layer.url,graphic,function(success,error){ + if(success){ + deferred.resolve(true); + } + else{ + deferred.resolve(false); + } + }); + return deferred.promise; + } + + function _deletePhantomGraphic(){ + var deferred = new Deferred(); + self._editStore.deletePhantomGraphic(phantomGraphicId,function(success){ + if(success) { + deferred.resolve(true); + } + else { + deferred.resolve(false); + } + }, function(error) { + deferred.resolve(false); + }); + return deferred.promise; + } + + all([_deleteGraphic(),_deletePhantomGraphic()]).then(function (results) { + callback(results); + }); + + }; + + layer._getFilesFromForm = function (formNode) { + var files = []; + var inputNodes = array.filter(formNode.elements, function (node) { + return node.type === "file"; + }); + inputNodes.forEach(function (inputNode) { + files.push.apply(files, inputNode.files); + }, this); + return files; + }; + + layer._replaceFeatureIds = function (tempObjectIds, newObjectIds, callback) { + console.log("replacing ids of attachments", tempObjectIds, newObjectIds); + console.assert(tempObjectIds.length === newObjectIds.length, "arrays must be the same length"); + + if (!tempObjectIds.length) { + console.log("no ids to replace!"); + callback(0); + } + + var i, n = tempObjectIds.length; + var count = n; + var successCount = 0; + for (i = 0; i < n; i++) { + self.attachmentsStore.replaceFeatureId(this.url, tempObjectIds[i], newObjectIds[i], function (success) { + --count; + successCount += (success ? 1 : 0); + if (count === 0) { + callback(successCount); + } + }.bind(this)); + } + }; + + // 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; + layer._getNextTempId = function () { + return this._nextTempId--; + }; + + function _initPhantomLayer() { + try { + layer._phantomLayer = new GraphicsLayer({opacity: 0.8}); + layer._map.addLayer(layer._phantomLayer); + } + catch (err) { + console.log("Unable to init PhantomLayer: " + err.message); + } + } + + _initPhantomLayer(); + + // We are currently only passing in a single deferred. + all(extendPromises).then(function (r) { + + // DB already initialized + if(r.length === 0 && url){ + // Initialize the internal featureLayerCollectionObject + if(this.ENABLE_FEATURECOLLECTION) { + layer._pushFeatureCollections(function(success){ + if(success){ + callback(true, null); + } + else { + callback(false, null); + } + }); + } + else { + callback(true, null); + } + } + else if(r[0].success && !url){ + + // This functionality is specifically for offline restarts + // and attempts to retrieve a feature layer url. + // It's a hack because layer.toJson() doesn't convert layer.url. + this._editStore.getFeatureLayerJSON(function(success,message){ + if(success) { + this._featureLayers[message.__featureLayerURL] = layer; + layer.url = message.__featureLayerURL; + + // Initialize the internal featureLayerCollectionObject + if(this.ENABLE_FEATURECOLLECTION) { + layer._pushFeatureCollections(function(success){ + if(success){ + callback(true, null); + } + else { + callback(false, null); + } + }); + } + else { + callback(true, null); + } + } + else { + // NOTE: We have to have a valid feature layer URL in order to initialize the featureLayerCollectionObject + console.error("getFeatureLayerJSON() failed and unable to create featureLayerCollectionObject."); + callback(false, message); + } + }.bind(this)); + } + else if(r[0].success){ + + if(this.ENABLE_FEATURECOLLECTION) { + layer._pushFeatureCollections(function(success){ + if(success){ + callback(true, null); + } + else { + callback(false, null); + } + }); + } + else { + callback(true, null); + } + } + }.bind(this)); + + }, // extend + + /** + * Forces library into an offline state. Any edits applied during this condition will be stored locally + */ + goOffline: function () { + console.log("offlineFeatureManager going offline"); + this._onlineStatus = this.OFFLINE; + }, + + /** + * Forces library to return to an online state. If there are pending edits, + * an attempt will be made to sync them with the remote feature server + * @param callback callback( boolean, errors ) + */ + goOnline: function (callback) { + console.log("OfflineEditAdvanced going online"); + this._onlineStatus = this.RECONNECTING; + this._replayStoredEdits(function (success, responses) { + var result = {success: success, responses: responses}; + this._onlineStatus = this.ONLINE; + if (this.attachmentsStore != null) { + console.log("sending attachments"); + this._sendStoredAttachments(function (success, uploadedResponses, dbResponses) { + //this._onlineStatus = this.ONLINE; + result.attachments = {success: success, responses: uploadedResponses, dbResponses: dbResponses}; + callback && callback(result); + }.bind(this)); + } + else { + //this._onlineStatus = this.ONLINE; + callback && callback(result); + } + }.bind(this)); + }, + + /** + * Determines if offline or online condition exists + * @returns {string} ONLINE or OFFLINE + */ + getOnlineStatus: function () { + return this._onlineStatus; + }, + + /** + * Serialize the feature layer graphics + * @param features Array of features + * @param callback Returns a JSON string + */ + serializeFeatureGraphicsArray: function (features, callback) { + var length = features.length; + var jsonArray = []; + for (var i = 0; i < length; i++) { + var jsonGraphic = features[i].toJson(); + jsonArray.push(jsonGraphic); + if (i == (length - 1)) { + var featureJSON = JSON.stringify(jsonArray); + callback(featureJSON); + break; + } + } + }, + + /** + * Retrieves the feature collection object. Specifically used in offline browser restarts. + * This is an object created automatically by the library and is updated with every ADD, UPDATE and DELETE. + * Attachments are handled separately and not part of the feature collection created here. + * + * It has the following signature: {id: "feature-collection-object-1001", + * featureLayerCollections: [{ featureCollection: [Object], featureLayerUrl: String }]} + * + * @param callback + */ + getFeatureCollections: function(callback){ + if(!this._editStore._isDBInit){ + + this._initializeDB(null,null).then(function(result){ + if(result.success){ + this._editStore._getFeatureCollections(function(success,message){ + callback(success,message); + }); + } + }.bind(this), function(err){ + callback(false, err); + }); + } + else { + this._editStore._getFeatureCollections(function(success,message){ + callback(success,message); + }); + } + }, + + /** + * Retrieves the optional feature layer storage object + * For use in full offline scenarios. + * @param callback callback(true, object) || callback(false, error) + */ + getFeatureLayerJSONDataStore: function(callback){ + if(!this._editStore._isDBInit){ + + this._initializeDB(null,null).then(function(result){ + if(result.success){ + this._editStore.getFeatureLayerJSON(function(success,message){ + callback(success,message); + }); + } + }.bind(this), function(err){ + callback(false, err); + }); + } + else { + this._editStore.getFeatureLayerJSON(function(success,message){ + callback(success,message); + }); + } + }, + + /* internal methods */ + + /** + * Initialize the database and push featureLayer JSON to DB if required. + * NOTE: also stores feature layer url in hidden dataStore property dataStore.__featureLayerURL. + * @param dataStore Object + * @param url Feature Layer's url. This is used by this library for internal feature identification. + * @param callback + * @private + */ + //_initializeDB: function(dataStore,url,callback){ + _initializeDB: function(dataStore,url){ + var deferred = new Deferred(); + + var editStore = this._editStore; + + // Configure the database + editStore.dbName = this.DB_NAME; + editStore.objectStoreName = this.DB_OBJECTSTORE_NAME; + editStore.objectId = this.DB_UID; + + // Attempt to initialize the database + editStore.init(function (result, error) { + + //////////////////////////////////////////////////// + // OFFLINE RESTART CONFIGURATION + // Added @ v2.5 + // + // Configure database for offline restart + // dataStore object allows you to store data that you'll + // use after an offline browser restart. + // + // If dataStore Object is not defined then do nothing. + // + //////////////////////////////////////////////////// + + if (typeof dataStore === "object" && result === true && (dataStore !== undefined) && (dataStore !== null)) { + + // Add a hidden property to hold the feature layer's url + // When converting a feature layer to json (layer.toJson()) we lose this information. + // This library needs to know the feature layer url. + if(url) { + dataStore.__featureLayerURL = url; + } + + editStore.pushFeatureLayerJSON(dataStore, function (success, err) { + if (success) { + deferred.resolve({success:true, error: null}); + } + else { + deferred.reject({success:false, error: err}); + } + }); + } + else if(result){ + deferred.resolve({success:true, error: null}); + } + else{ + deferred.reject({success:false, error: null}); + } + }); + + return deferred; + }, + + /** + * internal method that checks if this browser supports everything that is needed to handle offline attachments + * it also extends XMLHttpRequest with sendAsBinary() method, needed in Chrome + */ + _checkFileAPIs: function () { + if (window.File && window.FileReader && window.FileList && window.Blob) { + console.log("All APIs supported!"); + + if (!XMLHttpRequest.prototype.sendAsBinary) { + // https://code.google.com/p/chromium/issues/detail?id=35705#c40 + XMLHttpRequest.prototype.sendAsBinary = function (datastr) { + function byteValue(x) { + return x.charCodeAt(0) & 0xff; // jshint ignore:line + } + + var ords = Array.prototype.map.call(datastr, byteValue); + var ui8a = new Uint8Array(ords); + this.send(ui8a.buffer); + }; + console.log("extending XMLHttpRequest"); + } + return true; + } + console.log("The File APIs are not fully supported in this browser."); + return false; + }, + + /** + * internal method that extends an object with sendAsBinary() method + * sometimes extending XMLHttpRequest.prototype is not enough, as ArcGIS JS lib seems to mess with this object too + * @param oAjaxReq object to extend + */ + _extendAjaxReq: function (oAjaxReq) { + oAjaxReq.sendAsBinary = XMLHttpRequest.prototype.sendAsBinary; + console.log("extending XMLHttpRequest"); + }, + + // + // phantom symbols + // + + _phantomSymbols: [], + + _getPhantomSymbol: function (geometry, operation) { + if (this._phantomSymbols.length === 0) { + var color = [0, 255, 0, 255]; + var width = 1.5; + + this._phantomSymbols.point = []; + this._phantomSymbols.point[this._editStore.ADD] = new SimpleMarkerSymbol({ + "type": "esriSMS", "style": "esriSMSCross", + "xoffset": 10, "yoffset": 10, + "color": [255, 255, 255, 0], "size": 15, + "outline": {"color": color, "width": width, "type": "esriSLS", "style": "esriSLSSolid"} + }); + this._phantomSymbols.point[this._editStore.UPDATE] = new SimpleMarkerSymbol({ + "type": "esriSMS", "style": "esriSMSCircle", + "xoffset": 0, "yoffset": 0, + "color": [255, 255, 255, 0], "size": 15, + "outline": {"color": color, "width": width, "type": "esriSLS", "style": "esriSLSSolid"} + }); + this._phantomSymbols.point[this._editStore.DELETE] = new SimpleMarkerSymbol({ + "type": "esriSMS", "style": "esriSMSX", + "xoffset": 0, "yoffset": 0, + "color": [255, 255, 255, 0], "size": 15, + "outline": {"color": color, "width": width, "type": "esriSLS", "style": "esriSLSSolid"} + }); + this._phantomSymbols.multipoint = null; + + this._phantomSymbols.polyline = []; + this._phantomSymbols.polyline[this._editStore.ADD] = new SimpleLineSymbol({ + "type": "esriSLS", "style": "esriSLSSolid", + "color": color, "width": width + }); + this._phantomSymbols.polyline[this._editStore.UPDATE] = new SimpleLineSymbol({ + "type": "esriSLS", "style": "esriSLSSolid", + "color": color, "width": width + }); + this._phantomSymbols.polyline[this._editStore.DELETE] = new SimpleLineSymbol({ + "type": "esriSLS", "style": "esriSLSSolid", + "color": color, "width": width + }); + + this._phantomSymbols.polygon = []; + this._phantomSymbols.polygon[this._editStore.ADD] = new SimpleFillSymbol({ + "type": "esriSFS", + "style": "esriSFSSolid", + "color": [255, 255, 255, 0], + "outline": {"type": "esriSLS", "style": "esriSLSSolid", "color": color, "width": width} + }); + this._phantomSymbols.polygon[this._editStore.UPDATE] = new SimpleFillSymbol({ + "type": "esriSFS", + "style": "esriSFSSolid", + "color": [255, 255, 255, 0], + "outline": {"type": "esriSLS", "style": "esriSLSDash", "color": color, "width": width} + }); + this._phantomSymbols.polygon[this._editStore.DELETE] = new SimpleFillSymbol({ + "type": "esriSFS", + "style": "esriSFSSolid", + "color": [255, 255, 255, 0], + "outline": {"type": "esriSLS", "style": "esriSLSDot", "color": color, "width": width} + }); + } + + return this._phantomSymbols[geometry.type][operation]; + }, + + // + // methods to handle attachment uploads + // + + _uploadAttachment: function (attachment) { + var dfd = new Deferred(); + + var layer = this._featureLayers[attachment.featureLayerUrl]; + + var formData = new FormData(); + formData.append("attachment",attachment.file); + + switch(attachment.type){ + case this.attachmentsStore.TYPE.ADD: + layer.addAttachment(attachment.objectId,formData,function(evt){ + dfd.resolve({attachmentResult:evt,id:attachment.id}); + },function(err){ + dfd.reject(err); + }); + break; + case this.attachmentsStore.TYPE.UPDATE: + formData.append("attachmentId", attachment.id); + + // NOTE: + // We need to handle updates different from ADDS and DELETES because of how the JS API + // parses the DOM formNode property. + layer._sendAttachment("update",/* objectid */attachment.objectId, formData,function(evt){ + dfd.resolve({attachmentResult:evt,id:attachment.id}); + },function(err){ + dfd.reject(err); + }); + + break; + case this.attachmentsStore.TYPE.DELETE: + // IMPORTANT: This method returns attachmentResult as an Array. Whereas ADD and UPDATE do not!! + layer.deleteAttachments(attachment.objectId,[attachment.id],function(evt){ + dfd.resolve({attachmentResult:evt,id:attachment.id}); + },function(err){ + dfd.reject(err); + }); + break; + } + + return dfd.promise; + }, + + _deleteAttachmentFromDB: function (attachmentId, uploadResult) { + var dfd = new Deferred(); + + console.log("upload complete", uploadResult, attachmentId); + this.attachmentsStore.delete(attachmentId, function (success) { + console.assert(success === true, "can't delete attachment already uploaded"); + console.log("delete complete", success); + dfd.resolve({success:success,result:uploadResult}); + }); + + return dfd; + }, + + /** + * Removes attachments from DB if they were successfully uploaded + * @param results promises.results + * @callback callback callback( {errors: boolean, attachmentsDBResults: results, uploadResults: results} ) + * @private + */ + _cleanAttachmentsDB: function(results,callback){ + + var self = this; + var promises = []; + var count = 0; + + results.forEach(function(value){ + + if(typeof value.attachmentResult == "object" && value.attachmentResult.success){ + // Delete an attachment from the database if it was successfully + // submitted to the server. + promises.push(self._deleteAttachmentFromDB(value.id,null)); + } + // NOTE: layer.deleteAttachments returns an array rather than an object + else if(value.attachmentResult instanceof Array){ + + // Because we get an array we have to cycle thru it to verify all results + value.attachmentResult.forEach(function(deleteValue){ + if(deleteValue.success){ + // Delete an attachment from the database if it was successfully + // submitted to the server. + promises.push(self._deleteAttachmentFromDB(value.id,null)); + } + else { + count++; + } + }); + } + else{ + // Do nothing. Don't delete attachments from DB if we can't upload them + count++; + } + }); + + var allPromises = all(promises); + allPromises.then(function(dbResults){ + if(count > 0){ + // If count is greater than zero then we have errors and need to set errors to true + callback({errors: true, attachmentsDBResults: dbResults, uploadResults: results}); + } + else{ + callback({errors: false, attachmentsDBResults: dbResults, uploadResults: results}); + } + }); + }, + + /** + * Attempts to upload stored attachments when the library goes back on line. + * @param callback callback({success: boolean, uploadResults: results, dbResults: results}) + * @private + */ + _sendStoredAttachments: function (callback) { + this.attachmentsStore.getAllAttachments(function (attachments) { + + var self = this; + + console.log("we have", attachments.length, "attachments to upload"); + + var promises = []; + attachments.forEach(function (attachment) { + console.log("sending attachment", attachment.id, "to feature", attachment.featureId); + + var uploadAttachmentComplete = this._uploadAttachment(attachment); + promises.push(uploadAttachmentComplete); + }, this); + console.log("promises", promises.length); + var allPromises = all(promises); + allPromises.then(function (uploadResults) { + console.log(uploadResults); + self._cleanAttachmentsDB(uploadResults,function(dbResults){ + if(dbResults.errors){ + callback && callback(false, uploadResults,dbResults); + } + else{ + callback && callback(true, uploadResults,dbResults); + } + }); + }, + function (err) { + console.log("error!", err); + callback && callback(false, err); + }); + }.bind(this)); + }, + + // + // methods to send features back to the server + // + + /** + * Attempts to send any edits in the database. Monitor events for success or failure. + * @param callback + * @event ALL_EDITS_SENT when all edits have been successfully sent. Contains {[addResults],[updateResults],[deleteResults]} + * @event EDITS_SENT_ERROR some edits were not sent successfully. Contains {msg: error} + * @private + */ + _replayStoredEdits: function (callback) { + var promises = {}; + var that = this; + + // + // send edits for each of the layers + // + var layer; + var adds = [], updates = [], deletes = []; + var tempObjectIds = []; + var tempArray = []; + var featureLayers = this._featureLayers; + var attachmentsStore = this.attachmentsStore; + var editStore = this._editStore; + + this._editStore.getAllEditsArray(function (result, err) { + if (result.length > 0) { + tempArray = result; + + var length = tempArray.length; + + for (var n = 0; n < length; n++) { + layer = featureLayers[tempArray[n].layer]; + + // If the layer has attachments then check to see if the attachmentsStore has been initialized + if (attachmentsStore == null && layer.hasAttachments) { + console.log("NOTICE: you may need to run OfflineEditAdvanced.initAttachments(). Check the Attachments doc for more info. Layer id: " + layer.id + " accepts attachments"); + } + + // Assign the attachmentsStore to the layer as a private var so we can access it from + // the promises applyEdits() method. + layer._attachmentsStore = attachmentsStore; + + layer.__onEditsComplete = layer.onEditsComplete; + layer.onEditsComplete = function () { + console.log("intercepting events onEditsComplete"); + }; + + // Let's zero everything out + adds = [], updates = [], deletes = [], tempObjectIds = []; + + // IMPORTANT: reconstitute the graphic JSON into an actual esri.Graphic object + // NOTE: we are only sending one Graphic per loop! + var graphic = new Graphic(tempArray[n].graphic); + + switch (tempArray[n].operation) { + case editStore.ADD: + for (var i = 0; i < layer.graphics.length; i++) { + var g = layer.graphics[i]; + if (g.attributes[layer.objectIdField] === graphic.attributes[layer.objectIdField]) { + layer.remove(g); + break; + } + } + tempObjectIds.push(graphic.attributes[layer.objectIdField]); + delete graphic.attributes[layer.objectIdField]; + adds.push(graphic); + break; + case editStore.UPDATE: + updates.push(graphic); + break; + case editStore.DELETE: + deletes.push(graphic); + break; + } + + //if(that._featureCollectionUsageFlag){ + // Note: when the feature layer is created with a feature collection we have to handle applyEdits() differently + // TO-DO rename this method. + promises[n] = that._internalApplyEditsAll(layer, tempArray[n].id, tempObjectIds, adds, updates, deletes); + } + + // wait for all requests to finish + // responses contain {id,layer,tempId,addResults,updateResults,deleteResults} + var allPromises = all(promises); + allPromises.then( + function (responses) { + console.log("OfflineEditAdvanced sync - all responses are back"); + + this._parseResponsesArray(responses).then(function(result) { + if(result) { + this.emit(this.events.ALL_EDITS_SENT,responses); + } + else { + this.emit(this.events.EDITS_SENT_ERROR, {msg: "Not all edits synced", respones: responses}); + } + callback && callback(true, responses); + }.bind(this)); + }.bind(that), + function (errors) { + console.log("OfflineEditAdvanced._replayStoredEdits - ERROR!!"); + console.log(errors); + callback && callback(false, errors); + }.bind(that) + ); + + } + else{ + // No edits were found + callback(true,[]); + } + }); + }, + + /** + * Deletes edits from database. + * This does not handle phantom graphics! + * @param edit + * @returns {l.Deferred.promise|*|c.promise|q.promise|promise} + * @private + */ + _updateDatabase: function (edit) { + var dfd = new Deferred(); + var fakeGraphic = {}; + fakeGraphic.attributes = {}; + + // Use the correct attributes key! + fakeGraphic.attributes[this.DB_UID] = edit.id; + + this._editStore.delete(edit.layer, fakeGraphic, function (success, error) { + if (success) { + dfd.resolve({success: true, error: null}); + } + else { + dfd.reject({success: false, error: error}); + } + }.bind(this)); + + return dfd.promise; + + }, + + /** + * Retrieves f=json from the feature layer + * @param url FeatureLayer's URL + * @param callback + * @private + */ + getFeatureLayerJSON: function (url, callback) { + require(["esri/request"], function (esriRequest) { + var request = esriRequest({ + url: url, + content: {f: "json"}, + handleAs: "json", + callbackParamName: "callback" + }); + + request.then(function (response) { + console.log("Success: ", response); + callback(true, response); + }, function (error) { + console.log("Error: ", error.message); + callback(false, error.message); + }); + }); + }, + + /** + * Applies edits. This works with both standard feature layers and when a feature layer is created + * using a feature collection. + * + * This works around specific behaviors in esri.layers.FeatureLayer when using the pattern + * new FeatureLayer(featureCollectionObject). + * + * Details on the specific behaviors can be found here: + * https://developers.arcgis.com/javascript/jsapi/featurelayer-amd.html#featurelayer2 + * + * @param layer + * @param id + * @param tempObjectIds + * @param adds + * @param updates + * @param deletes + * @returns {*|r} + * @private + */ + _internalApplyEditsAll: function (layer, id, tempObjectIds, adds, updates, deletes) { + var that = this; + var dfd = new Deferred(); + + this._makeEditRequest(layer, adds, updates, deletes, + function (addResults, updateResults, deleteResults) { + layer._phantomLayer.clear(); + + // We use a different pattern if the attachmentsStore is valid and the layer has attachments + if (layer._attachmentsStore != null && layer.hasAttachments && tempObjectIds.length > 0) { + + var newObjectIds = addResults.map(function (r) { + return r.objectId; + }); + + layer._replaceFeatureIds(tempObjectIds, newObjectIds, function (count) { + console.log("Done replacing feature ids. Total count = " + count); + }); + } + + if(addResults.length > 0) { + var graphic = new Graphic(adds[0].geometry,null,adds[0].attributes); + layer.add(graphic); + } + + that._cleanDatabase(layer, tempObjectIds, addResults, updateResults, deleteResults).then(function(results){ + dfd.resolve({ + id: id, + layer: layer.url, + tempId: tempObjectIds, // let's us internally match an ADD to it's new ObjectId + addResults: addResults, + updateResults: updateResults, + deleteResults: deleteResults, + databaseResults: results, + databaseErrors: null, + syncError: null + }); + }, function(error) { + dfd.resolve({ + id: id, + layer: layer.url, + tempId: tempObjectIds, // let's us internally match an ADD to it's new ObjectId + addResults: addResults, + updateResults: updateResults, + deleteResults: deleteResults, + databaseResults: null, + databaseErrors: error, + syncError: error + }); + }); + + }, + function (error) { + layer.onEditsComplete = layer.__onEditsComplete; + delete layer.__onEditsComplete; + + dfd.reject(error); + } + ); + return dfd.promise; + }, + + _cleanDatabase: function(layer, tempId, addResults, updateResults, deleteResults) { + + var dfd = new Deferred(); + var id = null; + + if (updateResults.length > 0) { + if (updateResults[0].success) { + id = updateResults[0].objectId; + } + } + if (deleteResults.length > 0) { + if (deleteResults[0].success) { + id = deleteResults[0].objectId; + } + } + if (addResults.length > 0) { + if (addResults[0].success) { + id = tempId; + } + } + + var fakeGraphic = {}; + fakeGraphic.attributes = {}; + + // Use the correct attributes key! + fakeGraphic.attributes[this.DB_UID] = id; + + // Delete the edit from the database + this._editStore.delete(layer.url, fakeGraphic, function (success, error) { + if (success) { + + var id = this._editStore.PHANTOM_GRAPHIC_PREFIX + this._editStore._PHANTOM_PREFIX_TOKEN + fakeGraphic.attributes[this.DB_UID]; + + // Delete the phantom graphic associated with the dit + this._editStore.deletePhantomGraphic(id, function(success,error){ + if(!success) { + console.log("_cleanDatabase delete phantom graphic error: " + error); + dfd.reject({success: false, error: error, id: id}); + } + else { + console.log("_cleanDatabase success: " + id); + dfd.resolve({success: true, error: null, id: id}); + } + }); + } + else { + dfd.reject({success: false, error: error, id: id}); + } + }.bind(this)); + + return dfd.promise; + }, + + /** + * Used when a feature layer is created with a feature collection. + * + * In the current version of the ArcGIS JSAPI 3.12+ the applyEdit() method doesn't send requests + * to the server when a feature layer is created with a feature collection. + * + * The use case for using this is: clean start app > go offline and make edits > offline restart browser > + * go online. + * + * @param layer + * @param adds + * @param updates + * @param deletes + * @returns {*|r} + * @private + */ + _makeEditRequest: function(layer,adds, updates, deletes, callback, errback) { + + var f = "f=json", a = "", u = "", d = ""; + + if(adds.length > 0) { + array.forEach(adds, function(add){ + if(add.hasOwnProperty("infoTemplate")){ // if the add has an infoTemplate attached, + delete add.infoTemplate; // delete it to reduce payload size. + } + }, this); + a = "&adds=" + JSON.stringify((adds)); + } + if(updates.length > 0) { + array.forEach(updates, function(update){ + if(update.hasOwnProperty("infoTemplate")){ // if the update has an infoTemplate attached, + delete update.infoTemplate; // delete it to reduce payload size. + } + }, this); + u = "&updates=" + JSON.stringify(updates); + } + if(deletes.length > 0) { + var id = deletes[0].attributes[this.DB_UID]; + d = "&deletes=" + id; + } + + var params = f + a + u + d; + + if(layer.hasOwnProperty("credential") && layer.credential){ + if(layer.credential.hasOwnProperty("token") && layer.credential.token){ + params = params + "&token=" + layer.credential.token; + } + } + + var req = new XMLHttpRequest(); + req.open("POST", layer.url + "/applyEdits", true); + req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + req.onload = function() + { + if( req.status === 200 && req.responseText !== "") + { + try { + var obj = JSON.parse(this.response); + callback(obj.addResults, obj.updateResults, obj.deleteResults); + } + catch(err) { + console.error("EDIT REQUEST REPONSE WAS NOT SUCCESSFUL:", req); + errback("Unable to parse xhr response", req); + } + } + + }; + req.onerror = function(e) + { + console.error("_makeEditRequest failed: " + e); + errback(e); + }; + req.ontimeout = function() { + errback("xhr timeout error"); + }; + req.timeout = this._defaultXhrTimeout; + req.send(params); + }, + + /** + * Parses the respones related to going back online and cleaning up the database. + * @param responses + * @returns {promise} True means all was successful. False indicates there was a problem. + * @private + */ + _parseResponsesArray: function(responses) { + + var dfd = new Deferred(); + var err = 0; + + for (var key in responses) { + if (responses.hasOwnProperty(key)) { + responses[key].addResults.map(function(result){ + if(!result.success) { + err++; + } + }); + + responses[key].updateResults.map(function(result){ + if(!result.success) { + err++; + } + }); + + responses[key].deleteResults.map(function(result){ + if(!result.success) { + err++; + } + }); + } + } + + if(err > 0){ + dfd.resolve(false); + } + else { + dfd.resolve(true); + } + + return dfd.promise; + } + }); // declare + }); // define +/** + * Creates a namespace for the non-AMD libraries in this directory + */ +/*jshint -W020 */ +if(typeof O != "undefined"){ + O.esri.Edit = {}; +} +else{ + O = {}; + O.esri = { + Edit: {} + }; +} +/*global indexedDB */ +/*jshint -W030 */ +O.esri.Edit.EditStore = function () { + + "use strict"; + + this._db = null; + this._isDBInit = false; + + // Public properties + + this.dbName = "features_store"; + this.objectStoreName = "features"; + this.objectId = "objectid"; // set this depending on how your feature service is configured; + + //var _dbIndex = "featureId"; // @private + + // ENUMs + + 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 () { + if (!window.indexedDB) { + return false; + } + return true; + }; + + /** + * Commit an edit to the database + * @param operation add, update or delete + * @param layerUrl the URL of the feature layer + * @param graphic esri/graphic. The method will serialize to JSON + * @param callback callback(true, edit) or callback(false, error) + */ + this.pushEdit = function (operation, layerUrl, graphic, callback) { + + var edit = { + id: layerUrl + "/" + graphic.attributes[this.objectId], + operation: operation, + layer: layerUrl, + type: graphic.geometry.type, + graphic: graphic.toJson() + }; + + if(typeof graphic.attributes[this.objectId] === "undefined") { + console.error("editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineEdit.DB_UID? " + JSON.stringify(graphic.attributes)); + callback(false,"editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineEdit.DB_UID? " + JSON.stringify(graphic.attributes)); + } + else{ + var transaction = this._db.transaction([this.objectStoreName], "readwrite"); + + transaction.oncomplete = function (event) { + callback(true); + }; + + transaction.onerror = function (event) { + callback(false, event.target.error.message); + }; + + var objectStore = transaction.objectStore(this.objectStoreName); + objectStore.put(edit); + } + }; + + /** + * Use this to store any static FeatureLayer or related JSON data related to your app that will assist in restoring + * a FeatureLayer. + * + * Handles both adds and updates. It copies any object properties, so it will not, by default, overwrite the entire object. + * + * Example 1: If you just submit {featureLayerRenderer: {someJSON}} it will only update the featureLayerRenderer property + * Example 2: This is a full example + * { + * featureLayerJSON: ..., + * graphics: ..., // Serialized Feature Layer graphics. Must be serialized! + * renderer: ..., + * opacity: ..., + * outfields: ..., + * mode: ..., + * extent: ..., + * zoom: 7, + * lastEdit: ... + * } + * + * NOTE: "dataObject.id" is a reserved property. If you use "id" in your object this method will break. + * @param dataStore Object + * @param callback callback(true, null) or callback(false, error) + */ + this.pushFeatureLayerJSON = function (dataStore /*Object*/, callback) { + + console.assert(this._db !== null, "indexeddb not initialized"); + if (typeof dataStore != "object") { + callback(false, "dataObject type is not an object."); + } + + var db = this._db; + dataStore.id = this.FEATURE_LAYER_JSON_ID; + + this.getFeatureLayerJSON(function (success, result) { + + var objectStore; + + if (success && typeof result !== "undefined") { + + objectStore = db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); + + // Make a copy of the object + for (var key in dataStore) { + if (dataStore.hasOwnProperty(key)) { + result[key] = dataStore[key]; + } + } + + // Insert the update into the database + var updateFeatureLayerDataRequest = objectStore.put(result); + + updateFeatureLayerDataRequest.onsuccess = function () { + callback(true, null); + }; + + updateFeatureLayerDataRequest.onerror = function (err) { + callback(false, err); + }; + } + else { + + var transaction = db.transaction([this.objectStoreName], "readwrite"); + + transaction.oncomplete = function (event) { + callback(true, null); + }; + + transaction.onerror = function (event) { + callback(false, event.target.error.message); + }; + + objectStore = transaction.objectStore(this.objectStoreName); + + // Protect against data cloning errors since we don't validate the input object + // Example: if you attempt to use an esri.Graphic in its native form you'll get a data clone error + try { + objectStore.put(dataStore); + } + catch (err) { + callback(false, JSON.stringify(err)); + } + } + }.bind(this)); + }; + + /** + * Retrieve the FeatureLayer data object + * @param callback callback(true, object) || callback(false, error) + */ + this.getFeatureLayerJSON = function (callback) { + + console.assert(this._db !== null, "indexeddb not initialized"); + + var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); + + //Get the entry associated with the graphic + var objectStoreGraphicRequest = objectStore.get(this.FEATURE_LAYER_JSON_ID); + + objectStoreGraphicRequest.onsuccess = function () { + var object = objectStoreGraphicRequest.result; + if (typeof object != "undefined") { + callback(true, object); + } + else { + callback(false, "nothing found"); + } + }; + + objectStoreGraphicRequest.onerror = function (msg) { + callback(false, msg); + }; + }; + + /** + * Safe delete. Checks if id exists, then reverifies. + * @param callback callback(boolean, {message: String}) + */ + this.deleteFeatureLayerJSON = function (callback) { + // NOTE: the implementation of the IndexedDB spec has a design fault with respect to + // handling deletes. The result of a delete operation is always designated as undefined. + // What this means is that there is no way to tell if an operation was successful or not. + // And, it will always return 'true.' + // + // In order to get around this we have to verify if after the attempted deletion operation + // if the record is or is not in the database. Kinda dumb, but that's how IndexedDB works. + //http://stackoverflow.com/questions/17137879/is-there-a-way-to-get-information-on-deleted-record-when-calling-indexeddbs-obj + + var db = this._db; + var deferred = null; + var self = this; + + var id = this.FEATURE_LAYER_JSON_ID; + + require(["dojo/Deferred"], function (Deferred) { + deferred = new Deferred(); + + // Step 4 - Then we check to see if the record actually exists or not. + deferred.then(function (result) { + // IF the delete was successful, then the record should return an error because it doesn't exist. + // We aren't 100% sure how all platforms will perform so we also trap the promise for return results. + self.editExists(id).then(function (results) { + // If edit does exist then we have not been successful in deleting the object. + callback(false, {message: "object was not deleted."}); + }, + function (err) { + // If the result is false then in theory the id no longer exists + // and we should return 'true' to indicate a successful delete operation. + callback(true, {message: "id does not exist"}); //because we want this test to throw an error. That means item deleted. + }); + }, + // There was a problem with the delete operation on the database + // This error message will come from editExists(); + function (err) { + callback(false, {message: "id does not exist"}); + }); + + // Step 1 - lets see if record exits. If it does not then return callback. Otherwise, + // continue on with the deferred. + self.editExists(id).then(function (result) { + + var objectStore = db.transaction([self.objectStoreName], "readwrite").objectStore(self.objectStoreName); + + // Step 2 - go ahead and delete graphic + var objectStoreDeleteRequest = objectStore.delete(id); + + // Step 3 - We know that the onsuccess will always fire unless something serious goes wrong. + // So we go ahead and resolve the deferred here. + objectStoreDeleteRequest.onsuccess = function () { + deferred.resolve(true); + }; + + objectStoreDeleteRequest.onerror = function (msg) { + deferred.reject({success: false, error: msg}); + }; + + }, + // If there is an error in editExists() + function (err) { + deferred.reject({success: false, message: err}); + }.bind(this)); + }); + }; + + /** + * Add a phantom graphic to the store. + * IMPORTANT! Requires graphic to have an objectId + * @param graphic + * @param callback + */ + this.pushPhantomGraphic = function (graphic, callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + var db = this._db; + var id = this.PHANTOM_GRAPHIC_PREFIX + this._PHANTOM_PREFIX_TOKEN + graphic.attributes[this.objectId]; + + var object = { + id: id, + graphic: graphic.toJson() + }; + + var transaction = db.transaction([this.objectStoreName], "readwrite"); + + transaction.oncomplete = function (event) { + callback(true, null); + }; + + transaction.onerror = function (event) { + callback(false, event.target.error.message); + }; + + var objectStore = transaction.objectStore(this.objectStoreName); + objectStore.put(object); + + }; + + /** + * Return an array of phantom graphics + * @param callback + */ + this.getPhantomGraphicsArray = function (callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + var editsArray = []; + + if (this._db !== null) { + + 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.indexOf(phantomGraphicPrefix) != -1) { + editsArray.push(cursor.value); + + } + cursor.continue(); + } + else { + callback(editsArray, "end"); + } + }.bind(this); + transaction.onerror = function (err) { + callback(null, err); + }; + } + else { + callback(null, "no db"); + } + }; + + /** + * Internal method that returns an array of id's only + * @param callback + * @private + */ + this._getPhantomGraphicsArraySimple = function (callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + var editsArray = []; + + if (this._db !== null) { + + 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.indexOf(phantomGraphicPrefix) != -1) { + editsArray.push(cursor.value.id); + + } + cursor.continue(); + } + else { + callback(editsArray, "end"); + } + }.bind(this); + transaction.onerror = function (err) { + callback(null, err); + }; + } + else { + callback(null, "no db"); + } + }; + + /** + * Deletes an individual graphic from the phantom layer + * @param id Internal ID + * @param callback callback(boolean, message) + */ + this.deletePhantomGraphic = function (id, callback) { + // NOTE: the implementation of the IndexedDB spec has a design fault with respect to + // handling deletes. The result of a delete operation is always designated as undefined. + // What this means is that there is no way to tell if an operation was successful or not. + // And, it will always return 'true.' + // + // In order to get around this we have to verify if after the attempted deletion operation + // if the record is or is not in the database. Kinda dumb, but that's how IndexedDB works. + //http://stackoverflow.com/questions/17137879/is-there-a-way-to-get-information-on-deleted-record-when-calling-indexeddbs-obj + + var db = this._db; + var deferred = null; + var self = this; + + require(["dojo/Deferred"], function (Deferred) { + deferred = new Deferred(); + + // Step 1 - lets see if record exits. If it does then return callback. + self.editExists(id).then(function (result) { + + // Step 4 - Then we check to see if the record actually exists or not. + deferred.then(function (result) { + + // IF the delete was successful, then the record should return 'false' because it doesn't exist. + self.editExists(id).then(function (results) { + callback(false, "item was not deleted"); // item is still in the database!! + }, + function (err) { + callback(true, "item successfully deleted"); //because we want this test to throw an error. That means item deleted. + }); + }, + // There was a problem with the delete operation on the database + function (err) { + callback(false, err); + }); + + var objectStore = db.transaction([self.objectStoreName], "readwrite").objectStore(self.objectStoreName); + + // Step 2 - go ahead and delete graphic + var objectStoreDeleteRequest = objectStore.delete(id); + + // Step 3 - We know that the onsuccess will always fire unless something serious goes wrong. + // So we go ahead and resolve the deferred here. + objectStoreDeleteRequest.onsuccess = function () { + deferred.resolve(true); + }; + + objectStoreDeleteRequest.onerror = function (msg) { + deferred.reject({success: false, error: msg}); + }; + }, + // If there is an error in editExists() + function (err) { + callback(false, "item doesn't exist in db"); + }); + }); + }; + + /** + * Removes all phantom graphics from database + * @param callback boolean + */ + this.resetPhantomGraphicsQueue = function (callback) { + + var db = this._db; + + // First we need to get the array of graphics that are stored in the database + // so that we can cycle thru them. + this._getPhantomGraphicsArraySimple(function (array) { + if (array != []) { + + var errors = 0; + var tx = db.transaction([this.objectStoreName], "readwrite"); + var objectStore = tx.objectStore(this.objectStoreName); + + objectStore.onerror = function () { + errors++; + }; + + tx.oncomplete = function () { + errors === 0 ? callback(true) : callback(false); + }; + + var length = array.length; + for (var i = 0; i < length; i++) { + objectStore.delete(array[i]); + } + } + else { + callback(true); + } + }.bind(this)); + }; + + /** + * Retrieve an edit by its internal ID + * @param id String identifier + * @param callback callback(true,graphic) or callback(false, error) + */ + this.getEdit = function(id,callback){ + + console.assert(this._db !== null, "indexeddb not initialized"); + var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); + + if(typeof id === "undefined"){ + callback(false,"id is undefined."); + return; + } + + //Get the entry associated with the graphic + var objectStoreGraphicRequest = objectStore.get(id); + + objectStoreGraphicRequest.onsuccess = function () { + var graphic = objectStoreGraphicRequest.result; + if (graphic && (graphic.id == id)) { + callback(true,graphic); + } + else { + callback(false,"Id not found"); + } + }; + + objectStoreGraphicRequest.onerror = function (msg) { + callback(false,msg); + }; + }; + + /** + * Returns all the edits recursively via the callback + * @param callback {value, message} + */ + this.getAllEdits = function (callback) { + + console.assert(this._db !== null, "indexeddb not initialized"); + + 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.hasOwnProperty("value") && cursor.value.hasOwnProperty("id")) { + + // Make sure we are not return FeatureLayer JSON data or a Phantom Graphic + if (cursor.value.id !== fLayerJSONId && cursor.value.id !== fCollectionId && cursor.value.id.indexOf(phantomGraphicPrefix) == -1) { + callback(cursor.value, null); + } + cursor.continue(); + } + else { + callback(null, "end"); + } + }.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} + */ + this.getAllEditsArray = function (callback) { + + console.assert(this._db !== null, "indexeddb not initialized"); + var editsArray = []; + + 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) { + editsArray.push(cursor.value); + + } + cursor.continue(); + } + else { + callback(editsArray, "end"); + } + }.bind(this); + transaction.onerror = function (err) { + callback(null, err); + }; + } + else { + callback(null, "no db"); + } + }; + + /** + * Update an edit already exists in the database + * @param operation add, update or delete + * @param layer the URL of the feature layer + * @param graphic esri/graphic. The method will serialize to JSON + * @param callback {true, edit} or {false, error} + */ + this.updateExistingEdit = function (operation, layer, graphic, callback) { + + console.assert(this._db !== null, "indexeddb not initialized"); + + var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); + + //Let's get the entry associated with the graphic + var objectStoreGraphicRequest = objectStore.get(graphic.attributes[this.objectId]); + objectStoreGraphicRequest.onsuccess = function () { + + //Grab the data object returned as a result + // TO-DO Do we keep this?? + objectStoreGraphicRequest.result; + + //Create a new update object + var update = { + id: layer + "/" + graphic.attributes[this.objectId], + operation: operation, + layer: layer, + graphic: graphic.toJson() + }; + + // Insert the update into the database + var updateGraphicRequest = objectStore.put(update); + + updateGraphicRequest.onsuccess = function () { + callback(true); + }; + + updateGraphicRequest.onerror = function (err) { + callback(false, err); + }; + }.bind(this); + }; + + /** + * Delete a pending edit's record from the database. + * IMPORTANT: Be aware of false negatives. See Step 4 in this function. + * + * @param layerUrl + * @param graphic Graphic + * @param callback {boolean, error} + */ + this.delete = function (layerUrl, graphic, callback) { + + // NOTE: the implementation of the IndexedDB spec has a design fault with respect to + // handling deletes. The result of a delete operation is always designated as undefined. + // What this means is that there is no way to tell if an operation was successful or not. + // And, it will always return 'true.' + // + // In order to get around this we have to verify if after the attempted deletion operation + // if the record is or is not in the database. Kinda dumb, but that's how IndexedDB works. + //http://stackoverflow.com/questions/17137879/is-there-a-way-to-get-information-on-deleted-record-when-calling-indexeddbs-obj + + var db = this._db; + var deferred = null; + var self = this; + + var id = layerUrl + "/" + graphic.attributes[this.objectId]; + + require(["dojo/Deferred"], function (Deferred) { + deferred = new Deferred(); + + // Step 1 - lets see if record exits. If it does then return callback. + self.editExists(id).then(function (result) { + + // Step 4 - Then we check to see if the record actually exists or not. + deferred.then(function (result) { + + // IF the delete was successful, then the record should return 'false' because it doesn't exist. + self.editExists(id).then(function (results) { + callback(false); + }, + function (err) { + callback(true); //because we want this test to throw an error. That means item deleted. + }); + }, + // There was a problem with the delete operation on the database + function (err) { + callback(false, err); + }); + + var objectStore = db.transaction([self.objectStoreName], "readwrite").objectStore(self.objectStoreName); + + // Step 2 - go ahead and delete graphic + var objectStoreDeleteRequest = objectStore.delete(id); + + // Step 3 - We know that the onsuccess will always fire unless something serious goes wrong. + // So we go ahead and resolve the deferred here. + objectStoreDeleteRequest.onsuccess = function () { + deferred.resolve(true); + }; + + objectStoreDeleteRequest.onerror = function (msg) { + deferred.reject({success: false, error: msg}); + }; + + }, + // If there is an error in editExists() + function (err) { + callback(false); + }); + }); + }; + + /** + * Full database reset. + * CAUTION! If some edits weren't successfully sent, then their record + * will still exist in the database. If you use this function you + * will also delete those records. + * @param callback boolean + */ + this.resetEditsQueue = function (callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + var request = this._db.transaction([this.objectStoreName], "readwrite") + .objectStore(this.objectStoreName) + .clear(); + request.onsuccess = function (event) { + setTimeout(function () { + callback(true); + }, 0); + }; + request.onerror = function (err) { + callback(false, err); + }; + }; + + this.pendingEditsCount = function (callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + var count = 0; + var id = this.FEATURE_LAYER_JSON_ID; + var fCollectionId = this.FEATURE_COLLECTION_ID; + var phantomGraphicPrefix = this.PHANTOM_GRAPHIC_PREFIX; + + var transaction = this._db.transaction([this.objectStoreName], "readwrite"); + var objectStore = transaction.objectStore(this.objectStoreName); + objectStore.openCursor().onsuccess = function (evt) { + var cursor = evt.target.result; + + // IMPORTANT: + // Remember that we have feature layer JSON and Phantom Graphics in the same database + if (cursor && cursor.value && cursor.value.id && cursor.value.id.indexOf(phantomGraphicPrefix) == -1) { + if (cursor.value.id !== id && cursor.value.id !== fCollectionId) { + count++; + } + cursor.continue(); + } + else { + callback(count); + } + }; + }; + + /** + * Verify is an edit already exists in the database. Checks the objectId. + * @param id + * @returns {deferred} {success: boolean, error: message} + * @private + */ + this.editExists = function (id) { + + var db = this._db; + var deferred = null; + var self = this; + + require(["dojo/Deferred"], function (Deferred) { + deferred = new Deferred(); + + var objectStore = db.transaction([self.objectStoreName], "readwrite").objectStore(self.objectStoreName); + + //Get the entry associated with the graphic + var objectStoreGraphicRequest = objectStore.get(id); + + objectStoreGraphicRequest.onsuccess = function () { + var graphic = objectStoreGraphicRequest.result; + if (graphic && (graphic.id == id)) { + deferred.resolve({success: true, error: null}); + } + else { + deferred.reject({success: false, error: "objectId is not a match."}); + } + }; + + objectStoreGraphicRequest.onerror = function (msg) { + deferred.reject({success: false, error: msg}); + }; + }); + + //We return a deferred object so that when calling this function you can chain it with a then() statement. + return deferred; + }; + + /** + * Returns the approximate size of the database in bytes + * IMPORTANT: Currently requires all data be serialized! + * @param callback callback({usage}, error) Whereas, the usage Object is {sizeBytes: number, editCount: number} + */ + this.getUsage = function (callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + var id = this.FEATURE_LAYER_JSON_ID; + var fCollectionId = this.FEATURE_COLLECTION_ID; + var phantomGraphicPrefix = this.PHANTOM_GRAPHIC_PREFIX; + + var usage = {sizeBytes: 0, editCount: 0}; + + var transaction = this._db.transaction([this.objectStoreName]) + .objectStore(this.objectStoreName) + .openCursor(); + + console.log("dumping keys"); + + transaction.onsuccess = function (event) { + var cursor = event.target.result; + if (cursor && cursor.value && cursor.value.id) { + var storedObject = cursor.value; + var json = JSON.stringify(storedObject); + usage.sizeBytes += json.length; + + if (cursor.value.id.indexOf(phantomGraphicPrefix) == -1 && cursor.value.id !== id && cursor.value.id !== fCollectionId) { + usage.editCount += 1; + } + + cursor.continue(); + } + else { + callback(usage, null); + } + }; + transaction.onerror = function (err) { + callback(null, err); + }; + }; + + // + // internal methods + // + + /** + * The library automatically keeps a copy of the featureLayerCollection and its + * associated layer.url. + * + * There should be only one featureLayerCollection Object per feature layer. + * @param featureCollectionObject + * @param callback + * @private + */ + this._pushFeatureCollections = function(featureCollectionObject, callback){ + var transaction = this._db.transaction([this.objectStoreName], "readwrite"); + + transaction.oncomplete = function (event) { + callback(true); + }; + + transaction.onerror = function (event) { + callback(false, event.target.error.message); + }; + + var objectStore = transaction.objectStore(this.objectStoreName); + objectStore.put(featureCollectionObject); + }; + + this._getFeatureCollections = function(callback){ + var objectStore = this._db.transaction([this.objectStoreName], "readonly").objectStore(this.objectStoreName); + + //Get the entry associated with the graphic + var objectStoreGraphicRequest = objectStore.get(this.FEATURE_COLLECTION_ID); + + objectStoreGraphicRequest.onsuccess = function () { + var object = objectStoreGraphicRequest.result; + if (typeof object != "undefined") { + callback(true, object); + } + else { + callback(false, null); + } + }; + + objectStoreGraphicRequest.onerror = function (msg) { + callback(false, msg); + }; + }; + + this.init = function (callback) { + console.log("init editsStore.js"); + + var request = indexedDB.open(this.dbName, 11); + callback = callback || function (success) { + console.log("EditsStore::init() success:", success); + }.bind(this); + + request.onerror = function (event) { + console.log("indexedDB error: " + event.target.errorCode); + callback(false, event.target.errorCode); + }.bind(this); + + request.onupgradeneeded = function (event) { + var db = event.target.result; + + if (db.objectStoreNames.contains(this.objectStoreName)) { + db.deleteObjectStore(this.objectStoreName); + } + + db.createObjectStore(this.objectStoreName, {keyPath: "id"}); + }.bind(this); + + request.onsuccess = function (event) { + this._db = event.target.result; + this._isDBInit = true; + console.log("database opened successfully"); + callback(true, null); + }.bind(this); + }; +}; + + + +/*global IDBKeyRange,indexedDB */ + +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 () { + if (!window.indexedDB) { + return false; + } + return true; + }; + + /** + * Stores an attachment in the database. + * In theory, this abides by the query-attachment-infos-complete Object which can be found here: + * https://developers.arcgis.com/javascript/jsapi/featurelayer-amd.html#event-query-attachment-infos-complete + * @param featureLayerUrl + * @param attachmentId The temporary or actual attachmentId issued by the feature service + * @param objectId The actual ObjectId issues by the feature service + * @param attachmentFile + * @param type Type of operation: "add", "update" or "delete" + * @param callback + */ + this.store = function (featureLayerUrl, attachmentId, objectId, attachmentFile, type, callback) { + try { + // Avoid allowing the wrong type to be stored + if(type == this.TYPE.ADD || type == this.TYPE.UPDATE || type == this.TYPE.DELETE) { + + // first of all, read file content + this._readFile(attachmentFile, function (success, fileContent) { + + if (success) { + // now, store it in the db + var newAttachment = + { + id: attachmentId, + objectId: objectId, + type: type, + + // Unique ID - don't use the ObjectId + // multiple features services could have an a feature with the same ObjectId + featureId: featureLayerUrl + "/" + objectId, + contentType: attachmentFile.type, + name: attachmentFile.name, + size: attachmentFile.size, + featureLayerUrl: featureLayerUrl, + content: fileContent, + file: attachmentFile + }; + + var transaction = this._db.transaction([this.objectStoreName], "readwrite"); + + transaction.oncomplete = function (event) { + callback(true, newAttachment); + }; + + transaction.onerror = function (event) { + callback(false, event.target.error.message); + }; + + try { + transaction.objectStore(this.objectStoreName).put(newAttachment); + } + catch(err) { + callback(false, err); + } + } + else { + callback(false, fileContent); + } + }.bind(this)); + } + else{ + console.error("attachmentsStore.store() Invalid type in the constructor!"); + callback(false,"attachmentsStore.store() Invalid type in the constructor!"); + } + } + catch (err) { + console.log("AttachmentsStore: " + err.stack); + callback(false, err.stack); + } + }; + + this.retrieve = function (attachmentId, callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + var objectStore = this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName); + var request = objectStore.get(attachmentId); + request.onsuccess = function (event) { + var result = event.target.result; + if (!result) { + callback(false, "not found"); + } + else { + callback(true, result); + } + }; + request.onerror = function (err) { + console.log(err); + callback(false, err); + }; + }; + + this.getAttachmentsByFeatureId = function (featureLayerUrl, objectId, callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + var featureId = featureLayerUrl + "/" + objectId; + var attachments = []; + + var objectStore = this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName); + var index = objectStore.index("featureId"); + var keyRange = IDBKeyRange.only(featureId); + index.openCursor(keyRange).onsuccess = function (evt) { + var cursor = evt.target.result; + if (cursor) { + attachments.push(cursor.value); + cursor.continue(); + } + else { + callback(attachments); + } + }; + }; + + this.getAttachmentsByFeatureLayer = function (featureLayerUrl, callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + var attachments = []; + + var objectStore = this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName); + var index = objectStore.index("featureLayerUrl"); + var keyRange = IDBKeyRange.only(featureLayerUrl); + index.openCursor(keyRange).onsuccess = function (evt) { + var cursor = evt.target.result; + if (cursor) { + attachments.push(cursor.value); + cursor.continue(); + } + else { + callback(attachments); + } + }; + }; + + this.getAllAttachments = function (callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + var attachments = []; + + var objectStore = this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName); + objectStore.openCursor().onsuccess = function (evt) { + var cursor = evt.target.result; + if (cursor) { + attachments.push(cursor.value); + cursor.continue(); + } + else { + callback(attachments); + } + }; + }; + + this.deleteAttachmentsByFeatureId = function (featureLayerUrl, objectId, callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + var featureId = featureLayerUrl + "/" + objectId; + + var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); + var index = objectStore.index("featureId"); + var keyRange = IDBKeyRange.only(featureId); + var deletedCount = 0; + index.openCursor(keyRange).onsuccess = function (evt) { + var cursor = evt.target.result; + if (cursor) { + //var attachment = cursor.value; + //this._revokeLocalURL(attachment); + objectStore.delete(cursor.primaryKey); + deletedCount++; + cursor.continue(); + } + else { + setTimeout(function () { + callback(deletedCount); + }, 0); + } + }.bind(this); + }; + + this.delete = function (attachmentId, callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + // before deleting an attachment we must revoke the blob URL that it contains + // in order to free memory in the browser + this.retrieve(attachmentId, function (success, attachment) { + if (!success) { + callback(false, "attachment " + attachmentId + " not found"); + return; + } + + //this._revokeLocalURL(attachment); + + var request = this._db.transaction([this.objectStoreName], "readwrite") + .objectStore(this.objectStoreName) + .delete(attachmentId); + request.onsuccess = function (event) { + setTimeout(function () { + callback(true); + }, 0); + }; + request.onerror = function (err) { + callback(false, err); + }; + }.bind(this)); + }; + + this.deleteAll = function (callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + this.getAllAttachments(function (attachments) { + //attachments.forEach(function (attachment) { + // this._revokeLocalURL(attachment); + //}, this); + + var request = this._db.transaction([this.objectStoreName], "readwrite") + .objectStore(this.objectStoreName) + .clear(); + request.onsuccess = function (event) { + setTimeout(function () { + callback(true); + }, 0); + }; + request.onerror = function (err) { + callback(false, err); + }; + }.bind(this)); + }; + + this.replaceFeatureId = function (featureLayerUrl, oldId, newId, callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + var featureId = featureLayerUrl + "/" + oldId; + + var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); + var index = objectStore.index("featureId"); + var keyRange = IDBKeyRange.only(featureId); + var replacedCount = 0; + index.openCursor(keyRange).onsuccess = function (evt) { + var cursor = evt.target.result; + if (cursor) { + var newFeatureId = featureLayerUrl + "/" + newId; + var updated = cursor.value; + updated.featureId = newFeatureId; + updated.objectId = newId; + objectStore.put(updated); + replacedCount++; + cursor.continue(); + } + else { + // If no records match then evt.target.result = null + // allow time for all changes to persist... + setTimeout(function () { + callback(replacedCount); + }, 1); + } + }; + }; + + this.getUsage = function (callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + var usage = {sizeBytes: 0, attachmentCount: 0}; + + var transaction = this._db.transaction([this.objectStoreName]) + .objectStore(this.objectStoreName) + .openCursor(); + + console.log("dumping keys"); + + transaction.onsuccess = function (event) { + var cursor = event.target.result; + if (cursor) { + console.log(cursor.value.id, cursor.value.featureId, cursor.value.objectId); + var storedObject = cursor.value; + var json = JSON.stringify(storedObject); + usage.sizeBytes += json.length; + usage.attachmentCount += 1; + cursor.continue(); + } + else { + callback(usage, null); + } + }.bind(this); + transaction.onerror = function (err) { + callback(null, err); + }; + }; + + /** + * Full attachments database reset. + * CAUTION! If some attachments weren't successfully sent, then their record + * will still exist in the database. If you use this function you + * will also delete those records. + * @param callback boolean + */ + this.resetAttachmentsQueue = function (callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + var request = this._db.transaction([this.objectStoreName], "readwrite") + .objectStore(this.objectStoreName) + .clear(); + request.onsuccess = function (event) { + setTimeout(function () { + callback(true); + }, 0); + }; + request.onerror = function (err) { + callback(false, err); + }; + }; + + // internal methods + + this._readFile = function (attachmentFile, callback) { + var reader = new FileReader(); + reader.onload = function (evt) { + callback(true,evt.target.result); + }; + reader.onerror = function (evt) { + callback(false,evt.target.result); + }; + reader.readAsBinaryString(attachmentFile); + }; + + // Deprecated @ v2.7 + //this._createLocalURL = function (attachmentFile) { + // return window.URL.createObjectURL(attachmentFile); + //}; + + //this._revokeLocalURL = function (attachment) { + // window.URL.revokeObjectURL(attachment.url); + //}; + + this.init = function (callback) { + console.log("init AttachmentStore"); + + var request = indexedDB.open(this.dbName, 12); + callback = callback || function (success) { + console.log("AttachmentsStore::init() success:", success); + }.bind(this); + + request.onerror = function (event) { + console.log("indexedDB error: " + event.target.errorCode); + callback(false, event.target.errorCode); + }.bind(this); + + request.onupgradeneeded = function (event) { + var db = event.target.result; + + if (db.objectStoreNames.contains(this.objectStoreName)) { + db.deleteObjectStore(this.objectStoreName); + } + + var objectStore = db.createObjectStore(this.objectStoreName, {keyPath: "id"}); + objectStore.createIndex("featureId", "featureId", {unique: false}); + objectStore.createIndex("featureLayerUrl", "featureLayerUrl", {unique: false}); + }.bind(this); + + request.onsuccess = function (event) { + this._db = event.target.result; + console.log("database opened successfully"); + callback(true); + }.bind(this); + }; +}; + diff --git a/dist/offline-edit-basic-min.js b/dist/offline-edit-basic-min.js new file mode 100644 index 00000000..70f0eef5 --- /dev/null +++ b/dist/offline-edit-basic-min.js @@ -0,0 +1,97 @@ +/*! esri-offline-maps - v3.0.0 - 2015-11-23 +* Copyright (c) 2015 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 +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=[] +if(g.getOnlineStatus()===g.ONLINE){var k=a._applyEdits(d,e,f,function(){g.emit(g.events.EDITS_SENT,arguments),h&&h.apply(this,arguments)},i) +return k}var l=new b,m={addResults:[],updateResults:[],deleteResults:[]},n={},o=d||[] +return o.forEach(function(a){var c=new b,d=this._getNextTempId() +a.attributes[this.objectIdField]=d +var e=this +this._validateFeature(a,this.url,g._editStore.ADD).then(function(b){b.success?e._pushValidatedAddFeatureToDB(e,a,b.operation,m,d,c):c.resolve(!0)},function(a){c.reject(a)}),j.push(c)},this),e=e||[],e.forEach(function(a){var c=new b,d=a.attributes[this.objectIdField] +n[d]=a +var e=this +this._validateFeature(a,this.url,g._editStore.UPDATE).then(function(b){b.success?e._pushValidatedUpdateFeatureToDB(e,a,b.operation,m,d,c):c.resolve(!0)},function(a){c.reject(a)}),j.push(c)},this),f=f||[],f.forEach(function(a){var c=new b,d=a.attributes[this.objectIdField],e=this +this._validateFeature(a,this.url,g._editStore.DELETE).then(function(b){b.success?e._pushValidatedDeleteFeatureToDB(e,a,b.operation,m,d,c):c.resolve(!0)},function(a){c.reject(a)}),j.push(c)},this),c(j).then(function(a){for(var b=!0,c=0;c0){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=[] +var r=new m(j[q].graphic) +switch(j[q].operation){case l.ADD:for(var s=0;s0&&(g.updateResults[0].success?(h.layer=g.layer,h.id=g.updateResults[0].objectId,d.push(h)):e.push(g)),g.deleteResults.length>0&&(g.deleteResults[0].success?(h.layer=g.layer,h.id=g.deleteResults[0].objectId,d.push(h)):e.push(g)),g.addResults.length>0&&(g.addResults[0].success?(h.layer=g.layer,h.id=g.tempId,d.push(h)):e.push(g))}for(var i={},j=d.length,k=0;j>k;k++)i[k]=this._updateDatabase(d[k]) +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 +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="" +if(b.length>0&&(e.forEach(b,function(a){a.hasOwnProperty("infoTemplate")&&delete a.infoTemplate},this),i="&adds="+JSON.stringify(b)),c.length>0&&(e.forEach(c,function(a){a.hasOwnProperty("infoTemplate")&&delete a.infoTemplate},this),j="&updates="+JSON.stringify(c)),d.length>0){var l=d[0].attributes[this.DB_UID] +k="&deletes="+l}var m=h+i+j+k +a.hasOwnProperty("credential")&&a.credential&&a.credential.hasOwnProperty("token")&&a.credential.token&&(m=m+"&token="+a.credential.token) +var n=new XMLHttpRequest +n.open("POST",a.url+"/applyEdits",!0),n.setRequestHeader("Content-type","application/x-www-form-urlencoded"),n.onload=function(){if(200===n.status&&""!==n.responseText)try{var a=JSON.parse(this.response) +f(a.addResults,a.updateResults,a.deleteResults)}catch(b){g("Unable to parse xhr response",n)}},n.onerror=function(a){g(a)},n.ontimeout=function(){g("xhr timeout error")},n.timeout=this._defaultXhrTimeout,n.send(m)},_parseResponsesArray:function(a,b){var c=0 +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()} +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)} +var g=f.objectStore(this.objectStoreName) +g.put(e)}},this.getEdit=function(a,b){var c=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName) +if("undefined"==typeof a)return void b(!1,"id is undefined.") +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.getAllEditsArray=function(a){var b=[] +if(null!==this._db){var c=this.FEATURE_LAYER_JSON_ID,d=this.FEATURE_COLLECTION_ID,e=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor() +e.onsuccess=function(e){var f=e.target.result +f&&f.value&&f.value.id?(f.value.id!==c&&f.value.id!==d&&b.push(f.value),f["continue"]()):a(b,"end")}.bind(this),e.onerror=function(b){a(null,b)}}else a(null,"no db")},this.updateExistingEdit=function(a,b,c,d){var e=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName),f=e.get(c.attributes[this.objectId]) +f.onsuccess=function(){f.result +var g={id:b+"/"+c.attributes[this.objectId],operation:a,layer:b,graphic:c.toJson()},h=e.put(g) +h.onsuccess=function(){d(!0)},h.onerror=function(a){d(!1,a)}}.bind(this)},this["delete"]=function(a,b,c){var d=this._db,e=null,f=this,g=a+"/"+b.attributes[this.objectId] +require(["dojo/Deferred"],function(a){e=new a,f.editExists(g).then(function(a){e.then(function(a){f.editExists(g).then(function(a){c(!1)},function(a){c(!0)})},function(a){c(!1,a)}) +var b=d.transaction([f.objectStoreName],"readwrite").objectStore(f.objectStoreName),h=b["delete"](g) +h.onsuccess=function(){e.resolve(!0)},h.onerror=function(a){e.reject({success:!1,error:a})}},function(a){c(!1,a)})})},this.resetEditsQueue=function(a){var b=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName).clear() +b.onsuccess=function(b){setTimeout(function(){a(!0)},0)},b.onerror=function(b){a(!1,b)}},this.pendingEditsCount=function(a){var b=0,c=this.FEATURE_LAYER_JSON_ID,d=this.FEATURE_COLLECTION_ID,e=this._db.transaction([this.objectStoreName],"readwrite"),f=e.objectStore(this.objectStoreName) +f.openCursor().onsuccess=function(e){var f=e.target.result +f&&f.value&&f.value.id?(f.value.id!==c&&f.value.id!==d&&b++,f["continue"]()):a(b)}},this.editExists=function(a){var b=this._db,c=null,d=this +return require(["dojo/Deferred"],function(e){c=new e +var f=b.transaction([d.objectStoreName],"readwrite").objectStore(d.objectStoreName),g=f.get(a) +g.onsuccess=function(){var b=g.result +b&&b.id==a?c.resolve({success:!0,error:null}):c.reject({success:!1,error:"objectId is not a match."})},g.onerror=function(a){c.reject({success:!1,error:a})}}),c},this.getUsage=function(a){var b=this.FEATURE_LAYER_JSON_ID,c=this.FEATURE_COLLECTION_ID,d={sizeBytes:0,editCount:0},e=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor() +e.onsuccess=function(e){var f=e.target.result +if(f&&f.value&&f.value.id){var g=f.value,h=JSON.stringify(g) +d.sizeBytes+=h.length,f.value.id!==b&&f.value.id!==c&&(d.editCount+=1),f["continue"]()}else a(d,null)},e.onerror=function(b){a(null,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)}} diff --git a/dist/offline-edit-basic-src.js b/dist/offline-edit-basic-src.js new file mode 100644 index 00000000..c125af10 --- /dev/null +++ b/dist/offline-edit-basic-src.js @@ -0,0 +1,1457 @@ +/*! esri-offline-maps - v3.0.0 - 2015-11-23 +* Copyright (c) 2015 Environmental Systems Research Institute, Inc. +* Apache License*/ +// Configure offline/online detection +// Requires: http://github.hubspot.com/offline/docs/welcome/ + +Offline.options = { // jshint ignore:line + checks: { + image: { + url: function() { + return 'http://esri.github.io/offline-editor-js/tiny-image.png?_=' + (Math.floor(Math.random() * 1000000000)); + } + }, + active: 'image' + } +}; +/*jshint -W030 */ +/** + * This library is optimized for Partial Offline Support ONLY + */ +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 (Evented, Deferred, all, declare, array, domAttr, domStyle, query, on, + esriConfig, GraphicsLayer, FeatureLayer, Graphic) { + "use strict"; + return declare("O.esri.Edit.OfflineEditBasic", [Evented], + { + _onlineStatus: "online", + _featureLayers: {}, + _editStore: new O.esri.Edit.EditStorePOLS(), + _defaultXhrTimeout: 15000, // ms + _autoOfflineDetect: true, + + ONLINE: "online", // all edits will directly go to the server + OFFLINE: "offline", // edits will be enqueued + RECONNECTING: "reconnecting", // sending stored edits to the server + proxyPath: null, // by default we use CORS and therefore proxyPath is null + + // Database properties + DB_NAME: "features_store", // Sets the database name. + DB_OBJECTSTORE_NAME: "features",// Represents an object store that allows access to a set of data in the IndexedDB database + DB_UID: "objectid", // Set this based on the unique identifier is set up in the feature service + + // manager emits event when... + events: { + EDITS_SENT: "edits-sent", // ...whenever any edit is actually sent to the server + EDITS_ENQUEUED: "edits-enqueued", // ...when an edit is enqueued (and not sent to the server) + EDITS_ENQUEUED_ERROR: "edits-enqueued-error", // ...when there is an error during the queing process + }, + + constructor: function(options){ + if(options && options.hasOwnProperty("autoDetect")){ + this._autoOfflineDetect = options.autoDetect; + } + }, + + /** + * Overrides a feature layer. Call this AFTER the FeatureLayer's 'update-end' event. + * objects such as [esri.Graphic] will need to be serialized or you will get an IndexedDB error. + * @param layer + * @param updateEndEvent The FeatureLayer's update-end event object + * @param callback {true, null} or {false, errorString} Traps whether or not the database initialized + * @returns deferred + */ + extend: function (layer, callback) { + + var extendPromises = []; // deferred promises related to initializing this method + + var self = this; + layer.offlineExtended = true; // to identify layer has been extended + + if(!layer.loaded || layer._url === null) { + console.error("Make sure to initialize OfflineEditBasic after layer loaded and feature layer update-end event."); + } + + // NOTE: At v2.6.1 we've discovered that not all feature layers support objectIdField. + // However, to try to be consistent here with how the library is managing Ids + // we force the layer.objectIdField to DB_UID. This should be consistent with + // how esri.Graphics assign a unique ID to a graphic. If it is not, then this + // library will break and we'll have to re-architect how it manages UIDs. + layer.objectIdField = this.DB_UID; + + var url = null; + + // There have been reproducible use cases showing when a browser is restarted offline that + // for some reason the layer.url may be undefined. + // This is an attempt to minimize the possibility of that situation causing errors. + if(layer.url) { + url = layer.url; + // we keep track of the FeatureLayer object + this._featureLayers[layer.url] = layer; + } + + // Initialize the database as well as set offline data. + if(!this._editStore._isDBInit) { + extendPromises.push(this._initializeDB(url)); + } + + // replace the applyEdits() method + layer._applyEdits = layer.applyEdits; + + /** + * Overrides the ArcGIS API for JavaSccript applyEdits() method. + * @param adds Creates a new edit entry. + * @param updates Updates an existing entry. + * @param deletes Deletes an existing entry. + * @param callback Called when the operation is complete. + * @param errback An error object is returned if an error occurs + * @returns {*} deferred + * @event EDITS_ENQUEUED boolean if all edits successfully stored while offline + * @event EDITS_ENQUEUED_ERROR string message if there was an error while storing an edit while offline + */ + layer.applyEdits = function (adds, updates, deletes, callback, errback) { + // inside this method, 'this' will be the FeatureLayer + // and 'self' will be the offlineFeatureLayer object + var promises = []; + + if (self.getOnlineStatus() === self.ONLINE) { + var def = layer._applyEdits(adds, updates, deletes, + function () { + self.emit(self.events.EDITS_SENT, arguments); + callback && callback.apply(this, arguments); + }, + errback); + return def; + } + + var deferred1 = new Deferred(); + var results = {addResults: [], updateResults: [], deleteResults: []}; + var updatesMap = {}; + + var _adds = adds || []; + _adds.forEach(function (addEdit) { + var deferred = new Deferred(); + + var objectId = this._getNextTempId(); + + addEdit.attributes[this.objectIdField] = objectId; + + var thisLayer = this; + + // We need to run some validation tests against each feature being added. + // Adding the same feature multiple times results in the last edit wins. LIFO. + this._validateFeature(addEdit,this.url,self._editStore.ADD).then(function(result){ + console.log("EDIT ADD IS BACK!!! " ); + + if(result.success){ + thisLayer._pushValidatedAddFeatureToDB(thisLayer,addEdit,result.operation,results,objectId,deferred); + } + else{ + // If we get here then we deleted an edit that was added offline. + deferred.resolve(true); + } + + },function(error){ + console.log("_validateFeature: Unable to validate!"); + deferred.reject(error); + }); + + promises.push(deferred); + }, this); + + updates = updates || []; + updates.forEach(function (updateEdit) { + var deferred = new Deferred(); + + var objectId = updateEdit.attributes[this.objectIdField]; + updatesMap[objectId] = updateEdit; + + var thisLayer = this; + + // We need to run some validation tests against each feature being updated. + // If we have added a feature and we need to update it then we change it's operation type to "add" + // and the last edits wins. LIFO. + this._validateFeature(updateEdit,this.url,self._editStore.UPDATE).then(function(result){ + console.log("EDIT UPDATE IS BACK!!! " ); + + if(result.success){ + thisLayer._pushValidatedUpdateFeatureToDB(thisLayer,updateEdit,result.operation,results,objectId,deferred); + } + else{ + // If we get here then we deleted an edit that was added offline. + deferred.resolve(true); + } + + },function(error){ + console.log("_validateFeature: Unable to validate!"); + deferred.reject(error); + }); + + promises.push(deferred); + }, this); + + deletes = deletes || []; + deletes.forEach(function (deleteEdit) { + var deferred = new Deferred(); + + var objectId = deleteEdit.attributes[this.objectIdField]; + + var thisLayer = this; + + // We need to run some validation tests against each feature being deleted. + // If we have added a feature and then deleted it in the app + this._validateFeature(deleteEdit,this.url,self._editStore.DELETE).then(function(result){ + console.log("EDIT DELETE IS BACK!!! " ); + + if(result.success){ + thisLayer._pushValidatedDeleteFeatureToDB(thisLayer,deleteEdit,result.operation,results,objectId,deferred); + } + else{ + // If we get here then we deleted an edit that was added offline. + deferred.resolve(true); + } + + },function(error){ + console.log("_validateFeature: Unable to validate!"); + deferred.reject(error); + }); + + promises.push(deferred); + }, this); + + all(promises).then(function (r) { + // Make sure all edits were successful. If not throw an error. + var promisesSuccess = true; + for (var v = 0; v < r.length; v++) { + if (r[v] === false) { + promisesSuccess = false; + } + } + + promisesSuccess === true ? self.emit(self.events.EDITS_ENQUEUED, results) : self.emit(self.events.EDITS_ENQUEUED_ERROR, results); + this._editHandler(results, _adds, updatesMap, callback, errback, deferred1); + }.bind(this)); + + return deferred1; + + }; // layer.applyEdits() + + /** + * Returns the approximate size of the edits database in bytes + * @param callback callback({usage}, error) Whereas, the usage Object is {sizeBytes: number, editCount: number} + */ + layer.getUsage = function(callback){ + self._editStore.getUsage(function(usage,error){ + callback(usage,error); + }); + }; + + /** + * Full edits database reset. + * CAUTION! If some edits weren't successfully sent, then their record + * will still exist in the database. If you use this function you + * will also delete those records. + * @param callback (boolean, error) + */ + layer.resetDatabase = function(callback){ + self._editStore.resetEditsQueue(function(result,error){ + callback(result,error); + }); + }; + + /** + * Returns the number of edits pending in the database. + * @param callback callback( int ) + */ + layer.pendingEditsCount = function(callback){ + self._editStore.pendingEditsCount(function(count){ + callback(count); + }); + }; + + /** + * Create a featureDefinition + * @param featureLayer + * @param featuresArr + * @param geometryType + * @param callback + */ + layer.getFeatureDefinition = function (/* Object */ featureLayer, /* Array */ featuresArr, /* String */ geometryType, callback) { + + var featureDefinition = { + "layerDefinition": featureLayer, + "featureSet": { + "features": featuresArr, + "geometryType": geometryType + } + + }; + + callback(featureDefinition); + }; + + /** + * Returns an iterable array of all edits stored in the database + * Each item in the array is an object and contains: + * { + * id: "internal ID", + * operation: "add, update or delete", + * layer: "layerURL", + * type: "esri Geometry Type", + * graphic: "esri.Graphic converted to JSON then serialized" + * } + * @param callback (true, array) or (false, errorString) + */ + layer.getAllEditsArray = function(callback){ + self._editStore.getAllEditsArray(function(array,message){ + if(message == "end"){ + callback(true,array); + } + else{ + callback(false,message); + } + }); + }; + + /* internal methods */ + + /** + * Pushes a DELETE request to the database after it's been validated + * @param layer + * @param deleteEdit + * @param operation + * @param resultsArray + * @param objectId + * @param deferred + * @private + */ + layer._pushValidatedDeleteFeatureToDB = function(layer,deleteEdit,operation,resultsArray,objectId,deferred){ + self._editStore.pushEdit(operation, layer.url, deleteEdit, function (result, error) { + + if(result){ + resultsArray.deleteResults.push({success: true, error: null, objectId: objectId}); + + // Use the correct key as set by self.DB_UID + var tempIdObject = {}; + tempIdObject[self.DB_UID] = objectId; + } + else{ + resultsArray.deleteResults.push({success: false, error: error, objectId: objectId}); + } + + deferred.resolve(result); + }); + }; + + /** + * Pushes an UPDATE request to the database after it's been validated + * @param layer + * @param updateEdit + * @param operation + * @param resultsArray + * @param objectId + * @param deferred + * @private + */ + layer._pushValidatedUpdateFeatureToDB = function(layer,updateEdit,operation,resultsArray,objectId,deferred){ + self._editStore.pushEdit(operation, layer.url, updateEdit, function (result, error) { + + if(result){ + resultsArray.updateResults.push({success: true, error: null, objectId: objectId}); + + // Use the correct key as set by self.DB_UID + var tempIdObject = {}; + tempIdObject[self.DB_UID] = objectId; + } + else{ + resultsArray.updateResults.push({success: false, error: error, objectId: objectId}); + } + + deferred.resolve(result); + }); + }; + + /** + * Pushes an ADD request to the database after it's been validated + * @param layer + * @param addEdit + * @param operation + * @param resultsArray + * @param objectId + * @param deferred + * @private + */ + layer._pushValidatedAddFeatureToDB = function(layer,addEdit,operation,resultsArray,objectId,deferred){ + self._editStore.pushEdit(operation, layer.url, addEdit, function (result, error) { + if(result){ + resultsArray.addResults.push({success: true, error: null, objectId: objectId}); + + // Use the correct key as set by self.DB_UID + var tempIdObject = {}; + tempIdObject[self.DB_UID] = objectId; + } + else{ + resultsArray.addResults.push({success: false, error: error, objectId: objectId}); + } + + deferred.resolve(result); + }); + }; + + /** + * Validates duplicate entries. Last edit on same feature can overwite any previous values. + * Note: if an edit was already added offline and you delete it then we return success == false + * @param graphic esri.Graphic. + * @param layerUrl the URL of the feature service + * @param operation add, update or delete action on an edit + * @returns deferred {success:boolean,graphic:graphic,operation:add|update|delete} + * @private + */ + layer._validateFeature = function (graphic,layerUrl,operation) { + + var deferred = new Deferred(); + + var id = layerUrl + "/" + graphic.attributes[self.DB_UID]; + + self._editStore.getEdit(id,function(success,result){ + if (success) { + switch( operation ) + { + case self._editStore.ADD: + // Not good - however we'll allow the new ADD to replace/overwrite existing edit + // and pass it through unmodified. Last ADD wins. + deferred.resolve({"success":true,"graphic":graphic,"operation":operation}); + break; + case self._editStore.UPDATE: + // If we are doing an update on a feature that has not been added to + // the server yet, then we need to maintain its operation as an ADD + // and not an UPDATE. This avoids the potential for an error if we submit + // an update operation on a feature that has not been added to the + // database yet. + if(result.operation == self._editStore.ADD){ + graphic.operation = self._editStore.ADD; + operation = self._editStore.ADD; + } + deferred.resolve({"success":true,"graphic":graphic,"operation":operation}); + break; + case self._editStore.DELETE: + + var resolved = true; + + if(result.operation == self._editStore.ADD){ + // If we are deleting a new feature that has not been added to the + // server yet we need to delete it + layer._deleteTemporaryFeature(graphic,function(success, error){ + if(!success){ + resolved = false; + console.log("Unable to delete feature: " + JSON.stringify(error)); + } + }); + } + deferred.resolve({"success":resolved,"graphic":graphic,"operation":operation}); + break; + } + } + else if(result == "Id not found"){ + // Let's simply pass the graphic back as good-to-go. + // No modifications needed because the graphic does not + // already exist in the database. + deferred.resolve({"success":true,"graphic":graphic,"operation":operation}); + } + else{ + deferred.reject(graphic); + } + }); + + return deferred; + }; + + /** + * Delete a graphic that has been added while offline. + * @param graphic + * @param callback + * @private + */ + layer._deleteTemporaryFeature = function(graphic,callback){ + self._editStore.delete(layer.url,graphic,function(success,error){ + callback(success, error); + }); + }; + + layer._getFilesFromForm = function (formNode) { + var files = []; + var inputNodes = array.filter(formNode.elements, function (node) { + return node.type === "file"; + }); + inputNodes.forEach(function (inputNode) { + files.push.apply(files, inputNode.files); + }, this); + return files; + }; + + // we need to identify ADDs before sending them to the server + // we assign temporary ids (using negative numbers to distinguish them from real ids) + layer._nextTempId = -1; + layer._getNextTempId = function () { + return this._nextTempId--; + }; + + // We are currently only passing in a single deferred. + all(extendPromises).then(function (r) { + + if(self._autoOfflineDetect){ + Offline.on('up', function(){ // jshint ignore:line + + self.goOnline(function(success,error){ // jshint ignore:line + console.log("GOING ONLINE"); + }); + }); + + Offline.on('down', function(){ // jshint ignore:line + self.goOffline(); // jshint ignore:line + }); + } + + callback(true, null); + }); + + }, // extend + + /** + * Forces library into an offline state. Any edits applied during this condition will be stored locally + */ + goOffline: function () { + console.log("offlineFeatureManager going offline"); + this._onlineStatus = this.OFFLINE; + }, + + /** + * Forces library to return to an online state. If there are pending edits, + * an attempt will be made to sync them with the remote feature server + * @param callback callback( boolean, errors ) + */ + goOnline: function (callback) { + console.log("OfflineEditBasic going online"); + this._onlineStatus = this.RECONNECTING; + this._replayStoredEdits(function (success, responses) { + //var result = {success: success, responses: responses}; + this._onlineStatus = this.ONLINE; + + //this._onlineStatus = this.ONLINE; + callback && callback(success,responses); + + }.bind(this)); + }, + + /** + * Determines if offline or online condition exists + * @returns {string} ONLINE or OFFLINE + */ + getOnlineStatus: function () { + return this._onlineStatus; + }, + + /* internal methods */ + + /** + * Initialize the database and push featureLayer JSON to DB if required. + * @param url Feature Layer's url. This is used by this library for internal feature identification. + * @return deferred + * @private + */ + _initializeDB: function(url){ + var deferred = new Deferred(); + + var editStore = this._editStore; + + // Configure the database + editStore.dbName = this.DB_NAME; + editStore.objectStoreName = this.DB_OBJECTSTORE_NAME; + editStore.objectId = this.DB_UID; + + // Attempt to initialize the database + editStore.init(function (result, error) { + + if(result){ + deferred.resolve({success:true, error: null}); + } + else{ + deferred.reject({success:false, error: null}); + } + }); + + return deferred; + }, + + // + // methods to send features back to the server + // + + /** + * Attempts to send any edits in the database. Monitor events for success or failure. + * @param callback + * @event ALL_EDITS_SENT when all edits have been successfully sent. Contains {[addResults],[updateResults],[deleteResults]} + * @event EDITS_SENT_ERROR some edits were not sent successfully. Contains {msg: error} + * @private + */ + _replayStoredEdits: function (callback) { + var promises = {}; + var that = this; + + // + // send edits for each of the layers + // + var layer; + var adds = [], updates = [], deletes = []; + var tempObjectIds = []; + var tempArray = []; + var featureLayers = this._featureLayers; + + var editStore = this._editStore; + + this._editStore.getAllEditsArray(function (result, err) { + if (result.length > 0) { + tempArray = result; + + var length = tempArray.length; + + for (var n = 0; n < length; n++) { + layer = featureLayers[tempArray[n].layer]; + layer.__onEditsComplete = layer.onEditsComplete; + layer.onEditsComplete = function () { + console.log("intercepting events onEditsComplete"); + }; + + // Let's zero everything out + adds = [], updates = [], deletes = [], tempObjectIds = []; + + // IMPORTANT: reconstitute the graphic JSON into an actual esri.Graphic object + // NOTE: we are only sending one Graphic per loop! + var graphic = new Graphic(tempArray[n].graphic); + + switch (tempArray[n].operation) { + case editStore.ADD: + for (var i = 0; i < layer.graphics.length; i++) { + var g = layer.graphics[i]; + if (g.attributes[layer.objectIdField] === graphic.attributes[layer.objectIdField]) { + layer.remove(g); + break; + } + } + tempObjectIds.push(graphic.attributes[layer.objectIdField]); + delete graphic.attributes[layer.objectIdField]; + adds.push(graphic); + break; + case editStore.UPDATE: + updates.push(graphic); + break; + case editStore.DELETE: + deletes.push(graphic); + break; + } + + // Note: when the feature layer is created with a feature collection we have to handle applyEdits() differently + // TO-DO rename this method. + promises[n] = that._internalApplyEditsAll(layer, tempArray[n].id, tempObjectIds, adds, updates, deletes); + } + + // wait for all requests to finish + // responses contain {id,layer,tempId,addResults,updateResults,deleteResults} + var allPromises = all(promises); + allPromises.then( + function (responses) { + console.log("OfflineEditBasic sync - all responses are back"); + callback(true, responses); + }, + function (errors) { + console.log("OfflineEditBasic._replayStoredEdits - ERROR!!"); + callback(false, errors); + } + ); + + } + else{ + // No edits were found + callback(true,[]); + } + }); + }, + + /** + * DEPRECATED as of v2.11 - + * TO-DO remove in next release + * Only delete items from database that were verified as successfully updated on the server. + * @param responses Object + * @param callback callback(true, responses) or callback(false, responses) + * @private + */ + _cleanSuccessfulEditsDatabaseRecords: function (responses, callback) { + if (Object.keys(responses).length !== 0) { + + var editsArray = []; + var editsFailedArray = []; + + for (var key in responses) { + if (responses.hasOwnProperty(key)) { + + var edit = responses[key]; + var tempResult = {}; + + if (edit.updateResults.length > 0) { + if (edit.updateResults[0].success) { + tempResult.layer = edit.layer; + tempResult.id = edit.updateResults[0].objectId; + editsArray.push(tempResult); + } + else { + editsFailedArray.push(edit); + } + } + if (edit.deleteResults.length > 0) { + if (edit.deleteResults[0].success) { + tempResult.layer = edit.layer; + tempResult.id = edit.deleteResults[0].objectId; + editsArray.push(tempResult); + } + else { + editsFailedArray.push(edit); + } + } + if (edit.addResults.length > 0) { + if (edit.addResults[0].success) { + tempResult.layer = edit.layer; + tempResult.id = edit.tempId; + editsArray.push(tempResult); + } + else { + editsFailedArray.push(edit); + } + } + } + } + + var promises = {}; + var length = editsArray.length; + for (var i = 0; i < length; i++) { + promises[i] = this._updateDatabase(editsArray[i]); + } + //console.log("EDIT LIST " + JSON.stringify(editsArray)); + + // wait for all requests to finish + // + var allPromises = all(promises); + allPromises.then( + function (responses) { + editsFailedArray.length > 0 ? callback(false, responses) : callback(true, responses); + }, + function (errors) { + callback(false, errors); + } + ); + } + else { + callback(true, {}); + } + }, + + /** + * Deletes edits from database. + * @param edit + * @returns {l.Deferred.promise|*|c.promise|q.promise|promise} + * @private + */ + _updateDatabase: function (edit) { + var dfd = new Deferred(); + var fakeGraphic = {}; + fakeGraphic.attributes = {}; + + // Use the correct attributes key! + fakeGraphic.attributes[this.DB_UID] = edit.id; + + this._editStore.delete(edit.layer, fakeGraphic, function (success, error) { + if (success) { + dfd.resolve({success: true, error: null}); + } + else { + dfd.reject({success: false, error: error}); + } + }.bind(this)); + + return dfd.promise; + + }, + + /** + * Applies edits. This works with both standard feature layers and when a feature layer is created + * using a feature collection. + * + * This works around specific behaviors in esri.layers.FeatureLayer when using the pattern + * new FeatureLayer(featureCollectionObject). + * + * Details on the specific behaviors can be found here: + * https://developers.arcgis.com/javascript/jsapi/featurelayer-amd.html#featurelayer2 + * + * @param layer + * @param id + * @param tempObjectIds + * @param adds + * @param updates + * @param deletes + * @returns {*|r} + * @private + */ + _internalApplyEditsAll: function (layer, id, tempObjectIds, adds, updates, deletes) { + var that = this; + var dfd = new Deferred(); + + this._makeEditRequest(layer, adds, updates, deletes, + function (addResults, updateResults, deleteResults) { + + if(addResults.length > 0) { + var graphic = new Graphic(adds[0].geometry,null,adds[0].attributes); + layer.add(graphic); + } + + that._cleanDatabase(layer, tempObjectIds, addResults, updateResults, deleteResults).then(function(results){ + dfd.resolve({ + id: id, + layer: layer.url, + tempId: tempObjectIds, // let's us internally match an ADD to it's new ObjectId + addResults: addResults, + updateResults: updateResults, + deleteResults: deleteResults, + databaseResults: results, + databaseErrors: null, + syncError: null + }); + }, function(error) { + dfd.resolve({ + id: id, + layer: layer.url, + tempId: tempObjectIds, // let's us internally match an ADD to it's new ObjectId + addResults: addResults, + updateResults: updateResults, + deleteResults: deleteResults, + databaseResults: null, + databaseErrors: error, + syncError: error + }); + }); + + }, + function (error) { + layer.onEditsComplete = layer.__onEditsComplete; + delete layer.__onEditsComplete; + + dfd.reject(error); + } + ); + return dfd.promise; + }, + + _cleanDatabase: function(layer, tempId, addResults, updateResults, deleteResults) { + + var dfd = new Deferred(); + var id = null; + + if (updateResults.length > 0) { + if (updateResults[0].success) { + id = updateResults[0].objectId; + } + } + if (deleteResults.length > 0) { + if (deleteResults[0].success) { + id = deleteResults[0].objectId; + } + } + if (addResults.length > 0) { + if (addResults[0].success) { + id = tempId; + } + } + + var fakeGraphic = {}; + fakeGraphic.attributes = {}; + + // Use the correct attributes key! + fakeGraphic.attributes[this.DB_UID] = id; + + // Delete the edit from the database + this._editStore.delete(layer.url, fakeGraphic, function (success, error) { + if (success) { + dfd.resolve({success: true, error: null, id: id}); + } + else { + dfd.reject({success: false, error: error, id: id}); + } + }); + + return dfd.promise; + }, + + /** + * Used when a feature layer is created with a feature collection. + * + * In the current version of the ArcGIS JSAPI 3.12+ the applyEdit() method doesn't send requests + * to the server when a feature layer is created with a feature collection. + * + * The use case for using this is: clean start app > go offline and make edits > offline restart browser > + * go online. + * + * @param layer + * @param adds + * @param updates + * @param deletes + * @returns {*|r} + * @private + */ + _makeEditRequest: function(layer,adds, updates, deletes, callback, errback) { + + var f = "f=json", a = "", u = "", d = ""; + + if(adds.length > 0) { + array.forEach(adds, function(add){ + if(add.hasOwnProperty("infoTemplate")){ // if the add has an infoTemplate attached, + delete add.infoTemplate; // delete it to reduce payload size. + } + }, this); + a = "&adds=" + JSON.stringify((adds)); + } + if(updates.length > 0) { + array.forEach(updates, function(update){ + if(update.hasOwnProperty("infoTemplate")){ // if the update has an infoTemplate attached, + delete update.infoTemplate; // delete it to reduce payload size. + } + }, this); + u = "&updates=" + JSON.stringify(updates); + } + if(deletes.length > 0) { + var id = deletes[0].attributes[this.DB_UID]; + d = "&deletes=" + id; + } + + var params = f + a + u + d; + + if(layer.hasOwnProperty("credential") && layer.credential){ + if(layer.credential.hasOwnProperty("token") && layer.credential.token){ + params = params + "&token=" + layer.credential.token; + } + } + + var req = new XMLHttpRequest(); + req.open("POST", layer.url + "/applyEdits", true); + req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + req.onload = function() + { + if( req.status === 200 && req.responseText !== "") + { + try { + var obj = JSON.parse(this.response); + callback(obj.addResults, obj.updateResults, obj.deleteResults); + } + catch(err) { + console.error("EDIT REQUEST REPONSE WAS NOT SUCCESSFUL:", req); + errback("Unable to parse xhr response", req); + } + } + + }; + req.onerror = function(e) + { + console.error("_makeEditRequest failed: " + e); + errback(e); + }; + req.ontimeout = function() { + errback("xhr timeout error"); + }; + req.timeout = this._defaultXhrTimeout; + req.send(params); + }, + + /** + * Parses the respones related to going back online and cleaning up the database. + * @param responses + * @returns {promise} True means all was successful. False indicates there was a problem. + * @private + */ + _parseResponsesArray: function(responses,callback) { + + var err = 0; + + for (var key in responses) { + if (responses.hasOwnProperty(key)) { + responses[key].addResults.forEach(function(result){ + if(!result.success) { + err++; + } + }); + + responses[key].updateResults.forEach(function(result){ + if(!result.success) { + err++; + } + }); + + responses[key].deleteResults.forEach(function(result){ + if(!result.success) { + err++; + } + }); + } + } + + if(err > 0){ + callback(false); + } + else { + callback(true); + } + } + }); // declare + }); // define +/** + * Creates a namespace for the non-AMD libraries in this directory + */ +/*jshint -W020 */ +if(typeof O != "undefined"){ + O.esri.Edit = {}; +} +else{ + O = {}; + O.esri = { + Edit: {} + }; +} +/*global indexedDB */ +/*jshint -W030 */ +/** + * This library is optimized for Partial Offline Support ONLY + * @constructor + */ +O.esri.Edit.EditStorePOLS = function () { + + "use strict"; + + this._db = null; + this._isDBInit = false; + + // Public properties + + this.dbName = "features_store"; + this.objectStoreName = "features"; + this.objectId = "objectid"; // set this depending on how your feature service is configured; + + //var _dbIndex = "featureId"; // @private + + // ENUMs + + 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 () { + if (!window.indexedDB) { + return false; + } + return true; + }; + + /** + * Commit an edit to the database + * @param operation add, update or delete + * @param layerUrl the URL of the feature layer + * @param graphic esri/graphic. The method will serialize to JSON + * @param callback callback(true, edit) or callback(false, error) + */ + this.pushEdit = function (operation, layerUrl, graphic, callback) { + + var edit = { + id: layerUrl + "/" + graphic.attributes[this.objectId], + operation: operation, + layer: layerUrl, + type: graphic.geometry.type, + graphic: graphic.toJson() + }; + + if(typeof graphic.attributes[this.objectId] === "undefined") { + console.error("editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineEdit.DB_UID? " + JSON.stringify(graphic.attributes)); + callback(false,"editsStore.pushEdit() - failed to insert undefined objectId into database. Did you set offlineEdit.DB_UID? " + JSON.stringify(graphic.attributes)); + } + else{ + var transaction = this._db.transaction([this.objectStoreName], "readwrite"); + + transaction.oncomplete = function (event) { + callback(true); + }; + + transaction.onerror = function (event) { + callback(false, event.target.error.message); + }; + + var objectStore = transaction.objectStore(this.objectStoreName); + objectStore.put(edit); + } + }; + + /** + * Retrieve an edit by its internal ID + * @param id String identifier + * @param callback callback(true,graphic) or callback(false, error) + */ + this.getEdit = function(id,callback){ + + console.assert(this._db !== null, "indexeddb not initialized"); + var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); + + if(typeof id === "undefined"){ + callback(false,"id is undefined."); + return; + } + + //Get the entry associated with the graphic + var objectStoreGraphicRequest = objectStore.get(id); + + objectStoreGraphicRequest.onsuccess = function () { + var graphic = objectStoreGraphicRequest.result; + if (graphic && (graphic.id == id)) { + callback(true,graphic); + } + else { + callback(false,"Id not found"); + } + }; + + objectStoreGraphicRequest.onerror = function (msg) { + callback(false,msg); + }; + }; + + /** + * Returns all the edits as a single Array via the callback + * @param callback {array, messageString} or {null, messageString} + */ + this.getAllEditsArray = function (callback) { + + console.assert(this._db !== null, "indexeddb not initialized"); + var editsArray = []; + + if (this._db !== null) { + + var fLayerJSONId = this.FEATURE_LAYER_JSON_ID; + var fCollectionId = this.FEATURE_COLLECTION_ID; + + var transaction = this._db.transaction([this.objectStoreName]) + .objectStore(this.objectStoreName) + .openCursor(); + + transaction.onsuccess = function (event) { + var cursor = event.target.result; + if (cursor && cursor.value && cursor.value.id) { + + // Make sure we are not return FeatureLayer JSON data + if (cursor.value.id !== fLayerJSONId && cursor.value.id !== fCollectionId) { + editsArray.push(cursor.value); + + } + cursor.continue(); + } + else { + callback(editsArray, "end"); + } + }.bind(this); + transaction.onerror = function (err) { + callback(null, err); + }; + } + else { + callback(null, "no db"); + } + }; + + /** + * Update an edit already exists in the database + * @param operation add, update or delete + * @param layer the URL of the feature layer + * @param graphic esri/graphic. The method will serialize to JSON + * @param callback {true, edit} or {false, error} + */ + this.updateExistingEdit = function (operation, layer, graphic, callback) { + + console.assert(this._db !== null, "indexeddb not initialized"); + + var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName); + + //Let's get the entry associated with the graphic + var objectStoreGraphicRequest = objectStore.get(graphic.attributes[this.objectId]); + objectStoreGraphicRequest.onsuccess = function () { + + //Grab the data object returned as a result + // TO-DO Do we keep this?? + objectStoreGraphicRequest.result; + + //Create a new update object + var update = { + id: layer + "/" + graphic.attributes[this.objectId], + operation: operation, + layer: layer, + graphic: graphic.toJson() + }; + + // Insert the update into the database + var updateGraphicRequest = objectStore.put(update); + + updateGraphicRequest.onsuccess = function () { + callback(true); + }; + + updateGraphicRequest.onerror = function (err) { + callback(false, err); + }; + }.bind(this); + }; + + /** + * Delete a pending edit's record from the database. + * IMPORTANT: Be aware of false negatives. See Step 4 in this function. + * + * @param layerUrl + * @param graphic Graphic + * @param callback {boolean, error} + */ + this.delete = function (layerUrl, graphic, callback) { + + // NOTE: the implementation of the IndexedDB spec has a design fault with respect to + // handling deletes. The result of a delete operation is always designated as undefined. + // What this means is that there is no way to tell if an operation was successful or not. + // And, it will always return 'true.' + // + // In order to get around this we have to verify if after the attempted deletion operation + // if the record is or is not in the database. Kinda dumb, but that's how IndexedDB works. + //http://stackoverflow.com/questions/17137879/is-there-a-way-to-get-information-on-deleted-record-when-calling-indexeddbs-obj + + var db = this._db; + var deferred = null; + var self = this; + + var id = layerUrl + "/" + graphic.attributes[this.objectId]; + + require(["dojo/Deferred"], function (Deferred) { + deferred = new Deferred(); + + // Step 1 - lets see if record exits. If it does then return callback. + self.editExists(id).then(function (result) { + + // Step 4 - Then we check to see if the record actually exists or not. + deferred.then(function (result) { + + // IF the delete was successful, then the record should return 'false' because it doesn't exist. + self.editExists(id).then(function (results) { + callback(false); + }, + function (err) { + callback(true); //because we want this test to throw an error. That means item deleted. + }); + }, + // There was a problem with the delete operation on the database + function (err) { + callback(false, err); + }); + + var objectStore = db.transaction([self.objectStoreName], "readwrite").objectStore(self.objectStoreName); + + // Step 2 - go ahead and delete graphic + var objectStoreDeleteRequest = objectStore.delete(id); + + // Step 3 - We know that the onsuccess will always fire unless something serious goes wrong. + // So we go ahead and resolve the deferred here. + objectStoreDeleteRequest.onsuccess = function () { + deferred.resolve(true); + }; + + objectStoreDeleteRequest.onerror = function (msg) { + deferred.reject({success: false, error: msg}); + }; + + }, + // If there is an error in editExists() + function (err) { + callback(false, err); + }); + }); + }; + + /** + * Full database reset. + * CAUTION! If some edits weren't successfully sent, then their record + * will still exist in the database. If you use this function you + * will also delete those records. + * @param callback boolean + */ + this.resetEditsQueue = function (callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + var request = this._db.transaction([this.objectStoreName], "readwrite") + .objectStore(this.objectStoreName) + .clear(); + request.onsuccess = function (event) { + setTimeout(function () { + callback(true); + }, 0); + }; + request.onerror = function (err) { + callback(false, err); + }; + }; + + this.pendingEditsCount = function (callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + var count = 0; + var id = this.FEATURE_LAYER_JSON_ID; + var fCollectionId = this.FEATURE_COLLECTION_ID; + + var transaction = this._db.transaction([this.objectStoreName], "readwrite"); + var objectStore = transaction.objectStore(this.objectStoreName); + objectStore.openCursor().onsuccess = function (evt) { + var cursor = evt.target.result; + if (cursor && cursor.value && cursor.value.id) { + if (cursor.value.id !== id && cursor.value.id !== fCollectionId) { + count++; + } + cursor.continue(); + } + else { + callback(count); + } + }; + }; + + /** + * Verify is an edit already exists in the database. Checks the objectId. + * @param id + * @returns {deferred} {success: boolean, error: message} + * @private + */ + this.editExists = function (id) { + + var db = this._db; + var deferred = null; + var self = this; + + require(["dojo/Deferred"], function (Deferred) { + deferred = new Deferred(); + + var objectStore = db.transaction([self.objectStoreName], "readwrite").objectStore(self.objectStoreName); + + //Get the entry associated with the graphic + var objectStoreGraphicRequest = objectStore.get(id); + + objectStoreGraphicRequest.onsuccess = function () { + var graphic = objectStoreGraphicRequest.result; + if (graphic && (graphic.id == id)) { + deferred.resolve({success: true, error: null}); + } + else { + deferred.reject({success: false, error: "objectId is not a match."}); + } + }; + + objectStoreGraphicRequest.onerror = function (msg) { + deferred.reject({success: false, error: msg}); + }; + }); + + //We return a deferred object so that when calling this function you can chain it with a then() statement. + return deferred; + }; + + /** + * Returns the approximate size of the database in bytes + * IMPORTANT: Currently requires all data be serialized! + * @param callback callback({usage}, error) Whereas, the usage Object is {sizeBytes: number, editCount: number} + */ + this.getUsage = function (callback) { + console.assert(this._db !== null, "indexeddb not initialized"); + + var id = this.FEATURE_LAYER_JSON_ID; + var fCollectionId = this.FEATURE_COLLECTION_ID; + + var usage = {sizeBytes: 0, editCount: 0}; + + var transaction = this._db.transaction([this.objectStoreName]) + .objectStore(this.objectStoreName) + .openCursor(); + + console.log("dumping keys"); + + transaction.onsuccess = function (event) { + var cursor = event.target.result; + if (cursor && cursor.value && cursor.value.id) { + var storedObject = cursor.value; + var json = JSON.stringify(storedObject); + usage.sizeBytes += json.length; + + if (cursor.value.id !== id && cursor.value.id !== fCollectionId) { + usage.editCount += 1; + } + + cursor.continue(); + } + else { + callback(usage, null); + } + }; + transaction.onerror = function (err) { + callback(null, err); + }; + }; + + this.init = function (callback) { + console.log("init editsStore.js"); + + var request = indexedDB.open(this.dbName, 11); + callback = callback || function (success) { + console.log("EditsStore::init() success:", success); + }.bind(this); + + request.onerror = function (event) { + console.log("indexedDB error: " + event.target.errorCode); + callback(false, event.target.errorCode); + }.bind(this); + + request.onupgradeneeded = function (event) { + var db = event.target.result; + + if (db.objectStoreNames.contains(this.objectStoreName)) { + db.deleteObjectStore(this.objectStoreName); + } + + db.createObjectStore(this.objectStoreName, {keyPath: "id"}); + }.bind(this); + + request.onsuccess = function (event) { + this._db = event.target.result; + this._isDBInit = true; + console.log("database opened successfully"); + callback(true, null); + }.bind(this); + }; +}; + + diff --git a/dist/offline-tiles-advanced-min.js b/dist/offline-tiles-advanced-min.js index fde704bc..5e24e446 100644 --- a/dist/offline-tiles-advanced-min.js +++ b/dist/offline-tiles-advanced-min.js @@ -1,5 +1,92 @@ -/*! esri-offline-maps - v2.16.0 - 2015-10-29 +/*! esri-offline-maps - v3.0.0 - 2015-11-23 * Copyright (c) 2015 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.OfflineTileEnablerLayer",[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;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;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;if(this.offline.online)return this._lastTileUrl=i,i;i=i.split("?")[0];var j="void:/"+b+"/"+c+"/"+d,k=null;return this._tilesCore._getTiles(k,this._imageType,i,j,this.offline.store,a,this.showBlankTiles),j},getBasemapLayer:function(a){var b=a.layerIds[0];return a.getLayer(b)},getLevelEstimation:function(a,b,c){var d=new O.esri.Tiles.TilingScheme(this),e=d.getAllCellIdsInExtent(a,b),f={level:b,tileCount:e.length,sizeBytes:e.length*c};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=[];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;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);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));return c.join("")},O.esri.Tiles.Base64Utils.wordToBase64=function(a){for(var b="=",c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",d=[],e=0,f=4*a.length;f>e;e+=3)for(var g=(a[e>>2]>>8*(e%4)&255)<<16|(a[e+1>>2]>>8*((e+1)%4)&255)<<8|a[e+2>>2]>>8*((e+2)%4)&255,h=0;4>h;h++)8*e+6*h>32*a.length?d.push(b):d.push(c.charAt(g>>6*(3-h)&63));return d.join("")},/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ -O.esri.Tiles.saveAs=function(a){"use strict";var b=a.document,c=function(){return a.URL||a.webkitURL||a},d=a.URL||a.webkitURL||a,e=b.createElementNS("http://www.w3.org/1999/xhtml","a"),f=!a.externalHost&&"download"in e,g=a.webkitRequestFileSystem,h=a.requestFileSystem||g||a.mozRequestFileSystem,i=function(b){(a.setImmediate||a.setTimeout)(function(){throw b},0)},j="application/octet-stream",k=0,l=[],m=function(){for(var a=l.length;a--;){var b=l[a];"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};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;a.code!==a.ABORT_ERR&&v()},"writestart progress write abort".split(" ").forEach(function(a){b["on"+a]=q["on"+a]}),b.write(d),q.abort=function(){b.abort(),q.readyState=q.DONE},q.readyState=q.WRITING}),v)}),v)};a.getFile(i,{create:!1},w(function(a){a.remove(),b()}),w(function(a){a.code===a.NOT_FOUND_ERR?b():v()}))}),v)}),v)):void v()},p=o.prototype,q=function(a,b){return new o(a,b)};return p.abort=function(){var a=this;a.readyState=a.DONE,n(a,"abort")},p.readyState=p.INIT=0,p.WRITING=1,p.DONE=2,p.error=p.onwritestart=p.onprogress=p.onwrite=p.onabort=p.onerror=p.onwriteend=null,a.addEventListener("unload",m,!1),q}(this.self||this.window||this.content),O.esri.Tiles.TilesCore=function(){this._getTiles=function(a,b,c,d,e,f,g){e.retrieve(c,function(c,e){a=f("img[src="+d+"]")[0];var h;return c?(a.style.borderColor="blue",h="data:image/"+b+";base64,"+e.img):g?(a.style.borderColor="green",h="data:image/png;base64,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pmk0TROPj48Hn3l4eIimaSJN06O3A4NJwDMxm82ibdtXo4D5fB6r1Sp+//4dz8/Pw5H+6ekpdrtdJEkS8/n8S/9f713ie3vaceo9x557QAB451Sgfyin34HKshweunk5HzAej2MymXz5+f9nbjJyI9L39Wu5XP55+XQZ39uxR4Z3u90wSXjqEV0wAjhjx47oaZq63Me/ZhIQBAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEAAbAJQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEAvqe/BwCeKjUweoA8pQAAAABJRU5ErkJggg=="):h="data:image/png;base64,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AwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDDwAwMBPAABGrpAUwAAAABJRU5ErkJggg==",a.style.visibility="visible",a.src=h,""})},this._storeTile=function(a,b,c,d){a=a.split("?")[0];var e=b?b+"?"+a:a,f=new XMLHttpRequest;f.open("GET",e,!0),f.overrideMimeType("text/plain; charset=x-user-defined"),f.onload=function(){if(200===f.status&&""!==f.responseText){var b=O.esri.Tiles.Base64Utils.wordToBase64(O.esri.Tiles.Base64Utils.stringToWord(this.responseText)),g={url:a,img:b};c.store(g,d)}else d(!1,f.status+" "+f.statusText+": "+f.response+" when downloading "+e)},f.onerror=function(a){d(!1,a)},f.send(null)},this._createCellsForOffline=function(a,b,c,d,e){for(var f=new O.esri.Tiles.TilingScheme(a),g=[],h=b;c>=h;h++){var i=f.getAllCellIdsInExtent(d,h);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;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=c;c++)for(d=i;j>=d;d++)k.push([c,d]);return k}}; \ No newline at end of file +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 +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 +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 +if(this.offline.online)return this._lastTileUrl=i,i +i=i.split("?")[0] +var j="void:/"+b+"/"+c+"/"+d,k=null +return this._tilesCore._getTiles(k,this._imageType,i,j,this.offline.store,a,this.showBlankTiles),j},getBasemapLayer:function(a){var b=a.layerIds[0] +return a.getLayer(b)},getLevelEstimation:function(a,b,c){var d=new O.esri.Tiles.TilingScheme(this),e=d.getAllCellIdsInExtent(a,b),f={level:b,tileCount:e.length,sizeBytes:e.length*c} +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=[] +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 +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) +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)) +return c.join("")},O.esri.Tiles.Base64Utils.wordToBase64=function(a){for(var b="=",c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",d=[],e=0,f=4*a.length;f>e;e+=3)for(var g=(a[e>>2]>>8*(e%4)&255)<<16|(a[e+1>>2]>>8*((e+1)%4)&255)<<8|a[e+2>>2]>>8*((e+2)%4)&255,h=0;4>h;h++)8*e+6*h>32*a.length?d.push(b):d.push(c.charAt(g>>6*(3-h)&63)) +return d.join("")},/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ +O.esri.Tiles.saveAs=function(a){"use strict" +var b=a.document,c=function(){return a.URL||a.webkitURL||a},d=a.URL||a.webkitURL||a,e=b.createElementNS("http://www.w3.org/1999/xhtml","a"),f=!a.externalHost&&"download"in e,g=a.webkitRequestFileSystem,h=a.requestFileSystem||g||a.mozRequestFileSystem,i=function(b){(a.setImmediate||a.setTimeout)(function(){throw b},0)},j="application/octet-stream",k=0,l=[],m=function(){for(var a=l.length;a--;){var b=l[a] +"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} +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 +a.code!==a.ABORT_ERR&&v()},"writestart progress write abort".split(" ").forEach(function(a){b["on"+a]=q["on"+a]}),b.write(d),q.abort=function(){b.abort(),q.readyState=q.DONE},q.readyState=q.WRITING}),v)}),v)} +a.getFile(i,{create:!1},w(function(a){a.remove(),b()}),w(function(a){a.code===a.NOT_FOUND_ERR?b():v()}))}),v)}),v)):void v()},p=o.prototype,q=function(a,b){return new o(a,b)} +return p.abort=function(){var a=this +a.readyState=a.DONE,n(a,"abort")},p.readyState=p.INIT=0,p.WRITING=1,p.DONE=2,p.error=p.onwritestart=p.onprogress=p.onwrite=p.onabort=p.onerror=p.onwriteend=null,a.addEventListener("unload",m,!1),q}(this.self||this.window||this.content),O.esri.Tiles.TilesCore=function(){this._getTiles=function(a,b,c,d,e,f,g){e.retrieve(c,function(c,e){a=f("img[src="+d+"]")[0] +var h +return c?(a.style.borderColor="blue",h="data:image/"+b+";base64,"+e.img):g?(a.style.borderColor="green",h="data:image/png;base64,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pmk0TROPj48Hn3l4eIimaSJN06O3A4NJwDMxm82ibdtXo4D5fB6r1Sp+//4dz8/Pw5H+6ekpdrtdJEkS8/n8S/9f713ie3vaceo9x557QAB451Sgfyin34HKshweunk5HzAej2MymXz5+f9nbjJyI9L39Wu5XP55+XQZ39uxR4Z3u90wSXjqEV0wAjhjx47oaZq63Me/ZhIQBAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEAAbAJQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEAvqe/BwCeKjUweoA8pQAAAABJRU5ErkJggg=="):h="data:image/png;base64,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AwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDDwAwMBPAABGrpAUwAAAABJRU5ErkJggg==",a.style.visibility="visible",a.src=h,""})},this._storeTile=function(a,b,c,d){a=a.split("?")[0] +var e=b?b+"?"+a:a,f=new XMLHttpRequest +f.open("GET",e,!0),f.overrideMimeType("text/plain; charset=x-user-defined"),f.onload=function(){if(200===f.status&&""!==f.responseText){var b=O.esri.Tiles.Base64Utils.wordToBase64(O.esri.Tiles.Base64Utils.stringToWord(this.responseText)),g={url:a,img:b} +c.store(g,d)}else d(!1,f.status+" "+f.statusText+": "+f.response+" when downloading "+e)},f.onerror=function(a){d(!1,a)},f.send(null)},this._createCellsForOffline=function(a,b,c,d,e){for(var f=new O.esri.Tiles.TilingScheme(a),g=[],h=b;c>=h;h++){var i=f.getAllCellIdsInExtent(d,h) +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 +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=c;c++)for(d=i;j>=d;d++)k.push([c,d]) +return k}} diff --git a/dist/offline-tiles-advanced-src.js b/dist/offline-tiles-advanced-src.js index 1d7e17ee..b4b96bcb 100644 --- a/dist/offline-tiles-advanced-src.js +++ b/dist/offline-tiles-advanced-src.js @@ -1,4 +1,4 @@ -/*! esri-offline-maps - v2.16.0 - 2015-10-29 +/*! esri-offline-maps - v3.0.0 - 2015-11-23 * Copyright (c) 2015 Environmental Systems Research Institute, Inc. * Apache License*/ define([ @@ -15,7 +15,7 @@ define([ ], function(query, request, declare,LOD,Point,Extent,TileInfo,SpatialReference,Polygon,TiledMapServerLayer) { "use strict"; - return declare("O.esri.Tiles.OfflineTileEnablerLayer",[TiledMapServerLayer],{ + return declare("O.esri.Tiles.OfflineTilesAdvanced",[TiledMapServerLayer],{ tileInfo: null, _imageType: "", @@ -898,8 +898,8 @@ O.esri.Tiles.saveAs = /** - * This library contains common core code between offlineTilesEnabler.js - * and OfflineTilesEnablerLayer.js + * This library contains common core code between OfflineTilesBasic.js + * and OfflineTilesAdvanced.js */ O.esri.Tiles.TilesCore = function(){ diff --git a/dist/offline-tiles-basic-min.js b/dist/offline-tiles-basic-min.js index 3d28da50..97334721 100644 --- a/dist/offline-tiles-basic-min.js +++ b/dist/offline-tiles-basic-min.js @@ -1,5 +1,81 @@ -/*! esri-offline-maps - v2.16.0 - 2015-10-29 +/*! esri-offline-maps - v3.0.0 - 2015-11-23 * Copyright (c) 2015 Environmental Systems Research Institute, Inc. * Apache License*/ -define(["dojo/query","dojo/request","esri/geometry/Polygon","dojo/_base/declare"],function(a,b,c,d){"use strict";return d("O.esri.Tiles.OfflineTilesEnabler",[],{getBasemapLayer:function(a){var b=a.layerIds[0];return a.getLayer(b)},extend:function(c,d,e,f){c._tilesCore=new O.esri.Tiles.TilesCore,c._lastTileUrl="",c._imageType="",c._minZoom=null,c._maxZoom=null,void 0===f||null===f?(c.DB_NAME="offline_tile_store",c.DB_OBJECTSTORE_NAME="tilepath"):(c.DB_NAME=f.dbName,c.DB_OBJECTSTORE_NAME=f.objectStoreName),c._getTileUrl=c.getTileUrl;var g=!0;return"undefined"!=typeof e&&(g=e),c.showBlankTiles=!0,c.offline={online:g,store:new O.esri.Tiles.TilesStore,proxyPath:null},c.offline.store.isSupported()?(c.offline.store.dbName=c.DB_NAME,c.offline.store.objectStoreName=c.DB_OBJECTSTORE_NAME,c.offline.store.init(function(b){b&&(c.resampling=!1,c.getTileUrl=function(b,d,e){var f=this._getTileUrl(b,d,e);if(this.offline.online)return""===c._imageType&&(c._imageType=this.tileInfo.format.toLowerCase()),c._lastTileUrl=f,f;f=f.split("?")[0];var g="void:/"+b+"/"+d+"/"+e,h=null;return c._tilesCore._getTiles(h,this._imageType,f,g,this.offline.store,a,c.showBlankTiles),g},d&&d(!0))}.bind(this)),c.getLevelEstimation=function(a,b,c){var d=new O.esri.Tiles.TilingScheme(this),e=d.getAllCellIdsInExtent(a,b),f={level:b,tileCount:e.length,sizeBytes:e.length*c};return f},c.prepareForOffline=function(a,b,d,e){c._tilesCore._createCellsForOffline(this,a,b,d,function(a){this._doNextTile(0,a,e)}.bind(this))},c.goOffline=function(){this.offline.online=!1},c.goOnline=function(){this.offline.online=!0,this.refresh()},c.isOnline=function(){return this.offline.online},c.deleteAllTiles=function(a){var b=this.offline.store;b.deleteAll(a)},c.getOfflineUsage=function(a){var b=this.offline.store;b.usedSpace(a)},c.getTilePolygons=function(a){c._tilesCore._getTilePolygons(this.offline.store,c.url,this,a)},c.saveToFile=function(a,b){c._tilesCore._saveToFile(a,this.offline.store,b)},c.loadFromFile=function(a,b){c._tilesCore._loadFromFile(a,this.offline.store,b)},c.getMaxZoom=function(a){null==this._maxZoom&&(this._maxZoom=c.tileInfo.lods[c.tileInfo.lods.length-1].level),a(this._maxZoom)},c.getMinZoom=function(a){null==this._minZoom&&(this._minZoom=c.tileInfo.lods[0].level),a(this._minZoom)},c.getMinMaxLOD=function(a,b){var d={},e=c.getMap(),f=e.getLevel()-Math.abs(a),g=e.getLevel()+b;return null!=this._maxZoom&&null!=this._minZoom?(d.max=Math.min(this._maxZoom,g),d.min=Math.max(this._minZoom,f)):(c.getMinZoom(function(a){d.min=Math.max(a,f)}),c.getMaxZoom(function(a){d.max=Math.min(a,g)})),d},c.estimateTileSize=function(a){c._tilesCore._estimateTileSize(b,this._lastTileUrl,this.offline.proxyPath,a)},c.getExtentBuffer=function(a,b){return b.xmin-=a,b.ymin-=a,b.xmax+=a,b.ymax+=a,b},c.getTileUrlsByExtent=function(a,b){var d=new O.esri.Tiles.TilingScheme(c),e=d.getAllCellIdsInExtent(a,b),f=[];return e.forEach(function(a){f.push(c.url+"/"+b+"/"+a[1]+"/"+a[0])}.bind(this)),f},void(c._doNextTile=function(a,b,d){var e=b[a],f=this._getTileUrl(e.level,e.row,e.col);c._tilesCore._storeTile(f,this.offline.proxyPath,this.offline.store,function(c,f){c||(f={cell:e,msg:f});var g=d({countNow:a,countMax:b.length,cell:e,error:f,finishedDownloading:!1});g||a===b.length-1?d({finishedDownloading:!0,cancelRequested:g}):this._doNextTile(a+1,b,d)}.bind(this))})):d(!1,"indexedDB not supported")}})}),"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));return c.join("")},O.esri.Tiles.Base64Utils.wordToBase64=function(a){for(var b="=",c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",d=[],e=0,f=4*a.length;f>e;e+=3)for(var g=(a[e>>2]>>8*(e%4)&255)<<16|(a[e+1>>2]>>8*((e+1)%4)&255)<<8|a[e+2>>2]>>8*((e+2)%4)&255,h=0;4>h;h++)8*e+6*h>32*a.length?d.push(b):d.push(c.charAt(g>>6*(3-h)&63));return d.join("")},/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ -O.esri.Tiles.saveAs=function(a){"use strict";var b=a.document,c=function(){return a.URL||a.webkitURL||a},d=a.URL||a.webkitURL||a,e=b.createElementNS("http://www.w3.org/1999/xhtml","a"),f=!a.externalHost&&"download"in e,g=a.webkitRequestFileSystem,h=a.requestFileSystem||g||a.mozRequestFileSystem,i=function(b){(a.setImmediate||a.setTimeout)(function(){throw b},0)},j="application/octet-stream",k=0,l=[],m=function(){for(var a=l.length;a--;){var b=l[a];"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};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;a.code!==a.ABORT_ERR&&v()},"writestart progress write abort".split(" ").forEach(function(a){b["on"+a]=q["on"+a]}),b.write(d),q.abort=function(){b.abort(),q.readyState=q.DONE},q.readyState=q.WRITING}),v)}),v)};a.getFile(i,{create:!1},w(function(a){a.remove(),b()}),w(function(a){a.code===a.NOT_FOUND_ERR?b():v()}))}),v)}),v)):void v()},p=o.prototype,q=function(a,b){return new o(a,b)};return p.abort=function(){var a=this;a.readyState=a.DONE,n(a,"abort")},p.readyState=p.INIT=0,p.WRITING=1,p.DONE=2,p.error=p.onwritestart=p.onprogress=p.onwrite=p.onabort=p.onerror=p.onwriteend=null,a.addEventListener("unload",m,!1),q}(this.self||this.window||this.content),O.esri.Tiles.TilesCore=function(){this._getTiles=function(a,b,c,d,e,f,g){e.retrieve(c,function(c,e){a=f("img[src="+d+"]")[0];var h;return c?(a.style.borderColor="blue",h="data:image/"+b+";base64,"+e.img):g?(a.style.borderColor="green",h="data:image/png;base64,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pmk0TROPj48Hn3l4eIimaSJN06O3A4NJwDMxm82ibdtXo4D5fB6r1Sp+//4dz8/Pw5H+6ekpdrtdJEkS8/n8S/9f713ie3vaceo9x557QAB451Sgfyin34HKshweunk5HzAej2MymXz5+f9nbjJyI9L39Wu5XP55+XQZ39uxR4Z3u90wSXjqEV0wAjhjx47oaZq63Me/ZhIQBAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEAAbAJQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEAvqe/BwCeKjUweoA8pQAAAABJRU5ErkJggg=="):h="data:image/png;base64,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AwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDDwAwMBPAABGrpAUwAAAABJRU5ErkJggg==",a.style.visibility="visible",a.src=h,""})},this._storeTile=function(a,b,c,d){a=a.split("?")[0];var e=b?b+"?"+a:a,f=new XMLHttpRequest;f.open("GET",e,!0),f.overrideMimeType("text/plain; charset=x-user-defined"),f.onload=function(){if(200===f.status&&""!==f.responseText){var b=O.esri.Tiles.Base64Utils.wordToBase64(O.esri.Tiles.Base64Utils.stringToWord(this.responseText)),g={url:a,img:b};c.store(g,d)}else d(!1,f.status+" "+f.statusText+": "+f.response+" when downloading "+e)},f.onerror=function(a){d(!1,a)},f.send(null)},this._createCellsForOffline=function(a,b,c,d,e){for(var f=new O.esri.Tiles.TilingScheme(a),g=[],h=b;c>=h;h++){var i=f.getAllCellIdsInExtent(d,h);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;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=c;c++)for(d=i;j>=d;d++)k.push([c,d]);return k}}; \ No newline at end of file +define(["dojo/query","dojo/request","esri/geometry/Polygon","dojo/_base/declare"],function(a,b,c,d){"use strict" +return d("O.esri.Tiles.OfflineTilesBasic",[],{getBasemapLayer:function(a){var b=a.layerIds[0] +return a.getLayer(b)},extend:function(c,d,e,f){c._tilesCore=new O.esri.Tiles.TilesCore,c._lastTileUrl="",c._imageType="",c._minZoom=null,c._maxZoom=null,void 0===f||null===f?(c.DB_NAME="offline_tile_store",c.DB_OBJECTSTORE_NAME="tilepath"):(c.DB_NAME=f.dbName,c.DB_OBJECTSTORE_NAME=f.objectStoreName),c._getTileUrl=c.getTileUrl +var g=!0 +return"undefined"!=typeof e&&(g=e),c.showBlankTiles=!0,c.offline={online:g,store:new O.esri.Tiles.TilesStore,proxyPath:null},c.offline.store.isSupported()?(c.offline.store.dbName=c.DB_NAME,c.offline.store.objectStoreName=c.DB_OBJECTSTORE_NAME,c.offline.store.init(function(b){b&&(c.resampling=!1,c.getTileUrl=function(b,d,e){var f=this._getTileUrl(b,d,e) +if(this.offline.online)return""===c._imageType&&(c._imageType=this.tileInfo.format.toLowerCase()),c._lastTileUrl=f,f +f=f.split("?")[0] +var g="void:/"+b+"/"+d+"/"+e,h=null +return c._tilesCore._getTiles(h,this._imageType,f,g,this.offline.store,a,c.showBlankTiles),g},d&&d(!0))}.bind(this)),c.getLevelEstimation=function(a,b,c){var d=new O.esri.Tiles.TilingScheme(this),e=d.getAllCellIdsInExtent(a,b),f={level:b,tileCount:e.length,sizeBytes:e.length*c} +return f},c.prepareForOffline=function(a,b,d,e){c._tilesCore._createCellsForOffline(this,a,b,d,function(a){this._doNextTile(0,a,e)}.bind(this))},c.goOffline=function(){this.offline.online=!1},c.goOnline=function(){this.offline.online=!0,this.refresh()},c.isOnline=function(){return this.offline.online},c.deleteAllTiles=function(a){var b=this.offline.store +b.deleteAll(a)},c.getOfflineUsage=function(a){var b=this.offline.store +b.usedSpace(a)},c.getTilePolygons=function(a){c._tilesCore._getTilePolygons(this.offline.store,c.url,this,a)},c.saveToFile=function(a,b){c._tilesCore._saveToFile(a,this.offline.store,b)},c.loadFromFile=function(a,b){c._tilesCore._loadFromFile(a,this.offline.store,b)},c.getMaxZoom=function(a){null==this._maxZoom&&(this._maxZoom=c.tileInfo.lods[c.tileInfo.lods.length-1].level),a(this._maxZoom)},c.getMinZoom=function(a){null==this._minZoom&&(this._minZoom=c.tileInfo.lods[0].level),a(this._minZoom)},c.getMinMaxLOD=function(a,b){var d={},e=c.getMap(),f=e.getLevel()-Math.abs(a),g=e.getLevel()+b +return null!=this._maxZoom&&null!=this._minZoom?(d.max=Math.min(this._maxZoom,g),d.min=Math.max(this._minZoom,f)):(c.getMinZoom(function(a){d.min=Math.max(a,f)}),c.getMaxZoom(function(a){d.max=Math.min(a,g)})),d},c.estimateTileSize=function(a){c._tilesCore._estimateTileSize(b,this._lastTileUrl,this.offline.proxyPath,a)},c.getExtentBuffer=function(a,b){return b.xmin-=a,b.ymin-=a,b.xmax+=a,b.ymax+=a,b},c.getTileUrlsByExtent=function(a,b){var d=new O.esri.Tiles.TilingScheme(c),e=d.getAllCellIdsInExtent(a,b),f=[] +return e.forEach(function(a){f.push(c.url+"/"+b+"/"+a[1]+"/"+a[0])}.bind(this)),f},void(c._doNextTile=function(a,b,d){var e=b[a],f=this._getTileUrl(e.level,e.row,e.col) +c._tilesCore._storeTile(f,this.offline.proxyPath,this.offline.store,function(c,f){c||(f={cell:e,msg:f}) +var g=d({countNow:a,countMax:b.length,cell:e,error:f,finishedDownloading:!1}) +g||a===b.length-1?d({finishedDownloading:!0,cancelRequested:g}):this._doNextTile(a+1,b,d)}.bind(this))})):d(!1,"indexedDB not supported")}})}),"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)) +return c.join("")},O.esri.Tiles.Base64Utils.wordToBase64=function(a){for(var b="=",c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",d=[],e=0,f=4*a.length;f>e;e+=3)for(var g=(a[e>>2]>>8*(e%4)&255)<<16|(a[e+1>>2]>>8*((e+1)%4)&255)<<8|a[e+2>>2]>>8*((e+2)%4)&255,h=0;4>h;h++)8*e+6*h>32*a.length?d.push(b):d.push(c.charAt(g>>6*(3-h)&63)) +return d.join("")},/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ +O.esri.Tiles.saveAs=function(a){"use strict" +var b=a.document,c=function(){return a.URL||a.webkitURL||a},d=a.URL||a.webkitURL||a,e=b.createElementNS("http://www.w3.org/1999/xhtml","a"),f=!a.externalHost&&"download"in e,g=a.webkitRequestFileSystem,h=a.requestFileSystem||g||a.mozRequestFileSystem,i=function(b){(a.setImmediate||a.setTimeout)(function(){throw b},0)},j="application/octet-stream",k=0,l=[],m=function(){for(var a=l.length;a--;){var b=l[a] +"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} +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 +a.code!==a.ABORT_ERR&&v()},"writestart progress write abort".split(" ").forEach(function(a){b["on"+a]=q["on"+a]}),b.write(d),q.abort=function(){b.abort(),q.readyState=q.DONE},q.readyState=q.WRITING}),v)}),v)} +a.getFile(i,{create:!1},w(function(a){a.remove(),b()}),w(function(a){a.code===a.NOT_FOUND_ERR?b():v()}))}),v)}),v)):void v()},p=o.prototype,q=function(a,b){return new o(a,b)} +return p.abort=function(){var a=this +a.readyState=a.DONE,n(a,"abort")},p.readyState=p.INIT=0,p.WRITING=1,p.DONE=2,p.error=p.onwritestart=p.onprogress=p.onwrite=p.onabort=p.onerror=p.onwriteend=null,a.addEventListener("unload",m,!1),q}(this.self||this.window||this.content),O.esri.Tiles.TilesCore=function(){this._getTiles=function(a,b,c,d,e,f,g){e.retrieve(c,function(c,e){a=f("img[src="+d+"]")[0] +var h +return c?(a.style.borderColor="blue",h="data:image/"+b+";base64,"+e.img):g?(a.style.borderColor="green",h="data:image/png;base64,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pmk0TROPj48Hn3l4eIimaSJN06O3A4NJwDMxm82ibdtXo4D5fB6r1Sp+//4dz8/Pw5H+6ekpdrtdJEkS8/n8S/9f713ie3vaceo9x557QAB451Sgfyin34HKshweunk5HzAej2MymXz5+f9nbjJyI9L39Wu5XP55+XQZ39uxR4Z3u90wSXjqEV0wAjhjx47oaZq63Me/ZhIQBAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEAAbAJQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEAvqe/BwCeKjUweoA8pQAAAABJRU5ErkJggg=="):h="data:image/png;base64,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AwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDDwAwMBPAABGrpAUwAAAABJRU5ErkJggg==",a.style.visibility="visible",a.src=h,""})},this._storeTile=function(a,b,c,d){a=a.split("?")[0] +var e=b?b+"?"+a:a,f=new XMLHttpRequest +f.open("GET",e,!0),f.overrideMimeType("text/plain; charset=x-user-defined"),f.onload=function(){if(200===f.status&&""!==f.responseText){var b=O.esri.Tiles.Base64Utils.wordToBase64(O.esri.Tiles.Base64Utils.stringToWord(this.responseText)),g={url:a,img:b} +c.store(g,d)}else d(!1,f.status+" "+f.statusText+": "+f.response+" when downloading "+e)},f.onerror=function(a){d(!1,a)},f.send(null)},this._createCellsForOffline=function(a,b,c,d,e){for(var f=new O.esri.Tiles.TilingScheme(a),g=[],h=b;c>=h;h++){var i=f.getAllCellIdsInExtent(d,h) +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 +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=c;c++)for(d=i;j>=d;d++)k.push([c,d]) +return k}} diff --git a/dist/offline-tiles-basic-src.js b/dist/offline-tiles-basic-src.js index 43064592..23423f46 100644 --- a/dist/offline-tiles-basic-src.js +++ b/dist/offline-tiles-basic-src.js @@ -1,4 +1,4 @@ -/*! esri-offline-maps - v2.16.0 - 2015-10-29 +/*! esri-offline-maps - v3.0.0 - 2015-11-23 * Copyright (c) 2015 Environmental Systems Research Institute, Inc. * Apache License*/ define([ @@ -9,7 +9,7 @@ define([ ], function(query, request, Polygon,declare) { "use strict"; - return declare("O.esri.Tiles.OfflineTilesEnabler",[],{ + return declare("O.esri.Tiles.OfflineTilesBasic",[],{ /** * Utility method to get the basemap layer reference * @param map @@ -730,8 +730,8 @@ O.esri.Tiles.saveAs = /** - * This library contains common core code between offlineTilesEnabler.js - * and OfflineTilesEnablerLayer.js + * This library contains common core code between OfflineTilesBasic.js + * and OfflineTilesAdvanced.js */ O.esri.Tiles.TilesCore = function(){ diff --git a/dist/offline-tpk-min.js b/dist/offline-tpk-min.js index 8fa2951c..2f0159da 100644 --- a/dist/offline-tpk-min.js +++ b/dist/offline-tpk-min.js @@ -1,5 +1,342 @@ -/*! esri-offline-maps - v2.16.0 - 2015-10-29 +/*! esri-offline-maps - v3.0.0 - 2015-11-23 * Copyright (c) 2015 Environmental Systems Research Institute, Inc. * Apache License*/ -define(["dojo/_base/declare","esri/geometry/Extent","dojo/query","esri/SpatialReference","esri/layers/TileInfo","esri/layers/TiledMapServiceLayer","dojo/Deferred","dojo/promise/all","dojo/Evented"],function(a,b,c,d,e,f,g,h,i){return a("O.esri.TPK.TPKLayer",[f,i],{map:null,store:null,MAX_DB_SIZE:75,TILE_PATH:"",RECENTER_DELAY:350,PARSING_ERROR:"parsingError",DB_INIT_ERROR:"dbInitError",DB_FULL_ERROR:"dbFullError",NO_SUPPORT_ERROR:"libNotSupportedError",PROGRESS_START:"start",PROGRESS_END:"end",WINDOW_VALIDATED:"windowValidated",DB_VALIDATED:"dbValidated",DATABASE_ERROR_EVENT:"databaseErrorEvent",VALIDATION_EVENT:"validationEvent",PROGRESS_EVENT:"progress",_maxDBSize:75,_isDBWriteable:!0,_isDBValid:!1,_autoCenter:null,_fileEntriesLength:0,_inMemTilesObject:null,_inMemTilesObjectLength:0,_zeroLengthFileCounter:0,constructor:function(){this._self=this,this._inMemTilesIndex=[],this._inMemTilesObject={},this.store=new O.esri.Tiles.TilesStore,this._validate()},extend:function(a){this._fileEntriesLength=a.length,this.emit(this.PROGRESS_EVENT,this.PROGRESS_START),this._parseInMemFiles(a,function(){this._parseConfCdi(function(a){this.initialExtent=this.fullExtent=a,this._parseConfXml(function(a){this.tileInfo=new e(a),this.spatialReference=new d({wkid:this.tileInfo.spatialReference.wkid}),this.loaded=!0,this.onLoad(this),this.emit(this.PROGRESS_EVENT,this.PROGRESS_END)}.bind(this._self))}.bind(this._self))}.bind(this._self))},getTileUrl:function(a,b,d){this.emit(this.PROGRESS_EVENT,this.PROGRESS_START);var e=this._self.TILE_PATH+"_alllayers",f=this._getCacheFilePath(e,a,b,d);if(this._inMemTilesObject!={}){var g="void:/"+a+"/"+b+"/"+d;return null==this.map&&(this.map=this.getMap()),null==this._autoCenter&&(this._autoCenter=new O.esri.TPK.autoCenterMap(this.map,this.RECENTER_DELAY),this._autoCenter.init()),this._getInMemTiles(f,e,a,b,d,g,function(a,b,d){var e=c("img[src="+b+"]")[0];"undefined"==typeof e&&(e=new Image);var f;if(a){var g="data:image/png;base64,";switch(this.tileInfo.format){case"JPEG":f="data:image/jpg;base64,"+a;break;case"PNG":f=g+a;break;case"PNG8":f=g+a;break;case"PNG24":f=g+a;break;case"PNG32":f=g+a;break;default:f="data:image/jpg;base64,"+a}e.style.borderColor="blue"}else e.style.borderColor="green",f="data:image/png;base64,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pmk0TROPj48Hn3l4eIimaSJN06O3A4NJwDMxm82ibdtXo4D5fB6r1Sp+//4dz8/Pw5H+6ekpdrtdJEkS8/n8S/9f713ie3vaceo9x557QAB451Sgfyin34HKshweunk5HzAej2MymXz5+f9nbjJyI9L39Wu5XP55+XQZ39uxR4Z3u90wSXjqEV0wAjhjx47oaZq63Me/ZhIQBAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEAAbAJQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEAvqe/BwCeKjUweoA8pQAAAABJRU5ErkJggg==";return e.style.visibility="visible",e.src=f,this.emit(this.PROGRESS_EVENT,this.PROGRESS_END),""}.bind(this._self)),g}},setMaxDBSize:function(a){var b=/^\d+$/;b.test(a)&&a<=this.MAX_DB_SIZE&&(this._maxDBSize=a)},getDBSize:function(a){this.store.usedSpace(function(b,c){a(b,c)}.bind(this))},setDBWriteable:function(a){this._isDBWriteable=a},isDBValid:function(){return this._validate(),this._isDBValid},loadFromURL:function(a,b){this.isDBValid()?this.store.store(a,function(a,c){a?b(!0,""):b(!1,c)}):b(!1,"not supported")},_validate:function(){window.File||window.FileReader||window.Blob||window.btoa||window.DataView?this.emit(this.VALIDATION_EVENT,{msg:this.WINDOW_VALIDATED,err:null}):this.emit(this.VALIDATION_EVENT,{msg:this.NO_SUPPORT_ERROR,err:null}),this.store.isSupported()?this.store.init(function(a){a===!1?this.emit(this.DATABASE_ERROR_EVENT,{msg:this.DB_INIT_ERROR,err:null}):this.store.usedSpace(function(a,b){var c=this._bytes2MBs(a.sizeBytes);c>this.MAX_DB_SIZE&&this.emit(this.DATABASE_ERROR_EVENT,{msg:this.DB_FULL_ERROR,err:b}),this.emit(this.VALIDATION_EVENT,{msg:this.DB_VALIDATED,err:null}),this._isDBValid=!0}.bind(this))}.bind(this)):this.emit(this.VALIDATION_EVENT,{msg:this.NO_SUPPORT_ERROR,err:null})},_parseInMemFiles:function(a,b){var c=this._fileEntriesLength;this._zeroLengthFileCounter=0;for(var d=[],e=0;c>e;e++){var f=new g,i=a[e].filename.toLocaleUpperCase(),j=i.indexOf("_ALLLAYERS",0);-1!=j&&(this.TILE_PATH=i.slice(0,j)),0===a[e].compressedSize&&this._zeroLengthFileCounter++;var k=i.indexOf("CONF.CDI",0),l=i.indexOf("CONF.XML",0),m=i.indexOf("BUNDLE",0),n=i.indexOf("BUNDLX",0);-1!=k||-1!=l?this._unzipConfFiles(a,e,f,function(a,b){a.resolve(b)}):-1!=m||-1!=n?this._unzipTileFiles(a,e,f,function(a,b){a.resolve(b)}):f.resolve(e),d.push(f)}h(d).then(function(a){b&&b(a)})},ObjectSize:function(a){var b,c=0;for(b in a)a.hasOwnProperty(b)&&c++;return c},_unzipConfFiles:function(a,b,c,d){a[b].getData(new O.esri.zip.TextWriter(b),function(b){this._inMemTilesIndex.push("blank");var e=a[b.token].filename.toLocaleUpperCase();this._inMemTilesObject[e]=b.string;var f=this.ObjectSize(this._inMemTilesObject);f>0&&d(c,b.token)}.bind(this))},_unzipTileFiles:function(a,b,c,d){var e=this;a[b].getData(new O.esri.zip.BlobWriter(b),function(b){if(0!==b.size){var f=new FileReader;f.token=b.token,f.onerror=function(a){e.emit(e.PARSING_ERROR,{msg:"Error parsing file: ",err:a.target.error})},f.onloadend=function(g){if(void 0!==f.token){e._inMemTilesIndex.push("blank");var h=a[f.token].filename.toLocaleUpperCase();e._inMemTilesObject[h]=f.result;var i=e.ObjectSize(e._inMemTilesObject);i>0&&d(c,b.token)}},f.readAsArrayBuffer(b)}})},_parseConfCdi:function(a){var c=this._inMemTilesObject[this.TILE_PATH+"CONF.CDI"],e=new O.esri.TPK.X2JS,f=e.xml_str2json(c),g=f.EnvelopeN,h=parseFloat(g.XMin),i=parseFloat(g.YMin),j=parseFloat(g.XMax),k=parseFloat(g.YMax),l=parseInt(g.SpatialReference.WKID),m=new b(h,i,j,k,new d({wkid:l}));a(m)},_parseConfXml:function(a){var b=this._inMemTilesObject[this.TILE_PATH+"CONF.XML"],c=new O.esri.TPK.X2JS,d=c.xml_str2json(b),e=d.CacheInfo,f={};f.rows=parseInt(e.TileCacheInfo.TileRows),f.cols=parseInt(e.TileCacheInfo.TileCols),f.dpi=parseInt(e.TileCacheInfo.DPI),f.format=e.TileImageInfo.CacheTileFormat,f.compressionQuality=parseInt(e.TileImageInfo.CompressionQuality),f.origin={x:parseInt(e.TileCacheInfo.TileOrigin.X),y:parseInt(e.TileCacheInfo.TileOrigin.Y)},f.spatialReference={wkid:parseInt(e.TileCacheInfo.SpatialReference.WKID)};for(var g=e.TileCacheInfo.LODInfos.LODInfo,h=[],i=0;im;m+=3)f=i[m]<<16|i[m+1]<<8|i[m+2],b=(16515072&f)>>18,c=(258048&f)>>12,d=(4032&f)>>6,e=63&f,g+=h[b]+h[c]+h[d]+h[e];return 1==k?(f=i[l],b=(252&f)>>2,c=(3&f)<<4,g+=h[b]+h[c]+"=="):2==k&&(f=i[l]<<8|i[l+1],b=(64512&f)>>10,c=(1008&f)>>4,d=(15&f)<<2,g+=h[b]+h[c]+h[d]+"="),g},_buffer2Base64:function(a,b,c){var d=new DataView(a,b),e=d.getInt32(0,!0),f=d.buffer.slice(b+4,b+4+e),g=this._base64ArrayBuffer(f);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");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);d.onsuccess=function(a){var c=a.target.result;void 0===c?b(!1,"not found"):b(!0,c)},d.onerror=function(a){b(!1,a)}}},this.deleteAll=function(a){if(null!==this._db){var b=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName).clear();b.onsuccess=function(){a(!0)},b.onerror=function(b){a(!1,b)}}else a(!1,null)},this["delete"]=function(a,b){if(null!==this._db){var c=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName)["delete"](a);c.onsuccess=function(){b(!0)},c.onerror=function(a){b(!1,a)}}else b(!1,null)},this.getAllTiles=function(a){if(null!==this._db){var b=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor();b.onsuccess=function(b){var c=b.target.result;if(c){var d=c.value.url,e=c.value.img;a(d,e,null),c["continue"]()}else a(null,null,"end")}.bind(this),b.onerror=function(b){a(null,null,b)}}else a(null,null,"no db")},this.usedSpace=function(a){if(null!==this._db){var b={sizeBytes:0,tileCount:0},c=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor();c.onsuccess=function(c){var d=c.target.result;if(d){var e=d.value,f=JSON.stringify(e);b.sizeBytes+=this._stringBytes(f),b.tileCount+=1,d["continue"]()}else a(b,null)}.bind(this),c.onerror=function(b){a(null,b)}}else a(null,null)},this._stringBytes=function(a){return a.length},this.init=function(a){var b=indexedDB.open(this.dbName,4);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:"url"})}.bind(this),b.onsuccess=function(b){this._db=b.target.result,a(!0)}.bind(this)}},function(a){function b(){var a=-1,b=this;b.append=function(c){var d,e=b.table;for(d=0;d>>8^e[255&(a^c[d])]},b.get=function(){return~a}}function c(a,b,c){return a.slice?a.slice(b,b+c):a.webkitSlice?a.webkitSlice(b,b+c):a.mozSlice?a.mozSlice(b,b+c):a.msSlice?a.msSlice(b,b+c):void 0}function d(a,b){var c,d;return c=new ArrayBuffer(a),d=new Uint8Array(c),b&&d.set(b,0),{buffer:c,array:d,view:new DataView(c)}}function e(){}function f(a){function b(b,c){var f=new Blob([a],{type:M});d=new h(f),d.init(function(){e.size=d.size,b()},c)}function c(a,b,c,e){d.readUint8Array(a,b,c,e)}var d,e=this;e.size=0,e.init=b,e.readUint8Array=c}function g(b){function c(a){for(var c=b.length;"="==b.charAt(c-1);)c--;f=b.indexOf(",")+1,g.size=Math.floor(.75*(c-f)),a()}function e(c,e,g){var h,i=d(e),j=4*Math.floor(c/3),k=4*Math.ceil((c+e)/3),l=a.atob(b.substring(j+f,k+f)),m=c-3*Math.floor(j/4);for(h=m;m+e>h;h++)i.array[h-m]=l.charCodeAt(h);g(i.array)}var f,g=this;g.size=0,g.init=c,g.readUint8Array=e}function h(a){function b(b){this.size=a.size,b()}function d(b,d,e,f){var g=new FileReader;g.onload=function(a){e(new Uint8Array(a.target.result))},g.onerror=f,g.readAsArrayBuffer(c(a,b,d))}var e=this;e.size=0,e.init=b,e.readUint8Array=d}function i(){}function j(a,b){function c(a){f=new Blob([],{type:M}),a()}function d(a,b){f=new Blob([f,A?a:a.buffer],{type:M}),b()}function e(c,d){var e=new FileReader;e.onload=function(b){var d={string:b.target.result,token:a};c(d)},e.onerror=d,e.readAsText(f,b)}var f,g=this;g.init=c,g.writeUint8Array=d,g.getData=e}function k(b){function c(a){g+="data:"+(b||"")+";base64,",a()}function d(b,c){var d,e=h.length,f=h;for(h="",d=0;d<3*Math.floor((e+b.length)/3)-e;d++)f+=String.fromCharCode(b[d]);for(;d2?g+=a.btoa(f):h=f,c()}function e(b){b(g+a.btoa(h))}var f=this,g="",h="";f.init=c,f.writeUint8Array=d,f.getData=e}function l(a,b){function c(a){f=new Blob([],{type:b}),a()}function d(c,d){f=new Blob([f,A?c:c.buffer],{type:b}),f.token=a,d()}function e(a){a(f)}var f,g=this;g.init=c,g.writeUint8Array=d,g.getData=e}function m(a,b,c,d,e,f,g,h,i,j){function k(){a.removeEventListener(N,l,!1),h(o)}function l(a){var b=a.data,d=b.data;b.onappend&&(o+=d.length,c.writeUint8Array(d,function(){f(!1,d),m()},j)),b.onflush&&(d?(o+=d.length,c.writeUint8Array(d,function(){f(!1,d),k()},j)):k()),b.progress&&g&&g(n+b.current,e)}function m(){n=p*J,e>n?b.readUint8Array(d+n,Math.min(J,e-n),function(b){a.postMessage({append:!0,data:b}),p++,g&&g(n,e),f(!0,b)},i):a.postMessage({flush:!0})}var n,o,p=0;o=0,a.addEventListener(N,l,!1),m()}function n(a,b,c,d,e,f,g,h,i,j){function k(){var o;l=m*J,e>l?b.readUint8Array(d+l,Math.min(J,e-l),function(b){var h=a.append(b,function(){g&&g(d+l,e)});n+=h.length,f(!0,b),c.writeUint8Array(h,function(){f(!1,h),m++,setTimeout(k,1)},j),g&&g(l,e)},i):(o=a.flush(),o?(n+=o.length,c.writeUint8Array(o,function(){f(!1,o),h(n)},j)):h(n))}var l,m=0,n=0;k()}function o(c,d,e,f,g,h,i,j,k){function l(a,b){g&&!a&&q.append(b)}function o(a){h(a,q.get())}var p,q=new b;return a.zip.useWebWorkers?(p=new Worker(a.zip.workerScriptsPath+K),m(p,c,d,e,f,l,i,o,j,k)):n(new a.zip.Inflater,c,d,e,f,l,i,o,j,k),p}function p(c,d,e,f,g,h,i){function j(a,b){a&&p.append(b)}function k(a){f(a,p.get())}function l(){o.removeEventListener(N,l,!1),m(o,c,d,0,c.size,j,g,k,h,i)}var o,p=new b;return a.zip.useWebWorkers?(o=new Worker(a.zip.workerScriptsPath+L),o.addEventListener(N,l,!1),o.postMessage({init:!0,level:e})):n(new a.zip.Deflater,c,d,0,c.size,j,g,k,h,i),o}function q(a,c,d,e,f,g,h,i,j){function k(){var b=l*J;e>b?a.readUint8Array(d+b,Math.min(J,e-b),function(a){f&&m.append(a),h&&h(b,e,a),c.writeUint8Array(a,function(){l++,k()},j)},i):g(e,m.get())}var l=0,m=new b;k()}function r(a){var b,c,d="",e=["Ç","ü","é","â","ä","à","å","ç","ê","ë","è","ï","î","ì","Ä","Å","É","æ","Æ","ô","ö","ò","û","ù","ÿ","Ö","Ü","ø","£","Ø","×","ƒ","á","í","ó","ú","ñ","Ñ","ª","º","¿","®","¬","½","¼","¡","«","»","_","_","_","¦","¦","Á","Â","À","©","¦","¦","+","+","¢","¥","+","+","-","-","+","-","+","ã","Ã","+","+","-","-","¦","-","+","¤","ð","Ð","Ê","Ë","È","i","Í","Î","Ï","+","+","_","_","¦","Ì","_","Ó","ß","Ô","Ò","õ","Õ","µ","þ","Þ","Ú","Û","Ù","ý","Ý","¯","´","­","±","_","¾","¶","§","÷","¸","°","¨","·","¹","³","²","_"," "];for(b=0;b127?e[c-128]:String.fromCharCode(c);return d}function s(a){return decodeURIComponent(escape(a))}function t(a){var b,c="";for(b=0;b>16,c=65535&a;try{return new Date(1980+((65024&b)>>9),((480&b)>>5)-1,31&b,(63488&c)>>11,(2016&c)>>5,2*(31&c),0)}catch(d){}}function v(a,b,c,d,e){return a.version=b.view.getUint16(c,!0),a.bitFlag=b.view.getUint16(c+2,!0),a.compressionMethod=b.view.getUint16(c+4,!0),a.lastModDateRaw=b.view.getUint32(c+6,!0),a.lastModDate=u(a.lastModDateRaw),1===(1&a.bitFlag)?void e(C):((d||8!=(8&a.bitFlag))&&(a.crc32=b.view.getUint32(c+10,!0),a.compressedSize=b.view.getUint32(c+14,!0),a.uncompressedSize=b.view.getUint32(c+18,!0)),4294967295===a.compressedSize||4294967295===a.uncompressedSize?void e(D):(a.filenameLength=b.view.getUint16(c+22,!0),void(a.extraFieldLength=b.view.getUint16(c+24,!0))))}function w(a,b){function c(){}function e(c,f){a.readUint8Array(a.size-c,c,function(a){var b=d(a.length,a).view;1347093766!=b.getUint32(0)?e(c+1,f):f(b)},function(){b(E)})}return c.prototype.getData=function(c,e,f,g){function h(a,b){m&&m.terminate(),m=null,a&&a(b)}function i(a){var b=d(4);return b.view.setUint32(0,a),n.crc32==b.view.getUint32(0)}function j(a,b){g&&!i(b)?k():c.getData(function(a){h(e,a)})}function k(){h(b,H)}function l(){h(b,G)}var m,n=this;a.readUint8Array(n.offset,30,function(e){var h,i=d(e.length,e);return 1347093252!=i.view.getUint32(0)?void b(B):(v(n,i,4,!1,b),h=n.offset+30+n.filenameLength+n.extraFieldLength,void c.init(function(){0===n.compressionMethod?q(a,c,h,n.compressedSize,g,j,f,k,l):m=o(a,c,h,n.compressedSize,g,j,f,k,l)},l))},k)},{getEntries:function(f){return a.size<22?void b(B):void e(22,function(e){var g,h;g=e.getUint32(16,!0),h=e.getUint16(8,!0),a.readUint8Array(g,a.size-g,function(a){var e,g,i,j,k=0,l=[],m=d(a.length,a);for(e=0;h>e;e++){if(g=new c,1347092738!=m.view.getUint32(k))return void b(B);v(g,m,k+6,!0,b),g.commentLength=m.view.getUint16(k+32,!0),g.directory=16==(16&m.view.getUint8(k+38)),g.offset=m.view.getUint32(k+42,!0),i=t(m.array.subarray(k+46,k+46+g.filenameLength)),g.filename=2048===(2048&g.bitFlag)?s(i):r(i),g.directory||"/"!=g.filename.charAt(g.filename.length-1)||(g.directory=!0),j=t(m.array.subarray(k+46+g.filenameLength+g.extraFieldLength,k+46+g.filenameLength+g.extraFieldLength+g.commentLength)),g.comment=2048===(2048&g.bitFlag)?s(j):r(j),l.push(g),k+=46+g.filenameLength+g.extraFieldLength+g.commentLength}f(l)},function(){b(E)})})},close:function(a){a&&a()}}}function x(a){return unescape(encodeURIComponent(a))}function y(a){var b,c=[];for(b=0;ba;a++){for(c=a,b=0;8>b;b++)1&c?c=c>>>1^3988292384:c>>>=1;d[a]=c}return d}(),f.prototype=new e,f.prototype.constructor=f,g.prototype=new e,g.prototype.constructor=g,h.prototype=new e,h.prototype.constructor=h,i.prototype.getData=function(a){a(this.data)},j.prototype=new i,j.prototype.constructor=j,k.prototype=new i,k.prototype.constructor=k,l.prototype=new i,l.prototype.constructor=l,a.zip={Reader:e,Writer:i,BlobReader:h,Data64URIReader:g,TextReader:f,BlobWriter:l,Data64URIWriter:k,TextWriter:j,createReader:function(a,b,c){a.init(function(){b(w(a,c))},c)},createWriter:function(a,b,c,d){a.init(function(){b(z(a,c,d))},c)},workerScriptsPath:"",useWebWorkers:!0}}(O.esri),O.esri.TPK.autoCenterMap=function(a,b){function c(a){var b="onorientationchange"in window,c=b?"orientationchange":"resize";window.addEventListener(c,e(function(){d()},a))}function d(){require(["esri/geometry/Point","esri/SpatialReference"],function(b,c){var d=i().split(","),e=a.spatialReference.wkid,f=null;4326==e?f=new b(d[1],d[0]):102100==e&&(f=new b(d[0],d[1],new c({wkid:e}))),a.centerAt(f)})}function e(a,b,c){var d;return function(){var e=this,f=arguments;clearTimeout(d),d=setTimeout(function(){d=null,c||a.apply(e,f)},b),c&&!d&&a.apply(e,f)}}function f(){a.on("pan-end",function(){var b=a.extent.getCenter();h(b.x,b.y,a.spatialReference.wkid)})}function g(){a.on("zoom-end",function(){var b=a.extent.getCenter();h(b.x,b.y,a.spatialReference.wkid),a.setZoom(a.getZoom())})}function h(a,b,c){localStorage.setItem("_centerPtX",a),localStorage.setItem("_centerPtY",b),localStorage.setItem("_spatialReference",c)}function i(){var a=null;try{a=localStorage.getItem("_centerPtX")+","+localStorage.getItem("_centerPtY")+","+localStorage.getItem("_spatialReference")}catch(b){}return a}this.init=function(){f(),g(),c(b);var d=a.extent.getCenter();h(d.x,d.y,a.spatialReference.wkid)}},O.esri.TPK.inflate=function(a){function b(){function a(a,b,c,d,j,k,l,n,p,r,s){var t,u,v,w,x,y,z,A,C,D,E,F,G,H,I;D=0,x=c;do e[a[b+D]]++,D++,x--;while(0!==x);if(e[0]==c)return l[0]=-1,n[0]=0,i;for(A=n[0],y=1;B>=y&&0===e[y];y++);for(z=y,y>A&&(A=y),x=B;0!==x&&0===e[x];x--);for(v=x,A>x&&(A=x),n[0]=A,H=1<y;y++,H<<=1)if((H-=e[y])<0)return m;if((H-=e[x])<0)return m;for(e[x]+=H,h[1]=y=0,D=1,G=2;0!==--x;)h[G]=y+=e[D],G++,D++;x=0,D=0;do 0!==(y=a[b+D])&&(s[h[y]++]=x),D++;while(++x=z;z++)for(t=e[z];0!==t--;){for(;z>F+A;){if(w++,F+=A,I=v-F,I=I>A?A:I,(u=1<<(y=z-F))>t+1&&(u-=t+1,G=z,I>y))for(;++yq)return m;g[w]=E=r[0],r[0]+=I,0!==w?(h[w]=x,f[0]=y,f[1]=A,y=x>>>F-A,f[2]=E-g[w-1]-y,p.set(f,3*(g[w-1]+y))):l[0]=E}for(f[1]=z-F,D>=c?f[0]=192:s[D]>>F;I>y;y+=u)p.set(f,3*(E+y));for(y=1<>>=1)x^=y;for(x^=y,C=(1<b;b++)d[b]=0;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(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++))<>=l[B+1],r-=l[B+1],0!==(16&o)){for(o&=15,y=l[B+2]+(q&p[o]),q>>=o,r-=o;15>r;)t--,q|=(255&h.read_byte(s++))<>=l[B+1],r-=l[B+1],0!==(16&o)){for(o&=15;o>r;)t--,q|=(255&h.read_byte(s++))<>=o,r-=o,v-=y,u>=z)A=u-z,u-A>0&&2>u-A?(g.window[u++]=g.window[A++],g.window[u++]=g.window[A++],y-=2):(g.window.set(g.window.subarray(A,A+2),u),u+=2,A+=2,y-=2);else{A=u-z;do A+=g.end;while(0>A);if(o=g.end-A,y>o){if(y-=o,u-A>0&&o>u-A){do g.window[u++]=g.window[A++];while(0!==--o)}else g.window.set(g.window.subarray(A,A+o),u),u+=o,A+=o,o=0;A=0}}if(u-A>0&&y>u-A){do g.window[u++]=g.window[A++];while(0!==--y)}else g.window.set(g.window.subarray(A,A+y),u),u+=y,A+=y,y=0;break}if(0!==(64&o))return h.msg="invalid distance code",y=h.avail_in-t,y=y>r>>3?r>>3:y,t+=y,s-=y,r-=y<<3,g.bitb=q,g.bitk=r,h.avail_in=t,h.total_in+=s-h.next_in_index,h.next_in_index=s,g.write=u,m;k+=l[B+2],k+=q&p[o],B=3*(n+k),o=l[B]}break}if(0!==(64&o))return 0!==(32&o)?(y=h.avail_in-t,y=y>r>>3?r>>3:y,t+=y,s-=y,r-=y<<3,g.bitb=q,g.bitk=r,h.avail_in=t,h.total_in+=s-h.next_in_index,h.next_in_index=s,g.write=u,j):(h.msg="invalid literal/length code",y=h.avail_in-t,y=y>r>>3?r>>3:y,t+=y,s-=y,r-=y<<3,g.bitb=q,g.bitk=r,h.avail_in=t,h.total_in+=s-h.next_in_index,h.next_in_index=s,g.write=u,m);if(k+=l[B+2],k+=q&p[o],B=3*(n+k),0===(o=l[B])){q>>=l[B+1],r-=l[B+1],g.window[u++]=l[B+2],v--;break}}else q>>=l[B+1],r-=l[B+1],g.window[u++]=l[B+2],v--}while(v>=258&&t>=10);return y=h.avail_in-t,y=y>r>>3?r>>3:y,t+=y,s-=y,r-=y<<3,g.bitb=q,g.bitk=r,h.avail_in=t,h.total_in+=s-h.next_in_index,h.next_in_index=s,g.write=u,i}var b,c,d,e,f=this,g=0,h=0,k=0,n=0,o=0,q=0,r=0,s=0,t=0,u=0;f.init=function(a,f,g,h,i,j){b=C,r=a,s=f,d=g,t=h,e=i,u=j,c=null},f.proc=function(f,v,w){var x,y,z,A,B,M,N,O=0,P=0,Q=0;for(Q=v.next_in_index,A=v.avail_in,O=f.bitb,P=f.bitk,B=f.write,M=B=258&&A>=10&&(f.bitb=O,f.bitk=P,v.avail_in=A,v.total_in+=Q-v.next_in_index,v.next_in_index=Q,f.write=B,w=a(r,s,d,t,e,u,f,v),Q=v.next_in_index,A=v.avail_in,O=f.bitb,P=f.bitk,B=f.write,M=BP;){if(0===A)return f.bitb=O,f.bitk=P,v.avail_in=A,v.total_in+=Q-v.next_in_index,v.next_in_index=Q,f.write=B,f.inflate_flush(v,w);w=i,A--,O|=(255&v.read_byte(Q++))<>>=c[y+1],P-=c[y+1],z=c[y],0===z){n=c[y+2],b=I;break}if(0!==(16&z)){o=15&z, -g=c[y+2],b=E;break}if(0===(64&z)){k=z,h=y/3+c[y+2];break}if(0!==(32&z)){b=J;break}return b=L,v.msg="invalid literal/length code",w=m,f.bitb=O,f.bitk=P,v.avail_in=A,v.total_in+=Q-v.next_in_index,v.next_in_index=Q,f.write=B,f.inflate_flush(v,w);case E:for(x=o;x>P;){if(0===A)return f.bitb=O,f.bitk=P,v.avail_in=A,v.total_in+=Q-v.next_in_index,v.next_in_index=Q,f.write=B,f.inflate_flush(v,w);w=i,A--,O|=(255&v.read_byte(Q++))<>=x,P-=x,k=s,c=e,h=u,b=F;case F:for(x=k;x>P;){if(0===A)return f.bitb=O,f.bitk=P,v.avail_in=A,v.total_in+=Q-v.next_in_index,v.next_in_index=Q,f.write=B,f.inflate_flush(v,w);w=i,A--,O|=(255&v.read_byte(Q++))<>=c[y+1],P-=c[y+1],z=c[y],0!==(16&z)){o=15&z,q=c[y+2],b=G;break}if(0===(64&z)){k=z,h=y/3+c[y+2];break}return b=L,v.msg="invalid distance code",w=m,f.bitb=O,f.bitk=P,v.avail_in=A,v.total_in+=Q-v.next_in_index,v.next_in_index=Q,f.write=B,f.inflate_flush(v,w);case G:for(x=o;x>P;){if(0===A)return f.bitb=O,f.bitk=P,v.avail_in=A,v.total_in+=Q-v.next_in_index,v.next_in_index=Q,f.write=B,f.inflate_flush(v,w);w=i,A--,O|=(255&v.read_byte(Q++))<>=x,P-=x,b=H;case H:for(N=B-q;0>N;)N+=f.end;for(;0!==g;){if(0===M&&(B==f.end&&0!==f.read&&(B=0,M=B7&&(P-=8,A++,Q--),f.write=B,w=f.inflate_flush(v,w),B=f.write,M=Ba.avail_out&&(c=a.avail_out),0!==c&&b==o&&(b=i),a.avail_out-=c,a.total_out+=c,a.next_out.set(f.window.subarray(e,e+c),d),d+=c,e+=c,e==f.end&&(e=0,f.write==f.end&&(f.write=0),c=f.write-e,c>a.avail_out&&(c=a.avail_out),0!==c&&b==o&&(b=i),a.avail_out-=c,a.total_out+=c,a.next_out.set(f.window.subarray(e,e+c),d),d+=c,e+=c),a.next_out_index=d,f.read=e,b},f.proc=function(a,c){var d,o,q,w,y,z,A,B;for(w=a.next_in_index,y=a.avail_in,o=f.bitb,q=f.bitk,z=f.write,A=zq;){if(0===y)return f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c);c=i,y--,o|=(255&a.read_byte(w++))<>>1){case 0:o>>>=3,q-=3,d=7&q,o>>>=d,q-=d,g=O;break;case 1:var C=[],D=[],E=[[]],F=[[]];b.inflate_trees_fixed(C,D,E,F),t.init(C[0],D[0],E[0],0,F[0],0),o>>>=3,q-=3,g=T;break;case 2:o>>>=3,q-=3,g=Q;break;case 3:return o>>>=3,q-=3,g=W,a.msg="invalid block type",c=m,f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c)}break;case O:for(;32>q;){if(0===y)return f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c);c=i,y--,o|=(255&a.read_byte(w++))<>>16&65535)!=(65535&o))return g=W,a.msg="invalid stored block lengths",c=m,f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c);h=65535&o,o=q=0,g=0!==h?P:0!==u?U:N;break;case P:if(0===y)return f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c);if(0===A&&(z==f.end&&0!==f.read&&(z=0,A=zy&&(d=y),d>A&&(d=A),f.window.set(a.read_buf(w,d),z),w+=d,y-=d,z+=d,A-=d,0!==(h-=d))break;g=0!==u?U:N;break;case Q:for(;14>q;){if(0===y)return f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c);c=i,y--,o|=(255&a.read_byte(w++))<29||(d>>5&31)>29)return g=W,a.msg="too many length or distance symbols",c=m,f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c);if(d=258+(31&d)+(d>>5&31),!e||e.lengthB;B++)e[B]=0;o>>>=14,q-=14,n=0,g=R;case R:for(;4+(k>>>10)>n;){for(;3>q;){if(0===y)return f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c);c=i,y--,o|=(255&a.read_byte(w++))<>>=3,q-=3}for(;19>n;)e[M[n++]]=0;if(r[0]=7,d=x.inflate_trees_bits(e,r,s,v,a),d!=i)return c=d,c==m&&(e=null,g=W),f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c);n=0,g=S;case S:for(;;){if(d=k,!(258+(31&d)+(d>>5&31)>n))break;var G,H;for(d=r[0];d>q;){if(0===y)return f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c);c=i,y--,o|=(255&a.read_byte(w++))<H)o>>>=d,q-=d,e[n++]=H;else{for(B=18==H?7:H-14,G=18==H?11:3;d+B>q;){if(0===y)return f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c);c=i,y--,o|=(255&a.read_byte(w++))<>>=d,q-=d,G+=o&p[B],o>>>=B,q-=B,B=n,d=k,B+G>258+(31&d)+(d>>5&31)||16==H&&1>B)return e=null,g=W,a.msg="invalid bit length repeat",c=m,f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c);H=16==H?e[B-1]:0;do e[B++]=H;while(0!==--G);n=B}}s[0]=-1;var I=[],J=[],K=[],L=[];if(I[0]=9,J[0]=6,d=k,d=x.inflate_trees_dynamic(257+(31&d),1+(d>>5&31),e,I,J,K,L,v,a),d!=i)return d==m&&(e=null,g=W),c=d,f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c);t.init(I[0],J[0],v,K[0],v,L[0]),g=T;case T:if(f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,(c=t.proc(f,a,c))!=j)return f.inflate_flush(a,c);if(c=i,t.free(a),w=a.next_in_index,y=a.avail_in,o=f.bitb,q=f.bitk,z=f.write,A=ze||e>15?(b.inflateEnd(c),l):(b.wbits=e,c.istate.blocks=new d(c,1<>4)+8>a.istate.wbits){a.istate.mode=ga,a.msg="invalid window size",a.istate.marker=5;break}a.istate.mode=$;case $:if(0===a.avail_in)return c;if(c=b,a.avail_in--,a.total_in++,d=255&a.read_byte(a.next_in_index++),((a.istate.method<<8)+d)%31!==0){a.istate.mode=ga,a.msg="incorrect header check",a.istate.marker=5;break}if(0===(d&X)){a.istate.mode=ea;break}a.istate.mode=_;case _:if(0===a.avail_in)return c;c=b,a.avail_in--,a.total_in++,a.istate.need=(255&a.read_byte(a.next_in_index++))<<24&4278190080,a.istate.mode=aa;case aa:if(0===a.avail_in)return c;c=b,a.avail_in--,a.total_in++,a.istate.need+=(255&a.read_byte(a.next_in_index++))<<16&16711680,a.istate.mode=ba;case ba:if(0===a.avail_in)return c;c=b,a.avail_in--,a.total_in++,a.istate.need+=(255&a.read_byte(a.next_in_index++))<<8&65280,a.istate.mode=ca;case ca:return 0===a.avail_in?c:(c=b,a.avail_in--,a.total_in++,a.istate.need+=255&a.read_byte(a.next_in_index++),a.istate.mode=da,k);case da:return a.istate.mode=ga,a.msg="need dictionary",a.istate.marker=0,l;case ea:if(c=a.istate.blocks.proc(a,c),c==m){a.istate.mode=ga,a.istate.marker=0;break}if(c==i&&(c=b),c!=j)return c;c=b,a.istate.blocks.reset(a,a.istate.was),a.istate.mode=fa;case fa:return j;case ga:return m;default:return l}},b.inflateSetDictionary=function(a,b,c){var d=0,e=c;return a&&a.istate&&a.istate.mode==da?(e>=1<e;)b.read_byte(d)==ha[e]?e++:e=0!==b.read_byte(d)?0:4-e,d++,c--;return b.total_in+=d-b.next_in_index,b.next_in_index=d,b.avail_in=c,b.istate.marker=e,4!=e?m:(f=b.total_in,g=b.total_out,a(b),b.total_in=f,b.total_out=g,b.istate.mode=ea,i)},b.inflateSyncPoint=function(a){return a&&a.istate&&a.istate.blocks?a.istate.blocks.sync_point():l}}function f(){}function g(){var a=this,b=new f,c=512,d=r,e=new Uint8Array(c),g=!1;b.inflateInit(),b.next_out=e,a.append=function(a,f){var h,k,l=[],m=0,n=0,p=0;if(0!==a.length){b.next_in_index=0,b.next_in=a,b.avail_in=a.length;do{if(b.next_out_index=0,b.avail_out=c,0!==b.avail_in||g||(b.next_in_index=0,g=!0),h=b.inflate(d),g&&h==o)return-1;if(h!=i&&h!=j)throw"inflating: "+b.msg;if((g||h==j)&&b.avail_in==a.length)return-1;b.next_out_index&&(b.next_out_index==c?l.push(new Uint8Array(e)):l.push(new Uint8Array(e.subarray(0,b.next_out_index)))),p+=b.next_out_index,f&&b.next_in_index>0&&b.next_in_index!=m&&(f(b.next_in_index),m=b.next_in_index)}while(b.avail_in>0||0===b.avail_out);return k=new Uint8Array(p),l.forEach(function(a){k.set(a,n),n+=a.length}),k}},a.flush=function(){b.inflateEnd()}}var h=15,i=0,j=1,k=2,l=-2,m=-3,n=-4,o=-5,p=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535],q=1440,r=0,s=4,t=9,u=5,v=[96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,192,80,7,10,0,8,96,0,8,32,0,9,160,0,8,0,0,8,128,0,8,64,0,9,224,80,7,6,0,8,88,0,8,24,0,9,144,83,7,59,0,8,120,0,8,56,0,9,208,81,7,17,0,8,104,0,8,40,0,9,176,0,8,8,0,8,136,0,8,72,0,9,240,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,200,81,7,13,0,8,100,0,8,36,0,9,168,0,8,4,0,8,132,0,8,68,0,9,232,80,7,8,0,8,92,0,8,28,0,9,152,84,7,83,0,8,124,0,8,60,0,9,216,82,7,23,0,8,108,0,8,44,0,9,184,0,8,12,0,8,140,0,8,76,0,9,248,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,196,81,7,11,0,8,98,0,8,34,0,9,164,0,8,2,0,8,130,0,8,66,0,9,228,80,7,7,0,8,90,0,8,26,0,9,148,84,7,67,0,8,122,0,8,58,0,9,212,82,7,19,0,8,106,0,8,42,0,9,180,0,8,10,0,8,138,0,8,74,0,9,244,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,204,81,7,15,0,8,102,0,8,38,0,9,172,0,8,6,0,8,134,0,8,70,0,9,236,80,7,9,0,8,94,0,8,30,0,9,156,84,7,99,0,8,126,0,8,62,0,9,220,82,7,27,0,8,110,0,8,46,0,9,188,0,8,14,0,8,142,0,8,78,0,9,252,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,194,80,7,10,0,8,97,0,8,33,0,9,162,0,8,1,0,8,129,0,8,65,0,9,226,80,7,6,0,8,89,0,8,25,0,9,146,83,7,59,0,8,121,0,8,57,0,9,210,81,7,17,0,8,105,0,8,41,0,9,178,0,8,9,0,8,137,0,8,73,0,9,242,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,202,81,7,13,0,8,101,0,8,37,0,9,170,0,8,5,0,8,133,0,8,69,0,9,234,80,7,8,0,8,93,0,8,29,0,9,154,84,7,83,0,8,125,0,8,61,0,9,218,82,7,23,0,8,109,0,8,45,0,9,186,0,8,13,0,8,141,0,8,77,0,9,250,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,198,81,7,11,0,8,99,0,8,35,0,9,166,0,8,3,0,8,131,0,8,67,0,9,230,80,7,7,0,8,91,0,8,27,0,9,150,84,7,67,0,8,123,0,8,59,0,9,214,82,7,19,0,8,107,0,8,43,0,9,182,0,8,11,0,8,139,0,8,75,0,9,246,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,206,81,7,15,0,8,103,0,8,39,0,9,174,0,8,7,0,8,135,0,8,71,0,9,238,80,7,9,0,8,95,0,8,31,0,9,158,84,7,99,0,8,127,0,8,63,0,9,222,82,7,27,0,8,111,0,8,47,0,9,190,0,8,15,0,8,143,0,8,79,0,9,254,96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,193,80,7,10,0,8,96,0,8,32,0,9,161,0,8,0,0,8,128,0,8,64,0,9,225,80,7,6,0,8,88,0,8,24,0,9,145,83,7,59,0,8,120,0,8,56,0,9,209,81,7,17,0,8,104,0,8,40,0,9,177,0,8,8,0,8,136,0,8,72,0,9,241,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,201,81,7,13,0,8,100,0,8,36,0,9,169,0,8,4,0,8,132,0,8,68,0,9,233,80,7,8,0,8,92,0,8,28,0,9,153,84,7,83,0,8,124,0,8,60,0,9,217,82,7,23,0,8,108,0,8,44,0,9,185,0,8,12,0,8,140,0,8,76,0,9,249,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,197,81,7,11,0,8,98,0,8,34,0,9,165,0,8,2,0,8,130,0,8,66,0,9,229,80,7,7,0,8,90,0,8,26,0,9,149,84,7,67,0,8,122,0,8,58,0,9,213,82,7,19,0,8,106,0,8,42,0,9,181,0,8,10,0,8,138,0,8,74,0,9,245,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,205,81,7,15,0,8,102,0,8,38,0,9,173,0,8,6,0,8,134,0,8,70,0,9,237,80,7,9,0,8,94,0,8,30,0,9,157,84,7,99,0,8,126,0,8,62,0,9,221,82,7,27,0,8,110,0,8,46,0,9,189,0,8,14,0,8,142,0,8,78,0,9,253,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,195,80,7,10,0,8,97,0,8,33,0,9,163,0,8,1,0,8,129,0,8,65,0,9,227,80,7,6,0,8,89,0,8,25,0,9,147,83,7,59,0,8,121,0,8,57,0,9,211,81,7,17,0,8,105,0,8,41,0,9,179,0,8,9,0,8,137,0,8,73,0,9,243,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,203,81,7,13,0,8,101,0,8,37,0,9,171,0,8,5,0,8,133,0,8,69,0,9,235,80,7,8,0,8,93,0,8,29,0,9,155,84,7,83,0,8,125,0,8,61,0,9,219,82,7,23,0,8,109,0,8,45,0,9,187,0,8,13,0,8,141,0,8,77,0,9,251,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,199,81,7,11,0,8,99,0,8,35,0,9,167,0,8,3,0,8,131,0,8,67,0,9,231,80,7,7,0,8,91,0,8,27,0,9,151,84,7,67,0,8,123,0,8,59,0,9,215,82,7,19,0,8,107,0,8,43,0,9,183,0,8,11,0,8,139,0,8,75,0,9,247,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,207,81,7,15,0,8,103,0,8,39,0,9,175,0,8,7,0,8,135,0,8,71,0,9,239,80,7,9,0,8,95,0,8,31,0,9,159,84,7,99,0,8,127,0,8,63,0,9,223,82,7,27,0,8,111,0,8,47,0,9,191,0,8,15,0,8,143,0,8,79,0,9,255],w=[80,5,1,87,5,257,83,5,17,91,5,4097,81,5,5,89,5,1025,85,5,65,93,5,16385,80,5,3,88,5,513,84,5,33,92,5,8193,82,5,9,90,5,2049,86,5,129,192,5,24577,80,5,2,87,5,385,83,5,25,91,5,6145,81,5,7,89,5,1537,85,5,97,93,5,24577,80,5,4,88,5,769,84,5,49,92,5,12289,82,5,13,90,5,3073,86,5,193,192,5,24577],x=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],y=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,112,112],z=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],A=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],B=15;b.inflate_trees_fixed=function(a,b,c,d){return a[0]=t,b[0]=u,c[0]=v,d[0]=w,i};var C=0,D=1,E=2,F=3,G=4,H=5,I=6,J=7,K=8,L=9,M=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],N=0,O=1,P=2,Q=3,R=4,S=5,T=6,U=7,V=8,W=9,X=32,Y=8,Z=0,$=1,_=2,aa=3,ba=4,ca=5,da=6,ea=7,fa=12,ga=13,ha=[0,0,255,255];f.prototype={inflateInit:function(a){var b=this;return b.istate=new e,a||(a=h),b.istate.inflateInit(b,a)},inflate:function(a){var b=this;return b.istate?b.istate.inflate(b,a):l},inflateEnd:function(){var a=this;if(!a.istate)return l;var b=a.istate.inflateEnd(a);return a.istate=null,b},inflateSync:function(){var a=this;return a.istate?a.istate.inflateSync(a):l},inflateSetDictionary:function(a,b){var c=this;return c.istate?c.istate.inflateSetDictionary(c,a,b):l},read_byte:function(a){var b=this;return b.next_in.subarray(a,a+1)[0]},read_buf:function(a,b){var c=this;return c.next_in.subarray(a,a+b)}};var ia;a.zip?a.zip.Inflater=g:(ia=new g,a.addEventListener("message",function(b){var c=b.data;c.append&&a.postMessage({onappend:!0,data:ia.append(c.data,function(b){a.postMessage({progress:!0,current:b})})}),c.flush&&(ia.flush(),a.postMessage({onflush:!0}))},!1))},O.esri.TPK.___test=O.esri.TPK.inflate.toString(),O.esri.TPK.___blobURL=URL.createObjectURL(new Blob(["(",O.esri.TPK.___test,")(this)"],{type:"application/javascript"})),O.esri.zip.workerScriptsPath=O.esri.TPK.___blobURL,O.esri.TPK.X2JS=function(a){"use strict";function b(){void 0===a.escapeMode&&(a.escapeMode=!0),a.attributePrefix=a.attributePrefix||"_",a.arrayAccessForm=a.arrayAccessForm||"none",a.emptyNodeForm=a.emptyNodeForm||"text",void 0===a.enableToStringFunc&&(a.enableToStringFunc=!0),a.arrayAccessFormPaths=a.arrayAccessFormPaths||[],void 0===a.skipEmptyTextNodesForObj&&(a.skipEmptyTextNodesForObj=!0),void 0===a.stripWhitespaces&&(a.stripWhitespaces=!0),a.datetimeAccessFormPaths=a.datetimeAccessFormPaths||[]}function c(){function a(a){var b=String(a);return 1===b.length&&(b="0"+b),b}"function"!=typeof String.prototype.trim&&(String.prototype.trim=function(){return this.replace(/^\s+|^\n+|(\s|\n)+$/g,"")}),"function"!=typeof Date.prototype.toISOString&&(Date.prototype.toISOString=function(){return this.getUTCFullYear()+"-"+a(this.getUTCMonth()+1)+"-"+a(this.getUTCDate())+"T"+a(this.getUTCHours())+":"+a(this.getUTCMinutes())+":"+a(this.getUTCSeconds())+"."+String((this.getUTCMilliseconds()/1e3).toFixed(3)).slice(2,5)+"Z"})}function d(a){var b=a.localName;return null==b&&(b=a.baseName),(null==b||""==b)&&(b=a.nodeName),b}function e(a){return a.prefix}function f(a){return"string"==typeof a?a.replace(/&/g,"&").replace(//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())));return c}function j(b,c,d){if(a.datetimeAccessFormPaths.length>0){for(var e=d.split(".#")[0],f=0;f1&&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;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);return c}function r(b){var c="";return null!=b.__cdata&&(c+=""),null!=b.__text&&(c+=a.escapeMode?f(b.__text):b.__text),c}function s(b){var c="";return b instanceof Object?c+=r(b):null!=b&&(c+=a.escapeMode?f(b):b),c}function t(a,b,c){var d="";if(0==a.length)d+=l(a,b,c,!0);else for(var e=0;e0)for(var d in a)if(!o(a,d)){var e=a[d],f=q(e);if(null==e||void 0==e)b+=l(e,d,f,!0);else if(e instanceof Object)if(e instanceof Array)b+=t(e,d,f);else if(e instanceof Date)b+=l(e,d,f,!1),b+=e.toISOString(),b+=m(e,d);else{var g=p(e);g>0||null!=e.__text||null!=e.__cdata?(b+=l(e,d,f,!1),b+=u(e),b+=m(e,d)):b+=l(e,d,f,!0)}else b+=l(e,d,f,!1),b+=s(e),b+=m(e,d)}return b+=s(a)}var v="1.1.5",a=a||{};b(),c();var w={ELEMENT_NODE:1,TEXT_NODE:3,CDATA_SECTION_NODE:4,COMMENT_NODE:8,DOCUMENT_NODE:9};this.parseXmlString=function(a){var b=window.ActiveXObject||"ActiveXObject"in window;if(void 0===a)return null;var c;if(window.DOMParser){var d=new window.DOMParser,e=null;if(!b)try{e=d.parseFromString("INVALID","text/xml").childNodes[0].namespaceURI}catch(f){e=null}try{c=d.parseFromString(a,"text/xml"),null!=e&&c.getElementsByTagNameNS(e,"parsererror").length>0&&(c=null)}catch(f){c=null}}else 0==a.indexOf("")+2)),c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(a);return c},this.asArray=function(a){return a instanceof Array?a:[a]},this.toXmlDateTime=function(a){return a instanceof Date?a.toISOString():"number"==typeof a?new Date(a).toISOString():null},this.asDateTime=function(a){return"string"==typeof a?i(a):a},this.xml2json=function(a){return k(a)},this.xml_str2json=function(a){var b=this.parseXmlString(a);return null!=b?this.xml2json(b):null},this.json2xml_str=function(a){return u(a)},this.json2xml=function(a){var b=this.json2xml_str(a);return this.parseXmlString(b)},this.getVersion=function(){return v}}; \ No newline at end of file +define(["dojo/_base/declare","esri/geometry/Extent","dojo/query","esri/SpatialReference","esri/layers/TileInfo","esri/layers/TiledMapServiceLayer","dojo/Deferred","dojo/promise/all","dojo/Evented"],function(a,b,c,d,e,f,g,h,i){return a("O.esri.TPK.TPKLayer",[f,i],{map:null,store:null,MAX_DB_SIZE:75,TILE_PATH:"",RECENTER_DELAY:350,PARSING_ERROR:"parsingError",DB_INIT_ERROR:"dbInitError",DB_FULL_ERROR:"dbFullError",NO_SUPPORT_ERROR:"libNotSupportedError",PROGRESS_START:"start",PROGRESS_END:"end",WINDOW_VALIDATED:"windowValidated",DB_VALIDATED:"dbValidated",DATABASE_ERROR_EVENT:"databaseErrorEvent",VALIDATION_EVENT:"validationEvent",PROGRESS_EVENT:"progress",_maxDBSize:75,_isDBWriteable:!0,_isDBValid:!1,_autoCenter:null,_fileEntriesLength:0,_inMemTilesObject:null,_inMemTilesObjectLength:0,_zeroLengthFileCounter:0,constructor:function(){this._self=this,this._inMemTilesIndex=[],this._inMemTilesObject={},this.store=new O.esri.Tiles.TilesStore,this._validate()},extend:function(a){this._fileEntriesLength=a.length,this.emit(this.PROGRESS_EVENT,this.PROGRESS_START),this._parseInMemFiles(a,function(){this._parseConfCdi(function(a){this.initialExtent=this.fullExtent=a,this._parseConfXml(function(a){this.tileInfo=new e(a),this.spatialReference=new d({wkid:this.tileInfo.spatialReference.wkid}),this.loaded=!0,this.onLoad(this),this.emit(this.PROGRESS_EVENT,this.PROGRESS_END)}.bind(this._self))}.bind(this._self))}.bind(this._self))},getTileUrl:function(a,b,d){this.emit(this.PROGRESS_EVENT,this.PROGRESS_START) +var e=this._self.TILE_PATH+"_alllayers",f=this._getCacheFilePath(e,a,b,d) +if(this._inMemTilesObject!={}){var g="void:/"+a+"/"+b+"/"+d +return null==this.map&&(this.map=this.getMap()),null==this._autoCenter&&(this._autoCenter=new O.esri.TPK.autoCenterMap(this.map,this.RECENTER_DELAY),this._autoCenter.init()),this._getInMemTiles(f,e,a,b,d,g,function(a,b,d){var e=c("img[src="+b+"]")[0] +"undefined"==typeof e&&(e=new Image) +var f +if(a){var g="data:image/png;base64," +switch(this.tileInfo.format){case"JPEG":f="data:image/jpg;base64,"+a +break +case"PNG":f=g+a +break +case"PNG8":f=g+a +break +case"PNG24":f=g+a +break +case"PNG32":f=g+a +break +default:f="data:image/jpg;base64,"+a}e.style.borderColor="blue"}else e.style.borderColor="green",f="data:image/png;base64,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pmk0TROPj48Hn3l4eIimaSJN06O3A4NJwDMxm82ibdtXo4D5fB6r1Sp+//4dz8/Pw5H+6ekpdrtdJEkS8/n8S/9f713ie3vaceo9x557QAB451Sgfyin34HKshweunk5HzAej2MymXz5+f9nbjJyI9L39Wu5XP55+XQZ39uxR4Z3u90wSXjqEV0wAjhjx47oaZq63Me/ZhIQBAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEAAbAJQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEABAAAABAAQAEAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEAvqe/BwCeKjUweoA8pQAAAABJRU5ErkJggg==" +return e.style.visibility="visible",e.src=f,this.emit(this.PROGRESS_EVENT,this.PROGRESS_END),""}.bind(this._self)),g}},setMaxDBSize:function(a){var b=/^\d+$/ +b.test(a)&&a<=this.MAX_DB_SIZE&&(this._maxDBSize=a)},getDBSize:function(a){this.store.usedSpace(function(b,c){a(b,c)}.bind(this))},setDBWriteable:function(a){this._isDBWriteable=a},isDBValid:function(){return this._validate(),this._isDBValid},loadFromURL:function(a,b){this.isDBValid()?this.store.store(a,function(a,c){a?b(!0,""):b(!1,c)}):b(!1,"not supported")},_validate:function(){window.File||window.FileReader||window.Blob||window.btoa||window.DataView?this.emit(this.VALIDATION_EVENT,{msg:this.WINDOW_VALIDATED,err:null}):this.emit(this.VALIDATION_EVENT,{msg:this.NO_SUPPORT_ERROR,err:null}),this.store.isSupported()?this.store.init(function(a){a===!1?this.emit(this.DATABASE_ERROR_EVENT,{msg:this.DB_INIT_ERROR,err:null}):this.store.usedSpace(function(a,b){var c=this._bytes2MBs(a.sizeBytes) +c>this.MAX_DB_SIZE&&this.emit(this.DATABASE_ERROR_EVENT,{msg:this.DB_FULL_ERROR,err:b}),this.emit(this.VALIDATION_EVENT,{msg:this.DB_VALIDATED,err:null}),this._isDBValid=!0}.bind(this))}.bind(this)):this.emit(this.VALIDATION_EVENT,{msg:this.NO_SUPPORT_ERROR,err:null})},_parseInMemFiles:function(a,b){var c=this._fileEntriesLength +this._zeroLengthFileCounter=0 +for(var d=[],e=0;c>e;e++){var f=new g,i=a[e].filename.toLocaleUpperCase(),j=i.indexOf("_ALLLAYERS",0);-1!=j&&(this.TILE_PATH=i.slice(0,j)),0===a[e].compressedSize&&this._zeroLengthFileCounter++ +var k=i.indexOf("CONF.CDI",0),l=i.indexOf("CONF.XML",0),m=i.indexOf("BUNDLE",0),n=i.indexOf("BUNDLX",0);-1!=k||-1!=l?this._unzipConfFiles(a,e,f,function(a,b){a.resolve(b)}):-1!=m||-1!=n?this._unzipTileFiles(a,e,f,function(a,b){a.resolve(b)}):f.resolve(e),d.push(f)}h(d).then(function(a){b&&b(a)})},ObjectSize:function(a){var b,c=0 +for(b in a)a.hasOwnProperty(b)&&c++ +return c},_unzipConfFiles:function(a,b,c,d){a[b].getData(new O.esri.zip.TextWriter(b),function(b){this._inMemTilesIndex.push("blank") +var e=a[b.token].filename.toLocaleUpperCase() +this._inMemTilesObject[e]=b.string +var f=this.ObjectSize(this._inMemTilesObject) +f>0&&d(c,b.token)}.bind(this))},_unzipTileFiles:function(a,b,c,d){var e=this +a[b].getData(new O.esri.zip.BlobWriter(b),function(b){if(0!==b.size){var f=new FileReader +f.token=b.token,f.onerror=function(a){e.emit(e.PARSING_ERROR,{msg:"Error parsing file: ",err:a.target.error})},f.onloadend=function(g){if(void 0!==f.token){e._inMemTilesIndex.push("blank") +var h=a[f.token].filename.toLocaleUpperCase() +e._inMemTilesObject[h]=f.result +var i=e.ObjectSize(e._inMemTilesObject) +i>0&&d(c,b.token)}},f.readAsArrayBuffer(b)}})},_parseConfCdi:function(a){var c=this._inMemTilesObject[this.TILE_PATH+"CONF.CDI"],e=new O.esri.TPK.X2JS,f=e.xml_str2json(c),g=f.EnvelopeN,h=parseFloat(g.XMin),i=parseFloat(g.YMin),j=parseFloat(g.XMax),k=parseFloat(g.YMax),l=parseInt(g.SpatialReference.WKID),m=new b(h,i,j,k,new d({wkid:l})) +a(m)},_parseConfXml:function(a){var b=this._inMemTilesObject[this.TILE_PATH+"CONF.XML"],c=new O.esri.TPK.X2JS,d=c.xml_str2json(b),e=d.CacheInfo,f={} +f.rows=parseInt(e.TileCacheInfo.TileRows),f.cols=parseInt(e.TileCacheInfo.TileCols),f.dpi=parseInt(e.TileCacheInfo.DPI),f.format=e.TileImageInfo.CacheTileFormat,f.compressionQuality=parseInt(e.TileImageInfo.CompressionQuality),f.origin={x:parseInt(e.TileCacheInfo.TileOrigin.X),y:parseInt(e.TileCacheInfo.TileOrigin.Y)},f.spatialReference={wkid:parseInt(e.TileCacheInfo.SpatialReference.WKID)} +for(var g=e.TileCacheInfo.LODInfos.LODInfo,h=[],i=0;im;m+=3)f=i[m]<<16|i[m+1]<<8|i[m+2],b=(16515072&f)>>18,c=(258048&f)>>12,d=(4032&f)>>6,e=63&f,g+=h[b]+h[c]+h[d]+h[e] +return 1==k?(f=i[l],b=(252&f)>>2,c=(3&f)<<4,g+=h[b]+h[c]+"=="):2==k&&(f=i[l]<<8|i[l+1],b=(64512&f)>>10,c=(1008&f)>>4,d=(15&f)<<2,g+=h[b]+h[c]+h[d]+"="),g},_buffer2Base64:function(a,b,c){var d=new DataView(a,b),e=d.getInt32(0,!0),f=d.buffer.slice(b+4,b+4+e),g=this._base64ArrayBuffer(f) +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") +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) +d.onsuccess=function(a){var c=a.target.result +void 0===c?b(!1,"not found"):b(!0,c)},d.onerror=function(a){b(!1,a)}}},this.deleteAll=function(a){if(null!==this._db){var b=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName).clear() +b.onsuccess=function(){a(!0)},b.onerror=function(b){a(!1,b)}}else a(!1,null)},this["delete"]=function(a,b){if(null!==this._db){var c=this._db.transaction([this.objectStoreName],"readwrite").objectStore(this.objectStoreName)["delete"](a) +c.onsuccess=function(){b(!0)},c.onerror=function(a){b(!1,a)}}else b(!1,null)},this.getAllTiles=function(a){if(null!==this._db){var b=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor() +b.onsuccess=function(b){var c=b.target.result +if(c){var d=c.value.url,e=c.value.img +a(d,e,null),c["continue"]()}else a(null,null,"end")}.bind(this),b.onerror=function(b){a(null,null,b)}}else a(null,null,"no db")},this.usedSpace=function(a){if(null!==this._db){var b={sizeBytes:0,tileCount:0},c=this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName).openCursor() +c.onsuccess=function(c){var d=c.target.result +if(d){var e=d.value,f=JSON.stringify(e) +b.sizeBytes+=this._stringBytes(f),b.tileCount+=1,d["continue"]()}else a(b,null)}.bind(this),c.onerror=function(b){a(null,b)}}else a(null,null)},this._stringBytes=function(a){return a.length},this.init=function(a){var b=indexedDB.open(this.dbName,4) +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:"url"})}.bind(this),b.onsuccess=function(b){this._db=b.target.result,a(!0)}.bind(this)}},function(a){function b(){var a=-1,b=this +b.append=function(c){var d,e=b.table +for(d=0;d>>8^e[255&(a^c[d])]},b.get=function(){return~a}}function c(a,b,c){return a.slice?a.slice(b,b+c):a.webkitSlice?a.webkitSlice(b,b+c):a.mozSlice?a.mozSlice(b,b+c):a.msSlice?a.msSlice(b,b+c):void 0}function d(a,b){var c,d +return c=new ArrayBuffer(a),d=new Uint8Array(c),b&&d.set(b,0),{buffer:c,array:d,view:new DataView(c)}}function e(){}function f(a){function b(b,c){var f=new Blob([a],{type:M}) +d=new h(f),d.init(function(){e.size=d.size,b()},c)}function c(a,b,c,e){d.readUint8Array(a,b,c,e)}var d,e=this +e.size=0,e.init=b,e.readUint8Array=c}function g(b){function c(a){for(var c=b.length;"="==b.charAt(c-1);)c-- +f=b.indexOf(",")+1,g.size=Math.floor(.75*(c-f)),a()}function e(c,e,g){var h,i=d(e),j=4*Math.floor(c/3),k=4*Math.ceil((c+e)/3),l=a.atob(b.substring(j+f,k+f)),m=c-3*Math.floor(j/4) +for(h=m;m+e>h;h++)i.array[h-m]=l.charCodeAt(h) +g(i.array)}var f,g=this +g.size=0,g.init=c,g.readUint8Array=e}function h(a){function b(b){this.size=a.size,b()}function d(b,d,e,f){var g=new FileReader +g.onload=function(a){e(new Uint8Array(a.target.result))},g.onerror=f,g.readAsArrayBuffer(c(a,b,d))}var e=this +e.size=0,e.init=b,e.readUint8Array=d}function i(){}function j(a,b){function c(a){f=new Blob([],{type:M}),a()}function d(a,b){f=new Blob([f,A?a:a.buffer],{type:M}),b()}function e(c,d){var e=new FileReader +e.onload=function(b){var d={string:b.target.result,token:a} +c(d)},e.onerror=d,e.readAsText(f,b)}var f,g=this +g.init=c,g.writeUint8Array=d,g.getData=e}function k(b){function c(a){g+="data:"+(b||"")+";base64,",a()}function d(b,c){var d,e=h.length,f=h +for(h="",d=0;d<3*Math.floor((e+b.length)/3)-e;d++)f+=String.fromCharCode(b[d]) +for(;d2?g+=a.btoa(f):h=f,c()}function e(b){b(g+a.btoa(h))}var f=this,g="",h="" +f.init=c,f.writeUint8Array=d,f.getData=e}function l(a,b){function c(a){f=new Blob([],{type:b}),a()}function d(c,d){f=new Blob([f,A?c:c.buffer],{type:b}),f.token=a,d()}function e(a){a(f)}var f,g=this +g.init=c,g.writeUint8Array=d,g.getData=e}function m(a,b,c,d,e,f,g,h,i,j){function k(){a.removeEventListener(N,l,!1),h(o)}function l(a){var b=a.data,d=b.data +b.onappend&&(o+=d.length,c.writeUint8Array(d,function(){f(!1,d),m()},j)),b.onflush&&(d?(o+=d.length,c.writeUint8Array(d,function(){f(!1,d),k()},j)):k()),b.progress&&g&&g(n+b.current,e)}function m(){n=p*J,e>n?b.readUint8Array(d+n,Math.min(J,e-n),function(b){a.postMessage({append:!0,data:b}),p++,g&&g(n,e),f(!0,b)},i):a.postMessage({flush:!0})}var n,o,p=0 +o=0,a.addEventListener(N,l,!1),m()}function n(a,b,c,d,e,f,g,h,i,j){function k(){var o +l=m*J,e>l?b.readUint8Array(d+l,Math.min(J,e-l),function(b){var h=a.append(b,function(){g&&g(d+l,e)}) +n+=h.length,f(!0,b),c.writeUint8Array(h,function(){f(!1,h),m++,setTimeout(k,1)},j),g&&g(l,e)},i):(o=a.flush(),o?(n+=o.length,c.writeUint8Array(o,function(){f(!1,o),h(n)},j)):h(n))}var l,m=0,n=0 +k()}function o(c,d,e,f,g,h,i,j,k){function l(a,b){g&&!a&&q.append(b)}function o(a){h(a,q.get())}var p,q=new b +return a.zip.useWebWorkers?(p=new Worker(a.zip.workerScriptsPath+K),m(p,c,d,e,f,l,i,o,j,k)):n(new a.zip.Inflater,c,d,e,f,l,i,o,j,k),p}function p(c,d,e,f,g,h,i){function j(a,b){a&&p.append(b)}function k(a){f(a,p.get())}function l(){o.removeEventListener(N,l,!1),m(o,c,d,0,c.size,j,g,k,h,i)}var o,p=new b +return a.zip.useWebWorkers?(o=new Worker(a.zip.workerScriptsPath+L),o.addEventListener(N,l,!1),o.postMessage({init:!0,level:e})):n(new a.zip.Deflater,c,d,0,c.size,j,g,k,h,i),o}function q(a,c,d,e,f,g,h,i,j){function k(){var b=l*J +e>b?a.readUint8Array(d+b,Math.min(J,e-b),function(a){f&&m.append(a),h&&h(b,e,a),c.writeUint8Array(a,function(){l++,k()},j)},i):g(e,m.get())}var l=0,m=new b +k()}function r(a){var b,c,d="",e=["Ç","ü","é","â","ä","à","å","ç","ê","ë","è","ï","î","ì","Ä","Å","É","æ","Æ","ô","ö","ò","û","ù","ÿ","Ö","Ü","ø","£","Ø","×","ƒ","á","í","ó","ú","ñ","Ñ","ª","º","¿","®","¬","½","¼","¡","«","»","_","_","_","¦","¦","Á","Â","À","©","¦","¦","+","+","¢","¥","+","+","-","-","+","-","+","ã","Ã","+","+","-","-","¦","-","+","¤","ð","Ð","Ê","Ë","È","i","Í","Î","Ï","+","+","_","_","¦","Ì","_","Ó","ß","Ô","Ò","õ","Õ","µ","þ","Þ","Ú","Û","Ù","ý","Ý","¯","´","­","±","_","¾","¶","§","÷","¸","°","¨","·","¹","³","²","_"," "] +for(b=0;b127?e[c-128]:String.fromCharCode(c) +return d}function s(a){return decodeURIComponent(escape(a))}function t(a){var b,c="" +for(b=0;b>16,c=65535&a +try{return new Date(1980+((65024&b)>>9),((480&b)>>5)-1,31&b,(63488&c)>>11,(2016&c)>>5,2*(31&c),0)}catch(d){}}function v(a,b,c,d,e){return a.version=b.view.getUint16(c,!0),a.bitFlag=b.view.getUint16(c+2,!0),a.compressionMethod=b.view.getUint16(c+4,!0),a.lastModDateRaw=b.view.getUint32(c+6,!0),a.lastModDate=u(a.lastModDateRaw),1===(1&a.bitFlag)?void e(C):((d||8!=(8&a.bitFlag))&&(a.crc32=b.view.getUint32(c+10,!0),a.compressedSize=b.view.getUint32(c+14,!0),a.uncompressedSize=b.view.getUint32(c+18,!0)),4294967295===a.compressedSize||4294967295===a.uncompressedSize?void e(D):(a.filenameLength=b.view.getUint16(c+22,!0),void(a.extraFieldLength=b.view.getUint16(c+24,!0))))}function w(a,b){function c(){}function e(c,f){a.readUint8Array(a.size-c,c,function(a){var b=d(a.length,a).view +1347093766!=b.getUint32(0)?e(c+1,f):f(b)},function(){b(E)})}return c.prototype.getData=function(c,e,f,g){function h(a,b){m&&m.terminate(),m=null,a&&a(b)}function i(a){var b=d(4) +return b.view.setUint32(0,a),n.crc32==b.view.getUint32(0)}function j(a,b){g&&!i(b)?k():c.getData(function(a){h(e,a)})}function k(){h(b,H)}function l(){h(b,G)}var m,n=this +a.readUint8Array(n.offset,30,function(e){var h,i=d(e.length,e) +return 1347093252!=i.view.getUint32(0)?void b(B):(v(n,i,4,!1,b),h=n.offset+30+n.filenameLength+n.extraFieldLength,void c.init(function(){0===n.compressionMethod?q(a,c,h,n.compressedSize,g,j,f,k,l):m=o(a,c,h,n.compressedSize,g,j,f,k,l)},l))},k)},{getEntries:function(f){return a.size<22?void b(B):void e(22,function(e){var g,h +g=e.getUint32(16,!0),h=e.getUint16(8,!0),a.readUint8Array(g,a.size-g,function(a){var e,g,i,j,k=0,l=[],m=d(a.length,a) +for(e=0;h>e;e++){if(g=new c,1347092738!=m.view.getUint32(k))return void b(B) +v(g,m,k+6,!0,b),g.commentLength=m.view.getUint16(k+32,!0),g.directory=16==(16&m.view.getUint8(k+38)),g.offset=m.view.getUint32(k+42,!0),i=t(m.array.subarray(k+46,k+46+g.filenameLength)),g.filename=2048===(2048&g.bitFlag)?s(i):r(i),g.directory||"/"!=g.filename.charAt(g.filename.length-1)||(g.directory=!0),j=t(m.array.subarray(k+46+g.filenameLength+g.extraFieldLength,k+46+g.filenameLength+g.extraFieldLength+g.commentLength)),g.comment=2048===(2048&g.bitFlag)?s(j):r(j),l.push(g),k+=46+g.filenameLength+g.extraFieldLength+g.commentLength}f(l)},function(){b(E)})})},close:function(a){a&&a()}}}function x(a){return unescape(encodeURIComponent(a))}function y(a){var b,c=[] +for(b=0;ba;a++){for(c=a,b=0;8>b;b++)1&c?c=c>>>1^3988292384:c>>>=1 +d[a]=c}return d}(),f.prototype=new e,f.prototype.constructor=f,g.prototype=new e,g.prototype.constructor=g,h.prototype=new e,h.prototype.constructor=h,i.prototype.getData=function(a){a(this.data)},j.prototype=new i,j.prototype.constructor=j,k.prototype=new i,k.prototype.constructor=k,l.prototype=new i,l.prototype.constructor=l,a.zip={Reader:e,Writer:i,BlobReader:h,Data64URIReader:g,TextReader:f,BlobWriter:l,Data64URIWriter:k,TextWriter:j,createReader:function(a,b,c){a.init(function(){b(w(a,c))},c)},createWriter:function(a,b,c,d){a.init(function(){b(z(a,c,d))},c)},workerScriptsPath:"",useWebWorkers:!0}}(O.esri),O.esri.TPK.autoCenterMap=function(a,b){function c(a){var b="onorientationchange"in window,c=b?"orientationchange":"resize" +window.addEventListener(c,e(function(){d()},a))}function d(){require(["esri/geometry/Point","esri/SpatialReference"],function(b,c){var d=i().split(","),e=a.spatialReference.wkid,f=null +4326==e?f=new b(d[1],d[0]):102100==e&&(f=new b(d[0],d[1],new c({wkid:e}))),a.centerAt(f)})}function e(a,b,c){var d +return function(){var e=this,f=arguments +clearTimeout(d),d=setTimeout(function(){d=null,c||a.apply(e,f)},b),c&&!d&&a.apply(e,f)}}function f(){a.on("pan-end",function(){var b=a.extent.getCenter() +h(b.x,b.y,a.spatialReference.wkid)})}function g(){a.on("zoom-end",function(){var b=a.extent.getCenter() +h(b.x,b.y,a.spatialReference.wkid),a.setZoom(a.getZoom())})}function h(a,b,c){localStorage.setItem("_centerPtX",a),localStorage.setItem("_centerPtY",b),localStorage.setItem("_spatialReference",c)}function i(){var a=null +try{a=localStorage.getItem("_centerPtX")+","+localStorage.getItem("_centerPtY")+","+localStorage.getItem("_spatialReference")}catch(b){}return a}this.init=function(){f(),g(),c(b) +var d=a.extent.getCenter() +h(d.x,d.y,a.spatialReference.wkid)}},O.esri.TPK.inflate=function(a){function b(){function a(a,b,c,d,j,k,l,n,p,r,s){var t,u,v,w,x,y,z,A,C,D,E,F,G,H,I +D=0,x=c +do e[a[b+D]]++,D++,x-- +while(0!==x) +if(e[0]==c)return l[0]=-1,n[0]=0,i +for(A=n[0],y=1;B>=y&&0===e[y];y++);for(z=y,y>A&&(A=y),x=B;0!==x&&0===e[x];x--);for(v=x,A>x&&(A=x),n[0]=A,H=1<y;y++,H<<=1)if((H-=e[y])<0)return m +if((H-=e[x])<0)return m +for(e[x]+=H,h[1]=y=0,D=1,G=2;0!==--x;)h[G]=y+=e[D],G++,D++ +x=0,D=0 +do 0!==(y=a[b+D])&&(s[h[y]++]=x),D++ +while(++x=z;z++)for(t=e[z];0!==t--;){for(;z>F+A;){if(w++,F+=A,I=v-F,I=I>A?A:I,(u=1<<(y=z-F))>t+1&&(u-=t+1,G=z,I>y))for(;++yq)return m +g[w]=E=r[0],r[0]+=I,0!==w?(h[w]=x,f[0]=y,f[1]=A,y=x>>>F-A,f[2]=E-g[w-1]-y,p.set(f,3*(g[w-1]+y))):l[0]=E}for(f[1]=z-F,D>=c?f[0]=192:s[D]>>F;I>y;y+=u)p.set(f,3*(E+y)) +for(y=1<>>=1)x^=y +for(x^=y,C=(1<b;b++)d[b]=0 +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(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++))<>=l[B+1],r-=l[B+1],0!==(16&o)){for(o&=15,y=l[B+2]+(q&p[o]),q>>=o,r-=o;15>r;)t--,q|=(255&h.read_byte(s++))<>=l[B+1],r-=l[B+1],0!==(16&o)){for(o&=15;o>r;)t--,q|=(255&h.read_byte(s++))<>=o,r-=o,v-=y,u>=z)A=u-z,u-A>0&&2>u-A?(g.window[u++]=g.window[A++],g.window[u++]=g.window[A++],y-=2):(g.window.set(g.window.subarray(A,A+2),u),u+=2,A+=2,y-=2) +else{A=u-z +do A+=g.end +while(0>A) +if(o=g.end-A,y>o){if(y-=o,u-A>0&&o>u-A){do g.window[u++]=g.window[A++] +while(0!==--o)}else g.window.set(g.window.subarray(A,A+o),u),u+=o,A+=o,o=0 +A=0}}if(u-A>0&&y>u-A){do g.window[u++]=g.window[A++] +while(0!==--y)}else g.window.set(g.window.subarray(A,A+y),u),u+=y,A+=y,y=0 +break}if(0!==(64&o))return h.msg="invalid distance code",y=h.avail_in-t,y=y>r>>3?r>>3:y,t+=y,s-=y,r-=y<<3,g.bitb=q,g.bitk=r,h.avail_in=t,h.total_in+=s-h.next_in_index,h.next_in_index=s,g.write=u,m +k+=l[B+2],k+=q&p[o],B=3*(n+k),o=l[B]}break}if(0!==(64&o))return 0!==(32&o)?(y=h.avail_in-t,y=y>r>>3?r>>3:y,t+=y,s-=y,r-=y<<3,g.bitb=q,g.bitk=r,h.avail_in=t,h.total_in+=s-h.next_in_index,h.next_in_index=s,g.write=u,j):(h.msg="invalid literal/length code",y=h.avail_in-t,y=y>r>>3?r>>3:y,t+=y,s-=y,r-=y<<3,g.bitb=q,g.bitk=r,h.avail_in=t,h.total_in+=s-h.next_in_index,h.next_in_index=s,g.write=u,m) +if(k+=l[B+2],k+=q&p[o],B=3*(n+k),0===(o=l[B])){q>>=l[B+1],r-=l[B+1],g.window[u++]=l[B+2],v-- +break}}else q>>=l[B+1],r-=l[B+1],g.window[u++]=l[B+2],v--}while(v>=258&&t>=10) +return y=h.avail_in-t,y=y>r>>3?r>>3:y,t+=y,s-=y,r-=y<<3,g.bitb=q,g.bitk=r,h.avail_in=t,h.total_in+=s-h.next_in_index,h.next_in_index=s,g.write=u,i}var b,c,d,e,f=this,g=0,h=0,k=0,n=0,o=0,q=0,r=0,s=0,t=0,u=0 +f.init=function(a,f,g,h,i,j){b=C,r=a,s=f,d=g,t=h,e=i,u=j,c=null},f.proc=function(f,v,w){var x,y,z,A,B,M,N,O=0,P=0,Q=0 +for(Q=v.next_in_index,A=v.avail_in,O=f.bitb,P=f.bitk,B=f.write,M=B=258&&A>=10&&(f.bitb=O,f.bitk=P,v.avail_in=A,v.total_in+=Q-v.next_in_index,v.next_in_index=Q,f.write=B,w=a(r,s,d,t,e,u,f,v),Q=v.next_in_index,A=v.avail_in,O=f.bitb,P=f.bitk,B=f.write,M=BP;){if(0===A)return f.bitb=O,f.bitk=P,v.avail_in=A,v.total_in+=Q-v.next_in_index,v.next_in_index=Q,f.write=B,f.inflate_flush(v,w) +w=i,A--,O|=(255&v.read_byte(Q++))<>>=c[y+1],P-=c[y+1],z=c[y],0===z){n=c[y+2],b=I +break}if(0!==(16&z)){o=15&z,g=c[y+2],b=E +break}if(0===(64&z)){k=z,h=y/3+c[y+2] +break}if(0!==(32&z)){b=J +break}return b=L,v.msg="invalid literal/length code",w=m,f.bitb=O,f.bitk=P,v.avail_in=A,v.total_in+=Q-v.next_in_index,v.next_in_index=Q,f.write=B,f.inflate_flush(v,w) +case E:for(x=o;x>P;){if(0===A)return f.bitb=O,f.bitk=P,v.avail_in=A,v.total_in+=Q-v.next_in_index,v.next_in_index=Q,f.write=B,f.inflate_flush(v,w) +w=i,A--,O|=(255&v.read_byte(Q++))<>=x,P-=x,k=s,c=e,h=u,b=F +case F:for(x=k;x>P;){if(0===A)return f.bitb=O,f.bitk=P,v.avail_in=A,v.total_in+=Q-v.next_in_index,v.next_in_index=Q,f.write=B,f.inflate_flush(v,w) +w=i,A--,O|=(255&v.read_byte(Q++))<>=c[y+1],P-=c[y+1],z=c[y],0!==(16&z)){o=15&z,q=c[y+2],b=G +break}if(0===(64&z)){k=z,h=y/3+c[y+2] +break}return b=L,v.msg="invalid distance code",w=m,f.bitb=O,f.bitk=P,v.avail_in=A,v.total_in+=Q-v.next_in_index,v.next_in_index=Q,f.write=B,f.inflate_flush(v,w) +case G:for(x=o;x>P;){if(0===A)return f.bitb=O,f.bitk=P,v.avail_in=A,v.total_in+=Q-v.next_in_index,v.next_in_index=Q,f.write=B,f.inflate_flush(v,w) +w=i,A--,O|=(255&v.read_byte(Q++))<>=x,P-=x,b=H +case H:for(N=B-q;0>N;)N+=f.end +for(;0!==g;){if(0===M&&(B==f.end&&0!==f.read&&(B=0,M=B7&&(P-=8,A++,Q--),f.write=B,w=f.inflate_flush(v,w),B=f.write,M=Ba.avail_out&&(c=a.avail_out),0!==c&&b==o&&(b=i),a.avail_out-=c,a.total_out+=c,a.next_out.set(f.window.subarray(e,e+c),d),d+=c,e+=c,e==f.end&&(e=0,f.write==f.end&&(f.write=0),c=f.write-e,c>a.avail_out&&(c=a.avail_out),0!==c&&b==o&&(b=i),a.avail_out-=c,a.total_out+=c,a.next_out.set(f.window.subarray(e,e+c),d),d+=c,e+=c),a.next_out_index=d,f.read=e,b},f.proc=function(a,c){var d,o,q,w,y,z,A,B +for(w=a.next_in_index,y=a.avail_in,o=f.bitb,q=f.bitk,z=f.write,A=zq;){if(0===y)return f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c) +c=i,y--,o|=(255&a.read_byte(w++))<>>1){case 0:o>>>=3,q-=3,d=7&q,o>>>=d,q-=d,g=O +break +case 1:var C=[],D=[],E=[[]],F=[[]] +b.inflate_trees_fixed(C,D,E,F),t.init(C[0],D[0],E[0],0,F[0],0),o>>>=3,q-=3,g=T +break +case 2:o>>>=3,q-=3,g=Q +break +case 3:return o>>>=3,q-=3,g=W,a.msg="invalid block type",c=m,f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c)}break +case O:for(;32>q;){if(0===y)return f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c) +c=i,y--,o|=(255&a.read_byte(w++))<>>16&65535)!=(65535&o))return g=W,a.msg="invalid stored block lengths",c=m,f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c) +h=65535&o,o=q=0,g=0!==h?P:0!==u?U:N +break +case P:if(0===y)return f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c) +if(0===A&&(z==f.end&&0!==f.read&&(z=0,A=zy&&(d=y),d>A&&(d=A),f.window.set(a.read_buf(w,d),z),w+=d,y-=d,z+=d,A-=d,0!==(h-=d))break +g=0!==u?U:N +break +case Q:for(;14>q;){if(0===y)return f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c) +c=i,y--,o|=(255&a.read_byte(w++))<29||(d>>5&31)>29)return g=W,a.msg="too many length or distance symbols",c=m,f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c) +if(d=258+(31&d)+(d>>5&31),!e||e.lengthB;B++)e[B]=0 +o>>>=14,q-=14,n=0,g=R +case R:for(;4+(k>>>10)>n;){for(;3>q;){if(0===y)return f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c) +c=i,y--,o|=(255&a.read_byte(w++))<>>=3,q-=3}for(;19>n;)e[M[n++]]=0 +if(r[0]=7,d=x.inflate_trees_bits(e,r,s,v,a),d!=i)return c=d,c==m&&(e=null,g=W),f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c) +n=0,g=S +case S:for(;;){if(d=k,!(258+(31&d)+(d>>5&31)>n))break +var G,H +for(d=r[0];d>q;){if(0===y)return f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c) +c=i,y--,o|=(255&a.read_byte(w++))<H)o>>>=d,q-=d,e[n++]=H +else{for(B=18==H?7:H-14,G=18==H?11:3;d+B>q;){if(0===y)return f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c) +c=i,y--,o|=(255&a.read_byte(w++))<>>=d,q-=d,G+=o&p[B],o>>>=B,q-=B,B=n,d=k,B+G>258+(31&d)+(d>>5&31)||16==H&&1>B)return e=null,g=W,a.msg="invalid bit length repeat",c=m,f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c) +H=16==H?e[B-1]:0 +do e[B++]=H +while(0!==--G) +n=B}}s[0]=-1 +var I=[],J=[],K=[],L=[] +if(I[0]=9,J[0]=6,d=k,d=x.inflate_trees_dynamic(257+(31&d),1+(d>>5&31),e,I,J,K,L,v,a),d!=i)return d==m&&(e=null,g=W),c=d,f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,f.inflate_flush(a,c) +t.init(I[0],J[0],v,K[0],v,L[0]),g=T +case T:if(f.bitb=o,f.bitk=q,a.avail_in=y,a.total_in+=w-a.next_in_index,a.next_in_index=w,f.write=z,(c=t.proc(f,a,c))!=j)return f.inflate_flush(a,c) +if(c=i,t.free(a),w=a.next_in_index,y=a.avail_in,o=f.bitb,q=f.bitk,z=f.write,A=ze||e>15?(b.inflateEnd(c),l):(b.wbits=e,c.istate.blocks=new d(c,1<>4)+8>a.istate.wbits){a.istate.mode=ga,a.msg="invalid window size",a.istate.marker=5 +break}a.istate.mode=$ +case $:if(0===a.avail_in)return c +if(c=b,a.avail_in--,a.total_in++,d=255&a.read_byte(a.next_in_index++),((a.istate.method<<8)+d)%31!==0){a.istate.mode=ga,a.msg="incorrect header check",a.istate.marker=5 +break}if(0===(d&X)){a.istate.mode=ea +break}a.istate.mode=_ +case _:if(0===a.avail_in)return c +c=b,a.avail_in--,a.total_in++,a.istate.need=(255&a.read_byte(a.next_in_index++))<<24&4278190080,a.istate.mode=aa +case aa:if(0===a.avail_in)return c +c=b,a.avail_in--,a.total_in++,a.istate.need+=(255&a.read_byte(a.next_in_index++))<<16&16711680,a.istate.mode=ba +case ba:if(0===a.avail_in)return c +c=b,a.avail_in--,a.total_in++,a.istate.need+=(255&a.read_byte(a.next_in_index++))<<8&65280,a.istate.mode=ca +case ca:return 0===a.avail_in?c:(c=b,a.avail_in--,a.total_in++,a.istate.need+=255&a.read_byte(a.next_in_index++),a.istate.mode=da,k) +case da:return a.istate.mode=ga,a.msg="need dictionary",a.istate.marker=0,l +case ea:if(c=a.istate.blocks.proc(a,c),c==m){a.istate.mode=ga,a.istate.marker=0 +break}if(c==i&&(c=b),c!=j)return c +c=b,a.istate.blocks.reset(a,a.istate.was),a.istate.mode=fa +case fa:return j +case ga:return m +default:return l}},b.inflateSetDictionary=function(a,b,c){var d=0,e=c +return a&&a.istate&&a.istate.mode==da?(e>=1<e;)b.read_byte(d)==ha[e]?e++:e=0!==b.read_byte(d)?0:4-e,d++,c-- +return b.total_in+=d-b.next_in_index,b.next_in_index=d,b.avail_in=c,b.istate.marker=e,4!=e?m:(f=b.total_in,g=b.total_out,a(b),b.total_in=f,b.total_out=g,b.istate.mode=ea,i)},b.inflateSyncPoint=function(a){return a&&a.istate&&a.istate.blocks?a.istate.blocks.sync_point():l}}function f(){}function g(){var a=this,b=new f,c=512,d=r,e=new Uint8Array(c),g=!1 +b.inflateInit(),b.next_out=e,a.append=function(a,f){var h,k,l=[],m=0,n=0,p=0 +if(0!==a.length){b.next_in_index=0,b.next_in=a,b.avail_in=a.length +do{if(b.next_out_index=0,b.avail_out=c,0!==b.avail_in||g||(b.next_in_index=0,g=!0),h=b.inflate(d),g&&h==o)return-1 +if(h!=i&&h!=j)throw"inflating: "+b.msg +if((g||h==j)&&b.avail_in==a.length)return-1 +b.next_out_index&&(b.next_out_index==c?l.push(new Uint8Array(e)):l.push(new Uint8Array(e.subarray(0,b.next_out_index)))),p+=b.next_out_index,f&&b.next_in_index>0&&b.next_in_index!=m&&(f(b.next_in_index),m=b.next_in_index)}while(b.avail_in>0||0===b.avail_out) +return k=new Uint8Array(p),l.forEach(function(a){k.set(a,n),n+=a.length}),k}},a.flush=function(){b.inflateEnd()}}var h=15,i=0,j=1,k=2,l=-2,m=-3,n=-4,o=-5,p=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535],q=1440,r=0,s=4,t=9,u=5,v=[96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,192,80,7,10,0,8,96,0,8,32,0,9,160,0,8,0,0,8,128,0,8,64,0,9,224,80,7,6,0,8,88,0,8,24,0,9,144,83,7,59,0,8,120,0,8,56,0,9,208,81,7,17,0,8,104,0,8,40,0,9,176,0,8,8,0,8,136,0,8,72,0,9,240,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,200,81,7,13,0,8,100,0,8,36,0,9,168,0,8,4,0,8,132,0,8,68,0,9,232,80,7,8,0,8,92,0,8,28,0,9,152,84,7,83,0,8,124,0,8,60,0,9,216,82,7,23,0,8,108,0,8,44,0,9,184,0,8,12,0,8,140,0,8,76,0,9,248,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,196,81,7,11,0,8,98,0,8,34,0,9,164,0,8,2,0,8,130,0,8,66,0,9,228,80,7,7,0,8,90,0,8,26,0,9,148,84,7,67,0,8,122,0,8,58,0,9,212,82,7,19,0,8,106,0,8,42,0,9,180,0,8,10,0,8,138,0,8,74,0,9,244,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,204,81,7,15,0,8,102,0,8,38,0,9,172,0,8,6,0,8,134,0,8,70,0,9,236,80,7,9,0,8,94,0,8,30,0,9,156,84,7,99,0,8,126,0,8,62,0,9,220,82,7,27,0,8,110,0,8,46,0,9,188,0,8,14,0,8,142,0,8,78,0,9,252,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,194,80,7,10,0,8,97,0,8,33,0,9,162,0,8,1,0,8,129,0,8,65,0,9,226,80,7,6,0,8,89,0,8,25,0,9,146,83,7,59,0,8,121,0,8,57,0,9,210,81,7,17,0,8,105,0,8,41,0,9,178,0,8,9,0,8,137,0,8,73,0,9,242,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,202,81,7,13,0,8,101,0,8,37,0,9,170,0,8,5,0,8,133,0,8,69,0,9,234,80,7,8,0,8,93,0,8,29,0,9,154,84,7,83,0,8,125,0,8,61,0,9,218,82,7,23,0,8,109,0,8,45,0,9,186,0,8,13,0,8,141,0,8,77,0,9,250,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,198,81,7,11,0,8,99,0,8,35,0,9,166,0,8,3,0,8,131,0,8,67,0,9,230,80,7,7,0,8,91,0,8,27,0,9,150,84,7,67,0,8,123,0,8,59,0,9,214,82,7,19,0,8,107,0,8,43,0,9,182,0,8,11,0,8,139,0,8,75,0,9,246,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,206,81,7,15,0,8,103,0,8,39,0,9,174,0,8,7,0,8,135,0,8,71,0,9,238,80,7,9,0,8,95,0,8,31,0,9,158,84,7,99,0,8,127,0,8,63,0,9,222,82,7,27,0,8,111,0,8,47,0,9,190,0,8,15,0,8,143,0,8,79,0,9,254,96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,193,80,7,10,0,8,96,0,8,32,0,9,161,0,8,0,0,8,128,0,8,64,0,9,225,80,7,6,0,8,88,0,8,24,0,9,145,83,7,59,0,8,120,0,8,56,0,9,209,81,7,17,0,8,104,0,8,40,0,9,177,0,8,8,0,8,136,0,8,72,0,9,241,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,201,81,7,13,0,8,100,0,8,36,0,9,169,0,8,4,0,8,132,0,8,68,0,9,233,80,7,8,0,8,92,0,8,28,0,9,153,84,7,83,0,8,124,0,8,60,0,9,217,82,7,23,0,8,108,0,8,44,0,9,185,0,8,12,0,8,140,0,8,76,0,9,249,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,197,81,7,11,0,8,98,0,8,34,0,9,165,0,8,2,0,8,130,0,8,66,0,9,229,80,7,7,0,8,90,0,8,26,0,9,149,84,7,67,0,8,122,0,8,58,0,9,213,82,7,19,0,8,106,0,8,42,0,9,181,0,8,10,0,8,138,0,8,74,0,9,245,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,205,81,7,15,0,8,102,0,8,38,0,9,173,0,8,6,0,8,134,0,8,70,0,9,237,80,7,9,0,8,94,0,8,30,0,9,157,84,7,99,0,8,126,0,8,62,0,9,221,82,7,27,0,8,110,0,8,46,0,9,189,0,8,14,0,8,142,0,8,78,0,9,253,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,195,80,7,10,0,8,97,0,8,33,0,9,163,0,8,1,0,8,129,0,8,65,0,9,227,80,7,6,0,8,89,0,8,25,0,9,147,83,7,59,0,8,121,0,8,57,0,9,211,81,7,17,0,8,105,0,8,41,0,9,179,0,8,9,0,8,137,0,8,73,0,9,243,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,203,81,7,13,0,8,101,0,8,37,0,9,171,0,8,5,0,8,133,0,8,69,0,9,235,80,7,8,0,8,93,0,8,29,0,9,155,84,7,83,0,8,125,0,8,61,0,9,219,82,7,23,0,8,109,0,8,45,0,9,187,0,8,13,0,8,141,0,8,77,0,9,251,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,199,81,7,11,0,8,99,0,8,35,0,9,167,0,8,3,0,8,131,0,8,67,0,9,231,80,7,7,0,8,91,0,8,27,0,9,151,84,7,67,0,8,123,0,8,59,0,9,215,82,7,19,0,8,107,0,8,43,0,9,183,0,8,11,0,8,139,0,8,75,0,9,247,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,207,81,7,15,0,8,103,0,8,39,0,9,175,0,8,7,0,8,135,0,8,71,0,9,239,80,7,9,0,8,95,0,8,31,0,9,159,84,7,99,0,8,127,0,8,63,0,9,223,82,7,27,0,8,111,0,8,47,0,9,191,0,8,15,0,8,143,0,8,79,0,9,255],w=[80,5,1,87,5,257,83,5,17,91,5,4097,81,5,5,89,5,1025,85,5,65,93,5,16385,80,5,3,88,5,513,84,5,33,92,5,8193,82,5,9,90,5,2049,86,5,129,192,5,24577,80,5,2,87,5,385,83,5,25,91,5,6145,81,5,7,89,5,1537,85,5,97,93,5,24577,80,5,4,88,5,769,84,5,49,92,5,12289,82,5,13,90,5,3073,86,5,193,192,5,24577],x=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],y=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,112,112],z=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],A=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],B=15 +b.inflate_trees_fixed=function(a,b,c,d){return a[0]=t,b[0]=u,c[0]=v,d[0]=w,i} +var C=0,D=1,E=2,F=3,G=4,H=5,I=6,J=7,K=8,L=9,M=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],N=0,O=1,P=2,Q=3,R=4,S=5,T=6,U=7,V=8,W=9,X=32,Y=8,Z=0,$=1,_=2,aa=3,ba=4,ca=5,da=6,ea=7,fa=12,ga=13,ha=[0,0,255,255] +f.prototype={inflateInit:function(a){var b=this +return b.istate=new e,a||(a=h),b.istate.inflateInit(b,a)},inflate:function(a){var b=this +return b.istate?b.istate.inflate(b,a):l},inflateEnd:function(){var a=this +if(!a.istate)return l +var b=a.istate.inflateEnd(a) +return a.istate=null,b},inflateSync:function(){var a=this +return a.istate?a.istate.inflateSync(a):l},inflateSetDictionary:function(a,b){var c=this +return c.istate?c.istate.inflateSetDictionary(c,a,b):l},read_byte:function(a){var b=this +return b.next_in.subarray(a,a+1)[0]},read_buf:function(a,b){var c=this +return c.next_in.subarray(a,a+b)}} +var ia +a.zip?a.zip.Inflater=g:(ia=new g,a.addEventListener("message",function(b){var c=b.data +c.append&&a.postMessage({onappend:!0,data:ia.append(c.data,function(b){a.postMessage({progress:!0,current:b})})}),c.flush&&(ia.flush(),a.postMessage({onflush:!0}))},!1))},O.esri.TPK.___test=O.esri.TPK.inflate.toString(),O.esri.TPK.___blobURL=URL.createObjectURL(new Blob(["(",O.esri.TPK.___test,")(this)"],{type:"application/javascript"})),O.esri.zip.workerScriptsPath=O.esri.TPK.___blobURL,O.esri.TPK.X2JS=function(a){"use strict" +function b(){void 0===a.escapeMode&&(a.escapeMode=!0),a.attributePrefix=a.attributePrefix||"_",a.arrayAccessForm=a.arrayAccessForm||"none",a.emptyNodeForm=a.emptyNodeForm||"text",void 0===a.enableToStringFunc&&(a.enableToStringFunc=!0),a.arrayAccessFormPaths=a.arrayAccessFormPaths||[],void 0===a.skipEmptyTextNodesForObj&&(a.skipEmptyTextNodesForObj=!0),void 0===a.stripWhitespaces&&(a.stripWhitespaces=!0),a.datetimeAccessFormPaths=a.datetimeAccessFormPaths||[]}function c(){function a(a){var b=String(a) +return 1===b.length&&(b="0"+b),b}"function"!=typeof String.prototype.trim&&(String.prototype.trim=function(){return this.replace(/^\s+|^\n+|(\s|\n)+$/g,"")}),"function"!=typeof Date.prototype.toISOString&&(Date.prototype.toISOString=function(){return this.getUTCFullYear()+"-"+a(this.getUTCMonth()+1)+"-"+a(this.getUTCDate())+"T"+a(this.getUTCHours())+":"+a(this.getUTCMinutes())+":"+a(this.getUTCSeconds())+"."+String((this.getUTCMilliseconds()/1e3).toFixed(3)).slice(2,5)+"Z"})}function d(a){var b=a.localName +return null==b&&(b=a.baseName),(null==b||""==b)&&(b=a.nodeName),b}function e(a){return a.prefix}function f(a){return"string"==typeof a?a.replace(/&/g,"&").replace(//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()))) +return c}function j(b,c,d){if(a.datetimeAccessFormPaths.length>0){for(var e=d.split(".#")[0],f=0;f1&&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 +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) +return c}function r(b){var c="" +return null!=b.__cdata&&(c+=""),null!=b.__text&&(c+=a.escapeMode?f(b.__text):b.__text),c}function s(b){var c="" +return b instanceof Object?c+=r(b):null!=b&&(c+=a.escapeMode?f(b):b),c}function t(a,b,c){var d="" +if(0==a.length)d+=l(a,b,c,!0) +else for(var e=0;e0)for(var d in a)if(!o(a,d)){var e=a[d],f=q(e) +if(null==e||void 0==e)b+=l(e,d,f,!0) +else if(e instanceof Object)if(e instanceof Array)b+=t(e,d,f) +else if(e instanceof Date)b+=l(e,d,f,!1),b+=e.toISOString(),b+=m(e,d) +else{var g=p(e) +g>0||null!=e.__text||null!=e.__cdata?(b+=l(e,d,f,!1),b+=u(e),b+=m(e,d)):b+=l(e,d,f,!0)}else b+=l(e,d,f,!1),b+=s(e),b+=m(e,d)}return b+=s(a)}var v="1.1.5",a=a||{} +b(),c() +var w={ELEMENT_NODE:1,TEXT_NODE:3,CDATA_SECTION_NODE:4,COMMENT_NODE:8,DOCUMENT_NODE:9} +this.parseXmlString=function(a){var b=window.ActiveXObject||"ActiveXObject"in window +if(void 0===a)return null +var c +if(window.DOMParser){var d=new window.DOMParser,e=null +if(!b)try{e=d.parseFromString("INVALID","text/xml").childNodes[0].namespaceURI}catch(f){e=null}try{c=d.parseFromString(a,"text/xml"),null!=e&&c.getElementsByTagNameNS(e,"parsererror").length>0&&(c=null)}catch(f){c=null}}else 0==a.indexOf("")+2)),c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(a) +return c},this.asArray=function(a){return a instanceof Array?a:[a]},this.toXmlDateTime=function(a){return a instanceof Date?a.toISOString():"number"==typeof a?new Date(a).toISOString():null},this.asDateTime=function(a){return"string"==typeof a?i(a):a},this.xml2json=function(a){return k(a)},this.xml_str2json=function(a){var b=this.parseXmlString(a) +return null!=b?this.xml2json(b):null},this.json2xml_str=function(a){return u(a)},this.json2xml=function(a){var b=this.json2xml_str(a) +return this.parseXmlString(b)},this.getVersion=function(){return v}} diff --git a/dist/offline-tpk-src.js b/dist/offline-tpk-src.js index 14a139f6..199cf2bc 100644 --- a/dist/offline-tpk-src.js +++ b/dist/offline-tpk-src.js @@ -1,4 +1,4 @@ -/*! esri-offline-maps - v2.16.0 - 2015-10-29 +/*! esri-offline-maps - v3.0.0 - 2015-11-23 * Copyright (c) 2015 Environmental Systems Research Institute, Inc. * Apache License*/ /** @@ -147,7 +147,7 @@ define([ // sets img visibility to 'hidden', so we need to show the image back once we have put the data:image img.style.visibility = "visible"; img.src = imgURL; - console.log("URL length " + imgURL.length + ", image: " + imgURL); + //console.log("URL length " + imgURL.length + ", image: " + imgURL); this.emit(this.PROGRESS_EVENT,this.PROGRESS_END); return ""; /* this result goes nowhere, seriously */ @@ -204,7 +204,7 @@ define([ }, /** - * Reads a tile into tile database. Works with offlineTilesEnabler.js and OfflineTilesEnablerLayer.js + * Reads a tile into tile database. Works with OfflineTilesBasic.js and OfflineTilesAdvanced.js * saveToFile() functionality. * IMPORTANT! The tile must confirm to an object using the pattern shown in _storeTile(). * @param file diff --git a/package.json b/package.json index c78b67a1..60349aae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "esri-offline-maps", - "version": "2.16.0", + "version": "3.0.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",