"use strict"; define([ "edit/editsStore", "edit/attachmentsStore", "dojo/Evented", "dojo/_base/Deferred", "dojo/promise/all", "dojo/_base/declare", "dojo/_base/lang", "dojo/_base/array", "dojo/dom-attr", "dojo/dom-style", "esri/layers/GraphicsLayer", "esri/graphic", "esri/symbols/SimpleMarkerSymbol", "esri/symbols/SimpleLineSymbol", "esri/symbols/SimpleFillSymbol", "esri/request"], function(editsStore, AttachmentsStore, Evented,Deferred,all,declare,lang,array,domAttr,domStyle, GraphicsLayer,Graphic,SimpleMarkerSymbol,SimpleLineSymbol,SimpleFillSymbol, esriRequest) { return declare([Evented], { _onlineStatus: "online", _featureLayers: {}, ONLINE: "online", // all edits will directly go to the server OFFLINE: "offline", // edits will be enqueued RECONNECTING: "reconnecting", // sending stored edits to the server // manager emits event when... events: { EDITS_SENT: 'edits-sent', // ...whenever any edit is actually sent to the server EDITS_ENQUEUED: 'edits-enqueued', // ...when an edit is enqueued (and not sent to the server) 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' }, _checkFileAPIs: function() { if( window.File && window.FileReader && window.FileList && window.Blob ) { console.log("All APIs supported!"); if(!XMLHttpRequest.prototype.sendAsBinary ) { // https://code.google.com/p/chromium/issues/detail?id=35705#c40 XMLHttpRequest.prototype.sendAsBinary = function(datastr) { function byteValue(x) { return x.charCodeAt(0) & 0xff; } var ords = Array.prototype.map.call(datastr, byteValue); var ui8a = new Uint8Array(ords); this.send(ui8a.buffer); } console.log("extending XMLHttpRequest"); } return true; } else { alert('The File APIs are not fully supported in this browser.'); return false; } }, /** * Overrides a feature layer. * @param layer * @returns deferred */ extend: function(layer, callback) { var self = this; // we keep track of the FeatureLayer object this._featureLayers[ layer.url ] = layer; // replace the applyEdits() method layer._applyEdits = layer.applyEdits; // attachments layer._addAttachment = layer.addAttachment; layer._queryAttachmentInfos = layer.queryAttachmentInfos; layer._deleteAttachments = layer.deleteAttachments; if( !this._checkFileAPIs()) { return callback(false, "File APIs not supported"); } try { layer.attachmentsStore = new AttachmentsStore(); if( /*false &&*/ layer.attachmentsStore.isSupported() ) layer.attachmentsStore.init(callback); else return callback(false, "indexedDB not supported"); } catch(err) { console.log("problem! " + err.toString()) } /* operations supported offline: 1. add a new attachment to an existing feature 2. add a new attachment to a new feature 3. remove an attachment that is already in the server... how do we know about it? 4. remove an attachment that is not in the server yet 5. update an existing attachment to an existing feature, how do we know about it? 6. update a new attachment concerns: - should the key in the db be the objectId or the attachmentId? - manage the relationship between offline features and attachments: what if the user wants to add an attachment to a feature that is still offline? we need to keep track of objectids so that when the feature is sent to the server and receives a final objectid we replace the temporary negative id by its final objectid - what if the user deletes an offline feature that had offline attachments? we need to discard the attachment */ // // attachments // layer.queryAttachmentInfos = function(objectId,callback,errback){ if( self.getOnlineStatus() == self.ONLINE) { var def = this._queryAttachmentInfos(objectId, function() { console.log(arguments); self.emit(self.events.ATTACHMENTS_INFO,arguments); callback && callback.apply(this,arguments); }, errback); return def; } else { // NEEDS TESTING var deferred = new Deferred(); this.attachmentsStore.getAttachmentsByFeatureId(objectId, function(found,attachments) { callback && callback.apply(this,attachments); deferred.resolve(attachments); }); return deferred; } } layer.addAttachment = function(objectId,formNode,callback,errback){ if( self.getOnlineStatus() == self.ONLINE) { return this._addAttachment(objectId,formNode, function(){ self.emit(self.events.ATTACHMENTS_SENT,arguments); callback && callback.apply(this,arguments); }, function(err){ console.log("addAttachment: " + err) errback && errback.apply(this,arguments); } ) } else { // TODO: we need to: // 1. extract the file from formNode // 2. store the file in a format that can be later sent to the server using REST API // concerns: // a) the mechanism to convert a file path into binary data to be sent over POST // is normally handled by the API/Browser... how can we reuse as much of that mechanism as // possible? There is a big leap between parameters we receive here (objectId and formNode) and // the data that gets sent via REST to the server (use chrome devtools network tab to see that) // b) what fields should be in our attachment object stored in the db? // c) what should be the key in the db? objectid? attachmentid? problem is that when we are offline // we don't know about attachmentids and other attachments that can be attached to the same feature // we could use negative attachmentids, as we do with feature objectids console.assert(objectId); console.log(formNode); // get input node(s) // read file from input into variable this._readFilesFromForm(formNode, function(files) { // callback called once for each input[type=file] node in formNode console.log(files); // store the file(s) }); } } // callback called once for each input[type=file] node in formNode layer._readFilesFromForm = function(formNode, callback) { var inputNodes = array.filter(formNode.elements, function(node) { return node.type == "file"}); inputNodes.forEach(function(inputNode) { var files = []; var pendingFiles = inputNode.files.length var i, n = inputNode.files.length; if( pendingFiles == 0) return callback(files); for(i=0;i