aboutsummaryrefslogtreecommitdiffstats
path: root/www/js
diff options
context:
space:
mode:
Diffstat (limited to 'www/js')
-rw-r--r--www/js/config.js-example8
-rw-r--r--www/js/models/draft.js26
-rw-r--r--www/js/models/report.js122
-rw-r--r--www/js/views/home.js2
-rw-r--r--www/js/views/offline.js6
-rw-r--r--www/js/views/photo.js76
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();
}
})
});