diff options
Diffstat (limited to 'www/js')
-rw-r--r-- | www/js/config.js-example | 8 | ||||
-rw-r--r-- | www/js/models/draft.js | 26 | ||||
-rw-r--r-- | www/js/models/report.js | 122 | ||||
-rw-r--r-- | www/js/views/home.js | 2 | ||||
-rw-r--r-- | www/js/views/offline.js | 6 | ||||
-rw-r--r-- | www/js/views/photo.js | 76 |
6 files changed, 173 insertions, 67 deletions
diff --git a/www/js/config.js-example b/www/js/config.js-example index a221cb0..3ca674c 100644 --- a/www/js/config.js-example +++ b/www/js/config.js-example @@ -50,6 +50,14 @@ var CONFIG = { // elements in your CSS. HELP_DISABLED: false, + // Set this to true if the user must provide at least one photo when making + // a report. If this is true the 'skip' button on the photo page is removed + // and 'next' doesn't appear until at least one photo is attached. + PHOTO_REQUIRED: false, + + // The maximum number of photos the user can attach to a report. + MAX_PHOTOS: 3, + // If this is true then the user must login as the first step after // installing the app, and before making any reports. LOGIN_REQUIRED: false, diff --git a/www/js/models/draft.js b/www/js/models/draft.js index 56d8111..e517f1b 100644 --- a/www/js/models/draft.js +++ b/www/js/models/draft.js @@ -3,17 +3,19 @@ Draft: Backbone.Model.extend({ localStorage: new Backbone.LocalStorage(CONFIG.NAMESPACE + '-drafts'), - defaults: { - lat: 0, - lon: 0, - title: '', - details: '', - may_show_name: '', - category: '', - phone: '', - pc: '', - file: '', - created: moment.utc() + defaults: function() { + return { + lat: 0, + lon: 0, + title: '', + details: '', + may_show_name: '', + category: '', + phone: '', + pc: '', + files: [], + created: moment.utc() + }; }, description: function() { @@ -33,7 +35,7 @@ this.get('title') || this.get('details') || this.get('category') || - this.get('file') + this.get('files').length ) { return true; } diff --git a/www/js/models/report.js b/www/js/models/report.js index 1a72e73..6926980 100644 --- a/www/js/models/report.js +++ b/www/js/models/report.js @@ -3,16 +3,18 @@ Report: Backbone.Model.extend({ urlRoot: CONFIG.FMS_URL + '/report/ajax', - defaults: { - lat: 0, - lon: 0, - title: '', - details: '', - may_show_name: '', - category: '', - phone: '', - pc: '', - file: '' + defaults: function() { + return { + lat: 0, + lon: 0, + title: '', + details: '', + may_show_name: '', + category: '', + phone: '', + pc: '', + files: [] + }; }, sync: function(method, model, options) { @@ -51,6 +53,74 @@ return false; }, + _readFileAsBase64String: function(file, success, error) { + return this._readFileAsBinaryString(file, function(data) { + var b64 = btoa(data); + success(b64); + }, error); + }, + + _readFileAsBinaryString: function(file, success, error) { + var reader = new FileReader(); + reader.onloadend = function() { + success(this.result); + }; + reader.onerror = error; + return reader.readAsBinaryString(file); + }, + + _getParamName: function(field, encoding, length) { + // The FileTransfer plugin technically only supports a single + // file in each upload. However, we can force other files to + // be added with a little workaround. + // FileTransfer allows extra parameters to be sent with the + // HTTP POST request, each of which is its own part of the + // multipart-encoded request. + // For a part to be treated as a file by the backend we need + // to provide a 'filename' value in the Content-Disposition + // header. The FileTransfer code doesn't escape the names of + // extra POST parameters[0][1], so we can take advantage of this + // and essentially inject our own header lines and filename + // value with a carefully-crafted HTTP POST field name that's + // passed to FileTransfer.upload. + // FIXME: This is basically a hack, and needs a better + // solution at some point. + // [0]: https://github.com/apache/cordova-plugin-file-transfer/blob/49c21f951f51381d887646b38823222ed11c60c1/src/ios/CDVFileTransfer.m#L208 + // [1]: https://github.com/apache/cordova-plugin-file-transfer/blob/49c21f951f51381d887646b38823222ed11c60c1/src/android/FileTransfer.java#L369 + var name = field + '"; filename="' + field + '.jpg"\r\n'; + name += "Content-Type: image/jpeg\r\n"; + name += "Content-Transfer-Encoding: " + encoding + "\r\n"; + name += "Content-Length: " + length + "\r\n"; + name += 'X-Ignore-This-Header: "'; // to close the open quotes + return name; + }, + + _addExtraPhotos: function(files, options, success, error) { + var photos = []; + for (var i = 0; i < files.length; i++) { + var uri = files[i]; + photos.push({field: "photo"+(i+2), uri: uri}); + } + this._addNextExtraPhoto(photos, options, success, error); + }, + + _addNextExtraPhoto: function(photos, options, success, error) { + var photo = photos.shift(); + if (photo === undefined) { + success(); + return; + } + var self = this; + resolveLocalFileSystemURL(photo.uri, function(fileentry) { + fileentry.file(function(file) { + self._readFileAsBase64String(file, function(data) { + options.params[self._getParamName(photo.field, "base64", data.length)] = data; + self._addNextExtraPhoto(photos, options, success, error); + }, error); + }, error); + }, error); + }, + post: function(model,options) { var params = { @@ -86,7 +156,7 @@ } var that = this; - if ( model.get('file') && model.get('file') !== '' ) { + if ( model.get('files') && model.get('files').length > 0 ) { var fileUploadSuccess = function(r) { FMS.uploading = false; $.mobile.loading('hide'); @@ -122,7 +192,8 @@ } }; - fileURI = model.get('file'); + var files = model.get('files').slice(); + fileURI = files.shift(); var fileOptions = new FileUploadOptions(); fileOptions.fileKey="photo"; @@ -169,14 +240,25 @@ uploadPcnt++; } }; - $.mobile.loading('show', { - text: FMS.strings.photo_loading, - textVisible: true, - html: '<span class="ui-icon ui-icon-loading"></span><h1>' + FMS.strings.photo_loading + '</h1><span id="progress"></span>' - }); - window.setTimeout( checkUpload, 15000 ); - FMS.uploading = true; - ft.upload(fileURI, CONFIG.FMS_URL + "/report/new/mobile", fileUploadSuccess, fileUploadFail, fileOptions); + + // If file2 or file3 have been set on this model we need to + // add the photos to the file upload request manually + // as FileTransfer only supports a single file upload. + that._addExtraPhotos( + files, + fileOptions, + function() { + $.mobile.loading('show', { + text: FMS.strings.photo_loading, + textVisible: true, + html: '<span class="ui-icon ui-icon-loading"></span><h1>' + FMS.strings.photo_loading + '</h1><span id="progress"></span>' + }); + window.setTimeout( checkUpload, 15000 ); + FMS.uploading = true; + ft.upload(fileURI, CONFIG.FMS_URL + "/report/new/mobile", fileUploadSuccess, fileUploadFail, fileOptions); + }, + fileUploadFail + ); }; setupChecker(); } else { diff --git a/www/js/views/home.js b/www/js/views/home.js index 05ee2f7..d82b874 100644 --- a/www/js/views/home.js +++ b/www/js/views/home.js @@ -31,7 +31,7 @@ this.navigate( 'login' ); } else if ( FMS.currentDraft && ( FMS.currentDraft.get('title') || FMS.currentDraft.get('lat') || - FMS.currentDraft.get('details') || FMS.currentDraft.get('file') ) + FMS.currentDraft.get('details') || FMS.currentDraft.get('files').length > 0 ) ) { this.navigate( 'existing' ); } else { diff --git a/www/js/views/offline.js b/www/js/views/offline.js index 3c2f7af..c5264a1 100644 --- a/www/js/views/offline.js +++ b/www/js/views/offline.js @@ -15,7 +15,7 @@ 'vclick .ui-btn-right': 'onClickButtonNext', 'vclick #id_photo_button': 'takePhoto', 'vclick #id_existing': 'addPhoto', - 'vclick #id_del_photo_button': 'deletePhoto', + 'vclick .del_photo_button': 'deletePhoto', 'vclick #locate': 'onClickLocate', 'vclick #locate_cancel': 'onClickCancel', 'blur input': 'toggleNextButton', @@ -86,7 +86,7 @@ var that = this; move.done( function( file ) { - $('#photo').attr('src', file.toURL()); + $('.photo-wrapper .photo img').attr('src', file.toURL()); that.model.set('file', file.toURL()); FMS.saveCurrentDraft(); @@ -113,7 +113,7 @@ del.done( function() { that.model.set('file', ''); FMS.saveCurrentDraft(); - $('#photo').attr('src', ''); + $('.photo-wrapper .photo img').attr('src', ''); $('#photo-next-btn .ui-btn-text').text('Skip'); $('#display_photo').hide(); diff --git a/www/js/views/photo.js b/www/js/views/photo.js index 8891298..6ba4a01 100644 --- a/www/js/views/photo.js +++ b/www/js/views/photo.js @@ -14,17 +14,22 @@ 'vclick .ui-btn-right': 'onClickButtonNext', 'vclick #id_photo_button': 'takePhoto', 'vclick #id_existing': 'addPhoto', - 'vclick #id_del_photo_button': 'deletePhoto' + 'vclick .del_photo_button': 'deletePhoto' }, beforeDisplay: function() { this.fixPageHeight(); - this.$('#id_del_photo_button').hide(); - if ( this.model.get('file') ) { - $('#id_photo_button').parents('.ui-btn').hide(); - $('#id_existing').parents('.ui-btn').hide(); - window.setTimeout( function() { $('#id_del_photo_button').show(); }, 250 ); - } + }, + + afterDisplay: function() { + // The height of the photos container needs to be adjusted + // depending on the number of photos - if there are 3 photos + // then the 'add photo' UI isn't shown so we should use all the + // vertical space for the thumbnails. + var wrapperHeight = $(".ui-content").height(); + wrapperHeight -= $(".ui-content h2").outerHeight(true); + wrapperHeight -= $(".ui-content .bottom-btn").outerHeight(true) + $(".photo-wrapper").height(wrapperHeight); }, getOptions: function(isFromAlbum) { @@ -52,7 +57,7 @@ takePhoto: function(e) { e.preventDefault(); $.mobile.loading('show'); - $('#photo').hide(); + $('.photo-wrapper .photo img').hide(); var that = this; var options = this.getOptions(); @@ -63,7 +68,7 @@ addPhoto: function(e) { e.preventDefault(); $.mobile.loading('show'); - $('#photo').hide(); + $('.photo-wrapper .photo img').hide(); var that = this; var options = this.getOptions(true); navigator.camera.getPicture( function(imgURI) { that.addPhotoSuccess(imgURI); }, function(error) { that.addPhotoFail(error); }, options); @@ -86,25 +91,22 @@ var that = this; move.done( function( file ) { - $('#nophoto_title').hide(); - $('#photo_title').html(FMS.strings.photo_added).show(); - $('#photo').attr('src', file.toURL()).addClass('small').removeClass('placeholder'); - that.model.set('file', file.toURL()); + var files = that.model.get('files'); + files.push(file.toURL()); + that.model.set('files', files); FMS.saveCurrentDraft(); - $('#photo-next-btn .ui-btn-text').text(FMS.strings.next); - $('#id_photo_button').parents('.ui-btn').hide(); - $('#id_existing').parents('.ui-btn').hide(); - $('#photo').show(); - window.setTimeout(function() { $('#id_del_photo_button').show() }, 500); - window.setTimeout(function() { $.mobile.loading('hide') }, 100); + window.setTimeout(function() { + $.mobile.loading('hide'); + that.rerender(); + }, 100); }); move.fail( function() { that.addPhotoFail(); } ); }, addPhotoFail: function(message) { - $('#photo').show(); + $('.photo-wrapper .photo img').show(); $.mobile.loading('hide'); if ( message != 'no image selected' && message != 'Selection cancelled.' && @@ -115,22 +117,34 @@ deletePhoto: function(e) { e.preventDefault(); - var that = this; - var del = FMS.files.deleteURI( this.model.get('file') ); + var files = this.model.get('files'); + var index = parseInt($(e.target).data('fileIndex')); + var deleted_file = files.splice(index, 1)[0]; + + var del = FMS.files.deleteURI( deleted_file ); + var that = this; del.done( function() { - $('#photo_title').hide(); - $('#nophoto_title').show(); - $('#id_del_photo_button').hide(); - that.model.set('file', ''); + // $('#photo_title').hide(); + // $('#nophoto_title').show(); + // $('.del_photo_button').hide(); + that.model.set('files', files); FMS.saveCurrentDraft(true); - $('#photo').attr('src', 'images/placeholder-photo.png').addClass('placeholder').removeClass('small'); - - $('#photo-next-btn .ui-btn-text').text('Skip'); - $('#id_photo_button').parents('.ui-btn').show(); - $('#id_existing').parents('.ui-btn').show(); + that.rerender(); + // $('.photo-wrapper .photo img').attr('src', 'images/placeholder-photo.png').addClass('placeholder').removeClass('small'); + // + // $('#photo-next-btn .ui-btn-text').text('Skip'); + // $('#id_photo_button').parents('.ui-btn').show(); + // $('#id_existing').parents('.ui-btn').show(); }); + }, + + rerender: function() { + // Simply calling this.render() breaks the DOM in a weird and + // interesting way, so this is a convenience wrapper around + // the correct router call. + FMS.router.photo(); } }) }); |