diff --git a/dist/offline-edit-min.js b/dist/offline-edit-min.js index 43f85cd..6cf2256 100644 --- a/dist/offline-edit-min.js +++ b/dist/offline-edit-min.js @@ -1 +1 @@ -define(["dojo/Evented","dojo/_base/Deferred","dojo/DeferredList","dojo/promise/all","dojo/_base/declare","dojo/_base/array","dojo/dom-attr","dojo/dom-style","dojo/query","esri/config","esri/layers/GraphicsLayer","esri/graphic","esri/symbols/SimpleMarkerSymbol","esri/symbols/SimpleLineSymbol","esri/symbols/SimpleFillSymbol","esri/urlUtils"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o){"use strict";return e("O.esri.Edit.OfflineFeaturesManager",[a],{_onlineStatus:"online",_featureLayers:{},_editStore:new O.esri.Edit.EditStore,ONLINE:"online",OFFLINE:"offline",RECONNECTING:"reconnecting",attachmentsStore:null,proxyPath:null,events:{EDITS_SENT:"edits-sent",EDITS_ENQUEUED:"edits-enqueued",ALL_EDITS_SENT:"all-edits-sent",ATTACHMENT_ENQUEUED:"attachment-enqueued",ATTACHMENTS_SENT:"attachments-sent"},initAttachments:function(a){if(a=a||function(a){},!this._checkFileAPIs())return a(!1,"File APIs not supported");try{if(this.attachmentsStore=new O.esri.Edit.AttachmentsStore,!this.attachmentsStore.isSupported())return a(!1,"indexedDB not supported");this.attachmentsStore.init(a)}catch(b){}},_checkFileAPIs:function(){return window.File&&window.FileReader&&window.FileList&&window.Blob?(XMLHttpRequest.prototype.sendAsBinary||(XMLHttpRequest.prototype.sendAsBinary=function(a){function b(a){return 255&a.charCodeAt(0)}var c=Array.prototype.map.call(a,b),d=new Uint8Array(c);this.send(d.buffer)}),!0):!1},_extendAjaxReq:function(a){a.sendAsBinary=XMLHttpRequest.prototype.sendAsBinary},extend:function(a,c){function e(){try{a._phantomLayer=new k({opacity:.8}),a._map.addLayer(a._phantomLayer)}catch(b){}}var j=this;j._editStore.init(function(a){c(a)}),this._featureLayers[a.url]=a,a._applyEdits=a.applyEdits,a._addAttachment=a.addAttachment,a._queryAttachmentInfos=a.queryAttachmentInfos,a._deleteAttachments=a.deleteAttachments,a.queryAttachmentInfos=function(a,c,d){if(j.getOnlineStatus()===j.ONLINE){var e=this._queryAttachmentInfos(a,function(){j.emit(j.events.ATTACHMENTS_INFO,arguments),c&&c.apply(this,arguments)},d);return e}if(j.attachmentsStore){var f=new b;return j.attachmentsStore.getAttachmentsByFeatureId(this.url,a,function(a){c&&c(a),f.resolve(a)}),f}},a.addAttachment=function(a,c,d,e){if(j.getOnlineStatus()===j.ONLINE)return this._addAttachment(a,c,function(){j.emit(j.events.ATTACHMENTS_SENT,arguments),d&&d.apply(this,arguments)},function(a){e&&e.apply(this,arguments)});if(j.attachmentsStore){var f=this._getFilesFromForm(c),g=f[0],h=new b,k=this._getNextTempId();return j.attachmentsStore.store(this.url,k,a,g,function(b,c){var f={attachmentId:k,objectId:a,success:b};if(b){j.emit(j.events.ATTACHMENT_ENQUEUED,f),d&&d(f),h.resolve(f);var g=this._url.path+"/"+a+"/attachments/"+k,l=i("[href="+g+"]");l.attr("href",c.url)}else f.error="can't store attachment",e&&e(f),h.reject(f)}.bind(this)),h}},a.deleteAttachments=function(a,c,e,f){if(j.getOnlineStatus()===j.ONLINE){var g=this._deleteAttachments(a,c,function(){e&&e.apply(this,arguments)},function(a){f&&f.apply(this,arguments)});return g}if(j.attachmentsStore){var h=[];c.forEach(function(c){c=parseInt(c,10);var d=new b;j.attachmentsStore.delete(c,function(b){var e={objectId:a,attachmentId:c,success:b};d.resolve(e)}),h.push(d)},this);var i=d(h);return i.then(function(a){e&&e(a)}),i}},a.applyEdits=function(a,c,e,f,i){var k=[];if(j.getOnlineStatus()===j.ONLINE){var m=this._applyEdits(a,c,e,function(){j.emit(j.events.EDITS_SENT,arguments),f&&f.apply(this,arguments)},i);return m}var n=new b,o={addResults:[],updateResults:[],deleteResults:[]},p={};this.onBeforeApplyEdits(a,c,e),a=a||[],a.forEach(function(a){var c=new b,d=this._getNextTempId();a.attributes[this.objectIdField]=d,j._editStore.pushEdit(j._editStore.ADD,this.url,a,function(a,b){1==a?c.resolve(a):c.reject(b)}),o.addResults.push({success:!0,error:null,objectId:d});var e=new l(a.geometry,j._getPhantomSymbol(a.geometry,j._editStore.ADD),{objectId:d});this._phantomLayer.add(e),g.set(e.getNode(),"stroke-dasharray","10,4"),h.set(e.getNode(),"pointer-events","none"),k.push(c)},this),c=c||[],c.forEach(function(a){var c=new b,d=a.attributes[this.objectIdField];p[d]=a,j._editStore.pushEdit(j._editStore.UPDATE,this.url,a,function(a,b){1==a?c.resolve(a):c.reject(b)}),o.updateResults.push({success:!0,error:null,objectId:d});var e=new l(a.geometry,j._getPhantomSymbol(a.geometry,j._editStore.UPDATE),{objectId:d});this._phantomLayer.add(e),g.set(e.getNode(),"stroke-dasharray","5,2"),h.set(e.getNode(),"pointer-events","none"),k.push(c)},this),e=e||[],e.forEach(function(a){var c=new b,d=a.attributes[this.objectIdField];j._editStore.pushEdit(j._editStore.DELETE,this.url,a,function(a,b){1==a?c.resolve(a):c.reject(b)}),o.deleteResults.push({success:!0,error:null,objectId:d});var e=new l(a.geometry,j._getPhantomSymbol(a.geometry,j._editStore.DELETE),{objectId:d});this._phantomLayer.add(e),g.set(e.getNode(),"stroke-dasharray","4,4"),h.set(e.getNode(),"pointer-events","none"),j.attachmentsStore&&j.attachmentsStore.deleteAttachmentsByFeatureId(this.url,d,function(a){}),k.push(c)},this),d(k).then(function(){return setTimeout(function(){this._editHandler(o,a,p,f,i,n),j.emit(j.events.EDITS_ENQUEUED,o)}.bind(this),0),n}.bind(this))},a.convertGraphicLayerToJSON=function(a,b,c){var d={};d.objectIdFieldName=b.target.objectIdField,d.globalIdFieldName=b.target.globalIdField,d.geometryType=b.target.geometryType,d.spatialReference=b.target.spatialReference,d.fields=b.target.fields;for(var e=a.length,f=[],g=0;e>g;g++){var h=a[g].toJson();if(f.push(h),g==e-1){var i=JSON.stringify(f),j=JSON.stringify(d);c(i,j);break}}},a.setPhantomLayerGraphics=function(a){var b=a.length;if(b>0)for(var c=0;b>c;c++){var d=new l(a[c]);this._phantomLayer.add(d)}},a.getPhantomLayerGraphics=function(b){for(var c=a._phantomLayer.graphics,d=a._phantomLayer.graphics.length,e=[],f=0;d>f;f++){var g=c[f].toJson();if(e.push(g),f==d-1){var h=JSON.stringify(e);b(h);break}}},a.getFeatureDefinition=function(a,b,c,d){var e={layerDefinition:a,featureSet:{features:b,geometryType:c}};d(e)},a._getFilesFromForm=function(a){var b=[],c=f.filter(a.elements,function(a){return"file"===a.type});return c.forEach(function(a){b.push.apply(b,a.files)},this),b},a._replaceFeatureIds=function(a,b,c){a.length||c(0);var d,e=a.length,f=e,g=0;for(d=0;e>d;d++)j.attachmentsStore.replaceFeatureId(this.url,a[d],b[d],function(a){--f,g+=a?1:0,0===f&&c(g)}.bind(this))},a._nextTempId=-1,a._getNextTempId=function(){return this._nextTempId--},e()},goOffline:function(){this._onlineStatus=this.OFFLINE},goOnline:function(a){this._onlineStatus=this.RECONNECTING,this._replayStoredEdits(function(b,c){var d={features:{success:b,responses:c}};null!=this.attachmentsStore?this._sendStoredAttachments(function(b,c){this._onlineStatus=this.ONLINE,d.attachments={success:b,responses:c},a&&a(d)}.bind(this)):(this._onlineStatus=this.ONLINE,a&&a(d))}.bind(this))},getOnlineStatus:function(){return this._onlineStatus},getReadableEdit:function(a){var b=this._featureLayers[a.layer],c=this._editStore._deserialize(a.graphic),d=c.geometry.type,e=a.layer.substring(a.layer.lastIndexOf("/")+1);return b&&(d+=" [id="+c.attributes[b.objectIdField]+"]"),"o:"+a.operation+", l:"+e+", g:"+d},_phantomSymbols:[],_getPhantomSymbol:function(a,b){if(0===this._phantomSymbols.length){var c=[0,255,0,255],d=1.5;this._phantomSymbols.point=[],this._phantomSymbols.point[this._editStore.ADD]=new m({type:"esriSMS",style:"esriSMSCross",xoffset:10,yoffset:10,color:[255,255,255,0],size:15,outline:{color:c,width:d,type:"esriSLS",style:"esriSLSSolid"}}),this._phantomSymbols.point[this._editStore.UPDATE]=new m({type:"esriSMS",style:"esriSMSCircle",xoffset:0,yoffset:0,color:[255,255,255,0],size:15,outline:{color:c,width:d,type:"esriSLS",style:"esriSLSSolid"}}),this._phantomSymbols.point[this._editStore.DELETE]=new m({type:"esriSMS",style:"esriSMSX",xoffset:0,yoffset:0,color:[255,255,255,0],size:15,outline:{color:c,width:d,type:"esriSLS",style:"esriSLSSolid"}}),this._phantomSymbols.multipoint=null,this._phantomSymbols.polyline=[],this._phantomSymbols.polyline[this._editStore.ADD]=new n({type:"esriSLS",style:"esriSLSSolid",color:c,width:d}),this._phantomSymbols.polyline[this._editStore.UPDATE]=new n({type:"esriSLS",style:"esriSLSSolid",color:c,width:d}),this._phantomSymbols.polyline[this._editStore.DELETE]=new n({type:"esriSLS",style:"esriSLSSolid",color:c,width:d}),this._phantomSymbols.polygon=[],this._phantomSymbols.polygon[this._editStore.ADD]=new o({type:"esriSFS",style:"esriSFSSolid",color:[255,255,255,0],outline:{type:"esriSLS",style:"esriSLSSolid",color:c,width:d}}),this._phantomSymbols.polygon[this._editStore.UPDATE]=new o({type:"esriSFS",style:"esriSFSSolid",color:[255,255,255,0],outline:{type:"esriSLS",style:"esriSLSDash",color:c,width:d}}),this._phantomSymbols.polygon[this._editStore.DELETE]=new o({type:"esriSFS",style:"esriSFSSolid",color:[255,255,255,0],outline:{type:"esriSLS",style:"esriSLSDot",color:c,width:d}})}return this._phantomSymbols[a.type][b]},_fieldSegment:function(a,b){return'Content-Disposition: form-data; name="'+a+'"\r\n\r\n'+b+"\r\n"},_fileSegment:function(a,b,c,d){return'Content-Disposition: form-data; name="'+a+'"; filename="'+b+'"\r\nContent-Type: '+c+"\r\n\r\n"+d+"\r\n"},_uploadAttachment:function(a){var c=new b,d=[];d.push(this._fieldSegment("f","json")),d.push(this._fileSegment("attachment",a.name,a.contentType,a.content));var e=new XMLHttpRequest;e.sendAsBinary||this._extendAjaxReq(e),e.onload=function(a){c.resolve(JSON.parse(a.target.response))},e.onerror=function(a){c.reject(a)};var f=this.proxyPath||j.defaults.io.proxyUrl||"";""!==f&&(f+="?"),e.open("post",f+a.featureId+"/addAttachment",!0);var g="---------------------------"+Date.now().toString(16);return e.setRequestHeader("Content-Type","multipart/form-data; boundary="+g),e.sendAsBinary("--"+g+"\r\n"+d.join("--"+g+"\r\n")+"--"+g+"--\r\n"),c},_deleteAttachment:function(a,c){var d=new b;return this.attachmentsStore.delete(a,function(a){d.resolve(c)}),d},_sendStoredAttachments:function(a){this.attachmentsStore.getAllAttachments(function(b){var c=[];b.forEach(function(a){var b=this._uploadAttachment(a).then(function(b){return b.addAttachmentResult&&b.addAttachmentResult.success===!0?this._deleteAttachment(a.id,b):null}.bind(this),function(){});c.push(b)},this);var e=d(c);e.then(function(b){a&&a(!0,b)},function(b){a&&a(!1,b)})}.bind(this))},_optimizeEditsQueue:function(){for(var a,b,c,d,e={},f=(this._editStore.pendingEditsCount(),0);this._editStore.hasPendingEdits();){if(a=this._editStore.popFirstEdit(),b=this._featureLayers[a.layer],a.layer in e||(e[a.layer]={}),c=e[a.layer],d=a.graphic.attributes[b.objectIdField],d in c)switch(a.operation){case this._editStore.ADD:throw"can't add the same feature twice!";case this._editStore.UPDATE:c[d].graphic=a.graphic;break;case this._editStore.DELETE:0>d?(delete c[d],f-=1):c[d].operation=this._editStore.DELETE}else c[d]=a,f+=1;0===Object.keys(c).length&&delete e[a.layer]}return e},_internalApplyEdits:function(a,c,d,e,f){var g=new b,h=a;return h.dfd,h._applyEdits(d,e,f,function(a,b,d){try{h._phantomLayer.clear(),h.onEditsComplete=h.__onEditsComplete,delete h.__onEditsComplete,h.onBeforeApplyEdits=h.__onBeforeApplyEdits,delete h.__onBeforeApplyEdits;var e=a.map(function(a){return a.objectId});null!=h._attachmentsStore&&h.hasAttachments&&c.length>0?h._replaceFeatureIds(c,e,function(){g.resolve({addResults:a,updateResults:b,deleteResults:d})}):g.resolve({addResults:a,updateResults:b,deleteResults:d})}catch(f){}},function(a){h.onEditsComplete=h.__onEditsComplete,delete h.__onEditsComplete,h.onBeforeApplyEdits=h.__onBeforeApplyEdits,delete h.__onBeforeApplyEdits,g.reject(a)}),g},_replayStoredEdits:function(a){var b,e,f,g,h,i,j,k={},m=[],n=this,o=[],p=this._featureLayers,q=this.attachmentsStore,r=this._editStore;this._editStore.getAllEditsArray(function(s){if(null!=s){o=s;for(var t=o.length,u=0;t>u;u++){if(b=p[o[u].layer],null==q&&b.hasAttachments)throw new Error("OfflineFeaturesManager: Attachments aren't initialized.");b._attachmentsStore=q,b.__onEditsComplete=b.onEditsComplete,b.onEditsComplete=function(){},b.__onBeforeApplyEdits=b.onBeforeApplyEdits,b.onBeforeApplyEdits=function(){},e=[],f=[],g=[],h=[];var v=new l(JSON.parse(o[u].graphic));switch(o[u].operation){case r.ADD:for(i=0;ig;g++){var h=a[g].toJson();if(f.push(h),g==e-1){var i=JSON.stringify(f),j=JSON.stringify(d);c(i,j);break}}},a.setPhantomLayerGraphics=function(a){var b=a.length;if(b>0)for(var c=0;b>c;c++){var d=new l(a[c]);this._phantomLayer.add(d)}},a.getPhantomLayerGraphics=function(b){for(var c=a._phantomLayer.graphics,d=a._phantomLayer.graphics.length,e=[],f=0;d>f;f++){var g=c[f].toJson();if(e.push(g),f==d-1){var h=JSON.stringify(e);b(h);break}}},a.getFeatureDefinition=function(a,b,c,d){var e={layerDefinition:a,featureSet:{features:b,geometryType:c}};d(e)},a._getFilesFromForm=function(a){var b=[],c=f.filter(a.elements,function(a){return"file"===a.type});return c.forEach(function(a){b.push.apply(b,a.files)},this),b},a._replaceFeatureIds=function(a,b,c){a.length||c(0);var d,e=a.length,f=e,g=0;for(d=0;e>d;d++)j.attachmentsStore.replaceFeatureId(this.url,a[d],b[d],function(a){--f,g+=a?1:0,0===f&&c(g)}.bind(this))},a._nextTempId=-1,a._getNextTempId=function(){return this._nextTempId--},e()},goOffline:function(){this._onlineStatus=this.OFFLINE},goOnline:function(a){this._onlineStatus=this.RECONNECTING,this._replayStoredEdits(function(b,c){var d={features:{success:b,responses:c}};null!=this.attachmentsStore?this._sendStoredAttachments(function(b,c){this._onlineStatus=this.ONLINE,d.attachments={success:b,responses:c},a&&a(d)}.bind(this)):(this._onlineStatus=this.ONLINE,a&&a(d))}.bind(this))},getOnlineStatus:function(){return this._onlineStatus},_checkFileAPIs:function(){return window.File&&window.FileReader&&window.FileList&&window.Blob?(XMLHttpRequest.prototype.sendAsBinary||(XMLHttpRequest.prototype.sendAsBinary=function(a){function b(a){return 255&a.charCodeAt(0)}var c=Array.prototype.map.call(a,b),d=new Uint8Array(c);this.send(d.buffer)}),!0):!1},_extendAjaxReq:function(a){a.sendAsBinary=XMLHttpRequest.prototype.sendAsBinary},_phantomSymbols:[],_getPhantomSymbol:function(a,b){if(0===this._phantomSymbols.length){var c=[0,255,0,255],d=1.5;this._phantomSymbols.point=[],this._phantomSymbols.point[this._editStore.ADD]=new m({type:"esriSMS",style:"esriSMSCross",xoffset:10,yoffset:10,color:[255,255,255,0],size:15,outline:{color:c,width:d,type:"esriSLS",style:"esriSLSSolid"}}),this._phantomSymbols.point[this._editStore.UPDATE]=new m({type:"esriSMS",style:"esriSMSCircle",xoffset:0,yoffset:0,color:[255,255,255,0],size:15,outline:{color:c,width:d,type:"esriSLS",style:"esriSLSSolid"}}),this._phantomSymbols.point[this._editStore.DELETE]=new m({type:"esriSMS",style:"esriSMSX",xoffset:0,yoffset:0,color:[255,255,255,0],size:15,outline:{color:c,width:d,type:"esriSLS",style:"esriSLSSolid"}}),this._phantomSymbols.multipoint=null,this._phantomSymbols.polyline=[],this._phantomSymbols.polyline[this._editStore.ADD]=new n({type:"esriSLS",style:"esriSLSSolid",color:c,width:d}),this._phantomSymbols.polyline[this._editStore.UPDATE]=new n({type:"esriSLS",style:"esriSLSSolid",color:c,width:d}),this._phantomSymbols.polyline[this._editStore.DELETE]=new n({type:"esriSLS",style:"esriSLSSolid",color:c,width:d}),this._phantomSymbols.polygon=[],this._phantomSymbols.polygon[this._editStore.ADD]=new o({type:"esriSFS",style:"esriSFSSolid",color:[255,255,255,0],outline:{type:"esriSLS",style:"esriSLSSolid",color:c,width:d}}),this._phantomSymbols.polygon[this._editStore.UPDATE]=new o({type:"esriSFS",style:"esriSFSSolid",color:[255,255,255,0],outline:{type:"esriSLS",style:"esriSLSDash",color:c,width:d}}),this._phantomSymbols.polygon[this._editStore.DELETE]=new o({type:"esriSFS",style:"esriSFSSolid",color:[255,255,255,0],outline:{type:"esriSLS",style:"esriSLSDot",color:c,width:d}})}return this._phantomSymbols[a.type][b]},_fieldSegment:function(a,b){return'Content-Disposition: form-data; name="'+a+'"\r\n\r\n'+b+"\r\n"},_fileSegment:function(a,b,c,d){return'Content-Disposition: form-data; name="'+a+'"; filename="'+b+'"\r\nContent-Type: '+c+"\r\n\r\n"+d+"\r\n"},_uploadAttachment:function(a){var c=new b,d=[];d.push(this._fieldSegment("f","json")),d.push(this._fileSegment("attachment",a.name,a.contentType,a.content));var e=new XMLHttpRequest;e.sendAsBinary||this._extendAjaxReq(e),e.onload=function(a){c.resolve(JSON.parse(a.target.response))},e.onerror=function(a){c.reject(a)};var f=this.proxyPath||j.defaults.io.proxyUrl||"";""!==f&&(f+="?"),e.open("post",f+a.featureId+"/addAttachment",!0);var g="---------------------------"+Date.now().toString(16);return e.setRequestHeader("Content-Type","multipart/form-data; boundary="+g),e.sendAsBinary("--"+g+"\r\n"+d.join("--"+g+"\r\n")+"--"+g+"--\r\n"),c},_deleteAttachment:function(a,c){var d=new b;return this.attachmentsStore.delete(a,function(a){d.resolve(c)}),d},_sendStoredAttachments:function(a){this.attachmentsStore.getAllAttachments(function(b){var c=[];b.forEach(function(a){var b=this._uploadAttachment(a).then(function(b){return b.addAttachmentResult&&b.addAttachmentResult.success===!0?this._deleteAttachment(a.id,b):null}.bind(this),function(){});c.push(b)},this);var e=d(c);e.then(function(b){a&&a(!0,b)},function(b){a&&a(!1,b)})}.bind(this))},_replayStoredEdits:function(a){var b,c={},e=this,f=[],g=[],h=[],i=[],j=[],k=this._featureLayers,m=this.attachmentsStore,n=this._editStore;this._editStore.getAllEditsArray(function(o){if(null!=o){j=o;for(var p=j.length,q=0;p>q;q++){if(b=k[j[q].layer],null==m&&b.hasAttachments)throw new Error("OfflineFeaturesManager: Attachments aren't initialized.");b._attachmentsStore=m,b.__onEditsComplete=b.onEditsComplete,b.onEditsComplete=function(){},b.__onBeforeApplyEdits=b.onBeforeApplyEdits,b.onBeforeApplyEdits=function(){},f=[],g=[],h=[],i=[];var r=new l(JSON.parse(j[q].graphic));switch(j[q].operation){case n.ADD:for(var s=0;s0?a._replaceFeatureIds(c,f,function(){g.resolve({addResults:b,updateResults:d,deleteResults:e})}):g.resolve({addResults:b,updateResults:d,deleteResults:e})},function(b){a.onEditsComplete=a.__onEditsComplete,delete a.__onEditsComplete,a.onBeforeApplyEdits=a.__onBeforeApplyEdits,delete a.__onBeforeApplyEdits,g.reject(b)}),g.promise},_optimizeEditsQueue:function(){for(var a,b,c,d,e={},f=(this._editStore.pendingEditsCount(),0);this._editStore.hasPendingEdits();){if(a=this._editStore.popFirstEdit(),b=this._featureLayers[a.layer],a.layer in e||(e[a.layer]={}),c=e[a.layer],d=a.graphic.attributes[b.objectIdField],d in c)switch(a.operation){case this._editStore.ADD:throw"can't add the same feature twice!";case this._editStore.UPDATE:c[d].graphic=a.graphic;break;case this._editStore.DELETE:0>d?(delete c[d],f-=1):c[d].operation=this._editStore.DELETE}else c[d]=a,f+=1;0===Object.keys(c).length&&delete e[a.layer]}return e},getReadableEdit:function(a){var b=this._featureLayers[a.layer],c=this._editStore._deserialize(a.graphic),d=c.geometry.type,e=a.layer.substring(a.layer.lastIndexOf("/")+1);return b&&(d+=" [id="+c.attributes[b.objectIdField]+"]"),"o:"+a.operation+", l:"+e+", g:"+d}})}),"undefined"!=typeof O?O.esri.Edit={}:(O={},O.esri={Edit:{}}),O.esri.Edit.EditStore=function(){this._db=null;var a="features_store",b="features",c="featureId";this.ADD="add",this.UPDATE="update",this.DELETE="delete",this.isSupported=function(){return window.indexedDB?!0:!1},this.pushEdit=function(a,c,d,e){var f={id:c+"/"+d.attributes.objectid,operation:a,layer:c,graphic:this._serialize(d)},g=this._db.transaction([b],"readwrite");g.oncomplete=function(){e(!0)},g.onerror=function(a){e(!1,a.target.error.message)};var h=g.objectStore(b);h.put(f)},this.getAllEdits=function(a){if(null!==this._db){var c=this._db.transaction([b]).objectStore(b).openCursor();c.onsuccess=function(b){var c=b.target.result;c?(a(c.value,null),c.continue()):a(null,"end")}.bind(this),c.onerror=function(b){a(null,b)}}else a(null,"no db")},this.getAllEditsArray=function(a){var c=[];if(null!==this._db){var d=this._db.transaction([b]).objectStore(b).openCursor();d.onsuccess=function(b){var d=b.target.result;d?(c.push(d.value),d.continue()):a(c,"end")}.bind(this),d.onerror=function(b){a(null,b)}}else a(null,"no db")},this.updateExistingEdit=function(a,c,d,e){var f=this._db.transaction([b],"readwrite").objectStore(b),g=f.get(d.attributes.objectid);g.onsuccess=function(){var b=(g.result,{id:c+"/"+d.attributes.objectid,operation:a,layer:c,graphic:d.toJson()}),h=f.put(b);h.onsuccess=function(){e(!0)},h.onerror=function(a){e(!1,a)}}},this.delete=function(a,c,d){var e=this._db,f=null,g=this,h={id:a+"/"+c.attributes.objectid,operation:null,layer:a,graphic:c.toJson()};require(["dojo/Deferred"],function(a){f=new a,g.editExists(h).then(function(a){if(a.success){f.then(function(a){g.editExists(a).then(function(a){d(0==a.success?!0:!1)},function(){d(!0)})},function(a){d(a)});var c=e.transaction([b],"readwrite").objectStore(b),i=c.delete(h.id);i.onsuccess=function(){f.resolve(h)},i.onerror=function(a){f.reject({success:!1,error:a})}}},function(){d(!1)})})},this.resetEditsQueue=function(a){var c=this._db.transaction([b],"readwrite").objectStore(b).clear();c.onsuccess=function(){setTimeout(function(){a(!0)},0)},c.onerror=function(b){a(!1,b)}},this.pendingEditsCount=function(a){var c=0,d=this._db.transaction([b]).objectStore(b);d.openCursor().onsuccess=function(b){var d=b.target.result;d?(c++,d.continue()):a(c)}},this.editExists=function(a){var c=this._db,d=null;return require(["dojo/Deferred"],function(e){d=new e;var f=c.transaction([b],"readwrite").objectStore(b),g=f.get(a.id);g.onsuccess=function(){var b=g.result;b&&b.id==a.id?d.resolve({success:!0,error:null}):d.reject({success:!1,error:"Layer id is not a match."})},g.onerror=function(a){d.reject({success:!1,error:a})}}),d},this.getUsage=function(a){var c={sizeBytes:0,editCount:0},d=this._db.transaction([b]).objectStore(b).openCursor();d.onsuccess=function(b){var d=b.target.result;if(d){var e=d.value,f=JSON.stringify(e);c.sizeBytes+=f.length,c.editCount+=1,d.continue()}else a(c,null)},d.onerror=function(b){a(null,b)}},this._serialize=function(a){var b=a.toJson(),c={attributes:b.attributes,geometry:b.geometry,infoTemplate:b.infoTemplate,symbol:b.symbol};return JSON.stringify(c)},this._deserialize=function(a){var b;return require(["esri/graphic"],function(c){b=new c(JSON.parse(a))}),b},this.init=function(d){var e=indexedDB.open(a,11);d=d||function(a){}.bind(this),e.onerror=function(a){d(!1,a.target.errorCode)}.bind(this),e.onupgradeneeded=function(a){var d=a.target.result;d.objectStoreNames.contains(b)&&d.deleteObjectStore(b);var e=d.createObjectStore(b,{keyPath:"id"});e.createIndex(c,c,{unique:!1})}.bind(this),e.onsuccess=function(a){this._db=a.target.result,d(!0)}.bind(this)},this.hasPendingEdits=function(){return"DEPRECATED at v2.5!"},this._isEditDuplicated=function(){return"DEPRECATED at v2.5!"}},O.esri.Edit.AttachmentsStore=function(){this._db=null;var a="attachments_store",b="attachments";this.isSupported=function(){return window.indexedDB?!0:!1},this.store=function(a,c,d,e,f){try{this._readFile(e,function(g){var h={id:c,objectId:d,featureId:a+"/"+d,contentType:e.type,name:e.name,size:e.size,url:this._createLocalURL(e),content:g},i=this._db.transaction([b],"readwrite");i.oncomplete=function(){f(!0,h)},i.onerror=function(a){f(!1,a.target.error.message)};var j=i.objectStore(b),k=j.put(h);k.onsuccess=function(){}}.bind(this))}catch(g){f(!1,g.stack)}},this.retrieve=function(a,c){var d=this._db.transaction([b]).objectStore(b),e=d.get(a);e.onsuccess=function(a){var b=a.target.result;b?c(!0,b):c(!1,"not found")},e.onerror=function(a){c(!1,a)}},this.getAttachmentsByFeatureId=function(a,c,d){var e=a+"/"+c,f=[],g=this._db.transaction([b]).objectStore(b),h=g.index("featureId"),i=IDBKeyRange.only(e);h.openCursor(i).onsuccess=function(a){var b=a.target.result;b?(f.push(b.value),b.continue()):d(f)}},this.getAttachmentsByFeatureLayer=function(a,c){var d=[],e=this._db.transaction([b]).objectStore(b),f=e.index("featureId"),g=IDBKeyRange.bound(a+"/",a+"/A");f.openCursor(g).onsuccess=function(a){var b=a.target.result;b?(d.push(b.value),b.continue()):c(d)}},this.getAllAttachments=function(a){var c=[],d=this._db.transaction([b]).objectStore(b);d.openCursor().onsuccess=function(b){var d=b.target.result;d?(c.push(d.value),d.continue()):a(c)}},this.deleteAttachmentsByFeatureId=function(a,c,d){var e=a+"/"+c,f=this._db.transaction([b],"readwrite").objectStore(b),g=f.index("featureId"),h=IDBKeyRange.only(e),i=0;g.openCursor(h).onsuccess=function(a){var b=a.target.result;if(b){var c=b.value;this._revokeLocalURL(c),f.delete(b.primaryKey),i++,b.continue()}else setTimeout(function(){d(i)},0)}.bind(this)},this.delete=function(a,c){this.retrieve(a,function(d,e){if(!d)return void c(!1,"attachment "+a+" not found");this._revokeLocalURL(e);var f=this._db.transaction([b],"readwrite").objectStore(b).delete(a);f.onsuccess=function(){setTimeout(function(){c(!0)},0)},f.onerror=function(a){c(!1,a)}}.bind(this))},this.deleteAll=function(a){this.getAllAttachments(function(c){c.forEach(function(a){this._revokeLocalURL(a)},this);var d=this._db.transaction([b],"readwrite").objectStore(b).clear();d.onsuccess=function(){setTimeout(function(){a(!0)},0)},d.onerror=function(b){a(!1,b)}}.bind(this))},this.replaceFeatureId=function(a,c,d,e){var f=a+"/"+c,g=this._db.transaction([b],"readwrite").objectStore(b),h=g.index("featureId"),i=IDBKeyRange.only(f),j=0;h.openCursor(i).onsuccess=function(b){var c=b.target.result;if(c){var f=a+"/"+d,h=c.value;h.featureId=f,h.objectId=d,g.put(h),j++,c.continue()}else setTimeout(function(){e(j)},1)}},this.getUsage=function(a){var c={sizeBytes:0,attachmentCount:0},d=this._db.transaction([b]).objectStore(b).openCursor();d.onsuccess=function(b){var d=b.target.result;if(d){var e=d.value,f=JSON.stringify(e);c.sizeBytes+=f.length,c.attachmentCount+=1,d.continue()}else a(c,null)}.bind(this),d.onerror=function(b){a(null,b)}},this._readFile=function(a,b){var c=new FileReader;c.onload=function(a){b(a.target.result)},c.readAsBinaryString(a)},this._createLocalURL=function(a){return window.URL.createObjectURL(a)},this._revokeLocalURL=function(a){window.URL.revokeObjectURL(a.url)},this.init=function(c){var d=indexedDB.open(a,11);c=c||function(a){}.bind(this),d.onerror=function(a){c(!1,a.target.errorCode)}.bind(this),d.onupgradeneeded=function(a){var c=a.target.result;c.objectStoreNames.contains(b)&&c.deleteObjectStore(b);var d=c.createObjectStore(b,{keyPath:"id"});d.createIndex("featureId","featureId",{unique:!1})}.bind(this),d.onsuccess=function(a){this._db=a.target.result,c(!0)}.bind(this)}}; \ No newline at end of file diff --git a/dist/offline-edit-src.js b/dist/offline-edit-src.js index 59c5e24..a4731f4 100644 --- a/dist/offline-edit-src.js +++ b/dist/offline-edit-src.js @@ -79,47 +79,6 @@ define([ } }, - /** - * internal method that checks if this browser supports everything that is needed to handle offline attachments - * it also extends XMLHttpRequest with sendAsBinary() method, needed in Chrome - */ - _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; - } - console.log("The File APIs are not fully supported in this browser."); - return false; - }, - - /** - * internal method that extends an object with sendAsBinary() method - * sometimes extending XMLHttpRequest.prototype is not enough, as ArcGIS JS lib seems to mess with this object too - * @param oAjaxReq object to extend - */ - _extendAjaxReq: function(oAjaxReq) - { - oAjaxReq.sendAsBinary = XMLHttpRequest.prototype.sendAsBinary; - console.log("extending XMLHttpRequest"); - }, - /** * Overrides a feature layer. * @param layer @@ -132,7 +91,7 @@ define([ // Attempt to initialize the database self._editStore.init(function(result){ callback(result); - return; + //return; }); // we keep track of the FeatureLayer object @@ -434,7 +393,7 @@ define([ },this); all(promises).then( function(r) - { console.log("DONE WITH DATABASE PUSH"); + { //TO-DO - handle information related to any failed edits that didn't get stored /* we already pushed the edits into the database, now we let the FeatureLayer to do the local updating of the layer graphics */ @@ -648,25 +607,48 @@ define([ return this._onlineStatus; }, + /* internal methods */ + /** - * A string value representing human readable information on pending edits - * @param edit - * @returns {string} + * internal method that checks if this browser supports everything that is needed to handle offline attachments + * it also extends XMLHttpRequest with sendAsBinary() method, needed in Chrome */ - getReadableEdit: function(edit) + _checkFileAPIs: function() { - var layer = this._featureLayers[ edit.layer ]; - var graphic = this._editStore._deserialize(edit.graphic); - var readableGraphic = graphic.geometry.type; - var layerId = edit.layer.substring(edit.layer.lastIndexOf("/")+1); - if(layer) + if( window.File && window.FileReader && window.FileList && window.Blob ) { - readableGraphic += " [id=" + graphic.attributes[layer.objectIdField] + "]"; + 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; } - return "o:" + edit.operation + ", l:" + layerId + ", g:" + readableGraphic; + console.log("The File APIs are not fully supported in this browser."); + return false; }, - /* internal methods */ + /** + * internal method that extends an object with sendAsBinary() method + * sometimes extending XMLHttpRequest.prototype is not enough, as ArcGIS JS lib seems to mess with this object too + * @param oAjaxReq object to extend + */ + _extendAjaxReq: function(oAjaxReq) + { + oAjaxReq.sendAsBinary = XMLHttpRequest.prototype.sendAsBinary; + console.log("extending XMLHttpRequest"); + }, // // phantom symbols @@ -864,6 +846,171 @@ define([ // methods to send features back to the server // + _replayStoredEdits: function(callback) + { + var promises = {}; + var that = this; + + // + // send edits for each of the layers + // + var layer; + var adds = [], updates = [], deletes = []; + var tempObjectIds = []; + var tempArray = []; + var featureLayers = this._featureLayers; + var attachmentsStore = this.attachmentsStore; + var editStore = this._editStore; + + this._editStore.getAllEditsArray(function(result,err){ + if(result != null) { + tempArray = result; + + var length = tempArray.length; + + for(var n= 0;n < length;n++) { + layer = featureLayers[tempArray[n].layer]; + + // If the layer has attachments then check to see if the attachmentsStore has been initialized + if (attachmentsStore == null && layer.hasAttachments) { + console.log("ERROR: you need to run OfflineFeaturesManager.initAttachments(). Check the Attachments doc for more info.") + throw new Error("OfflineFeaturesManager: Attachments aren't initialized."); + } + + // Assign the attachmentsStore to the layer as a private var so we can access it from + // the promises applyEdits() method. + layer._attachmentsStore = attachmentsStore; + + layer.__onEditsComplete = layer.onEditsComplete; + layer.onEditsComplete = function () { + console.log("intercepting events onEditsComplete"); + }; + layer.__onBeforeApplyEdits = layer.onBeforeApplyEdits; + layer.onBeforeApplyEdits = function () { + console.log("intercepting events onBeforeApplyEdits"); + }; + + adds = [], updates = [], deletes = [], tempObjectIds = []; + + // IMPORTANT: reconstitute the graphic JSON into an actual esri.Graphic object + var graphic = new Graphic(JSON.parse(tempArray[n].graphic)); + + switch (tempArray[n].operation) { + case editStore.ADD: + for (var i = 0; i < layer.graphics.length; i++) { + var g = layer.graphics[i]; + if (g.attributes[layer.objectIdField] === graphic.attributes[layer.objectIdField]) { + layer.remove(g); + break; + } + } + tempObjectIds.push(graphic.attributes[layer.objectIdField]); + delete graphic.attributes[layer.objectIdField]; + adds.push(graphic); + break; + case editStore.UPDATE: + updates.push(graphic); + break; + case editStore.DELETE: + deletes.push(graphic); + break; + } + + promises[n] = that._internalApplyEdits(layer, tempObjectIds, adds, updates, deletes); + } + + } + + // wait for all requests to finish + // + var allPromises = all(promises); + allPromises.then( + function(responses) + { + console.log("OfflineFeaturesManager - all responses are back"); + this._cleanDatabase(responses); + this.emit(this.events.EDITS_SENT); + this.emit(this.events.ALL_EDITS_SENT); + callback && callback(true,responses); + }.bind(that), + function(errors) + { + console.log("OfflineFeaturesManager - ERROR!!"); + console.log(errors); + callback && callback(false,errors); + }.bind(that) + ); + }); + }, + + _cleanDatabase: function(responses){ + if( Object.keys(responses).length !== 0 ){ + for(var key in responses){ + if (responses.hasOwnProperty(key)) { + console.log("KEY " + key); + } + } + } + }, + + /** + * Executes the _applyEdits() method + * @param layer + * @param tempObjectIds + * @param adds + * @param updates + * @param deletes + * @returns {l.Deferred.promise|*|c.promise|q.promise|promise} + * @private + */ + _internalApplyEdits: function(layer,tempObjectIds,adds,updates,deletes) + { + var dfd = new Deferred(); + + layer._applyEdits(adds,updates,deletes, + function(addResults,updateResults,deleteResults) + { + layer._phantomLayer.clear(); + + layer.onBeforeApplyEdits = layer.__onBeforeApplyEdits; + delete layer.__onBeforeApplyEdits; + 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({ + 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.promise; + }, + + /** + * DEPRECATED @ v2.5 + * @returns {{}} + * @private + */ _optimizeEditsQueue: function() { var optimizedEdits = {}, @@ -926,227 +1073,23 @@ define([ return optimizedEdits; }, - _internalApplyEdits: function(layer,tempObjectIds,adds,updates,deletes) + /** + * DEPRECATED @ v2.5 + * A string value representing human readable information on pending edits + * @param edit + * @returns {string} + */ + getReadableEdit: function(edit) { - // unfortunately we can't use the promise that is returned from layer._applyEdits() - // because it returns 3 result parameters (addResults,updateResults,deleteResults) - // and when we combine all promises in the dojo/promise/all() method below this only - // supports promises that return one value - var dfd = new Deferred(); - -console.log("PRE APPLYEDITS " + adds + ", " + updates + ", " + deletes); - - var internalLayer = layer; - internalLayer.dfd; - - internalLayer._applyEdits(adds,updates,deletes, - function(addResults,updateResults,deleteResults) - { console.log("RUNNNING APPLY EDITS") - - try { - - internalLayer._phantomLayer.clear(); - internalLayer.onEditsComplete = internalLayer.__onEditsComplete; - delete internalLayer.__onEditsComplete; - internalLayer.onBeforeApplyEdits = internalLayer.__onBeforeApplyEdits; - delete internalLayer.__onBeforeApplyEdits; - var newObjectIds = addResults.map(function (r) { - return r.objectId; - }); - console.log("INTERNAL " + addResults + ", update " + updateResults + ", delete " + deleteResults) - // We use a different pattern if the attachmentsStore is valid and the layer has attachments - if (internalLayer._attachmentsStore != null && internalLayer.hasAttachments && tempObjectIds.length > 0) { - internalLayer._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 - } - }catch(err){ - console.log("ERROR ERROR " + err); - } - }, - function(error) - { console.log("INTERNAL ERROR " + adds + ", update " + JSON.stringify(updates) + ", delete " + deletes + ", " + JSON.stringify(error)) - internalLayer.onEditsComplete = internalLayer.__onEditsComplete; delete internalLayer.__onEditsComplete; - internalLayer.onBeforeApplyEdits = internalLayer.__onBeforeApplyEdits; delete internalLayer.__onBeforeApplyEdits; - dfd.reject(error); - } - ); - return dfd; - }, - - _replayStoredEdits: function(callback) - { - var promises = {}; - var deferredListArr = []; - var that = this; - - // - // send edits for each of the layers - // - var layerUrl, layer, layerEdits; - var adds, updates, deletes; - var tempObjectIds; - var objectId, c= 1000; - var i,g; - var tempArray = []; - var featureLayers = this._featureLayers; - var attachmentsStore = this.attachmentsStore; - var editStore = this._editStore; - - this._editStore.getAllEditsArray(function(result,err){ - if(result != null) { - tempArray = result; - //} - //else { - - var length = tempArray.length; - //length = 9; - - for(var n= 0;n < length;n++) { - layer = featureLayers[tempArray[n].layer]; - - // If the layer has attachments then check to see if the attachmentsStore has been initialized - if (attachmentsStore == null && layer.hasAttachments) { - console.log("ERROR: you need to run OfflineFeaturesManager.initAttachments(). Check the Attachments doc for more info.") - throw new Error("OfflineFeaturesManager: Attachments aren't initialized."); - } - - // Assign the attachmentsStore to the layer as a private var so we can access it from - // the promises applyEdits() method. - layer._attachmentsStore = attachmentsStore; - - //layerEdits = optimizedEdits[layerUrl]; - // - //console.assert(Object.keys(layerEdits).length !== 0); - - layer.__onEditsComplete = layer.onEditsComplete; - layer.onEditsComplete = function () { - console.log("intercepting events onEditsComplete"); - }; - layer.__onBeforeApplyEdits = layer.onBeforeApplyEdits; - layer.onBeforeApplyEdits = function () { - console.log("intercepting events onBeforeApplyEdits"); - }; - - adds = []; - updates = []; - deletes = []; - tempObjectIds = []; - - // IMPORTANT: we have to reconstitute the graphic JSON into an actual esri.Graphic object - var graphic = new Graphic(JSON.parse(tempArray[n].graphic)); - - switch (tempArray[n].operation) { - case editStore.ADD: - for (i = 0; i < layer.graphics.length; i++) { - g = layer.graphics[i]; - if (g.attributes[layer.objectIdField] === graphic.attributes[layer.objectIdField]) { - layer.remove(g); - break; - } - } - tempObjectIds.push(graphic.attributes[layer.objectIdField]); - delete graphic.attributes[layer.objectIdField]; - adds.push(graphic); - break; - case editStore.UPDATE: - updates.push(graphic); - break; - case editStore.DELETE: - deletes.push(graphic); - break; - } - console.log("TEST " + layer + ", ids " + tempObjectIds + ", adds " + adds + ", updates " + updates + ", deletes " + deletes) - console.log("COUNT " + n); - //setTimeout(function() - //{ - // promises[n] = that._internalApplyEdits(layer, tempObjectIds, adds, updates, deletes); - deferredListArr.push(that._internalApplyEdits(layer, tempObjectIds, adds, updates, deletes)); - //deferredListArr.push(test(layer, tempObjectIds, adds, updates, deletes)); - //}.bind(this),c++); - - - - //// closure to keep layer and tempObjectIds values - //promises[n] = function(layer,tempObjectIds) - //{ - // // unfortunately we can't use the promise that is returned from layer._applyEdits() - // // because it returns 3 result parameters (addResults,updateResults,deleteResults) - // // and when we combine all promises in the dojo/promise/all() method below this only - // // supports promises that return one value - // var dfd = new Deferred(); - // promises[n] = layer._applyEdits(adds,updates,deletes, - // function(addResults,updateResults,deleteResults) - // { - // layer._phantomLayer.clear(); - // layer.onEditsComplete = layer.__onEditsComplete; delete layer.__onEditsComplete; - // layer.onBeforeApplyEdits = layer.__onBeforeApplyEdits; delete layer.__onBeforeApplyEdits; - // 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({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); - - - if(n == length - 1) console.log("DONE with loop " ) - } - - } - - //return all(promises); - - // wait for all requests to finish - // - var allPromises = all(promises); - - var deferredList = new DeferredList(deferredListArr); - - deferredList.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(self), - function(errors) - { - console.log("ERROR!!"); - console.log(errors); - callback && callback(false,errors); - }.bind(this) - ); - }); + var layer = this._featureLayers[ edit.layer ]; + var graphic = this._editStore._deserialize(edit.graphic); + var readableGraphic = graphic.geometry.type; + var layerId = edit.layer.substring(edit.layer.lastIndexOf("/")+1); + if(layer) + { + readableGraphic += " [id=" + graphic.attributes[layer.objectIdField] + "]"; + } + return "o:" + edit.operation + ", l:" + layerId + ", g:" + readableGraphic; } }); // declare @@ -1530,19 +1473,23 @@ O.esri.Edit.EditStore = function() // internal methods - // - // graphic serialization/deserialization - // + /** + * Save space in the database...don't need to store the entire Graphic object just its public properties! + * @param graphic + * @returns {*} + * @private + */ this._serialize = function(graphic) { - // keep only attributes and geometry, that are the values that get sent to the server by applyEdits() // see http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#/Apply_Edits_Feature_Service_Layer/02r3000000r6000000/ // use graphic's built-in serializing method var json = graphic.toJson(); var jsonClean = { attributes: json.attributes, - geometry: json.geometry + geometry: json.geometry, + infoTemplate: json.infoTemplate, + symbol: json.symbol }; return JSON.stringify(jsonClean); }; diff --git a/lib/edit/editsStore.js b/lib/edit/editsStore.js index 97feeb6..199e08d 100644 --- a/lib/edit/editsStore.js +++ b/lib/edit/editsStore.js @@ -359,19 +359,23 @@ O.esri.Edit.EditStore = function() // internal methods - // - // graphic serialization/deserialization - // + /** + * Save space in the database...don't need to store the entire Graphic object just its public properties! + * @param graphic + * @returns {*} + * @private + */ this._serialize = function(graphic) { - // keep only attributes and geometry, that are the values that get sent to the server by applyEdits() // see http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#/Apply_Edits_Feature_Service_Layer/02r3000000r6000000/ // use graphic's built-in serializing method var json = graphic.toJson(); var jsonClean = { attributes: json.attributes, - geometry: json.geometry + geometry: json.geometry, + infoTemplate: json.infoTemplate, + symbol: json.symbol }; return JSON.stringify(jsonClean); }; diff --git a/lib/edit/offlineFeaturesManager.js b/lib/edit/offlineFeaturesManager.js index 83a60aa..2674343 100644 --- a/lib/edit/offlineFeaturesManager.js +++ b/lib/edit/offlineFeaturesManager.js @@ -76,47 +76,6 @@ define([ } }, - /** - * internal method that checks if this browser supports everything that is needed to handle offline attachments - * it also extends XMLHttpRequest with sendAsBinary() method, needed in Chrome - */ - _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; - } - console.log("The File APIs are not fully supported in this browser."); - return false; - }, - - /** - * internal method that extends an object with sendAsBinary() method - * sometimes extending XMLHttpRequest.prototype is not enough, as ArcGIS JS lib seems to mess with this object too - * @param oAjaxReq object to extend - */ - _extendAjaxReq: function(oAjaxReq) - { - oAjaxReq.sendAsBinary = XMLHttpRequest.prototype.sendAsBinary; - console.log("extending XMLHttpRequest"); - }, - /** * Overrides a feature layer. * @param layer @@ -129,7 +88,7 @@ define([ // Attempt to initialize the database self._editStore.init(function(result){ callback(result); - return; + //return; }); // we keep track of the FeatureLayer object @@ -431,7 +390,7 @@ define([ },this); all(promises).then( function(r) - { console.log("DONE WITH DATABASE PUSH"); + { //TO-DO - handle information related to any failed edits that didn't get stored /* we already pushed the edits into the database, now we let the FeatureLayer to do the local updating of the layer graphics */ @@ -645,25 +604,48 @@ define([ return this._onlineStatus; }, + /* internal methods */ + /** - * A string value representing human readable information on pending edits - * @param edit - * @returns {string} + * internal method that checks if this browser supports everything that is needed to handle offline attachments + * it also extends XMLHttpRequest with sendAsBinary() method, needed in Chrome */ - getReadableEdit: function(edit) + _checkFileAPIs: function() { - var layer = this._featureLayers[ edit.layer ]; - var graphic = this._editStore._deserialize(edit.graphic); - var readableGraphic = graphic.geometry.type; - var layerId = edit.layer.substring(edit.layer.lastIndexOf("/")+1); - if(layer) + if( window.File && window.FileReader && window.FileList && window.Blob ) { - readableGraphic += " [id=" + graphic.attributes[layer.objectIdField] + "]"; + 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; } - return "o:" + edit.operation + ", l:" + layerId + ", g:" + readableGraphic; + console.log("The File APIs are not fully supported in this browser."); + return false; }, - /* internal methods */ + /** + * internal method that extends an object with sendAsBinary() method + * sometimes extending XMLHttpRequest.prototype is not enough, as ArcGIS JS lib seems to mess with this object too + * @param oAjaxReq object to extend + */ + _extendAjaxReq: function(oAjaxReq) + { + oAjaxReq.sendAsBinary = XMLHttpRequest.prototype.sendAsBinary; + console.log("extending XMLHttpRequest"); + }, // // phantom symbols @@ -861,6 +843,171 @@ define([ // methods to send features back to the server // + _replayStoredEdits: function(callback) + { + var promises = {}; + var that = this; + + // + // send edits for each of the layers + // + var layer; + var adds = [], updates = [], deletes = []; + var tempObjectIds = []; + var tempArray = []; + var featureLayers = this._featureLayers; + var attachmentsStore = this.attachmentsStore; + var editStore = this._editStore; + + this._editStore.getAllEditsArray(function(result,err){ + if(result != null) { + tempArray = result; + + var length = tempArray.length; + + for(var n= 0;n < length;n++) { + layer = featureLayers[tempArray[n].layer]; + + // If the layer has attachments then check to see if the attachmentsStore has been initialized + if (attachmentsStore == null && layer.hasAttachments) { + console.log("ERROR: you need to run OfflineFeaturesManager.initAttachments(). Check the Attachments doc for more info.") + throw new Error("OfflineFeaturesManager: Attachments aren't initialized."); + } + + // Assign the attachmentsStore to the layer as a private var so we can access it from + // the promises applyEdits() method. + layer._attachmentsStore = attachmentsStore; + + layer.__onEditsComplete = layer.onEditsComplete; + layer.onEditsComplete = function () { + console.log("intercepting events onEditsComplete"); + }; + layer.__onBeforeApplyEdits = layer.onBeforeApplyEdits; + layer.onBeforeApplyEdits = function () { + console.log("intercepting events onBeforeApplyEdits"); + }; + + adds = [], updates = [], deletes = [], tempObjectIds = []; + + // IMPORTANT: reconstitute the graphic JSON into an actual esri.Graphic object + var graphic = new Graphic(JSON.parse(tempArray[n].graphic)); + + switch (tempArray[n].operation) { + case editStore.ADD: + for (var i = 0; i < layer.graphics.length; i++) { + var g = layer.graphics[i]; + if (g.attributes[layer.objectIdField] === graphic.attributes[layer.objectIdField]) { + layer.remove(g); + break; + } + } + tempObjectIds.push(graphic.attributes[layer.objectIdField]); + delete graphic.attributes[layer.objectIdField]; + adds.push(graphic); + break; + case editStore.UPDATE: + updates.push(graphic); + break; + case editStore.DELETE: + deletes.push(graphic); + break; + } + + promises[n] = that._internalApplyEdits(layer, tempObjectIds, adds, updates, deletes); + } + + } + + // wait for all requests to finish + // + var allPromises = all(promises); + allPromises.then( + function(responses) + { + console.log("OfflineFeaturesManager - all responses are back"); + this._cleanDatabase(responses); + this.emit(this.events.EDITS_SENT); + this.emit(this.events.ALL_EDITS_SENT); + callback && callback(true,responses); + }.bind(that), + function(errors) + { + console.log("OfflineFeaturesManager - ERROR!!"); + console.log(errors); + callback && callback(false,errors); + }.bind(that) + ); + }); + }, + + _cleanDatabase: function(responses){ + if( Object.keys(responses).length !== 0 ){ + for(var key in responses){ + if (responses.hasOwnProperty(key)) { + console.log("KEY " + key); + } + } + } + }, + + /** + * Executes the _applyEdits() method + * @param layer + * @param tempObjectIds + * @param adds + * @param updates + * @param deletes + * @returns {l.Deferred.promise|*|c.promise|q.promise|promise} + * @private + */ + _internalApplyEdits: function(layer,tempObjectIds,adds,updates,deletes) + { + var dfd = new Deferred(); + + layer._applyEdits(adds,updates,deletes, + function(addResults,updateResults,deleteResults) + { + layer._phantomLayer.clear(); + + layer.onBeforeApplyEdits = layer.__onBeforeApplyEdits; + delete layer.__onBeforeApplyEdits; + 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({ + 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.promise; + }, + + /** + * DEPRECATED @ v2.5 + * @returns {{}} + * @private + */ _optimizeEditsQueue: function() { var optimizedEdits = {}, @@ -923,227 +1070,23 @@ define([ return optimizedEdits; }, - _internalApplyEdits: function(layer,tempObjectIds,adds,updates,deletes) + /** + * DEPRECATED @ v2.5 + * A string value representing human readable information on pending edits + * @param edit + * @returns {string} + */ + getReadableEdit: function(edit) { - // unfortunately we can't use the promise that is returned from layer._applyEdits() - // because it returns 3 result parameters (addResults,updateResults,deleteResults) - // and when we combine all promises in the dojo/promise/all() method below this only - // supports promises that return one value - var dfd = new Deferred(); - -console.log("PRE APPLYEDITS " + adds + ", " + updates + ", " + deletes); - - var internalLayer = layer; - internalLayer.dfd; - - internalLayer._applyEdits(adds,updates,deletes, - function(addResults,updateResults,deleteResults) - { console.log("RUNNNING APPLY EDITS") - - try { - - internalLayer._phantomLayer.clear(); - internalLayer.onEditsComplete = internalLayer.__onEditsComplete; - delete internalLayer.__onEditsComplete; - internalLayer.onBeforeApplyEdits = internalLayer.__onBeforeApplyEdits; - delete internalLayer.__onBeforeApplyEdits; - var newObjectIds = addResults.map(function (r) { - return r.objectId; - }); - console.log("INTERNAL " + addResults + ", update " + updateResults + ", delete " + deleteResults) - // We use a different pattern if the attachmentsStore is valid and the layer has attachments - if (internalLayer._attachmentsStore != null && internalLayer.hasAttachments && tempObjectIds.length > 0) { - internalLayer._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 - } - }catch(err){ - console.log("ERROR ERROR " + err); - } - }, - function(error) - { console.log("INTERNAL ERROR " + adds + ", update " + JSON.stringify(updates) + ", delete " + deletes + ", " + JSON.stringify(error)) - internalLayer.onEditsComplete = internalLayer.__onEditsComplete; delete internalLayer.__onEditsComplete; - internalLayer.onBeforeApplyEdits = internalLayer.__onBeforeApplyEdits; delete internalLayer.__onBeforeApplyEdits; - dfd.reject(error); - } - ); - return dfd; - }, - - _replayStoredEdits: function(callback) - { - var promises = {}; - var deferredListArr = []; - var that = this; - - // - // send edits for each of the layers - // - var layerUrl, layer, layerEdits; - var adds, updates, deletes; - var tempObjectIds; - var objectId, c= 1000; - var i,g; - var tempArray = []; - var featureLayers = this._featureLayers; - var attachmentsStore = this.attachmentsStore; - var editStore = this._editStore; - - this._editStore.getAllEditsArray(function(result,err){ - if(result != null) { - tempArray = result; - //} - //else { - - var length = tempArray.length; - //length = 9; - - for(var n= 0;n < length;n++) { - layer = featureLayers[tempArray[n].layer]; - - // If the layer has attachments then check to see if the attachmentsStore has been initialized - if (attachmentsStore == null && layer.hasAttachments) { - console.log("ERROR: you need to run OfflineFeaturesManager.initAttachments(). Check the Attachments doc for more info.") - throw new Error("OfflineFeaturesManager: Attachments aren't initialized."); - } - - // Assign the attachmentsStore to the layer as a private var so we can access it from - // the promises applyEdits() method. - layer._attachmentsStore = attachmentsStore; - - //layerEdits = optimizedEdits[layerUrl]; - // - //console.assert(Object.keys(layerEdits).length !== 0); - - layer.__onEditsComplete = layer.onEditsComplete; - layer.onEditsComplete = function () { - console.log("intercepting events onEditsComplete"); - }; - layer.__onBeforeApplyEdits = layer.onBeforeApplyEdits; - layer.onBeforeApplyEdits = function () { - console.log("intercepting events onBeforeApplyEdits"); - }; - - adds = []; - updates = []; - deletes = []; - tempObjectIds = []; - - // IMPORTANT: we have to reconstitute the graphic JSON into an actual esri.Graphic object - var graphic = new Graphic(JSON.parse(tempArray[n].graphic)); - - switch (tempArray[n].operation) { - case editStore.ADD: - for (i = 0; i < layer.graphics.length; i++) { - g = layer.graphics[i]; - if (g.attributes[layer.objectIdField] === graphic.attributes[layer.objectIdField]) { - layer.remove(g); - break; - } - } - tempObjectIds.push(graphic.attributes[layer.objectIdField]); - delete graphic.attributes[layer.objectIdField]; - adds.push(graphic); - break; - case editStore.UPDATE: - updates.push(graphic); - break; - case editStore.DELETE: - deletes.push(graphic); - break; - } - console.log("TEST " + layer + ", ids " + tempObjectIds + ", adds " + adds + ", updates " + updates + ", deletes " + deletes) - console.log("COUNT " + n); - //setTimeout(function() - //{ - // promises[n] = that._internalApplyEdits(layer, tempObjectIds, adds, updates, deletes); - deferredListArr.push(that._internalApplyEdits(layer, tempObjectIds, adds, updates, deletes)); - //deferredListArr.push(test(layer, tempObjectIds, adds, updates, deletes)); - //}.bind(this),c++); - - - - //// closure to keep layer and tempObjectIds values - //promises[n] = function(layer,tempObjectIds) - //{ - // // unfortunately we can't use the promise that is returned from layer._applyEdits() - // // because it returns 3 result parameters (addResults,updateResults,deleteResults) - // // and when we combine all promises in the dojo/promise/all() method below this only - // // supports promises that return one value - // var dfd = new Deferred(); - // promises[n] = layer._applyEdits(adds,updates,deletes, - // function(addResults,updateResults,deleteResults) - // { - // layer._phantomLayer.clear(); - // layer.onEditsComplete = layer.__onEditsComplete; delete layer.__onEditsComplete; - // layer.onBeforeApplyEdits = layer.__onBeforeApplyEdits; delete layer.__onBeforeApplyEdits; - // 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({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); - - - if(n == length - 1) console.log("DONE with loop " ) - } - - } - - //return all(promises); - - // wait for all requests to finish - // - var allPromises = all(promises); - - var deferredList = new DeferredList(deferredListArr); - - deferredList.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(self), - function(errors) - { - console.log("ERROR!!"); - console.log(errors); - callback && callback(false,errors); - }.bind(this) - ); - }); + var layer = this._featureLayers[ edit.layer ]; + var graphic = this._editStore._deserialize(edit.graphic); + var readableGraphic = graphic.geometry.type; + var layerId = edit.layer.substring(edit.layer.lastIndexOf("/")+1); + if(layer) + { + readableGraphic += " [id=" + graphic.attributes[layer.objectIdField] + "]"; + } + return "o:" + edit.operation + ", l:" + layerId + ", g:" + readableGraphic; } }); // declare diff --git a/test/spec/offlineFeaturesManagerSpec.js b/test/spec/offlineFeaturesManagerSpec.js index bf42f6e..3a9750c 100644 --- a/test/spec/offlineFeaturesManagerSpec.js +++ b/test/spec/offlineFeaturesManagerSpec.js @@ -512,12 +512,6 @@ describe("Offline Editing", function() expect(result).toBe(6); done(); }); - countFeatures(g_featureLayers[0], function(success,result) - { - expect(success).toBeTruthy(); - expect(result.count).toBe(3); // still 3, the delete is still offline - done(); - }); }, function(error) { @@ -647,13 +641,13 @@ describe("Offline Editing", function() g_offlineFeaturesManager.goOnline(function(results,responses) { console.log("went online"); - //expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE); - //expect(listener).toHaveBeenCalled(); - //expect(results.features.success).toBeTruthy(); + expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE); + expect(listener).toHaveBeenCalled(); + expect(results.features.success).toBeTruthy(); console.log("RESPONSES " + JSON.stringify(responses) + ", " + JSON.stringify(results)) - expect(Object.keys(results.features.responses).length).toBe(2); + expect(Object.keys(results.features.responses).length).toBe(9); //for (var layerUrl in results.features.responses) { // if (!results.features.responses.hasOwnProperty(layerUrl)) // continue;