diff options
Diffstat (limited to 'web/js')
-rw-r--r-- | web/js/jquery.multi-select.js | 256 | ||||
-rw-r--r-- | web/js/map-OpenLayers.js | 44 |
2 files changed, 291 insertions, 9 deletions
diff --git a/web/js/jquery.multi-select.js b/web/js/jquery.multi-select.js new file mode 100644 index 000000000..a41190e9c --- /dev/null +++ b/web/js/jquery.multi-select.js @@ -0,0 +1,256 @@ +// jquery.multi-select.js +// by mySociety +// https://github.com/mysociety/jquery-multi-select + +;(function($) { + + "use strict"; + + var pluginName = "multiSelect", + defaults = { + containerHTML: '<div class="multi-select-container">', + menuHTML: '<div class="multi-select-menu">', + buttonHTML: '<span class="multi-select-button">', + menuItemHTML: '<label class="multi-select-menuitem">', + activeClass: 'multi-select-container--open', + noneText: '-- Select --', + allText: undefined, + positionedMenuClass: 'multi-select-container--positioned', + positionMenuWithin: undefined + }; + + function Plugin(element, options) { + this.element = element; + this.$element = $(element); + this.settings = $.extend( {}, defaults, options ); + this._defaults = defaults; + this._name = pluginName; + this.init(); + } + + $.extend(Plugin.prototype, { + + init: function() { + this.checkSuitableInput(); + this.findLabels(); + this.constructContainer(); + this.constructButton(); + this.constructMenu(); + + this.setUpBodyClickListener(); + this.setUpLabelsClickListener(); + + this.$element.hide(); + }, + + checkSuitableInput: function(text) { + if ( this.$element.is('select[multiple]') === false ) { + throw new Error('$.multiSelect only works on <select multiple> elements'); + } + }, + + findLabels: function() { + this.$labels = $('label[for="' + this.$element.attr('id') + '"]'); + }, + + constructContainer: function() { + this.$container = $(this.settings.containerHTML); + this.$element.data('multi-select-container', this.$container); + this.$container.insertAfter(this.$element); + }, + + constructButton: function() { + var _this = this; + this.$button = $(this.settings.buttonHTML); + this.$button.attr({ + 'role': 'button', + 'aria-haspopup': 'true', + 'tabindex': 0, + 'aria-label': this.$labels.eq(0).text() + }) + .on('keydown.multiselect', function(e) { + var key = e.which; + var returnKey = 13; + var spaceKey = 32; + if ((key === returnKey) || (key === spaceKey)) { + _this.$button.click(); + } + }).on('click.multiselect', function(e) { + _this.menuToggle(); + }); + + this.$element.on('change.multiselect', function() { + _this.updateButtonContents(); + }); + + this.$container.append(this.$button); + + this.updateButtonContents(); + }, + + constructMenu: function() { + var _this = this; + + this.$menu = $(this.settings.menuHTML); + this.$menu.attr({ + 'role': 'menu' + }).on('keyup.multiselect', function(e){ + var key = e.which; + var escapeKey = 27; + if (key === escapeKey) { + _this.menuHide(); + } + }); + + this.$menu.on('change.multiselect', function() { + _this.updateButtonContents(); + }); + + this.$element.on('change.multiselect', function(e, internal) { + // Don't need to update the menu contents if this + // change event was fired by our tickbox handler. + if(internal !== true){ + _this.updateMenuContents(); + } + }); + + this.$container.append(this.$menu); + + this.updateMenuContents(); + }, + + setUpBodyClickListener: function() { + var _this = this; + + // Hide the $menu when you click outside of it. + $('html').on('click.multiselect', function(){ + _this.menuHide(); + }); + + // Stop click events from inside the $button or $menu from + // bubbling up to the body and closing the menu! + this.$container.on('click.multiselect', function(e){ + e.stopPropagation(); + }); + }, + + setUpLabelsClickListener: function() { + var _this = this; + this.$labels.on('click.multiselect', function(e) { + e.preventDefault(); + e.stopPropagation(); + _this.menuToggle(); + }); + }, + + updateMenuContents: function() { + var _this = this; + this.$menu.empty(); + this.$element.children('option').each(function(option_index, option) { + var $item = _this.constructMenuItem($(option), option_index); + _this.$menu.append($item); + }); + }, + + constructMenuItem: function($option, option_index) { + var unique_id = this.$element.attr('name') + '_' + option_index; + var $item = $(this.settings.menuItemHTML) + .attr({ + 'for': unique_id, + 'role': 'menuitem' + }) + .text(' ' + $option.text()); + + var $input = $('<input>') + .attr({ + 'type': 'checkbox', + 'id': unique_id, + 'value': $option.val() + }); + if ( $option.is(':disabled') ) { + $input.attr('disabled', 'disabled'); + } + if ( $option.is(':selected') ) { + $input.prop('checked', 'checked'); + } + + $input.on('change.multiselect', function() { + if ($(this).prop('checked')) { + $option.prop('selected', true); + } else { + $option.prop('selected', false); + } + + // .prop() on its own doesn't generate a change event. + // Other plugins might want to do stuff onChange. + $option.trigger('change', [true]); + }); + + $item.prepend($input); + return $item; + }, + + updateButtonContents: function() { + var _this = this; + var options = []; + var selected = []; + + this.$element.children('option').each(function() { + var text = $(this).text(); + options.push(text); + if ($(this).is(':selected')) { + selected.push( $.trim(text) ); + } + }); + + this.$button.empty(); + + if (selected.length == 0) { + this.$button.text( this.settings.noneText ); + } else if ( (selected.length === options.length) && this.settings.allText) { + this.$button.text( this.settings.allText ); + } else { + this.$button.text( selected.join(', ') ); + } + }, + + menuShow: function() { + this.$container.addClass(this.settings.activeClass); + if (this.settings.positionMenuWithin && this.settings.positionMenuWithin instanceof $) { + var menuLeftEdge = this.$menu.offset().left + this.$menu.outerWidth(); + var withinLeftEdge = this.settings.positionMenuWithin.offset().left + + this.settings.positionMenuWithin.outerWidth(); + + if( menuLeftEdge > withinLeftEdge ) { + this.$menu.css( 'width', (withinLeftEdge - this.$menu.offset().left) ); + this.$container.addClass(this.settings.positionedMenuClass); + } + } + }, + + menuHide: function() { + this.$container.removeClass(this.settings.activeClass); + this.$container.removeClass(this.settings.positionedMenuClass); + this.$menu.css('width', 'auto'); + }, + + menuToggle: function() { + if ( this.$container.hasClass(this.settings.activeClass) ) { + this.menuHide(); + } else { + this.menuShow(); + } + } + + }); + + $.fn[ pluginName ] = function(options) { + return this.each(function() { + if ( !$.data(this, "plugin_" + pluginName) ) { + $.data(this, "plugin_" + pluginName, + new Plugin(this, options) ); + } + }); + }; + +})(jQuery); diff --git a/web/js/map-OpenLayers.js b/web/js/map-OpenLayers.js index f6b2c879b..7d264860f 100644 --- a/web/js/map-OpenLayers.js +++ b/web/js/map-OpenLayers.js @@ -270,6 +270,36 @@ var fixmystreet = fixmystreet || {}; fixmystreet.markers.refresh({force: true}); } + function parse_query_string() { + var qs = {}; + location.search.substring(1).split('&').forEach(function(i) { + var s = i.split('='), + k = s[0], + v = s[1] && decodeURIComponent(s[1].replace(/\+/g, ' ')); + qs[k] = v; + }); + return qs; + } + + function replace_query_parameter(qs, id, key) { + var value = $('#' + id).val(); + value ? qs[key] = value.join(',') : delete qs[key]; + return value; + } + + function categories_or_status_changed_history() { + if (!('pushState' in history)) { + return; + } + var qs = parse_query_string(); + var filter_categories = replace_query_parameter(qs, 'filter_categories', 'filter_category'); + var filter_statuses = replace_query_parameter(qs, 'statuses', 'status'); + var new_url = location.href.replace(location.search, '?' + $.param(qs)); + history.pushState({ + filter_change: { 'filter_categories': filter_categories, 'statuses': filter_statuses } + }, null, new_url); + } + function setup_inspector_marker_drag() { // On the 'inspect report' page the pin is draggable, so we need to // update the easting/northing fields when it's dragged. @@ -433,15 +463,11 @@ var fixmystreet = fixmystreet || {}; fixmystreet.select_feature.activate(); fixmystreet.map.events.register( 'zoomend', null, fixmystreet.maps.markers_resize ); - // If the category filter dropdown exists on the page set up the - // event handlers to populate it and react to it changing - if ($("select#filter_categories").length) { - $("body").on("change", "#filter_categories", categories_or_status_changed); - } - // Do the same for the status dropdown - if ($("select#statuses").length) { - $("body").on("change", "#statuses", categories_or_status_changed); - } + // Set up the event handlers to populate the filters and react to them changing + $("#filter_categories").on("change.filters", categories_or_status_changed); + $("#statuses").on("change.filters", categories_or_status_changed); + $("#filter_categories").on("change.user", categories_or_status_changed_history); + $("#statuses").on("change.user", categories_or_status_changed_history); } else if (fixmystreet.page == 'new') { drag.activate(); } |