This commit is contained in:
Andy Gup 2015-04-27 18:27:59 -06:00
parent bd2b14cc45
commit e9d09e3b9a
27 changed files with 2257 additions and 752 deletions

View File

@ -1,5 +1,29 @@
# offline-editor-js - Changelog
## Version 2.7
This release focused on improving the handling of attachments. Has breaking changes.
** Enhancements **
* Added a new sample attachments-editor-secure.html to demonstrate the pattern for working with secure feature services and attachments.
* Closes #286 - support for secure services (HTTPS) when working with attachments
* Closes #305 - Support both ADD and UPDATE attachment.
* Closes #306 - removes createObjectURL functionality from attachmentsStore. This allows for attachments to be used in full offline scenarios.
* Closes #318 - added OfflineFeaturesManager.ATTACHMENTS_DB_NAME and ATTACHMENTS_DB_OBJECSTORE_NAME.
* Closes #321 - switch offlineFeaturesManager unit test to a different feature service that's attachments enabled.
* Closes #322 - rewrite offlineAttachmentsSpec.
* Closes #324 - attachmentsStore._readFile() can indicate false positives.
* Closes #325 - support DELETE an existing attachment.
* Closes #328 - add layer.resestAttachmentsDatabase().
* Closes #329 - add layer.getAttachmentsUsage().
** Breaking Changes **
* attachmentsStore.DB_NAME has been renamed to attachmentsStore.dbName to be consistent with editStore.
* attachmentsStore.OBJECTSTORE_NAME has been renamed to attachmentsStore.objectStoreName to be consistent with editStore.
* Added use of the browser's [FormData() API](https://developer.mozilla.org/en-US/docs/Web/API/FormData) along with `FormData.append`. This may cause
attachment support to break in certain older browsers. This is a courtesy heads-up because as a rule this library only
supports the latest version of Chrome, Firefox and Safari.
## Version 2.6.1 - April 13, 2015
Patch release. Recommended update. No breaking changes.

View File

@ -13,7 +13,7 @@ module.exports = function(grunt) {
'lib/tpk/*.js'
],
tasks: ['concat', 'uglify'],
tasks: ['jshint','concat', 'uglify'],
options: {
spawn: false
}
@ -120,8 +120,8 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.registerTask('build',['concat','uglify']);
grunt.registerTask('build',['jshint','concat','uglify']);
grunt.registerTask('test',['jshint']);
grunt.registerTask('buildAll',['jshint','concat','uglify']);
//grunt.registerTask('buildAll',['jshint','concat','uglify']);
};

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
/*! offline-editor-js - v2.6.1 - 2015-04-13
/*! offline-editor-js - v2.7.0 - 2015-04-27
* Copyright (c) 2015 Environmental Systems Research Institute, Inc.
* Apache License*/
/*jshint -W030 */
@ -38,6 +38,13 @@ define([
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.
// 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
@ -66,6 +73,8 @@ define([
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);
@ -81,8 +90,8 @@ define([
/**
* Overrides a feature layer. Call this AFTER the FeatureLayer's 'update-end' event.
* IMPORTANT: If options are specified they will be saved to the database. Any complex
* objects such as [esri.Graphic] will need to be serialized or you will get an error.
* 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
@ -115,6 +124,7 @@ define([
layer._addAttachment = layer.addAttachment;
layer._queryAttachmentInfos = layer.queryAttachmentInfos;
layer._deleteAttachments = layer.deleteAttachments;
layer._updateAttachment = layer.updateAttachment;
/*
operations supported offline:
@ -166,6 +176,7 @@ define([
};
layer.addAttachment = function (objectId, formNode, callback, errback) {
if (self.getOnlineStatus() === self.ONLINE) {
return this._addAttachment(objectId, formNode,
function () {
@ -181,7 +192,7 @@ define([
}
if (!self.attachmentsStore) {
console.log("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 offlineFeaturesManager");
return;
}
@ -190,7 +201,7 @@ define([
var deferred = new Deferred();
var attachmentId = this._getNextTempId();
self.attachmentsStore.store(this.url, attachmentId, objectId, file, function (success, newAttachment) {
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);
@ -212,6 +223,51 @@ define([
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 deferred = new Deferred();
self.attachmentsStore.store(this.url, attachmentId, objectId, file, self.attachmentsStore.TYPE.UPDATE, 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,
@ -232,22 +288,42 @@ define([
// case 1.- it is a new attachment
// case 2.- it is an already existing attachment
// only case 1 is supported right now
// asynchronously delete each of the attachments
var promises = [];
attachmentsIds.forEach(function (attachmentId) {
attachmentId = parseInt(attachmentId, 10); // to number
console.assert(attachmentId < 0, "we only support deleting local attachments");
var deferred = new Deferred();
self.attachmentsStore.delete(attachmentId, function (success) {
var result = {objectId: objectId, attachmentId: attachmentId, success: success};
deferred.resolve(result);
});
// 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);
@ -543,7 +619,30 @@ define([
};
/**
* Returns the approximate size of the database in bytes
* 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){
@ -553,7 +652,7 @@ define([
};
/**
* Full database reset.
* 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.
@ -954,11 +1053,12 @@ define([
this._onlineStatus = this.RECONNECTING;
this._replayStoredEdits(function (success, responses) {
var result = {features: {success: success, responses: responses}};
this._onlineStatus = this.ONLINE;
if (this.attachmentsStore != null) {
console.log("sending attachments");
this._sendStoredAttachments(function (success, responses) {
this._sendStoredAttachments(function (success, uploadedResponses, dbResponses) {
this._onlineStatus = this.ONLINE;
result.attachments = {success: success, responses: responses};
result.attachments = {success: success, responses: uploadedResponses, dbResponses: dbResponses};
callback && callback(result);
}.bind(this));
}
@ -1021,10 +1121,10 @@ define([
// Added @ v2.5
//
// Configure database for offline restart
// Options object allows you to store data that you'll
// dataStore object allows you to store data that you'll
// use after an offline browser restart.
//
// If options Object is not defined then do nothing.
// If dataStore Object is not defined then do nothing.
//
////////////////////////////////////////////////////
@ -1172,83 +1272,171 @@ define([
_uploadAttachment: function (attachment) {
var dfd = new Deferred();
var segments = [];
segments.push(this._fieldSegment("f", "json"));
segments.push(this._fileSegment("attachment", attachment.name, attachment.contentType, attachment.content));
var layer = this._featureLayers[attachment.featureLayerUrl];
var oAjaxReq = new XMLHttpRequest();
var formData = new FormData();
formData.append("attachment",attachment.file);
// surprisingly, sometimes the oAjaxReq object doesn't have the sendAsBinary() method, even if we added it to the XMLHttpRequest.prototype
if (!oAjaxReq.sendAsBinary) {
this._extendAjaxReq(oAjaxReq);
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;
}
oAjaxReq.onload = function (result) {
dfd.resolve(JSON.parse(result.target.response));
};
oAjaxReq.onerror = function (err) {
dfd.reject(err);
};
// IMPORTANT!
// Proxy path can be set to null if feature service is CORS enabled
// Refer to "Using the Proxy Page" for more information: https://developers.arcgis.com/en/javascript/jshelp/ags_proxy.html
var proxy = this.proxyPath || esriConfig.defaults.io.proxyUrl || "";
if (proxy !== "") {
proxy += "?";
}
console.log("proxy:", proxy);
oAjaxReq.open("post", proxy + attachment.featureId + "/addAttachment", true);
var sBoundary = "---------------------------" + Date.now().toString(16);
oAjaxReq.setRequestHeader("Content-Type", "multipart\/form-data; boundary=" + sBoundary);
oAjaxReq.sendAsBinary("--" + sBoundary + "\r\n" + segments.join("--" + sBoundary + "\r\n") + "--" + sBoundary + "--\r\n");
return dfd;
return dfd.promise;
},
_deleteAttachment: function (attachmentId, uploadResult) {
_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(uploadResult);
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 deleteCompleted =
this._uploadAttachment(attachment)
.then(function (uploadResult) {
if (uploadResult.addAttachmentResult && uploadResult.addAttachmentResult.success === true) {
console.log("upload success", uploadResult.addAttachmentResult.success);
return this._deleteAttachment(attachment.id, uploadResult);
}
else {
console.log("upload failed", uploadResult);
return null;
}
}.bind(this),
function (err) {
console.log("failed uploading attachment", attachment);
}
);
promises.push(deleteCompleted);
var uploadAttachmentComplete =
this._uploadAttachment(attachment);
//.then(function (uploadResult) {
// if (uploadResult.addAttachmentResult && uploadResult.addAttachmentResult.success === true) {
// console.log("upload success", uploadResult.addAttachmentResult.success);
// return this._deleteAttachment(attachment.id, uploadResult);
// }
// else {
// console.log("upload failed", uploadResult);
// return null;
// }
//}.bind(this),
//function (err) {
// console.log("failed uploading attachment", attachment);
// return null;
//}
//);
promises.push(uploadAttachmentComplete);
}, this);
console.log("promises", promises.length);
var allPromises = all(promises);
allPromises.then(function (results) {
console.log(results);
callback && callback(true, results);
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);
}
});
//results.forEach(function(value){
// if(value.attachmentResult.success){
// // Delete an attachment from the database if it was successfully
// // submitted to the server.
// self._deleteAttachmentFromDB(value.id,null).then(function(result){
// if(result.success){
// callback && callback(true, results);
// }
// else{
// callback && callback(false, results);
// }
// });
// }
//});
},
function (err) {
console.log("error!", err);
@ -1296,6 +1484,10 @@ define([
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");
}
else if(layer.hasAttachments === false){
console.error("WARNING: Layer " + layer.id + "doesn't seem to accept attachments. Recheck the layer permissions.");
callback(false,"WARNING: Attachments not supported in layer: " + layer.id);
}
// Assign the attachmentsStore to the layer as a private var so we can access it from
// the promises applyEdits() method.
@ -2638,8 +2830,14 @@ O.esri.Edit.AttachmentsStore = function () {
this._db = null;
var DB_NAME = "attachments_store";
var OBJECT_STORE_NAME = "attachments";
this.dbName = "attachments_store";
this.objectStoreName = "attachments";
this.TYPE = {
"ADD" : "add",
"UPDATE" : "update",
"DELETE" : "delete"
};
this.isSupported = function () {
if (!window.indexedDB) {
@ -2648,40 +2846,70 @@ O.esri.Edit.AttachmentsStore = function () {
return true;
};
this.store = function (featureLayerUrl, attachmentId, objectId, attachmentFile, callback) {
/**
* 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 {
// first of all, read file content
this._readFile(attachmentFile, function (fileContent) {
// now, store it in the db
var newAttachment =
{
id: attachmentId,
objectId: objectId,
featureId: featureLayerUrl + "/" + objectId,
contentType: attachmentFile.type,
name: attachmentFile.name,
size: attachmentFile.size,
url: this._createLocalURL(attachmentFile),
content: fileContent
};
// Avoid allowing the wrong type to be stored
if(type == this.TYPE.ADD || type == this.TYPE.UPDATE || type == this.TYPE.DELETE) {
var transaction = this._db.transaction([OBJECT_STORE_NAME], "readwrite");
// first of all, read file content
this._readFile(attachmentFile, function (success, fileContent) {
transaction.oncomplete = function (event) {
callback(true, newAttachment);
};
if (success) {
// now, store it in the db
var newAttachment =
{
id: attachmentId,
objectId: objectId,
type: type,
transaction.onerror = function (event) {
callback(false, event.target.error.message);
};
// 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 objectStore = transaction.objectStore(OBJECT_STORE_NAME);
var request = objectStore.put(newAttachment);
request.onsuccess = function (event) {
//console.log("item added to db " + event.target.result);
};
var transaction = this._db.transaction([this.objectStoreName], "readwrite");
}.bind(this));
transaction.oncomplete = function (event) {
callback(true, newAttachment);
};
transaction.onerror = function (event) {
callback(false, event.target.error.message);
};
var objectStore = transaction.objectStore(this.objectStoreName);
var request = objectStore.put(newAttachment);
request.onsuccess = function (event) {
//console.log("item added to db " + event.target.result);
};
}
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);
@ -2692,7 +2920,7 @@ O.esri.Edit.AttachmentsStore = function () {
this.retrieve = function (attachmentId, callback) {
console.assert(this._db !== null, "indexeddb not initialized");
var objectStore = this._db.transaction([OBJECT_STORE_NAME]).objectStore(OBJECT_STORE_NAME);
var objectStore = this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName);
var request = objectStore.get(attachmentId);
request.onsuccess = function (event) {
var result = event.target.result;
@ -2715,7 +2943,7 @@ O.esri.Edit.AttachmentsStore = function () {
var featureId = featureLayerUrl + "/" + objectId;
var attachments = [];
var objectStore = this._db.transaction([OBJECT_STORE_NAME]).objectStore(OBJECT_STORE_NAME);
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) {
@ -2735,9 +2963,9 @@ O.esri.Edit.AttachmentsStore = function () {
var attachments = [];
var objectStore = this._db.transaction([OBJECT_STORE_NAME]).objectStore(OBJECT_STORE_NAME);
var index = objectStore.index("featureId");
var keyRange = IDBKeyRange.bound(featureLayerUrl + "/", featureLayerUrl + "/A");
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) {
@ -2755,7 +2983,7 @@ O.esri.Edit.AttachmentsStore = function () {
var attachments = [];
var objectStore = this._db.transaction([OBJECT_STORE_NAME]).objectStore(OBJECT_STORE_NAME);
var objectStore = this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName);
objectStore.openCursor().onsuccess = function (evt) {
var cursor = evt.target.result;
if (cursor) {
@ -2773,15 +3001,15 @@ O.esri.Edit.AttachmentsStore = function () {
var featureId = featureLayerUrl + "/" + objectId;
var objectStore = this._db.transaction([OBJECT_STORE_NAME], "readwrite").objectStore(OBJECT_STORE_NAME);
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);
//var attachment = cursor.value;
//this._revokeLocalURL(attachment);
objectStore.delete(cursor.primaryKey);
deletedCount++;
cursor.continue();
@ -2805,10 +3033,10 @@ O.esri.Edit.AttachmentsStore = function () {
return;
}
this._revokeLocalURL(attachment);
//this._revokeLocalURL(attachment);
var request = this._db.transaction([OBJECT_STORE_NAME], "readwrite")
.objectStore(OBJECT_STORE_NAME)
var request = this._db.transaction([this.objectStoreName], "readwrite")
.objectStore(this.objectStoreName)
.delete(attachmentId);
request.onsuccess = function (event) {
setTimeout(function () {
@ -2825,12 +3053,12 @@ O.esri.Edit.AttachmentsStore = function () {
console.assert(this._db !== null, "indexeddb not initialized");
this.getAllAttachments(function (attachments) {
attachments.forEach(function (attachment) {
this._revokeLocalURL(attachment);
}, this);
//attachments.forEach(function (attachment) {
// this._revokeLocalURL(attachment);
//}, this);
var request = this._db.transaction([OBJECT_STORE_NAME], "readwrite")
.objectStore(OBJECT_STORE_NAME)
var request = this._db.transaction([this.objectStoreName], "readwrite")
.objectStore(this.objectStoreName)
.clear();
request.onsuccess = function (event) {
setTimeout(function () {
@ -2848,7 +3076,7 @@ O.esri.Edit.AttachmentsStore = function () {
var featureId = featureLayerUrl + "/" + oldId;
var objectStore = this._db.transaction([OBJECT_STORE_NAME], "readwrite").objectStore(OBJECT_STORE_NAME);
var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName);
var index = objectStore.index("featureId");
var keyRange = IDBKeyRange.only(featureId);
var replacedCount = 0;
@ -2877,8 +3105,8 @@ O.esri.Edit.AttachmentsStore = function () {
var usage = {sizeBytes: 0, attachmentCount: 0};
var transaction = this._db.transaction([OBJECT_STORE_NAME])
.objectStore(OBJECT_STORE_NAME)
var transaction = this._db.transaction([this.objectStoreName])
.objectStore(this.objectStoreName)
.openCursor();
console.log("dumping keys");
@ -2902,28 +3130,55 @@ O.esri.Edit.AttachmentsStore = function () {
};
};
/**
* 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(evt.target.result);
callback(true,evt.target.result);
};
reader.onerror = function (evt) {
callback(false,evt.target.result);
};
reader.readAsBinaryString(attachmentFile);
};
this._createLocalURL = function (attachmentFile) {
return window.URL.createObjectURL(attachmentFile);
};
// Deprecated @ v2.7
//this._createLocalURL = function (attachmentFile) {
// return window.URL.createObjectURL(attachmentFile);
//};
this._revokeLocalURL = function (attachment) {
window.URL.revokeObjectURL(attachment.url);
};
//this._revokeLocalURL = function (attachment) {
// window.URL.revokeObjectURL(attachment.url);
//};
this.init = function (callback) {
console.log("init AttachmentStore");
var request = indexedDB.open(DB_NAME, 11);
var request = indexedDB.open(this.dbName, 12);
callback = callback || function (success) {
console.log("AttachmentsStore::init() success:", success);
}.bind(this);
@ -2936,12 +3191,13 @@ O.esri.Edit.AttachmentsStore = function () {
request.onupgradeneeded = function (event) {
var db = event.target.result;
if (db.objectStoreNames.contains(OBJECT_STORE_NAME)) {
db.deleteObjectStore(OBJECT_STORE_NAME);
if (db.objectStoreNames.contains(this.objectStoreName)) {
db.deleteObjectStore(this.objectStoreName);
}
var objectStore = db.createObjectStore(OBJECT_STORE_NAME, {keyPath: "id"});
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) {

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
/*! offline-editor-js - v2.6.1 - 2015-04-13
/*! offline-editor-js - v2.7.0 - 2015-04-27
* Copyright (c) 2015 Environmental Systems Research Institute, Inc.
* Apache License*/
define([

View File

@ -1,4 +1,4 @@
/*! offline-editor-js - v2.6.1 - 2015-04-13
/*! offline-editor-js - v2.7.0 - 2015-04-27
* 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){c._tilesCore=new O.esri.Tiles.TilesCore,c._lastTileUrl="",c._imageType="",c._minZoom=null,c._maxZoom=null,c._getTileUrl=c.getTileUrl;var f=!0;return"undefined"!=typeof e&&(f=e),c.offline={online:f,store:new O.esri.Tiles.TilesStore,proxyPath:null},c.offline.store.isSupported()?(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),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()+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<<b)-1,d=[],e=0,f=a.length*b;f>e;e+=b)d[e>>5]|=(a.charCodeAt(e/b)&c)<<e%32;return d},O.esri.Tiles.Base64Utils.wordToString=function(a){for(var b=8,c=(1<<b)-1,d=[],e=0,f=32*a.length;f>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++)d.push(8*e+6*h>32*a.length?b:c.charAt(g>>6*(3-h)&63));return d.join("")},/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */

View File

@ -1,4 +1,4 @@
/*! offline-editor-js - v2.6.1 - 2015-04-13
/*! offline-editor-js - v2.7.0 - 2015-04-27
* Copyright (c) 2015 Environmental Systems Research Institute, Inc.
* Apache License*/
define([

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
/*! offline-editor-js - v2.6.1 - 2015-04-13
/*! offline-editor-js - v2.7.0 - 2015-04-27
* Copyright (c) 2015 Environmental Systems Research Institute, Inc.
* Apache License*/
/**

View File

@ -5,36 +5,61 @@ The __offline-edit-min.js__ has support for attachments in offline mode. See [at
While your application is in `OFFLINE` mode, you can:
* add attachments to any feature, either a feature that already exists in the server or a newly added feature.
* remove attachments from features. It only works for attachments that have been added while offline.
* delete attachments from features if you have pre-cached the attachments or if you have added a feature while offline you can delete it from the local database.
* query for attachments of a particular feature. It will only return attachments that have been added while offline.
* view the attached files (see __limitations__ below)
* when the app goes to `ONLINE` mode, all attachments are sent back to the server and removed from local browser storage
* when the app goes to `ONLINE` mode, all attachments are sent back to the server and removed from the local database.
##How you do that:
You can either use the ArcGIS FeatureLayer API _(esri.layers.FeatureLayer)_ directly or use the built-in [AttachmentEditor](https://developers.arcgis.com/javascript/jsapi/attachmenteditor-amd.html) widget that support feature attachment editing. Both approaches work well, and the code you write works the same either if you are on `ONLINE` or `OFFLINE` modes.
##How you do use it:
You can either use the ArcGIS FeatureLayer API _(esri.layers.FeatureLayer)_ directly or use the [AttachmentEditor](https://developers.arcgis.com/javascript/jsapi/attachmenteditor-amd.html) widget that supports feature attachment editing. Both approaches work well, and the code you write works the same either if you are on `ONLINE` or `OFFLINE` modes.
The only differences in your code are:
* create an offlineFeaturesManager enabled for attachment support:
* create an offlineFeaturesManager enabled for attachment support. Make sure you initialize the attachments database:
var offlineFeaturesManager = new esri.OfflineFeaturesManager();
offlineFeaturesManager.initAttachments();
* extend your featureLayers with offline editing functionality:
offlineFeaturesManager.extend(featureLayer, function(success)
offlineFeaturesManager.extend(featureLayer, function(success, error)
{
console.log("layer extended", success? "success" : "failed");
});
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";
offlineFeaturesManager.initAttachments();
###Using the FeatureLayer API
The FeatureLayer API for handling attachments consists primarily of three methods:
The FeatureLayer API for handling attachments consists primarily of four methods. In general you should let `OfflineFeaturesManager`
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)
* `layer.addAttachment(objectId, formNode, callback, errback)` [doc](https://developers.arcgis.com/javascript/jsapi/featurelayer.html#addattachment)
* `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 browser storage (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 `offlineFeaturesManager.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:
layer.getAttachmentsUsage(function(usage, error) {
console.log("Size: " + usage.sizeBytes + ", attachmentCount: " + usage.attachmentCount);
});
##Resetting the database
Under certain circumstances you may want to force the database to delete everything.
layer.resetAttachmentsDatabase(function(result, error) {
console.log("Reset succes: " + result); // result is a boolean
});
###Using the AttachmentEditor widget
The [AttachmentEditor](https://developers.arcgis.com/javascript/jsapi/attachmenteditor-amd.html) is not very fancy, but it's easy to work with:
@ -59,5 +84,5 @@ The widget internally uses the FeatureLayer API, and it works well in OFFLINE mo
##Limitations
Attachment support in OFFLINE mode has some limitations:
* while in OFFLINE mode, features in a featureLayer don't know whether they have any attachments in the server or any other information about attachments. Therefore queryAttachmentInfos() and deleteAttachments() can't take those attachments into account. Calling queryAttachmentInfos() will only return attachments that are stored in local storage and deleteAttachments() can only remove local attachments.
* in order to see local attachments, the library uses [window.URL.createObjectURL() API](https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL). This API generates an opaque URL that represents the content of the File passed as parameter. This allows the user of the app to see and download attachments that are still in local browser storage as if they were actual files accessible through a URL. However, the lifetime of this URL is tied to the page where it is created. This means that if the user reloads (while still offline) the page after adding some local attachments, then these URLs will be invalid.
* while in OFFLINE mode, features in a featureLayer don't know whether they have any attachments in the server or any other
information about attachments unless you specifically build out that functionality. Therefore queryAttachmentInfos() and deleteAttachments() can't take those attachments into account. Calling queryAttachmentInfos() will only return attachments that are stored in local storage and deleteAttachments() can only remove local attachments.

View File

@ -154,7 +154,7 @@ Force the library to return to an online condition. If there are pending edits,
```js
function goOnline()
{
offlineFeaturesManager.goOnline(function(success,errors)
offlineFeaturesManager.goOnline(function(success,results)
{
if(success){
//Modify user inteface depending on success/failure
@ -163,6 +163,25 @@ Force the library to return to an online condition. If there are pending edits,
}
```
It's important to note that the `results` object contains all the necessary information about successes and failures that may have occurred during the online resync process. Here is a description of what's inside. The `features.responses` object contains information on features sync. The `attachments.uploadResponses` contain information on attachments sync. And, the `attachments.dbResponses` contains information on whether or not any attachment that was successfully sync'd was deleted from the local database.
```js
resultsObject = {
features:{
success : boolean,
responses : responses
},
attachments:{
success : boolean,
uploadResponses : uploadResponses,
dbResponses : dbResponses
}
}
```
####offlineFeaturesManager.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.

View File

@ -13,9 +13,11 @@ Constructor | Description
###Properties
Property | Value | Description
--- | --- | ---
`DB_NAME` | "features_store" | **New @ v2.5** Sets the database name. You can instantiate multiple databases within the same application by creating seperate instances of OfflineFeaturesManager.
`DB_OBJECTSTORE_NAME` | "features" | **New @ v2.5** Represents an object store that allows access to a set of data in the database.
`DB_UID` | "objectid" | **New @ v2.5 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`.
`DB_NAME` | "features_store" | Sets the database name. You can instantiate multiple databases within the same application by creating seperate instances of OfflineFeaturesManager.
`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" | **New @ v2.7** Sets the attachments database name.
`ATTACHMENTS_DB_OBJECTSTORE_NAME` | "attachments" | **New @ v2.7** Sets the attachments database object store name.
`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.
`attachmentsStore` | null | Default is `null`. If you are using attachments, this property gives you access to the associated database.
@ -36,9 +38,9 @@ OfflineFeaturesManager provides the following functionality.
Methods | Returns | Description
--- | --- | ---
`extend(layer,callback,dataStore)`|`callback( boolean, errors )`| **Updated @ v2.5** Overrides a feature layer, by replacing the `applyEdits()` method of the layer. You can use the FeatureLayer as always, but it's behaviour will be enhanced according to the online status of the manager and the capabilities included in this library. `Callback` is related to initialization the library. `dataStore` is an optional Object that contains any information you need when reconsistuting the layer after an offline browser restart. Refer to the [How to use the edit library doc](howtouseeditlibrary.md) for addition information.
`extend(layer,callback,dataStore)`|`callback( boolean, errors )`| Overrides a feature layer, by replacing the `applyEdits()` method of the layer. You can use the FeatureLayer as always, but it's behaviour will be enhanced according to the online status of the manager and the capabilities included in this library. `Callback` is related to initialization the library. <br><br>`dataStore` is an optional Object that contains any information you need when reconsistuting the layer after an offline browser restart. Refer to the [How to use the edit library doc](howtouseeditlibrary.md) for addition information.
`goOffline()` | nothing | Forces library into an offline state. Any edits applied to extended FeatureLayers during this condition will be stored locally.
`goOnline(callback)` | `callback( boolean, errors )` | 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.
`goOnline(callback)` | `callback( boolean, results )` | 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. <br><br>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.
`getReadableEdit()` | String | **DEPRECATED** @ v2.5. A string value representing human readable information on pending edits. Use `featureLayer.getAllEditsArray()`.
@ -58,10 +60,10 @@ Application code can subscribe to offlineFeaturesManager events to be notified o
Event | Value | Returns | Description
--- | --- | --- | ---
`events.EDITS_SENT` | "edits-sent" | nothing | **Updated @ v2.5** When any edit is actually sent to the server while online-only.
`events.EDITS_SENT_ERROR` | "edits-sent-error" | {msg:error} | **New @ v2.5** There was a problem while sending errors to the server.
`events.EDITS_SENT` | "edits-sent" | nothing | When any edit is actually sent to the server while online-only.
`events.EDITS_SENT_ERROR` | "edits-sent-error" | {msg:error} | There was a problem while sending errors to the server.
`events.EDITS_ENQUEUED` | "edits-enqueued" | nothing | When an edit is enqueued and not sent to the server.
`events.EDITS_ENQUEUED_ERROR` | "edits-enqueued-error" | {msg:error} | **New @ v2.5** An error occurred while trying to store the edit. In your app it is recommended to verify if the edit is in the database or not.
`events.EDITS_ENQUEUED_ERROR` | "edits-enqueued-error" | {msg:error} | An error occurred while trying to store the edit. In your app it is recommended to verify if the edit is in the database or not.
`events.ALL_EDITS_SENT` | "all-edits-sent" | {[addResults] ,[updateResults], [deleteResults]} | After going online and there are no pending edits remaining in the queue. Be sure to also check for `EDITS_SENT_ERROR`.
`events.ATTACHMENT_ENQUEUED` | "attachment-enqueued" | nothing | An attachment is in the queue to be sent to the server.
`events.ATTACHMENTS_SENT` | "attachments-sent" | nothing | When any attachment is actually sent to the server.
@ -86,19 +88,24 @@ A FeatureLayer that has been extended using OfflineFeaturesManager.extend() will
Methods | Returns | Description
--- | --- | ---
`applyEdits(` `adds, updates, deletes,` `callback, errback)` | `deferred` | applyEdits() method is replaced by this library. It's behaviour depends upon online state of the manager. You need to pass the same arguments as to the original applyEdits() method and it returns a deferred object, that will be resolved in the same way as the original, as well as the callbacks will be called under the same conditions. This method looks the same as the original to calling code, the only difference is internal. Listen for `EDITS_ENQUEUED` or `EDITS_ENQUEUED_ERROR`.
`addAttachment( objectId, formNode,` `callback,errback)` | `deferred` | Adds a single attachment.
`updateAttachment( objectId, attachmentId,` `formNode, callback, errback)` | `deferred` | **New @ v2.7** Updates an existing attachment.
`deleteAttachments( objectId, attachmentsIds,` `callback, errback)`| `deferred` | Deletes existing attachments as well as attachments that were created while offline.
`getAttachmentsUsage(callback)` | `callback(usageObject,error)` | **New @ v2.7** Returns the approximate size of the attachments database. The usage Object is {sizeBytes: number, attachmentCount: number}.
`resetAttachmentsDatabase( callback)` | `callback(boolean, error)` | **New @ v2.7** Resets the entire attachments database -- use with **caution**.
`convertGraphicLayerToJSON(` `features, updateEndEvent, callback)` | `callback( featureJSON, layerDefJSON)` | Not really needed @ v2.5 when you can store the entire feature layer's JSON using the `dataStore` property in the `OfflineFeatureManager` contructor. Used with offline browser restarts. In order to reconstitute the feature layer and map you'll need to store the featureJSON and layerDefJSON in local storage and then it read back upon an offline restart. The `updateEndEvent` is the Feature Layer's `update-end` event object. The appcache-features.html sample demonstrates this pattern.
`getFeatureDefinition(` `featureLayer, featuresArr` `geometryType, callback)` | Object | Used with offline browser restarts. Not really needed @ v2.5 when you can store the entire feature layer's JSON using the `dataStore` property in the `OfflineFeatureManager` contructor. Pass it a FeatureLayer instance, an array of features and specify the Esri geometry type. It will return a FeatureLayer Definition object that can be used to reconstitute a Feature Layer from scratch. The appcache-features.html sample demonstrates this pattern. Go here for more info on the ArcGIS REST API [layerDefinition](http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#//02r30000004v000000), and [Layer](http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#/Layer/02r30000004q000000/).
`getFeatureDefinition(` `featureLayer, featuresArr` `geometryType, callback)` | `Object` | Used with offline browser restarts. Not really needed @ v2.5 when you can store the entire feature layer's JSON using the `dataStore` property in the `OfflineFeatureManager` contructor. Pass it a FeatureLayer instance, an array of features and specify the Esri geometry type. It will return a FeatureLayer Definition object that can be used to reconstitute a Feature Layer from scratch. The appcache-features.html sample demonstrates this pattern. Go here for more info on the ArcGIS REST API [layerDefinition](http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#//02r30000004v000000), and [Layer](http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#/Layer/02r30000004q000000/).
`setPhantomLayerGraphics( graphicsArray) ` | nothing | Used with offline browser restarts. Adds the graphics in the `graphicsArray` to the internal phantom graphics layer. This layer is designed to indicate to the user any graphic that has been modified while offline. The appcache-features.html sample demonstrates this pattern.
`getPhantomLayerGraphics( callback) ` | `callback( graphicsLayerJSON)` | Used with offline browser restarts. Returns a JSON representation of the internal phantom graphics layer. This layer is designed to indicate to the user any graphic that has been modified while offline. The appcache-features.html sample demonstrates this pattern.
`resetDatabase(callback)` | `callback( boolean, error)` | **New @ v2.5** Full database reset -- use with **caution**. If some edits weren't successfully sent, then the record will still exist in the database. If you use this function then those pending records will also be deleted.
`pendingEditsCount(callback)` | `callback( int )` | **New @ v2.5** Returns the number of pending edits in the database.
`getUsage(callback)`| `callback({usage}, error)` | **New @ v2.5** Returns the approximate size of the database in bytes. the usage Object is {sizeBytes: number, editCount: number}.
`getPhantomGraphicsArray( callback)` | `callback(boolean, array)` | **New @ v2.5** Used with offline browser restarts. Returns an array of phantom graphics from the database.
`getAllEditsArray(callback)` | `callback(boolean, array)` | **New @ v2.5** Returns an array of all edits stored in the database. Each item in array is an object that contains: {"id":"internalID", "operation":"add, update, delete","layer":"layerURL","type":"esriGeometryType","graphic":"esri.Graphic JSON"}
`getFeatureLayerJSON(url,callback)` | `callback( boolean, JSON )` | **New @ v2.5.** Helper function that retrieves the feature layer's JSON using `f=json` parameter.
`setFeatureLayerJSONDataStore( jsonObject, callback)` | `callback( boolean, error)` | **New @ v2.5** 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)` | **New @ v2.5** Retrieves the optional feature layer storage object. This data store object is used for full offline browser restarts.
`convertFeatureGraphicsToJSON(` `[features],callback)` | `callback( jsonString )` | **New @ v2.5.** Helper function that converts an array of feature layer graphics to a JSON string.
`resetDatabase(callback)` | `callback( boolean, error)` | Full edits database reset -- use with **caution**. If some edits weren't successfully sent, then the record will still exist in the database. If you use this function then those pending records will also be deleted.
`pendingEditsCount(callback)` | `callback( int )` | Returns the number of pending edits in the database.
`getUsage(callback)`| `callback({usage}, error)` | Returns the approximate size of the edits database in bytes. The usage Object is {sizeBytes: number, editCount: number}.
`getPhantomGraphicsArray( callback)` | `callback(boolean, array)` | Used with offline browser restarts. Returns an array of phantom graphics from the database.
`getAllEditsArray(callback)` | `callback(boolean, array)` | Returns an array of all edits stored in the database. Each item in array is an object that contains: {"id":"internalID", "operation":"add, update, delete","layer":"layerURL","type":"esriGeometryType","graphic":"esri.Graphic JSON"}
`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
@ -123,29 +130,29 @@ Property | Value | Description
Property | Value | Description
--- | --- | ---
`dbName` | "features_store" | **New @ v2.5.** Defines the database name. You can have multiple databases within the same application.
`objectStoreName` | "features" | **New @ v2.5.** Represents an object store that allows access to a set of data in the IndexedDB database, looked up via primary key.
`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)` | **Updated @ v2.5.** 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 )` | **Updated @ v2.5.** The total number of edits that are queued in the database.
`getAllEditsArray( callback)` | `callback()` | **New @ v2.5.** Returns all edits in an iterable array.
`getFeatureLayerJSON( callback)` | `callback( boolean, Object)` | **New @ v2.5.** Returns the feature layer JSON object.
`deleteFeatureLayerJSON( callback)` | `callback( boolean, {message:String)` | **New @ v2.5.** Delete the feature layer JSON object from the database.
`pushFeatureLayerJSON( dataObject, callback)` | `callback( boolean, error)` | **New @ v2.5.** 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)` | **New @ v2.5.** Returns the approximate size of the database in bytes.
`hasPendingEdits()` | boolean | **Deprecated @ v2.5.** Determines if there are any queued edits in the local cache. Use `pendingEditsCount()` instead.
`retrieveEditsQueue()` | Array | **Deprecated @ v2.5.** Returns an array of all pending edits.
`getEditsStoreSizeBytes()` | Number | **Deprecated @ v2.5.** Returns the total size of all pending edits in bytes. Use `getUsage()` instead.
`getLocalStorageSizeBytes()` | Number | **Deprecated @ v2.5.** Returns the total size in bytes of all items for local storage cached using the current domain name. Use `getUsage()` instead.
`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.
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
@ -156,8 +163,9 @@ Constructor | Description
Property | Value | Description
--- | --- | ---
`DB_NAME` | "attachments_store" | Represents a FeatureLayer.add() operation.
`OBJECT_STORE_NAME` | "attachments" | Represents a FeatureLayer.update() operation.
`dbName` | "attachments_store" | **Updated @ v2.7** Represents a FeatureLayer.add() operation.
`objectStoreName` | "attachments" | **Updated @ v2.7** Represents a FeatureLayer.update() operation.
`TYPE` | "ADD", "UPDATE" or "DELETE" | **New @ v2.7** Specifies the type of operation against an attachment.
###Public Methods
Methods | Returns | Description

View File

@ -5,8 +5,14 @@ O.esri.Edit.AttachmentsStore = function () {
this._db = null;
var DB_NAME = "attachments_store";
var OBJECT_STORE_NAME = "attachments";
this.dbName = "attachments_store";
this.objectStoreName = "attachments";
this.TYPE = {
"ADD" : "add",
"UPDATE" : "update",
"DELETE" : "delete"
};
this.isSupported = function () {
if (!window.indexedDB) {
@ -15,40 +21,70 @@ O.esri.Edit.AttachmentsStore = function () {
return true;
};
this.store = function (featureLayerUrl, attachmentId, objectId, attachmentFile, callback) {
/**
* 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 {
// first of all, read file content
this._readFile(attachmentFile, function (fileContent) {
// now, store it in the db
var newAttachment =
{
id: attachmentId,
objectId: objectId,
featureId: featureLayerUrl + "/" + objectId,
contentType: attachmentFile.type,
name: attachmentFile.name,
size: attachmentFile.size,
url: this._createLocalURL(attachmentFile),
content: fileContent
};
// Avoid allowing the wrong type to be stored
if(type == this.TYPE.ADD || type == this.TYPE.UPDATE || type == this.TYPE.DELETE) {
var transaction = this._db.transaction([OBJECT_STORE_NAME], "readwrite");
// first of all, read file content
this._readFile(attachmentFile, function (success, fileContent) {
transaction.oncomplete = function (event) {
callback(true, newAttachment);
};
if (success) {
// now, store it in the db
var newAttachment =
{
id: attachmentId,
objectId: objectId,
type: type,
transaction.onerror = function (event) {
callback(false, event.target.error.message);
};
// 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 objectStore = transaction.objectStore(OBJECT_STORE_NAME);
var request = objectStore.put(newAttachment);
request.onsuccess = function (event) {
//console.log("item added to db " + event.target.result);
};
var transaction = this._db.transaction([this.objectStoreName], "readwrite");
}.bind(this));
transaction.oncomplete = function (event) {
callback(true, newAttachment);
};
transaction.onerror = function (event) {
callback(false, event.target.error.message);
};
var objectStore = transaction.objectStore(this.objectStoreName);
var request = objectStore.put(newAttachment);
request.onsuccess = function (event) {
//console.log("item added to db " + event.target.result);
};
}
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);
@ -59,7 +95,7 @@ O.esri.Edit.AttachmentsStore = function () {
this.retrieve = function (attachmentId, callback) {
console.assert(this._db !== null, "indexeddb not initialized");
var objectStore = this._db.transaction([OBJECT_STORE_NAME]).objectStore(OBJECT_STORE_NAME);
var objectStore = this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName);
var request = objectStore.get(attachmentId);
request.onsuccess = function (event) {
var result = event.target.result;
@ -82,7 +118,7 @@ O.esri.Edit.AttachmentsStore = function () {
var featureId = featureLayerUrl + "/" + objectId;
var attachments = [];
var objectStore = this._db.transaction([OBJECT_STORE_NAME]).objectStore(OBJECT_STORE_NAME);
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) {
@ -102,9 +138,9 @@ O.esri.Edit.AttachmentsStore = function () {
var attachments = [];
var objectStore = this._db.transaction([OBJECT_STORE_NAME]).objectStore(OBJECT_STORE_NAME);
var index = objectStore.index("featureId");
var keyRange = IDBKeyRange.bound(featureLayerUrl + "/", featureLayerUrl + "/A");
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) {
@ -122,7 +158,7 @@ O.esri.Edit.AttachmentsStore = function () {
var attachments = [];
var objectStore = this._db.transaction([OBJECT_STORE_NAME]).objectStore(OBJECT_STORE_NAME);
var objectStore = this._db.transaction([this.objectStoreName]).objectStore(this.objectStoreName);
objectStore.openCursor().onsuccess = function (evt) {
var cursor = evt.target.result;
if (cursor) {
@ -140,15 +176,15 @@ O.esri.Edit.AttachmentsStore = function () {
var featureId = featureLayerUrl + "/" + objectId;
var objectStore = this._db.transaction([OBJECT_STORE_NAME], "readwrite").objectStore(OBJECT_STORE_NAME);
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);
//var attachment = cursor.value;
//this._revokeLocalURL(attachment);
objectStore.delete(cursor.primaryKey);
deletedCount++;
cursor.continue();
@ -172,10 +208,10 @@ O.esri.Edit.AttachmentsStore = function () {
return;
}
this._revokeLocalURL(attachment);
//this._revokeLocalURL(attachment);
var request = this._db.transaction([OBJECT_STORE_NAME], "readwrite")
.objectStore(OBJECT_STORE_NAME)
var request = this._db.transaction([this.objectStoreName], "readwrite")
.objectStore(this.objectStoreName)
.delete(attachmentId);
request.onsuccess = function (event) {
setTimeout(function () {
@ -192,12 +228,12 @@ O.esri.Edit.AttachmentsStore = function () {
console.assert(this._db !== null, "indexeddb not initialized");
this.getAllAttachments(function (attachments) {
attachments.forEach(function (attachment) {
this._revokeLocalURL(attachment);
}, this);
//attachments.forEach(function (attachment) {
// this._revokeLocalURL(attachment);
//}, this);
var request = this._db.transaction([OBJECT_STORE_NAME], "readwrite")
.objectStore(OBJECT_STORE_NAME)
var request = this._db.transaction([this.objectStoreName], "readwrite")
.objectStore(this.objectStoreName)
.clear();
request.onsuccess = function (event) {
setTimeout(function () {
@ -215,7 +251,7 @@ O.esri.Edit.AttachmentsStore = function () {
var featureId = featureLayerUrl + "/" + oldId;
var objectStore = this._db.transaction([OBJECT_STORE_NAME], "readwrite").objectStore(OBJECT_STORE_NAME);
var objectStore = this._db.transaction([this.objectStoreName], "readwrite").objectStore(this.objectStoreName);
var index = objectStore.index("featureId");
var keyRange = IDBKeyRange.only(featureId);
var replacedCount = 0;
@ -244,8 +280,8 @@ O.esri.Edit.AttachmentsStore = function () {
var usage = {sizeBytes: 0, attachmentCount: 0};
var transaction = this._db.transaction([OBJECT_STORE_NAME])
.objectStore(OBJECT_STORE_NAME)
var transaction = this._db.transaction([this.objectStoreName])
.objectStore(this.objectStoreName)
.openCursor();
console.log("dumping keys");
@ -269,28 +305,55 @@ O.esri.Edit.AttachmentsStore = function () {
};
};
/**
* 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(evt.target.result);
callback(true,evt.target.result);
};
reader.onerror = function (evt) {
callback(false,evt.target.result);
};
reader.readAsBinaryString(attachmentFile);
};
this._createLocalURL = function (attachmentFile) {
return window.URL.createObjectURL(attachmentFile);
};
// Deprecated @ v2.7
//this._createLocalURL = function (attachmentFile) {
// return window.URL.createObjectURL(attachmentFile);
//};
this._revokeLocalURL = function (attachment) {
window.URL.revokeObjectURL(attachment.url);
};
//this._revokeLocalURL = function (attachment) {
// window.URL.revokeObjectURL(attachment.url);
//};
this.init = function (callback) {
console.log("init AttachmentStore");
var request = indexedDB.open(DB_NAME, 11);
var request = indexedDB.open(this.dbName, 12);
callback = callback || function (success) {
console.log("AttachmentsStore::init() success:", success);
}.bind(this);
@ -303,12 +366,13 @@ O.esri.Edit.AttachmentsStore = function () {
request.onupgradeneeded = function (event) {
var db = event.target.result;
if (db.objectStoreNames.contains(OBJECT_STORE_NAME)) {
db.deleteObjectStore(OBJECT_STORE_NAME);
if (db.objectStoreNames.contains(this.objectStoreName)) {
db.deleteObjectStore(this.objectStoreName);
}
var objectStore = db.createObjectStore(OBJECT_STORE_NAME, {keyPath: "id"});
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) {

View File

@ -35,6 +35,13 @@ define([
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.
// 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
@ -63,6 +70,8 @@ define([
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);
@ -78,8 +87,8 @@ define([
/**
* Overrides a feature layer. Call this AFTER the FeatureLayer's 'update-end' event.
* IMPORTANT: If options are specified they will be saved to the database. Any complex
* objects such as [esri.Graphic] will need to be serialized or you will get an error.
* 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
@ -112,6 +121,7 @@ define([
layer._addAttachment = layer.addAttachment;
layer._queryAttachmentInfos = layer.queryAttachmentInfos;
layer._deleteAttachments = layer.deleteAttachments;
layer._updateAttachment = layer.updateAttachment;
/*
operations supported offline:
@ -163,6 +173,7 @@ define([
};
layer.addAttachment = function (objectId, formNode, callback, errback) {
if (self.getOnlineStatus() === self.ONLINE) {
return this._addAttachment(objectId, formNode,
function () {
@ -178,7 +189,7 @@ define([
}
if (!self.attachmentsStore) {
console.log("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 offlineFeaturesManager");
return;
}
@ -187,7 +198,7 @@ define([
var deferred = new Deferred();
var attachmentId = this._getNextTempId();
self.attachmentsStore.store(this.url, attachmentId, objectId, file, function (success, newAttachment) {
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);
@ -209,6 +220,51 @@ define([
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 deferred = new Deferred();
self.attachmentsStore.store(this.url, attachmentId, objectId, file, self.attachmentsStore.TYPE.UPDATE, 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,
@ -229,22 +285,42 @@ define([
// case 1.- it is a new attachment
// case 2.- it is an already existing attachment
// only case 1 is supported right now
// asynchronously delete each of the attachments
var promises = [];
attachmentsIds.forEach(function (attachmentId) {
attachmentId = parseInt(attachmentId, 10); // to number
console.assert(attachmentId < 0, "we only support deleting local attachments");
var deferred = new Deferred();
self.attachmentsStore.delete(attachmentId, function (success) {
var result = {objectId: objectId, attachmentId: attachmentId, success: success};
deferred.resolve(result);
});
// 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);
@ -540,7 +616,30 @@ define([
};
/**
* Returns the approximate size of the database in bytes
* 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){
@ -550,7 +649,7 @@ define([
};
/**
* Full database reset.
* 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.
@ -951,11 +1050,12 @@ define([
this._onlineStatus = this.RECONNECTING;
this._replayStoredEdits(function (success, responses) {
var result = {features: {success: success, responses: responses}};
this._onlineStatus = this.ONLINE;
if (this.attachmentsStore != null) {
console.log("sending attachments");
this._sendStoredAttachments(function (success, responses) {
this._sendStoredAttachments(function (success, uploadedResponses, dbResponses) {
this._onlineStatus = this.ONLINE;
result.attachments = {success: success, responses: responses};
result.attachments = {success: success, responses: uploadedResponses, dbResponses: dbResponses};
callback && callback(result);
}.bind(this));
}
@ -1018,10 +1118,10 @@ define([
// Added @ v2.5
//
// Configure database for offline restart
// Options object allows you to store data that you'll
// dataStore object allows you to store data that you'll
// use after an offline browser restart.
//
// If options Object is not defined then do nothing.
// If dataStore Object is not defined then do nothing.
//
////////////////////////////////////////////////////
@ -1169,83 +1269,171 @@ define([
_uploadAttachment: function (attachment) {
var dfd = new Deferred();
var segments = [];
segments.push(this._fieldSegment("f", "json"));
segments.push(this._fileSegment("attachment", attachment.name, attachment.contentType, attachment.content));
var layer = this._featureLayers[attachment.featureLayerUrl];
var oAjaxReq = new XMLHttpRequest();
var formData = new FormData();
formData.append("attachment",attachment.file);
// surprisingly, sometimes the oAjaxReq object doesn't have the sendAsBinary() method, even if we added it to the XMLHttpRequest.prototype
if (!oAjaxReq.sendAsBinary) {
this._extendAjaxReq(oAjaxReq);
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;
}
oAjaxReq.onload = function (result) {
dfd.resolve(JSON.parse(result.target.response));
};
oAjaxReq.onerror = function (err) {
dfd.reject(err);
};
// IMPORTANT!
// Proxy path can be set to null if feature service is CORS enabled
// Refer to "Using the Proxy Page" for more information: https://developers.arcgis.com/en/javascript/jshelp/ags_proxy.html
var proxy = this.proxyPath || esriConfig.defaults.io.proxyUrl || "";
if (proxy !== "") {
proxy += "?";
}
console.log("proxy:", proxy);
oAjaxReq.open("post", proxy + attachment.featureId + "/addAttachment", true);
var sBoundary = "---------------------------" + Date.now().toString(16);
oAjaxReq.setRequestHeader("Content-Type", "multipart\/form-data; boundary=" + sBoundary);
oAjaxReq.sendAsBinary("--" + sBoundary + "\r\n" + segments.join("--" + sBoundary + "\r\n") + "--" + sBoundary + "--\r\n");
return dfd;
return dfd.promise;
},
_deleteAttachment: function (attachmentId, uploadResult) {
_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(uploadResult);
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 deleteCompleted =
this._uploadAttachment(attachment)
.then(function (uploadResult) {
if (uploadResult.addAttachmentResult && uploadResult.addAttachmentResult.success === true) {
console.log("upload success", uploadResult.addAttachmentResult.success);
return this._deleteAttachment(attachment.id, uploadResult);
}
else {
console.log("upload failed", uploadResult);
return null;
}
}.bind(this),
function (err) {
console.log("failed uploading attachment", attachment);
}
);
promises.push(deleteCompleted);
var uploadAttachmentComplete =
this._uploadAttachment(attachment);
//.then(function (uploadResult) {
// if (uploadResult.addAttachmentResult && uploadResult.addAttachmentResult.success === true) {
// console.log("upload success", uploadResult.addAttachmentResult.success);
// return this._deleteAttachment(attachment.id, uploadResult);
// }
// else {
// console.log("upload failed", uploadResult);
// return null;
// }
//}.bind(this),
//function (err) {
// console.log("failed uploading attachment", attachment);
// return null;
//}
//);
promises.push(uploadAttachmentComplete);
}, this);
console.log("promises", promises.length);
var allPromises = all(promises);
allPromises.then(function (results) {
console.log(results);
callback && callback(true, results);
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);
}
});
//results.forEach(function(value){
// if(value.attachmentResult.success){
// // Delete an attachment from the database if it was successfully
// // submitted to the server.
// self._deleteAttachmentFromDB(value.id,null).then(function(result){
// if(result.success){
// callback && callback(true, results);
// }
// else{
// callback && callback(false, results);
// }
// });
// }
//});
},
function (err) {
console.log("error!", err);
@ -1293,6 +1481,10 @@ define([
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");
}
else if(layer.hasAttachments === false){
console.error("WARNING: Layer " + layer.id + "doesn't seem to accept attachments. Recheck the layer permissions.");
callback(false,"WARNING: Attachments not supported in layer: " + layer.id);
}
// Assign the attachmentsStore to the layer as a private var so we can access it from
// the promises applyEdits() method.

View File

@ -1,6 +1,6 @@
{
"name": "offline-editor-js",
"version": "2.6.1",
"version": "2.7.0",
"description": "Lightweight set of libraries for working offline with map tiles and ArcGIS feature services",
"author": "Andy Gup <agup@esri.com> (http://blog.andygup.net)",
"license": "Apache 2",

View File

@ -0,0 +1,365 @@
<!DOCTYPE html>
<html>
<head>
<!--
This example is essentially the same as attachments-editor.html however
it's configured to work with a secure feature service.
NOTE: You will need your own secure feature service with attachments enabled.
If you are using a feature service hosted on ArcGIS Online you may not need a proxy.
If you are hosting the feature service on a non-CORS enabled server you will need a proxy.
-->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<!--The viewport meta tag is used to improve the presentation and behavior of the samples
on iOS devices-->
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
<title>Secure Attachments</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" >
<link rel="stylesheet" href="//js.arcgis.com/3.13/dijit/themes/claro/claro.css">
<link rel="stylesheet" href="http://js.arcgis.com/3.13/esri/css/esri.css">
<style>
html, body { height: 100%; width: 100%; margin: 0; overflow: hidden; font-family: Arial; }
#map { height: 100%; padding: 0;}
#footer { height: 2em; text-align: center; font-size: 1.1em; padding: 0.5em; }
.dj_ie .infowindow .window .top .right .user .content { position: relative; }
.dj_ie .simpleInfoWindow .content {position: relative;}
#connectivity-indicator,#storage-info
{
text-align: center;
border: 1px solid black;
padding: 4px;
}
#connectivity-indicator { color: white; background-color: #aaa; font-size: 16px; }
#connectivity-indicator.online { background-color: #0C0; }
#connectivity-indicator.offline { background-color: #E00; }
#connectivity-indicator.reconnecting { background-color: orange; }
#local-attachments-list {
border: 1px solid black;
overflow: scroll;
height: 400px;
margin: 0px;
list-style: none;
padding: 4px;
font-size: 12px;
}
#local-attachments-list li {
border-bottom: 1px solid #ddd;
}
.action {
color: blue;
cursor: pointer;
text-decoration: underline;
z-index: 999;
}
</style>
<script src="http://js.arcgis.com/3.13/"></script>
<script src="../vendor/offline/offline.min.js"></script>
<script>
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>
"use strict";
var map;
require([
"esri/map",
"esri/layers/FeatureLayer",
"esri/dijit/editing/AttachmentEditor",
"esri/config",
"esri/arcgis/OAuthInfo", "esri/IdentityManager",
"dojo/parser", "dojo/dom", "dojo/dom-class",
"dojo/dom-style", "dojo/dom-attr",
"dojo/dom-construct",
"dojo/on",
"dijit/layout/BorderContainer", "dijit/layout/ContentPane",
"../dist/offline-edit-src.js",
"dojo/domReady!"
], function(
Map,FeatureLayer,AttachmentEditor,esriConfig,OAuthInfo, esriId,
parser,dom,domClass,domStyle,domAttr,domConstruct,on
)
{
parser.parse();
var featureLayer,
attachmentsInited = false;
// 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.OfflineFeaturesManager();
// 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;
offlineFeaturesManager.initAttachments(function(success)
{
attachmentsInited = success;
updateStatus();
});
configUI();
map = new Map("map", {
basemap: "streets",
center: [-104.99,39.74],
zoom: 13
});
map.on("load", mapLoaded);
map.on("add-attachment-complete", updateStatus);
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);
function mapLoaded()
{
featureLayer = new FeatureLayer("Enter your own secure feature service here that has attachments enabled.",{
mode: FeatureLayer.MODE_SNAPSHOT
});
map.infoWindow.setContent("<div id='content' style='width:100%'></div>");
map.infoWindow.resize(350,200);
var attachmentEditor = new AttachmentEditor({}, dom.byId("content"));
attachmentEditor.startup();
featureLayer.on("click", function(evt)
{
var event = evt;
var objectId = evt.graphic.attributes[featureLayer.objectIdField];
map.infoWindow.setTitle(objectId);
attachmentEditor.showAttachments(event.graphic,featureLayer);
map.infoWindow.show(evt.screenPoint, map.getInfoWindowAnchor(evt.screenPoint));
});
map.on('layer-add-result', initEditor);
map.addLayer(featureLayer);
}
function configUI()
{
on(dom.byId('storage-info'), 'click', updateStorageInfo);
on(dom.byId('clear-local-attachments-btn'),'click', clearLocalAttachments);
on(dom.byId('go-offline-btn'),'click', goOffline);
on(dom.byId('go-online-btn'),'click', goOnline);
on(dom.byId('refresh-feature-layers-btn'),'click', refreshFeatureLayer);
updateConnectivityIndicator();
updateStatus();
Offline.check();
Offline.on('up', goOnline );
Offline.on('down', goOffline );
}
function initEditor(evt)
{
if(evt.layer.hasOwnProperty("type") && evt.layer.type == "Feature Layer")
{
try
{
var layer = evt.layer;
offlineFeaturesManager.extend(featureLayer,function(success, error){
if(success == false) alert("There was a problem initiating the database. " + error);
});
}
catch(err)
{
console.log("initEditor: " + err.toString());
}
}
}
function goOnline()
{
offlineFeaturesManager.goOnline(function()
{
updateConnectivityIndicator();
updateStatus();
});
updateConnectivityIndicator();
}
function goOffline()
{
offlineFeaturesManager.goOffline();
updateConnectivityIndicator();
}
function clearLocalAttachments()
{
offlineFeaturesManager.attachmentsStore.deleteAll(function(success)
{
updateStatus();
});
}
function refreshFeatureLayer()
{
featureLayer.refresh();
}
function updateStatus()
{
updateLocalAttachmentsList();
updateStorageInfo();
}
function updateLocalAttachmentsList()
{
if(!attachmentsInited)
return;
domConstruct.empty('local-attachments-list');
offlineFeaturesManager.attachmentsStore.getAllAttachments(function(attachments)
{
var li;
if( attachments.length )
{
attachments.forEach(function(attachment)
{
var readableAttachment =
"<a target='_blank' href='" + attachment.url + "'>" + attachment.name + "</a>, " +
"feature: <a target='_blank' href='" + attachment.featureId + "'>" + attachment.objectId + "</a>";
li = "<li>" + readableAttachment + "</li>";
domConstruct.place(li, 'local-attachments-list','last');
},this);
}
else
{
li = "<li>No local attachments</li>";
domConstruct.place(li, 'local-attachments-list','last');
}
});
}
function updateConnectivityIndicator()
{
var node = dom.byId('connectivity-indicator');
domClass.remove(node, "online offline reconnecting");
switch( offlineFeaturesManager.getOnlineStatus() )
{
case offlineFeaturesManager.OFFLINE:
node.innerHTML = "<i class='fa fa-chain-broken'></i> offline";
domClass.add(node, "offline");
break;
case offlineFeaturesManager.ONLINE:
node.innerHTML = "<i class='fa fa-link'></i> online";
domClass.add(node, "online");
break;
case offlineFeaturesManager.RECONNECTING:
node.innerHTML = "<i class='fa fa-cog fa-spin'></i> reconnecting";
domClass.add(node, "reconnecting");
break;
}
}
function formatSize(sizeInBytes)
{
if( sizeInBytes < 1024 )
{
return sizeInBytes + " bytes";
}
else if( sizeInBytes < 1024 * 1024 )
{
return (Math.floor(sizeInBytes / 1024 * 10) / 10) + " Kb";
}
return (Math.floor(sizeInBytes / 1024 / 1024 * 10) / 10) + " Mb";
}
function updateStorageInfo()
{
if(!attachmentsInited)
return;
offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
var str = "attachment" + ((usage.attachmentCount === 1)? "" : "s");
var info = usage.attachmentCount + " " + str + " (" + formatSize( usage.sizeBytes) + ")";
dom.byId('storage-info').innerHTML = info;
});
}
});
</script>
</head>
<body class="claro">
<div data-dojo-type="dijit/layout/BorderContainer"
data-dojo-props="design:'headline'"
style="width:100%;height:100%;">
<div data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region:'left'" style="width: 200px;overflow:hidden;">
<div id="anonymousPanel" style="display: none; padding: 5px; text-align: center;">
<span id="sign-in" class="action">Sign In</span> and view your ArcGIS Online items.
</div>
<div id="personalizedPanel" style="display: none; padding: 5px; text-align: center;">
Welcome <span id="userId" style="font-weight: bold;"></span>
-
<span id="sign-out" class="action">Sign Out</span>
</div>
<div id='connectivity-pane' data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region:'top'">
<div id='connectivity-indicator' >unknown</div>
</div>
<div id='storage-info-pane' data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region:'top'">
<div id='storage-info'>Storage used: 0 MBs</div>
</div>
<div id='local-attachments-list-pane' data-dojo-type="dijit/layout/ContentPane" data-dojo-propos="region:'top'">
<button style="width:100%" id="clear-local-attachments-btn" class="btn1">Clear Attachments</button>
<button style="width:100%" id="go-offline-btn" class="btn1">Go Offline</button>
<button style="width:100%" id="refresh-feature-layers-btn" class="btn1">Refresh Layers</button>
<button style="width:100%" id="go-online-btn" class="btn1">Go Online</button>
<ul id='local-attachments-list'></ul>
</div>
</div>
<div id="map"
data-dojo-type="dijit/layout/ContentPane"
data-dojo-props="region:'center'"></div>
<div id="footer"
data-dojo-type="dijit/layout/ContentPane"
data-dojo-props="region:'bottom'">
Click point to view/create/delete attachments.
</div>
</div>
</div>
</body>
</html>

View File

@ -5,10 +5,10 @@
<!--The viewport meta tag is used to improve the presentation and behavior of the samples
on iOS devices-->
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
<title>SanFrancisco311 - Incidents</title>
<title>Basic Attachments</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" >
<link rel="stylesheet" href="http://js.arcgis.com/3.12/esri/css/esri.css">
<link rel="stylesheet" href="http://js.arcgis.com/3.13/esri/css/esri.css">
<style>
html, body { height: 100%; width: 100%; margin: 0; overflow: hidden; font-family: Arial; }
#map { height: 100%; padding: 0;}
@ -43,7 +43,7 @@
}
</style>
<script src="http://js.arcgis.com/3.12/"></script>
<script src="http://js.arcgis.com/3.13/"></script>
<script src="../vendor/offline/offline.min.js"></script>
<script>
Offline.options = {
@ -124,8 +124,7 @@
function mapLoaded()
{
featureLayer = new FeatureLayer("http://sampleserver6.arcgisonline.com/arcgis/rest/services/ServiceRequest/FeatureServer/0",{
mode: FeatureLayer.MODE_ONDEMAND
//mode: FeatureLayer.MODE_SNAPSHOT
mode: FeatureLayer.MODE_SNAPSHOT
});
map.infoWindow.setContent("<div id='content' style='width:100%'></div>");
@ -309,10 +308,10 @@
<div id='storage-info'>Storage used: 0 MBs</div>
</div>
<div id='local-attachments-list-pane' data-dojo-type="dijit/layout/ContentPane" data-dojo-propos="region:'top'">
<button style="width:60%" id="clear-local-attachments-btn" class="btn1">Clear Attachments</button>
<button style="width:38%" id="go-offline-btn" class="btn1">Go Offline</button>
<button style="width:60%" id="refresh-feature-layers-btn" class="btn1">Refresh Layers</button>
<button style="width:38%" id="go-online-btn" class="btn1">Go Online</button>
<button style="width:100%" id="clear-local-attachments-btn" class="btn1">Clear Attachments</button>
<button style="width:100%" id="go-offline-btn" class="btn1">Go Offline</button>
<button style="width:100%" id="refresh-feature-layers-btn" class="btn1">Refresh Layers</button>
<button style="width:100%" id="go-online-btn" class="btn1">Go Online</button>
<ul id='local-attachments-list'></ul>
</div>
</div>

View File

@ -9,7 +9,7 @@
"appHomePage": "appcache-features.html",
"optimizedApiURL": "../samples/jsolib",
"arcGISBaseURL": "http://js.arcgis.com/3.11",
"version": "2.6.1",
"version": "2.7.0",
"private": true,
"description": "manifest generator project",
"repository": {

View File

@ -2,99 +2,147 @@
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Jasmine Spec Runner - Graphics Store</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Jasmine Spec Runner - Graphics Store</title>
<link rel="shortcut icon" type="image/png" href="../vendor/jasmine-1.3.1/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="../vendor/jasmine-1.3.1/jasmine.css">
<script type="text/javascript" src="../vendor/jasmine-1.3.1/jasmine.js"></script>
<script type="text/javascript" src="../vendor/jasmine-1.3.1/jasmine-html.js"></script>
<script type="text/javascript" src="../vendor/jasmine.async/lib/jasmine.async.js"></script>
<link rel="shortcut icon" type="image/png" href="../vendor/jasmine-1.3.1/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="../vendor/jasmine-1.3.1/jasmine.css">
<script type="text/javascript" src="../vendor/jasmine-1.3.1/jasmine.js"></script>
<script type="text/javascript" src="../vendor/jasmine-1.3.1/jasmine-html.js"></script>
<script type="text/javascript" src="../vendor/jasmine.async/lib/jasmine.async.js"></script>
<script>
var dojoConfig = {
paths: { edit: location.pathname.replace(/\/[^/]+$/, "") + "../../lib/edit" }
}
</script>
<script>
var dojoConfig = {
paths: { edit: location.pathname.replace(/\/[^/]+$/, "") + "../../lib/edit" }
}
</script>
<link rel="stylesheet" href="http://js.arcgis.com/3.10/js/dojo/dijit/themes/claro/claro.css">
<link rel="stylesheet" href="http://js.arcgis.com/3.10/js/esri/css/esri.css">
<script src="http://js.arcgis.com/3.10/"></script>
<link rel="stylesheet" href="http://js.arcgis.com/3.13/dijit/themes/claro/claro.css">
<link rel="stylesheet" href="http://js.arcgis.com/3.13/esri/css/esri.css">
<script src="http://js.arcgis.com/3.13/"></script>
<!-- include spec files here... -->
<script type="text/javascript" src="spec/attachmentsStoreSpec.js"></script>
<script type="text/javascript">
"use strict"
<!-- include spec files here... -->
<script type="text/javascript" src="spec/attachmentsStoreSpec.js"></script>
<script type="text/javascript">
"use strict";
var g_attachmentsStore;
var g_inputNode;
var g_attachmentsStore;
var g_inputNode = {};
require(["esri/map",
"esri/layers/GraphicsLayer", "esri/graphic", "esri/symbols/SimpleFillSymbol", "esri/symbols/SimpleMarkerSymbol", "esri/symbols/SimpleLineSymbol",
"esri/SpatialReference","esri/geometry",
"dojo/dom", "dojo/on", "dojo/query",
"dojo/dom-construct",
"../dist/offline-edit-min.js",
"dojo/domReady!"],
function(Map,
GraphicsLayer, Graphic, SimpleFillSymbol, SimpleMarkerSymbol, SimpleLineSymbol,
SpatialReference, geometry,
dom, on, query,
domConstruct)
{
g_attachmentsStore = new O.esri.Edit.AttachmentsStore();
g_inputNode = dom.byId('theFile');
require(["esri/map",
"esri/layers/GraphicsLayer", "esri/graphic", "esri/symbols/SimpleFillSymbol", "esri/symbols/SimpleMarkerSymbol", "esri/symbols/SimpleLineSymbol",
"esri/SpatialReference","esri/geometry",
"dojo/dom", "dojo/on", "dojo/query",
"dojo/dom-construct",
"../dist/offline-edit-src.js",
"dojo/domReady!"],
function(Map,
GraphicsLayer, Graphic, SimpleFillSymbol, SimpleMarkerSymbol, SimpleLineSymbol,
SpatialReference, geometry,
dom, on, query,
domConstruct)
{
g_attachmentsStore = new O.esri.Edit.AttachmentsStore();
test();
test();
function test()
{
try
{
console.log("everything ok!");
function test()
{
try
{
console.log("everything ok!");
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
jasmineEnv.defaultTimeoutInterval = 1000; // 1 sec
var htmlReporter = new jasmine.HtmlReporter();
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
jasmineEnv.defaultTimeoutInterval = 1000; // 1 sec
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
/*
var currentWindowOnload = window.onload;
/*
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
*/
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
*/
// execJasmine();
on(dom.byId('theFile'),'change',execJasmine);
}
catch(err)
{
alert(err);
}
function execJasmine() {
jasmineEnv.execute();
}
}; // test()
// execJasmine();
retrieveFile();
}); // require()
// on(dom.byId('theFile'),'change',execJasmine);
}
catch(err)
{
alert(err);
}
function execJasmine() {
jasmineEnv.execute();
}
function retrieveFile(){
var xhr = new XMLHttpRequest();
xhr.open("GET","images/blue-pin.png",true);
xhr.responseType = "blob";
xhr.onload = function()
{
if( xhr.status === 200)
{
var blob = new Blob([this.response],{type: 'image/png'});
var parts = [blob,"test", new ArrayBuffer(blob.size)];
var files = [];
var file = new File(parts,"blue-pin.png",{
lastModified: new Date(0),
type: "image/png"
});
// var formNode = {
// elements:[
// {type:"file",
// files:[file]}
// ]
// };
files.push(file);
g_inputNode.files = files;
execJasmine();
}
else
{
console.log("Retrieve file failed");
}
};
xhr.onerror = function(e)
{
console.log("Retrieved file failed: " + JSON.stringify(e));
};
xhr.send(null);
}
} // test()
}); // require()
</script>
</head>
<body>
<form id="theForm">
<input type="file" id="theFile"><br/>
<span>Select any file (.png, .jpg,...) to launch unit testing</span>
</form>
<!--<form id="theForm">-->
<!--<input type="file" id="theFile"><br/>-->
<!--<span>Select any file (.png, .jpg,...) to launch unit testing</span>-->
<!--</form>-->
</body>
</html>

View File

@ -19,9 +19,9 @@
}
</script>
<link rel="stylesheet" href="http://js.arcgis.com/3.10/js/dojo/dijit/themes/claro/claro.css">
<link rel="stylesheet" href="http://js.arcgis.com/3.10/js/esri/css/esri.css">
<script src="http://js.arcgis.com/3.10/"></script>
<link rel="stylesheet" href="http://js.arcgis.com/3.13/dijit/themes/claro/claro.css">
<link rel="stylesheet" href="http://js.arcgis.com/3.13/esri/css/esri.css">
<script src="http://js.arcgis.com/3.13/"></script>
<!-- include spec files here... -->
<script type="text/javascript" src="spec/offlineAttachmentsSpec.js"></script>
@ -30,10 +30,11 @@
var g_map;
var g_featureLayers = [];
var g_featureLayer = null;
var g_offlineFeaturesManager;
var g_modules = {};
var g_editsStore;
var g_formNode;
var g_formNode, g_formData, g_formData2, g_formNode2;
require(["esri/map",
"esri/layers/GraphicsLayer", "esri/graphic",
@ -51,6 +52,8 @@
g_modules.esriRequest = esriRequest;
g_modules.Graphic = Graphic;
g_offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManager();
g_offlineFeaturesManager.DB_UID = "OBJECTID";
g_editsStore = new O.esri.Edit.EditStore();
g_offlineFeaturesManager.initAttachments(function(success)
@ -58,7 +61,9 @@
console.log("attachments inited", success);
});
g_formNode = dom.byId('theForm');
var form = dom.byId('theForm');
on(dom.byId('theFile'),'change',test);
g_map = new Map("map", {
basemap: "topo",
@ -67,46 +72,36 @@
sliderStyle: "small"
});
var fsUrl = "http://services2.arcgis.com/CQWCKwrSm5dkM28A/arcgis/rest/services/Military/FeatureServer/";
var fsUrl = "http://services2.arcgis.com/CQWCKwrSm5dkM28A/arcgis/rest/services/Military/FeatureServer/3";
var fsUrl2 = "http://services1.arcgis.com/M8KJPUwAXP8jhtnM/arcgis/rest/services/Simple_Point_Service/FeatureServer/0";
// var layersIds = [0,1,2,3,4,5,6];
var layersIds = [1,2,3,6];
// var layersIds = [1,2,3,6];
layersIds.forEach(function(layerId)
{
var layer = new FeatureLayer(fsUrl + layerId, {
mode: FeatureLayer.MODE_SNAPSHOT,
outFields: ['*']
});
g_featureLayers.push(layer);
})
g_featureLayer = new FeatureLayer(fsUrl2, {
mode: FeatureLayer.MODE_SNAPSHOT,
outFields: ['*']
});
g_map.addLayers(g_featureLayers);
g_map.on('layers-add-result', function(evt){
console.log("layers ready");
});
g_map.on('layers-add-result', test);
g_map.addLayers([g_featureLayer]);
function test()
{
try
{
g_featureLayers.forEach(function(layer)
{
g_offlineFeaturesManager.extend(layer,function(success,message){
if(!success){
alert("There was a problem extending the layer: " + layer);
}
});
});
}
catch(err)
{
console.log(err);
}
g_offlineFeaturesManager.extend(g_featureLayer,function(success,message){
if(!success){
alert("There was a problem extending the layer: " + layer);
}
});
try
{
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
jasmineEnv.defaultTimeoutInterval = 5000; // 10 sec
jasmineEnv.defaultTimeoutInterval = 10000; // 10 sec
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
@ -130,13 +125,65 @@
}
// execJasmine();
on(dom.byId('theFile'),'change',execJasmine);
// on(dom.byId('theFile'),'change',execJasmine);
function retrieveFile(){
var xhr = new XMLHttpRequest();
xhr.open("GET","images/blue-pin.png",true);
xhr.responseType = "blob";
xhr.onload = function()
{
if( xhr.status === 200)
{
var blob = new Blob([this.response],{type: this.response.type});
var parts = [blob,"test", new ArrayBuffer(blob.size)];
var files = [];
var file = new File(parts,"blue-pin.png",{
lastModified: new Date(0),
type: this.response.type
});
files.push(file);
// Fake a form node for our custom parser in offlineFeaturesManager.js
g_formNode = {
elements:[
{type:"file",
files:files}
]
};
g_formData = new FormData();
g_formData.append("attachment",file);
g_formData2 = form;
// retrieveFile2();
execJasmine();
}
else
{
console.log("Retrieve file failed");
}
};
xhr.onerror = function(e)
{
console.log("Retrieved file failed: " + JSON.stringify(e));
};
xhr.send(null);
}
}
catch(err)
{
console.log(err);
}
retrieveFile();
}; // test()
@ -146,7 +193,7 @@
<body>
<form id="theForm">
<input type="file" id="theFile"><br/>
<input type="file" name="attachment" id="theFile"><br/>
<span>Select any file (.png, .jpg,...) to launch unit testing</span>
</form>
<div id="map" style="position: absolute; bottom: 0; right: 0; height:200px; width: 200px;"></div>

View File

@ -18,10 +18,9 @@
}
}
</script>
<link rel="stylesheet" href="http://js.arcgis.com/3.12/dijit/themes/claro/claro.css">
<link rel="stylesheet" href="http://js.arcgis.com/3.12/esri/css/esri.css">
<script src="http://js.arcgis.com/3.12/"></script>
<link rel="stylesheet" href="http://js.arcgis.com/3.13/esri/css/esri.css">
<script src="http://js.arcgis.com/3.13/"></script>
<!-- include spec files here... -->
<script type="text/javascript" src="spec/offlineFeaturesManagerSpec.js"></script>
@ -31,8 +30,10 @@
var g_map;
var g_featureLayers = [];
var g_offlineFeaturesManager;
var g_offlineFeaturesManagerAttach;
var g_modules = {};
var g_editsStore;
var g_layersIds = [];
require(["esri/map",
"esri/layers/GraphicsLayer", "esri/graphic",
@ -52,6 +53,13 @@
g_modules.Graphic = Graphic;
g_offlineFeaturesManager = new O.esri.Edit.OfflineFeaturesManager();
g_offlineFeaturesManager.DB_NAME = "FEATURES_TEST";
g_offlineFeaturesManager.DB_UID = "OBJECTID"; //VERY IMPORTANT! We get this from the Service Directory "Fields" section.
// For testing attachments
g_offlineFeaturesManagerAttach = new O.esri.Edit.OfflineFeaturesManager();
g_offlineFeaturesManagerAttach.DB_NAME = "ATTACH_TEST";
// We are also validating the OfflineFeatureManager directly against the database
g_editsStore = new O.esri.Edit.EditStore();
g_editsStore.dbName = "FEATURES_TEST";
@ -63,19 +71,20 @@
});
var fsUrl = "http://services2.arcgis.com/CQWCKwrSm5dkM28A/arcgis/rest/services/Military/FeatureServer/";
var fsUrlAttachmentsEnabled = "http://services1.arcgis.com/M8KJPUwAXP8jhtnM/arcgis/rest/services/Simple_Point_Service/FeatureServer/";
// Layer 1 = points
// Layer 2 = lines
// Layer 3 = polygons
var layersIds = [1,2,3];
g_layersIds = [0];
layersIds.forEach(function(layerId)
g_layersIds.forEach(function(layerId)
{
var layer = new FeatureLayer(fsUrl + layerId, {
var layer = new FeatureLayer(fsUrlAttachmentsEnabled + layerId, {
mode: FeatureLayer.MODE_SNAPSHOT,
outFields: ['*']
});
g_featureLayers.push(layer);
})
});
g_map.addLayers(g_featureLayers);

BIN
test/images/blue-pin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

BIN
test/images/red-pin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 B

View File

@ -45,13 +45,13 @@ describe("attachments store module", function()
{
e.push(file);
});
console.log(testData);
console.log("TEST DATA " + JSON.stringify(testData));
done();
});
async.it("store one attachment", function(done)
{
g_attachmentsStore.store(testData[0][0],testData[0][1],testData[0][2],testData[0][3], function(success)
g_attachmentsStore.store(testData[0][0],testData[0][1],testData[0][2],testData[0][3], "add",function(success)
{
expect(success).toBeTruthy();
g_attachmentsStore.getUsage(function(usage)
@ -63,6 +63,22 @@ describe("attachments store module", function()
});
});
async.it("fail to store attachment", function(done){
var form = document.getElementById("theForm");
g_attachmentsStore.store(testData[0][0],testData[0][1],testData[0][2],form,"add", function(success)
{
expect(success).toBe(false);
g_attachmentsStore.getUsage(function(usage)
{
expect(usage).not.toBeNull();
expect(usage.attachmentCount).toBe(1);
done();
})
});
});
async.it("store more attachments", function(done)
{
var i=1, n=testData.length;
@ -78,12 +94,21 @@ describe("attachments store module", function()
if( i == n)
done();
else
g_attachmentsStore.store(testData[i][0],testData[i][1],testData[i][2],testData[i][3], addAttachment);
g_attachmentsStore.store(testData[i][0],testData[i][1],testData[i][2],testData[i][3],"add", addAttachment);
})
};
g_attachmentsStore.store(testData[i][0],testData[i][1],testData[i][2],testData[i][3], addAttachment);
g_attachmentsStore.store(testData[i][0],testData[i][1],testData[i][2],testData[i][3],"add", addAttachment);
});
async.it("Check usage", function(done){
g_attachmentsStore.getUsage(function(usage)
{
expect(usage.sizeBytes).toBeGreaterThan(0);
expect(usage.attachmentCount).toBe(5);
done();
})
});
async.it("query attachments of a feature", function(done)
{
g_attachmentsStore.getAttachmentsByFeatureId("layer1", 300, function(attachments)
@ -94,20 +119,20 @@ describe("attachments store module", function()
expect(attachments.length).toBe(2);
expect(attachments[0].objectId).toBe(-1);
expect(attachments[1].objectId).toBe(-1);
expect(attachments[0].url).toContain("blob:");
expect(attachments[1].url).toContain("blob:");
//expect(attachments[0].url).toContain("blob:");
//expect(attachments[1].url).toContain("blob:");
g_attachmentsStore.getAttachmentsByFeatureId("layer1", -2, function(attachments)
{
expect(attachments.length).toBe(1);
expect(attachments[0].objectId).toBe(-2);
expect(attachments[0].url).toContain("blob:");
//expect(attachments[0].url).toContain("blob:");
g_attachmentsStore.getAttachmentsByFeatureId("layer2", 1, function(attachments)
{
expect(attachments.length).toBe(2);
expect(attachments[0].objectId).toBe(1);
expect(attachments[1].objectId).toBe(1);
expect(attachments[0].url).toContain("blob:");
expect(attachments[1].url).toContain("blob:");
//expect(attachments[0].url).toContain("blob:");
//expect(attachments[1].url).toContain("blob:");
done();
});
});
@ -196,6 +221,15 @@ describe("attachments store module", function()
});
});
async.it("Check usage", function(done){
g_attachmentsStore.getUsage(function(usage)
{
expect(usage.sizeBytes).toBeGreaterThan(0);
expect(usage.attachmentCount).toBe(4);
done();
})
});
async.it("delete attachments of a single feature", function(done)
{
g_attachmentsStore.deleteAttachmentsByFeatureId("layer1", 300, function(deletedCount)
@ -243,4 +277,13 @@ describe("attachments store module", function()
});
});
async.it("Check usage", function(done){
g_attachmentsStore.getUsage(function(usage)
{
expect(usage.sizeBytes).toBe(0);
expect(usage.attachmentCount).toBe(0);
done();
})
});
});

View File

@ -38,7 +38,7 @@ function countFeatures(featureLayer, cb)
function getObjectIds(graphics)
{
return graphics.map( function(g) { return g.attributes.objectid; });
return graphics.map( function(g) { return g_offlineFeaturesManager.DB_UID; });
}
/*
@ -55,12 +55,14 @@ describe("Attachments", function()
{
async.it("delete all features", function(done)
{
clearFeatureLayer( g_featureLayers[3], function(success,response)
{
expect(success).toBeTruthy();
var listener = g_featureLayers[3].on('update-end', function(){ listener.remove(); })
g_featureLayers[3].refresh();
done();
clearFeatureLayer(g_featureLayer,function(success){
clearFeatureLayer( g_featureLayer, function(success)
{
expect(success).toBeTruthy();
var listener = g_featureLayer.on('update-end', function(){ listener.remove(); })
g_featureLayer.refresh();
done();
});
});
});
@ -84,21 +86,23 @@ describe("Attachments", function()
async.it("add online feature", function(done)
{
expect(g_featureLayers[3].graphics.length).toBe(0);
expect(g_featureLayer.graphics.length).toBe(0);
g1_online = new g_modules.Graphic({
"geometry": {"rings": [[[-109922,5108923],[-94801,5119577],[-86348,5107580],[-101470,5096926],[-109922,5108923]]],"spatialReference":{"wkid":102100}},
"attributes":{"ruleid": 2, "name": "Zaragoza"}
});
g1_online = new g_modules.Graphic({"geometry":{"x":-105400,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":1,"lat":0.0,"lng":0.0,"description":"g1"}});
//g1_online = new g_modules.Graphic({
// "geometry": {"rings": [[[-109922,5108923],[-94801,5119577],[-86348,5107580],[-101470,5096926],[-109922,5108923]]],"spatialReference":{"wkid":102100}},
// "attributes":{"ruleid": 2, "name": "Zaragoza"}
//});
var adds = [g1_online];
g_featureLayers[3].applyEdits(adds,null,null,function(addResults,updateResults,deleteResults)
g_featureLayer.applyEdits(adds,null,null,function(addResults,updateResults,deleteResults)
{
expect(addResults.length).toBe(1);
expect(addResults[0].success).toBeTruthy();
g1_online.attributes.objectid = addResults[0].objectId;
expect(getObjectIds(g_featureLayers[3].graphics)).toEqual(getObjectIds([g1_online]));
expect(g_featureLayers[3].graphics.length).toBe(1);
expect(getObjectIds(g_featureLayer.graphics)).toEqual(getObjectIds([g1_online]));
expect(g_featureLayer.graphics.length).toBe(1);
done();
},
function(error)
@ -108,6 +112,72 @@ describe("Attachments", function()
});
});
async.it("add online attachment", function(done){
g_featureLayer.addAttachment( g1_online.attributes.objectid, g_formData,
function(result)
{
g1_online.attributes.attachmentsId = result.attachmentId;
expect(result.success).toBe(true);
expect(result.objectId).toBeGreaterThan(0);
expect(result.attachmentId).toBeGreaterThan(0);
expect(result.objectId).toBe( g1_online.attributes.objectid );
done();
},
function(err)
{
expect(true).toBeFalsy();
done();
});
});
async.it("add a second online attachment", function(done){
g_featureLayer.addAttachment( g1_online.attributes.objectid, g_formData2,
function(result)
{
g1_online.attributes.attachmentsId2 = result.attachmentId;
expect(result.success).toBe(true);
expect(result.objectId).toBeGreaterThan(0);
expect(result.attachmentId).toBeGreaterThan(0);
expect(result.objectId).toBe( g1_online.attributes.objectid );
done();
},
function(err)
{
expect(true).toBeFalsy();
done();
});
});
async.it("Update second online attachment", function(done){
//g_formData2.append("lat",2);
g_featureLayer.updateAttachment( g1_online.attributes.objectid, g1_online.attributes.attachmentsId2, g_formData2,
function(result)
{
g1_online.attributes.attachmentsId2 = result.attachmentId;
expect(result.success).toBe(true);
expect(result.objectId).toBeGreaterThan(0);
expect(result.attachmentId).toBeGreaterThan(0);
expect(result.objectId).toBe( g1_online.attributes.objectid );
done();
},
function(err)
{
expect(true).toBeFalsy();
done();
});
});
async.it("Get attachments database usage", function(done){
g_featureLayer.getAttachmentsUsage(function(usage,error){
expect(usage.sizeBytes).toBe(0);
expect(usage.attachmentCount).toBe(0);
done();
});
});
});
describe("Go offline", function(){
async.it("go offline", function(done)
{
expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE);
@ -118,34 +188,37 @@ describe("Attachments", function()
async.it("add offline features", function(done)
{
expect(g_featureLayers[3].graphics.length).toBe(1);
expect(g_featureLayer.graphics.length).toBe(1);
g2_offline = new g_modules.Graphic({
"geometry": {
"rings": [[[-518920,4967379],[-474892,4975940],[-439425,5015076],[-377053,5050543],[-290220,5049320],[-271876,5021191],[-417412,4975940],[-510359,4891554],[-670571,4862202],[-682801,4880547],[-665679,4916014],[-518920,4967379]]],
"spatialReference":{"wkid":102100}
},
"attributes":{"ruleid": 3, "name": "Sistema Central"}
});
g2_offline = new g_modules.Graphic({"geometry":{"x":-105600,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":2,"lat":0.0,"lng":0.0,"description":"g2"}});
g3_offline = new g_modules.Graphic({"geometry":{"x":-105800,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":3,"lat":0.0,"lng":0.0,"description":"g3"}});
g3_offline = new g_modules.Graphic({
"geometry":{
"rings":[[[-275852.307236338,5103437.42576518],[-131539.197833964,5103437.42576518],[-131539.197833964,5003152.04465505],[-275852.307236338,5003152.04465505],[-275852.307236338,5103437.42576518]]],
"spatialReference":{"wkid":102100}
},
"attributes":{"ruleid":2,"name":"to delete"}
});
//g2_offline = new g_modules.Graphic({
// "geometry": {
// "rings": [[[-518920,4967379],[-474892,4975940],[-439425,5015076],[-377053,5050543],[-290220,5049320],[-271876,5021191],[-417412,4975940],[-510359,4891554],[-670571,4862202],[-682801,4880547],[-665679,4916014],[-518920,4967379]]],
// "spatialReference":{"wkid":102100}
// },
// "attributes":{"ruleid": 3, "name": "Sistema Central"}
//});
//
//g3_offline = new g_modules.Graphic({
// "geometry":{
// "rings":[[[-275852.307236338,5103437.42576518],[-131539.197833964,5103437.42576518],[-131539.197833964,5003152.04465505],[-275852.307236338,5003152.04465505],[-275852.307236338,5103437.42576518]]],
// "spatialReference":{"wkid":102100}
// },
// "attributes":{"ruleid":2,"name":"to delete"}
//});
var adds = [g2_offline, g3_offline];
g_featureLayers[3].applyEdits(adds,null,null,function(addResults,updateResults,deleteResults)
g_featureLayer.applyEdits(adds,null,null,function(addResults,updateResults,deleteResults)
{
expect(addResults.length).toBe(2);
expect(addResults[0].success).toBeTruthy();
expect(addResults[1].success).toBeTruthy();
g2_offline.attributes.objectid = addResults[0].objectId;
g3_offline.attributes.objectid = addResults[1].objectId;
expect(getObjectIds(g_featureLayers[3].graphics)).toEqual(getObjectIds([g1_online,g2_offline,g3_offline]));
expect(g_featureLayers[3].graphics.length).toBe(3);
expect(getObjectIds(g_featureLayer.graphics)).toEqual(getObjectIds([g1_online,g2_offline,g3_offline]));
expect(g_featureLayer.graphics.length).toBe(3);
done();
},
function(error)
@ -154,13 +227,21 @@ describe("Attachments", function()
done();
});
});
async.it("Verify Attachment DB usage as zero", function(done){
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(0);
done();
});
});
});
describe("Add and Query offline attachments", function()
{
async.it("query attachment info - 1", function(done)
{
g_featureLayers[3].queryAttachmentInfos(g1_online.attributes.objectid,
g_featureLayer.queryAttachmentInfos(g1_online.attributes.objectid,
function(attachmentsInfo)
{
expect(attachmentsInfo.length).toBe(0);
@ -175,7 +256,7 @@ describe("Attachments", function()
async.it("query attachment info - 2", function(done)
{
g_featureLayers[3].queryAttachmentInfos(g2_offline.attributes.objectid,
g_featureLayer.queryAttachmentInfos(g2_offline.attributes.objectid,
function(attachmentsInfo)
{
expect(attachmentsInfo.length).toBe(0);
@ -190,27 +271,18 @@ describe("Attachments", function()
async.it("add attachment to (online) feature", function(done)
{
expect(g_featureLayers[3].graphics.length).toBe(3);
expect(g_featureLayer.graphics.length).toBe(3);
expect(g_offlineFeaturesManager.attachmentsStore).not.toBeUndefined();
expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.OFFLINE);
expect(g1_online.attributes.objectid).toBeGreaterThan(0);
g_featureLayers[3].addAttachment( g1_online.attributes.objectid, g_formNode,
g_featureLayer.addAttachment( g1_online.attributes.objectid, g_formNode,
function(result)
{
expect(result).not.toBeUndefined();
expect(result.attachmentId).toBeLessThan(0);
expect(result.objectId).toBe( g1_online.attributes.objectid );
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(1);
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayers[3].url, g1_online.attributes.objectid, function(attachments)
{
expect(attachments.length).toBe(1);
console.log("attached file:", attachments[0]);
done();
});
});
done();
},
function(err)
{
@ -219,30 +291,34 @@ describe("Attachments", function()
});
});
async.it("Verify Attachment DB usage", function(done){
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(1);
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g1_online.attributes.objectid, function(attachments)
{
expect(attachments.length).toBe(1);
console.log("attached file:", attachments[0]);
done();
});
});
});
async.it("add attachment to (offline) feature", function(done)
{
expect(g_featureLayers[3].graphics.length).toBe(3);
expect(g_featureLayer.graphics.length).toBe(3);
expect(g_offlineFeaturesManager.attachmentsStore).not.toBeUndefined();
expect(g2_offline.attributes.objectid).toBeLessThan(0);
g_featureLayers[3].addAttachment( g2_offline.attributes.objectid, g_formNode,
g_featureLayer.addAttachment( g2_offline.attributes.objectid, g_formNode,
function(result)
{
console.log(result);
expect(result).not.toBeUndefined();
expect(result.attachmentId).toBeLessThan(0);
expect(result.objectId).toBe( g2_offline.attributes.objectid );
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(2);
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayers[3].url, g2_offline.attributes.objectid, function(attachments)
{
expect(attachments.length).toBe(1);
console.log("attached file:", attachments[0]);
done();
});
});
done();
},
function(err)
{
@ -251,30 +327,34 @@ describe("Attachments", function()
});
});
async.it("Verify Attachment DB usage", function(done){
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(2);
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g2_offline.attributes.objectid, function(attachments)
{
expect(attachments.length).toBe(1);
console.log("attached file:", attachments[0]);
done();
});
});
});
async.it("add attachment to (offline) feature (to be deleted)", function(done)
{
expect(g_featureLayers[3].graphics.length).toBe(3);
expect(g_featureLayer.graphics.length).toBe(3);
expect(g_offlineFeaturesManager.attachmentsStore).not.toBeUndefined();
expect(g3_offline.attributes.objectid).toBeLessThan(0);
g_featureLayers[3].addAttachment( g3_offline.attributes.objectid, g_formNode,
g_featureLayer.addAttachment( g3_offline.attributes.objectid, g_formNode,
function(result)
{
console.log(result);
expect(result).not.toBeUndefined();
expect(result.attachmentId).toBeLessThan(0);
expect(result.objectId).toBe( g3_offline.attributes.objectid );
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(3);
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayers[3].url, g3_offline.attributes.objectid, function(attachments)
{
expect(attachments.length).toBe(1);
console.log("attached file:", attachments[0]);
done();
});
});
done();
},
function(err)
{
@ -283,25 +363,37 @@ describe("Attachments", function()
});
});
async.it("Verify attachment g3_offline", function(done) {
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g3_offline.attributes.objectid, function(attachments)
{
expect(attachments.length).toBe(1);
console.log("attached file:", attachments[0]);
done();
});
});
async.it("Verify Attachment DB usage", function(done){
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(3);
done();
});
});
async.it("query offline attachments of layer", function(done)
{
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(3);
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureLayer(g_featureLayers[3].url, function(attachments)
{
expect(attachments.length).toBe(3);
var objectIds = attachments.map(function(a){ return a.objectId; }).sort();
expect(objectIds).toEqual([g1_online.attributes.objectid, g2_offline.attributes.objectid, g3_offline.attributes.objectid].sort());
done();
});
});
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureLayer(g_featureLayer.url, function(attachments)
{
expect(attachments.length).toBe(3);
var objectIds = attachments.map(function(a){ return a.objectId; }).sort();
expect(objectIds).toEqual([g1_online.attributes.objectid, g2_offline.attributes.objectid, g3_offline.attributes.objectid].sort());
done();
});
});
async.it("query attachment info - 1", function(done)
{
g_featureLayers[3].queryAttachmentInfos(g1_online.attributes.objectid,
g_featureLayer.queryAttachmentInfos(g1_online.attributes.objectid,
function(attachmentsInfo)
{
expect(attachmentsInfo.length).toBe(1);
@ -318,7 +410,7 @@ describe("Attachments", function()
async.it("query attachment info - 2", function(done)
{
g_featureLayers[3].queryAttachmentInfos(g2_offline.attributes.objectid,
g_featureLayer.queryAttachmentInfos(g2_offline.attributes.objectid,
function(attachmentsInfo)
{
expect(attachmentsInfo.length).toBe(1);
@ -336,7 +428,7 @@ describe("Attachments", function()
async.it("query attachment info - inexistent", function(done)
{
var inexistentId = g2_offline.attributes.objectid+1;
g_featureLayers[3].queryAttachmentInfos(inexistentId,
g_featureLayer.queryAttachmentInfos(inexistentId,
function(attachmentsInfo)
{
expect(attachmentsInfo.length).toBe(0);
@ -355,12 +447,12 @@ describe("Attachments", function()
async.it("delete (offline) feature with attachments", function(done)
{
var deletes = [g3_offline];
g_featureLayers[3].applyEdits(null,null,deletes,function(addResults,updateResults,deleteResults)
g_featureLayer.applyEdits(null,null,deletes,function(addResults,updateResults,deleteResults)
{
expect(deleteResults.length).toBe(1);
expect(deleteResults[0].success).toBeTruthy();
expect(getObjectIds(g_featureLayers[3].graphics)).toEqual(getObjectIds([g1_online,g2_offline]));
expect(g_featureLayers[3].graphics.length).toBe(2);
expect(getObjectIds(g_featureLayer.graphics)).toEqual(getObjectIds([g1_online,g2_offline]));
expect(g_featureLayer.graphics.length).toBe(2);
done();
},
function(error)
@ -369,20 +461,33 @@ describe("Attachments", function()
done();
});
});
async.it("Verify Attachment DB usage", function(done){
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(2);
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g3_offline.attributes.objectid, function(attachments)
{
expect(attachments.length).toBe(0);
console.log("attached file:", attachments[0]);
done();
});
});
});
});
describe("delete attachments", function()
describe("delete new attachments", function()
{
var attachmentId;
async.it("add attachment", function(done)
{
expect(g_featureLayers[3].graphics.length).toBe(2);
expect(g_featureLayer.graphics.length).toBe(2);
expect(g_offlineFeaturesManager.attachmentsStore).not.toBeUndefined();
expect(g2_offline.attributes.objectid).toBeLessThan(0);
g_featureLayers[3].addAttachment( g2_offline.attributes.objectid, g_formNode,
g_featureLayer.addAttachment( g2_offline.attributes.objectid, g_formNode,
function(result)
{
attachmentId = result.attachmentId;
@ -391,15 +496,7 @@ describe("Attachments", function()
expect(result).not.toBeUndefined();
expect(result.attachmentId).toBeLessThan(0);
expect(result.objectId).toBe( g2_offline.attributes.objectid );
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(3);
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayers[3].url, g2_offline.attributes.objectid, function(attachments)
{
expect(attachments.length).toBe(2);
done();
});
});
done();
},
function(err)
{
@ -409,26 +506,29 @@ describe("Attachments", function()
);
});
async.it("Verify Attachment DB usage",function(done){
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(3);
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g2_offline.attributes.objectid, function(attachments)
{
expect(attachments.length).toBe(2);
done();
});
});
});
async.it("delete attachment", function(done)
{
expect(g_featureLayers[3].graphics.length).toBe(2);
expect(g_featureLayer.graphics.length).toBe(2);
expect(g_offlineFeaturesManager.attachmentsStore).not.toBeUndefined();
g_featureLayers[3].deleteAttachments( g2_offline.attributes.objectid, [attachmentId],
g_featureLayer.deleteAttachments( g2_offline.attributes.objectid, [attachmentId],
function(result)
{
console.log(result);
expect(result).not.toBeUndefined();
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(2);
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayers[3].url, g2_offline.attributes.objectid, function(attachments)
{
expect(attachments.length).toBe(1);
done();
});
});
done();
},
function(err)
{
@ -437,24 +537,91 @@ describe("Attachments", function()
}
);
});
async.it("Verify Attachment DB usage", function(done){
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(2);
done();
});
});
async.it("Verify attachment g2_offline", function(done){
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureId(g_featureLayer.url, g2_offline.attributes.objectid, function(attachments)
{
expect(attachments.length).toBe(1);
done();
});
});
});
describe("go Online and finish all", function()
describe("Delete existing attachment", function(){
async.it("Delete attachment g1_online", function(done){
g_featureLayer.deleteAttachments( g1_online.attributes.objectid, [g1_online.attributes.attachmentsId],
function(result)
{
console.log(result);
expect(result).not.toBeUndefined();
done();
},
function(err)
{
expect(true).toBeFalsy();
done();
}
);
});
});
describe("Update existing attachment", function(){
async.it("Update attachment g1_online", function(done){
g_formData.append("attachmentId",g1_online.attributes.attachmentsId2);
g_featureLayer.updateAttachment( g1_online.attributes.objectid, g1_online.attributes.attachmentsId2, g_formData2,
function(result)
{
console.log(result);
expect(result).not.toBeUndefined();
done();
},
function(err)
{
expect(true).toBeFalsy();
done();
}
);
});
async.it("Get attachments database usage", function(done){
g_featureLayer.getAttachmentsUsage(function(usage,error){
expect(usage.sizeBytes).toBe(129627);
expect(usage.attachmentCount).toBe(4);
done();
});
});
});
describe("go Online and finish all", function()
{
async.it("query offline attachments of layer", function(done)
{
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureLayer(g_featureLayers[3].url, function(attachments)
g_offlineFeaturesManager.attachmentsStore.getAttachmentsByFeatureLayer(g_featureLayer.url, function(attachments)
{
expect(attachments.length).toBe(2);
// 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
// removed until we do a successful sync.
expect(attachments.length).toBe(4);
var objectIds = attachments.map(function(a){ return a.objectId; }).sort();
expect(objectIds).toEqual([g1_online.attributes.objectid, g2_offline.attributes.objectid].sort());
expect(objectIds).toEqual([g1_online.attributes.objectid,g1_online.attributes.objectid, g1_online.attributes.objectid, g2_offline.attributes.objectid].sort());
done();
});
});
async.it("go Online", function(done)
{
expect(g_featureLayers[3].graphics.length).toBe(2);
expect(g_featureLayer.graphics.length).toBe(2);
var listener = jasmine.createSpy('event listener');
g_offlineFeaturesManager.on(g_offlineFeaturesManager.events.ALL_EDITS_SENT, listener);
@ -467,15 +634,15 @@ describe("Attachments", function()
expect(result.features.success).toBeTruthy();
expect(result.attachments.success).toBeTruthy();
expect(Object.keys(result.features.responses).length).toBe(1);
expect(Object.keys(result.attachments.responses).length).toBe(2);
expect(Object.keys(result.attachments.responses).length).toBe(4);
var attachmentResults = result.attachments.responses;
expect(attachmentResults).not.toBeUndefined();
expect(attachmentResults.length).toBe(2);
expect(attachmentResults[0].addAttachmentResult).not.toBeUndefined();
expect(attachmentResults[0].addAttachmentResult.success).toBeTruthy();
expect(attachmentResults[1].addAttachmentResult).not.toBeUndefined();
expect(attachmentResults[1].addAttachmentResult.success).toBeTruthy();
expect(attachmentResults.length).toBe(4);
//expect(attachmentResults[0].addAttachmentResult).not.toBeUndefined();
//expect(attachmentResults[0].addAttachmentResult.success).toBeTruthy();
//expect(attachmentResults[1].addAttachmentResult).not.toBeUndefined();
//expect(attachmentResults[1].addAttachmentResult.success).toBeTruthy();
expect(result.features.responses[0]).not.toBeUndefined();
var featureResults = result.features.responses[0];
@ -485,66 +652,83 @@ describe("Attachments", function()
expect(featureResults.addResults[0].success).toBeTruthy();
g2_offline.attributes.objectid = featureResults.addResults[0].objectId;
expect(getObjectIds(g_featureLayers[3].graphics)).toEqual(getObjectIds([g1_online,g2_offline]));
expect(getObjectIds(g_featureLayers[3].graphics).filter(function(id){ return id<0; })).toEqual([]); //all of them are positive
expect(g_featureLayers[3].graphics.length).toBe(2);
countFeatures(g_featureLayers[3], function(success,result)
{
expect(success).toBeTruthy();
expect(result.count).toBe(2);
done();
});
expect(getObjectIds(g_featureLayer.graphics)).toEqual(getObjectIds([g1_online,g2_offline]));
expect(getObjectIds(g_featureLayer.graphics).filter(function(id){ return id<0; })).toEqual([]); //all of them are positive
expect(g_featureLayer.graphics.length).toBe(2);
done();
});
expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.RECONNECTING);
});
});
describe("Wrap up and clean up",function(){
async.it("Verify feature count", function(done){
countFeatures(g_featureLayer, function(success,result)
{
expect(success).toBeTruthy();
expect(result.count).toBe(2);
done();
});
});
it("no edits pending", function(done)
async.it("no edits pending", function(done)
{
expect(g_featureLayers[3].pendingEditsCount(function(count){
expect(g_featureLayer.pendingEditsCount(function(count){
expect(count).toBe(0);
done();
}));
});
it("no attachments pending", function(done)
async.it("Get attachments database usage - check directly via attachmentsStore", function(done)
{
g_offlineFeaturesManager.attachmentsStore.getUsage(function(usage)
{
expect(usage.attachmentCount).toBe(0);
// done();
done();
});
});
async.it("Get attachments database usage via the feature layer", function(done){
g_featureLayer.getAttachmentsUsage(function(usage,error){
expect(usage.sizeBytes).toBe(0);
expect(usage.attachmentCount).toBe(0);
done();
});
});
it("query attachments info - online - 1", function(done)
async.it("query attachments info - online - 1", function(done)
{
g_featureLayers[3].queryAttachmentInfos(g1_online.attributes.objectid,
g_featureLayer.queryAttachmentInfos(g1_online.attributes.objectid,
function(attachmentsInfo)
{
expect(attachmentsInfo.length).toBe(1);
expect(attachmentsInfo.length).toBe(2);
expect(attachmentsInfo[0].objectId).toBe(g1_online.attributes.objectid);
expect(attachmentsInfo[0].id).toBeGreaterThan(0);
// done();
done();
},
function(err)
{
expect(true).toBeFalsy();
// done();
done();
});
});
it("query attachments info - online - 2", function(done)
async.it("query attachments info - online - 2", function(done)
{
g_featureLayers[3].queryAttachmentInfos(g2_offline.attributes.objectid,
g_featureLayer.queryAttachmentInfos(g2_offline.attributes.objectid,
function(attachmentsInfo)
{
expect(attachmentsInfo.length).toBe(1);
expect(attachmentsInfo.length).toEqual(1);
expect(attachmentsInfo[0].objectId).toBe(g2_offline.attributes.objectid);
expect(attachmentsInfo[0].id).toBeGreaterThan(0);
// done();
},
done();
},
function(err)
{
expect(true).toBeFalsy();
// done();
done();
});
});
});

View File

@ -58,39 +58,48 @@ describe("Normal online editing - Exercise the feature services", function()
function completedOne()
{
count += 1;
if(count==3)
if(count==g_layersIds.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();
});
clearFeatureLayer( g_featureLayers[1], function(success,response)
{
expect(success).toBeTruthy();
var listener = g_featureLayers[1].on('update-end', function(){ listener.remove(); completedOne();})
g_featureLayers[1].refresh();
});
clearFeatureLayer( g_featureLayers[2], function(success,response)
{
expect(success).toBeTruthy();
var listener = g_featureLayers[2].on('update-end', function(){ listener.remove(); completedOne();})
g_featureLayers[2].refresh();
});
// 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();
});
});
//clearFeatureLayer( g_featureLayers[1], function(success,response)
//{
// expect(success).toBeTruthy();
// var listener = g_featureLayers[1].on('update-end', function(){ listener.remove(); completedOne();})
// g_featureLayers[1].refresh();
//});
//clearFeatureLayer( g_featureLayers[2], function(success,response)
//{
// expect(success).toBeTruthy();
// var listener = g_featureLayers[2].on('update-end', function(){ listener.remove(); completedOne();})
// g_featureLayers[2].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":{"symbolname":"Ground Zero","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}});
g2 = new g_modules.Graphic({"geometry":{"x":-105600,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Ground Zero","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}});
g3 = new g_modules.Graphic({"geometry":{"x":-105800,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Ground Zero","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}});
//g1 = new g_modules.Graphic({"geometry":{"x":-105400,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Ground Zero","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}});
//g2 = new g_modules.Graphic({"geometry":{"x":-105600,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Ground Zero","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}});
//g3 = new g_modules.Graphic({"geometry":{"x":-105800,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Ground Zero","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}});
var adds = [g1,g2,g3];
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);
@ -161,6 +170,75 @@ describe("Normal online editing - Exercise the feature services", function()
});
});
//describe("Online attachments", function(){
// async.it("Add image attachment", function(done){
//
//
// var xhr = new XMLHttpRequest();
// xhr.open("GET","../samples/images/blue-pin.png",true);
// xhr.responseType = "blob";
//
// xhr.onload = function()
// {
// if( xhr.status === 200)
// {
// var blob = new Blob([this.response],{type: 'image/png'});
//
// // Verify our image is a PNG file!
// var reader = new FileReader();
// reader.onload = function (evt) {
// file = evt.target.result;
// var test = file.slice(0,4);
// expect(test).toContain("PNG");
// };
// reader.readAsBinaryString(blob);
//
// var parts = [blob,"test", new ArrayBuffer(blob.size)];
//
// var file = new File(parts,"blue-pin.png",{
// lastModified: new Date(0),
// type: "image/png"
// });
//
// var formNode = {
// elements:[
// {type:"file",
// files:[file]}
// ]
// };
//
// g_offlineFeaturesManager.initAttachments(function(success){
// expect(success).toBe(true);
// if(success){
//
// done();
// //g_featureLayers[0].addAttachment(/* objectid */1,/* form node */ formNode,function(event){
// // expect(event.attachmentId).toBe(-4);
// // expect(event.objectId).toBe(1);
// // expect(event.success).toBe(true);
// // //console.log("ATTACHMENTS: " + JSON.stringify(event));
// // done();
// //},function(error){
// // expect(error).toBe(true); // we want to fail if there is an error!
// // done();
// //});
// }
// });
// }
// else
// {
// console.log("Test attachments failed");
// }
// };
// xhr.onerror = function(e)
// {
// console.log("Test attachments failed: " + JSON.stringify(e));
// };
//
// xhr.send(null);
// });
//});
describe("Offline Editing", function()
{
@ -205,7 +283,7 @@ describe("Offline Editing", function()
//Verify that we did not overwrite the options object.
//If this tests passes then it's still there!
expect(graphics.layerDefinition.id).toEqual(3);
expect(graphics.layerDefinition.id).toEqual(0);
done();
});
@ -231,7 +309,7 @@ describe("Offline Editing", function()
function completedOne()
{
count += 1;
if(count==3)
if(count== g_layersIds.length)
done();
}
clearFeatureLayer( g_featureLayers[0], function(success,response)
@ -241,27 +319,32 @@ describe("Offline Editing", function()
g_featureLayers[0].refresh();
});
clearFeatureLayer( g_featureLayers[1], function(success,response)
{
expect(success).toBeTruthy();
var listener = g_featureLayers[1].on('update-end', function(){ listener.remove(); completedOne();})
g_featureLayers[1].refresh();
});
clearFeatureLayer( g_featureLayers[2], function(success,response)
{
expect(success).toBeTruthy();
var listener = g_featureLayers[2].on('update-end', function(){ listener.remove(); completedOne();})
g_featureLayers[2].refresh();
});
//clearFeatureLayer( g_featureLayers[1], function(success,response)
//{
// expect(success).toBeTruthy();
// var listener = g_featureLayers[1].on('update-end', function(){ listener.remove(); completedOne();})
// g_featureLayers[1].refresh();
//});
//clearFeatureLayer( g_featureLayers[2], function(success,response)
//{
// expect(success).toBeTruthy();
// var listener = g_featureLayers[2].on('update-end', function(){ listener.remove(); completedOne();})
// g_featureLayers[2].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":{"symbolname":"Ground Zero","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}});
g2 = new g_modules.Graphic({"geometry":{"x":-105600,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Ground Zero","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}});
g3 = new g_modules.Graphic({"geometry":{"x":-105800,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Ground Zero","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}});
//g1 = new g_modules.Graphic({"geometry":{"x":-105400,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Ground Zero","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}});
//g2 = new g_modules.Graphic({"geometry":{"x":-105600,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Ground Zero","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}});
//g3 = new g_modules.Graphic({"geometry":{"x":-105800,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Ground Zero","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}});
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)
@ -282,32 +365,33 @@ describe("Offline Editing", function()
});
});
async.it("Prepare feature service. Add some features online - lines", function(done)
{
expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.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}});
l3 = new g_modules.Graphic({"geometry":{"paths":[[[-101300,5136700],[-108400,5136700]]],"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}});
var adds = [l1,l2,l3];
g_featureLayers[1].applyEdits(adds,null,null,function(addResults,updateResults,deleteResults)
{
expect(addResults.length).toBe(3);
expect(getObjectIds(g_featureLayers[1].graphics)).toEqual(getObjectIds([l1,l2,l3]));
expect(g_featureLayers[1].graphics.length).toBe(3);
countFeatures(g_featureLayers[1], function(success,result)
{
expect(success).toBeTruthy();
expect(result.count).toBe(3);
done();
});
},
function(error)
{
expect(true).toBeFalsy();
});
});
// 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);
//
// 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}});
// l3 = new g_modules.Graphic({"geometry":{"paths":[[[-101300,5136700],[-108400,5136700]]],"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}});
//
// var adds = [l1,l2,l3];
// g_featureLayers[1].applyEdits(adds,null,null,function(addResults,updateResults,deleteResults)
// {
// expect(addResults.length).toBe(3);
// expect(getObjectIds(g_featureLayers[1].graphics)).toEqual(getObjectIds([l1,l2,l3]));
// expect(g_featureLayers[1].graphics.length).toBe(3);
// countFeatures(g_featureLayers[1], function(success,result)
// {
// expect(success).toBeTruthy();
// expect(result.count).toBe(3);
// done();
// });
// },
// function(error)
// {
// expect(true).toBeFalsy();
// });
//});
});
describe("Go offline", function()
@ -346,7 +430,7 @@ describe("Offline Editing", function()
g3.geometry.y -= 200;
var updates = [g1,g2,g3];
g_featureLayers[0].applyEdits(null,updates,null,function(addResults,updateResults,deleteResults)
{ console.log("LINE 298: " + JSON.stringify(updateResults))
{ console.log("update existing features - points: " + JSON.stringify(updateResults))
expect(updateResults.length).toBe(3);
expect(updateResults[0].success).toBeTruthy();
expect(updateResults[1].success).toBeTruthy();
@ -365,37 +449,38 @@ describe("Offline Editing", function()
});
});
async.it("update existing features - lines", function(done)
{
expect(getObjectIds(g_featureLayers[1].graphics)).toEqual(getObjectIds([l1,l2,l3]));
expect(g_featureLayers[1].graphics.length).toBe(3);
expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.OFFLINE);
l1.geometry.y += 300; // jabadia: change
l2.geometry.y += 100;
l3.geometry.y -= 200;
var updates = [l1,l2,l3];
g_featureLayers[1].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[1].graphics)).toEqual(getObjectIds([l1,l2,l3]));
expect(g_featureLayers[1].graphics.length).toBe(3);
g_editsStore.pendingEditsCount(function(result){
expect(result).toBe(6);
done();
});
},
function(error)
{
expect(true).toBeFalsy();
done();
});
});
// NOTE: We are only dealing with points!
//async.it("update existing features - lines", function(done)
//{
// 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);
//
//
// l1.geometry.y += 300; // jabadia: change
// l2.geometry.y += 100;
// l3.geometry.y -= 200;
//
// var updates = [l1,l2,l3];
// g_featureLayers[1].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[1].graphics)).toEqual(getObjectIds([l1,l2,l3]));
// expect(g_featureLayers[1].graphics.length).toBe(3);
// g_editsStore.pendingEditsCount(function(result){
// expect(result).toBe(6);
// done();
// });
// },
// function(error)
// {
// expect(true).toBeFalsy();
// done();
// });
//});
async.it("delete existing features - points", function(done)
{
@ -412,7 +497,7 @@ describe("Offline Editing", function()
// Expected count will stil be 6! This record is the database gets overwritten
// with the latest edit request. Last edit wins.
expect(result).toBe(6);
expect(result).toBe(3);
done();
});
},
@ -429,9 +514,13 @@ describe("Offline Editing", function()
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":{"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)
@ -441,7 +530,7 @@ describe("Offline Editing", function()
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(9);
expect(result).toBe(6);
done();
});
@ -474,7 +563,7 @@ describe("Offline Editing", function()
// 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(9);
expect(result).toBe(6);
done();
});
},
@ -567,8 +656,8 @@ describe("Offline Editing", function()
async.it("check db size", function(done){
g_featureLayers[0].getUsage(function(usage,error){
expect(usage.sizeBytes).toBe(7083);
expect(usage.editCount).toBe(8);
expect(usage.sizeBytes).toBe(3847);
expect(usage.editCount).toBe(5);
expect(error).toBe(null);
done();
})
@ -576,9 +665,147 @@ describe("Offline Editing", function()
});
// TO-DO!!
//describe("Test attachments", function(){
// //TO-DO
//});
describe("Test attachments", function(){
async.it("Add image attachment", function(done){
var xhr = new XMLHttpRequest();
xhr.open("GET","../samples/images/blue-pin.png",true);
xhr.responseType = "blob";
xhr.onload = function()
{
if( xhr.status === 200)
{
var blob = new Blob([this.response],{type: 'image/png'});
// Verify our image is a PNG file!
var reader = new FileReader();
reader.onload = function (evt) {
file = evt.target.result;
var test = file.slice(0,4);
expect(test).toContain("PNG");
};
reader.readAsBinaryString(blob);
var parts = [blob,"test", new ArrayBuffer(blob.size)];
var file = new File(parts,"blue-pin.png",{
lastModified: new Date(0),
type: "image/png"
});
var formNode = {
elements:[
{type:"file",
files:[file]}
]
};
g_offlineFeaturesManager.initAttachments(function(success){
expect(success).toBe(true);
if(success){
g_featureLayers[0].addAttachment(/* objectid */1,/* form node */ formNode,function(event){
expect(event.attachmentId).toBe(-4);
expect(event.objectId).toBe(1);
expect(event.success).toBe(true);
//console.log("ATTACHMENTS: " + JSON.stringify(event));
done();
},function(error){
expect(error).toBe(true); // we want to fail if there is an error!
done();
});
}
});
}
else
{
console.log("Test attachments failed");
}
};
xhr.onerror = function(e)
{
console.log("Test attachments failed: " + JSON.stringify(e));
};
xhr.send(null);
});
// Change the image to a GIF
async.it("Update image attachment", function(done){
var xhr = new XMLHttpRequest();
xhr.open("GET","../samples/images/loading.gif",true);
xhr.responseType = "blob";
xhr.onload = function()
{
if( xhr.status === 200)
{
var blob = new Blob([this.response],{type: 'image/png'});
// Verify our image is a GIF file!
var reader = new FileReader();
reader.onload = function (evt) {
file = evt.target.result;
var test = file.slice(0,6);
expect(test).toContain("GIF89a");
};
reader.readAsBinaryString(blob);
var parts = [blob,"test", new ArrayBuffer(blob.size)];
var file = new File(parts,"loading.gif",{
lastModified: new Date(0),
type: "image/png"
});
var formNode = {
elements:[
{type:"file",
files:[file]}
]
};
g_featureLayers[0].updateAttachment(/* objectid */1,/* attachmentId */-4,/* form node */ formNode,function(event){
expect(event.attachmentId).toBe(-4);
expect(event.objectId).toBe(1);
expect(event.success).toBe(true);
//console.log("ATTACHMENTS: " + JSON.stringify(event));
done();
},function(error){
expect(error).toBe(true); // we want to fail if there is an error!
done();
});
}
else
{
console.log("Test attachments failed");
}
};
xhr.onerror = function(e)
{
console.log("Test attachments failed: " + JSON.stringify(e));
};
xhr.send(null);
});
async.it("Delete image attachment", function(done){
g_featureLayers[0].deleteAttachments(1,[-4],function(event) {
expect(event[0].attachmentId).toBe(-4);
expect(event[0].objectId).toBe(1);
expect(event[0].success).toBe(true);
console.log("DELETE DELETE " + JSON.stringify(event));
done();
}, function(error) {
expect(error).toBe(true);
done();
});
});
});
describe("Test PhantomGraphicsLayer using editsStore directly", function()
{
@ -586,15 +813,15 @@ describe("Offline Editing", function()
g_editsStore.getPhantomGraphicsArray(function(results,errors){
// Should be the same size as the number of edits!!
expect(results.length).toBe(8);
expect(results.length).toBe(5);
expect(results[0].id).toBe("phantom-layer|@|-1");
//expect(results[1].id).toBe("phantom-layer|@|-2");
expect(results[1].id).toBe("phantom-layer|@|-3");
expect((results[2].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((results[3].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((results[4].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((results[5].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((results[6].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
//expect((results[5].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
//expect((results[6].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
//expect((results[8].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect(errors).toBe("end");
done();
@ -606,15 +833,15 @@ describe("Offline Editing", function()
g_featureLayers[0].getPhantomGraphicsArray(function(result,array){
expect(result).toBe(true);
expect(typeof array).toBe("object");
expect(array.length).toBe(8);
expect(array.length).toBe(5);
expect(array[0].id).toBe("phantom-layer|@|-1");
//expect(results[1].id).toBe("phantom-layer|@|-2");
expect(array[1].id).toBe("phantom-layer|@|-3");
expect((array[2].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((array[3].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((array[4].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((array[5].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((array[6].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
//expect((array[5].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
//expect((array[6].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
//expect((results[8].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
done();
});
@ -633,17 +860,16 @@ describe("Offline Editing", function()
g_editsStore._getPhantomGraphicsArraySimple(function(results,errors){
// Should be the previous size + 1 additional phantom graphic.
expect(results.length).toBe(9);
expect(results.length).toBe(6);
expect(results[0]).toBe("phantom-layer|@|-1");
//expect(results[1]).toBe("phantom-layer|@|-2");
expect(results[1]).toBe("phantom-layer|@|-3");
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]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((results[5]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((results[6]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((results[7]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect(results[8]).toBe("phantom-layer|@|test001");
//expect((results[5]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
//expect((results[6]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
//expect((results[7]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect(results[5]).toBe("phantom-layer|@|test001");
expect(errors).toBe("end");
done();
})
@ -670,19 +896,19 @@ describe("Offline Editing", function()
g_editsStore._getPhantomGraphicsArraySimple(function(results,errors){
// We added two phantom graphics to previous result
expect(results.length).toBe(11);
expect(results.length).toBe(8);
expect(results[0]).toBe("phantom-layer|@|-1");
//expect(results[1]).toBe("phantom-layer|@|-2");
expect(results[1]).toBe("phantom-layer|@|-3");
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]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((results[5]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((results[6]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((results[7]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect(results[8]).toBe("phantom-layer|@|test001");
expect(results[9]).toBe("phantom-layer|@|test002");
expect(results[10]).toBe("phantom-layer|@|test003");
//expect((results[5]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
//expect((results[6]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
//expect((results[7]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect(results[5]).toBe("phantom-layer|@|test001");
expect(results[6]).toBe("phantom-layer|@|test002");
expect(results[7]).toBe("phantom-layer|@|test003");
expect(errors).toBe("end");
done();
})
@ -695,18 +921,18 @@ describe("Offline Editing", function()
g_editsStore._getPhantomGraphicsArraySimple(function(results,errors){
// We remove one graphic from the previous result of 12
expect(results.length).toBe(10);
expect(results.length).toBe(7);
expect(results[0]).toBe("phantom-layer|@|-1");
//expect(results[1]).toBe("phantom-layer|@|-2");
expect(results[1]).toBe("phantom-layer|@|-3");
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]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((results[5]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((results[6]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((results[7]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect(results[8]).toBe("phantom-layer|@|test002");
expect(results[9]).toBe("phantom-layer|@|test003");
//expect((results[5]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
//expect((results[6]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
//expect((results[7]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect(results[5]).toBe("phantom-layer|@|test002");
expect(results[6]).toBe("phantom-layer|@|test003");
expect(errors).toBe("end");
done();
})
@ -753,17 +979,17 @@ describe("Offline Editing", function()
g_editsStore._getPhantomGraphicsArraySimple(function(results,errors){
// We remove one graphic from the previous result and should now be @ 9
expect(results.length).toBe(9);
expect(results.length).toBe(6);
//expect(results[0]).toBe("phantom-layer|@|-1");
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]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((results[5]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect((results[6]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
expect(results[7]).toBe("phantom-layer|@|test002");
expect(results[8]).toBe("phantom-layer|@|test003");
//expect((results[4]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
//expect((results[5]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
//expect((results[6]).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();
})
@ -855,17 +1081,17 @@ describe("Offline Editing", 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(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);
//expect(g_featureLayers[1].graphics.length).toBe(3);
//expect(g_featureLayers[2].graphics.length).toBe(0);
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 " + JSON.stringify(array))
expect(array.length).toBe(8);
expect(array.length).toBe(5);
done();
});
});
@ -889,7 +1115,7 @@ describe("Offline Editing", function()
//console.log("RESPONSES " + JSON.stringify(responses) + ", " + JSON.stringify(results))
expect(Object.keys(results.features.responses).length).toBe(8);
expect(Object.keys(results.features.responses).length).toBe(5);
for (var key in results.features.responses) {
var response = results.features.responses[key];
@ -926,20 +1152,15 @@ describe("Offline Editing", function()
describe("After online", function(){
async.it("After online - verify feature layer graphic counts",function(done){
// all of them are positive
expect(getObjectIds(g_featureLayers[0].graphics).filter(function(id){ return id<0; })).toEqual([-2]);
expect(getObjectIds(g_featureLayers[1].graphics).filter(function(id){ return id<0; })).toEqual([]);
expect(getObjectIds(g_featureLayers[0].graphics).filter(function(id){ return id<0; })).toEqual([-2,-3]);
//expect(getObjectIds(g_featureLayers[1].graphics).filter(function(id){ return id<0; })).toEqual([]);
expect(g_featureLayers[0].graphics.length).toBe(5);
expect(g_featureLayers[1].graphics.length).toBe(3);
//expect(g_featureLayers[1].graphics.length).toBe(3);
countFeatures(g_featureLayers[0], function(success,result)
{
expect(success).toBeTruthy();
expect(result.count).toBe(4);
countFeatures(g_featureLayers[1], function(success,result)
{
expect(success).toBeTruthy();
expect(result.count).toBe(3);
done();
});
done();
});
});