diff options
Diffstat (limited to 'web')
-rw-r--r-- | web/cobrands/fixmystreet/fixmystreet.js | 28 | ||||
-rw-r--r-- | web/cobrands/fixmystreet/staff.js | 80 | ||||
-rw-r--r-- | web/cobrands/sass/_base.scss | 158 | ||||
-rw-r--r-- | web/cobrands/sass/_layout.scss | 13 | ||||
-rw-r--r-- | web/cobrands/sass/_report_list_pins.scss | 2 | ||||
-rw-r--r-- | web/js/duplicates.js | 206 | ||||
-rw-r--r-- | web/js/map-OpenLayers.js | 8 |
7 files changed, 391 insertions, 104 deletions
diff --git a/web/cobrands/fixmystreet/fixmystreet.js b/web/cobrands/fixmystreet/fixmystreet.js index 5efc0d878..d6d03eac3 100644 --- a/web/cobrands/fixmystreet/fixmystreet.js +++ b/web/cobrands/fixmystreet/fixmystreet.js @@ -974,7 +974,7 @@ $.extend(fixmystreet.set_up, { e.preventDefault(); var form = $('<form/>').attr({ method:'post', action:"/alert/subscribe" }); form.append($('<input name="alert" value="Subscribe me to an email alert" type="hidden" />')); - $('#alerts input[type=text], #alerts input[type=hidden], #alerts input[type=radio]:checked').each(function() { + $(this).closest('.js-alert-list').find('input[type=text], input[type=hidden], input[type=radio]:checked').each(function() { var $v = $(this); $('<input/>').attr({ name:$v.attr('name'), value:$v.val(), type:'hidden' }).appendTo(form); }); @@ -1049,6 +1049,32 @@ $.extend(fixmystreet.set_up, { var set_map_state = true; fixmystreet.back_to_reports_list(e, report_list_url, map_state, set_map_state); }); + }, + + expandable_list_items: function(){ + $(document).on('click', '.js-toggle-expansion', function(e) { + e.preventDefault(); // eg: prevent button from submitting parent form + var $toggle = $(this); + var $parent = $toggle.closest('.js-expandable'); + $parent.toggleClass('expanded'); + $toggle.text($parent.hasClass('expanded') ? $toggle.data('less') : $toggle.data('more')); + }); + + $(document).on('click', '.js-expandable', function(e) { + var $parent = $(this); + // Ignore parents that are already expanded. + if ( ! $parent.hasClass('expanded') ) { + // Ignore clicks on action buttons (clicks on the + // .js-toggle-expansion button will be handled by + // the more specific handler above). + if ( ! $(e.target).is('.item-list__item--expandable__actions *') ) { + e.preventDefault(); + $parent.addClass('expanded'); + var $toggle = $parent.find('.js-toggle-expansion'); + $toggle.text($toggle.data('less')); + } + } + }); } }); diff --git a/web/cobrands/fixmystreet/staff.js b/web/cobrands/fixmystreet/staff.js index fadf18356..a7e0c8896 100644 --- a/web/cobrands/fixmystreet/staff.js +++ b/web/cobrands/fixmystreet/staff.js @@ -1,84 +1,4 @@ fixmystreet.staff_set_up = { - manage_duplicates: function() { - // Deal with changes to report state by inspector/other staff, specifically - // displaying nearby reports if it's changed to 'duplicate'. - function refresh_duplicate_list() { - var report_id = $("#report_inspect_form .js-report-id").text(); - var args = { - filter_category: $("#report_inspect_form select#category").val(), - latitude: $('input[name="latitude"]').val(), - longitude: $('input[name="longitude"]').val() - }; - $("#js-duplicate-reports ul").html('<li class="item-list__item">Loading...</li>'); - var nearby_url = '/report/'+report_id+'/nearby.json'; - $.getJSON(nearby_url, args, function(data) { - var duplicate_of = $("#report_inspect_form [name=duplicate_of]").val(); - var $reports = $(data.reports_list) - .not("[data-report-id="+report_id+"]") - .slice(0, 5); - $reports.filter("[data-report-id="+duplicate_of+"]").addClass("item-list--reports__item--selected"); - - (function() { - var timeout; - $reports.on('mouseenter', function(){ - clearTimeout(timeout); - fixmystreet.maps.markers_highlight(parseInt($(this).data('reportId'), 10)); - }).on('mouseleave', function(){ - timeout = setTimeout(fixmystreet.maps.markers_highlight, 50); - }); - })(); - - $("#js-duplicate-reports ul").empty().prepend($reports); - - $reports.find("a").click(function() { - var report_id = $(this).closest("li").data('reportId'); - $("#report_inspect_form [name=duplicate_of]").val(report_id); - $("#js-duplicate-reports ul li").removeClass("item-list--reports__item--selected"); - $(this).closest("li").addClass("item-list--reports__item--selected"); - return false; - }); - - show_nearby_pins(data, report_id); - }); - } - - function show_nearby_pins(data, report_id) { - var markers = fixmystreet.maps.markers_list( data.pins, true ); - // We're replacing all the features in the markers layer with the - // possible duplicates, but the list of pins from the server doesn't - // include the current report. So we need to extract the feature for - // the current report and include it in the list of features we're - // showing on the layer. - var report_marker = fixmystreet.maps.get_marker_by_id(parseInt(report_id, 10)); - if (report_marker) { - markers.unshift(report_marker); - } - fixmystreet.markers.removeAllFeatures(); - fixmystreet.markers.addFeatures( markers ); - } - - function state_change() { - // The duplicate report list only makes sense when state is 'duplicate' - if ($(this).val() !== "duplicate") { - $("#js-duplicate-reports").addClass("hidden"); - return; - } else { - $("#js-duplicate-reports").removeClass("hidden"); - } - // If this report is already marked as a duplicate of another, then - // there's no need to refresh the list of duplicate reports - var duplicate_of = $("#report_inspect_form [name=duplicate_of]").val(); - if (!!duplicate_of) { - return; - } - - refresh_duplicate_list(); - } - - $("#report_inspect_form").on("change.state", "select#state", state_change); - $("#js-change-duplicate-report").click(refresh_duplicate_list); - }, - action_scheduled_raise_defect: function() { $("#report_inspect_form").find('[name=state]').on('change', function() { if ($(this).val() !== "action scheduled") { diff --git a/web/cobrands/sass/_base.scss b/web/cobrands/sass/_base.scss index 276db90ae..c3cf3cb1b 100644 --- a/web/cobrands/sass/_base.scss +++ b/web/cobrands/sass/_base.scss @@ -530,6 +530,53 @@ ul.error { @include border-radius(0.25em); } +.duplicate-report-suggestions { + position: relative; + + .item-list__item--expandable { + border-top: 1px solid #ddd; + } + + .item-list { + margin: 0 -1em 1em -1em; + border-bottom: 1px solid #ddd; + } +} + +.duplicate-report-suggestions__close { + position: absolute; + right: 0; + top: 0; + display: block; + width: 32px; + height: 0; + padding-top: 32px; + overflow: hidden; + border: none; + background: transparent; + opacity: 0.5; + line-height: 2em; // Make sure line box is taller than text, so text is pushed below hidden overflow. + + &:hover, + &:focus { + opacity: 0.7; + } + + // Doing some gymnastics so we can reuse the existing .btn--close icon. + &:before { + content: ""; + display: block; + width: 16px; + height: 16px; + position: absolute; + top: 8px; + left: 8px; + background-repeat: no-repeat; + background-size: 112px 16px; + @include svg-background-image('/cobrands/fixmystreet/images/button-icons'); + background-position: -32px 0; + } +} /*** LAYOUT ***/ @@ -1194,12 +1241,10 @@ input.final-submit { // that appears in Inspector form, when closing a report as a duplicate. .item-list--inspect-duplicates { border-bottom: none; + background-color: rgba(255, 255, 255, 0.5); - .item-list__item { - background-color: rgba(255, 255, 255, 0.5); - } - - .item-list--reports__item--selected { + .item-list__item--selected, + .js & .item-list__item--selected.expanded:hover { border: 0.25em solid $primary; background-color: #fff; } @@ -1256,6 +1301,92 @@ input.final-submit { } } +.item-list__item--expandable { + @include clearfix(); + margin: 0; + padding: 1em; + + .js & { + cursor: pointer; + + &:hover, + &.hovered { + background-color: $itemlist_item_background_hover; + } + + &.item-list__item--selected, + &.item-list__item--selected:hover, + &.item-list__item--selected.hovered { + cursor: default; + background-color: #fff; + } + } + + h3 { + margin: 0; + } + + .img { + float: $right; + width: 90px; + height: auto; + margin: flip(0 0 0.5em 1em, 0 1em 0.5em 0); + } + + small { + color: #666; + display: block; + margin-top: 0.5em; + } + + p { + line-height: 1.4em; + margin-top: 0.5em; + } + + &.expanded { + .js & { + cursor: default; + + &:hover { + background-color: $itemlist_item_background; + } + } + } +} + +.item-list__item--expandable__actions { + @include flex-container(); + + & > * { + @include flex(1); // Force equal width children + } +} + +.item-list__item--expandable__hide-until-expanded { + display: none; + + .expanded & { + display: block; + } +} + +.item-list__item--expandable__hide-when-expanded { + .expanded & { + display: none; + } +} + +.item-list__item .get-updates { + margin: 0 -1em -1em -1em; + padding: 1em; + background-color: $itemlist_item_background_hover; + + p, label { + margin: 0 0 0.5em 0; + } +} + .problem-header .update-img, .item-list .update-img { float: $right; @@ -1768,6 +1899,23 @@ img.pin { height: 10em; // eg: at the top of individual report pages } +#map_sidebar { + // Chrome/Safari count padding-bottom as part of the scrollable content in + // an overflow:scroll element (technically in contravention of the spec), + // whereas Firefox/IE render the padding outside the scrollable area. + // In desktop mode, we need padding at the bottom of the sidebar, to stop + // .shadow-wrap from obscuring content at the bottom of the sidebar. So we + // use an :after pseudo-element instead of padding. + // In mobile mode, the extra space here still helps differentiate the page + // content on report/reporting pages, from the nav immediately below. + // https://bugzilla.mozilla.org/show_bug.cgi?id=748518 + &:after { + content: ""; + display: block; + height: 4em; + } +} + // When you're in the reporting flow on mobile, we hide the site-header // and make the map full screen to reduce distractions. JavaScript also // tweaks the text content of some of the map-related elements, to make diff --git a/web/cobrands/sass/_layout.scss b/web/cobrands/sass/_layout.scss index 8735da4f5..7d2b99c9b 100644 --- a/web/cobrands/sass/_layout.scss +++ b/web/cobrands/sass/_layout.scss @@ -268,19 +268,6 @@ body.mappage.admin { width: $mappage-sidebar-width--medium + $mappage-actions-width--medium; } } - - // Chrome/Safari count padding-bottom as part of the scrollable content in - // an overflow:scroll element (technically in contravention of the spec), - // whereas Firefox/IE render the padding outside the scrollable area. - // We need padding at the bottom of the sidebar, to stop .shadow-wrap from - // obscuring content at the bottom of the sidebar. So we use an :after - // pseudo-element instead of padding. - // https://bugzilla.mozilla.org/show_bug.cgi?id=748518 - &:after { - content: ""; - display: block; - height: 4em; - } } // This goes inside #map_sidebar, and establishes a flex container allowing diff --git a/web/cobrands/sass/_report_list_pins.scss b/web/cobrands/sass/_report_list_pins.scss index 55ef1cf56..f6fcb46f9 100644 --- a/web/cobrands/sass/_report_list_pins.scss +++ b/web/cobrands/sass/_report_list_pins.scss @@ -27,7 +27,7 @@ $pin_prefix: '/i/' !default; background-image: url('#{$pin_prefix}pin-orange-small.png'); } } -.item-list--reports__item--selected { +.item-list__item--selected { background: $base_bg; a, a:hover, a:focus { diff --git a/web/js/duplicates.js b/web/js/duplicates.js new file mode 100644 index 000000000..4ed54846c --- /dev/null +++ b/web/js/duplicates.js @@ -0,0 +1,206 @@ +$(function() { + + // Store a reference to the "duplicate" report pins so we can + // quickly remove them when we’re finished showing duplicates. + var current_duplicate_markers; + + // Report ID will be available on report inspect page, + // but undefined on new report page. + var report_id = $("#report_inspect_form .js-report-id").text() || undefined; + + function refresh_duplicate_list() { + // This function will return a jQuery Promise, so callbacks can be + // hooked onto it, once the ajax request as completed. + var dfd = $.Deferred(); + + var nearby_url; + var url_params = { + filter_category: $('select[name="category"]').val(), + latitude: $('input[name="latitude"]').val(), + longitude: $('input[name="longitude"]').val() + }; + + if ( report_id ) { + nearby_url = '/report/' + report_id + '/nearby.json'; + url_params.distance = 1000; // Inspectors might want to see reports fairly far away (1000 metres) + url_params.pin_size = 'small'; // How it's always been + } else { + nearby_url = '/around/nearby'; + url_params.distance = 250; // Only want to bother public with very nearby reports (250 metres) + url_params.pin_size = 'normal'; + } + + $.ajax({ + url: nearby_url, + data: url_params, + dataType: 'json' + }).done(function(response) { + if ( response.pins.length ){ + render_duplicate_list(response); + render_duplicate_pins(response); + } else { + remove_duplicate_pins(); + remove_duplicate_list(); + } + dfd.resolve(); + }).fail(function(){ + remove_duplicate_pins(); + remove_duplicate_list(); + dfd.reject(); + }); + + return dfd.promise(); + } + + function render_duplicate_list(api_response) { + var $reports = $( api_response.reports_list ); + + var duplicate_of = $('#report_inspect_form [name="duplicate_of"]').val(); + if ( duplicate_of ) { + $reports.filter('[data-report-id="' + duplicate_of + '"]') + .addClass("item-list__item--selected"); + } + + $("#js-duplicate-reports ul").empty().prepend( $reports ); + fixmystreet.set_up.fancybox_images(); + + $('#js-duplicate-reports').hide().removeClass('hidden').slideDown(); + if ( $('#problem_form').length ) { + $('.js-hide-if-invalid-category').slideUp(); + } + + // Highlight map pin when hovering associated list item. + var timeout; + $reports.on('mouseenter', function(){ + var id = parseInt( $(this).data('reportId'), 10 ); + clearTimeout( timeout ); + fixmystreet.maps.markers_highlight( id ); + }).on('mouseleave', function(){ + timeout = setTimeout( fixmystreet.maps.markers_highlight, 50 ); + }); + + // Add a "select this report" button, when on the report inspect form. + if ( $('#report_inspect_form').length ) { + $reports.each(function(){ + var $button = $('<button>').addClass('btn btn--small btn--primary'); + $button.text(translation_strings.this_report); + $button.on('click', function(e) { + e.preventDefault(); // Prevent button from submitting parent form + var report_id = $(this).closest('li').data('reportId'); + $('#report_inspect_form [name="duplicate_of"]').val(report_id); + $(this).closest('li') + .addClass('item-list__item--selected') + .siblings('.item-list__item--selected') + .removeClass('item-list__item--selected'); + }); + $(this).find('.item-list__item--expandable__actions').append($button); + }); + } + + // Add a "track this report" button when on the regular reporting form. + if ( $('#problem_form').length ) { + $reports.each(function() { + var $li = $(this); + var id = parseInt( $li.data('reportId'), 10 ); + var alert_url = '/alert/subscribe?id=' + encodeURIComponent(id); + var $button = $('<a>').addClass('btn btn--small btn--primary'); + $button.text(translation_strings.this_is_the_problem); + $button.attr('href', alert_url); + $button.on('click', function(e){ + e.preventDefault(); + var $div = $('.js-template-get-updates > div').clone(); + $div.find('input[name="id"]').val(id); + $div.find('input[disabled]').prop('disabled', false); + $div.hide().appendTo($li).slideDown(250, function(){ + $div.find('input[type="email"]').focus(); + }); + $li.find('.item-list__item--expandable__actions').slideUp(250); + $li.removeClass('js-expandable'); + $li.addClass('item-list__item--selected'); + }); + $li.find('.item-list__item--expandable__actions').append($button); + }); + } + } + + function render_duplicate_pins(api_response) { + var markers = fixmystreet.maps.markers_list( api_response.pins, true ); + fixmystreet.markers.removeFeatures( current_duplicate_markers ); + fixmystreet.markers.addFeatures( markers ); + current_duplicate_markers = markers; + } + + function remove_duplicate_list(cb) { + var animations = []; + + animations.push( $.Deferred() ); + $('#js-duplicate-reports').slideUp(function(){ + $(this).addClass('hidden'); + $(this).find('ul').empty(); + animations[0].resolve(); + }); + if ( $('#problem_form').length ) { + animations.push( $.Deferred() ); + $('.js-hide-if-invalid-category').slideDown(function(){ + animations[1].resolve(); + }); + } + + $.when.apply(this, animations).then(cb); + } + + function remove_duplicate_pins() { + fixmystreet.markers.removeFeatures( current_duplicate_markers ); + } + + function inspect_form_state_change() { + // The duplicate report list only makes sense when state is 'duplicate' + if ($(this).val() !== "duplicate") { + $("#js-duplicate-reports").addClass("hidden"); + return; + } else { + $("#js-duplicate-reports").removeClass("hidden"); + } + // If this report is already marked as a duplicate of another, then + // there's no need to refresh the list of duplicate reports + var duplicate_of = $("#report_inspect_form [name=duplicate_of]").val(); + if (!!duplicate_of) { + return; + } + refresh_duplicate_list(); + } + + var category_changing = false; + function problem_form_category_change() { + // Annoyingly this event seems to fire a few times in quick succession, + // so set a flag to avoid multiple overlapping refreshes. + if (category_changing) { return; } + category_changing = true; + + refresh_duplicate_list().always(function(){ + // Wait an extra second until we allow another reload. + setTimeout(function(){ + category_changing = false; + }, 1000); + }); + } + + // Want to show potential duplicates when a regular user starts a new + // report, or changes the category/location of a partial report. + $("#problem_form").on("change.category", "select#form_category", problem_form_category_change); + + // Want to show duplicates when an inspector sets a report’s state to "duplicate". + $("#report_inspect_form").on("change.state", "select#state", inspect_form_state_change); + + // Also want to give inspectors a way to select a *new* duplicate report. + $("#js-change-duplicate-report").click(refresh_duplicate_list); + + $('.js-hide-duplicate-suggestions').on('click', function(e){ + e.preventDefault(); + remove_duplicate_pins(); + remove_duplicate_list(function(){ + $('#form_title').focus(); + }); + }); + +}); diff --git a/web/js/map-OpenLayers.js b/web/js/map-OpenLayers.js index fdfeed314..984f4b098 100644 --- a/web/js/map-OpenLayers.js +++ b/web/js/map-OpenLayers.js @@ -381,15 +381,15 @@ $.extend(fixmystreet.utils, { function sidebar_highlight(problem_id) { if (typeof problem_id !== 'undefined') { - var $a = $('.item-list--reports a[href$="/' + problem_id + '"]'); - $a.parent().addClass('hovered'); + var $li = $('[data-report-id="' + problem_id + '"]'); + $li.addClass('hovered'); } else { - $('.item-list--reports .hovered').removeClass('hovered'); + $('.item-list .hovered').removeClass('hovered'); } } function marker_click(problem_id, evt) { - var $a = $('.item-list--reports a[href$="/' + problem_id + '"]'); + var $a = $('.item-list a[href$="/' + problem_id + '"]'); if (!$a[0]) { return; } |