mirror of
https://github.com/Esri/offline-editor-js.git
synced 2025-12-15 15:20:05 +00:00
simple edit library initial commit
This commit is contained in:
parent
53c931d546
commit
4bc476a57a
1003
lib/edit/OfflineFeatureLayer.js
Normal file
1003
lib/edit/OfflineFeatureLayer.js
Normal file
File diff suppressed because it is too large
Load Diff
420
lib/edit/editStorePOLS.js
Normal file
420
lib/edit/editStorePOLS.js
Normal file
@ -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);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
13
lib/edit/offlineJSOptions.js
Normal file
13
lib/edit/offlineJSOptions.js
Normal file
@ -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'
|
||||
}
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user