PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 5.0.09
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v5.0.09
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
← All changes | js/bootstrap-multiselect.js +1724 -3 6.275.0.09 View file →
@@ -1,9 +1,9 @@
1 1 /**
2 2 * Bootstrap Multiselect (http://davidstutz.de/bootstrap-multiselect/)
3 3 *
4 4 * Apache License, Version 2.0:
5 - * Copyright (c) 2012 - 2022 David Stutz
5 + * Copyright (c) 2012 - 2018 David Stutz
6 6 *
7 7 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
8 8 * use this file except in compliance with the License. You may obtain a
9 9 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0
@@ -14,9 +14,9 @@
14 14 * License for the specific language governing permissions and limitations
15 15 * under the License.
16 16 *
17 17 * BSD 3-Clause License:
18 - * Copyright (c) 2012 - 2022 David Stutz
18 + * Copyright (c) 2012 - 2018 David Stutz
19 19 * All rights reserved.
20 20 *
21 21 * Redistribution and use in source and binary forms, with or without
22 22 * modification, are permitted provided that the following conditions are met:
@@ -40,5 +40,1726 @@
40 40 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
41 41 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42 42 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43 43 */
44 -!function(t,e){"function"==typeof define&&define.amd&&"function"==typeof require&&"function"==typeof require.specified&&require.specified("knockout")?define(["jquery","knockout"],e):e(t.jQuery,t.ko)}(this,(function(t,e){"use strict";void 0!==e&&e.bindingHandlers&&!e.bindingHandlers.multiselect&&(e.bindingHandlers.multiselect={after:["options","value","selectedOptions","enable","disable"],init:function(i,s,o,n,l){var p=t(i),a=e.toJS(s());if(p.multiselect(a),o.has("options")){var r=o.get("options");e.isObservable(r)&&e.computed({read:function(){r(),setTimeout((function(){var t=p.data("multiselect");t&&t.updateOriginalOptions(),p.multiselect("rebuild")}),1)},disposeWhenNodeIsRemoved:i})}if(o.has("value")){var c=o.get("value");e.isObservable(c)&&e.computed({read:function(){c(),setTimeout((function(){p.multiselect("refresh")}),1)},disposeWhenNodeIsRemoved:i}).extend({rateLimit:100,notifyWhenChangesStop:!0})}if(o.has("selectedOptions")){var h=o.get("selectedOptions");e.isObservable(h)&&e.computed({read:function(){h(),setTimeout((function(){p.multiselect("refresh")}),1)},disposeWhenNodeIsRemoved:i}).extend({rateLimit:100,notifyWhenChangesStop:!0})}var u=function(t){setTimeout((function(){t?p.multiselect("enable"):p.multiselect("disable")}))};if(o.has("enable")){var d=o.get("enable");e.isObservable(d)?e.computed({read:function(){u(d())},disposeWhenNodeIsRemoved:i}).extend({rateLimit:100,notifyWhenChangesStop:!0}):u(d)}if(o.has("disable")){var f=o.get("disable");e.isObservable(f)?e.computed({read:function(){u(!f())},disposeWhenNodeIsRemoved:i}).extend({rateLimit:100,notifyWhenChangesStop:!0}):u(!f)}e.utils.domNodeDisposal.addDisposeCallback(i,(function(){p.multiselect("destroy")}))},update:function(i,s,o,n,l){var p=t(i),a=e.toJS(s());p.multiselect("setOptions",a),p.multiselect("rebuild")}});function i(e,i){this.$select=t(e),this.options=this.mergeOptions(t.extend({},i,this.$select.data())),this.$select.attr("data-placeholder")&&(this.options.nonSelectedText=this.$select.data("placeholder")),this.originalOptions=this.$select.clone()[0].options,this.query="",this.searchTimeout=null,this.lastToggledInput=null,this.multiselectId=this.generateUniqueId()+"_0",this.internalIdCount=0,this.options.multiple="multiple"===this.$select.attr("multiple"),this.options.onChange=t.proxy(this.options.onChange,this),this.options.onSelectAll=t.proxy(this.options.onSelectAll,this),this.options.onDeselectAll=t.proxy(this.options.onDeselectAll,this),this.options.onDropdownShow=t.proxy(this.options.onDropdownShow,this),this.options.onDropdownHide=t.proxy(this.options.onDropdownHide,this),this.options.onDropdownShown=t.proxy(this.options.onDropdownShown,this),this.options.onDropdownHidden=t.proxy(this.options.onDropdownHidden,this),this.options.onInitialized=t.proxy(this.options.onInitialized,this),this.options.onFiltering=t.proxy(this.options.onFiltering,this),this.buildContainer(),this.buildButton(),this.buildDropdown(),this.buildReset(),this.buildSelectAll(),this.buildDropdownOptions(),this.buildFilter(),this.buildButtons(),this.updateButtonText(),this.updateSelectAll(!0),this.options.enableClickableOptGroups&&this.options.multiple&&this.updateOptGroups(),this.options.wasDisabled=this.$select.prop("disabled"),this.options.disableIfEmpty&&t("option",this.$select).length<=0&&!this.options.wasDisabled&&this.disable(!0),this.$select.wrap('<span class="multiselect-native-select" />').after(this.$container),this.$select.prop("tabindex","-1"),"never"!==this.options.widthSynchronizationMode&&this.synchronizeButtonAndPopupWidth(),this.$select.data("multiselect",this),this.options.onInitialized(this.$select,this.$container)}i.prototype={defaults:{buttonText:function(e,i){if(this.disabledText.length>0&&i.prop("disabled"))return this.disabledText;if(0===e.length)return this.nonSelectedText;if(this.allSelectedText&&e.length===t("option",t(i)).length&&1!==t("option",t(i)).length&&this.multiple)return this.selectAllNumber?this.allSelectedText+" ("+e.length+")":this.allSelectedText;if(0!=this.numberDisplayed&&e.length>this.numberDisplayed)return e.length+" "+this.nSelectedText;var s="",o=this.delimiterText;return e.each((function(){var e=void 0!==t(this).attr("label")?t(this).attr("label"):t(this).text();s+=e+o})),s.substr(0,s.length-this.delimiterText.length)},buttonTitle:function(e,i){if(0===e.length)return this.nonSelectedText;var s="",o=this.delimiterText;return e.each((function(){var e=void 0!==t(this).attr("label")?t(this).attr("label"):t(this).text();s+=e+o})),s.substr(0,s.length-this.delimiterText.length)},checkboxName:function(t){return!1},optionLabel:function(e){return t(e).attr("label")||t(e).text()},optionClass:function(e){return t(e).attr("class")||""},onChange:function(t,e){},onDropdownShow:function(t){},onDropdownHide:function(t){},onDropdownShown:function(t){},onDropdownHidden:function(t){},onSelectAll:function(t){},onDeselectAll:function(t){},onInitialized:function(t,e){},onFiltering:function(t){},enableHTML:!1,buttonClass:"custom-select",inheritClass:!1,buttonWidth:"auto",buttonContainer:'<div class="btn-group" />',dropRight:!1,dropUp:!1,selectedClass:"active",maxHeight:null,includeSelectAllOption:!1,includeSelectAllIfMoreThan:0,selectAllText:" Select all",selectAllValue:"multiselect-all",selectAllName:!1,selectAllNumber:!0,selectAllJustVisible:!0,enableFiltering:!1,enableCaseInsensitiveFiltering:!1,enableFullValueFiltering:!1,enableClickableOptGroups:!1,enableCollapsibleOptGroups:!1,collapseOptGroupsByDefault:!1,filterPlaceholder:"Search",filterBehavior:"text",includeFilterClearBtn:!0,preventInputChangeEvent:!1,nonSelectedText:"None selected",nSelectedText:"selected",allSelectedText:"All selected",resetButtonText:"Reset",numberDisplayed:3,disableIfEmpty:!1,disabledText:"",delimiterText:", ",includeResetOption:!1,includeResetDivider:!1,resetText:"Reset",indentGroupOptions:!0,widthSynchronizationMode:"never",buttonTextAlignment:"center",enableResetButton:!1,templates:{button:'<button type="button" class="multiselect form-select dropdown-toggle" data-bs-toggle="dropdown"><span class="multiselect-selected-text"></span></button>',popupContainer:'<div class="multiselect-container dropdown-menu"></div>',filter:'<div class="multiselect-filter d-flex align-items-center"><i class="fas fa-sm fa-search text-body-secondary"></i><input type="search" class="multiselect-search form-control" /></div>',buttonGroup:'<div class="multiselect-buttons btn-group" style="display:flex;"></div>',buttonGroupReset:'<button type="button" class="multiselect-reset btn btn-secondary w-100"></button>',option:'<button type="button" class="multiselect-option dropdown-item"></button>',divider:'<div class="dropdown-divider"></div>',optionGroup:'<button type="button" class="multiselect-group dropdown-item"></button>',resetButton:'<div class="multiselect-reset text-center p-2"><button type="button" class="btn btn-sm w-100 btn-outline-secondary"></button></div>'}},constructor:i,buildContainer:function(){this.$container=t(this.options.buttonContainer),"never"!==this.options.widthSynchronizationMode?this.$container.on("show.bs.dropdown",t.proxy((function(){this.synchronizeButtonAndPopupWidth(),this.options.onDropdownShow()}),this)):this.$container.on("show.bs.dropdown",this.options.onDropdownShow),this.$container.on("hide.bs.dropdown",this.options.onDropdownHide),this.$container.on("shown.bs.dropdown",this.options.onDropdownShown),this.$container.on("hidden.bs.dropdown",this.options.onDropdownHidden)},buildButton:function(){if(this.$button=t(this.options.templates.button).addClass(this.options.buttonClass),this.$select.attr("class")&&this.options.inheritClass&&this.$button.addClass(this.$select.attr("class")),this.$select.prop("disabled")?this.disable():this.enable(),this.options.buttonWidth&&"auto"!==this.options.buttonWidth&&(this.$button.css({width:"100%"}),this.$container.css({width:this.options.buttonWidth})),this.options.buttonTextAlignment)switch(this.options.buttonTextAlignment){case"left":this.$button.addClass("text-start");break;case"center":this.$button.addClass("text-center");break;case"right":this.$button.addClass("text-end")}var e=this.$select.attr("tabindex");e&&this.$button.attr("tabindex",e),this.$container.prepend(this.$button)},buildDropdown:function(){this.$popupContainer=t(this.options.templates.popupContainer),this.options.dropRight?this.$container.addClass("dropend"):this.options.dropUp&&this.$container.addClass("dropup"),this.options.maxHeight&&this.$popupContainer.css({"max-height":this.options.maxHeight+"px","overflow-y":"auto","overflow-x":"hidden"}),"never"!==this.options.widthSynchronizationMode&&this.$popupContainer.css("overflow-x","hidden"),this.$popupContainer.on("touchstart click",(function(t){t.stopPropagation()})),this.$container.append(this.$popupContainer)},synchronizeButtonAndPopupWidth:function(){if(this.$popupContainer&&"never"!==this.options.widthSynchronizationMode){var t=this.$button.outerWidth();switch(this.options.widthSynchronizationMode){case"always":this.$popupContainer.css("min-width",t),this.$popupContainer.css("max-width",t);break;case"ifPopupIsSmaller":this.$popupContainer.css("min-width",t);break;case"ifPopupIsWider":this.$popupContainer.css("max-width",t)}}},buildDropdownOptions:function(){if(this.$select.children().each(t.proxy((function(e,i){var s=t(i),o=s.prop("tagName").toLowerCase();s.prop("value")!==this.options.selectAllValue&&("optgroup"===o?this.createOptgroup(i):"option"===o&&("divider"===s.data("role")?this.createDivider():this.createOptionValue(i,!1)))}),this)),t(this.$popupContainer).off("change",'> *:not(.multiselect-group) input[type="checkbox"], > *:not(.multiselect-group) input[type="radio"]'),t(this.$popupContainer).on("change",'> *:not(.multiselect-group) input[type="checkbox"], > *:not(.multiselect-group) input[type="radio"]',t.proxy((function(e){var i=t(e.target),s=i.prop("checked")||!1,o=i.val()===this.options.selectAllValue;this.options.selectedClass&&(s?i.closest(".multiselect-option").addClass(this.options.selectedClass):i.closest(".multiselect-option").removeClass(this.options.selectedClass));var n=i.attr("id"),l=this.getOptionById(n),p=t("option",this.$select).not(l),a=t("input",this.$container).not(i);if(o?s?this.selectAll(this.options.selectAllJustVisible,!0):this.deselectAll(this.options.selectAllJustVisible,!0):(s?(l.prop("selected",!0),this.options.multiple?l.prop("selected",!0):(this.options.selectedClass&&t(a).closest(".dropdown-item").removeClass(this.options.selectedClass),t(a).prop("checked",!1),p.prop("selected",!1),this.$button.click()),"active"===this.options.selectedClass&&p.closest(".dropdown-item").css("outline","")):l.prop("selected",!1),this.options.onChange(l,s),this.updateSelectAll(),this.options.enableClickableOptGroups&&this.options.multiple&&this.updateOptGroups()),this.$select.change(),this.updateButtonText(),this.options.preventInputChangeEvent)return!1}),this)),t(".multiselect-option",this.$popupContainer).off("mousedown"),t(".multiselect-option",this.$popupContainer).on("mousedown",(function(t){if(t.shiftKey)return!1})),t(this.$popupContainer).off("touchstart click",".multiselect-option, .multiselect-all, .multiselect-group"),t(this.$popupContainer).on("touchstart click",".multiselect-option, .multiselect-all, .multiselect-group",t.proxy((function(e){e.stopPropagation();var i=t(e.target);if(e.shiftKey&&this.options.multiple){i.is("input")||(e.preventDefault(),(i=i.closest(".multiselect-option").find("input")).prop("checked",!i.prop("checked")));var s=i.prop("checked")||!1;if(null!==this.lastToggledInput&&this.lastToggledInput!==i){var o=this.$popupContainer.find(".multiselect-option:visible").index(i.closest(".multiselect-option")),n=this.$popupContainer.find(".multiselect-option:visible").index(this.lastToggledInput.closest(".multiselect-option"));if(o>n){var l=n;n=o,o=l}++n;var p=this.$popupContainer.find(".multiselect-option:not(.multiselect-filter-hidden)").slice(o,n).find("input");p.prop("checked",s),this.options.selectedClass&&p.closest(".multiselect-option").toggleClass(this.options.selectedClass,s);for(var a=0,r=p.length;a<r;a++){var c=t(p[a]);this.getOptionById(c.attr("id")).prop("selected",s)}}i.trigger("change")}else if(!i.is("input")){if((c=i.closest(".multiselect-option, .multiselect-all").find(".form-check-input")).length>0)!this.options.multiple&&c.prop("checked")||(c.prop("checked",!c.prop("checked")),c.change());else if(this.options.enableClickableOptGroups&&this.options.multiple&&!i.hasClass("caret-container")){var h=i;h.hasClass("multiselect-group")||(h=i.closest(".multiselect-group")),(c=h.find(".form-check-input")).length>0&&(c.prop("checked",!c.prop("checked")),c.change())}e.preventDefault()}i.closest(".multiselect-option").find("input[type='checkbox'], input[type='radio']").length>0?this.lastToggledInput=i:this.lastToggledInput=null,i.blur()}),this)),this.$container.off("keydown.multiselect").on("keydown.multiselect",t.proxy((function(e){var i=t(this.$container).find(".multiselect-option:not(.disabled), .multiselect-group:not(.disabled), .multiselect-all").filter(":visible"),s=i.index(i.filter(":focus")),o=t(".multiselect-search",this.$container);if(9===e.keyCode&&this.$container.hasClass("show"))this.$button.click();else if(13==e.keyCode){var n=i.eq(s);setTimeout((function(){n.focus()}),1)}else if(38==e.keyCode)0!=s||o.is(":focus")||setTimeout((function(){o.focus()}),1);else if(40==e.keyCode)if(o.is(":focus")){var l=i.eq(0);setTimeout((function(){o.blur(),l.focus()}),1)}else-1==s&&setTimeout((function(){o.focus()}),1)}),this)),this.options.enableClickableOptGroups&&this.options.multiple&&(t(".multiselect-group input",this.$popupContainer).off("change"),t(".multiselect-group input",this.$popupContainer).on("change",t.proxy((function(e){e.stopPropagation();var i=t(e.target).prop("checked")||!1,s=t(e.target).closest(".dropdown-item"),o=s.nextUntil(".multiselect-group").not(".multiselect-filter-hidden").not(".disabled").find("input"),n=[];this.options.selectedClass&&(i?s.addClass(this.options.selectedClass):s.removeClass(this.options.selectedClass)),t.each(o,t.proxy((function(e,s){var o=t(s),l=o.attr("id"),p=this.getOptionById(l);i?(o.prop("checked",!0),o.closest(".dropdown-item").addClass(this.options.selectedClass),p.prop("selected",!0)):(o.prop("checked",!1),o.closest(".dropdown-item").removeClass(this.options.selectedClass),p.prop("selected",!1)),n.push(p)}),this)),this.options.onChange(n,i),this.$select.change(),this.updateButtonText(),this.updateSelectAll()}),this))),this.options.enableCollapsibleOptGroups){let e=this.options.enableClickableOptGroups?".multiselect-group .caret-container":".multiselect-group";t(e,this.$popupContainer).off("click"),t(e,this.$popupContainer).on("click",t.proxy((function(e){var i=t(e.target).closest(".multiselect-group"),s=i.nextUntil(".multiselect-group").not(".multiselect-filter-hidden"),o=!0;s.each((function(){o=o&&!t(this).hasClass("multiselect-collapsible-hidden")})),o?(s.hide().addClass("multiselect-collapsible-hidden"),i.get(0).classList.add("closed")):(s.show().removeClass("multiselect-collapsible-hidden"),i.get(0).classList.remove("closed"))}),this))}},createCheckbox:function(e,i,s,o,n,l,p){var a=t("<span />");a.addClass("form-check");var r=t('<label class="form-check-label" />');this.options.enableHTML&&t(i).length>0?r.html(i):r.text(i),a.append(r);var c=t('<input class="form-check-input"/>').attr("type",l);return c.val(o),a.prepend(c),p&&(c.attr("id",p),r.attr("for",p)),s&&c.attr("name",s),e.prepend(a),e.attr("title",n||i),c},createOptionValue:function(e,i){var s=t(e);s.is(":selected")&&s.prop("selected",!0);var o=this.options.optionLabel(e),n=this.options.optionClass(e),l=s.val(),p=this.options.multiple?"checkbox":"radio",a=s.attr("title"),r=t(this.options.templates.option);r.addClass(n),i&&this.options.indentGroupOptions&&(this.options.enableCollapsibleOptGroups?r.addClass("multiselect-group-option-indented-full"):r.addClass("multiselect-group-option-indented")),this.options.collapseOptGroupsByDefault&&"optgroup"===t(e).parent().prop("tagName").toLowerCase()&&(r.addClass("multiselect-collapsible-hidden"),r.hide());var c=this.options.checkboxName(s),h=this.createAndApplyUniqueId(s),u=this.createCheckbox(r,o,c,l,a,p,h),d=s.prop("selected")||!1;l===this.options.selectAllValue&&(r.addClass("multiselect-all"),r.removeClass("multiselect-option"),u.parent().parent().addClass("multiselect-all")),this.$popupContainer.append(r),s.is(":disabled")&&u.attr("disabled","disabled").prop("disabled",!0).closest(".dropdown-item").addClass("disabled"),u.prop("checked",d),d&&this.options.selectedClass&&u.closest(".dropdown-item").addClass(this.options.selectedClass)},createDivider:function(e){var i=t(this.options.templates.divider);this.$popupContainer.append(i)},createOptgroup:function(e){var i=t(e),s=i.attr("label"),o=i.attr("value"),n=i.attr("title"),l=t("<span class='multiselect-group dropdown-item-text'></span>");if(this.options.enableClickableOptGroups&&this.options.multiple){l=t(this.options.templates.optionGroup);var p=this.createAndApplyUniqueId(i);this.createCheckbox(l,s,null,o,n,"checkbox",p)}else this.options.enableHTML?l.html(" "+s):l.text(" "+s);var a=this.options.optionClass(e);l.addClass(a),this.options.enableCollapsibleOptGroups&&(l.find(".form-check").addClass("d-inline-block"),l.get(0).insertAdjacentHTML("afterbegin",'<span class="caret-container dropdown-toggle"></span>')),i.is(":disabled")&&l.addClass("disabled"),this.$popupContainer.append(l),t("option",e).each(t.proxy((function(t,e){this.createOptionValue(e,!0)}),this))},buildReset:function(){if(this.options.includeResetOption){if(this.options.includeResetDivider){var e=t(this.options.templates.divider);e.addClass("mt-0"),this.$popupContainer.prepend(e)}var i=t(this.options.templates.resetButton);this.options.enableHTML?t("button",i).html(this.options.resetText):t("button",i).text(this.options.resetText),t("button",i).click(t.proxy((function(){this.clearSelection()}),this)),this.$popupContainer.prepend(i)}},buildSelectAll:function(){if("number"==typeof this.options.selectAllValue&&(this.options.selectAllValue=this.options.selectAllValue.toString()),!this.hasSelectAll()&&this.options.includeSelectAllOption&&this.options.multiple&&t("option",this.$select).length>this.options.includeSelectAllIfMoreThan){this.options.includeSelectAllDivider&&this.$popupContainer.prepend(t(this.options.templates.divider));var e=t(this.options.templates.li||this.options.templates.option),i=this.createCheckbox(e,this.options.selectAllText,this.options.selectAllName,this.options.selectAllValue,this.options.selectAllText,"checkbox",this.createAndApplyUniqueId(null));e.addClass("multiselect-all"),e.removeClass("multiselect-option"),e.find(".form-check-label").addClass("font-weight-bold"),this.$popupContainer.prepend(e),i.prop("checked",!1)}},buildFilter:function(){if(this.options.enableFiltering||this.options.enableCaseInsensitiveFiltering){var e=Math.max(this.options.enableFiltering,this.options.enableCaseInsensitiveFiltering);this.$select.find("option").length>=e&&(this.$filter=t(this.options.templates.filter),t("input",this.$filter).attr("placeholder",this.options.filterPlaceholder),this.options.includeFilterClearBtn?(this.isFirefox()&&0===this.$filter.find(".multiselect-clear-filter").length&&this.$filter.append("<i class='fas fa-times text-body-secondary multiselect-clear-filter multiselect-moz-clear-filter'></i>"),this.$filter.find(".multiselect-clear-filter").on("click",t.proxy((function(e){clearTimeout(this.searchTimeout),this.query="",this.$filter.find(".multiselect-search").val(""),t(".dropdown-item",this.$popupContainer).show().removeClass("multiselect-filter-hidden"),this.updateSelectAll(),this.options.enableClickableOptGroups&&this.options.multiple&&this.updateOptGroups()}),this))):(this.$filter.find(".multiselect-search").attr("type","text"),this.$filter.find(".multiselect-clear-filter").remove()),this.$popupContainer.prepend(this.$filter),this.$filter.val(this.query).on("click",(function(t){t.stopPropagation()})).on("input keydown",t.proxy((function(e){13===e.which&&e.preventDefault(),this.isFirefox()&&this.options.includeFilterClearBtn&&(e.target.value?this.$filter.find(".multiselect-moz-clear-filter").show():this.$filter.find(".multiselect-moz-clear-filter").hide()),clearTimeout(this.searchTimeout),this.searchTimeout=this.asyncFunction(t.proxy((function(){var i,s;this.query!==e.target.value&&(this.query=e.target.value,t.each(t(".multiselect-option, .multiselect-group",this.$popupContainer),t.proxy((function(e,o){var n=t("input",o).length>0?t("input",o).val():"",l=t(".form-check-label",o).text(),p="";if("text"===this.options.filterBehavior?p=l:"value"===this.options.filterBehavior?p=n:"both"===this.options.filterBehavior&&(p=l+"\n"+n),n!==this.options.selectAllValue&&l){var a=!1;if(this.options.enableCaseInsensitiveFiltering&&(p=p.toLowerCase(),this.query=this.query.toLowerCase()),this.options.enableFullValueFiltering&&"both"!==this.options.filterBehavior){var r=p.trim().substring(0,this.query.length);this.query.indexOf(r)>-1&&(a=!0)}else p.indexOf(this.query)>-1&&(a=!0);a||(t(o).css("display","none"),t(o).addClass("multiselect-filter-hidden")),a&&(t(o).css("display","block"),t(o).removeClass("multiselect-filter-hidden")),t(o).hasClass("multiselect-group")?(i=o,s=a):(a&&t(i).show().removeClass("multiselect-filter-hidden"),!a&&s&&t(o).show().removeClass("multiselect-filter-hidden"))}}),this)));this.updateSelectAll(),this.options.enableClickableOptGroups&&this.options.multiple&&this.updateOptGroups(),this.updatePopupPosition(),this.options.onFiltering(e.target)}),this),300,this)}),this)))}},buildButtons:function(){if(this.options.enableResetButton){var e=t(this.options.templates.buttonGroup);this.$buttonGroupReset=t(this.options.templates.buttonGroupReset).text(this.options.resetButtonText),e.append(this.$buttonGroupReset),this.$popupContainer.prepend(e),this.defaultSelection={},t("option",this.$select).each(t.proxy((function(e,i){var s=t(i);this.defaultSelection[s.val()]=s.prop("selected")}),this)),this.$buttonGroupReset.on("click",t.proxy((function(e){t("option",this.$select).each(t.proxy((function(e,i){var s=t(i);s.prop("selected",this.defaultSelection[s.val()])}),this)),this.refresh(),this.options.enableFiltering&&(this.$filter.trigger("keydown"),t("input",this.$filter).val(""))}),this))}},updatePopupPosition:function(){var t=this.$popupContainer.css("transform"),e=t.substring(0,t.indexOf("(")),i=t.substring(t.indexOf("(")+1,t.length-1).split(","),s=5;if("matrix3d"===e&&(s=13),!(i.length<s)){var o=i[s];(o=void 0===o?0:o.trim())<0&&(o=-1*this.$popupContainer.css("height").replace("px",""),i[s]=o,t=e+"("+i.join(",")+")",this.$popupContainer.css("transform",t))}},destroy:function(){this.$container.remove(),this.$select.unwrap(),this.$select.show(),this.$select.prop("disabled",this.options.wasDisabled),this.$select.find("option, optgroup").removeAttr("data-multiselectid"),this.$select.data("multiselect",null)},refresh:function(){var e={};t(".multiselect-option input",this.$popupContainer).each((function(){e[t(this).val()]=t(this)})),t("option",this.$select).each(t.proxy((function(i,s){var o=t(s),n=e[t(s).val()];o.is(":selected")?(n.prop("checked",!0),this.options.selectedClass&&n.closest(".multiselect-option").addClass(this.options.selectedClass)):(n.prop("checked",!1),this.options.selectedClass&&n.closest(".multiselect-option").removeClass(this.options.selectedClass)),o.is(":disabled")?n.attr("disabled","disabled").prop("disabled",!0).closest(".multiselect-option").addClass("disabled"):n.prop("disabled",!1).closest(".multiselect-option").removeClass("disabled")}),this)),this.updateButtonText(),this.updateSelectAll(),this.options.enableClickableOptGroups&&this.options.multiple&&this.updateOptGroups()},select:function(e,i){t.isArray(e)||(e=[e]);for(var s=0;s<e.length;s++){var o=e[s];if(null!=o){var n=this.getInputsByValue(o);if(n&&0!==n.length)for(var l=0;l<n.length;++l){var p=n[l],a=this.getOptionById(p.attr("id"));if(void 0!==a){if(this.options.selectedClass&&p.closest(".dropdown-item").addClass(this.options.selectedClass),p.prop("checked",!0),a.prop("selected",!0),!this.options.multiple){var r=t("input",this.$container).not(p);t(r).prop("checked",!1),t(r).closest(".multiselect-option").removeClass("active"),t("option",this.$select).not(a).prop("selected",!1)}i&&this.options.onChange(a,!0)}}}}this.updateButtonText(),this.updateSelectAll(),this.options.enableClickableOptGroups&&this.options.multiple&&this.updateOptGroups()},clearSelection:function(){this.deselectAll(!1),this.updateButtonText(),this.updateSelectAll(),this.options.enableClickableOptGroups&&this.options.multiple&&this.updateOptGroups()},deselect:function(e,i){if(this.options.multiple){t.isArray(e)||(e=[e]);for(var s=0;s<e.length;s++){var o=e[s];if(null!=o){var n=this.getInputsByValue(o);if(n&&0!==n.length)for(var l=0;l<n.length;++l){var p=n[l],a=this.getOptionById(p.attr("id"));a&&(this.options.selectedClass&&p.closest(".dropdown-item").removeClass(this.options.selectedClass),p.prop("checked",!1),a.prop("selected",!1),i&&this.options.onChange(a,!1))}}}this.updateButtonText(),this.updateSelectAll(),this.options.enableClickableOptGroups&&this.options.multiple&&this.updateOptGroups()}},selectAll:function(e,i){if(this.options.multiple){var s=[];if(e=void 0===e||e){var o=t(".multiselect-option:not(.disabled):not(.multiselect-filter-hidden)",this.$popupContainer);t("input:enabled",o).prop("checked",!0),o.addClass(this.options.selectedClass),t("input:enabled",o).each(t.proxy((function(e,i){var o=t(i).attr("id"),n=this.getOptionById(o);t(n).prop("selected")||s.push(n),t(n).prop("selected",!0)}),this))}else{var n=t(".multiselect-option:not(.disabled)",this.$popupContainer);t("input:enabled",n).prop("checked",!0),n.addClass(this.options.selectedClass),t("input:enabled",n).each(t.proxy((function(e,i){var o=t(i).attr("id"),n=this.getOptionById(o);t(n).prop("selected")||s.push(n),t(n).prop("selected",!0)}),this))}t('.multiselect-option input[value="'+this.options.selectAllValue+'"]',this.$popupContainer).prop("checked",!0),this.options.enableClickableOptGroups&&this.options.multiple&&this.updateOptGroups(),this.updateButtonText(),this.updateSelectAll(),i&&this.options.onSelectAll(s)}},deselectAll:function(e,i){if(this.options.multiple){var s=[];if(e=void 0===e||e){var o=t(".multiselect-option:not(.disabled):not(.multiselect-filter-hidden)",this.$popupContainer);t('input[type="checkbox"]:enabled',o).prop("checked",!1),o.removeClass(this.options.selectedClass),t('input[type="checkbox"]:enabled',o).each(t.proxy((function(e,i){var o=t(i).attr("id"),n=this.getOptionById(o);t(n).prop("selected")&&s.push(n),t(n).prop("selected",!1)}),this))}else{var n=t(".multiselect-option:not(.disabled):not(.multiselect-group)",this.$popupContainer);t('input[type="checkbox"]:enabled',n).prop("checked",!1),n.removeClass(this.options.selectedClass),t('input[type="checkbox"]:enabled',n).each(t.proxy((function(e,i){var o=t(i).attr("id"),n=this.getOptionById(o);t(n).prop("selected")&&s.push(n),t(n).prop("selected",!1)}),this))}t('.multiselect-all input[value="'+this.options.selectAllValue+'"]',this.$popupContainer).prop("checked",!1),this.options.enableClickableOptGroups&&this.options.multiple&&this.updateOptGroups(),this.updateButtonText(),this.updateSelectAll(),i&&this.options.onDeselectAll(s)}},rebuild:function(){this.internalIdCount=0,this.$popupContainer.html(""),this.$select.find("option, optgroup").removeAttr("data-multiselectid"),this.options.multiple="multiple"===this.$select.attr("multiple"),this.buildSelectAll(),this.buildDropdownOptions(),this.buildFilter(),this.buildButtons(),this.updateButtonText(),this.updateSelectAll(!0),this.options.enableClickableOptGroups&&this.options.multiple&&this.updateOptGroups(),this.options.disableIfEmpty&&(t("option",this.$select).length<=0?this.$select.prop("disabled")||this.disable(!0):this.$select.data("disabled-by-option")&&this.enable()),this.options.dropRight?this.$container.addClass("dropend"):this.options.dropUp&&this.$container.addClass("dropup"),"never"!==this.options.widthSynchronizationMode&&this.synchronizeButtonAndPopupWidth()},dataprovider:function(e){var i=0,s=this.$select.empty();t.each(e,(function(e,o){var n;if(t.isArray(o.children))i++,n=t("<optgroup/>").attr({label:o.label||"Group "+i,disabled:!!o.disabled,value:o.value}),function(t,e){for(var i=0;i<t.length;++i)e(t[i],i)}(o.children,(function(e){var i={value:e.value,label:void 0!==e.label&&null!==e.label?e.label:e.value,title:e.title,class:e.class,selected:!!e.selected,disabled:!!e.disabled};for(var s in e.attributes)i["data-"+s]=e.attributes[s];n.append(t("<option/>").attr(i))}));else{var l={value:o.value,label:void 0!==o.label&&null!==o.label?o.label:o.value,title:o.title,class:o.class,selected:!!o.selected,disabled:!!o.disabled};for(var p in o.attributes)l["data-"+p]=o.attributes[p];(n=t("<option/>").attr(l)).text(void 0!==o.label&&null!==o.label?o.label:o.value)}s.append(n)})),this.rebuild()},enable:function(){this.$select.prop("disabled",!1),this.$button.prop("disabled",!1).removeClass("disabled"),this.updateButtonText()},disable:function(t){this.$select.prop("disabled",!0),this.$button.prop("disabled",!0).addClass("disabled"),t?this.$select.data("disabled-by-option",!0):this.$select.data("disabled-by-option",null),this.updateButtonText()},setOptions:function(t){this.options=this.mergeOptions(t)},mergeOptions:function(e){return t.extend(!0,{},this.defaults,this.options,e)},hasSelectAll:function(){return t(".multiselect-all",this.$popupContainer).length>0},updateOptGroups:function(){var e=t(".multiselect-group",this.$popupContainer),i=this.options.selectedClass;e.each((function(){var e=t(this).nextUntil(".multiselect-group").not(".multiselect-filter-hidden").not(".disabled"),s=!0;e.each((function(){t("input",this).prop("checked")||(s=!1)})),i&&(s?t(this).addClass(i):t(this).removeClass(i)),t("input",this).prop("checked",s)}))},updateSelectAll:function(e){if(this.hasSelectAll()){var i=t(".multiselect-option:not(.multiselect-filter-hidden):not(.multiselect-group):not(.disabled) input:enabled",this.$popupContainer),s=i.length,o=i.filter(":checked").length,n=t(".multiselect-all",this.$popupContainer),l=n.find("input");o>0&&o===s?(l.prop("checked",!0),n.addClass(this.options.selectedClass)):(l.prop("checked",!1),n.removeClass(this.options.selectedClass))}},updateButtonText:function(){var e=this.getSelected();this.options.enableHTML?t(".multiselect .multiselect-selected-text",this.$container).html(this.options.buttonText(e,this.$select)):t(".multiselect .multiselect-selected-text",this.$container).text(this.options.buttonText(e,this.$select)),t(".multiselect",this.$container).attr("title",this.options.buttonTitle(e,this.$select)),this.$button.trigger("change")},getSelected:function(){return t("option",this.$select).filter(":selected")},getOptionById:function(t){return t?this.$select.find("option[data-multiselectid="+t+"], optgroup[data-multiselectid="+t+"]"):null},getInputsByValue:function(e){for(var i=t(".multiselect-option input:not(.multiselect-search)",this.$popupContainer),s=e.toString(),o=[],n=0;n<i.length;n+=1){var l=i[n];l.value===s&&o.push(t(l))}return o},updateOriginalOptions:function(){this.originalOptions=this.$select.clone()[0].options},asyncFunction:function(t,e,i){var s=Array.prototype.slice.call(arguments,3);return setTimeout((function(){t.apply(i||window,s)}),e)},setAllSelectedText:function(t){this.options.allSelectedText=t,this.updateButtonText()},isFirefox:function(){return!(!navigator||!navigator.userAgent)&&navigator.userAgent.toLocaleLowerCase().indexOf("firefox")>-1},createAndApplyUniqueId:function(t){var e="multiselect_"+this.multiselectId+"_"+this.internalIdCount++;return t&&(t[0].dataset.multiselectid=e),e},generateUniqueId:function(){return Math.random().toString(36).substr(2)}},t.fn.multiselect=function(e,s,o){return this.each((function(){var n=t(this).data("multiselect");n||(n=new i(this,"object"==typeof e&&e)),"string"==typeof e&&n[e](s,o)}))},t.fn.multiselect.Constructor=i,t((function(){t("select[data-role=multiselect]").multiselect()}))}));
44 +!function ($) {
45 + "use strict";// jshint ;_;
46 +
47 + if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) {
48 + ko.bindingHandlers.multiselect = {
49 + after: ['options', 'value', 'selectedOptions', 'enable', 'disable'],
50 +
51 + init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
52 + var $element = $(element);
53 + var config = ko.toJS(valueAccessor());
54 +
55 + $element.multiselect(config);
56 +
57 + if (allBindings.has('options')) {
58 + var options = allBindings.get('options');
59 + if (ko.isObservable(options)) {
60 + ko.computed({
61 + read: function() {
62 + options();
63 + setTimeout(function() {
64 + var ms = $element.data('multiselect');
65 + if (ms)
66 + ms.updateOriginalOptions();//Not sure how beneficial this is.
67 + $element.multiselect('rebuild');
68 + }, 1);
69 + },
70 + disposeWhenNodeIsRemoved: element
71 + });
72 + }
73 + }
74 +
75 + //value and selectedOptions are two-way, so these will be triggered even by our own actions.
76 + //It needs some way to tell if they are triggered because of us or because of outside change.
77 + //It doesn't loop but it's a waste of processing.
78 + if (allBindings.has('value')) {
79 + var value = allBindings.get('value');
80 + if (ko.isObservable(value)) {
81 + ko.computed({
82 + read: function() {
83 + value();
84 + setTimeout(function() {
85 + $element.multiselect('refresh');
86 + }, 1);
87 + },
88 + disposeWhenNodeIsRemoved: element
89 + }).extend({ rateLimit: 100, notifyWhenChangesStop: true });
90 + }
91 + }
92 +
93 + //Switched from arrayChange subscription to general subscription using 'refresh'.
94 + //Not sure performance is any better using 'select' and 'deselect'.
95 + if (allBindings.has('selectedOptions')) {
96 + var selectedOptions = allBindings.get('selectedOptions');
97 + if (ko.isObservable(selectedOptions)) {
98 + ko.computed({
99 + read: function() {
100 + selectedOptions();
101 + setTimeout(function() {
102 + $element.multiselect('refresh');
103 + }, 1);
104 + },
105 + disposeWhenNodeIsRemoved: element
106 + }).extend({ rateLimit: 100, notifyWhenChangesStop: true });
107 + }
108 + }
109 +
110 + var setEnabled = function (enable) {
111 + setTimeout(function () {
112 + if (enable)
113 + $element.multiselect('enable');
114 + else
115 + $element.multiselect('disable');
116 + });
117 + };
118 +
119 + if (allBindings.has('enable')) {
120 + var enable = allBindings.get('enable');
121 + if (ko.isObservable(enable)) {
122 + ko.computed({
123 + read: function () {
124 + setEnabled(enable());
125 + },
126 + disposeWhenNodeIsRemoved: element
127 + }).extend({ rateLimit: 100, notifyWhenChangesStop: true });
128 + } else {
129 + setEnabled(enable);
130 + }
131 + }
132 +
133 + if (allBindings.has('disable')) {
134 + var disable = allBindings.get('disable');
135 + if (ko.isObservable(disable)) {
136 + ko.computed({
137 + read: function () {
138 + setEnabled(!disable());
139 + },
140 + disposeWhenNodeIsRemoved: element
141 + }).extend({ rateLimit: 100, notifyWhenChangesStop: true });
142 + } else {
143 + setEnabled(!disable);
144 + }
145 + }
146 +
147 + ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
148 + $element.multiselect('destroy');
149 + });
150 + },
151 +
152 + update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
153 + var $element = $(element);
154 + var config = ko.toJS(valueAccessor());
155 +
156 + $element.multiselect('setOptions', config);
157 + $element.multiselect('rebuild');
158 + }
159 + };
160 + }
161 +
162 + function forEach(array, callback) {
163 + for (var index = 0; index < array.length; ++index) {
164 + callback(array[index], index);
165 + }
166 + }
167 +
168 + /**
169 + * Constructor to create a new multiselect using the given select.
170 + *
171 + * @param {jQuery} select
172 + * @param {Object} options
173 + * @returns {Multiselect}
174 + */
175 + function Multiselect(select, options) {
176 +
177 + this.$select = $(select);
178 + this.options = this.mergeOptions($.extend({}, options, this.$select.data()));
179 +
180 + // Placeholder via data attributes
181 + if (this.$select.attr("data-placeholder")) {
182 + this.options.nonSelectedText = this.$select.data("placeholder");
183 + }
184 +
185 + // Initialization.
186 + // We have to clone to create a new reference.
187 + this.originalOptions = this.$select.clone()[0].options;
188 + this.query = '';
189 + this.searchTimeout = null;
190 + this.lastToggledInput = null;
191 +
192 + this.options.multiple = this.$select.attr('multiple') === "multiple";
193 + this.options.onChange = $.proxy(this.options.onChange, this);
194 + this.options.onSelectAll = $.proxy(this.options.onSelectAll, this);
195 + this.options.onDeselectAll = $.proxy(this.options.onDeselectAll, this);
196 + this.options.onDropdownShow = $.proxy(this.options.onDropdownShow, this);
197 + this.options.onDropdownHide = $.proxy(this.options.onDropdownHide, this);
198 + this.options.onDropdownShown = $.proxy(this.options.onDropdownShown, this);
199 + this.options.onDropdownHidden = $.proxy(this.options.onDropdownHidden, this);
200 + this.options.onInitialized = $.proxy(this.options.onInitialized, this);
201 + this.options.onFiltering = $.proxy(this.options.onFiltering, this);
202 +
203 + // Build select all if enabled.
204 + this.buildContainer();
205 + this.buildButton();
206 + this.buildDropdown();
207 + this.buildReset();
208 + this.buildSelectAll();
209 + this.buildDropdownOptions();
210 + this.buildFilter();
211 +
212 + this.updateButtonText();
213 + this.updateSelectAll(true);
214 +
215 + if (this.options.enableClickableOptGroups && this.options.multiple) {
216 + this.updateOptGroups();
217 + }
218 +
219 + this.options.wasDisabled = this.$select.prop('disabled');
220 + if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) {
221 + this.disable();
222 + }
223 +
224 + this.$select.wrap('<span class="multiselect-native-select" />').after(this.$container);
225 + this.options.onInitialized(this.$select, this.$container);
226 + }
227 +
228 + Multiselect.prototype = {
229 +
230 + defaults: {
231 + /**
232 + * Default text function will either print 'None selected' in case no
233 + * option is selected or a list of the selected options up to a length
234 + * of 3 selected options.
235 + *
236 + * @param {jQuery} options
237 + * @param {jQuery} select
238 + * @returns {String}
239 + */
240 + buttonText: function(options, select) {
241 + if (this.disabledText.length > 0
242 + && (select.prop('disabled') || (options.length == 0 && this.disableIfEmpty))) {
243 +
244 + return this.disabledText;
245 + }
246 + else if (options.length === 0) {
247 + return this.nonSelectedText;
248 + }
249 + else if (this.allSelectedText
250 + && options.length === $('option', $(select)).length
251 + && $('option', $(select)).length !== 1
252 + && this.multiple) {
253 +
254 + if (this.selectAllNumber) {
255 + return this.allSelectedText + ' (' + options.length + ')';
256 + }
257 + else {
258 + return this.allSelectedText;
259 + }
260 + }
261 + else if (this.numberDisplayed != 0 && options.length > this.numberDisplayed) {
262 + return options.length + ' ' + this.nSelectedText;
263 + }
264 + else {
265 + var selected = '';
266 + var delimiter = this.delimiterText;
267 +
268 + options.each(function() {
269 + var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text();
270 + selected += label + delimiter;
271 + });
272 +
273 + return selected.substr(0, selected.length - this.delimiterText.length);
274 + }
275 + },
276 + /**
277 + * Updates the title of the button similar to the buttonText function.
278 + *
279 + * @param {jQuery} options
280 + * @param {jQuery} select
281 + * @returns {@exp;selected@call;substr}
282 + */
283 + buttonTitle: function(options, select) {
284 + if (options.length === 0) {
285 + return this.nonSelectedText;
286 + }
287 + else {
288 + var selected = '';
289 + var delimiter = this.delimiterText;
290 +
291 + options.each(function () {
292 + var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text();
293 + selected += label + delimiter;
294 + });
295 + return selected.substr(0, selected.length - this.delimiterText.length);
296 + }
297 + },
298 + checkboxName: function(option) {
299 + return false; // no checkbox name
300 + },
301 + /**
302 + * Create a label.
303 + *
304 + * @param {jQuery} element
305 + * @returns {String}
306 + */
307 + optionLabel: function(element){
308 + return $(element).attr('label') || $(element).text();
309 + },
310 + /**
311 + * Create a class.
312 + *
313 + * @param {jQuery} element
314 + * @returns {String}
315 + */
316 + optionClass: function(element) {
317 + return $(element).attr('class') || '';
318 + },
319 + /**
320 + * Triggered on change of the multiselect.
321 + *
322 + * Not triggered when selecting/deselecting options manually.
323 + *
324 + * @param {jQuery} option
325 + * @param {Boolean} checked
326 + */
327 + onChange : function(option, checked) {
328 +
329 + },
330 + /**
331 + * Triggered when the dropdown is shown.
332 + *
333 + * @param {jQuery} event
334 + */
335 + onDropdownShow: function(event) {
336 +
337 + },
338 + /**
339 + * Triggered when the dropdown is hidden.
340 + *
341 + * @param {jQuery} event
342 + */
343 + onDropdownHide: function(event) {
344 +
345 + },
346 + /**
347 + * Triggered after the dropdown is shown.
348 + *
349 + * @param {jQuery} event
350 + */
351 + onDropdownShown: function(event) {
352 +
353 + },
354 + /**
355 + * Triggered after the dropdown is hidden.
356 + *
357 + * @param {jQuery} event
358 + */
359 + onDropdownHidden: function(event) {
360 +
361 + },
362 + /**
363 + * Triggered on select all.
364 + */
365 + onSelectAll: function() {
366 +
367 + },
368 + /**
369 + * Triggered on deselect all.
370 + */
371 + onDeselectAll: function() {
372 +
373 + },
374 + /**
375 + * Triggered after initializing.
376 + *
377 + * @param {jQuery} $select
378 + * @param {jQuery} $container
379 + */
380 + onInitialized: function($select, $container) {
381 +
382 + },
383 + /**
384 + * Triggered on filtering.
385 + *
386 + * @param {jQuery} $filter
387 + */
388 + onFiltering: function($filter) {
389 +
390 + },
391 + enableHTML: false,
392 + buttonClass: 'btn btn-default',
393 + inheritClass: false,
394 + buttonWidth: 'auto',
395 + buttonContainer: '<div class="btn-group" />',
396 + dropRight: false,
397 + dropUp: false,
398 + selectedClass: 'active',
399 + // Maximum height of the dropdown menu.
400 + // If maximum height is exceeded a scrollbar will be displayed.
401 + maxHeight: false,
402 + includeSelectAllOption: false,
403 + includeSelectAllIfMoreThan: 0,
404 + selectAllText: ' Select all',
405 + selectAllValue: 'multiselect-all',
406 + selectAllName: false,
407 + selectAllNumber: true,
408 + selectAllJustVisible: true,
409 + enableFiltering: false,
410 + enableCaseInsensitiveFiltering: false,
411 + enableFullValueFiltering: false,
412 + enableClickableOptGroups: false,
413 + enableCollapsibleOptGroups: false,
414 + collapseOptGroupsByDefault: false,
415 + filterPlaceholder: 'Search',
416 + // possible options: 'text', 'value', 'both'
417 + filterBehavior: 'text',
418 + includeFilterClearBtn: true,
419 + preventInputChangeEvent: false,
420 + nonSelectedText: 'None selected',
421 + nSelectedText: 'selected',
422 + allSelectedText: 'All selected',
423 + numberDisplayed: 3,
424 + disableIfEmpty: false,
425 + disabledText: '',
426 + delimiterText: ', ',
427 + includeResetOption: false,
428 + includeResetDivider: false,
429 + resetText: 'Reset',
430 + templates: {
431 + button: '<button type="button" class="multiselect dropdown-toggle" data-toggle="dropdown"><span class="multiselect-selected-text"></span> <b class="caret"></b></button>',
432 + ul: '<ul class="multiselect-container dropdown-menu"></ul>',
433 + filter: '<li class="multiselect-item multiselect-filter"><div class="input-group"><span class="input-group-addon"><i class="glyphicon glyphicon-search"></i></span><input class="form-control multiselect-search" type="text" /></div></li>',
434 + filterClearBtn: '<span class="input-group-btn"><button class="btn btn-default multiselect-clear-filter" type="button"><i class="glyphicon glyphicon-remove-circle"></i></button></span>',
435 + li: '<li><a tabindex="0"><label></label></a></li>',
436 + divider: '<li class="multiselect-item divider"></li>',
437 + liGroup: '<li class="multiselect-item multiselect-group"><label></label></li>',
438 + resetButton: '<li class="multiselect-reset text-center"><div class="input-group"><a class="btn btn-default btn-block"></a></div></li>'
439 + }
440 + },
441 +
442 + constructor: Multiselect,
443 +
444 + /**
445 + * Builds the container of the multiselect.
446 + */
447 + buildContainer: function() {
448 + this.$container = $(this.options.buttonContainer);
449 + this.$container.on('show.bs.dropdown', this.options.onDropdownShow);
450 + this.$container.on('hide.bs.dropdown', this.options.onDropdownHide);
451 + this.$container.on('shown.bs.dropdown', this.options.onDropdownShown);
452 + this.$container.on('hidden.bs.dropdown', this.options.onDropdownHidden);
453 + },
454 +
455 + /**
456 + * Builds the button of the multiselect.
457 + */
458 + buildButton: function() {
459 + this.$button = $(this.options.templates.button).addClass(this.options.buttonClass);
460 + if (this.$select.attr('class') && this.options.inheritClass) {
461 + this.$button.addClass(this.$select.attr('class'));
462 + }
463 + // Adopt active state.
464 + if (this.$select.prop('disabled')) {
465 + this.disable();
466 + }
467 + else {
468 + this.enable();
469 + }
470 +
471 + // Manually add button width if set.
472 + if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') {
473 + this.$button.css({
474 + 'width' : '100%', //this.options.buttonWidth,
475 + 'overflow' : 'hidden',
476 + 'text-overflow' : 'ellipsis'
477 + });
478 + this.$container.css({
479 + 'width': this.options.buttonWidth
480 + });
481 + }
482 +
483 + // Keep the tab index from the select.
484 + var tabindex = this.$select.attr('tabindex');
485 + if (tabindex) {
486 + this.$button.attr('tabindex', tabindex);
487 + }
488 +
489 + this.$container.prepend(this.$button);
490 + },
491 +
492 + /**
493 + * Builds the ul representing the dropdown menu.
494 + */
495 + buildDropdown: function() {
496 +
497 + // Build ul.
498 + this.$ul = $(this.options.templates.ul);
499 +
500 + if (this.options.dropRight) {
501 + this.$ul.addClass('pull-right');
502 + }
503 +
504 + // Set max height of dropdown menu to activate auto scrollbar.
505 + if (this.options.maxHeight) {
506 + // TODO: Add a class for this option to move the css declarations.
507 + this.$ul.css({
508 + 'max-height': this.options.maxHeight + 'px',
509 + 'overflow-y': 'auto',
510 + 'overflow-x': 'hidden'
511 + });
512 + }
513 +
514 + if (this.options.dropUp) {
515 +
516 + var height = Math.min(this.options.maxHeight, $('option[data-role!="divider"]', this.$select).length*26 + $('option[data-role="divider"]', this.$select).length*19 + (this.options.includeSelectAllOption ? 26 : 0) + (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering ? 44 : 0));
517 + var moveCalc = height + 34;
518 +
519 + this.$ul.css({
520 + 'max-height': height + 'px',
521 + 'overflow-y': 'auto',
522 + 'overflow-x': 'hidden',
523 + 'margin-top': "-" + moveCalc + 'px'
524 + });
525 + }
526 +
527 + this.$container.append(this.$ul);
528 + },
529 +
530 + /**
531 + * Build the dropdown options and binds all necessary events.
532 + *
533 + * Uses createDivider and createOptionValue to create the necessary options.
534 + */
535 + buildDropdownOptions: function() {
536 +
537 + this.$select.children().each($.proxy(function(index, element) {
538 +
539 + var $element = $(element);
540 + // Support optgroups and options without a group simultaneously.
541 + var tag = $element.prop('tagName')
542 + .toLowerCase();
543 +
544 + if ($element.prop('value') === this.options.selectAllValue) {
545 + return;
546 + }
547 +
548 + if (tag === 'optgroup') {
549 + this.createOptgroup(element);
550 + }
551 + else if (tag === 'option') {
552 +
553 + if ($element.data('role') === 'divider') {
554 + this.createDivider();
555 + }
556 + else {
557 + this.createOptionValue(element);
558 + }
559 +
560 + }
561 +
562 + // Other illegal tags will be ignored.
563 + }, this));
564 +
565 + // Bind the change event on the dropdown elements.
566 + $(this.$ul).off('change', 'li:not(.multiselect-group) input[type="checkbox"], li:not(.multiselect-group) input[type="radio"]');
567 + $(this.$ul).on('change', 'li:not(.multiselect-group) input[type="checkbox"], li:not(.multiselect-group) input[type="radio"]', $.proxy(function(event) {
568 + var $target = $(event.target);
569 +
570 + var checked = $target.prop('checked') || false;
571 + var isSelectAllOption = $target.val() === this.options.selectAllValue;
572 +
573 + // Apply or unapply the configured selected class.
574 + if (this.options.selectedClass) {
575 + if (checked) {
576 + $target.closest('li')
577 + .addClass(this.options.selectedClass);
578 + }
579 + else {
580 + $target.closest('li')
581 + .removeClass(this.options.selectedClass);
582 + }
583 + }
584 +
585 + // Get the corresponding option.
586 + var value = $target.val();
587 + var $option = this.getOptionByValue(value);
588 +
589 + var $optionsNotThis = $('option', this.$select).not($option);
590 + var $checkboxesNotThis = $('input', this.$container).not($target);
591 +
592 + if (isSelectAllOption) {
593 +
594 + if (checked) {
595 + this.selectAll(this.options.selectAllJustVisible, true);
596 + }
597 + else {
598 + this.deselectAll(this.options.selectAllJustVisible, true);
599 + }
600 + }
601 + else {
602 + if (checked) {
603 + $option.prop('selected', true);
604 +
605 + if (this.options.multiple) {
606 + // Simply select additional option.
607 + $option.prop('selected', true);
608 + }
609 + else {
610 + // Unselect all other options and corresponding checkboxes.
611 + if (this.options.selectedClass) {
612 + $($checkboxesNotThis).closest('li').removeClass(this.options.selectedClass);
613 + }
614 +
615 + $($checkboxesNotThis).prop('checked', false);
616 + $optionsNotThis.prop('selected', false);
617 +
618 + // It's a single selection, so close.
619 + this.$button.click();
620 + }
621 +
622 + if (this.options.selectedClass === "active") {
623 + $optionsNotThis.closest("a").css("outline", "");
624 + }
625 + }
626 + else {
627 + // Unselect option.
628 + $option.prop('selected', false);
629 + }
630 +
631 + // To prevent select all from firing onChange: #575
632 + this.options.onChange($option, checked);
633 +
634 + // Do not update select all or optgroups on select all change!
635 + this.updateSelectAll();
636 +
637 + if (this.options.enableClickableOptGroups && this.options.multiple) {
638 + this.updateOptGroups();
639 + }
640 + }
641 +
642 + this.$select.change();
643 + this.updateButtonText();
644 +
645 + if(this.options.preventInputChangeEvent) {
646 + return false;
647 + }
648 + }, this));
649 +
650 + $('li a', this.$ul).on('mousedown', function(e) {
651 + if (e.shiftKey) {
652 + // Prevent selecting text by Shift+click
653 + return false;
654 + }
655 + });
656 +
657 + $(this.$ul).on('touchstart click', 'li a', $.proxy(function(event) {
658 + event.stopPropagation();
659 +
660 + var $target = $(event.target);
661 +
662 + if (event.shiftKey && this.options.multiple) {
663 + if($target.is("label")){ // Handles checkbox selection manually (see https://github.com/davidstutz/bootstrap-multiselect/issues/431)
664 + event.preventDefault();
665 + $target = $target.find("input");
666 + $target.prop("checked", !$target.prop("checked"));
667 + }
668 + var checked = $target.prop('checked') || false;
669 +
670 + if (this.lastToggledInput !== null && this.lastToggledInput !== $target) { // Make sure we actually have a range
671 + var from = this.$ul.find("li:visible").index($target.parents("li"));
672 + var to = this.$ul.find("li:visible").index(this.lastToggledInput.parents("li"));
673 +
674 + if (from > to) { // Swap the indices
675 + var tmp = to;
676 + to = from;
677 + from = tmp;
678 + }
679 +
680 + // Make sure we grab all elements since slice excludes the last index
681 + ++to;
682 +
683 + // Change the checkboxes and underlying options
684 + var range = this.$ul.find("li").not(".multiselect-filter-hidden").slice(from, to).find("input");
685 +
686 + range.prop('checked', checked);
687 +
688 + if (this.options.selectedClass) {
689 + range.closest('li')
690 + .toggleClass(this.options.selectedClass, checked);
691 + }
692 +
693 + for (var i = 0, j = range.length; i < j; i++) {
694 + var $checkbox = $(range[i]);
695 +
696 + var $option = this.getOptionByValue($checkbox.val());
697 +
698 + $option.prop('selected', checked);
699 + }
700 + }
701 +
702 + // Trigger the select "change" event
703 + $target.trigger("change");
704 + }
705 +
706 + // Remembers last clicked option
707 + if($target.is("input") && !$target.closest("li").is(".multiselect-item")){
708 + this.lastToggledInput = $target;
709 + }
710 +
711 + $target.trigger( 'blur' );
712 + }, this));
713 +
714 + // Keyboard support.
715 + this.$container.off('keydown.multiselect').on('keydown.multiselect', $.proxy(function(event) {
716 + if ($('input[type="text"]', this.$container).is(':focus')) {
717 + return;
718 + }
719 +
720 + if (event.keyCode === 9 && this.$container.hasClass('open')) {
721 + this.$button.click();
722 + }
723 + else {
724 + var $items = $(this.$container).find("li:not(.divider):not(.disabled) a").filter(":visible");
725 +
726 + if (!$items.length) {
727 + return;
728 + }
729 +
730 + var index = $items.index($items.filter(':focus'));
731 +
732 + // Navigation up.
733 + if (event.keyCode === 38 && index > 0) {
734 + index--;
735 + }
736 + // Navigate down.
737 + else if (event.keyCode === 40 && index < $items.length - 1) {
738 + index++;
739 + }
740 + else if (!~index) {
741 + index = 0;
742 + }
743 +
744 + var $current = $items.eq(index);
745 + $current.trigger( 'focus' );
746 +
747 + if (event.keyCode === 32 || event.keyCode === 13) {
748 + var $checkbox = $current.find('input');
749 +
750 + $checkbox.prop("checked", !$checkbox.prop("checked"));
751 + $checkbox.change();
752 + }
753 +
754 + event.stopPropagation();
755 + event.preventDefault();
756 + }
757 + }, this));
758 +
759 + if (this.options.enableClickableOptGroups && this.options.multiple) {
760 + $("li.multiselect-group input", this.$ul).on("change", $.proxy(function(event) {
761 + event.stopPropagation();
762 +
763 + var $target = $(event.target);
764 + var checked = $target.prop('checked') || false;
765 +
766 + var $li = $(event.target).closest('li');
767 + var $group = $li.nextUntil("li.multiselect-group")
768 + .not('.multiselect-filter-hidden')
769 + .not('.disabled');
770 +
771 + var $inputs = $group.find("input");
772 +
773 + var values = [];
774 + var $options = [];
775 +
776 + if (this.options.selectedClass) {
777 + if (checked) {
778 + $li.addClass(this.options.selectedClass);
779 + }
780 + else {
781 + $li.removeClass(this.options.selectedClass);
782 + }
783 + }
784 +
785 + $.each($inputs, $.proxy(function(index, input) {
786 + var value = $(input).val();
787 + var $option = this.getOptionByValue(value);
788 +
789 + if (checked) {
790 + $(input).prop('checked', true);
791 + $(input).closest('li')
792 + .addClass(this.options.selectedClass);
793 +
794 + $option.prop('selected', true);
795 + }
796 + else {
797 + $(input).prop('checked', false);
798 + $(input).closest('li')
799 + .removeClass(this.options.selectedClass);
800 +
801 + $option.prop('selected', false);
802 + }
803 +
804 + $options.push(this.getOptionByValue(value));
805 + }, this))
806 +
807 + // Cannot use select or deselect here because it would call updateOptGroups again.
808 +
809 + this.options.onChange($options, checked);
810 +
811 + this.$select.change();
812 + this.updateButtonText();
813 + this.updateSelectAll();
814 + }, this));
815 + }
816 +
817 + if (this.options.enableCollapsibleOptGroups && this.options.multiple) {
818 + $("li.multiselect-group .caret-container", this.$ul).on("click", $.proxy(function(event) {
819 + var $li = $(event.target).closest('li');
820 + var $inputs = $li.nextUntil("li.multiselect-group")
821 + .not('.multiselect-filter-hidden');
822 +
823 + var visible = true;
824 + $inputs.each(function() {
825 + visible = visible && !$(this).hasClass('multiselect-collapsible-hidden');
826 + });
827 +
828 + if (visible) {
829 + $inputs.hide()
830 + .addClass('multiselect-collapsible-hidden');
831 + }
832 + else {
833 + $inputs.show()
834 + .removeClass('multiselect-collapsible-hidden');
835 + }
836 + }, this));
837 +
838 + $("li.multiselect-all", this.$ul).css('background', '#f3f3f3').css('border-bottom', '1px solid #eaeaea');
839 + $("li.multiselect-all > a > label.checkbox", this.$ul).css('padding', '3px 20px 3px 35px');
840 + $("li.multiselect-group > a > input", this.$ul).css('margin', '4px 0px 5px -20px');
841 + }
842 + },
843 +
844 + /**
845 + * Create an option using the given select option.
846 + *
847 + * @param {jQuery} element
848 + */
849 + createOptionValue: function(element) {
850 + var $element = $(element);
851 + if ($element.is(':selected')) {
852 + $element.prop('selected', true);
853 + }
854 +
855 + // Support the label attribute on options.
856 + var label = this.options.optionLabel(element);
857 + var classes = this.options.optionClass(element);
858 + var value = $element.val();
859 + var inputType = this.options.multiple ? "checkbox" : "radio";
860 +
861 + var $li = $(this.options.templates.li);
862 + var $label = $('label', $li);
863 + $label.addClass(inputType);
864 + $label.attr("title", label);
865 + $li.addClass(classes);
866 +
867 + // Hide all children items when collapseOptGroupsByDefault is true
868 + if (this.options.collapseOptGroupsByDefault && $(element).parent().prop("tagName").toLowerCase() === "optgroup") {
869 + $li.addClass("multiselect-collapsible-hidden");
870 + $li.hide();
871 + }
872 +
873 + if (this.options.enableHTML) {
874 + $label.html(" " + label);
875 + }
876 + else {
877 + $label.text(" " + label);
878 + }
879 +
880 + var $checkbox = $('<input/>').attr('type', inputType);
881 +
882 + var name = this.options.checkboxName($element);
883 + if (name) {
884 + $checkbox.attr('name', name);
885 + }
886 +
887 + $label.prepend($checkbox);
888 +
889 + var selected = $element.prop('selected') || false;
890 + $checkbox.val(value);
891 +
892 + if (value === this.options.selectAllValue) {
893 + $li.addClass("multiselect-item multiselect-all");
894 + $checkbox.parent().parent()
895 + .addClass('multiselect-all');
896 + }
897 +
898 + $label.attr('title', $element.attr('title'));
899 +
900 + this.$ul.append($li);
901 +
902 + if ($element.is(':disabled')) {
903 + $checkbox.attr('disabled', 'disabled')
904 + .prop('disabled', true)
905 + .closest('a')
906 + .attr("tabindex", "-1")
907 + .closest('li')
908 + .addClass('disabled');
909 + }
910 +
911 + $checkbox.prop('checked', selected);
912 +
913 + if (selected && this.options.selectedClass) {
914 + $checkbox.closest('li')
915 + .addClass(this.options.selectedClass);
916 + }
917 + },
918 +
919 + /**
920 + * Creates a divider using the given select option.
921 + *
922 + * @param {jQuery} element
923 + */
924 + createDivider: function(element) {
925 + var $divider = $(this.options.templates.divider);
926 + this.$ul.append($divider);
927 + },
928 +
929 + /**
930 + * Creates an optgroup.
931 + *
932 + * @param {jQuery} group
933 + */
934 + createOptgroup: function(group) {
935 + var label = $(group).attr("label");
936 + var value = $(group).attr("value");
937 + var $li = $('<li class="multiselect-item multiselect-group"><a href="javascript:void(0);"><label><b></b></label></a></li>');
938 +
939 + var classes = this.options.optionClass(group);
940 + $li.addClass(classes);
941 +
942 + if (this.options.enableHTML) {
943 + $('label b', $li).html(" " + label);
944 + }
945 + else {
946 + $('label b', $li).text(" " + label);
947 + }
948 +
949 + if (this.options.enableCollapsibleOptGroups && this.options.multiple) {
950 + $('a', $li).append('<span class="caret-container"><b class="caret"></b></span>');
951 + }
952 +
953 + if (this.options.enableClickableOptGroups && this.options.multiple) {
954 + $('a label', $li).prepend('<input type="checkbox" value="' + value + '"/>');
955 + }
956 +
957 + if ($(group).is(':disabled')) {
958 + $li.addClass('disabled');
959 + }
960 +
961 + this.$ul.append($li);
962 +
963 + $("option", group).each($.proxy(function($, group) {
964 + this.createOptionValue(group);
965 + }, this))
966 + },
967 +
968 + /**
969 + * Build the reset.
970 + *
971 + */
972 + buildReset: function() {
973 + if (this.options.includeResetOption) {
974 +
975 + // Check whether to add a divider after the reset.
976 + if (this.options.includeResetDivider) {
977 + this.$ul.prepend($(this.options.templates.divider));
978 + }
979 +
980 + var $resetButton = $(this.options.templates.resetButton);
981 +
982 + if (this.options.enableHTML) {
983 + $('a', $resetButton).html(this.options.resetText);
984 + }
985 + else {
986 + $('a', $resetButton).text(this.options.resetText);
987 + }
988 +
989 + $('a', $resetButton).click($.proxy(function(){
990 + this.clearSelection();
991 + }, this));
992 +
993 + this.$ul.prepend($resetButton);
994 + }
995 + },
996 +
997 + /**
998 + * Build the select all.
999 + *
1000 + * Checks if a select all has already been created.
1001 + */
1002 + buildSelectAll: function() {
1003 + if (typeof this.options.selectAllValue === 'number') {
1004 + this.options.selectAllValue = this.options.selectAllValue.toString();
1005 + }
1006 +
1007 + var alreadyHasSelectAll = this.hasSelectAll();
1008 +
1009 + if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple
1010 + && $('option', this.$select).length > this.options.includeSelectAllIfMoreThan) {
1011 +
1012 + // Check whether to add a divider after the select all.
1013 + if (this.options.includeSelectAllDivider) {
1014 + this.$ul.prepend($(this.options.templates.divider));
1015 + }
1016 +
1017 + var $li = $(this.options.templates.li);
1018 + $('label', $li).addClass("checkbox");
1019 +
1020 + if (this.options.enableHTML) {
1021 + $('label', $li).html(" " + this.options.selectAllText);
1022 + }
1023 + else {
1024 + $('label', $li).text(" " + this.options.selectAllText);
1025 + }
1026 +
1027 + if (this.options.selectAllName) {
1028 + $('label', $li).prepend('<input type="checkbox" name="' + this.options.selectAllName + '" />');
1029 + }
1030 + else {
1031 + $('label', $li).prepend('<input type="checkbox" />');
1032 + }
1033 +
1034 + var $checkbox = $('input', $li);
1035 + $checkbox.val(this.options.selectAllValue);
1036 +
1037 + $li.addClass("multiselect-item multiselect-all");
1038 + $checkbox.parent().parent()
1039 + .addClass('multiselect-all');
1040 +
1041 + this.$ul.prepend($li);
1042 +
1043 + $checkbox.prop('checked', false);
1044 + }
1045 + },
1046 +
1047 + /**
1048 + * Builds the filter.
1049 + */
1050 + buildFilter: function() {
1051 +
1052 + // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength.
1053 + if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) {
1054 + var enableFilterLength = Math.max(this.options.enableFiltering, this.options.enableCaseInsensitiveFiltering);
1055 +
1056 + if (this.$select.find('option').length >= enableFilterLength) {
1057 +
1058 + this.$filter = $(this.options.templates.filter);
1059 + $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder);
1060 +
1061 + // Adds optional filter clear button
1062 + if(this.options.includeFilterClearBtn) {
1063 + var clearBtn = $(this.options.templates.filterClearBtn);
1064 + clearBtn.on('click', $.proxy(function(event){
1065 + clearTimeout(this.searchTimeout);
1066 +
1067 + this.query = '';
1068 + this.$filter.find('.multiselect-search').val('');
1069 + $('li', this.$ul).show().removeClass('multiselect-filter-hidden');
1070 +
1071 + this.updateSelectAll();
1072 +
1073 + if (this.options.enableClickableOptGroups && this.options.multiple) {
1074 + this.updateOptGroups();
1075 + }
1076 +
1077 + }, this));
1078 + this.$filter.find('.input-group').append(clearBtn);
1079 + }
1080 +
1081 + this.$ul.prepend(this.$filter);
1082 +
1083 + this.$filter.val(this.query).on('click', function(event) {
1084 + event.stopPropagation();
1085 + }).on('input keydown', $.proxy(function(event) {
1086 + // Cancel enter key default behaviour
1087 + if (event.which === 13) {
1088 + event.preventDefault();
1089 + }
1090 +
1091 + // This is useful to catch "keydown" events after the browser has updated the control.
1092 + clearTimeout(this.searchTimeout);
1093 +
1094 + this.searchTimeout = this.asyncFunction($.proxy(function() {
1095 +
1096 + if (this.query !== event.target.value) {
1097 + this.query = event.target.value;
1098 +
1099 + var currentGroup, currentGroupVisible;
1100 + $.each($('li', this.$ul), $.proxy(function(index, element) {
1101 + var value = $('input', element).length > 0 ? $('input', element).val() : "";
1102 + var text = $('label', element).text();
1103 +
1104 + var filterCandidate = '';
1105 + if ((this.options.filterBehavior === 'text')) {
1106 + filterCandidate = text;
1107 + }
1108 + else if ((this.options.filterBehavior === 'value')) {
1109 + filterCandidate = value;
1110 + }
1111 + else if (this.options.filterBehavior === 'both') {
1112 + filterCandidate = text + '\n' + value;
1113 + }
1114 +
1115 + if (value !== this.options.selectAllValue && text) {
1116 +
1117 + // By default lets assume that element is not
1118 + // interesting for this search.
1119 + var showElement = false;
1120 +
1121 + if (this.options.enableCaseInsensitiveFiltering) {
1122 + filterCandidate = filterCandidate.toLowerCase();
1123 + this.query = this.query.toLowerCase();
1124 + }
1125 +
1126 + if (this.options.enableFullValueFiltering && this.options.filterBehavior !== 'both') {
1127 + var valueToMatch = filterCandidate.trim().substring(0, this.query.length);
1128 + if (this.query.indexOf(valueToMatch) > -1) {
1129 + showElement = true;
1130 + }
1131 + }
1132 + else if (filterCandidate.indexOf(this.query) > -1) {
1133 + showElement = true;
1134 + }
1135 +
1136 + // Toggle current element (group or group item) according to showElement boolean.
1137 + if(!showElement){
1138 + $(element).css('display', 'none');
1139 + $(element).addClass('multiselect-filter-hidden');
1140 + }
1141 + if(showElement){
1142 + $(element).css('display', 'block');
1143 + $(element).removeClass('multiselect-filter-hidden');
1144 + }
1145 +
1146 + // Differentiate groups and group items.
1147 + if ($(element).hasClass('multiselect-group')) {
1148 + // Remember group status.
1149 + currentGroup = element;
1150 + currentGroupVisible = showElement;
1151 + }
1152 + else {
1153 + // Show group name when at least one of its items is visible.
1154 + if (showElement) {
1155 + $(currentGroup).show()
1156 + .removeClass('multiselect-filter-hidden');
1157 + }
1158 +
1159 + // Show all group items when group name satisfies filter.
1160 + if (!showElement && currentGroupVisible) {
1161 + $(element).show()
1162 + .removeClass('multiselect-filter-hidden');
1163 + }
1164 + }
1165 + }
1166 + }, this));
1167 + }
1168 +
1169 + this.updateSelectAll();
1170 +
1171 + if (this.options.enableClickableOptGroups && this.options.multiple) {
1172 + this.updateOptGroups();
1173 + }
1174 +
1175 + this.options.onFiltering(event.target);
1176 +
1177 + }, this), 300, this);
1178 + }, this));
1179 + }
1180 + }
1181 + },
1182 +
1183 + /**
1184 + * Unbinds the whole plugin.
1185 + */
1186 + destroy: function() {
1187 + this.$container.remove();
1188 + this.$select.show();
1189 +
1190 + // reset original state
1191 + this.$select.prop('disabled', this.options.wasDisabled);
1192 +
1193 + this.$select.data('multiselect', null);
1194 + },
1195 +
1196 + /**
1197 + * Refreshs the multiselect based on the selected options of the select.
1198 + */
1199 + refresh: function () {
1200 + var inputs = {};
1201 + $('li input', this.$ul).each(function() {
1202 + inputs[$(this).val()] = $(this);
1203 + });
1204 +
1205 + $('option', this.$select).each($.proxy(function (index, element) {
1206 + var $elem = $(element);
1207 + var $input = inputs[$(element).val()];
1208 +
1209 + if ($elem.is(':selected')) {
1210 + $input.prop('checked', true);
1211 +
1212 + if (this.options.selectedClass) {
1213 + $input.closest('li')
1214 + .addClass(this.options.selectedClass);
1215 + }
1216 + }
1217 + else {
1218 + $input.prop('checked', false);
1219 +
1220 + if (this.options.selectedClass) {
1221 + $input.closest('li')
1222 + .removeClass(this.options.selectedClass);
1223 + }
1224 + }
1225 +
1226 + if ($elem.is(":disabled")) {
1227 + $input.attr('disabled', 'disabled')
1228 + .prop('disabled', true)
1229 + .closest('li')
1230 + .addClass('disabled');
1231 + }
1232 + else {
1233 + $input.prop('disabled', false)
1234 + .closest('li')
1235 + .removeClass('disabled');
1236 + }
1237 + }, this));
1238 +
1239 + this.updateButtonText();
1240 + this.updateSelectAll();
1241 +
1242 + if (this.options.enableClickableOptGroups && this.options.multiple) {
1243 + this.updateOptGroups();
1244 + }
1245 + },
1246 +
1247 + /**
1248 + * Select all options of the given values.
1249 + *
1250 + * If triggerOnChange is set to true, the on change event is triggered if
1251 + * and only if one value is passed.
1252 + *
1253 + * @param {Array} selectValues
1254 + * @param {Boolean} triggerOnChange
1255 + */
1256 + select: function(selectValues, triggerOnChange) {
1257 + if(!$.isArray(selectValues)) {
1258 + selectValues = [selectValues];
1259 + }
1260 +
1261 + for (var i = 0; i < selectValues.length; i++) {
1262 + var value = selectValues[i];
1263 +
1264 + if (value === null || value === undefined) {
1265 + continue;
1266 + }
1267 +
1268 + var $option = this.getOptionByValue(value);
1269 + var $checkbox = this.getInputByValue(value);
1270 +
1271 + if($option === undefined || $checkbox === undefined) {
1272 + continue;
1273 + }
1274 +
1275 + if (!this.options.multiple) {
1276 + this.deselectAll(false);
1277 + }
1278 +
1279 + if (this.options.selectedClass) {
1280 + $checkbox.closest('li')
1281 + .addClass(this.options.selectedClass);
1282 + }
1283 +
1284 + $checkbox.prop('checked', true);
1285 + $option.prop('selected', true);
1286 +
1287 + if (triggerOnChange) {
1288 + this.options.onChange($option, true);
1289 + }
1290 + }
1291 +
1292 + this.updateButtonText();
1293 + this.updateSelectAll();
1294 +
1295 + if (this.options.enableClickableOptGroups && this.options.multiple) {
1296 + this.updateOptGroups();
1297 + }
1298 + },
1299 +
1300 + /**
1301 + * Clears all selected items.
1302 + */
1303 + clearSelection: function () {
1304 + this.deselectAll(false);
1305 + this.updateButtonText();
1306 + this.updateSelectAll();
1307 +
1308 + if (this.options.enableClickableOptGroups && this.options.multiple) {
1309 + this.updateOptGroups();
1310 + }
1311 + },
1312 +
1313 + /**
1314 + * Deselects all options of the given values.
1315 + *
1316 + * If triggerOnChange is set to true, the on change event is triggered, if
1317 + * and only if one value is passed.
1318 + *
1319 + * @param {Array} deselectValues
1320 + * @param {Boolean} triggerOnChange
1321 + */
1322 + deselect: function(deselectValues, triggerOnChange) {
1323 + if(!$.isArray(deselectValues)) {
1324 + deselectValues = [deselectValues];
1325 + }
1326 +
1327 + for (var i = 0; i < deselectValues.length; i++) {
1328 + var value = deselectValues[i];
1329 +
1330 + if (value === null || value === undefined) {
1331 + continue;
1332 + }
1333 +
1334 + var $option = this.getOptionByValue(value);
1335 + var $checkbox = this.getInputByValue(value);
1336 +
1337 + if($option === undefined || $checkbox === undefined) {
1338 + continue;
1339 + }
1340 +
1341 + if (this.options.selectedClass) {
1342 + $checkbox.closest('li')
1343 + .removeClass(this.options.selectedClass);
1344 + }
1345 +
1346 + $checkbox.prop('checked', false);
1347 + $option.prop('selected', false);
1348 +
1349 + if (triggerOnChange) {
1350 + this.options.onChange($option, false);
1351 + }
1352 + }
1353 +
1354 + this.updateButtonText();
1355 + this.updateSelectAll();
1356 +
1357 + if (this.options.enableClickableOptGroups && this.options.multiple) {
1358 + this.updateOptGroups();
1359 + }
1360 + },
1361 +
1362 + /**
1363 + * Selects all enabled & visible options.
1364 + *
1365 + * If justVisible is true or not specified, only visible options are selected.
1366 + *
1367 + * @param {Boolean} justVisible
1368 + * @param {Boolean} triggerOnSelectAll
1369 + */
1370 + selectAll: function (justVisible, triggerOnSelectAll) {
1371 +
1372 + var justVisible = typeof justVisible === 'undefined' ? true : justVisible;
1373 + var allLis = $("li:not(.divider):not(.disabled):not(.multiselect-group)", this.$ul);
1374 + var visibleLis = $("li:not(.divider):not(.disabled):not(.multiselect-group):not(.multiselect-filter-hidden):not(.multiselect-collapisble-hidden)", this.$ul).filter(':visible');
1375 +
1376 + if(justVisible) {
1377 + $('input:enabled' , visibleLis).prop('checked', true);
1378 + visibleLis.addClass(this.options.selectedClass);
1379 +
1380 + $('input:enabled' , visibleLis).each($.proxy(function(index, element) {
1381 + var value = $(element).val();
1382 + var option = this.getOptionByValue(value);
1383 + $(option).prop('selected', true);
1384 + }, this));
1385 + }
1386 + else {
1387 + $('input:enabled' , allLis).prop('checked', true);
1388 + allLis.addClass(this.options.selectedClass);
1389 +
1390 + $('input:enabled' , allLis).each($.proxy(function(index, element) {
1391 + var value = $(element).val();
1392 + var option = this.getOptionByValue(value);
1393 + $(option).prop('selected', true);
1394 + }, this));
1395 + }
1396 +
1397 + $('li input[value="' + this.options.selectAllValue + '"]', this.$ul).prop('checked', true);
1398 +
1399 + if (this.options.enableClickableOptGroups && this.options.multiple) {
1400 + this.updateOptGroups();
1401 + }
1402 +
1403 + if (triggerOnSelectAll) {
1404 + this.options.onSelectAll();
1405 + }
1406 + },
1407 +
1408 + /**
1409 + * Deselects all options.
1410 + *
1411 + * If justVisible is true or not specified, only visible options are deselected.
1412 + *
1413 + * @param {Boolean} justVisible
1414 + */
1415 + deselectAll: function (justVisible, triggerOnDeselectAll) {
1416 +
1417 + var justVisible = typeof justVisible === 'undefined' ? true : justVisible;
1418 + var allLis = $("li:not(.divider):not(.disabled):not(.multiselect-group)", this.$ul);
1419 + var visibleLis = $("li:not(.divider):not(.disabled):not(.multiselect-group):not(.multiselect-filter-hidden):not(.multiselect-collapisble-hidden)", this.$ul).filter(':visible');
1420 +
1421 + if(justVisible) {
1422 + $('input[type="checkbox"]:enabled' , visibleLis).prop('checked', false);
1423 + visibleLis.removeClass(this.options.selectedClass);
1424 +
1425 + $('input[type="checkbox"]:enabled' , visibleLis).each($.proxy(function(index, element) {
1426 + var value = $(element).val();
1427 + var option = this.getOptionByValue(value);
1428 + $(option).prop('selected', false);
1429 + }, this));
1430 + }
1431 + else {
1432 + $('input[type="checkbox"]:enabled' , allLis).prop('checked', false);
1433 + allLis.removeClass(this.options.selectedClass);
1434 +
1435 + $('input[type="checkbox"]:enabled' , allLis).each($.proxy(function(index, element) {
1436 + var value = $(element).val();
1437 + var option = this.getOptionByValue(value);
1438 + $(option).prop('selected', false);
1439 + }, this));
1440 + }
1441 +
1442 + $('li input[value="' + this.options.selectAllValue + '"]', this.$ul).prop('checked', false);
1443 +
1444 + if (this.options.enableClickableOptGroups && this.options.multiple) {
1445 + this.updateOptGroups();
1446 + }
1447 +
1448 + if (triggerOnDeselectAll) {
1449 + this.options.onDeselectAll();
1450 + }
1451 + },
1452 +
1453 + /**
1454 + * Rebuild the plugin.
1455 + *
1456 + * Rebuilds the dropdown, the filter and the select all option.
1457 + */
1458 + rebuild: function() {
1459 + this.$ul.html('');
1460 +
1461 + // Important to distinguish between radios and checkboxes.
1462 + this.options.multiple = this.$select.attr('multiple') === "multiple";
1463 +
1464 + this.buildSelectAll();
1465 + this.buildDropdownOptions();
1466 + this.buildFilter();
1467 +
1468 + this.updateButtonText();
1469 + this.updateSelectAll(true);
1470 +
1471 + if (this.options.enableClickableOptGroups && this.options.multiple) {
1472 + this.updateOptGroups();
1473 + }
1474 +
1475 + if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) {
1476 + this.disable();
1477 + }
1478 + else {
1479 + this.enable();
1480 + }
1481 +
1482 + if (this.options.dropRight) {
1483 + this.$ul.addClass('pull-right');
1484 + }
1485 + },
1486 +
1487 + /**
1488 + * The provided data will be used to build the dropdown.
1489 + */
1490 + dataprovider: function(dataprovider) {
1491 +
1492 + var groupCounter = 0;
1493 + var $select = this.$select.empty();
1494 +
1495 + $.each(dataprovider, function (index, option) {
1496 + var $tag;
1497 +
1498 + if ($.isArray(option.children)) { // create optiongroup tag
1499 + groupCounter++;
1500 +
1501 + $tag = $('<optgroup/>').attr({
1502 + label: option.label || 'Group ' + groupCounter,
1503 + disabled: !!option.disabled,
1504 + value: option.value
1505 + });
1506 +
1507 + forEach(option.children, function(subOption) { // add children option tags
1508 + var attributes = {
1509 + value: subOption.value,
1510 + label: subOption.label || subOption.value,
1511 + title: subOption.title,
1512 + selected: !!subOption.selected,
1513 + disabled: !!subOption.disabled
1514 + };
1515 +
1516 + //Loop through attributes object and add key-value for each attribute
1517 + for (var key in subOption.attributes) {
1518 + attributes['data-' + key] = subOption.attributes[key];
1519 + }
1520 + //Append original attributes + new data attributes to option
1521 + $tag.append($('<option/>').attr(attributes));
1522 + });
1523 + }
1524 + else {
1525 +
1526 + var attributes = {
1527 + 'value': option.value,
1528 + 'label': option.label || option.value,
1529 + 'title': option.title,
1530 + 'class': option['class'],
1531 + 'selected': !!option['selected'],
1532 + 'disabled': !!option['disabled']
1533 + };
1534 + //Loop through attributes object and add key-value for each attribute
1535 + for (var key in option.attributes) {
1536 + attributes['data-' + key] = option.attributes[key];
1537 + }
1538 + //Append original attributes + new data attributes to option
1539 + $tag = $('<option/>').attr(attributes);
1540 +
1541 + $tag.text(option.label || option.value);
1542 + }
1543 +
1544 + $select.append($tag);
1545 + });
1546 +
1547 + this.rebuild();
1548 + },
1549 +
1550 + /**
1551 + * Enable the multiselect.
1552 + */
1553 + enable: function() {
1554 + this.$select.prop('disabled', false);
1555 + this.$button.prop('disabled', false)
1556 + .removeClass('disabled');
1557 + },
1558 +
1559 + /**
1560 + * Disable the multiselect.
1561 + */
1562 + disable: function() {
1563 + this.$select.prop('disabled', true);
1564 + this.$button.prop('disabled', true)
1565 + .addClass('disabled');
1566 + },
1567 +
1568 + /**
1569 + * Set the options.
1570 + *
1571 + * @param {Array} options
1572 + */
1573 + setOptions: function(options) {
1574 + this.options = this.mergeOptions(options);
1575 + },
1576 +
1577 + /**
1578 + * Merges the given options with the default options.
1579 + *
1580 + * @param {Array} options
1581 + * @returns {Array}
1582 + */
1583 + mergeOptions: function(options) {
1584 + return $.extend(true, {}, this.defaults, this.options, options);
1585 + },
1586 +
1587 + /**
1588 + * Checks whether a select all checkbox is present.
1589 + *
1590 + * @returns {Boolean}
1591 + */
1592 + hasSelectAll: function() {
1593 + return $('li.multiselect-all', this.$ul).length > 0;
1594 + },
1595 +
1596 + /**
1597 + * Update opt groups.
1598 + */
1599 + updateOptGroups: function() {
1600 + var $groups = $('li.multiselect-group', this.$ul)
1601 + var selectedClass = this.options.selectedClass;
1602 +
1603 + $groups.each(function() {
1604 + var $options = $(this).nextUntil('li.multiselect-group')
1605 + .not('.multiselect-filter-hidden')
1606 + .not('.disabled');
1607 +
1608 + var checked = true;
1609 + $options.each(function() {
1610 + var $input = $('input', this);
1611 +
1612 + if (!$input.prop('checked')) {
1613 + checked = false;
1614 + }
1615 + });
1616 +
1617 + if (selectedClass) {
1618 + if (checked) {
1619 + $(this).addClass(selectedClass);
1620 + }
1621 + else {
1622 + $(this).removeClass(selectedClass);
1623 + }
1624 + }
1625 +
1626 + $('input', this).prop('checked', checked);
1627 + });
1628 + },
1629 +
1630 + /**
1631 + * Updates the select all checkbox based on the currently displayed and selected checkboxes.
1632 + */
1633 + updateSelectAll: function(notTriggerOnSelectAll) {
1634 + if (this.hasSelectAll()) {
1635 + var allBoxes = $("li:not(.multiselect-item):not(.multiselect-filter-hidden):not(.multiselect-group):not(.disabled) input:enabled", this.$ul);
1636 + var allBoxesLength = allBoxes.length;
1637 + var checkedBoxesLength = allBoxes.filter(":checked").length;
1638 + var selectAllLi = $("li.multiselect-all", this.$ul);
1639 + var selectAllInput = selectAllLi.find("input");
1640 +
1641 + if (checkedBoxesLength > 0 && checkedBoxesLength === allBoxesLength) {
1642 + selectAllInput.prop("checked", true);
1643 + selectAllLi.addClass(this.options.selectedClass);
1644 + }
1645 + else {
1646 + selectAllInput.prop("checked", false);
1647 + selectAllLi.removeClass(this.options.selectedClass);
1648 + }
1649 + }
1650 + },
1651 +
1652 + /**
1653 + * Update the button text and its title based on the currently selected options.
1654 + */
1655 + updateButtonText: function() {
1656 + var options = this.getSelected();
1657 +
1658 + // First update the displayed button text.
1659 + if (this.options.enableHTML) {
1660 + $('.multiselect .multiselect-selected-text', this.$container).html(this.options.buttonText(options, this.$select));
1661 + }
1662 + else {
1663 + $('.multiselect .multiselect-selected-text', this.$container).text(this.options.buttonText(options, this.$select));
1664 + }
1665 +
1666 + // Now update the title attribute of the button.
1667 + $('.multiselect', this.$container).attr('title', this.options.buttonTitle(options, this.$select));
1668 + },
1669 +
1670 + /**
1671 + * Get all selected options.
1672 + *
1673 + * @returns {jQUery}
1674 + */
1675 + getSelected: function() {
1676 + return $('option', this.$select).filter(":selected");
1677 + },
1678 +
1679 + /**
1680 + * Gets a select option by its value.
1681 + *
1682 + * @param {String} value
1683 + * @returns {jQuery}
1684 + */
1685 + getOptionByValue: function (value) {
1686 +
1687 + var options = $('option', this.$select);
1688 + var valueToCompare = value.toString();
1689 +
1690 + for (var i = 0; i < options.length; i = i + 1) {
1691 + var option = options[i];
1692 + if (option.value === valueToCompare) {
1693 + return $(option);
1694 + }
1695 + }
1696 + },
1697 +
1698 + /**
1699 + * Get the input (radio/checkbox) by its value.
1700 + *
1701 + * @param {String} value
1702 + * @returns {jQuery}
1703 + */
1704 + getInputByValue: function (value) {
1705 +
1706 + var checkboxes = $('li input:not(.multiselect-search)', this.$ul);
1707 + var valueToCompare = value.toString();
1708 +
1709 + for (var i = 0; i < checkboxes.length; i = i + 1) {
1710 + var checkbox = checkboxes[i];
1711 + if (checkbox.value === valueToCompare) {
1712 + return $(checkbox);
1713 + }
1714 + }
1715 + },
1716 +
1717 + /**
1718 + * Used for knockout integration.
1719 + */
1720 + updateOriginalOptions: function() {
1721 + this.originalOptions = this.$select.clone()[0].options;
1722 + },
1723 +
1724 + asyncFunction: function(callback, timeout, self) {
1725 + var args = Array.prototype.slice.call(arguments, 3);
1726 + return setTimeout(function() {
1727 + callback.apply(self || window, args);
1728 + }, timeout);
1729 + },
1730 +
1731 + setAllSelectedText: function(allSelectedText) {
1732 + this.options.allSelectedText = allSelectedText;
1733 + this.updateButtonText();
1734 + }
1735 + };
1736 +
1737 + $.fn.multiselect = function(option, parameter, extraOptions) {
1738 + return this.each(function() {
1739 + var data = $(this).data('multiselect');
1740 + var options = typeof option === 'object' && option;
1741 +
1742 + // Initialize the multiselect.
1743 + if (!data) {
1744 + data = new Multiselect(this, options);
1745 + $(this).data('multiselect', data);
1746 + }
1747 +
1748 + // Call multiselect method.
1749 + if (typeof option === 'string') {
1750 + data[option](parameter, extraOptions);
1751 +
1752 + if (option === 'destroy') {
1753 + $(this).data('multiselect', false);
1754 + }
1755 + }
1756 + });
1757 + };
1758 +
1759 + $.fn.multiselect.Constructor = Multiselect;
1760 +
1761 + $(function() {
1762 + $("select[data-role=multiselect]").multiselect();
1763 + });
1764 +
1765 +}(window.jQuery);