diff options
author | Zarino Zappia <mail@zarino.co.uk> | 2017-11-28 11:45:49 +0000 |
---|---|---|
committer | Zarino Zappia <mail@zarino.co.uk> | 2017-11-29 11:16:44 +0000 |
commit | 5d485f64d864b236fdfaba1c3cbf452a7331b18d (patch) | |
tree | 7b7f60080c830e9beceaddc43e891ab75d891d3f | |
parent | 26860bb14c49061e94bd4a728d1150e53094ff79 (diff) |
Nicer multi-select elements in Admin pages
Fixes #1589.
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | templates/web/base/admin/body-form.html | 3 | ||||
-rw-r--r-- | templates/web/base/admin/user-form.html | 2 | ||||
-rw-r--r-- | templates/web/zurich/admin/body-form.html | 3 | ||||
-rw-r--r-- | web/cobrands/fixmystreet/fixmystreet.js | 53 | ||||
-rw-r--r-- | web/cobrands/sass/_base.scss | 3 | ||||
-rw-r--r-- | web/cobrands/sass/_multiselect.scss | 8 | ||||
-rw-r--r-- | web/vendor/jquery.multi-select.min.js | 18 |
8 files changed, 53 insertions, 38 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 221fa3ade..5f9e335a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ - Report field pre-filling for inspectors configurable #1854 - Admins can now unban users #1881 - Council dashboard has date range for report generation #1885 + - More JavaScript-enhanced `<select multiple>` elements #1589 - UK: - Use SVG logo, inlined on front page. #1887 - Inline critical CSS on front page. diff --git a/templates/web/base/admin/body-form.html b/templates/web/base/admin/body-form.html index 55d0e500c..54445a282 100644 --- a/templates/web/base/admin/body-form.html +++ b/templates/web/base/admin/body-form.html @@ -75,8 +75,7 @@ </div> <p> <label for="area_ids">[% loc('Area covered') %]</label> - <select class="form-control" name="area_ids" id="area_ids" multiple> - <option value=""> -- [% loc('Select an area') %] -- </option> + <select class="form-control js-multiple" name="area_ids" id="area_ids" multiple data-none="-- [% loc('Select an area') %] --"> [% SET body_areas = body.areas %] [% FOR area IN areas %] [% SET aid = area.id %] diff --git a/templates/web/base/admin/user-form.html b/templates/web/base/admin/user-form.html index 7b27f7497..9a2c0b9e2 100644 --- a/templates/web/base/admin/user-form.html +++ b/templates/web/base/admin/user-form.html @@ -127,7 +127,7 @@ </div> [% IF c.user.is_superuser %] [% loc('Trusted by bodies:') %]<br /> - <select class="form-control" id='body' name='trusted_bodies' multiple> + <select class="form-control js-multiple" id='body' name='trusted_bodies' multiple> [% FOR body IN bodies %] <option value="[% body.id %]"[% ' selected' IF user.has_permission_to('trusted', body.id) %]>[% body.name %]</option> [% END %] diff --git a/templates/web/zurich/admin/body-form.html b/templates/web/zurich/admin/body-form.html index de9fac0e2..44adcbc72 100644 --- a/templates/web/zurich/admin/body-form.html +++ b/templates/web/zurich/admin/body-form.html @@ -22,8 +22,7 @@ <p> <label for="area_ids">[% loc('Area covered') %]</label> - <select class="form-control" name="area_ids" id="area_ids" multiple> - <option value=""> -- [% loc('Select an area') %] -- </option> + <select class="form-control js-multiple" name="area_ids" id="area_ids" multiple data-none="-- [% loc('Select an area') %] --"> [% SET body_areas = body.areas %] [% FOR area IN areas %] [% SET aid = area.id %] diff --git a/web/cobrands/fixmystreet/fixmystreet.js b/web/cobrands/fixmystreet/fixmystreet.js index f73792a3c..16ee7b511 100644 --- a/web/cobrands/fixmystreet/fixmystreet.js +++ b/web/cobrands/fixmystreet/fixmystreet.js @@ -112,27 +112,38 @@ function isR2L() { }, make_multi: function() { - var $this = $(this), - all = $this.data('all'), - none = $this.data('none') || all, - allOpts = $this.data('all-options') || [], - extra = $this.data('extra'), - extraOpts = $this.data('extra-options') || []; - - var presets = [{ - name: all, - options: allOpts - }]; - - if (extra) { - presets.push({name: extra, options: extraOpts}); - } - $this.multiSelect({ - allText: all, - noneText: none, - positionMenuWithin: $('#side'), - presets: presets - }); + // A convenience wrapper around $.multiSelect() that translates HTML + // data-* attributes into settings for the multiSelect constructor. + var $select = $(this); + var settings = {}; + + if ( $select.data('none') ) { + settings.noneText = $select.data('none'); + } + + if ( $select.data('all') ) { + settings.allText = $select.data('all'); + settings.noneText = settings.noneText || settings.allText; + settings.presets = []; + settings.presets.push({ + name: settings.allText, + options: $select.data('all-options') || [] + }); + } + + if ( $select.data('extra') && $select.data('extra-options') ) { + settings.presets = settings.presets || []; + settings.presets.push({ + name: $select.data('extra'), + options: $select.data('extra-options') + }); + } + + if ( document.querySelector('#side') && document.querySelector('#side').contains($select[0]) ) { + settings.positionMenuWithin = $('#side'); + } + + $select.multiSelect(settings); } }); diff --git a/web/cobrands/sass/_base.scss b/web/cobrands/sass/_base.scss index 2c4ce1e33..109a67d64 100644 --- a/web/cobrands/sass/_base.scss +++ b/web/cobrands/sass/_base.scss @@ -294,6 +294,9 @@ select.form-control { &[multiple] { height: auto; } + + // Prevent a flash of tall select elements + // before $.multiSelect() has replaced them. .js &.js-multiple[multiple] { height: 2.2em; } diff --git a/web/cobrands/sass/_multiselect.scss b/web/cobrands/sass/_multiselect.scss index 9dda17fea..ba90c0d89 100644 --- a/web/cobrands/sass/_multiselect.scss +++ b/web/cobrands/sass/_multiselect.scss @@ -1,14 +1,15 @@ .multi-select-container { position: relative; + display: inline-block; // shrink to width of .multi-select-button } .multi-select-menu { position: absolute; left: 0; top: 0.8em; - z-index: 1; - float: left; - min-width: 100%; + z-index: 2; // stack above mysoc-footer + float: left; // shrink to width of child elements + min-width: 100%; // always at least as wide as its .multi-select-button sibling background: #fff; margin: 1em 0; border: 1px solid #aaa; @@ -20,6 +21,7 @@ display: block; font-size: 0.875em; padding: 0.6em 1em 0.6em 30px; + margin: 0; white-space: nowrap; & + & { diff --git a/web/vendor/jquery.multi-select.min.js b/web/vendor/jquery.multi-select.min.js index 6c7cefced..015aee226 100644 --- a/web/vendor/jquery.multi-select.min.js +++ b/web/vendor/jquery.multi-select.min.js @@ -1,9 +1,9 @@ -(function(c){function f(a,b){this.b=c(a);this.a=c.extend({},g,b);this.H()}var g={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">',activeClass:"multi-select-container--open",noneText:"-- Select --",allText:void 0,presets:void 0,positionedMenuClass:"multi-select-container--positioned", -positionMenuWithin:void 0};c.extend(f.prototype,{H:function(){this.v();this.G();this.A();this.w();this.B();this.J();this.K();this.b.hide()},v:function(){if(!1===this.b.is("select[multiple]"))throw Error("$.multiSelect only works on <select multiple> elements");},G:function(){this.l=c('label[for="'+this.b.attr("id")+'"]')},A:function(){this.c=c(this.a.containerHTML);this.b.data("multi-select-container",this.c);this.c.insertAfter(this.b)},w:function(){var a=this;this.g=c(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){b=b.which;13!==b&&32!==b||a.g.click()}).on("click.multiselect",function(){a.m()}).appendTo(this.c);this.b.on("change.multiselect",function(){a.o()});this.o()},o:function(){var a=[],b=[];this.b.children("option").each(function(){var d=c(this).text();a.push(d);c(this).is(":selected")&&b.push(c.trim(d))});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(", "))},B:function(){var a=this;this.f=c(this.a.menuHTML);this.f.attr({role:"menu"}).on("keyup.multiselect",function(b){27===b.which&&a.j()}).appendTo(this.c);this.D();this.a.presets&&this.F()},D:function(){var a=this;this.h=c(this.a.menuItemsHTML);this.f.append(this.h);this.b.on("change.multiselect",function(b,d){!0!==d&&a.s()});this.s()},s:function(){var a=this;this.h.empty();this.b.children("option").each(function(b,d){b=a.C(c(d),b);a.h.append(b)})}, -F:function(){var a=this;this.i=c(this.a.presetsHTML);this.f.prepend(this.i);c.each(this.a.presets,function(b,d){b=a.b.attr("name")+"_preset_"+b;var h=c(a.a.menuItemHTML).attr({"for":b,role:"menuitem"}).text(" "+d.name).appendTo(a.i);c("<input>").attr({type:"radio",name:a.b.attr("name")+"_presets",id:b}).prependTo(h).on("change.multiselect",function(){a.b.val(d.options);a.b.trigger("change")})});this.b.on("change.multiselect",function(){a.u()});this.u()},u:function(){var a=this;c.each(this.a.presets, -function(b,d){b=a.b.attr("name")+"_preset_"+b;b=a.i.find("#"+b);a:{d=d.options||[];var c=a.b.val()||[];if(d.length!=c.length)d=!1;else{d.sort();c.sort();for(var e=0;e<d.length;e++)if(d[e]!==c[e]){d=!1;break a}d=!0}}d?b.prop("checked",!0):b.prop("checked",!1)})},C:function(a,b){var d=this.b.attr("name")+"_"+b;b=c(this.a.menuItemHTML).attr({"for":d,role:"menuitem"}).text(" "+a.text());d=c("<input>").attr({type:"checkbox",id:d,value:a.val()}).prependTo(b);a.is(":disabled")&&d.attr("disabled","disabled"); -a.is(":selected")&&d.prop("checked","checked");d.on("change.multiselect",function(){c(this).prop("checked")?a.prop("selected",!0):a.prop("selected",!1);a.trigger("change",[!0])});return b},J:function(){var a=this;c("html").on("click.multiselect",function(){a.j()});this.c.on("click.multiselect",function(a){a.stopPropagation()})},K:function(){var a=this;this.l.on("click.multiselect",function(b){b.preventDefault();b.stopPropagation();a.m()})},I:function(){c("html").trigger("click.multiselect");this.c.addClass(this.a.activeClass); -if(this.a.positionMenuWithin&&this.a.positionMenuWithin instanceof c){var a=this.f.offset().left+this.f.outerWidth(),b=this.a.positionMenuWithin.offset().left+this.a.positionMenuWithin.outerWidth();a>b&&(this.f.css("width",b-this.f.offset().left),this.c.addClass(this.a.positionedMenuClass))}},j:function(){this.c.removeClass(this.a.activeClass);this.c.removeClass(this.a.positionedMenuClass);this.f.css("width","auto")},m:function(){this.c.hasClass(this.a.activeClass)?this.j():this.I()}});c.fn.multiSelect= -function(a){return this.each(function(){c.data(this,"plugin_multiSelect")||c.data(this,"plugin_multiSelect",new f(this,a))})}})(jQuery); +(function(c){function f(b,a){this.b=c(b);this.a=c.extend({},g,a);this.H()}var g={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">',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};c.extend(f.prototype,{H:function(){this.v();this.G();this.A();this.w();this.B();this.J();this.K();this.b.hide()},v:function(){if(!1===this.b.is("select[multiple]"))throw Error("$.multiSelect only works on <select multiple> elements");},G:function(){this.l=c('label[for="'+this.b.attr("id")+'"]')},A:function(){this.f=c(this.a.containerHTML);this.b.data("multi-select-container",this.f);this.f.insertAfter(this.b)},w:function(){var b= +this;this.g=c(this.a.buttonHTML);this.g.attr({role:"button","aria-haspopup":"true",tabindex:0,"aria-label":this.l.eq(0).text()}).on("keydown.multiselect",function(a){a=a.which;13!==a&&32!==a||b.g.click()}).on("click.multiselect",function(){b.m()}).appendTo(this.f);this.b.on("change.multiselect",function(){b.o()});this.o()},o:function(){var b=[],a=[];this.b.children("option").each(function(){var d=c(this).text();b.push(d);c(this).is(":selected")&&a.push(c.trim(d))});this.g.empty();0==a.length?this.g.text(this.a.noneText): +a.length===b.length&&this.a.allText?this.g.text(this.a.allText):this.g.text(a.join(", "))},B:function(){var b=this;this.c=c(this.a.menuHTML);this.c.attr({role:"menu"}).on("keyup.multiselect",function(a){27===a.which&&b.j()}).appendTo(this.f);this.D();this.a.presets&&this.F()},D:function(){var b=this;this.h=c(this.a.menuItemsHTML);this.c.append(this.h);this.b.on("change.multiselect",function(a,c){!0!==c&&b.s()});this.s()},s:function(){var b=this;this.h.empty();this.b.children("option").each(function(a, +d){a=b.C(c(d),a);b.h.append(a)})},F:function(){var b=this;this.i=c(this.a.presetsHTML);this.c.prepend(this.i);c.each(this.a.presets,function(a,d){a=b.b.attr("name")+"_preset_"+a;var h=c(b.a.menuItemHTML).attr({"for":a,role:"menuitem"}).text(" "+d.name).appendTo(b.i);c("<input>").attr({type:"radio",name:b.b.attr("name")+"_presets",id:a}).prependTo(h).on("change.multiselect",function(){b.b.val(d.options);b.b.trigger("change")})});this.b.on("change.multiselect",function(){b.u()});this.u()},u:function(){var b= +this;c.each(this.a.presets,function(a,c){a=b.b.attr("name")+"_preset_"+a;a=b.i.find("#"+a);a:{c=c.options||[];var d=b.b.val()||[];if(c.length!=d.length)c=!1;else{c.sort();d.sort();for(var e=0;e<c.length;e++)if(c[e]!==d[e]){c=!1;break a}c=!0}}c?a.prop("checked",!0):a.prop("checked",!1)})},C:function(b,a){var d=this.b.attr("name")+"_"+a;a=c(this.a.menuItemHTML).attr({"for":d,role:"menuitem"}).text(" "+b.text());d=c("<input>").attr({type:"checkbox",id:d,value:b.val()}).prependTo(a);b.is(":disabled")&& +d.attr("disabled","disabled");b.is(":selected")&&d.prop("checked","checked");d.on("change.multiselect",function(){c(this).prop("checked")?b.prop("selected",!0):b.prop("selected",!1);b.trigger("change",[!0])});return a},J:function(){var b=this;c("html").on("click.multiselect",function(){b.j()});this.f.on("click.multiselect",function(a){a.stopPropagation()})},K:function(){var b=this;this.l.on("click.multiselect",function(a){a.preventDefault();a.stopPropagation();b.m()})},I:function(){c("html").trigger("click.multiselect"); +this.f.addClass(this.a.activeClass);if(this.a.positionMenuWithin&&this.a.positionMenuWithin instanceof c){var b=this.c.offset().left+this.c.outerWidth(),a=this.a.positionMenuWithin.offset().left+this.a.positionMenuWithin.outerWidth();b>a&&(this.c.css("width",a-this.c.offset().left),this.f.addClass(this.a.positionedMenuClass))}b=this.c.offset().top+this.c.outerHeight();a=c(window).scrollTop()+c(window).height();b>a-this.a.viewportBottomGutter?this.c.css({maxHeight:Math.max(a-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")},m:function(){this.f.hasClass(this.a.activeClass)?this.j():this.I()}});c.fn.multiSelect=function(b){return this.each(function(){c.data(this,"plugin_multiSelect")||c.data(this,"plugin_multiSelect",new f(this,b))})}})(jQuery); |