diff options
Diffstat (limited to 'www/js/views')
-rw-r--r-- | www/js/views/around.js | 502 | ||||
-rw-r--r-- | www/js/views/details.js | 151 | ||||
-rw-r--r-- | www/js/views/details_extra.js | 108 | ||||
-rw-r--r-- | www/js/views/existing.js | 50 | ||||
-rw-r--r-- | www/js/views/fms.js | 117 | ||||
-rw-r--r-- | www/js/views/home.js | 40 | ||||
-rw-r--r-- | www/js/views/locator.js | 69 | ||||
-rw-r--r-- | www/js/views/login.js | 108 | ||||
-rw-r--r-- | www/js/views/offline.js | 160 | ||||
-rw-r--r-- | www/js/views/photo.js | 137 | ||||
-rw-r--r-- | www/js/views/reports.js | 104 | ||||
-rw-r--r-- | www/js/views/save_offline.js | 32 | ||||
-rw-r--r-- | www/js/views/search.js | 81 | ||||
-rw-r--r-- | www/js/views/sent.js | 44 | ||||
-rw-r--r-- | www/js/views/submit.js | 480 |
15 files changed, 2183 insertions, 0 deletions
diff --git a/www/js/views/around.js b/www/js/views/around.js new file mode 100644 index 0000000..57b4bc6 --- /dev/null +++ b/www/js/views/around.js @@ -0,0 +1,502 @@ +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + AroundView: FMS.LocatorView.extend({ + template: 'around', + id: 'around-page', + + events: { + 'pagehide': 'destroy', + 'pagebeforeshow': 'beforeDisplay', + 'pageshow': 'afterDisplay', + 'vclick #locate_cancel': 'goSearch', + 'vclick #login-options': 'goLogin', + 'vclick #view-my-reports': 'goReports', + 'vclick #search': 'goSearch', + 'vclick .ui-input-clear': 'clearSearchErrors', + 'blur #pc': 'clearSearchErrors', + 'vclick #relocate': 'centerMapOnPosition', + 'vclick #cancel': 'onClickCancel', + 'vclick #confirm-map': 'onClickReport', + 'vclick #mark-here': 'onClickMark', + 'vclick #locate-here': 'onClickMark', + 'vclick #reposition': 'onClickReposition', + 'vclick a.address': 'goAddress', + 'submit #postcodeForm': 'search' + }, + + render: function(){ + if ( !this.template ) { + FMS.printDebug('no template to render'); + return; + } + template = _.template( tpl.get( this.template ) ); + if ( this.model ) { + this.$el.html(template({ model: this.model.toJSON(), user: FMS.currentUser.toJSON() })); + } else { + this.$el.html(template()); + } + this.afterRender(); + return this; + }, + + beforeDisplay: function() { + this.origPcPlaceholder = $('#pc').attr('placeholder'); + $('a[data-role="button"]').hide(); + $('#view-my-reports').hide(); + $('#login-options').hide(); + $('#postcodeForm').hide(); + $('#relocate').hide(); + $('#cancel').hide(); + $('#map_box').removeClass('background-map'); + this.fixPageHeight(); + $('#map_box').on('touchend', function() { if ( ! $('#popup').length ) { $('#OpenLayers_Control_Crosshairs_crosshairs').show(); } } ); + }, + + afterDisplay: function() { + if ( FMS.isOffline ) { + $('#locating').hide(); + this.navigate( 'offline' ); + } else if ( this.model && this.model.get('lat') ) { + var modelInfo = { coordinates: { latitude: this.model.get('lat'), longitude: this.model.get('lon') } }; + this.setMapPosition(modelInfo); + this.displayButtons(true); + this.setReportPosition({ lat: this.model.get('lat'), lon: this.model.get('lon') }, true); + this.listenTo(FMS.locator, 'gps_current_position', this.positionUpdate); + FMS.locator.trackPosition(); + } else if ( FMS.currentPosition ) { + var info = { coordinates: FMS.currentPosition }; + FMS.currentPosition = null; + if ( !fixmystreet.map ) { + this.setMapPosition(info); + } + this.displayButtons(false); + this.listenTo(FMS.locator, 'gps_current_position', this.positionUpdate); + FMS.locator.trackPosition(); + } else { + this.locate(); + this.displayButtons(false); + } + }, + + _back: function(e) { + if ( $('#confirm-map').css('display') == 'block' ) { + this.onClickCancel(e); + } else { + navigator.app.exitApp(); + } + }, + + setMapPosition: function( info ) { + var coords = info.coordinates; + fixmystreet.latitude = coords.latitude; + fixmystreet.longitude = coords.longitude; + + if ( !fixmystreet.map ) { + show_map(); + } else { + FMS.currentPosition = coords; + var centre = this.projectCoords( coords ); + fixmystreet.map.panTo(centre); + } + }, + + gotLocation: function( info ) { + $('#relocate').show(); + this.finishedLocating(); + + this.listenTo(FMS.locator, 'gps_current_position', this.positionUpdate); + + this.located = true; + this.locateCount = 21; + + this.setMapPosition( info ); + + FMS.locator.trackPosition(); + // FIXME: not sure why I need to do this + fixmystreet.select_feature.deactivate(); + fixmystreet.select_feature.activate(); + fixmystreet.nav.activate(); + this.displayHelpIfFirstTime(); + }, + + positionUpdate: function( info ) { + if ( $('#front-howto').is(':hidden') ) { + $('#relocate').show(); + } + FMS.currentPosition = info.coordinates; + var centre = this.projectCoords( info.coordinates ); + + var point = new OpenLayers.Geometry.Point( centre.lon, centre.lat ); + + fixmystreet.location.removeAllFeatures(); + var x = new OpenLayers.Feature.Vector( + point, + {}, + { + graphicZIndex: 3000, + graphicName: 'circle', + 'externalGraphic': 'images/gps-marker.svg', + pointRadius: 16 + } + ); + fixmystreet.location.addFeatures([ x ]); + }, + + centerMapOnPosition: function(e) { + e.preventDefault(); + if ( !fixmystreet.map ) { + return; + } + + // if there isn't a currentPosition then something + // is up so we probably should not recenter + if ( FMS.currentPosition ) { + fixmystreet.map.panTo(this.projectCoords( FMS.currentPosition )); + + // If we've confirmed the report position then we should be able + // to reposition it if we've moved the map. + if ( $('#confirm-map').css('display') == 'block' ) { + var currentPos = this.projectCoords(FMS.currentPosition); + var markerPos = this.getMarkerPosition(true); + + // Displaying the button if the report is in the same place as the + // GPS location could be confusing so check they are different. + // The slight margin of error is there to account for both rounding + // wiggle in the projectCoords and also so that small changes due to + // GPS noise are ignored + if ( Math.abs(markerPos.lat - currentPos.lat) > 1 || + Math.abs(markerPos.lon - currentPos.lon) > 1 ) { + $('#reposition').show(); + } + } + } + }, + + failedLocation: function( details ) { + this.finishedLocating(); + this.locateCount = 21; + var msg = ''; + if ( details.msg ) { + msg = details.msg; + } else { + msg = FMS.strings.location_problem; + } + if ( !fixmystreet.map ) { + $('#relocate').hide(); + $('#mark-here').hide(); + // if we are going to display the help then we don't want to focus on + // the search box as it will show through the help + if ( FMS.usedBefore ) { + $('#pc').attr('placeholder', FMS.strings.search_placeholder).focus(); + } + } + $('#front-howto').html('<p>' + msg + '</p>'); + $('#front-howto').show(); + + this.displayHelpIfFirstTime(); + }, + + displayHelpIfFirstTime: function() { + if ( !FMS.usedBefore ) { + FMS.helpShow(); + } + }, + + displayButtons: function(isLocationSet) { + if ( fixmystreet.map ) { + fixmystreet.nav.activate(); + fixmystreet.actionafterdrag.activate(); + } + if (isLocationSet) { + $('#cancel').addClass('ui-btn-left').show(); + $('#confirm-map').show(); + $('#view-my-reports').hide(); + $('#login-options').hide(); + $('#mark-here').hide(); + $('#locate-here').hide(); + $('#postcodeForm').hide(); + if ( fixmystreet.map ) { + fixmystreet.markers.setVisibility(false); + fixmystreet.select_feature.deactivate(); + fixmystreet.bbox_strategy.deactivate(); + } + } else { + if ( FMS.currentDraft.isPartial() ) { + $('#cancel').addClass('ui-btn-left').show(); + $('#view-my-reports').hide(); + $('#login-options').hide(); + $('#locate-here').show(); + } else { + $('#cancel').hide().removeClass('ui-btn-left'); + $('#view-my-reports').show(); + $('#login-options').show(); + $('#mark-here').show(); + $('#locate-here').hide(); + } + $('#confirm-map').hide(); + $('#postcodeForm').show(); + $('#reposition').hide(); + if ( fixmystreet.map ) { + fixmystreet.bbox_strategy.activate(); + fixmystreet.report_location.setVisibility(false); + fixmystreet.markers.setVisibility(true); + fixmystreet.select_feature.deactivate(); + fixmystreet.select_feature.activate(); + } + } + }, + + setReportPosition: function(lonlat, convertPosition) { + var markers = fms_markers_list( [ [ lonlat.lat, lonlat.lon, 'green', 'location', '', 'location' ] ], convertPosition ); + fixmystreet.report_location.removeAllFeatures(); + fixmystreet.report_location.addFeatures( markers ); + fixmystreet.report_location.setVisibility(true); + }, + + onClickMark: function(e) { + e.preventDefault(); + this.clearSearchErrors(); + this.displayButtons(true); + $('#popup').hide(); + $('#OpenLayers_Control_Crosshairs_crosshairs').show(); + $('#reposition').hide(); + + var lonlat = this.getCrossHairPosition(); + this.setReportPosition(lonlat, true); + }, + + onClickCancel: function(e) { + e.preventDefault(); + fixmystreet.markers.removeAllFeatures(); + fixmystreet_activate_drag(); + // force pins to be refetched and displayed + fixmystreet.bbox_strategy.update({force: true}); + if ( this.model.isPartial() ) { + FMS.clearCurrentDraft(); + } else { + // it's not partial but we've created a draft anyway so + // delete it + if ( this.model.id ) { + var del = FMS.removeDraft( this.model.id, true ); + var that = this; + del.done( function() { that.decrementDraftCount(); } ); + } + this.model.set('lat', null); + this.model.set('lon', null); + } + this.displayButtons(false); + }, + + decrementDraftCount: function() { + var counter = $('#view-my-reports .draft_count'); + var count = counter.text(); + count--; + counter.text(count); + }, + + onClickReposition: function(e) { + e.preventDefault(); + var lonlat = this.getCrossHairPosition(); + lonlat.transform( + new OpenLayers.Projection("EPSG:4326"), + fixmystreet.map.getProjectionObject() + ); + fixmystreet.report_location.features[0].move(lonlat); + $('#reposition').hide(); + }, + + onClickReport: function(e) { + e.preventDefault(); + var position = this.getMarkerPosition(); + + if ( FMS.isOffline ) { + this.stopListening(FMS.locator); + FMS.locator.stopTracking(); + // these may be out of the area but lets just save them + // for now and they can be checked when we are online. + this.model.set('lat', position.lat ); + this.model.set('lon', position.lon ); + FMS.saveCurrentDraft(); + this.navigate( 'offline' ); + } else { + this.listenTo(FMS.locator, 'gps_located', this.goPhoto); + this.listenTo(FMS.locator, 'gps_failed', this.locationCheckFailed ); + FMS.locator.check_location( { latitude: position.lat, longitude: position.lon } ); + } + }, + + search: function(e) { + $('#pc').blur(); + // this is to stop form submission + e.preventDefault(); + $('#front-howto').hide(); + this.clearSearchErrors(); + this.clearValidationErrors(); + var pc = this.$('#pc').val(); + this.listenTo(FMS.locator, 'search_located', this.searchSuccess ); + this.listenTo(FMS.locator, 'search_failed', this.searchFail); + + FMS.locator.lookup(pc); + }, + + searchSuccess: function( info ) { + this.stopListening(FMS.locator, 'search_located'); + this.stopListening(FMS.locator, 'search_failed'); + var coords = info.coordinates; + if ( fixmystreet.map ) { + fixmystreet.map.panTo(this.projectCoords( coords )); + } else { + this.setMapPosition(info); + this.displayButtons(false); + } + }, + + goAddress: function(e) { + $('#relocate').show(); + $('#front-howto').html('').hide(); + var t = $(e.target); + var lat = t.attr('data-lat'); + var long = t.attr('data-long'); + + var coords = { latitude: lat, longitude: long }; + if ( fixmystreet.map ) { + fixmystreet.map.panTo(this.projectCoords( coords )); + } else { + this.setMapPosition({ coordinates: coords }); + } + }, + + searchError: function(msg) { + if ( msg.length < 30 ) { + $('#pc').attr('placeholder', msg).addClass('error');; + } else { + $('#front-howto').html(msg); + $('#relocate').hide(); + $('#front-howto').show(); + } + }, + + clearSearchErrors: function() { + $('#pc').attr('placeholder', this.origPcPlaceholder).removeClass('error');; + if ( fixmystreet.map ) { + $('#front-howto').hide(); + $('#relocate').show(); + } + }, + + searchFail: function( details ) { + // this makes sure any onscreen keyboard is dismissed + $('#submit').focus(); + this.stopListening(FMS.locator, 'search_located'); + this.stopListening(FMS.locator, 'search_failed'); + if ( details.msg ) { + this.searchError( details.msg ); + } else if ( details.locations ) { + var multiple = ''; + for ( var i = 0; i < details.locations.length; i++ ) { + var loc = details.locations[i]; + var li = '<li><a class="address" id="location_' + i + '" data-lat="' + loc.lat + '" data-long="' + loc.long + '">' + loc.address + '</a></li>'; + multiple = multiple + li; + } + $('#front-howto').html('<p>Multiple matches found</p><ul data-role="listview" data-inset="true">' + multiple + '</ul>'); + $('.ui-page').trigger('create'); + $('#relocate').hide(); + $('#front-howto').show(); + } else { + this.searchError( FMS.strings.location_problem ); + } + }, + + pauseMap: function() { + this.stopListening(FMS.locator); + FMS.locator.stopTracking(); + if ( FMS.iPhoneModel > 3 ) { + $('#map_box').addClass('background-map'); + } + $('#map_box').off('touchend'); + if ( fixmystreet.map ) { + fixmystreet.nav.deactivate(); + fixmystreet.actionafterdrag.deactivate(); + } + }, + + goPhoto: function(info) { + this.pauseMap(); + this.model.set('lat', info.coordinates.latitude ); + this.model.set('lon', info.coordinates.longitude ); + this.model.set('categories', info.details.category ); + if ( info.details.titles_list ) { + this.model.set('titles_list', info.details.titles_list); + } + FMS.saveCurrentDraft(); + + this.navigate( 'photo' ); + }, + + locationCheckFailed: function() { + this.displayAlert(FMS.strings.location_check_failed); + }, + + goSearch: function(e) { + e.preventDefault(); + if ( !fixmystreet.map ) { + this.$('#mark-here').hide(); + this.$('#relocate').hide(); + $('#front-howto').html('<p>' + FMS.strings.locate_dismissed + '</p>'); + $('#front-howto').show(); + } + this.finishedLocating(); + }, + + goLogin: function(e) { + e.preventDefault(); + this.pauseMap(); + this.navigate( 'login' ); + }, + + goReports: function(e) { + e.preventDefault(); + this.pauseMap(); + this.navigate( 'reports' ); + }, + + getCrossHairPosition: function() { + var cross = fixmystreet.map.getControlsByClass( + "OpenLayers.Control.Crosshairs"); + + var position = cross[0].getMapPosition(); + position.transform( + fixmystreet.map.getProjectionObject(), + new OpenLayers.Projection("EPSG:4326") + ); + + return position; + }, + + getMarkerPosition: function(skipTransform) { + var marker = fixmystreet.report_location.features[0].geometry; + + var position = new OpenLayers.LonLat( marker.x, marker.y ); + if ( skipTransform ) { + return position; + } + position.transform( + fixmystreet.map.getProjectionObject(), + new OpenLayers.Projection("EPSG:4326") + ); + + return position; + }, + + projectCoords: function( coords ) { + var centre = new OpenLayers.LonLat( coords.longitude, coords.latitude ); + centre.transform( + new OpenLayers.Projection("EPSG:4326"), + fixmystreet.map.getProjectionObject() + ); + + return centre; + } + }) + }); +})(FMS, Backbone, _, $); diff --git a/www/js/views/details.js b/www/js/views/details.js new file mode 100644 index 0000000..9f03d58 --- /dev/null +++ b/www/js/views/details.js @@ -0,0 +1,151 @@ +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + DetailsView: FMS.FMSView.extend({ + template: 'details', + id: 'details-page', + prev: 'photo', + next: 'submit-start', + bottomMargin: -20, + + events: { + 'pagehide': 'destroy', + 'pagebeforeshow': 'beforeDisplay', + 'pageshow': 'afterDisplay', + 'vclick .ui-btn-left': 'onClickButtonPrev', + 'vclick .ui-btn-right': 'onClickButtonNext', + 'blur textarea': 'updateCurrentReport', + 'change select': 'updateSelect', + 'blur input': 'updateCurrentReport' + }, + + afterRender: function() { + this.$('#form_category').attr('data-role', 'none'); + + if ( this.model.get('category') ) { + this.$('#form_category').val( this.model.get('category') ); + } + this.setSelectClass(); + + }, + + beforeDisplay: function() { + this.fixPageHeight(); + var header = this.$("div[data-role='header']:visible"), + detail = this.$('#form_detail'), + top = detail.position().top, + viewHeight = $(window).height(), + contentHeight = viewHeight - header.outerHeight() + 15; + + detail.height( contentHeight - top ); + }, + + onClickButtonPrev: function(e) { + e.preventDefault(); + this.updateCurrentReport(); + this.navigate( this.prev, true ); + }, + + onClickButtonNext: function(e) { + e.preventDefault(); + // dismiss on screen keyboard + $('.ui-btn-right').focus(); + this.clearValidationErrors(); + var valid = 1; + + if ( !$('#form_title').val() ) { + valid = 0; + this.validationError( 'form_title', FMS.validationStrings.title ); + } + + if ( !$('#form_detail').val() ) { + valid = 0; + this.validationError( 'form_detail', FMS.validationStrings.detail ); + } + + var cat = $('#form_category').val(); + if ( cat == '-- Pick a category --' ) { + valid = 0; + this.validationError( 'form_category', FMS.validationStrings.category ); + } + + if ( valid ) { + this.clearValidationErrors(); + this.updateCurrentReport(); + if ( FMS.isOffline ) { + this.navigate( 'save_offline' ); + } else { + var that = this; + $.ajax( { + url: CONFIG.FMS_URL + '/report/new/category_extras', + type: 'POST', + data: { + category: this.model.get('category'), + latitude: this.model.get('lat'), + longitude: this.model.get('lon') + }, + dataType: 'json', + timeout: 30000, + success: function( data, status ) { + if ( data && data.category_extra && data.category_extra.length > 0 ) { + that.model.set('category_extras', data.category_extra); + that.navigate('details_extra'); + } else { + that.navigate( that.next ); + } + }, + error: function() { + that.displayAlert(FMS.strings.category_extra_check_error); + } + } ); + } + } + }, + + validationError: function(id, error) { + var el_id = '#' + id; + var el = $(el_id); + + el.addClass('error'); + if ( el.val() === '' ) { + el.attr('orig-placeholder', el.attr('placeholder')); + el.attr('placeholder', error); + } + }, + + clearValidationErrors: function() { + $('.error').removeClass('error'); + $('.error').each(function(el) { if ( el.attr('orig-placeholder') ) { el.attr('placeholder', el.attr('orig-placeholder') ); } } ); + }, + + setSelectClass: function() { + var cat = this.$('#form_category'); + if ( cat.val() !== "" && cat.val() !== '-- Pick a category --' ) { + cat.removeClass('noselection'); + } else { + cat.addClass('noselection'); + } + }, + + updateSelect: function() { + this.updateCurrentReport(); + this.setSelectClass(); + }, + + updateCurrentReport: function() { + var category = $('#form_category').val(); + if ( category === '-- Pick a category --' ) { + category = ''; + } + if ( category && $('#form_title').val() && $('#form_detail').val() ) { + $('#next').addClass('page_complete_btn'); + } else { + $('#next').removeClass('page_complete_btn'); + } + this.model.set('category', category); + this.model.set('title', $('#form_title').val()); + this.model.set('details', $('#form_detail').val()); + FMS.saveCurrentDraft(); + } + }) + }); +})(FMS, Backbone, _, $); diff --git a/www/js/views/details_extra.js b/www/js/views/details_extra.js new file mode 100644 index 0000000..160ff11 --- /dev/null +++ b/www/js/views/details_extra.js @@ -0,0 +1,108 @@ +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + DetailsExtraView: FMS.FMSView.extend({ + template: 'details_extra', + id: 'details-extra-page', + prev: 'details', + next: 'submit-start', + + events: { + 'pagehide': 'destroy', + 'pagebeforeshow': 'beforeDisplay', + 'pageshow': 'afterDisplay', + 'vclick .ui-btn-left': 'onClickButtonPrev', + 'vclick .ui-btn-right': 'onClickButtonNext', + 'blur textarea': 'updateCurrentReport', + 'change select': 'updateCurrentReport', + 'blur input': 'updateCurrentReport' + }, + + afterRender: function() { + this.populateFields(); + }, + + onClickButtonPrev: function() { + this.model.set('hasExtras', 0); + this.updateCurrentReport(); + this.navigate( this.prev, true ); + }, + + onClickButtonNext: function() { + this.clearValidationErrors(); + var valid = 1; + var that = this; + + var isRequired = function(index) { + var el = $(this); + if ( el.attr('required') && el.val() === '' ) { + valid = 0; + that.validationError(el.attr('id'), FMS.strings.required); + } + }; + // do validation + $('input').each(isRequired); + $('textarea').each(isRequired); + $('select').each(isRequired); + this.model.set('hasExtras', 1); + + if ( valid ) { + this.clearValidationErrors(); + this.updateCurrentReport(); + this.navigate( this.next ); + } + }, + + validationError: function(id, error) { + var el_id = '#' + id; + var el = $(el_id); + + el.addClass('error'); + if ( el.val() === '' ) { + el.attr('orig-placeholder', el.attr('placeholder')); + el.attr('placeholder', error); + } + }, + + clearValidationErrors: function() { + $('.error').removeClass('error'); + $('.error').each(function(el) { if ( el.attr('orig-placeholder') ) { el.attr('placeholder', el.attr('orig-placeholder') ); } } ); + }, + + updateSelect: function() { + this.updateCurrentReport(); + }, + + updateCurrentReport: function() { + var fields = []; + var that = this; + var update = function(index) { + var el = $(this); + if ( el.val() !== '' ) { + that.model.set(el.attr('name'), el.val()); + fields.push(el.attr('name')); + } else { + that.model.set(el.attr('name'), ''); + } + + }; + + $('input').each(update); + $('select').each(update); + $('textarea').each(update); + + this.model.set('extra_details', fields); + FMS.saveCurrentDraft(); + }, + + populateFields: function() { + var that = this; + var populate = function(index) { + that.$(this).val(that.model.get(that.$(this).attr('name'))); + }; + this.$('input').each(populate); + this.$('select').each(populate); + this.$('textarea').each(populate); + } + }) + }); +})(FMS, Backbone, _, $); diff --git a/www/js/views/existing.js b/www/js/views/existing.js new file mode 100644 index 0000000..a34fd0b --- /dev/null +++ b/www/js/views/existing.js @@ -0,0 +1,50 @@ +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + ExistingView: FMS.FMSView.extend({ + template: 'existing', + id: 'existing', + + events: { + 'pagehide': 'destroy', + 'pagebeforeshow': 'beforeDisplay', + 'pageshow': 'afterDisplay', + 'vclick #use_report': 'useReport', + 'vclick #save_report': 'saveReport', + 'vclick #discard': 'discardReport' + }, + + _back: function() { + navigator.app.exitApp(); + }, + + setHeight: function(content, height) { + content.css( 'min-height', content + 'px'); + }, + + useReport: function(e) { + e.preventDefault(); + FMS.setCurrentDraft(this.model); + this.navigate('around'); + }, + + saveReport: function(e) { + e.preventDefault(); + FMS.clearCurrentDraft(); + this.navigate('around'); + }, + + discardReport: function(e) { + e.preventDefault(); + var reset = FMS.removeDraft(this.model.id, true); + var that = this; + reset.done( function() { that.onDraftRemove(); } ); + reset.fail( function() { that.onDraftRemove(); } ); + }, + + onDraftRemove: function() { + FMS.clearCurrentDraft(); + this.navigate( 'around', 'left' ); + } + }) + }); +})(FMS, Backbone, _, $); diff --git a/www/js/views/fms.js b/www/js/views/fms.js new file mode 100644 index 0000000..1b51a27 --- /dev/null +++ b/www/js/views/fms.js @@ -0,0 +1,117 @@ +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + FMSView: Backbone.View.extend({ + tag: 'div', + bottomMargin: 20, + contentSelector: '[data-role="content"]', + + events: { + 'pagehide': 'destroy', + 'pagebeforeshow': 'beforeDisplay', + 'pageshow': 'afterDisplay', + 'vclick .ui-btn-left': 'onClickButtonPrev', + 'vclick .ui-btn-right': 'onClickButtonNext' + }, + + back: function(e) { + if ( $('#help #dismiss').is(':visible') ) { + FMS.helpHide(); + } else if ( this._back ) { + this._back(e); + } else if ( this.prev ) { + this.onClickButtonPrev(e); + } + }, + + render: function(){ + if ( !this.template ) { + FMS.printDebug('no template to render'); + return; + } + template = _.template( tpl.get( this.template ) ); + var args = null; + if ( this.options.msg ) { + args = { msg: this.options.msg }; + } + if ( this.model ) { + if ( args ) { + args.model = this.model.toJSON(); + } else { + args = this.model.toJSON(); + } + } + this.$el.html(template(args)); + this.afterRender(); + return this; + }, + + fixPageHeight: function() { + var header = this.$("div[data-role='header']:visible"), + content = this.$(this.contentSelector), + top = content.position().top, + viewHeight = $(window).height(), + contentHeight = FMS.windowHeight - header.outerHeight() - this.bottomMargin; + + this.setHeight( content, contentHeight - top ); + }, + + setHeight: function(content, height) { + content.height(height); + }, + + afterRender: function() {}, + + beforeDisplay: function() { + this.fixPageHeight(); + }, + + afterDisplay: function() {}, + + navigate: function( route, reverse ) { + if ( FMS.isAndroid ) { + var softkeyboard = window.plugins.SoftKeyBoard; + softkeyboard.hide(); + } + if ( reverse ) { + FMS.router.reverseTransition(); + } + + FMS.router.navigate( route, { trigger: true } ); + }, + + onClickButtonPrev: function(e) { + e.preventDefault(); + this.navigate( this.prev, true ); + }, + + onClickButtonNext: function(e) { + e.preventDefault(); + this.navigate( this.next ); + }, + + displayAlert: function(msg) { + navigator.notification.alert(msg, null, CONFIG.APP_NAME); + }, + + validationError: function( id, error ) { + var el_id = '#' + id; + var el = $(el_id); + var err = '<div for="' + id + '" class="form-error">' + error + '</div>'; + if ( $('div[for='+id+']').length === 0 ) { + el.before(err); + el.addClass('form-error'); + } + }, + + clearValidationErrors: function() { + $('div.form-error').remove(); + $('.form-error').removeClass('form-error'); + }, + + destroy: function() { FMS.printDebug('destory for ' + this.id); this._destroy(); this.remove(); }, + + _destroy: function() {} + }) + }); + _.extend( FMS.FMSView, Backbone.Events ); +})(FMS, Backbone, _, $); diff --git a/www/js/views/home.js b/www/js/views/home.js new file mode 100644 index 0000000..998af1c --- /dev/null +++ b/www/js/views/home.js @@ -0,0 +1,40 @@ +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + HomeView: FMS.FMSView.extend({ + template: 'home', + id: 'front-page', + + afterRender: function() { + /* + if ( !can_geolocate && ( !navigator.network || !navigator.network.connection ) ) { + geocheck_count++; + window.setTimeout( decide_front_page, 1000 ); + return; + } + + // sometime onDeviceReady does not fire so set this here to be sure + can_geolocate = true; + + geocheck_count = 0; + */ + + $('#locating').show(); + + }, + + afterDisplay: function() { + $('#load-screen').hide(); + if ( FMS.isOffline ) { + this.navigate( 'offline' ); + } else if ( FMS.currentDraft && ( + FMS.currentDraft.get('title') || FMS.currentDraft.get('lat') || + FMS.currentDraft.get('details') || FMS.currentDraft.get('file') ) + ) { + this.navigate( 'existing' ); + } else { + this.navigate( 'around' ); + } + } + }) + }); +})(FMS, Backbone, _, $); diff --git a/www/js/views/locator.js b/www/js/views/locator.js new file mode 100644 index 0000000..8e067a9 --- /dev/null +++ b/www/js/views/locator.js @@ -0,0 +1,69 @@ +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + LocatorView: FMS.FMSView.extend({ + skipLocationCheck: false, + + locate: function() { + $(document).delegate('.ui-content', 'touchmove', false); + $('#locating').show(); + this.listenTo(FMS.locator, 'gps_located', this.gotLocation); + this.listenTo(FMS.locator, 'gps_failed', this.failedLocation); + this.listenTo(FMS.locator, 'gps_locating', this.locationUpdate); + + FMS.locator.geolocate(CONFIG.ACCURACY, this.skipLocationCheck); + this.startLocateProgress(); + }, + + startLocateProgress: function() { + this.located = false; + this.locateCount = 1; + var that = this; + window.setTimeout( function() {that.showLocateProgress();}, 1000); + }, + + locationUpdate: function( accuracy ) { + if ( accuracy && accuracy < 500 ) { + $('#progress-bar').css( 'background-color', 'orange' ); + } else if ( accuracy && accuracy < 250 ) { + $('#progress-bar').css( 'background-color', 'yellow' ); + } else { + $('#progress-bar').css( 'background-color', 'grey' ); + } + + $('#accuracy').text(parseInt(accuracy, 10) + 'm'); + }, + + showLocateProgress: function() { + if ( $('#locating').css('display') == 'none' ) { + return; + } + if ( !this.located && this.locateCount > 20 ) { + var details = { msg: FMS.strings.geolocation_failed }; + this.failedLocation(details); + return; + } + var percent = ( this.locateCount / 20 ) * 100; + $('#progress-bar').css( 'width', percent + '%' ); + this.locateCount++; + var that = this; + window.setTimeout( function() {that.showLocateProgress();}, 1000); + }, + + finishedLocating: function() { + this.stopListening(FMS.locator, 'gps_locating'); + this.stopListening(FMS.locator, 'gps_located'); + this.stopListening(FMS.locator, 'gps_failed'); + $(document).undelegate('.ui-content', 'touchmove', false); + $('#locating').hide(); + }, + + failedLocation: function(details) { + this.finishedLocating(); + }, + + gotLocation: function(info) { + this.finishedLocating(); + } + }) + }); +})(FMS, Backbone, _, $); diff --git a/www/js/views/login.js b/www/js/views/login.js new file mode 100644 index 0000000..c0f16ba --- /dev/null +++ b/www/js/views/login.js @@ -0,0 +1,108 @@ +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + LoginView: FMS.FMSView.extend({ + template: 'login', + id: 'login', + next: 'around', + prev: 'around', + + events: { + 'pagehide': 'destroy', + 'pagebeforeshow': 'beforeDisplay', + 'pageshow': 'afterDisplay', + 'vclick #login': 'onClickLogin', + 'submit #signinForm': 'onClickLogin', + 'vclick #logout': 'onClickLogout', + 'vclick .ui-btn-left': 'onClickButtonPrev', + 'vclick .ui-btn-right': 'onClickButtonNext' + }, + + onClickLogin: function(e) { + // prevent form submission from onscreen keyboard + e.preventDefault(); + $('#login').focus(); + if ( this.validate() ) { + var that = this; + $.ajax( { + url: CONFIG.FMS_URL + '/auth/ajax/sign_in', + type: 'POST', + data: { + email: $('#form_email').val(), + password_sign_in: $('#form_password').val(), + remember_me: 1 + }, + dataType: 'json', + timeout: 30000, + success: function( data, status ) { + if ( data.name ) { + that.model.set('password', $('#form_password').val()); + that.model.set('email', $('#form_email').val()); + that.model.set('name', data.name); + that.model.save(); + FMS.isLoggedIn = 1; + that.$('#password_row').hide(); + that.$('#success_row').show(); + } else { + that.validationError('signinForm', FMS.strings.login_details_error); + } + }, + error: function() { + that.validationError('signinForm', FMS.strings.login_error); + } + } ); + } + }, + + onClickLogout: function(e) { + e.preventDefault(); + var that = this; + $.ajax( { + url: CONFIG.FMS_URL + '/auth/ajax/sign_out', + type: 'GET', + dataType: 'json', + timeout: 30000, + success: function( data, status ) { + FMS.isLoggedIn = 0; + that.model.set('password', ''); + that.model.save(); + that.$('#form_email').val(''); + that.$('#form_password').val(''); + that.$('#success_row').hide(); + that.$('#signed_in_row').hide(); + that.$('#password_row').show(); + }, + error: function() { + that.validationError('err', FMS.strings.logout_error); + } + } ); + }, + + validate: function() { + this.clearValidationErrors(); + var isValid = 1; + + if ( !$('#form_password').val() ) { + isValid = 0; + this.validationError('form_password', FMS.validationStrings.password ); + } + + var email = $('#form_email').val(); + if ( !email ) { + isValid = 0; + this.validationError('form_email', FMS.validationStrings.email.required); + // regexp stolen from jquery validate module + } else if ( ! /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(email) ) { + isValid = 0; + this.validationError('form_email', FMS.validationStrings.email.email); + } + + if ( !isValid ) { + // this makes sure the onscreen keyboard is dismissed + $('#login').focus(); + } + + return isValid; + } + }) + }); +})(FMS, Backbone, _, $); diff --git a/www/js/views/offline.js b/www/js/views/offline.js new file mode 100644 index 0000000..5f25223 --- /dev/null +++ b/www/js/views/offline.js @@ -0,0 +1,160 @@ +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + OfflineView: FMS.LocatorView.extend({ + template: 'offline', + id: 'offline', + prev: 'around', + next: 'reports', + skipLocationCheck: true, + + events: { + 'pagehide': 'destroy', + 'pagebeforeshow': 'beforeShow', + 'pageshow': 'afterDisplay', + 'vclick .ui-btn-left': 'onClickButtonPrev', + 'vclick .ui-btn-right': 'onClickButtonNext', + 'vclick #id_photo_button': 'takePhoto', + 'vclick #id_existing': 'addPhoto', + 'vclick #id_del_photo_button': 'deletePhoto', + 'vclick #locate': 'onClickLocate', + 'vclick #locate_cancel': 'onClickCancel', + 'blur input': 'toggleNextButton', + 'blur textarea': 'toggleNextButton' + }, + + _back: function() { + navigator.app.exitApp(); + }, + + draftHasContent: function() { + var hasContent = false; + + if ( $('#form_title').val() || $('#form_detail').val() || + this.model.get('lat') || this.model.get('file') ) { + hasContent = true; + } + + return hasContent; + }, + + afterDisplay: function() { + $('body')[0].scrollTop = 0; + $('div[data-role="content"]').show(); + }, + + beforeShow: function() { + $('div[data-role="content"]').hide(); + this.toggleNextButton(); + }, + + toggleNextButton: function() { + if ( this.draftHasContent() ) { + $('#offline-next-btn .ui-btn-text').text('Save'); + } else { + $('#offline-next-btn .ui-btn-text').text('Skip'); + } + }, + + failedLocation: function(details) { + this.finishedLocating(); + this.locateCount = 21; + + $('#locate_result').html(FMS.strings.offline_failed_position); + }, + + gotLocation: function(info) { + this.finishedLocating(); + + this.model.set('lat', info.coordinates.latitude); + this.model.set('lon', info.coordinates.longitude); + + $('#locate_result').html(FMS.strings.offline_got_position); + }, + + takePhoto: function() { + var that = this; + navigator.camera.getPicture( function(imgURI) { that.addPhotoSuccess(imgURI); }, function(error) { that.addPhotoFail(error); }, { saveToPhotoAlbum: true, quality: 49, destinationType: Camera.DestinationType.FILE_URI, sourceType: navigator.camera.PictureSourceType.CAMERA, correctOrientation: true }); + }, + + addPhoto: function() { + var that = this; + navigator.camera.getPicture( function(imgURI) { that.addPhotoSuccess(imgURI); }, function(error) { that.addPhotoFail(error); }, { saveToPhotoAlbum: false, quality: 49, destinationType: Camera.DestinationType.FILE_URI, sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY, correctOrientation: true }); + }, + + addPhotoSuccess: function(imgURI) { + var move = FMS.files.moveURI( imgURI ); + + var that = this; + move.done( function( file ) { + $('#photo').attr('src', file.toURL()); + that.model.set('file', file.toURL()); + FMS.saveCurrentDraft(); + + $('#photo-next-btn .ui-btn-text').text('Next'); + $('#display_photo').show(); + $('#add_photo').hide(); + }); + + move.fail( function() { that.addPhotoFail(); } ); + }, + + addPhotoFail: function() { + if ( message != 'no image selected' && + message != 'Selection cancelled.' && + message != 'Camera cancelled.' ) { + this.displayAlert(FMS.strings.photo_failed); + } + }, + + deletePhoto: function() { + var that = this; + var del = FMS.files.deleteURI( this.model.get('file') ); + + del.done( function() { + that.model.set('file', ''); + FMS.saveCurrentDraft(); + $('#photo').attr('src', ''); + + $('#photo-next-btn .ui-btn-text').text('Skip'); + $('#display_photo').hide(); + $('#add_photo').show(); + }); + }, + + onClickLocate: function(e) { + e.preventDefault(); + this.locate(); + }, + + onClickCancel: function(e) { + e.preventDefault(); + this.finishedLocating(); + }, + + onClickButtonNext: function() { + this.updateCurrentReport(); + if ( !this.draftHasContent() && this.model.id ) { + var del = FMS.removeDraft( this.model.id ); + + var that = this; + del.done( function() { that.draftDeleted(); } ); + del.fail( function() { that.draftDeleted(); } ); + } else { + FMS.clearCurrentDraft(); + this.navigate( this.next ); + } + }, + + draftDeleted: function() { + FMS.clearCurrentDraft(); + this.navigate( this.next ); + }, + + updateCurrentReport: function() { + this.model.set('title', $('#form_title').val()); + this.model.set('details', $('#form_detail').val()); + FMS.saveCurrentDraft(); + } + }) + }); +})(FMS, Backbone, _, $); diff --git a/www/js/views/photo.js b/www/js/views/photo.js new file mode 100644 index 0000000..f24207e --- /dev/null +++ b/www/js/views/photo.js @@ -0,0 +1,137 @@ +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + PhotoView: FMS.FMSView.extend({ + template: 'photo', + id: 'photo-page', + prev: 'around', + next: 'details', + + events: { + 'pagehide': 'destroy', + 'pagebeforeshow': 'beforeDisplay', + 'pageshow': 'afterDisplay', + 'vclick .ui-btn-left': 'onClickButtonPrev', + 'vclick .ui-btn-right': 'onClickButtonNext', + 'vclick #id_photo_button': 'takePhoto', + 'vclick #id_existing': 'addPhoto', + 'vclick #id_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 ); + } + }, + + getOptions: function(isFromAlbum) { + var options = { + destinationType: Camera.DestinationType.FILE_URI, + sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY, + correctOrientation: true, + targetHeight: 768, + targetWidth: 1024 + }; + + if ( ! isFromAlbum ) { + options.saveToPhotoAlbum = true; + options.sourceType = navigator.camera.PictureSourceType.CAMERA; + } + + // this helps with out of memory errors on iPhones but not on Android it seems + if ( ! FMS.isAndroid ) { + options.quality = 49; + } + + return options; + }, + + takePhoto: function(e) { + e.preventDefault(); + $.mobile.loading('show'); + $('#photo').hide(); + var that = this; + + var options = this.getOptions(); + + navigator.camera.getPicture( function(imgURI) { that.addPhotoSuccess(imgURI); }, function(error) { that.addPhotoFail(error); }, options); + }, + + addPhoto: function(e) { + e.preventDefault(); + $.mobile.loading('show'); + $('#photo').hide(); + var that = this; + var options = this.getOptions(true); + navigator.camera.getPicture( function(imgURI) { that.addPhotoSuccess(imgURI); }, function(error) { that.addPhotoFail(error); }, options); + }, + + addPhotoSuccess: function(imgURI) { + var move; + // on iOS the photos go into a temp folder in the apps own filespace so we + // can move them, and indeed have to as the tmp space is cleaned out by the OS + // so draft reports might have their images removed. on android you access the + // images where they are stored on the filesystem so if you move, and then delete + // them, you are moving and deleting the only copy of them which is likely to be + // surprising and unwelcome so we copy them instead. + var fileName = CONFIG.NAMESPACE + '_' + this.model.cid + '_' + moment().unix() + '.jpg'; + if ( FMS.isAndroid ) { + move = FMS.files.copyURI( imgURI, fileName ); + } else { + move = FMS.files.moveURI( imgURI, fileName ); + } + + 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()); + FMS.saveCurrentDraft(); + + $('#photo-next-btn .ui-btn-text').text('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); + }); + + move.fail( function() { that.addPhotoFail(); } ); + }, + + addPhotoFail: function() { + $('#photo').show(); + $.mobile.loading('hide'); + if ( message != 'no image selected' && + message != 'Selection cancelled.' && + message != 'Camera cancelled.' ) { + this.displayAlert(FMS.strings.photo_failed); + } + }, + + deletePhoto: function(e) { + e.preventDefault(); + var that = this; + var del = FMS.files.deleteURI( this.model.get('file') ); + + del.done( function() { + $('#photo_title').hide(); + $('#nophoto_title').show(); + $('#id_del_photo_button').hide(); + that.model.set('file', ''); + 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(); + }); + + } + }) + }); +})(FMS, Backbone, _, $); diff --git a/www/js/views/reports.js b/www/js/views/reports.js new file mode 100644 index 0000000..ff65700 --- /dev/null +++ b/www/js/views/reports.js @@ -0,0 +1,104 @@ +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + ReportsView: FMS.FMSView.extend({ + template: 'reports', + id: 'reports', + next: 'around', + prev: 'around', + contentSelector: '#drafts', + + events: { + 'pagehide': 'destroy', + 'pagebeforeshow': 'beforeDisplay', + 'pageshow': 'afterDisplay', + 'vclick .del_report': 'deleteReport', + 'vclick .use_report': 'useReport', + 'vclick .ui-btn-left': 'onClickButtonPrev', + 'vclick .ui-btn-right': 'onClickButtonNext' + }, + + onClickButtonPrev: function(e) { + $('#drafts').hide(); + $('body')[0].scrollTop = 0; + e.preventDefault(); + this.navigate( this.prev, true ); + }, + + onClickButtonNext: function(e) { + $('#drafts').hide(); + $('body')[0].scrollTop = 0; + e.preventDefault(); + this.navigate( this.next ); + }, + + deleteReport: function(e) { + e.preventDefault(); + var el = $(e.target); + var id = el.parents('li').attr('id'); + var del = FMS.removeDraft( id, true ); + var that = this; + del.done( function() { that.onRemoveDraft(el); } ); + del.fail( function() { that.onRemoveDraft(el); } ); + }, + + setHeight: function(content, height) { + content.css( 'min-height', content + 'px'); + }, + + beforeDisplay: function() { + if ( FMS.allDrafts.length === 0 ) { + $('#noreports').show(); + } else { + $('#report-list').show(); + } + }, + + useReport: function(e) { + e.preventDefault(); + var el = $(e.target); + var id = el.parents('li').attr('id'); + FMS.currentDraft = FMS.allDrafts.get(id); + $('#drafts').hide(); + if ( FMS.currentDraft && FMS.currentDraft.get('lat') ) { + var coords = { latitude: FMS.currentDraft.get('lat'), longitude: FMS.currentDraft.get('lon') }; + fixmystreet.latitude = coords.latitude; + fixmystreet.longitude = coords.longitude; + + if ( fixmystreet.map ) { + var centre = new OpenLayers.LonLat( coords.longitude, coords.latitude ); + centre.transform( + new OpenLayers.Projection("EPSG:4326"), + fixmystreet.map.getProjectionObject() + ); + + fixmystreet.map.panTo(centre); + } + } + this.navigate('around'); + }, + + onRemoveDraft: function(el) { + el.parents('li').remove(); + if ( FMS.allDrafts.length === 0 ) { + $('#report-list').hide(); + $('#noreports').show(); + } + }, + + render: function(){ + if ( !this.template ) { + FMS.printDebug('no template to render'); + return; + } + template = _.template( tpl.get( this.template ) ); + if ( this.model ) { + this.$el.html(template({ model: this.model.toJSON(), drafts: FMS.allDrafts })); + } else { + this.$el.html(template()); + } + this.afterRender(); + return this; + } + }) + }); +})(FMS, Backbone, _, $); diff --git a/www/js/views/save_offline.js b/www/js/views/save_offline.js new file mode 100644 index 0000000..d00170c --- /dev/null +++ b/www/js/views/save_offline.js @@ -0,0 +1,32 @@ +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + SaveOfflineView: FMS.FMSView.extend({ + template: 'save_offline', + id: 'save_offline', + + events: { + 'pagehide': 'destroy', + 'pagebeforeshow': 'beforeDisplay', + 'pageshow': 'afterDisplay', + 'vclick #save_report': 'saveReport', + 'vclick #discard': 'discardReport' + }, + + saveReport: function() { + FMS.clearCurrentDraft(); + this.navigate('reports'); + }, + + discardReport: function() { + var reset = FMS.removeDraft(FMS.currentDraft.id, true); + var that = this; + reset.done( function() { that.onDraftRemove(); } ); + reset.fail( function() { that.onDraftRemove(); } ); + }, + + onDraftRemove: function() { + this.navigate( 'around', 'left' ); + } + }) + }); +})(FMS, Backbone, _, $); diff --git a/www/js/views/search.js b/www/js/views/search.js new file mode 100644 index 0000000..6930e2a --- /dev/null +++ b/www/js/views/search.js @@ -0,0 +1,81 @@ +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + SearchView: FMS.FMSView.extend({ + template: 'address_search', + id: 'search-page', + + events: { + 'vclick a.address': 'goAddress', + 'vclick #submit': 'search', + 'vclick #locate': 'goLocate', + 'pagehide': 'destroy', + 'pagebeforeshow': 'beforeDisplay', + 'pageshow': 'afterDisplay', + 'submit #postcodeForm': 'search' + }, + + afterDisplay: function() { + if ( FMS.isOffline ) { + this.navigate('offline'); + } + }, + + search: function(e) { + // this is to stop form submission + e.preventDefault(); + this.clearValidationErrors(); + var pc = this.$('#pc').val(); + this.listenTo(FMS.locator, 'search_located', this.searchSuccess ); + this.listenTo(FMS.locator, 'search_failed', this.searchFail); + + FMS.locator.lookup(pc); + }, + + searchSuccess: function( info ) { + this.stopListening(FMS.locator); + var coords = info.coordinates; + FMS.currentPosition = coords; + this.navigate('around'); + }, + + goAddress: function(e) { + var t = $(e.target); + var lat = t.attr('data-lat'); + var long = t.attr('data-long'); + + FMS.currentPosition = { latitude: lat, longitude: long }; + this.navigate('around'); + }, + + searchFail: function( details ) { + // this makes sure any onscreen keyboard is dismissed + $('#submit').focus(); + this.stopListening(FMS.locator); + if ( details.msg ) { + this.validationError( 'pc', details.msg ); + } else if ( details.locations ) { + var multiple = ''; + for ( var i = 0; i < details.locations.length; i++ ) { + var loc = details.locations[i]; + var li = '<li><a class="address" id="location_' + i + '" data-lat="' + loc.lat + '" data-long="' + loc.long + '">' + loc.address + '</a></li>'; + multiple = multiple + li; + } + $('#front-howto').html('<p>Multiple matches found</p><ul data-role="listview" data-inset="true">' + multiple + '</ul>'); + $('.ui-page').trigger('create'); + } else { + this.validationError( 'pc', FMS.strings.location_problem ); + } + }, + + goLocate: function(e) { + e.preventDefault(); + this.navigate( 'around' ); + }, + + _destroy: function() { + delete FMS.searchMessage; + this.stopListening(FMS.locator); + } + }) + }); +})(FMS, Backbone, _, $); diff --git a/www/js/views/sent.js b/www/js/views/sent.js new file mode 100644 index 0000000..f25a178 --- /dev/null +++ b/www/js/views/sent.js @@ -0,0 +1,44 @@ +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + SentView: FMS.FMSView.extend({ + template: 'sent', + id: 'sent-page', + prev: 'around', + + events: { + 'pagehide': 'destroy', + 'pagebeforeshow': 'beforeDisplay', + 'pageshow': 'afterDisplay', + 'vclick .ui-btn-left': 'onClickButtonPrev', + 'vclick #id_report_another': 'onClickButtonPrev', + 'vclick #open_report': 'onClickOpenReport', + 'vclick #rate_app': 'onClickRateApp' + }, + + render: function(){ + if ( !this.template ) { + FMS.printDebug('no template to render'); + return; + } + template = _.template( tpl.get( this.template ) ); + this.$el.html(template(FMS.createdReport.toJSON())); + this.afterRender(); + return this; + }, + + onClickOpenReport: function(e) { + e.preventDefault(); + window.open(FMS.createdReport.get('site_url'), '_system'); + return false; + }, + + onClickRateApp: function(e) { + e.preventDefault(); + var el = $('#rate_app'); + var href = el.attr('href'); + window.open(href, '_system'); + return false; + } + }) + }); +})(FMS, Backbone, _, $); diff --git a/www/js/views/submit.js b/www/js/views/submit.js new file mode 100644 index 0000000..6a7c946 --- /dev/null +++ b/www/js/views/submit.js @@ -0,0 +1,480 @@ +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + SubmitView: FMS.FMSView.extend({ + template: 'submit', + id: 'submit-page', + prev: 'details', + nopassword: 0, + + events: { + 'pagehide': 'destroy', + 'pagebeforeshow': 'beforeDisplay', + 'pageshow': 'afterDisplay', + 'vclick .ui-btn-left': 'onClickButtonPrev', + 'vclick .ui-btn-right': 'onClickButtonNext', + 'vclick #submit_signed_in': 'onClickSubmit', + 'vclick #submit_sign_in': 'onClickSubmit', + 'vclick #submit_register': 'onClickSubmit' + }, + + render: function(){ + if ( !this.template ) { + FMS.printDebug('no template to render'); + return; + } + template = _.template( tpl.get( this.template ) ); + if ( this.model ) { + this.$el.html(template({ model: this.model.toJSON(), user: FMS.currentUser.toJSON(), nopassword: this.nopassword })); + } else { + this.$el.html(template()); + } + this.afterRender(); + return this; + }, + + onClickSubmit: function(e) { + // in case we are getting here from a form submission + e.preventDefault(); + this.doSubmit(); + }, + + doSubmit: function() { + this.beforeSubmit(); + + if ( this.validate() ) { + this.model.set('user', FMS.currentUser); + if ( FMS.isOffline ) { + this.navigate( 'save_offline' ); + } else { + this.report = new FMS.Report( this.model.toJSON() ); + this.listenTo( this.report, 'sync', this.onReportSync ); + this.listenTo( this.report, 'invalid', this.onReportInvalid ); + this.listenTo( this.report, 'error', this.onReportError ); + this.report.save(); + } + } + }, + + onReportSync: function(model, resp, options) { + if ( model.success !== 1 ) { + return; + } + this.stopListening(); + this.afterSubmit(); + if ( FMS.currentUser ) { + FMS.currentUser.save(); + } + if (resp.report) { + this.report.set('site_id', resp.report); + this.report.set('site_url', CONFIG.FMS_URL + '/report/' + resp.report); + } else { + this.report.set('email_confirm', 1); + } + var reset = FMS.removeDraft( model.id, true); + var that = this; + reset.done( function() { that.onRemoveDraft(); } ); + reset.fail( function() { that.onRemoveDraft(); } ); + }, + + onRemoveDraft: function() { + FMS.clearCurrentDraft(); + FMS.createdReport = this.report; + this.navigate( 'sent' ); + }, + + onReportInvalid: function(model, err, options) { + if ( !this._handleInvalid( model, err, options ) ) { + var errors = err.errors; + var errorList = '<ul><li class="plain">' + FMS.strings.invalid_report + '</li>'; + var validErrors = [ 'password', 'category', 'name' ]; + for ( var k in errors ) { + if ( validErrors.indexOf(k) >= 0 || errors[k].match(/required/) ) { + if ( k === 'password' ) { + error = FMS.strings.password_problem; + } else if ( k !== '') { + error = errors[k]; + } + errorList += '<li>' + error + '</li>'; + } + } + errorList += '</ul>'; + $('p.top').hide(); + $('#errors').html(errorList).show(); + } + }, + + onReportError: function(model, err, options) { + var msg = FMS.strings.unknown_sync_error; + if ( err.errors ) { + msg = msg + ': ' + err.errors; + } else if ( options.aborted ) { + msg = FMS.strings.upload_aborted; + } + var that = this; + navigator.notification.confirm( + msg, + function(index) { that.handleReportError(index); }, + CONFIG.APP_NAME, + [FMS.strings.save_for_later,FMS.strings.try_again]); + }, + + handleReportError: function(index) { + if ( index === 1 ) { + this.stopListening(); + FMS.clearCurrentDraft(); + this.navigate('reports'); + } else if ( index === 2 ) { + this.doSubmit(); + } + }, + + beforeSubmit: function() {}, + afterSubmit: function() {}, + + _handleInvalid: function() { + return false; + }, + + _destroy: function() { + this.stopListening(); + } + }) + }); +})(FMS, Backbone, _, $); + +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + SubmitInitialPageView: FMS.SubmitView.extend({ + onClickButtonPrev: function() { + if ( this.model.get('hasExtras') == 1 ) { + this.navigate( 'details_extra', true ); + } else { + this.navigate( 'details', true ); + } + } + }) + }); +})(FMS, Backbone, _, $); + +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + SubmitEmailView: FMS.SubmitInitialPageView.extend({ + template: 'submit_email', + id: 'submit-email-page', + prev: 'details', + + events: { + 'pagehide': 'destroy', + 'pagebeforeshow': 'beforeDisplay', + 'pageshow': 'afterDisplay', + 'vclick .ui-btn-left': 'onClickButtonPrev', + 'vclick #set_password': 'onClickSetPassword', + 'vclick #have_password': 'onClickPassword', + 'vclick #email_confirm': 'onClickConfirm' + }, + + afterRender: function() { + // if we are coming back to this page we want to reset how + // they are reporting + this.model.set('submit_clicked', ''); + }, + + validate: function() { + this.clearValidationErrors(); + var isValid = 1; + + var email = $('#form_email').val(); + if ( !email ) { + isValid = 0; + this.validationError('form_email', FMS.validationStrings.email.required); + // regexp stolen from jquery validate module + } else if ( ! /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(email) ) { + isValid = 0; + this.validationError('form_email', FMS.validationStrings.email.email); + } + + return isValid; + }, + + onClickSetPassword: function(e) { + e.preventDefault(); + if ( this.validate() ) { + this.model.set('submit_clicked', 'submit_register'); + FMS.currentUser.set('email', $('#form_email').val()); + this.navigate( 'submit-set-password' ); + } + }, + + onClickPassword: function(e) { + e.preventDefault(); + if ( this.validate() ) { + FMS.currentUser.set('email', $('#form_email').val()); + this.navigate( 'submit-password' ); + } + }, + + onClickConfirm: function(e) { + e.preventDefault(); + if ( this.validate() ) { + FMS.currentUser.set('email', $('#form_email').val()); + FMS.currentUser.save(); + this.navigate( 'submit-name' ); + } + }, + + _destroy: function() {} + }) + }); +})(FMS, Backbone, _, $); + +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + SubmitNameView: FMS.SubmitView.extend({ + template: 'submit_name', + id: 'submit-name-page', + prev: 'submit-email', + nopassword: 1, + + events: { + 'pagehide': 'destroy', + 'pagebeforeshow': 'beforeDisplay', + 'pageshow': 'afterDisplay', + 'vclick .ui-btn-left': 'onClickButtonPrev', + 'vclick #send_confirm': 'onClickSubmit' + }, + + initialize: function() { + this.listenTo(this.model, 'sync', this.onReportSync ); + this.listenTo( this.model, 'error', this.onReportError ); + }, + + validate: function() { + this.clearValidationErrors(); + var isValid = 1; + + var name = $('#form_name').val(); + if ( !name ) { + isValid = 0; + this.validationError('form_name', FMS.validationStrings.name.required ); + } else { + var validNamePat = /\ba\s*n+on+((y|o)mo?u?s)?(ly)?\b/i; + if ( name.length < 6 || !name.match( /\s/ ) || !name.match( /\S/ ) || name.match( validNamePat ) ) { + isValid = 0; + this.validationError('form_name', FMS.validationStrings.name.validName); + } + } + + if ( this.model.get('titles_list') && this.model.get('titles_list').length > 0 ) { + if ( $('#form_title').val() === '' ) { + this.validationError('form_title', FMS.strings.required); + isValid = 0; + } + } + + return isValid; + }, + + beforeSubmit: function() { + $('#errors').hide(); + $('p.top').show(); + this.model.set('name', $('#form_name').val()); + this.model.set('phone', $('#form_phone').val()); + this.model.set('may_show_name', $('#form_may_show_name').is(':checked')); + FMS.currentUser.set('name', $('#form_name').val()); + FMS.currentUser.set('may_show_name', $('#form_may_show_name').is(':checked')); + + if ( this.model.get('titles_list') && this.model.get('titles_list').length > 0 ) { + FMS.currentUser.set('title', $('#form_title').val()); + } + + if ( FMS.currentUser ) { + FMS.currentUser.save(); + } + } + }) + }); +})(FMS, Backbone, _, $); + +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + SubmitNameSetPasswordView: FMS.SubmitNameView.extend({ + template: 'submit_name', + id: 'submit-name-page-set-password', + prev: 'submit-set-password', + nopassword: 0 + }) + }); +})(FMS, Backbone, _, $); + +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + SubmitPasswordView: FMS.SubmitView.extend({ + template: 'submit_password', + id: 'submit-password-page', + prev: 'submit-email', + + events: { + 'pagehide': 'destroy', + 'pagebeforeshow': 'beforeDisplay', + 'pageshow': 'afterDisplay', + 'vclick .ui-btn-left': 'onClickButtonPrev', + 'vclick #report': 'onClickSubmit', + 'vclick #confirm_name': 'onClickSubmit', + 'submit #passwordForm': 'onClickSubmit' + }, + + initialize: function() { + this.listenTo(this.model, 'sync', this.onReportSync ); + this.listenTo( this.model, 'error', this.onReportError ); + }, + + validate: function() { + var isValid = 1; + + if ( !$('#form_password').val() ) { + isValid = 0; + this.validationError('form_password', FMS.validationStrings.password ); + } + + if ( $('#form_name').val() && this.model.get('titles_list') && this.model.get('titles_list').length > 0 ) { + if ( $('#form_title').val() === '' ) { + this.validationError('form_title', FMS.strings.required); + isValid = 0; + } + } + + return isValid; + }, + + beforeSubmit: function() { + $('#errors').hide(); + $('p.top').show(); + $('#report').focus(); + if ( $('#form_name').val() ) { + this.model.set('submit_clicked', 'submit_register'); + this.model.set('phone', $('#form_phone').val()); + this.model.set('name', $('#form_name').val()); + this.model.set('may_show_name', $('#form_may_show_name').is(':checked')); + FMS.currentUser.set('name', $('#form_name').val()); + FMS.currentUser.set('may_show_name', $('#form_may_show_name').is(':checked')); + if ( this.model.get('titles_list') && this.model.get('titles_list').length > 0 ) { + FMS.currentUser.set('title', $('#form_title').val()); + } + FMS.currentUser.save(); + } else { + // if this is set then we are registering a password + if ( ! this.model.get('submit_clicked') ) { + this.model.set('submit_clicked', 'submit_sign_in'); + } + FMS.currentUser.set('password', $('#form_password').val()); + } + }, + + afterSubmit: function() { + FMS.isLoggedIn = 1; + }, + + _handleInvalid: function(model, err, options) { + if ( err.check_name ) { + $('#form_name').val(err.check_name); + $('#password_row').hide(); + $('#check_name').show(); + $('#confirm_name').focus(); + return true; + } + return false; + } + + }) + }); +})(FMS, Backbone, _, $); + +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + SubmitSetPasswordView: FMS.SubmitPasswordView.extend({ + template: 'submit_set_password', + id: 'submit-set-password-page', + prev: 'submit-email', + next: 'submit-name-set-password', + + events: { + 'pagehide': 'destroy', + 'pagebeforeshow': 'beforeDisplay', + 'pageshow': 'afterDisplay', + 'vclick .ui-btn-left': 'onClickButtonPrev', + 'vclick #continue': 'onClickContinue', + 'submit #passwordForm': 'onClickContinue' + }, + + onClickContinue: function(e) { + e.preventDefault(); + if ( this.validate() ) { + $('#continue').focus(); + if ( ! this.model.get('submit_clicked') ) { + this.model.set('submit_clicked', 'submit_sign_in'); + } + FMS.currentUser.set('password', $('#form_password').val()); + this.navigate( this.next ); + } + } + }) + }); +})(FMS, Backbone, _, $); + +(function (FMS, Backbone, _, $) { + _.extend( FMS, { + SubmitConfirmView: FMS.SubmitInitialPageView.extend({ + template: 'submit_confirm', + id: 'submit-confirm-page', + prev: 'details', + + events: { + 'pagehide': 'destroy', + 'pagebeforeshow': 'beforeDisplay', + 'pageshow': 'afterDisplay', + 'vclick .ui-btn-left': 'onClickButtonPrev', + 'vclick #report': 'onClickSubmit' + }, + + validate: function() { + this.clearValidationErrors(); + var isValid = 1; + + var name = $('#form_name').val(); + if ( !name ) { + isValid = 0; + this.validationError('form_name', FMS.validationStrings.name.required ); + } else { + var validNamePat = /\ba\s*n+on+((y|o)mo?u?s)?(ly)?\b/i; + if ( name.length < 6 || !name.match( /\S/ ) || name.match( validNamePat ) ) { + isValid = 0; + this.validationError('form_name', FMS.validationStrings.name.validName); + } + } + + return isValid; + }, + + beforeSubmit: function() { + this.model.set('name', $('#form_name').val()); + this.model.set('phone', $('#form_phone').val()); + this.model.set('may_show_name', $('#form_may_show_name').is(':checked')); + this.model.set('submit_clicked', 'submit_register'); + FMS.currentUser.set('name', $('#form_name').val()); + FMS.currentUser.set('may_show_name', $('#form_may_show_name').is(':checked')); + }, + + onReportError: function(model, err, options) { + // TODO: this is a temporary measure which should be replaced by a more + // sensible login mechanism + if ( err.check_name ) { + this.onClickSubmit(); + } else { + if ( err.errors && err.errors.password ) { + this.validationError('form_password', err.errors.password ); + } + } + } + }) + }); +})(FMS, Backbone, _, $); |