Merge pull request #337 from andygup/goonline

v2.8
This commit is contained in:
Andy 2015-05-05 12:02:35 -06:00
commit 1fa1bb8c1a
18 changed files with 626 additions and 280 deletions

View File

@ -1,5 +1,19 @@
# offline-editor-js - Changelog
## Version 2.8 - May 4, 2015
This release focused on updating full offline editing capabilities. Recommended update. No breaking changes.
**Enhancements**
* Added functionality to `offlineFeaturesManager.js` to detect and handle when a feature layer is created using a feature collection.
* Addresses browser changes in Chrome 42.x and Firefox 37.x with respect to how they handle HTML/JS apps when going offline and
then transitioning back online.
* Updated `appcache-features.html` sample.
**Bug Fix**
* Closes #336 - problem with appcache-features sample. The application now correctly syncs when going back online after
a full offline restart. It also contains improvements in how the app life-cycle is handled.
## Version 2.7.1 - April 29, 2015
This release has enhancements and has no breaking changes.

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
/*! offline-editor-js - v2.7.1 - 2015-04-29
/*! offline-editor-js - v2.8 - 2015-05-05
* Copyright (c) 2015 Environmental Systems Research Institute, Inc.
* Apache License*/
/*jshint -W030 */
@ -14,17 +14,19 @@ define([
"esri/config",
"esri/layers/GraphicsLayer",
"esri/graphic",
"esri/request",
"esri/symbols/SimpleMarkerSymbol",
"esri/symbols/SimpleLineSymbol",
"esri/symbols/SimpleFillSymbol",
"esri/urlUtils"],
function (Evented, Deferred, all, declare, array, domAttr, domStyle, query,
esriConfig, GraphicsLayer, Graphic, SimpleMarkerSymbol, SimpleLineSymbol, SimpleFillSymbol, urlUtils) {
esriConfig, GraphicsLayer, Graphic, esriRequest, SimpleMarkerSymbol, SimpleLineSymbol, SimpleFillSymbol, urlUtils) {
"use strict";
return declare("O.esri.Edit.OfflineFeaturesManager", [Evented],
{
_onlineStatus: "online",
_featureLayers: {},
_featureCollectionUsageFlag: false, // if a feature collection was used to create the feature layer.
_editStore: new O.esri.Edit.EditStore(),
ONLINE: "online", // all edits will directly go to the server
@ -40,7 +42,7 @@ define([
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.
// NOTE: attachments don't have the same issues as Graphics as related to UIDs (e.g. the need for DB_UID).
// You can manually create a graphic, but it would be very rare for someone to
// manually create an attachment. So, we don't provide a public property for
// the attachments database UID.
@ -53,7 +55,8 @@ define([
EDITS_SENT_ERROR: "edits-sent-error", // ...there was a problem with one or more edits!
ALL_EDITS_SENT: "all-edits-sent", // ...after going online and there are no pending edits in the queue
ATTACHMENT_ENQUEUED: "attachment-enqueued",
ATTACHMENTS_SENT: "attachments-sent"
ATTACHMENTS_SENT: "attachments-sent",
EXTEND_COMPLETE: "extend-complete" // ...when the libary has completed its initialization
},
/**
@ -101,23 +104,44 @@ define([
* @returns deferred
*/
extend: function (layer, callback, dataStore) {
var extendPromises = []; // deferred promises related to initializing this method
var self = this;
layer.offlineExtended = true; // to identify layer has been extended
// NOTE: At v2.6.1 we've discovered that not all feature layers support objectIdField.
// However, we want to try to be consistent here with how the library is managing Ids.
// So, we force the layer.objectIdField to DB_UID. This should be consistent with
// However, to try to be consistent here with how the library is managing Ids
// we force the layer.objectIdField to DB_UID. This should be consistent with
// how esri.Graphics assign a unique ID to a graphic. If it is not, then this
// library will break and we'll have to re-architect how we manage UIDs.
// library will break and we'll have to re-architect how it manages UIDs.
layer.objectIdField = this.DB_UID;
var url = null;
// There have been reproducible use cases showing when a browser is restarted offline that
// for some reason the layer.url may be undefined.
// This is an attempt to minimize the possibility of that situation causing errors.
if(layer.url) {
url = layer.url;
// we keep track of the FeatureLayer object
this._featureLayers[layer.url] = layer;
}
// This is a potentially brittle solution to detecting if a feature layer collection
// was used to create the feature layer.
// Is there a better way??
if(layer._mode.featureLayer.hasOwnProperty("_collection")){
// This means a feature collection was used to create the feature layer and it will
// require different handling when running applyEdit()
this._featureCollectionUsageFlag = true;
}
// Initialize the database as well as set offline data.
if(!this._editStore._isDBInit) {
this._initializeDB(dataStore,callback);
extendPromises.push(this._initializeDB(dataStore, url));
}
// we keep track of the FeatureLayer object
this._featureLayers[layer.url] = layer;
// replace the applyEdits() method
layer._applyEdits = layer.applyEdits;
@ -135,7 +159,7 @@ define([
3. remove an attachment that is already in the server... (DONE)
4. remove an attachment that is not in the server yet (DONE)
5. update an existing attachment to an existing feature (DONE)
6. update a new attachment (NOT YET)
6. update a new attachment (DONE)
concerns:
- manage the relationship between offline features and attachments: what if the user wants to add
@ -143,9 +167,6 @@ define([
the feature is sent to the server and receives a final objectid we replace the temporary negative id
by its final objectid (DONE)
- what if the user deletes an offline feature that had offline attachments? we need to discard the attachment (DONE)
pending tasks:
- check for hasAttachments attribute in the FeatureLayer (NOT YET)
*/
//
@ -284,7 +305,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;
}
@ -468,8 +489,7 @@ define([
all(promises).then(function (r) {
// Make sure all edits were successful. If not throw an error.
var success = true;
var length = r.length;
for (var v = 0; v < length; v++) {
for (var v = 0; v < r.length; v++) {
if (r[v] === false) {
success = false;
}
@ -723,7 +743,7 @@ define([
/* internal methods */
/**
* Pushes an DELETE request to the database after it's been validated
* Pushes a DELETE request to the database after it's been validated
* @param layer
* @param deleteEdit
* @param operation
@ -1035,6 +1055,33 @@ define([
_initPhantomLayer();
// We are currently only passing in a single deferred.
all(extendPromises).then(function (r) {
if(r.length === 0){
callback(true, null);
}
else if(r[0].success && !url){
// This functionality is specifically for offline restarts
// and attempts to retrieve a feature layer url.
// It's a hack because layer.toJson() doesn't convert layer.url.
this._editStore.getFeatureLayerJSON(function(success,message){
if(success) {
this._featureLayers[message.__featureLayerURL] = layer;
layer.url = message.__featureLayerURL;
callback(true, null);
}
else {
console.error("getFeatureLayerJSON() failed.");
callback(false, message);
}
}.bind(this));
}
else if(r[0].success){
callback(true, null);
}
}.bind(this));
}, // extend
/**
@ -1054,7 +1101,7 @@ define([
console.log("offlineFeaturesManager going online");
this._onlineStatus = this.RECONNECTING;
this._replayStoredEdits(function (success, responses) {
var result = {features: {success: success, responses: responses}};
var result = {success: success, responses: responses};
this._onlineStatus = this.ONLINE;
if (this.attachmentsStore != null) {
console.log("sending attachments");
@ -1105,13 +1152,16 @@ define([
*/
getFeatureLayerJSONDataStore: function(callback){
if(!this._editStore._isDBInit){
this._initializeDB(null,function(success) {
if(success){
this._initializeDB(null,null).then(function(result){
if(result.success){
this._editStore.getFeatureLayerJSON(function(success,message){
callback(success,message);
});
}
}.bind(this));
}.bind(this), function(err){
callback(false, err);
});
}
else {
this._editStore.getFeatureLayerJSON(function(success,message){
@ -1123,12 +1173,16 @@ define([
/* internal methods */
/**
* Intialize the database and push featureLayer JSON to DB if required
* Initialize the database and push featureLayer JSON to DB if required.
* NOTE: also stores feature layer url in hidden dataStore property dataStore.__featureLayerURL.
* @param dataStore Object
* @param url Feature Layer's url. This is used by this library for internal feature identification.
* @param callback
* @private
*/
_initializeDB: function(dataStore,callback){
//_initializeDB: function(dataStore,url,callback){
_initializeDB: function(dataStore,url){
var deferred = new Deferred();
var editStore = this._editStore;
@ -1153,22 +1207,32 @@ define([
////////////////////////////////////////////////////
if (typeof dataStore === "object" && result === true && (dataStore !== undefined) && (dataStore !== null)) {
// Add a hidden property to hold the feature layer's url
// When converting a feature layer to json (layer.toJson()) we lose this information.
// This library needs to know the feature layer url.
if(url) {
dataStore.__featureLayerURL = url;
}
editStore.pushFeatureLayerJSON(dataStore, function (success, err) {
if (success) {
callback(true, null);
deferred.resolve({success:true, error: null});
}
else {
callback(false, err);
deferred.reject({success:false, error: err});
}
});
}
else if(result){
callback(true, null);
deferred.resolve({success:true, error: null});
}
else{
callback(false, error);
deferred.reject({success:false, error: null});
}
});
return deferred;
},
/**
@ -1416,23 +1480,7 @@ define([
attachments.forEach(function (attachment) {
console.log("sending attachment", attachment.id, "to feature", attachment.featureId);
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;
//}
//);
var uploadAttachmentComplete = this._uploadAttachment(attachment);
promises.push(uploadAttachmentComplete);
}, this);
console.log("promises", promises.length);
@ -1447,20 +1495,6 @@ define([
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);
@ -1508,10 +1542,6 @@ 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.
@ -1550,7 +1580,13 @@ define([
break;
}
promises[n] = that._internalApplyEdits(layer, tempArray[n].id, tempObjectIds, adds, updates, deletes);
if(that._featureCollectionUsageFlag){
// Note: when the feature layer is created with a feature collection we have to handle applyEdits() differently
promises[n] = that._internalApplyEditsFeatureCollection(layer, tempArray[n].id, tempObjectIds, adds, updates, deletes);
}
else {
promises[n] = that._internalApplyEdits(layer, tempArray[n].id, tempObjectIds, adds, updates, deletes);
}
}
// wait for all requests to finish
@ -1735,7 +1771,7 @@ define([
},
/**
* Executes the _applyEdits() method
* Executes the _applyEdits() method when a feature layer is created using a REST endpoint
* @param layer
* @param id the unique id that identifies the Graphic in the database
* @param tempObjectIds
@ -1790,6 +1826,126 @@ define([
return dfd.promise;
},
/**
* Executes the _applyEdits() method when a feature layer is created using a feature collection.
* This works around specific behaviors in esri.layers.FeatureLayer when using the pattern
* new FeatureLayer(featureCollectionObject).
*
* Details on the specific behaviors can be found here:
* https://developers.arcgis.com/javascript/jsapi/featurelayer-amd.html#featurelayer2
*
* @param layer
* @param id
* @param tempObjectIds
* @param adds
* @param updates
* @param deletes
* @returns {*|r}
* @private
*/
_internalApplyEditsFeatureCollection: function (layer, id, tempObjectIds, adds, updates, deletes) {
var dfd = new Deferred();
this._makeEditRequest(layer.url, adds, updates, deletes,
function (addResults, updateResults, deleteResults) {
layer._phantomLayer.clear();
var newObjectIds = addResults.map(function (r) {
return r.objectId;
});
// We use a different pattern if the attachmentsStore is valid and the layer has attachments
if (layer._attachmentsStore != null && layer.hasAttachments && tempObjectIds.length > 0) {
layer._replaceFeatureIds(tempObjectIds, newObjectIds, function (success) {
dfd.resolve({
id: id,
layer: layer.url,
tempId: tempObjectIds, // let's us internally match an ADD to it's new ObjectId
addResults: addResults,
updateResults: updateResults,
deleteResults: deleteResults
}); // wrap three arguments in a single object
});
}
else {
dfd.resolve({
id: id,
layer: layer.url,
tempId: tempObjectIds, // let's us internally match an ADD to it's new ObjectId
addResults: addResults,
updateResults: updateResults,
deleteResults: deleteResults
}); // wrap three arguments in a single object
}
},
function (error) {
layer.onEditsComplete = layer.__onEditsComplete;
delete layer.__onEditsComplete;
dfd.reject(error);
}
);
return dfd.promise;
},
/**
* Used when a feature layer is created with a feature collection.
*
* In the current version of the ArcGIS JSAPI 3.12+ the applyEdit() method doesn't send requests
* to the server when a feature layer is created with a feature collection.
*
* The use case for using this is: clean start app > go offline and make edits > offline restart browser >
* go online.
*
* @param url
* @param adds
* @param updates
* @param deletes
* @returns {*|r}
* @private
*/
_makeEditRequest: function(url,adds, updates, deletes, callback, errback) {
//var dfd = new Deferred();
var data = new FormData();
data.append("f", "json");
if(adds.length > 0) {
data.append("adds", JSON.stringify(adds));
}
if(updates.length > 0) {
data.append("updates", JSON.stringify(updates));
}
if(deletes.length > 0) {
data.append("deletes", JSON.stringify(deletes));
}
var req = new XMLHttpRequest();
req.open("POST", url + "/applyEdits", true);
req.onload = function()
{
if( req.status === 200 && req.responseText !== "")
{
var obj = JSON.parse(this.response);
//dfd.resolve(obj);
callback(obj.addResults, obj.updateResults, obj.deleteResults);
//callback(this.response);
//Object.keys(this.response).forEach(function(key) {
// console.log(key, this.response[key]);
//});
}
};
req.onerror = function(e)
{
console.log("_getTileInfoPrivate failed: " + e);
errback(e);
//dfd.error(e);
};
req.send(data);
//return dfd.promise;
},
/**
* Deprecated @ v2.5. Internal-use only
* @returns {string}
@ -2784,7 +2940,7 @@ O.esri.Edit.EditStore = function () {
this._db = event.target.result;
this._isDBInit = true;
console.log("database opened successfully");
callback(true);
callback(true, null);
}.bind(this);
};

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
/*! offline-editor-js - v2.7.1 - 2015-04-29
/*! offline-editor-js - v2.8 - 2015-05-05
* Copyright (c) 2015 Environmental Systems Research Institute, Inc.
* Apache License*/
define([

View File

@ -1,4 +1,4 @@
/*! offline-editor-js - v2.7.1 - 2015-04-29
/*! offline-editor-js - v2.8 - 2015-05-05
* 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.7.1 - 2015-04-29
/*! offline-editor-js - v2.8 - 2015-05-05
* 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.7.1 - 2015-04-29
/*! offline-editor-js - v2.8 - 2015-05-05
* Copyright (c) 2015 Environmental Systems Research Institute, Inc.
* Apache License*/
/**

View File

@ -75,7 +75,9 @@ NOTE: You can also monitor standard ArcGIS API for JavaScript layer events using
```
**Step 4** After the `layers-add-result` event fires extend the feature layer using the `extend()` method. Optionally, if you are building a fully offline app then you will also need to set the `dataStore` property in the constructor.
**Step 4** After the `layers-add-result` event fires extend the feature layer using the `extend()` method. Optionally, if you are building a fully offline app then you will also need to set the `dataStore` property in the constructor.
Note: the `layer.extend()` callback only indicates that the edits database has been successfully initialized.
```js
@ -86,7 +88,7 @@ NOTE: You can also monitor standard ArcGIS API for JavaScript layer events using
// options.graphics = JSON.stringify(layer1.toJson());
// options.zoom = map.getZoom();
offlineFeaturesManager.extend(layer1,function(success,error){
offlineFeaturesManager.extend(layer1,function(success, error){
if(success){
console.log("layer1 has been extended for offline use.");
}
@ -95,6 +97,34 @@ NOTE: You can also monitor standard ArcGIS API for JavaScript layer events using
```
When working with fully offline browser restarts you should wait until the layer has been successfully extended before forcing the library to go back online. The workflow for this coding pattern is you start out online > offline > browser restart > then back online.
```js
offlineFeaturesManager.extend(layer1, function(success, error) {
if(success) {
// If the app is online then force offlineFeaturesManager to its online state
// This will force the library to check for pending edits and attempt to
// resend them to the Feature Service.
if(_isOnline){ // Check if app is online or offline
offlineFeaturesManager.goOnline(function(result){
if(!result.success){
alert("There was a problem when attempting to go back online.");
}
else {
// Do somthing good!
}
});
}
else {
offlineFeaturesManager.goOffline();
}
}
});
```
The `dataStore` property is an object that is used to store any data related to your app that will assist in restoring it and any feature layers after a full offline browser restart. The `dataStore` object has one reserved key and that is `id`. If you overwrite the `id` key the application will fail to update the `dataStore` object correctly. Here is an example of one possible `dataStore` object:
```js
@ -160,9 +190,9 @@ Force the library to return to an online condition. If there are pending edits,
```js
function goOnline()
{
offlineFeaturesManager.goOnline(function(success,results)
offlineFeaturesManager.goOnline(function(result)
{
if(success){
if(result.success){
//Modify user inteface depending on success/failure
}
});

View File

@ -38,11 +38,11 @@ OfflineFeaturesManager provides the following functionality.
Methods | Returns | Description
--- | --- | ---
`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.
`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.<br><br> `Callback` indicates the layer has been extended. <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, 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.
`goOnline(callback)` | No attachments: `callback( {success: boolean, responses: Object } )`<br><br> With attachments: `callback( {success: boolean, responses: uploadedResponses, dbResponses: dbResponses })` | Forces library to return to an online state. If there are pending edits, an attempt will be made to sync them with the remote feature server. Callback function will be called when resync process is done. <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.
`getFeatureLayerJSONDataStore` | `callback( boolean, Object)` | **New @ v2.7.1** Returns the feature layer's dataStore Object.
`getFeatureLayerJSONDataStore( callback )` | `callback( boolean, Object)` | **New @ v2.7.1** Returns the feature layer's dataStore Object.
`getReadableEdit()` | String | **DEPRECATED** @ v2.5. A string value representing human readable information on pending edits. Use `featureLayer.getAllEditsArray()`.

View File

@ -957,7 +957,7 @@ O.esri.Edit.EditStore = function () {
this._db = event.target.result;
this._isDBInit = true;
console.log("database opened successfully");
callback(true);
callback(true, null);
}.bind(this);
};

View File

@ -11,17 +11,19 @@ define([
"esri/config",
"esri/layers/GraphicsLayer",
"esri/graphic",
"esri/request",
"esri/symbols/SimpleMarkerSymbol",
"esri/symbols/SimpleLineSymbol",
"esri/symbols/SimpleFillSymbol",
"esri/urlUtils"],
function (Evented, Deferred, all, declare, array, domAttr, domStyle, query,
esriConfig, GraphicsLayer, Graphic, SimpleMarkerSymbol, SimpleLineSymbol, SimpleFillSymbol, urlUtils) {
esriConfig, GraphicsLayer, Graphic, esriRequest, SimpleMarkerSymbol, SimpleLineSymbol, SimpleFillSymbol, urlUtils) {
"use strict";
return declare("O.esri.Edit.OfflineFeaturesManager", [Evented],
{
_onlineStatus: "online",
_featureLayers: {},
_featureCollectionUsageFlag: false, // if a feature collection was used to create the feature layer.
_editStore: new O.esri.Edit.EditStore(),
ONLINE: "online", // all edits will directly go to the server
@ -37,7 +39,7 @@ define([
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.
// NOTE: attachments don't have the same issues as Graphics as related to UIDs (e.g. the need for DB_UID).
// You can manually create a graphic, but it would be very rare for someone to
// manually create an attachment. So, we don't provide a public property for
// the attachments database UID.
@ -50,7 +52,8 @@ define([
EDITS_SENT_ERROR: "edits-sent-error", // ...there was a problem with one or more edits!
ALL_EDITS_SENT: "all-edits-sent", // ...after going online and there are no pending edits in the queue
ATTACHMENT_ENQUEUED: "attachment-enqueued",
ATTACHMENTS_SENT: "attachments-sent"
ATTACHMENTS_SENT: "attachments-sent",
EXTEND_COMPLETE: "extend-complete" // ...when the libary has completed its initialization
},
/**
@ -98,23 +101,44 @@ define([
* @returns deferred
*/
extend: function (layer, callback, dataStore) {
var extendPromises = []; // deferred promises related to initializing this method
var self = this;
layer.offlineExtended = true; // to identify layer has been extended
// NOTE: At v2.6.1 we've discovered that not all feature layers support objectIdField.
// However, we want to try to be consistent here with how the library is managing Ids.
// So, we force the layer.objectIdField to DB_UID. This should be consistent with
// However, to try to be consistent here with how the library is managing Ids
// we force the layer.objectIdField to DB_UID. This should be consistent with
// how esri.Graphics assign a unique ID to a graphic. If it is not, then this
// library will break and we'll have to re-architect how we manage UIDs.
// library will break and we'll have to re-architect how it manages UIDs.
layer.objectIdField = this.DB_UID;
var url = null;
// There have been reproducible use cases showing when a browser is restarted offline that
// for some reason the layer.url may be undefined.
// This is an attempt to minimize the possibility of that situation causing errors.
if(layer.url) {
url = layer.url;
// we keep track of the FeatureLayer object
this._featureLayers[layer.url] = layer;
}
// This is a potentially brittle solution to detecting if a feature layer collection
// was used to create the feature layer.
// Is there a better way??
if(layer._mode.featureLayer.hasOwnProperty("_collection")){
// This means a feature collection was used to create the feature layer and it will
// require different handling when running applyEdit()
this._featureCollectionUsageFlag = true;
}
// Initialize the database as well as set offline data.
if(!this._editStore._isDBInit) {
this._initializeDB(dataStore,callback);
extendPromises.push(this._initializeDB(dataStore, url));
}
// we keep track of the FeatureLayer object
this._featureLayers[layer.url] = layer;
// replace the applyEdits() method
layer._applyEdits = layer.applyEdits;
@ -132,7 +156,7 @@ define([
3. remove an attachment that is already in the server... (DONE)
4. remove an attachment that is not in the server yet (DONE)
5. update an existing attachment to an existing feature (DONE)
6. update a new attachment (NOT YET)
6. update a new attachment (DONE)
concerns:
- manage the relationship between offline features and attachments: what if the user wants to add
@ -140,9 +164,6 @@ define([
the feature is sent to the server and receives a final objectid we replace the temporary negative id
by its final objectid (DONE)
- what if the user deletes an offline feature that had offline attachments? we need to discard the attachment (DONE)
pending tasks:
- check for hasAttachments attribute in the FeatureLayer (NOT YET)
*/
//
@ -281,7 +302,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;
}
@ -465,8 +486,7 @@ define([
all(promises).then(function (r) {
// Make sure all edits were successful. If not throw an error.
var success = true;
var length = r.length;
for (var v = 0; v < length; v++) {
for (var v = 0; v < r.length; v++) {
if (r[v] === false) {
success = false;
}
@ -720,7 +740,7 @@ define([
/* internal methods */
/**
* Pushes an DELETE request to the database after it's been validated
* Pushes a DELETE request to the database after it's been validated
* @param layer
* @param deleteEdit
* @param operation
@ -1032,6 +1052,33 @@ define([
_initPhantomLayer();
// We are currently only passing in a single deferred.
all(extendPromises).then(function (r) {
if(r.length === 0){
callback(true, null);
}
else if(r[0].success && !url){
// This functionality is specifically for offline restarts
// and attempts to retrieve a feature layer url.
// It's a hack because layer.toJson() doesn't convert layer.url.
this._editStore.getFeatureLayerJSON(function(success,message){
if(success) {
this._featureLayers[message.__featureLayerURL] = layer;
layer.url = message.__featureLayerURL;
callback(true, null);
}
else {
console.error("getFeatureLayerJSON() failed.");
callback(false, message);
}
}.bind(this));
}
else if(r[0].success){
callback(true, null);
}
}.bind(this));
}, // extend
/**
@ -1051,7 +1098,7 @@ define([
console.log("offlineFeaturesManager going online");
this._onlineStatus = this.RECONNECTING;
this._replayStoredEdits(function (success, responses) {
var result = {features: {success: success, responses: responses}};
var result = {success: success, responses: responses};
this._onlineStatus = this.ONLINE;
if (this.attachmentsStore != null) {
console.log("sending attachments");
@ -1102,13 +1149,16 @@ define([
*/
getFeatureLayerJSONDataStore: function(callback){
if(!this._editStore._isDBInit){
this._initializeDB(null,function(success) {
if(success){
this._initializeDB(null,null).then(function(result){
if(result.success){
this._editStore.getFeatureLayerJSON(function(success,message){
callback(success,message);
});
}
}.bind(this));
}.bind(this), function(err){
callback(false, err);
});
}
else {
this._editStore.getFeatureLayerJSON(function(success,message){
@ -1120,12 +1170,16 @@ define([
/* internal methods */
/**
* Intialize the database and push featureLayer JSON to DB if required
* Initialize the database and push featureLayer JSON to DB if required.
* NOTE: also stores feature layer url in hidden dataStore property dataStore.__featureLayerURL.
* @param dataStore Object
* @param url Feature Layer's url. This is used by this library for internal feature identification.
* @param callback
* @private
*/
_initializeDB: function(dataStore,callback){
//_initializeDB: function(dataStore,url,callback){
_initializeDB: function(dataStore,url){
var deferred = new Deferred();
var editStore = this._editStore;
@ -1150,22 +1204,32 @@ define([
////////////////////////////////////////////////////
if (typeof dataStore === "object" && result === true && (dataStore !== undefined) && (dataStore !== null)) {
// Add a hidden property to hold the feature layer's url
// When converting a feature layer to json (layer.toJson()) we lose this information.
// This library needs to know the feature layer url.
if(url) {
dataStore.__featureLayerURL = url;
}
editStore.pushFeatureLayerJSON(dataStore, function (success, err) {
if (success) {
callback(true, null);
deferred.resolve({success:true, error: null});
}
else {
callback(false, err);
deferred.reject({success:false, error: err});
}
});
}
else if(result){
callback(true, null);
deferred.resolve({success:true, error: null});
}
else{
callback(false, error);
deferred.reject({success:false, error: null});
}
});
return deferred;
},
/**
@ -1413,23 +1477,7 @@ define([
attachments.forEach(function (attachment) {
console.log("sending attachment", attachment.id, "to feature", attachment.featureId);
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;
//}
//);
var uploadAttachmentComplete = this._uploadAttachment(attachment);
promises.push(uploadAttachmentComplete);
}, this);
console.log("promises", promises.length);
@ -1444,20 +1492,6 @@ define([
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);
@ -1505,10 +1539,6 @@ 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.
@ -1547,7 +1577,13 @@ define([
break;
}
promises[n] = that._internalApplyEdits(layer, tempArray[n].id, tempObjectIds, adds, updates, deletes);
if(that._featureCollectionUsageFlag){
// Note: when the feature layer is created with a feature collection we have to handle applyEdits() differently
promises[n] = that._internalApplyEditsFeatureCollection(layer, tempArray[n].id, tempObjectIds, adds, updates, deletes);
}
else {
promises[n] = that._internalApplyEdits(layer, tempArray[n].id, tempObjectIds, adds, updates, deletes);
}
}
// wait for all requests to finish
@ -1732,7 +1768,7 @@ define([
},
/**
* Executes the _applyEdits() method
* Executes the _applyEdits() method when a feature layer is created using a REST endpoint
* @param layer
* @param id the unique id that identifies the Graphic in the database
* @param tempObjectIds
@ -1787,6 +1823,126 @@ define([
return dfd.promise;
},
/**
* Executes the _applyEdits() method when a feature layer is created using a feature collection.
* This works around specific behaviors in esri.layers.FeatureLayer when using the pattern
* new FeatureLayer(featureCollectionObject).
*
* Details on the specific behaviors can be found here:
* https://developers.arcgis.com/javascript/jsapi/featurelayer-amd.html#featurelayer2
*
* @param layer
* @param id
* @param tempObjectIds
* @param adds
* @param updates
* @param deletes
* @returns {*|r}
* @private
*/
_internalApplyEditsFeatureCollection: function (layer, id, tempObjectIds, adds, updates, deletes) {
var dfd = new Deferred();
this._makeEditRequest(layer.url, adds, updates, deletes,
function (addResults, updateResults, deleteResults) {
layer._phantomLayer.clear();
var newObjectIds = addResults.map(function (r) {
return r.objectId;
});
// We use a different pattern if the attachmentsStore is valid and the layer has attachments
if (layer._attachmentsStore != null && layer.hasAttachments && tempObjectIds.length > 0) {
layer._replaceFeatureIds(tempObjectIds, newObjectIds, function (success) {
dfd.resolve({
id: id,
layer: layer.url,
tempId: tempObjectIds, // let's us internally match an ADD to it's new ObjectId
addResults: addResults,
updateResults: updateResults,
deleteResults: deleteResults
}); // wrap three arguments in a single object
});
}
else {
dfd.resolve({
id: id,
layer: layer.url,
tempId: tempObjectIds, // let's us internally match an ADD to it's new ObjectId
addResults: addResults,
updateResults: updateResults,
deleteResults: deleteResults
}); // wrap three arguments in a single object
}
},
function (error) {
layer.onEditsComplete = layer.__onEditsComplete;
delete layer.__onEditsComplete;
dfd.reject(error);
}
);
return dfd.promise;
},
/**
* Used when a feature layer is created with a feature collection.
*
* In the current version of the ArcGIS JSAPI 3.12+ the applyEdit() method doesn't send requests
* to the server when a feature layer is created with a feature collection.
*
* The use case for using this is: clean start app > go offline and make edits > offline restart browser >
* go online.
*
* @param url
* @param adds
* @param updates
* @param deletes
* @returns {*|r}
* @private
*/
_makeEditRequest: function(url,adds, updates, deletes, callback, errback) {
//var dfd = new Deferred();
var data = new FormData();
data.append("f", "json");
if(adds.length > 0) {
data.append("adds", JSON.stringify(adds));
}
if(updates.length > 0) {
data.append("updates", JSON.stringify(updates));
}
if(deletes.length > 0) {
data.append("deletes", JSON.stringify(deletes));
}
var req = new XMLHttpRequest();
req.open("POST", url + "/applyEdits", true);
req.onload = function()
{
if( req.status === 200 && req.responseText !== "")
{
var obj = JSON.parse(this.response);
//dfd.resolve(obj);
callback(obj.addResults, obj.updateResults, obj.deleteResults);
//callback(this.response);
//Object.keys(this.response).forEach(function(key) {
// console.log(key, this.response[key]);
//});
}
};
req.onerror = function(e)
{
console.log("_getTileInfoPrivate failed: " + e);
errback(e);
//dfd.error(e);
};
req.send(data);
//return dfd.promise;
},
/**
* Deprecated @ v2.5. Internal-use only
* @returns {string}

View File

@ -1,6 +1,6 @@
{
"name": "offline-editor-js",
"version": "2.7.1",
"version": "2.8",
"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

@ -143,8 +143,8 @@
<div class="row">
<div class="col-xs-10">
<div class="form form-group btn-group" data-toggle="buttons">
<button class="btn btn-success" id="btn-get-tiles">1. Download Tiles</button>
<button class="btn btn-success" disabled id="btn-online-offline">2. Go Offline</button>
<button class="btn btn-primary" id="btn-get-tiles">1. Download Tiles</button>
<button class="btn btn-primary" id="btn-online-offline">2. Go Offline</button>
</div>
<span class="span-pending">Pending Edits <span id="span-pending-edits" class="badge">0</span></span>
<img id="loader-gif" src="images/loading.gif"/>
@ -185,6 +185,7 @@
var map = null;
var _isOnline = true;
var defaultSymbol;
var _listener;
// Variables for editing handling
var currentFeature, busStopFeatureLayer = null, offlineFeaturesManager = null;
@ -221,7 +222,7 @@
* This is a utility check to 100% validate if the application is online or
* offline prior to launching any map functionality.
*/
verifyOnline(function(result) {
validateOnline(function(result) {
if(result) {
_isOnline = true;
setUIOnline();
@ -271,9 +272,6 @@
*/
function startMap() {
//Make sure map shows up after a browser refresh
Offline.check();
tileLayer = new O.esri.Tiles.OfflineTileEnablerLayer("http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer",function(evt){
console.log("Tile Layer initialized for offline. App state is: " + Offline.state);
},_isOnline);
@ -288,6 +286,10 @@
sliderStyle: "small"
});
map.on("load",function(evt) {
_currentExtent = evt.map.extent;
});
// Add our offline enabled tile layer to the map
map.addLayer(tileLayer);
@ -301,8 +303,7 @@
//on a mobile device.
busStopFeatureLayer.setRenderer(new SimpleRenderer(defaultSymbol));
initFeatureUpdateEndListener();
initMapLoadListener();
_listener = busStopFeatureLayer.on("update-end",initFeatureUpdateEndListener);
map.addLayer(busStopFeatureLayer);
}
@ -315,62 +316,56 @@
*/
function initFeatureUpdateEndListener() {
busStopFeatureLayer.on("update-end",function(evt){
_listener.remove();
//**************************************************
//
// This is where we detect an offline condition
// within the lifecycle of the "mapping" application.
// If we are offline then run our offline
// specific code for reconstituting our map.
//
//**************************************************
//**************************************************
//
// This is where we detect an offline condition
// within the lifecycle of the "mapping" application.
// If we are offline then run our offline
// specific code for reconstituting our map.
//
//**************************************************
//
// Extend the feature layer with offline capabilities.
//
//
// Extend the feature layer with offline capabilities.
//
initOfflineFeaturesMgr();
initOfflineFeaturesMgr();
// If app is online then we ONLY need to extend the feature layer.
if(_isOnline){
extendFeatureLayer(_isOnline, function(success) {
if(success){
// If app is online then we ONLY need to extend the feature layer.
if(_isOnline){
extendFeatureLayer(_isOnline, function(success) {
if(success){
// Set click listeners
on(btnGetTiles,"click",downloadTiles);
on(btnOnlineOffline, 'click', goOnlineOffline);
// Set click listeners
on(btnGetTiles,"click",downloadTiles);
on(btnOnlineOffline, 'click', goOnlineOffline);
initPanZoomListeners();
setFeatureLayerClickHandler();
setModalPopupClickListeners();
}
else{
alert("There was a problem initializing the map for offline.");
}
});
}
// If the app is offline then we need to retrieve the dataStore from OfflineFeaturesManager
// and then extend the feature layer using that information.
else {
loadFeatureLayerOffline(function(success) {
if(success) {
extendFeatureLayer(_isOnline, function(success) {
console.log("Feature Layer extended successfully OFFLINE!");
});
}
else {
alert("There was a problem initializing the map for offline.");
}
});
}
});
}
initPanZoomListeners();
setFeatureLayerClickHandler();
setModalPopupClickListeners();
}
else{
alert("There was a problem initializing the map for offline.");
}
});
}
// If the app is offline then we need to retrieve the dataStore from OfflineFeaturesManager
// and then extend the feature layer using that information.
else {
loadFeatureLayerOffline(function(success) {
if(success) {
extendFeatureLayer(_isOnline, function(success) {
console.log("Feature Layer extended successfully OFFLINE!");
});
}
else {
alert("There was a problem initializing the map for offline.");
}
});
}
function initMapLoadListener() {
map.on("load",function(evt) {
_currentExtent = evt.map.extent;
});
}
/**
@ -404,33 +399,33 @@
function loadFeatureLayerOffline(callback) {
offlineFeaturesManager.getFeatureLayerJSONDataStore(function(success,dataStore) {
if(success){
// Use the feature layer returns from getFeatureDefinition() to reconstitute the layer
busStopFeatureLayer = new FeatureLayer(JSON.parse(dataStore.featureLayerCollection), {
mode: FeatureLayer.MODE_SNAPSHOT,
outFields: ["GlobalID","BSID","ROUTES","STOPNAME"]
});
// We don't have to set any other properties on the layer because we are using it
// in SNAPSHOT mode which downloads all features within the given extent.
busStopFeatureLayer = new FeatureLayer(JSON.parse(dataStore.featureLayerCollection));
if(busStopFeatureLayer.url == null){
busStopFeatureLayer.url = dataStore.featureLayerUrl;
}
// Set the graphic symbols to red diamond to make it easy to click on them
// on a mobile device.
busStopFeatureLayer.setRenderer(new SimpleRenderer(defaultSymbol));
// If/when the update-end event has been thrown then let's finish initializing
var mapListen = map.on("update-end",function(evt) {
console.log("Feature has been added back to the map while offline.");
on(btnGetTiles,"click",downloadTiles);
on(btnOnlineOffline, 'click', goOnlineOffline);
setFeatureLayerClickHandler();
setModalPopupClickListeners();
map.centerAt(dataStore.centerPt);
map.setZoom(dataStore.zoom);
mapListen.remove();
callback(true);
});
map.addLayer(busStopFeatureLayer);
console.log("Feature has been added back to the map while offline.");
on(btnGetTiles,"click",downloadTiles);
on(btnOnlineOffline, 'click', goOnlineOffline);
setFeatureLayerClickHandler();
setModalPopupClickListeners();
map.centerAt(dataStore.centerPt);
map.setZoom(dataStore.zoom);
callback(true);
}
else{
alert("There was a problem retrieving feature layer options object. " + dataStore);
@ -461,16 +456,16 @@
// Refer to "Using the Proxy Page" for more information: https://developers.arcgis.com/en/javascript/jshelp/ags_proxy.html
offlineFeaturesManager.proxyPath = null;
offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_ENQUEUED, editsEnqueued);
offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_ENQUEUED, updateStatus);
offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_SENT, updateStatus);
offlineFeaturesManager.on(offlineFeaturesManager.events.ALL_EDITS_SENT, allEditsSent);
offlineFeaturesManager.on(offlineFeaturesManager.events.ALL_EDITS_SENT, updateStatus);
offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_SENT_ERROR, editsError);
}
function extendFeatureLayer(online,callback){
var featureLayerJSON = null;
if(online === true) {
if(online) {
// This object contains everything we need to restore the map and feature layer after an offline restart
//
@ -488,16 +483,26 @@
console.log("offlineFeaturesManager initialized.");
// This sets listeners to detect if the app goes online or offline.
Offline.check();
Offline.on('up', goOnline);
Offline.on('down', goOffline);
// If the app is online then force offlineFeaturesManager to its online state
// This will force the library to check for pending edits and attempt to
// resend them to the Feature Service.
if(_isOnline == true){
offlineFeaturesManager.goOnline();
}
// If the app is online then force offlineFeaturesManager to its online state
// This will force the library to check for pending edits and attempt to
// resend them to the Feature Service.
if(_isOnline){
offlineFeaturesManager.goOnline(function(result){
if(!result.success){
alert("There was a problem when attempting to go back online.");
}
else {
updateStatus();
}
});
}
else {
offlineFeaturesManager.goOffline();
updateStatus();
}
callback(true);
}
@ -505,7 +510,7 @@
callback(false);
alert("Unable to initialize the database. " + error);
}
},/* This is the optional offline configuration property */featureLayerJSON);
}.bind(this),/* This is the optional offline configuration property */featureLayerJSON);
}
/**
@ -572,10 +577,12 @@
}
function getFeatureLayerJSON() {
return {
"featureLayerCollection": JSON.stringify(busStopFeatureLayer.toJson()),
"zoomLevel": map.getZoom(),
"centerPt" : (map.extent.getCenter()).toJson()
"centerPt" : (map.extent.getCenter()).toJson(),
"featureLayerUrl": busStopFeatureLayer.url
}
}
@ -644,7 +651,6 @@
}
else {
globalState.downloadState = 'downloaded';
btnOnlineOffline.disabled = false;
alert("Tile download complete");
}
@ -669,6 +675,10 @@
loading.style.visibility = "visible";
if(busStopFeatureLayer && busStopFeatureLayer.offlineExtended) {
}
offlineFeaturesManager.goOnline(function(success,error) {
if(error === undefined) {
setUIOnline();
@ -735,33 +745,22 @@
})
}
/**
* Forces a check of the Offline.js state
*/
function forceInternalOfflineCheck() {
Offline.check();
if(Offline.state == "up") {
goOnline();
}
if(Offline.state == "down") {
goOffline();
}
}
/**
* Attempts a manual http request to verify if app is online or offline.
* Use this in conjunction with the offline checker library: offline.min.js
* @param callback
*/
function verifyOnline(callback) {
function validateOnline(callback) {
var req = new XMLHttpRequest();
req.open("GET", "http://esri.github.io/offline-editor-js/samples/images/blue-pin.png?" + (Math.floor(Math.random() * 1000000000)), true);
req.onload = function() {
if( req.status === 200 && req.responseText !== "") {
req = null;
callback(true);
}
else {
console.log("verifyOffline failed");
req = null;
callback(false);
}
};
@ -778,28 +777,19 @@
* ***********************************************å
*/
function editsEnqueued() {
busStopFeatureLayer.pendingEditsCount(function(count) {
pendingEdits.innerHTML = count;
});
}
function allEditsSent(){
busStopFeatureLayer.pendingEditsCount(function(count) {
pendingEdits.innerHTML = count;
});
}
function editsError(evt) {
alert("There was a problem. Not all edits were synced with the server. " + JSON.stringify(evt));
}
function updateStatus() {
busStopFeatureLayer.pendingEditsCount(function(count) {
pendingEdits.innerHTML = count;
});
// Depending on application life cycle busStopFeatureLayer may or may not be extended
// So we have to protect pendingEditsCount() from throwing an "is not a function" error.
if("pendingEditsCount" in busStopFeatureLayer) {
busStopFeatureLayer.pendingEditsCount(function(count) {
pendingEdits.innerHTML = count;
});
}
}
});
</script>

View File

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

View File

@ -631,9 +631,9 @@ describe("Attachments", function()
console.log("went online");
expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE);
expect(listener).toHaveBeenCalled();
expect(result.features.success).toBeTruthy();
expect(result.success).toBeTruthy();
expect(result.attachments.success).toBeTruthy();
expect(Object.keys(result.features.responses).length).toBe(1);
expect(Object.keys(result.responses).length).toBe(1);
expect(Object.keys(result.attachments.responses).length).toBe(4);
var attachmentResults = result.attachments.responses;
@ -644,8 +644,8 @@ describe("Attachments", function()
//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];
expect(result.responses[0]).not.toBeUndefined();
var featureResults = result.responses[0];
expect(featureResults.addResults.length).toBe(1);
expect(featureResults.updateResults.length).toBe(0);
expect(featureResults.deleteResults.length).toBe(0);

View File

@ -1111,14 +1111,14 @@ describe("Offline Editing", function()
console.log("Library is now back online");
expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE);
expect(listener).toHaveBeenCalled();
expect(results.features.success).toBeTruthy();
expect(results.success).toBeTruthy();
//console.log("RESPONSES " + JSON.stringify(responses) + ", " + JSON.stringify(results))
expect(Object.keys(results.features.responses).length).toBe(5);
for (var key in results.features.responses) {
expect(Object.keys(results.responses).length).toBe(5);
for (var key in results.responses) {
var response = results.features.responses[key];
var response = results.responses[key];
console.log("RESPONSE " + JSON.stringify(response))