diff options
author | Dave Arter <davea@mysociety.org> | 2019-11-27 11:59:20 +0000 |
---|---|---|
committer | Matthew Somerville <matthew@mysociety.org> | 2020-01-30 11:43:47 +0000 |
commit | 82346b798099d4662528a63fbfb355aa991a0ea7 (patch) | |
tree | 780ad83c321bcddceb80c0b69ef19b7194ed1cd5 | |
parent | c82b1734cd2b5f224e3d4779972e4dcdefb41c7f (diff) |
Enable category groups on map filter dropdown.
Includes an updated version of jquery-multi-select.
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Around.pm | 1 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/My.pm | 3 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Reports.pm | 1 | ||||
-rw-r--r-- | t/app/controller/around.t | 8 | ||||
-rw-r--r-- | t/app/controller/reports.t | 5 | ||||
-rw-r--r-- | templates/web/base/reports/_list-filters.html | 23 | ||||
-rw-r--r-- | web/cobrands/sass/_multiselect.scss | 22 | ||||
-rw-r--r-- | web/vendor/jquery.multi-select.min.js | 22 |
9 files changed, 68 insertions, 18 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e69a106f..1429800ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Add XSL to RSS feeds so they look nicer in browsers. - Add per-report OpenGraph images. #2394 - Display GPS marker on /around map. #2359 + - Use category groups whenever category lists are shown. #2702 - Admin improvements: - Add new roles system, to group permissions and apply to users. #2483 - Contact form emails now include user admin links. diff --git a/perllib/FixMyStreet/App/Controller/Around.pm b/perllib/FixMyStreet/App/Controller/Around.pm index fb3670fb7..5afca29df 100644 --- a/perllib/FixMyStreet/App/Controller/Around.pm +++ b/perllib/FixMyStreet/App/Controller/Around.pm @@ -254,6 +254,7 @@ sub check_and_stash_category : Private { )->all_sorted; $c->stash->{filter_categories} = \@categories; my %categories_mapped = map { $_->category => 1 } @categories; + $c->forward('/report/stash_category_groups', [ \@categories ]) if $c->cobrand->enable_category_groups; my $categories = [ $c->get_param_list('filter_category', 1) ]; my %valid_categories = map { $_ => 1 } grep { $_ && $categories_mapped{$_} } @$categories; diff --git a/perllib/FixMyStreet/App/Controller/My.pm b/perllib/FixMyStreet/App/Controller/My.pm index 1230bfcae..3328caac0 100644 --- a/perllib/FixMyStreet/App/Controller/My.pm +++ b/perllib/FixMyStreet/App/Controller/My.pm @@ -189,11 +189,12 @@ sub setup_page_data : Private { my @categories = $c->stash->{problems_rs}->search({ state => [ FixMyStreet::DB::Result::Problem->visible_states() ], }, { - columns => [ 'category', 'bodies_str' ], + columns => [ 'category', 'bodies_str', 'extra' ], distinct => 1, order_by => [ 'category' ], } )->all; $c->stash->{filter_categories} = \@categories; + $c->forward('/report/stash_category_groups', [ \@categories ]) if $c->cobrand->enable_category_groups; my $pins = $c->stash->{pins}; FixMyStreet::Map::display_map( diff --git a/perllib/FixMyStreet/App/Controller/Reports.pm b/perllib/FixMyStreet/App/Controller/Reports.pm index cbe91ed9e..efa8ead93 100644 --- a/perllib/FixMyStreet/App/Controller/Reports.pm +++ b/perllib/FixMyStreet/App/Controller/Reports.pm @@ -193,6 +193,7 @@ sub setup_categories_and_map :Private { $c->stash->{filter_categories} = \@categories; $c->stash->{filter_category} = { map { $_ => 1 } $c->get_param_list('filter_category', 1) }; + $c->forward('/report/stash_category_groups', [ \@categories ]) if $c->cobrand->enable_category_groups; my $pins = $c->stash->{pins} || []; diff --git a/t/app/controller/around.t b/t/app/controller/around.t index a9c008c9d..bd2bf2cee 100644 --- a/t/app/controller/around.t +++ b/t/app/controller/around.t @@ -232,7 +232,11 @@ subtest 'check category, status and extra filtering works on /around' => sub { # Create one open and one fixed report in each category foreach my $category ( @$categories ) { - $mech->create_contact_ok( category => $category, body_id => $body->id, email => "$category\@example.org" ); + my $contact = $mech->create_contact_ok( category => $category, body_id => $body->id, email => "$category\@example.org" ); + if ($category ne 'Pothole') { + $contact->set_extra_metadata(group => ['Environment']); + $contact->update; + } foreach my $state ( 'confirmed', 'fixed - user', 'fixed - council' ) { my %report_params = ( %$params, @@ -252,9 +256,11 @@ subtest 'check category, status and extra filtering works on /around' => sub { FixMyStreet::override_config { ALLOWED_COBRANDS => 'fixmystreet', MAPIT_URL => 'http://mapit.uk/', + COBRAND_FEATURES => { category_groups => { fixmystreet => 1 } }, }, sub { $mech->get_ok( '/around?filter_category=Pothole&bbox=' . $bbox ); $mech->content_contains('<option value="Pothole" selected>'); + $mech->content_contains('<optgroup label="Environment">'); }; $json = $mech->get_ok_json( '/around?ajax=1&filter_category=Pothole&bbox=' . $bbox ); diff --git a/t/app/controller/reports.t b/t/app/controller/reports.t index 670c5cfc3..1d9c8d216 100644 --- a/t/app/controller/reports.t +++ b/t/app/controller/reports.t @@ -18,6 +18,9 @@ my $body_west_id = $mech->create_body_ok(2504, 'Westminster City Council')->id; my $body_fife_id = $mech->create_body_ok(2649, 'Fife Council')->id; my $body_slash_id = $mech->create_body_ok(10000, 'Electricity/Gas Council')->id; +$mech->create_contact_ok(body_id => $body_edin_id, category => 'Potholes', email => 'potholes@example.org'); +$mech->create_contact_ok(body_id => $body_west_id, category => 'Graffiti', email => 'graffiti@example.org'); +$mech->create_contact_ok(body_id => $body_fife_id, category => 'Flytipping', email => 'flytipping@example.org'); my @edinburgh_problems = $mech->create_problems_for_body(3, $body_edin_id, 'All reports', { category => 'Potholes' }); my @westminster_problems = $mech->create_problems_for_body(5, $body_west_id, 'All reports', { category => 'Graffiti' }); my @fife_problems = $mech->create_problems_for_body(15, $body_fife_id, 'All reports', { category => 'Flytipping' }); @@ -128,9 +131,11 @@ $mech->content_contains('2,3,4,4'); FixMyStreet::override_config { ALLOWED_COBRANDS => 'fixmystreet', MAPIT_URL => 'http://mapit.uk/', + COBRAND_FEATURES => { category_groups => { fixmystreet => 1 } }, }, sub { $mech->submit_form_ok( { with_fields => { body => $body_edin_id } }, 'Submitted dropdown okay' ); is $mech->uri->path, '/reports/City+of+Edinburgh'; + $mech->content_contains('<optgroup label="">'); subtest "test ward pages" => sub { $mech->get_ok('/reports/Birmingham/Bad-Ward'); diff --git a/templates/web/base/reports/_list-filters.html b/templates/web/base/reports/_list-filters.html index 0cd477d1f..3125f63b1 100644 --- a/templates/web/base/reports/_list-filters.html +++ b/templates/web/base/reports/_list-filters.html @@ -1,15 +1,28 @@ [% select_status = PROCESS 'reports/_list-filter-status.html' %] +[% BLOCK category_options %] + [% FOR cat IN categories %] + <option value="[% cat.category %]"[% ' selected' IF filter_category.${cat.category} OR ( filter_group AND ( cat.get_extra_metadata('group') == filter_group OR cat.category == filter_group ) ) %]> + [% cat.category_display %] + [%~ IF cat.get_extra_metadata('help_text') %] ([% cat.get_extra_metadata('help_text') %])[% END ~%] + </option> + [% END %] +[% END %] + + [% select_category = BLOCK %] [% IF filter_categories.size %] [% SET filter_group = c.get_param('filter_group') %] <select class="form-control js-multiple" name="filter_category" id="filter_categories" multiple data-all="[% loc('Everything') %]"> - [% FOR cat IN filter_categories %] - <option value="[% cat.category | html %]"[% ' selected' IF filter_category.${cat.category} OR ( filter_group AND ( cat.get_extra_metadata('group') == filter_group OR cat.category == filter_group ) ) %]> - [% cat.category_display | html %] - [%~ IF cat.get_extra_metadata('help_text') %] ([% cat.get_extra_metadata('help_text') %])[% END ~%] - </option> + [% IF category_groups %] + [% FOR group IN category_groups %] + <optgroup label="[% group.name %]"> + [% INCLUDE category_options categories=group.categories %] + </optgroup> [% END %] + [% ELSE %] + [% INCLUDE category_options categories=filter_categories %] + [% END %] </select> [% ELSE %] [% loc('Everything') %] diff --git a/web/cobrands/sass/_multiselect.scss b/web/cobrands/sass/_multiselect.scss index 5f59599a6..27406dd36 100644 --- a/web/cobrands/sass/_multiselect.scss +++ b/web/cobrands/sass/_multiselect.scss @@ -35,6 +35,28 @@ } } +.multi-select-menuitem--titled:before { + display: block; + font-weight: bold; + content: attr(data-group-title); + margin: 0 0 0.25em -20px; + cursor: default; +} + +.multi-select-menuitem--titledsr:before { + display: block; + font-weight: bold; + content: attr(data-group-title); + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} + .multi-select-modal { display: none; } diff --git a/web/vendor/jquery.multi-select.min.js b/web/vendor/jquery.multi-select.min.js index 8b6fad748..546c4a169 100644 --- a/web/vendor/jquery.multi-select.min.js +++ b/web/vendor/jquery.multi-select.min.js @@ -1,11 +1,11 @@ -(function(d){function g(a,b){this.b=d(a);this.a=d.extend({},h,b);this.L()}var h={containerHTML:'<div class="multi-select-container">',menuHTML:'<div class="multi-select-menu">',buttonHTML:'<span class="multi-select-button">',menuItemsHTML:'<div class="multi-select-menuitems">',menuItemHTML:'<label class="multi-select-menuitem">',presetsHTML:'<div class="multi-select-presets">',modalHTML:void 0,activeClass:"multi-select-container--open",noneText:"-- Select --",allText:void 0,presets:void 0,positionedMenuClass:"multi-select-container--positioned", -positionMenuWithin:void 0,viewportBottomGutter:20,menuMinHeight:200};d.extend(g.prototype,{L:function(){this.B();this.K();this.D();this.C();this.F();this.I();this.M();this.N();this.b.hide()},B:function(){if(!1===this.b.is("select[multiple]"))throw Error("$.multiSelect only works on <select multiple> elements");},K:function(){this.l=d('label[for="'+this.b.attr("id")+'"]')},D:function(){this.f=d(this.a.containerHTML);this.b.data("multi-select-container",this.f);this.f.insertAfter(this.b)},C:function(){var a= -this;this.g=d(this.a.buttonHTML);this.g.attr({role:"button","aria-haspopup":"true",tabindex:0,"aria-label":this.l.eq(0).text()}).on("keydown.multiselect",function(b){var c=b.which;13===c||32===c?(b.preventDefault(),a.g.click()):40===c?(b.preventDefault(),a.o(),(a.h||a.i).children(":first").focus()):27===c&&a.j()}).on("click.multiselect",function(){a.s()}).appendTo(this.f);this.b.on("change.multiselect",function(){a.v()});this.v()},v:function(){var a=[],b=[];this.b.children("option").each(function(){var c= -d(this).text();a.push(c);d(this).is(":selected")&&b.push(d.trim(c))});this.g.empty();0==b.length?this.g.text(this.a.noneText):b.length===a.length&&this.a.allText?this.g.text(this.a.allText):this.g.text(b.join(", "))},F:function(){var a=this;this.c=d(this.a.menuHTML);this.c.attr({role:"menu"}).on("keyup.multiselect",function(b){27===b.which&&(a.j(),a.g.focus())}).appendTo(this.f);this.H();this.a.presets&&this.J()},H:function(){var a=this;this.i=d(this.a.menuItemsHTML);this.c.append(this.i);this.b.on("change.multiselect", -function(b,c){!0!==c&&a.w()});this.w()},w:function(){var a=this;this.i.empty();this.b.children("option").each(function(b,c){b=a.G(d(c),b);a.i.append(b)})},u:function(a,b){var c=b.which;38===c?(b.preventDefault(),b=d(b.currentTarget).prev(),b.length?b.focus():this.h&&"menuitem"===a?this.h.children(":last").focus():this.g.focus()):40===c&&(b.preventDefault(),b=d(b.currentTarget).next(),b.length||"menuitem"===a?b.focus():this.i.children(":first").focus())},J:function(){var a=this;this.h=d(this.a.presetsHTML); -this.c.prepend(this.h);d.each(this.a.presets,function(b,c){b=a.b.attr("name")+"_preset_"+b;var e=d(a.a.menuItemHTML).attr({"for":b,role:"menuitem"}).text(" "+c.name).on("keydown.multiselect",a.u.bind(a,"preset")).appendTo(a.h);d("<input>").attr({type:"radio",name:a.b.attr("name")+"_presets",id:b}).prependTo(e).on("change.multiselect",function(){a.b.val(c.options);a.b.trigger("change")})});this.b.on("change.multiselect",function(){a.A()});this.A()},A:function(){var a=this;d.each(this.a.presets,function(b, -c){b=a.b.attr("name")+"_preset_"+b;b=a.h.find("#"+b);a:{c=c.options||[];var e=a.b.val()||[];if(c.length!=e.length)c=!1;else{c.sort();e.sort();for(var f=0;f<c.length;f++)if(c[f]!==e[f]){c=!1;break a}c=!0}}c?b.prop("checked",!0):b.prop("checked",!1)})},G:function(a,b){var c=this.b.attr("name")+"_"+b;b=d(this.a.menuItemHTML).attr({"for":c,role:"menuitem"}).on("keydown.multiselect",this.u.bind(this,"menuitem")).text(" "+a.text());c=d("<input>").attr({type:"checkbox",id:c,value:a.val()}).prependTo(b); -a.is(":disabled")&&c.attr("disabled","disabled");a.is(":selected")&&c.prop("checked","checked");c.on("change.multiselect",function(){d(this).prop("checked")?a.prop("selected",!0):a.prop("selected",!1);a.trigger("change",[!0])});return b},I:function(){var a=this;this.a.modalHTML&&(this.m=d(this.a.modalHTML),this.m.on("click.multiselect",function(){a.j()}),this.m.insertBefore(this.c))},M:function(){var a=this;d("html").on("click.multiselect",function(){a.j()});this.f.on("click.multiselect",function(b){b.stopPropagation()})}, -N:function(){var a=this;this.l.on("click.multiselect",function(b){b.preventDefault();b.stopPropagation();a.s()})},o:function(){d("html").trigger("click.multiselect");this.f.addClass(this.a.activeClass);if(this.a.positionMenuWithin&&this.a.positionMenuWithin instanceof d){var a=this.c.offset().left+this.c.outerWidth(),b=this.a.positionMenuWithin.offset().left+this.a.positionMenuWithin.outerWidth();a>b&&(this.c.css("width",b-this.c.offset().left),this.f.addClass(this.a.positionedMenuClass))}a=this.c.offset().top+ -this.c.outerHeight();b=d(window).scrollTop()+d(window).height();a>b-this.a.viewportBottomGutter?this.c.css({maxHeight:Math.max(b-this.a.viewportBottomGutter-this.c.offset().top,this.a.menuMinHeight),overflow:"scroll"}):this.c.css({maxHeight:"",overflow:""})},j:function(){this.f.removeClass(this.a.activeClass);this.f.removeClass(this.a.positionedMenuClass);this.c.css("width","auto")},s:function(){this.f.hasClass(this.a.activeClass)?this.j():this.o()}});d.fn.multiSelect=function(a){return this.each(function(){d.data(this, -"plugin_multiSelect")||d.data(this,"plugin_multiSelect",new g(this,a))})}})(jQuery); +(function(d){function g(a,b){this.b=d(a);this.a=d.extend({},k,b);this.M()}var k={containerHTML:'<div class="multi-select-container">',menuHTML:'<div class="multi-select-menu">',buttonHTML:'<span class="multi-select-button">',menuItemsHTML:'<div class="multi-select-menuitems">',menuItemHTML:'<label class="multi-select-menuitem">',presetsHTML:'<div class="multi-select-presets">',modalHTML:void 0,menuItemTitleClass:"multi-select-menuitem--titled",activeClass:"multi-select-container--open",noneText:"-- Select --", +allText:void 0,presets:void 0,positionedMenuClass:"multi-select-container--positioned",positionMenuWithin:void 0,viewportBottomGutter:20,menuMinHeight:200};d.extend(g.prototype,{M:function(){this.C();this.L();this.F();this.D();this.G();this.J();this.N();this.O();this.b.hide()},C:function(){if(!1===this.b.is("select[multiple]"))throw Error("$.multiSelect only works on <select multiple> elements");},L:function(){this.l=d('label[for="'+this.b.attr("id")+'"]')},F:function(){this.f=d(this.a.containerHTML); +this.b.data("multi-select-container",this.f);this.f.insertAfter(this.b)},D:function(){var a=this;this.g=d(this.a.buttonHTML);this.g.attr({role:"button","aria-haspopup":"true",tabindex:0,"aria-label":this.l.eq(0).text()}).on("keydown.multiselect",function(b){var c=b.which;13===c||32===c?(b.preventDefault(),a.g.click()):40===c?(b.preventDefault(),a.s(),(a.i||a.h).children(":first").focus()):27===c&&a.j()}).on("click.multiselect",function(){a.u()}).appendTo(this.f);this.b.on("change.multiselect",function(){a.w()}); +this.w()},w:function(){var a=[],b=[];this.b.find("option").each(function(){var c=d(this).text();a.push(c);d(this).is(":selected")&&b.push(d.trim(c))});this.g.empty();0==b.length?this.g.text(this.a.noneText):b.length===a.length&&this.a.allText?this.g.text(this.a.allText):this.g.text(b.join(", "))},G:function(){var a=this;this.c=d(this.a.menuHTML);this.c.attr({role:"menu"}).on("keyup.multiselect",function(b){27===b.which&&(a.j(),a.g.focus())}).appendTo(this.f);this.H();this.a.presets&&this.K()},H:function(){var a= +this;this.h=d(this.a.menuItemsHTML);this.c.append(this.h);this.b.on("change.multiselect",function(b,c){!0!==c&&a.A()});this.A()},A:function(){var a=this;this.h.empty();this.b.children("optgroup,option").each(function(b,c){"OPTION"===c.nodeName?(b=a.o(d(c),b),a.h.append(b)):a.I(d(c),b)})},v:function(a,b){var c=b.which;38===c?(b.preventDefault(),b=d(b.currentTarget).prev(),b.length?b.focus():this.i&&"menuitem"===a?this.i.children(":last").focus():this.g.focus()):40===c&&(b.preventDefault(),b=d(b.currentTarget).next(), +b.length||"menuitem"===a?b.focus():this.h.children(":first").focus())},K:function(){var a=this;this.i=d(this.a.presetsHTML);this.c.prepend(this.i);d.each(this.a.presets,function(b,c){b=a.b.attr("name")+"_preset_"+b;var f=d(a.a.menuItemHTML).attr({"for":b,role:"menuitem"}).text(" "+c.name).on("keydown.multiselect",a.v.bind(a,"preset")).appendTo(a.i);d("<input>").attr({type:"radio",name:a.b.attr("name")+"_presets",id:b}).prependTo(f).on("change.multiselect",function(){a.b.val(c.options);a.b.trigger("change")})}); +this.b.on("change.multiselect",function(){a.B()});this.B()},B:function(){var a=this;d.each(this.a.presets,function(b,c){b=a.b.attr("name")+"_preset_"+b;b=a.i.find("#"+b);a:{c=c.options||[];var f=a.b.val()||[];if(c.length!=f.length)c=!1;else{c.sort();f.sort();for(var e=0;e<c.length;e++)if(c[e]!==f[e]){c=!1;break a}c=!0}}c?b.prop("checked",!0):b.prop("checked",!1)})},I:function(a,b){var c=this;a.children("option").each(function(f,e){e=c.o(d(e),b+"_"+f);var h=c.a.menuItemTitleClass;0!==f&&(h+="sr"); +e.addClass(h).attr("data-group-title",a.attr("label"));c.h.append(e)})},o:function(a,b){var c=this.b.attr("name")+"_"+b;b=d(this.a.menuItemHTML).attr({"for":c,role:"menuitem"}).on("keydown.multiselect",this.v.bind(this,"menuitem")).text(" "+a.text());c=d("<input>").attr({type:"checkbox",id:c,value:a.val()}).prependTo(b);a.is(":disabled")&&c.attr("disabled","disabled");a.is(":selected")&&c.prop("checked","checked");c.on("change.multiselect",function(){d(this).prop("checked")?a.prop("selected",!0): +a.prop("selected",!1);a.trigger("change",[!0])});return b},J:function(){var a=this;this.a.modalHTML&&(this.m=d(this.a.modalHTML),this.m.on("click.multiselect",function(){a.j()}),this.m.insertBefore(this.c))},N:function(){var a=this;d("html").on("click.multiselect",function(){a.j()});this.f.on("click.multiselect",function(b){b.stopPropagation()})},O:function(){var a=this;this.l.on("click.multiselect",function(b){b.preventDefault();b.stopPropagation();a.u()})},s:function(){d("html").trigger("click.multiselect"); +this.f.addClass(this.a.activeClass);if(this.a.positionMenuWithin&&this.a.positionMenuWithin instanceof d){var a=this.c.offset().left+this.c.outerWidth(),b=this.a.positionMenuWithin.offset().left+this.a.positionMenuWithin.outerWidth();a>b&&(this.c.css("width",b-this.c.offset().left),this.f.addClass(this.a.positionedMenuClass))}a=this.c.offset().top+this.c.outerHeight();b=d(window).scrollTop()+d(window).height();a>b-this.a.viewportBottomGutter?this.c.css({maxHeight:Math.max(b-this.a.viewportBottomGutter- +this.c.offset().top,this.a.menuMinHeight),overflow:"scroll"}):this.c.css({maxHeight:"",overflow:""})},j:function(){this.f.removeClass(this.a.activeClass);this.f.removeClass(this.a.positionedMenuClass);this.c.css("width","auto")},u:function(){this.f.hasClass(this.a.activeClass)?this.j():this.s()}});d.fn.multiSelect=function(a){return this.each(function(){d.data(this,"plugin_multiSelect")||d.data(this,"plugin_multiSelect",new g(this,a))})}})(jQuery); |