"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' }, initAttachments: function(callback) { if(!callback) callback = function(success) { console.log("attachments inited ", success? "ok" : "failed"); } if( !this._checkFileAPIs()) return callback(false, "File APIs not supported"); try { this.attachmentsStore = new AttachmentsStore(); if( /*false &&*/ this.attachmentsStore.isSupported() ) this.attachmentsStore.init(callback); else return callback(false, "indexedDB not supported"); } catch(err) { console.log("problem! " + err.toString()) } }, _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; } }, _extendAjaxReq: function(oAjaxReq) { oAjaxReq.sendAsBinary = XMLHttpRequest.prototype.sendAsBinary; console.log("extending XMLHttpRequest"); }, /** * 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; /* 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: - 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 pending tasks: - delete attachment - send attachments to server when reconnecting - check for hasAttachments attribute in the FeatureLayer */ // // 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 { if( !self.attachmentsStore ) console.log("in order to support attachments you need to call initAttachments() method of offlineFeaturesManager"); // will only return LOCAL attachments var deferred = new Deferred(); self.attachmentsStore.getAttachmentsByFeatureId(this.url, objectId, function(attachments) { callback && callback(attachments); deferred.resolve(attachments); }); return deferred; } } layer.addAttachment = function(objectId,formNode,callback,errback){ if( self.getOnlineStatus() == self.ONLINE) { return this._addAttachment(objectId,formNode, function(){ console.log(arguments); self.emit(self.events.ATTACHMENTS_SENT,arguments); callback && callback.apply(this,arguments); }, function(err){ console.log("addAttachment: " + err) errback && errback.apply(this,arguments); } ) } else { if( !self.attachmentsStore ) console.log("in order to support attachments you need to call initAttachments() method of offlineFeaturesManager"); var deferred = new Deferred(); console.assert(objectId); // get input node(s) // read file from input into variable this._readFilesFromForm(formNode, function(files) { console.log(files); console.assert(files.length == 1, "we don't support multiple files (yet?)"); files.forEach(function(file) { // store the attachment var attachmentId = this.getNextTempId(); self.attachmentsStore.store(this.url,attachmentId, objectId, file, function(success) { var returnValue = { attachmentId: attachmentId, objectId: objectId, success: success }; if( success ) { self.emit(self.events.ATTACHMENT_ENQUEUED,returnValue); callback && callback(returnValue); deferred.resolve(returnValue); // PROBLEM HERE: theoretically, the form can have multiple input[file] controls // and each input can contain multiple files BUT here we call the callback and // resolve the deferred with the FIRST of the files // In practice, popups only have one input, and it doesn't allow multiple files } else { returnValue.error = "can't store attachment"; errback && errback(returnValue); deferred.reject(returnValue); } }); },this); }.bind(this)); return deferred; } } layer._readFilesFromForm = function(formNode, callback) { var inputNodes = array.filter(formNode.elements, function(node) { return node.type == "file"}); console.assert(inputNodes.length <= 1, "we don't support multiple input nodes in one form (yet?)"); 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 0) { layer.replaceFeatureIds(tempObjectIds,newObjectIds,function(success) { dfd.resolve({addResults:addResults,updateResults:updateResults,deleteResults:deleteResults}); // wrap three arguments in a single object }); } else { dfd.resolve({addResults:addResults,updateResults:updateResults,deleteResults:deleteResults}); // wrap three arguments in a single object } }, function(error) { layer["onEditsComplete"] = layer.__onEditsComplete; delete layer.__onEditsComplete; layer["onBeforeApplyEdits"] = layer.__onBeforeApplyEdits; delete layer.__onBeforeApplyEdits; dfd.reject(error); } ); return dfd; }(layer,tempObjectIds); } // // wait for all requests to finish // var allPromises = new all(promises); allPromises.then( function(responses) { console.log("all responses are back"); this.emit(this.events.EDITS_SENT); this.emit(this.events.ALL_EDITS_SENT); callback && callback(true,responses); }.bind(this), function(errors) { console.log("ERROR!!"); console.log(errors); callback && callback(false,errors); }.bind(this)); } // hasPendingEdits() else { this.emit(this.events.ALL_EDITS_SENT); callback && callback(true, {}); } }, }); // declare }); // define