From 2a69a2c826c91258728a2bd5b4be3316a43a0f35 Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Tue, 13 Aug 2013 16:58:06 +0200 Subject: [PATCH] Bug 9043: Add the multiple select jquery plugin Signed-off-by: Jonathan Druart Signed-off-by: Kyle M Hall Signed-off-by: Tomas Cohen Arazi --- .../jquery/plugins/multiple-select/LICENSE | 21 + .../multiple-select/jquery.multiple.select.js | 589 ++++++++++++++++++ .../multiple-select/multiple-select.css | 191 ++++++ .../intranet-tmpl/prog/en/modules/about.tt | 7 +- .../prog/en/modules/admin/preferences.tt | 16 +- 5 files changed, 814 insertions(+), 10 deletions(-) create mode 100644 koha-tmpl/intranet-tmpl/lib/jquery/plugins/multiple-select/LICENSE create mode 100644 koha-tmpl/intranet-tmpl/lib/jquery/plugins/multiple-select/jquery.multiple.select.js create mode 100644 koha-tmpl/intranet-tmpl/lib/jquery/plugins/multiple-select/multiple-select.css diff --git a/koha-tmpl/intranet-tmpl/lib/jquery/plugins/multiple-select/LICENSE b/koha-tmpl/intranet-tmpl/lib/jquery/plugins/multiple-select/LICENSE new file mode 100644 index 0000000000..c3ebd2b524 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/lib/jquery/plugins/multiple-select/LICENSE @@ -0,0 +1,21 @@ +(The MIT License) + +Copyright (c) 2012-2014 Zhixin Wen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/koha-tmpl/intranet-tmpl/lib/jquery/plugins/multiple-select/jquery.multiple.select.js b/koha-tmpl/intranet-tmpl/lib/jquery/plugins/multiple-select/jquery.multiple.select.js new file mode 100644 index 0000000000..c55fa32d66 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/lib/jquery/plugins/multiple-select/jquery.multiple.select.js @@ -0,0 +1,589 @@ +/** + * @author zhixin wen + * @version 1.1.0 + * + * http://wenzhixin.net.cn/p/multiple-select/ + */ + +(function ($) { + + 'use strict'; + + function MultipleSelect($el, options) { + var that = this, + name = $el.attr('name') || options.name || '' + + $el.parent().hide(); + var elWidth = $el.css("width"); + $el.parent().show(); + if (elWidth=="0px") {elWidth = $el.outerWidth()+20} + + this.$el = $el.hide(); + this.options = options; + this.$parent = $(''); + this.$choice = $(''); + this.$drop = $('
'); + this.$el.after(this.$parent); + this.$parent.append(this.$choice); + this.$parent.append(this.$drop); + + if (this.$el.prop('disabled')) { + this.$choice.addClass('disabled'); + } + this.$parent.css('width', options.width || elWidth); + + if (!this.options.keepOpen) { + $('body').click(function (e) { + if ($(e.target)[0] === that.$choice[0] || + $(e.target).parents('.ms-choice')[0] === that.$choice[0]) { + return; + } + if (($(e.target)[0] === that.$drop[0] || + $(e.target).parents('.ms-drop')[0] !== that.$drop[0]) && + that.options.isOpen) { + that.close(); + } + }); + } + + this.selectAllName = 'name="selectAll' + name + '"'; + this.selectGroupName = 'name="selectGroup' + name + '"'; + this.selectItemName = 'name="selectItem' + name + '"'; + } + + MultipleSelect.prototype = { + constructor: MultipleSelect, + + init: function () { + var that = this, + html = []; + if (this.options.filter) { + html.push( + '' + ); + } + html.push('
    '); + if (this.options.selectAll && !this.options.single) { + html.push( + '
  • ', + '', + '
  • ' + ); + } + $.each(this.$el.children(), function (i, elm) { + html.push(that.optionToHtml(i, elm)); + }); + html.push('
  • ' + this.options.noMatchesFound + '
  • '); + html.push('
'); + this.$drop.html(html.join('')); + + this.$drop.find('ul').css('max-height', this.options.maxHeight + 'px'); + this.$drop.find('.multiple').css('width', this.options.multipleWidth + 'px'); + + this.$searchInput = this.$drop.find('.ms-search input'); + this.$selectAll = this.$drop.find('input[' + this.selectAllName + ']'); + this.$selectGroups = this.$drop.find('input[' + this.selectGroupName + ']'); + this.$selectItems = this.$drop.find('input[' + this.selectItemName + ']:enabled'); + this.$disableItems = this.$drop.find('input[' + this.selectItemName + ']:disabled'); + this.$noResults = this.$drop.find('.ms-no-results'); + this.events(); + this.updateSelectAll(true); + this.update(true); + + if (this.options.isOpen) { + this.open(); + } + }, + + optionToHtml: function (i, elm, group, groupDisabled) { + var that = this, + $elm = $(elm), + html = [], + multiple = this.options.multiple, + optAttributesToCopy = ['class', 'title'], + clss = $.map(optAttributesToCopy, function (att, i) { + var isMultiple = att === 'class' && multiple; + var attValue = $elm.attr(att) || ''; + return (isMultiple || attValue) ? + (' ' + att + '="' + (isMultiple ? ('multiple' + (attValue ? ' ' : '')) : '') + attValue + '"') : + ''; + }).join(''), + disabled, + type = this.options.single ? 'radio' : 'checkbox'; + + if ($elm.is('option')) { + var value = $elm.val(), + text = that.options.textTemplate($elm), + selected = (that.$el.attr('multiple') != undefined) ? $elm.prop('selected') : ($elm.attr('selected') == 'selected'), + style = this.options.styler(value) ? ' style="' + this.options.styler(value) + '"' : ''; + + disabled = groupDisabled || $elm.prop('disabled'); + if ((this.options.blockSeparator > "") && (this.options.blockSeparator == $elm.val())) { + html.push( + '', + '', + '' + ); + } else { + html.push( + '', + '', + ' ', + text, + '', + '' + ); + } + } else if (!group && $elm.is('optgroup')) { + var _group = 'group_' + i, + label = $elm.attr('label'); + + disabled = $elm.prop('disabled'); + html.push( + '
  • ', + '', + '
  • '); + $.each($elm.children(), function (i, elm) { + html.push(that.optionToHtml(i, elm, _group, disabled)); + }); + } + return html.join(''); + }, + + events: function () { + var that = this; + + function toggleOpen(e) { + e.preventDefault(); + that[that.options.isOpen ? 'close' : 'open'](); + } + + var label = this.$el.parent().closest('label')[0] || $('label[for=' + this.$el.attr('id') + ']')[0]; + if (label) { + $(label).off('click').on('click', function (e) { + if (e.target.nodeName.toLowerCase() !== 'label' || e.target !== this) { + return; + } + toggleOpen(e); + if (!that.options.filter || !that.options.isOpen) { + that.focus(); + } + e.stopPropagation(); // Causes lost focus otherwise + }); + } + this.$choice.off('click').on('click', toggleOpen) + .off('focus').on('focus', this.options.onFocus) + .off('blur').on('blur', this.options.onBlur); + + this.$parent.off('keydown').on('keydown', function (e) { + switch (e.which) { + case 27: // esc key + that.close(); + that.$choice.focus(); + break; + } + }); + this.$searchInput.off('keydown').on('keydown',function (e) { + if (e.keyCode === 9 && e.shiftKey) { // Ensure shift-tab causes lost focus from filter as with clicking away + that.close(); + } + }).off('keyup').on('keyup', function (e) { + if (that.options.filterAcceptOnEnter && + (e.which === 13 || e.which == 32) && // enter or space + that.$searchInput.val() // Avoid selecting/deselecting if no choices made + ) { + that.$selectAll.click(); + that.close(); + that.focus(); + return; + } + that.filter(); + }); + this.$selectAll.off('click').on('click', function () { + var checked = $(this).prop('checked'), + $items = that.$selectItems.filter(':visible'); + if ($items.length === that.$selectItems.length) { + that[checked ? 'checkAll' : 'uncheckAll'](); + } else { // when the filter option is true + that.$selectGroups.prop('checked', checked); + $items.prop('checked', checked); + that.options[checked ? 'onCheckAll' : 'onUncheckAll'](); + that.update(); + } + }); + this.$selectGroups.off('click').on('click', function () { + var group = $(this).parent().attr('data-group'), + $items = that.$selectItems.filter(':visible'), + $children = $items.filter('[data-group="' + group + '"]'), + checked = $children.length !== $children.filter(':checked').length; + $children.prop('checked', checked); + that.updateSelectAll(); + that.update(); + that.options.onOptgroupClick({ + label: $(this).parent().text(), + checked: checked, + children: $children.get() + }); + }); + this.$selectItems.off('click').on('click', function () { + that.updateSelectAll(); + that.update(); + that.updateOptGroupSelect(); + that.options.onClick({ + label: $(this).parent().text(), + value: $(this).val(), + checked: $(this).prop('checked') + }); + + if (that.options.single && that.options.isOpen && !that.options.keepOpen) { + that.close(); + } + }); + }, + + open: function () { + if (this.$choice.hasClass('disabled')) { + return; + } + this.options.isOpen = true; + this.$choice.find('>div').addClass('open'); + this.$drop.show(); + + // fix filter bug: no results show + this.$selectAll.parent().show(); + this.$noResults.hide(); + + // Fix #77: 'All selected' when no options + if (this.$el.children().length === 0) { + this.$selectAll.parent().hide(); + this.$noResults.show(); + } + + if (this.options.container) { + var offset = this.$drop.offset(); + this.$drop.appendTo($(this.options.container)); + this.$drop.offset({ top: offset.top, left: offset.left }); + } + if (this.options.filter) { + this.$searchInput.val(''); + this.$searchInput.focus(); + this.filter(); + } + this.options.onOpen(); + }, + + close: function () { + this.options.isOpen = false; + this.$choice.find('>div').removeClass('open'); + this.$drop.hide(); + if (this.options.container) { + this.$parent.append(this.$drop); + this.$drop.css({ + 'top': 'auto', + 'left': 'auto' + }); + } + this.options.onClose(); + }, + + update: function (isInit) { + var selects = this.getSelects(), + $span = this.$choice.find('>span'); + + if (selects.length === 0) { + $span.addClass('placeholder').html(this.options.placeholder); + } else if (this.options.countSelected && selects.length < this.options.minumimCountSelected) { + $span.removeClass('placeholder').html( + (this.options.displayValues ? selects : this.getSelects('text')) + .join(this.options.delimiter)); + } else if (this.options.allSelected && + selects.length === this.$selectItems.length + this.$disableItems.length) { + $span.removeClass('placeholder').html(this.options.allSelected); + } else if ((this.options.countSelected || this.options.etcaetera) && selects.length > this.options.minumimCountSelected) { + if (this.options.etcaetera) { + $span.removeClass('placeholder').html((this.options.displayValues ? selects : this.getSelects('text').slice(0, this.options.minumimCountSelected)).join(this.options.delimiter) + '...'); + } + else { + $span.removeClass('placeholder').html(this.options.countSelected + .replace('#', selects.length) + .replace('%', this.$selectItems.length + this.$disableItems.length)); + } + } else { + $span.removeClass('placeholder').html( + (this.options.displayValues ? selects : this.getSelects('text')) + .join(this.options.delimiter)); + } + // set selects to select + this.$el.val(this.getSelects()); + + // add selected class to selected li + this.$drop.find('li').removeClass('selected'); + this.$drop.find('input[' + this.selectItemName + ']:checked').each(function () { + $(this).parents('li').first().addClass('selected'); + }); + + // trigger