2022-03-30 07:54:07 +02:00
( function ( $ ) {
'use strict' ;
var DISALLOWED _ATTRIBUTES = [ 'sanitize' , 'whiteList' , 'sanitizeFn' ] ;
var uriAttrs = [
'background' ,
'cite' ,
'href' ,
'itemtype' ,
'longdesc' ,
'poster' ,
'src' ,
'xlink:href'
] ;
var ARIA _ATTRIBUTE _PATTERN = /^aria-[\w-]*$/i ;
var DefaultWhitelist = {
// Global attributes allowed on any supplied element below.
'*' : [ 'class' , 'dir' , 'id' , 'lang' , 'role' , 'tabindex' , 'style' , ARIA _ATTRIBUTE _PATTERN ] ,
a : [ 'target' , 'href' , 'title' , 'rel' ] ,
area : [ ] ,
b : [ ] ,
br : [ ] ,
col : [ ] ,
code : [ ] ,
div : [ ] ,
em : [ ] ,
hr : [ ] ,
h1 : [ ] ,
h2 : [ ] ,
h3 : [ ] ,
h4 : [ ] ,
h5 : [ ] ,
h6 : [ ] ,
i : [ ] ,
img : [ 'src' , 'alt' , 'title' , 'width' , 'height' ] ,
li : [ ] ,
ol : [ ] ,
p : [ ] ,
pre : [ ] ,
s : [ ] ,
small : [ ] ,
span : [ ] ,
sub : [ ] ,
sup : [ ] ,
strong : [ ] ,
u : [ ] ,
ul : [ ]
2022-07-08 11:28:27 +02:00
} ;
2022-03-30 07:54:07 +02:00
/ * *
* A pattern that recognizes a commonly useful subset of URLs that are safe .
*
* Shoutout to Angular 7 https : //github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
* /
var SAFE _URL _PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi ;
/ * *
* A pattern that matches safe data URLs . Only matches image , video and audio types .
*
* Shoutout to Angular 7 https : //github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
* /
var DATA _URL _PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i ;
2022-04-13 12:34:14 +02:00
var ParseableAttributes = [ 'title' , 'placeholder' ] ; // attributes to use as settings, can add others in the future
2022-03-30 07:54:07 +02:00
function allowedAttribute ( attr , allowedAttributeList ) {
2022-07-08 11:28:27 +02:00
var attrName = attr . nodeName . toLowerCase ( ) ;
2022-03-30 07:54:07 +02:00
if ( $ . inArray ( attrName , allowedAttributeList ) !== - 1 ) {
if ( $ . inArray ( attrName , uriAttrs ) !== - 1 ) {
2022-07-08 11:28:27 +02:00
return Boolean ( attr . nodeValue . match ( SAFE _URL _PATTERN ) || attr . nodeValue . match ( DATA _URL _PATTERN ) ) ;
2022-03-30 07:54:07 +02:00
}
2022-07-08 11:28:27 +02:00
return true ;
2022-03-30 07:54:07 +02:00
}
var regExp = $ ( allowedAttributeList ) . filter ( function ( index , value ) {
2022-07-08 11:28:27 +02:00
return value instanceof RegExp ;
} ) ;
2022-03-30 07:54:07 +02:00
// Check if a regular expression validates the attribute.
for ( var i = 0 , l = regExp . length ; i < l ; i ++ ) {
if ( attrName . match ( regExp [ i ] ) ) {
2022-07-08 11:28:27 +02:00
return true ;
2022-03-30 07:54:07 +02:00
}
}
2022-07-08 11:28:27 +02:00
return false ;
2022-03-30 07:54:07 +02:00
}
function sanitizeHtml ( unsafeElements , whiteList , sanitizeFn ) {
if ( sanitizeFn && typeof sanitizeFn === 'function' ) {
return sanitizeFn ( unsafeElements ) ;
}
var whitelistKeys = Object . keys ( whiteList ) ;
for ( var i = 0 , len = unsafeElements . length ; i < len ; i ++ ) {
var elements = unsafeElements [ i ] . querySelectorAll ( '*' ) ;
for ( var j = 0 , len2 = elements . length ; j < len2 ; j ++ ) {
var el = elements [ j ] ;
var elName = el . nodeName . toLowerCase ( ) ;
if ( whitelistKeys . indexOf ( elName ) === - 1 ) {
el . parentNode . removeChild ( el ) ;
continue ;
}
var attributeList = [ ] . slice . call ( el . attributes ) ;
var whitelistedAttributes = [ ] . concat ( whiteList [ '*' ] || [ ] , whiteList [ elName ] || [ ] ) ;
for ( var k = 0 , len3 = attributeList . length ; k < len3 ; k ++ ) {
var attr = attributeList [ k ] ;
if ( ! allowedAttribute ( attr , whitelistedAttributes ) ) {
el . removeAttribute ( attr . nodeName ) ;
}
}
}
}
}
2022-04-13 12:34:14 +02:00
function getAttributesObject ( $select ) {
var attributesObject = { } ,
attrVal ;
ParseableAttributes . forEach ( function ( item ) {
attrVal = $select . attr ( item ) ;
if ( attrVal ) attributesObject [ item ] = attrVal ;
} ) ;
// for backwards compatibility
// (using title as placeholder is deprecated - remove in v2.0.0)
if ( ! attributesObject . placeholder && attributesObject . title ) {
attributesObject . placeholder = attributesObject . title ;
}
return attributesObject ;
}
2022-03-30 07:54:07 +02:00
// Polyfill for browsers with no classList support
// Remove in v2
if ( ! ( 'classList' in document . createElement ( '_' ) ) ) {
( function ( view ) {
if ( ! ( 'Element' in view ) ) return ;
var classListProp = 'classList' ,
protoProp = 'prototype' ,
elemCtrProto = view . Element [ protoProp ] ,
objCtr = Object ,
classListGetter = function ( ) {
var $elem = $ ( this ) ;
return {
add : function ( classes ) {
classes = Array . prototype . slice . call ( arguments ) . join ( ' ' ) ;
return $elem . addClass ( classes ) ;
} ,
remove : function ( classes ) {
classes = Array . prototype . slice . call ( arguments ) . join ( ' ' ) ;
return $elem . removeClass ( classes ) ;
} ,
toggle : function ( classes , force ) {
return $elem . toggleClass ( classes , force ) ;
} ,
contains : function ( classes ) {
return $elem . hasClass ( classes ) ;
}
2022-07-08 11:28:27 +02:00
} ;
2022-03-30 07:54:07 +02:00
} ;
if ( objCtr . defineProperty ) {
var classListPropDesc = {
get : classListGetter ,
enumerable : true ,
configurable : true
} ;
try {
objCtr . defineProperty ( elemCtrProto , classListProp , classListPropDesc ) ;
} catch ( ex ) { // IE 8 doesn't support enumerable:true
// adding undefined to fight this issue https://github.com/eligrey/classList.js/issues/36
// modernie IE8-MSW7 machine has IE8 8.0.6001.18702 and is affected
if ( ex . number === undefined || ex . number === - 0x7FF5EC54 ) {
classListPropDesc . enumerable = false ;
objCtr . defineProperty ( elemCtrProto , classListProp , classListPropDesc ) ;
}
}
} else if ( objCtr [ protoProp ] . _ _defineGetter _ _ ) {
elemCtrProto . _ _defineGetter _ _ ( classListProp , classListGetter ) ;
}
} ( window ) ) ;
}
var testElement = document . createElement ( '_' ) ;
testElement . classList . add ( 'c1' , 'c2' ) ;
if ( ! testElement . classList . contains ( 'c2' ) ) {
var _add = DOMTokenList . prototype . add ,
_remove = DOMTokenList . prototype . remove ;
DOMTokenList . prototype . add = function ( ) {
Array . prototype . forEach . call ( arguments , _add . bind ( this ) ) ;
2022-07-08 11:28:27 +02:00
} ;
2022-03-30 07:54:07 +02:00
DOMTokenList . prototype . remove = function ( ) {
Array . prototype . forEach . call ( arguments , _remove . bind ( this ) ) ;
2022-07-08 11:28:27 +02:00
} ;
2022-03-30 07:54:07 +02:00
}
testElement . classList . toggle ( 'c3' , false ) ;
// Polyfill for IE 10 and Firefox <24, where classList.toggle does not
// support the second argument.
if ( testElement . classList . contains ( 'c3' ) ) {
var _toggle = DOMTokenList . prototype . toggle ;
DOMTokenList . prototype . toggle = function ( token , force ) {
if ( 1 in arguments && ! this . contains ( token ) === ! force ) {
return force ;
} else {
return _toggle . call ( this , token ) ;
}
} ;
}
testElement = null ;
2022-07-08 11:28:27 +02:00
// Polyfill for IE (remove in v2)
Object . values = typeof Object . values === 'function' ? Object . values : function ( obj ) {
return Object . keys ( obj ) . map ( function ( key ) {
return obj [ key ] ;
} ) ;
} ;
2022-03-30 07:54:07 +02:00
// shallow array comparison
function isEqual ( array1 , array2 ) {
return array1 . length === array2 . length && array1 . every ( function ( element , index ) {
return element === array2 [ index ] ;
} ) ;
} ;
// <editor-fold desc="Shims">
if ( ! String . prototype . startsWith ) {
( function ( ) {
'use strict' ; // needed to support `apply`/`call` with `undefined`/`null`
var toString = { } . toString ;
var startsWith = function ( search ) {
if ( this == null ) {
throw new TypeError ( ) ;
}
var string = String ( this ) ;
if ( search && toString . call ( search ) == '[object RegExp]' ) {
throw new TypeError ( ) ;
}
var stringLength = string . length ;
var searchString = String ( search ) ;
var searchLength = searchString . length ;
var position = arguments . length > 1 ? arguments [ 1 ] : undefined ;
// `ToInteger`
var pos = position ? Number ( position ) : 0 ;
if ( pos != pos ) { // better `isNaN`
pos = 0 ;
}
var start = Math . min ( Math . max ( pos , 0 ) , stringLength ) ;
// Avoid the `indexOf` call if no match is possible
if ( searchLength + start > stringLength ) {
return false ;
}
var index = - 1 ;
while ( ++ index < searchLength ) {
if ( string . charCodeAt ( start + index ) != searchString . charCodeAt ( index ) ) {
return false ;
}
}
return true ;
} ;
2022-04-13 12:34:14 +02:00
if ( Object . defineProperty ) {
Object . defineProperty ( String . prototype , 'startsWith' , {
2022-03-30 07:54:07 +02:00
'value' : startsWith ,
'configurable' : true ,
'writable' : true
} ) ;
} else {
String . prototype . startsWith = startsWith ;
}
} ( ) ) ;
}
2022-07-08 11:28:27 +02:00
function toKebabCase ( str ) {
return str . replace ( /[A-Z]+(?![a-z])|[A-Z]/g , function ( $ , ofs ) {
return ( ofs ? '-' : '' ) + $ . toLowerCase ( ) ;
} ) ;
}
2022-04-13 12:34:14 +02:00
function getSelectedOptions ( ) {
2022-07-08 11:28:27 +02:00
var options = this . selectpicker . main . data ;
if ( this . options . source . data || this . options . source . search ) {
options = Object . values ( this . selectpicker . optionValuesDataMap ) ;
}
var selectedOptions = options . filter ( function ( item ) {
2022-04-13 12:34:14 +02:00
if ( item . selected ) {
if ( this . options . hideDisabled && item . disabled ) return false ;
return true ;
2022-03-30 07:54:07 +02:00
}
2022-04-13 12:34:14 +02:00
return false ;
} , this ) ;
2022-03-30 07:54:07 +02:00
2022-04-13 12:34:14 +02:00
// ensure only 1 option is selected if multiple are set in the data source
if ( this . options . source . data && ! this . multiple && selectedOptions . length > 1 ) {
for ( var i = 0 ; i < selectedOptions . length - 1 ; i ++ ) {
selectedOptions [ i ] . selected = false ;
2022-03-30 07:54:07 +02:00
}
2022-04-13 12:34:14 +02:00
selectedOptions = [ selectedOptions [ selectedOptions . length - 1 ] ] ;
2022-03-30 07:54:07 +02:00
}
return selectedOptions ;
}
// much faster than $.val()
2022-04-13 12:34:14 +02:00
function getSelectValues ( selectedOptions ) {
2022-03-30 07:54:07 +02:00
var value = [ ] ,
2022-04-13 12:34:14 +02:00
options = selectedOptions || getSelectedOptions . call ( this ) ,
2022-03-30 07:54:07 +02:00
opt ;
for ( var i = 0 , len = options . length ; i < len ; i ++ ) {
opt = options [ i ] ;
2022-04-13 12:34:14 +02:00
if ( ! opt . disabled ) {
value . push ( opt . value === undefined ? opt . text : opt . value ) ;
2022-03-30 07:54:07 +02:00
}
}
2022-04-13 12:34:14 +02:00
if ( ! this . multiple ) {
2022-03-30 07:54:07 +02:00
return ! value . length ? null : value [ 0 ] ;
}
return value ;
}
// set data-selected on select element if the value has been programmatically selected
// prior to initialization of bootstrap-select
// * consider removing or replacing an alternative method *
var valHooks = {
useDefault : false ,
_set : $ . valHooks . select . set
} ;
$ . valHooks . select . set = function ( elem , value ) {
if ( value && ! valHooks . useDefault ) $ ( elem ) . data ( 'selected' , true ) ;
return valHooks . _set . apply ( this , arguments ) ;
} ;
var changedArguments = null ;
var EventIsSupported = ( function ( ) {
try {
new Event ( 'change' ) ;
return true ;
} catch ( e ) {
return false ;
}
} ) ( ) ;
$ . fn . triggerNative = function ( eventName ) {
var el = this [ 0 ] ,
event ;
if ( el . dispatchEvent ) { // for modern browsers & IE9+
if ( EventIsSupported ) {
// For modern browsers
event = new Event ( eventName , {
bubbles : true
} ) ;
} else {
// For IE since it doesn't support Event constructor
event = document . createEvent ( 'Event' ) ;
event . initEvent ( eventName , true , false ) ;
}
el . dispatchEvent ( event ) ;
}
} ;
// </editor-fold>
function stringSearch ( li , searchString , method , normalize ) {
var stringTypes = [
'display' ,
'subtext' ,
'tokens'
] ,
searchSuccess = false ;
for ( var i = 0 ; i < stringTypes . length ; i ++ ) {
var stringType = stringTypes [ i ] ,
string = li [ stringType ] ;
if ( string ) {
string = string . toString ( ) ;
// Strip HTML tags. This isn't perfect, but it's much faster than any other method
if ( stringType === 'display' ) {
string = string . replace ( /<[^>]+>/g , '' ) ;
}
if ( normalize ) string = normalizeToBase ( string ) ;
string = string . toUpperCase ( ) ;
2022-04-13 12:34:14 +02:00
if ( typeof method === 'function' ) {
searchSuccess = method ( string , searchString ) ;
} else if ( method === 'contains' ) {
2022-03-30 07:54:07 +02:00
searchSuccess = string . indexOf ( searchString ) >= 0 ;
} else {
searchSuccess = string . startsWith ( searchString ) ;
}
if ( searchSuccess ) break ;
}
}
return searchSuccess ;
}
function toInteger ( value ) {
return parseInt ( value , 10 ) || 0 ;
}
// Borrowed from Lodash (_.deburr)
/** Used to map Latin Unicode letters to basic Latin letters. */
var deburredLetters = {
// Latin-1 Supplement block.
'\xc0' : 'A' , '\xc1' : 'A' , '\xc2' : 'A' , '\xc3' : 'A' , '\xc4' : 'A' , '\xc5' : 'A' ,
'\xe0' : 'a' , '\xe1' : 'a' , '\xe2' : 'a' , '\xe3' : 'a' , '\xe4' : 'a' , '\xe5' : 'a' ,
'\xc7' : 'C' , '\xe7' : 'c' ,
'\xd0' : 'D' , '\xf0' : 'd' ,
'\xc8' : 'E' , '\xc9' : 'E' , '\xca' : 'E' , '\xcb' : 'E' ,
'\xe8' : 'e' , '\xe9' : 'e' , '\xea' : 'e' , '\xeb' : 'e' ,
'\xcc' : 'I' , '\xcd' : 'I' , '\xce' : 'I' , '\xcf' : 'I' ,
'\xec' : 'i' , '\xed' : 'i' , '\xee' : 'i' , '\xef' : 'i' ,
'\xd1' : 'N' , '\xf1' : 'n' ,
'\xd2' : 'O' , '\xd3' : 'O' , '\xd4' : 'O' , '\xd5' : 'O' , '\xd6' : 'O' , '\xd8' : 'O' ,
'\xf2' : 'o' , '\xf3' : 'o' , '\xf4' : 'o' , '\xf5' : 'o' , '\xf6' : 'o' , '\xf8' : 'o' ,
'\xd9' : 'U' , '\xda' : 'U' , '\xdb' : 'U' , '\xdc' : 'U' ,
'\xf9' : 'u' , '\xfa' : 'u' , '\xfb' : 'u' , '\xfc' : 'u' ,
'\xdd' : 'Y' , '\xfd' : 'y' , '\xff' : 'y' ,
'\xc6' : 'Ae' , '\xe6' : 'ae' ,
'\xde' : 'Th' , '\xfe' : 'th' ,
'\xdf' : 'ss' ,
// Latin Extended-A block.
'\u0100' : 'A' , '\u0102' : 'A' , '\u0104' : 'A' ,
'\u0101' : 'a' , '\u0103' : 'a' , '\u0105' : 'a' ,
'\u0106' : 'C' , '\u0108' : 'C' , '\u010a' : 'C' , '\u010c' : 'C' ,
'\u0107' : 'c' , '\u0109' : 'c' , '\u010b' : 'c' , '\u010d' : 'c' ,
'\u010e' : 'D' , '\u0110' : 'D' , '\u010f' : 'd' , '\u0111' : 'd' ,
'\u0112' : 'E' , '\u0114' : 'E' , '\u0116' : 'E' , '\u0118' : 'E' , '\u011a' : 'E' ,
'\u0113' : 'e' , '\u0115' : 'e' , '\u0117' : 'e' , '\u0119' : 'e' , '\u011b' : 'e' ,
'\u011c' : 'G' , '\u011e' : 'G' , '\u0120' : 'G' , '\u0122' : 'G' ,
'\u011d' : 'g' , '\u011f' : 'g' , '\u0121' : 'g' , '\u0123' : 'g' ,
'\u0124' : 'H' , '\u0126' : 'H' , '\u0125' : 'h' , '\u0127' : 'h' ,
'\u0128' : 'I' , '\u012a' : 'I' , '\u012c' : 'I' , '\u012e' : 'I' , '\u0130' : 'I' ,
'\u0129' : 'i' , '\u012b' : 'i' , '\u012d' : 'i' , '\u012f' : 'i' , '\u0131' : 'i' ,
'\u0134' : 'J' , '\u0135' : 'j' ,
'\u0136' : 'K' , '\u0137' : 'k' , '\u0138' : 'k' ,
'\u0139' : 'L' , '\u013b' : 'L' , '\u013d' : 'L' , '\u013f' : 'L' , '\u0141' : 'L' ,
'\u013a' : 'l' , '\u013c' : 'l' , '\u013e' : 'l' , '\u0140' : 'l' , '\u0142' : 'l' ,
'\u0143' : 'N' , '\u0145' : 'N' , '\u0147' : 'N' , '\u014a' : 'N' ,
'\u0144' : 'n' , '\u0146' : 'n' , '\u0148' : 'n' , '\u014b' : 'n' ,
'\u014c' : 'O' , '\u014e' : 'O' , '\u0150' : 'O' ,
'\u014d' : 'o' , '\u014f' : 'o' , '\u0151' : 'o' ,
'\u0154' : 'R' , '\u0156' : 'R' , '\u0158' : 'R' ,
'\u0155' : 'r' , '\u0157' : 'r' , '\u0159' : 'r' ,
'\u015a' : 'S' , '\u015c' : 'S' , '\u015e' : 'S' , '\u0160' : 'S' ,
'\u015b' : 's' , '\u015d' : 's' , '\u015f' : 's' , '\u0161' : 's' ,
'\u0162' : 'T' , '\u0164' : 'T' , '\u0166' : 'T' ,
'\u0163' : 't' , '\u0165' : 't' , '\u0167' : 't' ,
'\u0168' : 'U' , '\u016a' : 'U' , '\u016c' : 'U' , '\u016e' : 'U' , '\u0170' : 'U' , '\u0172' : 'U' ,
'\u0169' : 'u' , '\u016b' : 'u' , '\u016d' : 'u' , '\u016f' : 'u' , '\u0171' : 'u' , '\u0173' : 'u' ,
'\u0174' : 'W' , '\u0175' : 'w' ,
'\u0176' : 'Y' , '\u0177' : 'y' , '\u0178' : 'Y' ,
'\u0179' : 'Z' , '\u017b' : 'Z' , '\u017d' : 'Z' ,
'\u017a' : 'z' , '\u017c' : 'z' , '\u017e' : 'z' ,
'\u0132' : 'IJ' , '\u0133' : 'ij' ,
'\u0152' : 'Oe' , '\u0153' : 'oe' ,
'\u0149' : "'n" , '\u017f' : 's'
} ;
/** Used to match Latin Unicode letters (excluding mathematical operators). */
var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g ;
/** Used to compose unicode character classes. */
var rsComboMarksRange = '\\u0300-\\u036f' ,
reComboHalfMarksRange = '\\ufe20-\\ufe2f' ,
rsComboSymbolsRange = '\\u20d0-\\u20ff' ,
rsComboMarksExtendedRange = '\\u1ab0-\\u1aff' ,
rsComboMarksSupplementRange = '\\u1dc0-\\u1dff' ,
rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange + rsComboMarksExtendedRange + rsComboMarksSupplementRange ;
/** Used to compose unicode capture groups. */
var rsCombo = '[' + rsComboRange + ']' ;
/ * *
* Used to match [ combining diacritical marks ] ( https : //en.wikipedia.org/wiki/Combining_Diacritical_Marks) and
* [ combining diacritical marks for symbols ] ( https : //en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols).
* /
var reComboMark = RegExp ( rsCombo , 'g' ) ;
function deburrLetter ( key ) {
return deburredLetters [ key ] ;
} ;
function normalizeToBase ( string ) {
string = string . toString ( ) ;
return string && string . replace ( reLatin , deburrLetter ) . replace ( reComboMark , '' ) ;
}
// List of HTML entities for escaping.
var escapeMap = {
'&' : '&' ,
'<' : '<' ,
'>' : '>' ,
'"' : '"' ,
"'" : ''' ,
'`' : '`'
} ;
// Functions for escaping and unescaping strings to/from HTML interpolation.
var createEscaper = function ( map ) {
var escaper = function ( match ) {
return map [ match ] ;
} ;
// Regexes for identifying a key that needs to be escaped.
var source = '(?:' + Object . keys ( map ) . join ( '|' ) + ')' ;
var testRegexp = RegExp ( source ) ;
var replaceRegexp = RegExp ( source , 'g' ) ;
return function ( string ) {
string = string == null ? '' : '' + string ;
return testRegexp . test ( string ) ? string . replace ( replaceRegexp , escaper ) : string ;
} ;
} ;
var htmlEscape = createEscaper ( escapeMap ) ;
/ * *
* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* Constants
* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* /
var keyCodeMap = {
32 : ' ' ,
48 : '0' ,
49 : '1' ,
50 : '2' ,
51 : '3' ,
52 : '4' ,
53 : '5' ,
54 : '6' ,
55 : '7' ,
56 : '8' ,
57 : '9' ,
59 : ';' ,
65 : 'A' ,
66 : 'B' ,
67 : 'C' ,
68 : 'D' ,
69 : 'E' ,
70 : 'F' ,
71 : 'G' ,
72 : 'H' ,
73 : 'I' ,
74 : 'J' ,
75 : 'K' ,
76 : 'L' ,
77 : 'M' ,
78 : 'N' ,
79 : 'O' ,
80 : 'P' ,
81 : 'Q' ,
82 : 'R' ,
83 : 'S' ,
84 : 'T' ,
85 : 'U' ,
86 : 'V' ,
87 : 'W' ,
88 : 'X' ,
89 : 'Y' ,
90 : 'Z' ,
96 : '0' ,
97 : '1' ,
98 : '2' ,
99 : '3' ,
100 : '4' ,
101 : '5' ,
102 : '6' ,
103 : '7' ,
104 : '8' ,
105 : '9'
} ;
var keyCodes = {
ESCAPE : 27 , // KeyboardEvent.which value for Escape (Esc) key
ENTER : 13 , // KeyboardEvent.which value for Enter key
SPACE : 32 , // KeyboardEvent.which value for space key
TAB : 9 , // KeyboardEvent.which value for tab key
ARROW _UP : 38 , // KeyboardEvent.which value for up arrow key
ARROW _DOWN : 40 // KeyboardEvent.which value for down arrow key
2022-07-08 11:28:27 +02:00
} ;
2022-03-30 07:54:07 +02:00
2022-04-13 12:34:14 +02:00
// eslint-disable-next-line no-undef
var Dropdown = window . Dropdown || bootstrap . Dropdown ;
function getVersion ( ) {
var version ;
try {
version = $ . fn . dropdown . Constructor . VERSION ;
} catch ( err ) {
version = Dropdown . VERSION ;
}
return version ;
}
2022-03-30 07:54:07 +02:00
var version = {
success : false ,
major : '3'
} ;
try {
2022-04-13 12:34:14 +02:00
version . full = ( getVersion ( ) || '' ) . split ( ' ' ) [ 0 ] . split ( '.' ) ;
2022-03-30 07:54:07 +02:00
version . major = version . full [ 0 ] ;
version . success = true ;
} catch ( err ) {
// do nothing
}
var selectId = 0 ;
var EVENT _KEY = '.bs.select' ;
var classNames = {
DISABLED : 'disabled' ,
DIVIDER : 'divider' ,
SHOW : 'open' ,
DROPUP : 'dropup' ,
MENU : 'dropdown-menu' ,
MENURIGHT : 'dropdown-menu-right' ,
MENULEFT : 'dropdown-menu-left' ,
// to-do: replace with more advanced template/customization options
2022-06-28 07:21:26 +02:00
BUTTONCLASS : 'btn-secondary' ,
2022-03-30 07:54:07 +02:00
POPOVERHEADER : 'popover-title' ,
ICONBASE : 'glyphicon' ,
TICKICON : 'glyphicon-ok'
2022-07-08 11:28:27 +02:00
} ;
2022-03-30 07:54:07 +02:00
var Selector = {
2022-04-13 12:34:14 +02:00
MENU : '.' + classNames . MENU ,
DATA _TOGGLE : 'data-toggle="dropdown"'
2022-07-08 11:28:27 +02:00
} ;
2022-03-30 07:54:07 +02:00
var elementTemplates = {
2022-04-13 12:34:14 +02:00
div : document . createElement ( 'div' ) ,
2022-03-30 07:54:07 +02:00
span : document . createElement ( 'span' ) ,
i : document . createElement ( 'i' ) ,
subtext : document . createElement ( 'small' ) ,
a : document . createElement ( 'a' ) ,
li : document . createElement ( 'li' ) ,
whitespace : document . createTextNode ( '\u00A0' ) ,
2022-04-13 12:34:14 +02:00
fragment : document . createDocumentFragment ( ) ,
option : document . createElement ( 'option' )
2022-07-08 11:28:27 +02:00
} ;
2022-03-30 07:54:07 +02:00
2022-04-13 12:34:14 +02:00
elementTemplates . selectedOption = elementTemplates . option . cloneNode ( false ) ;
elementTemplates . selectedOption . setAttribute ( 'selected' , true ) ;
elementTemplates . noResults = elementTemplates . li . cloneNode ( false ) ;
elementTemplates . noResults . className = 'no-results' ;
2022-03-30 07:54:07 +02:00
elementTemplates . a . setAttribute ( 'role' , 'option' ) ;
2022-04-13 12:34:14 +02:00
elementTemplates . a . className = 'dropdown-item' ;
2022-03-30 07:54:07 +02:00
elementTemplates . subtext . className = 'text-muted' ;
elementTemplates . text = elementTemplates . span . cloneNode ( false ) ;
elementTemplates . text . className = 'text' ;
elementTemplates . checkMark = elementTemplates . span . cloneNode ( false ) ;
var REGEXP _ARROW = new RegExp ( keyCodes . ARROW _UP + '|' + keyCodes . ARROW _DOWN ) ;
var REGEXP _TAB _OR _ESCAPE = new RegExp ( '^' + keyCodes . TAB + '$|' + keyCodes . ESCAPE ) ;
var generateOption = {
li : function ( content , classes , optgroup ) {
var li = elementTemplates . li . cloneNode ( false ) ;
if ( content ) {
if ( content . nodeType === 1 || content . nodeType === 11 ) {
li . appendChild ( content ) ;
} else {
li . innerHTML = content ;
}
}
if ( typeof classes !== 'undefined' && classes !== '' ) li . className = classes ;
if ( typeof optgroup !== 'undefined' && optgroup !== null ) li . classList . add ( 'optgroup-' + optgroup ) ;
return li ;
} ,
a : function ( text , classes , inline ) {
var a = elementTemplates . a . cloneNode ( true ) ;
if ( text ) {
if ( text . nodeType === 11 ) {
a . appendChild ( text ) ;
} else {
a . insertAdjacentHTML ( 'beforeend' , text ) ;
}
}
2022-04-13 12:34:14 +02:00
if ( typeof classes !== 'undefined' && classes !== '' ) a . classList . add . apply ( a . classList , classes . split ( /\s+/ ) ) ;
2022-03-30 07:54:07 +02:00
if ( inline ) a . setAttribute ( 'style' , inline ) ;
return a ;
} ,
text : function ( options , useFragment ) {
var textElement = elementTemplates . text . cloneNode ( false ) ,
subtextElement ,
iconElement ;
if ( options . content ) {
textElement . innerHTML = options . content ;
} else {
textElement . textContent = options . text ;
if ( options . icon ) {
var whitespace = elementTemplates . whitespace . cloneNode ( false ) ;
// need to use <i> for icons in the button to prevent a breaking change
// note: switch to span in next major release
iconElement = ( useFragment === true ? elementTemplates . i : elementTemplates . span ) . cloneNode ( false ) ;
iconElement . className = this . options . iconBase + ' ' + options . icon ;
elementTemplates . fragment . appendChild ( iconElement ) ;
elementTemplates . fragment . appendChild ( whitespace ) ;
}
if ( options . subtext ) {
subtextElement = elementTemplates . subtext . cloneNode ( false ) ;
subtextElement . textContent = options . subtext ;
textElement . appendChild ( subtextElement ) ;
}
}
if ( useFragment === true ) {
while ( textElement . childNodes . length > 0 ) {
elementTemplates . fragment . appendChild ( textElement . childNodes [ 0 ] ) ;
}
} else {
elementTemplates . fragment . appendChild ( textElement ) ;
}
return elementTemplates . fragment ;
} ,
label : function ( options ) {
var textElement = elementTemplates . text . cloneNode ( false ) ,
subtextElement ,
iconElement ;
textElement . innerHTML = options . display ;
if ( options . icon ) {
var whitespace = elementTemplates . whitespace . cloneNode ( false ) ;
iconElement = elementTemplates . span . cloneNode ( false ) ;
iconElement . className = this . options . iconBase + ' ' + options . icon ;
elementTemplates . fragment . appendChild ( iconElement ) ;
elementTemplates . fragment . appendChild ( whitespace ) ;
}
if ( options . subtext ) {
subtextElement = elementTemplates . subtext . cloneNode ( false ) ;
subtextElement . textContent = options . subtext ;
textElement . appendChild ( subtextElement ) ;
}
elementTemplates . fragment . appendChild ( textElement ) ;
return elementTemplates . fragment ;
}
2022-07-08 11:28:27 +02:00
} ;
2022-03-30 07:54:07 +02:00
2022-04-13 12:34:14 +02:00
var getOptionData = {
fromOption : function ( option , type ) {
var value ;
switch ( type ) {
case 'divider' :
value = option . getAttribute ( 'data-divider' ) === 'true' ;
break ;
case 'text' :
value = option . textContent ;
break ;
case 'label' :
value = option . label ;
break ;
case 'style' :
value = option . style . cssText ;
break ;
2022-07-08 11:28:27 +02:00
case 'title' :
value = option . title ;
break ;
default :
value = option . getAttribute ( 'data-' + toKebabCase ( type ) ) ;
2022-04-13 12:34:14 +02:00
break ;
}
return value ;
} ,
fromDataSource : function ( option , type ) {
var value ;
switch ( type ) {
case 'text' :
case 'label' :
value = option . text || option . value || '' ;
break ;
2022-07-08 11:28:27 +02:00
default :
2022-04-13 12:34:14 +02:00
value = option [ type ] ;
break ;
}
return value ;
}
2022-07-08 11:28:27 +02:00
} ;
2022-04-13 12:34:14 +02:00
function showNoResults ( searchMatch , searchValue ) {
if ( ! searchMatch . length ) {
elementTemplates . noResults . innerHTML = this . options . noneResultsText . replace ( '{0}' , '"' + htmlEscape ( searchValue ) + '"' ) ;
this . $menuInner [ 0 ] . firstChild . appendChild ( elementTemplates . noResults ) ;
}
}
function filterHidden ( item ) {
return ! ( item . hidden || this . options . hideDisabled && item . disabled ) ;
}
2022-03-30 07:54:07 +02:00
var Selectpicker = function ( element , options ) {
var that = this ;
// bootstrap-select has been initialized - revert valHooks.select.set back to its original function
if ( ! valHooks . useDefault ) {
$ . valHooks . select . set = valHooks . _set ;
valHooks . useDefault = true ;
}
this . $element = $ ( element ) ;
this . $newElement = null ;
this . $button = null ;
this . $menu = null ;
this . options = options ;
this . selectpicker = {
2022-04-13 12:34:14 +02:00
main : {
2022-07-08 11:28:27 +02:00
data : [ ] ,
optionQueue : elementTemplates . fragment . cloneNode ( false ) ,
hasMore : false
2022-04-13 12:34:14 +02:00
} ,
2022-07-08 11:28:27 +02:00
search : {
data : [ ] ,
hasMore : false
} ,
current : { } , // current is either equal to main or search depending on if a search is in progress
2022-03-30 07:54:07 +02:00
view : { } ,
2022-07-08 11:28:27 +02:00
// map of option values and their respective data (only used in conjunction with options.source)
optionValuesDataMap : { } ,
2022-03-30 07:54:07 +02:00
isSearching : false ,
keydown : {
keyHistory : '' ,
resetKeyHistory : {
start : function ( ) {
return setTimeout ( function ( ) {
that . selectpicker . keydown . keyHistory = '' ;
} , 800 ) ;
}
}
}
} ;
this . sizeInfo = { } ;
// Format window padding
var winPad = this . options . windowPadding ;
if ( typeof winPad === 'number' ) {
this . options . windowPadding = [ winPad , winPad , winPad , winPad ] ;
}
// Expose public methods
this . val = Selectpicker . prototype . val ;
this . render = Selectpicker . prototype . render ;
this . refresh = Selectpicker . prototype . refresh ;
this . setStyle = Selectpicker . prototype . setStyle ;
this . selectAll = Selectpicker . prototype . selectAll ;
this . deselectAll = Selectpicker . prototype . deselectAll ;
this . destroy = Selectpicker . prototype . destroy ;
this . remove = Selectpicker . prototype . remove ;
this . show = Selectpicker . prototype . show ;
this . hide = Selectpicker . prototype . hide ;
this . init ( ) ;
} ;
2022-07-08 11:28:27 +02:00
Selectpicker . VERSION = '1.14.0-beta3' ;
2022-03-30 07:54:07 +02:00
// part of this is duplicated in i18n/defaults-en_US.js. Make sure to update both.
Selectpicker . DEFAULTS = {
noneSelectedText : 'Nothing selected' ,
noneResultsText : 'No results matched {0}' ,
countSelectedText : function ( numSelected , numTotal ) {
return ( numSelected == 1 ) ? '{0} item selected' : '{0} items selected' ;
} ,
maxOptionsText : function ( numAll , numGroup ) {
return [
( numAll == 1 ) ? 'Limit reached ({n} item max)' : 'Limit reached ({n} items max)' ,
( numGroup == 1 ) ? 'Group limit reached ({n} item max)' : 'Group limit reached ({n} items max)'
] ;
} ,
selectAllText : 'Select All' ,
deselectAllText : 'Deselect All' ,
2022-07-08 11:28:27 +02:00
source : {
pageSize : 40
} ,
2022-04-13 12:34:14 +02:00
chunkSize : 40 ,
2022-03-30 07:54:07 +02:00
doneButton : false ,
doneButtonText : 'Close' ,
multipleSeparator : ', ' ,
styleBase : 'btn' ,
style : classNames . BUTTONCLASS ,
size : 'auto' ,
title : null ,
2022-04-13 12:34:14 +02:00
placeholder : null ,
allowClear : false ,
2022-03-30 07:54:07 +02:00
selectedTextFormat : 'values' ,
width : false ,
container : false ,
hideDisabled : false ,
showSubtext : false ,
showIcon : true ,
showContent : true ,
dropupAuto : true ,
header : false ,
liveSearch : false ,
liveSearchPlaceholder : null ,
liveSearchNormalize : false ,
liveSearchStyle : 'contains' ,
actionsBox : false ,
iconBase : classNames . ICONBASE ,
tickIcon : classNames . TICKICON ,
showTick : false ,
template : {
caret : '<span class="caret"></span>'
} ,
maxOptions : false ,
mobile : false ,
2022-07-08 11:28:27 +02:00
selectOnTab : true ,
2022-03-30 07:54:07 +02:00
dropdownAlignRight : false ,
windowPadding : 0 ,
virtualScroll : 600 ,
display : false ,
sanitize : true ,
sanitizeFn : null ,
whiteList : DefaultWhitelist
} ;
Selectpicker . prototype = {
constructor : Selectpicker ,
init : function ( ) {
var that = this ,
2022-04-13 12:34:14 +02:00
id = this . $element . attr ( 'id' ) ,
element = this . $element [ 0 ] ,
form = element . form ;
2022-03-30 07:54:07 +02:00
selectId ++ ;
this . selectId = 'bs-select-' + selectId ;
2022-04-13 12:34:14 +02:00
element . classList . add ( 'bs-select-hidden' ) ;
2022-03-30 07:54:07 +02:00
this . multiple = this . $element . prop ( 'multiple' ) ;
this . autofocus = this . $element . prop ( 'autofocus' ) ;
2022-04-13 12:34:14 +02:00
if ( element . classList . contains ( 'show-tick' ) ) {
2022-03-30 07:54:07 +02:00
this . options . showTick = true ;
}
this . $newElement = this . createDropdown ( ) ;
2022-04-13 12:34:14 +02:00
2022-03-30 07:54:07 +02:00
this . $element
. after ( this . $newElement )
. prependTo ( this . $newElement ) ;
2022-04-13 12:34:14 +02:00
// ensure select is associated with form element if it got unlinked after moving it inside newElement
if ( form && element . form === null ) {
if ( ! form . id ) form . id = 'form-' + this . selectId ;
element . setAttribute ( 'form' , form . id ) ;
}
2022-03-30 07:54:07 +02:00
this . $button = this . $newElement . children ( 'button' ) ;
2022-04-13 12:34:14 +02:00
if ( this . options . allowClear ) this . $clearButton = this . $button . children ( '.bs-select-clear-selected' ) ;
2022-03-30 07:54:07 +02:00
this . $menu = this . $newElement . children ( Selector . MENU ) ;
this . $menuInner = this . $menu . children ( '.inner' ) ;
this . $searchbox = this . $menu . find ( 'input' ) ;
2022-04-13 12:34:14 +02:00
element . classList . remove ( 'bs-select-hidden' ) ;
this . fetchData ( function ( ) {
that . render ( true ) ;
that . buildList ( ) ;
requestAnimationFrame ( function ( ) {
that . $element . trigger ( 'loaded' + EVENT _KEY ) ;
} ) ;
} ) ;
2022-03-30 07:54:07 +02:00
if ( this . options . dropdownAlignRight === true ) this . $menu [ 0 ] . classList . add ( classNames . MENURIGHT ) ;
if ( typeof id !== 'undefined' ) {
this . $button . attr ( 'data-id' , id ) ;
}
this . checkDisabled ( ) ;
this . clickListener ( ) ;
2022-04-13 12:34:14 +02:00
if ( version . major > 4 ) this . dropdown = new Dropdown ( this . $button [ 0 ] ) ;
2022-03-30 07:54:07 +02:00
if ( this . options . liveSearch ) {
this . liveSearchListener ( ) ;
this . focusedParent = this . $searchbox [ 0 ] ;
} else {
this . focusedParent = this . $menuInner [ 0 ] ;
}
this . setStyle ( ) ;
this . setWidth ( ) ;
if ( this . options . container ) {
this . selectPosition ( ) ;
} else {
this . $element . on ( 'hide' + EVENT _KEY , function ( ) {
if ( that . isVirtual ( ) ) {
// empty menu on close
var menuInner = that . $menuInner [ 0 ] ,
emptyMenu = menuInner . firstChild . cloneNode ( false ) ;
// replace the existing UL with an empty one - this is faster than $.empty() or innerHTML = ''
menuInner . replaceChild ( emptyMenu , menuInner . firstChild ) ;
menuInner . scrollTop = 0 ;
}
} ) ;
}
this . $menu . data ( 'this' , this ) ;
this . $newElement . data ( 'this' , this ) ;
if ( this . options . mobile ) this . mobile ( ) ;
this . $newElement . on ( {
'hide.bs.dropdown' : function ( e ) {
that . $element . trigger ( 'hide' + EVENT _KEY , e ) ;
} ,
'hidden.bs.dropdown' : function ( e ) {
that . $element . trigger ( 'hidden' + EVENT _KEY , e ) ;
} ,
'show.bs.dropdown' : function ( e ) {
that . $element . trigger ( 'show' + EVENT _KEY , e ) ;
} ,
'shown.bs.dropdown' : function ( e ) {
that . $element . trigger ( 'shown' + EVENT _KEY , e ) ;
}
} ) ;
2022-04-13 12:34:14 +02:00
if ( element . hasAttribute ( 'required' ) ) {
2022-03-30 07:54:07 +02:00
this . $element . on ( 'invalid' + EVENT _KEY , function ( ) {
that . $button [ 0 ] . classList . add ( 'bs-invalid' ) ;
that . $element
. on ( 'shown' + EVENT _KEY + '.invalid' , function ( ) {
that . $element
. val ( that . $element . val ( ) ) // set the value to hide the validation message in Chrome when menu is opened
. off ( 'shown' + EVENT _KEY + '.invalid' ) ;
} )
. on ( 'rendered' + EVENT _KEY , function ( ) {
// if select is no longer invalid, remove the bs-invalid class
if ( this . validity . valid ) that . $button [ 0 ] . classList . remove ( 'bs-invalid' ) ;
that . $element . off ( 'rendered' + EVENT _KEY ) ;
} ) ;
that . $button . on ( 'blur' + EVENT _KEY , function ( ) {
that . $element . trigger ( 'focus' ) . trigger ( 'blur' ) ;
that . $button . off ( 'blur' + EVENT _KEY ) ;
} ) ;
} ) ;
}
2022-04-13 12:34:14 +02:00
if ( form ) {
$ ( form ) . on ( 'reset' + EVENT _KEY , function ( ) {
requestAnimationFrame ( function ( ) {
that . render ( ) ;
} ) ;
} ) ;
}
2022-03-30 07:54:07 +02:00
} ,
createDropdown : function ( ) {
// Options
// If we are multiple or showTick option is set, then add the show-tick class
var showTick = ( this . multiple || this . options . showTick ) ? ' show-tick' : '' ,
multiselectable = this . multiple ? ' aria-multiselectable="true"' : '' ,
inputGroup = '' ,
autofocus = this . autofocus ? ' autofocus' : '' ;
if ( version . major < 4 && this . $element . parent ( ) . hasClass ( 'input-group' ) ) {
inputGroup = ' input-group-btn' ;
}
// Elements
var drop ,
header = '' ,
searchbox = '' ,
actionsbox = '' ,
2022-04-13 12:34:14 +02:00
donebutton = '' ,
clearButton = '' ;
2022-03-30 07:54:07 +02:00
if ( this . options . header ) {
header =
'<div class="' + classNames . POPOVERHEADER + '">' +
'<button type="button" class="close" aria-hidden="true">×</button>' +
this . options . header +
'</div>' ;
}
if ( this . options . liveSearch ) {
searchbox =
'<div class="bs-searchbox">' +
'<input type="search" class="form-control" autocomplete="off"' +
(
this . options . liveSearchPlaceholder === null ? ''
:
' placeholder="' + htmlEscape ( this . options . liveSearchPlaceholder ) + '"'
) +
' role="combobox" aria-label="Search" aria-controls="' + this . selectId + '" aria-autocomplete="list">' +
'</div>' ;
}
if ( this . multiple && this . options . actionsBox ) {
actionsbox =
'<div class="bs-actionsbox">' +
2022-04-13 12:34:14 +02:00
'<div class="btn-group btn-group-sm">' +
2022-03-30 07:54:07 +02:00
'<button type="button" class="actions-btn bs-select-all btn ' + classNames . BUTTONCLASS + '">' +
this . options . selectAllText +
'</button>' +
'<button type="button" class="actions-btn bs-deselect-all btn ' + classNames . BUTTONCLASS + '">' +
this . options . deselectAllText +
'</button>' +
'</div>' +
'</div>' ;
}
if ( this . multiple && this . options . doneButton ) {
donebutton =
'<div class="bs-donebutton">' +
2022-04-13 12:34:14 +02:00
'<div class="btn-group">' +
2022-03-30 07:54:07 +02:00
'<button type="button" class="btn btn-sm ' + classNames . BUTTONCLASS + '">' +
this . options . doneButtonText +
'</button>' +
'</div>' +
'</div>' ;
}
2022-04-13 12:34:14 +02:00
if ( this . options . allowClear ) {
clearButton = '<span class="close bs-select-clear-selected" title="' + this . options . deselectAllText + '"><span>×</span>' ;
}
2022-03-30 07:54:07 +02:00
drop =
'<div class="dropdown bootstrap-select' + showTick + inputGroup + '">' +
2022-04-13 12:34:14 +02:00
'<button type="button" tabindex="-1" class="' +
this . options . styleBase +
' dropdown-toggle" ' +
( this . options . display === 'static' ? 'data-display="static"' : '' ) +
Selector . DATA _TOGGLE +
autofocus +
' role="combobox" aria-owns="' +
this . selectId +
'" aria-haspopup="listbox" aria-expanded="false">' +
2022-03-30 07:54:07 +02:00
'<div class="filter-option">' +
'<div class="filter-option-inner">' +
2022-04-13 12:34:14 +02:00
'<div class="filter-option-inner-inner"> </div>' +
2022-03-30 07:54:07 +02:00
'</div> ' +
'</div>' +
2022-04-13 12:34:14 +02:00
clearButton +
'</span>' +
2022-03-30 07:54:07 +02:00
(
2022-04-13 12:34:14 +02:00
version . major >= '4' ? ''
2022-03-30 07:54:07 +02:00
:
'<span class="bs-caret">' +
this . options . template . caret +
'</span>'
) +
'</button>' +
2022-04-13 12:34:14 +02:00
'<div class="' + classNames . MENU + ' ' + ( version . major >= '4' ? '' : classNames . SHOW ) + '">' +
2022-03-30 07:54:07 +02:00
header +
searchbox +
actionsbox +
'<div class="inner ' + classNames . SHOW + '" role="listbox" id="' + this . selectId + '" tabindex="-1" ' + multiselectable + '>' +
2022-04-13 12:34:14 +02:00
'<ul class="' + classNames . MENU + ' inner ' + ( version . major >= '4' ? classNames . SHOW : '' ) + '" role="presentation">' +
2022-03-30 07:54:07 +02:00
'</ul>' +
'</div>' +
donebutton +
'</div>' +
'</div>' ;
return $ ( drop ) ;
} ,
setPositionData : function ( ) {
this . selectpicker . view . canHighlight = [ ] ;
this . selectpicker . view . size = 0 ;
2022-04-13 12:34:14 +02:00
this . selectpicker . view . firstHighlightIndex = false ;
2022-03-30 07:54:07 +02:00
for ( var i = 0 ; i < this . selectpicker . current . data . length ; i ++ ) {
var li = this . selectpicker . current . data [ i ] ,
canHighlight = true ;
if ( li . type === 'divider' ) {
canHighlight = false ;
li . height = this . sizeInfo . dividerHeight ;
} else if ( li . type === 'optgroup-label' ) {
canHighlight = false ;
li . height = this . sizeInfo . dropdownHeaderHeight ;
} else {
li . height = this . sizeInfo . liHeight ;
}
if ( li . disabled ) canHighlight = false ;
this . selectpicker . view . canHighlight . push ( canHighlight ) ;
if ( canHighlight ) {
this . selectpicker . view . size ++ ;
li . posinset = this . selectpicker . view . size ;
2022-04-13 12:34:14 +02:00
if ( this . selectpicker . view . firstHighlightIndex === false ) this . selectpicker . view . firstHighlightIndex = i ;
2022-03-30 07:54:07 +02:00
}
li . position = ( i === 0 ? 0 : this . selectpicker . current . data [ i - 1 ] . position ) + li . height ;
}
} ,
isVirtual : function ( ) {
2022-04-13 12:34:14 +02:00
return ( this . options . virtualScroll !== false ) && ( this . selectpicker . main . data . length >= this . options . virtualScroll ) || this . options . virtualScroll === true ;
2022-03-30 07:54:07 +02:00
} ,
createView : function ( isSearching , setSize , refresh ) {
var that = this ,
2022-07-08 11:28:27 +02:00
scrollTop = 0 ;
2022-03-30 07:54:07 +02:00
this . selectpicker . isSearching = isSearching ;
this . selectpicker . current = isSearching ? this . selectpicker . search : this . selectpicker . main ;
this . setPositionData ( ) ;
if ( setSize ) {
if ( refresh ) {
scrollTop = this . $menuInner [ 0 ] . scrollTop ;
} else if ( ! that . multiple ) {
var element = that . $element [ 0 ] ,
selectedIndex = ( element . options [ element . selectedIndex ] || { } ) . liIndex ;
if ( typeof selectedIndex === 'number' && that . options . size !== false ) {
var selectedData = that . selectpicker . main . data [ selectedIndex ] ,
position = selectedData && selectedData . position ;
if ( position ) {
scrollTop = position - ( ( that . sizeInfo . menuInnerHeight + that . sizeInfo . liHeight ) / 2 ) ;
}
}
}
}
scroll ( scrollTop , true ) ;
this . $menuInner . off ( 'scroll.createView' ) . on ( 'scroll.createView' , function ( e , updateValue ) {
if ( ! that . noScroll ) scroll ( this . scrollTop , updateValue ) ;
that . noScroll = false ;
} ) ;
function scroll ( scrollTop , init ) {
2022-04-13 12:34:14 +02:00
var size = that . selectpicker . current . data . length ,
2022-03-30 07:54:07 +02:00
chunks = [ ] ,
chunkSize ,
chunkCount ,
firstChunk ,
lastChunk ,
currentChunk ,
prevPositions ,
positionIsDifferent ,
previousElements ,
menuIsDifferent = true ,
isVirtual = that . isVirtual ( ) ;
that . selectpicker . view . scrollTop = scrollTop ;
2022-04-13 12:34:14 +02:00
chunkSize = that . options . chunkSize ; // number of options in a chunk
chunkCount = Math . ceil ( size / chunkSize ) || 1 ; // number of chunks
2022-03-30 07:54:07 +02:00
for ( var i = 0 ; i < chunkCount ; i ++ ) {
var endOfChunk = ( i + 1 ) * chunkSize ;
if ( i === chunkCount - 1 ) {
endOfChunk = size ;
}
chunks [ i ] = [
( i ) * chunkSize + ( ! i ? 0 : 1 ) ,
endOfChunk
] ;
if ( ! size ) break ;
if ( currentChunk === undefined && scrollTop - 1 <= that . selectpicker . current . data [ endOfChunk - 1 ] . position - that . sizeInfo . menuInnerHeight ) {
currentChunk = i ;
}
}
if ( currentChunk === undefined ) currentChunk = 0 ;
prevPositions = [ that . selectpicker . view . position0 , that . selectpicker . view . position1 ] ;
// always display previous, current, and next chunks
firstChunk = Math . max ( 0 , currentChunk - 1 ) ;
lastChunk = Math . min ( chunkCount - 1 , currentChunk + 1 ) ;
that . selectpicker . view . position0 = isVirtual === false ? 0 : ( Math . max ( 0 , chunks [ firstChunk ] [ 0 ] ) || 0 ) ;
that . selectpicker . view . position1 = isVirtual === false ? size : ( Math . min ( size , chunks [ lastChunk ] [ 1 ] ) || 0 ) ;
positionIsDifferent = prevPositions [ 0 ] !== that . selectpicker . view . position0 || prevPositions [ 1 ] !== that . selectpicker . view . position1 ;
2022-07-08 11:28:27 +02:00
if ( that . activeElement !== undefined ) {
2022-03-30 07:54:07 +02:00
if ( init ) {
2022-07-08 11:28:27 +02:00
if ( that . activeElement !== that . selectedElement ) {
that . defocusItem ( that . activeElement ) ;
2022-03-30 07:54:07 +02:00
}
2022-07-08 11:28:27 +02:00
that . activeElement = undefined ;
2022-03-30 07:54:07 +02:00
}
2022-07-08 11:28:27 +02:00
if ( that . activeElement !== that . selectedElement ) {
that . defocusItem ( that . selectedElement ) ;
2022-03-30 07:54:07 +02:00
}
}
2022-07-08 11:28:27 +02:00
if ( that . prevActiveElement !== undefined && that . prevActiveElement !== that . activeElement && that . prevActiveElement !== that . selectedElement ) {
that . defocusItem ( that . prevActiveElement ) ;
2022-03-30 07:54:07 +02:00
}
2022-07-08 11:28:27 +02:00
if ( init || positionIsDifferent || that . selectpicker . current . hasMore ) {
2022-03-30 07:54:07 +02:00
previousElements = that . selectpicker . view . visibleElements ? that . selectpicker . view . visibleElements . slice ( ) : [ ] ;
if ( isVirtual === false ) {
that . selectpicker . view . visibleElements = that . selectpicker . current . elements ;
} else {
that . selectpicker . view . visibleElements = that . selectpicker . current . elements . slice ( that . selectpicker . view . position0 , that . selectpicker . view . position1 ) ;
}
that . setOptionStatus ( ) ;
// if searching, check to make sure the list has actually been updated before updating DOM
// this prevents unnecessary repaints
if ( isSearching || ( isVirtual === false && init ) ) menuIsDifferent = ! isEqual ( previousElements , that . selectpicker . view . visibleElements ) ;
// if virtual scroll is disabled and not searching,
// menu should never need to be updated more than once
if ( ( init || isVirtual === true ) && menuIsDifferent ) {
var menuInner = that . $menuInner [ 0 ] ,
menuFragment = document . createDocumentFragment ( ) ,
emptyMenu = menuInner . firstChild . cloneNode ( false ) ,
marginTop ,
marginBottom ,
elements = that . selectpicker . view . visibleElements ,
toSanitize = [ ] ;
// replace the existing UL with an empty one - this is faster than $.empty()
menuInner . replaceChild ( emptyMenu , menuInner . firstChild ) ;
for ( var i = 0 , visibleElementsLen = elements . length ; i < visibleElementsLen ; i ++ ) {
var element = elements [ i ] ,
elText ,
elementData ;
if ( that . options . sanitize ) {
elText = element . lastChild ;
if ( elText ) {
elementData = that . selectpicker . current . data [ i + that . selectpicker . view . position0 ] ;
if ( elementData && elementData . content && ! elementData . sanitized ) {
toSanitize . push ( elText ) ;
elementData . sanitized = true ;
}
}
}
menuFragment . appendChild ( element ) ;
}
if ( that . options . sanitize && toSanitize . length ) {
sanitizeHtml ( toSanitize , that . options . whiteList , that . options . sanitizeFn ) ;
}
if ( isVirtual === true ) {
marginTop = ( that . selectpicker . view . position0 === 0 ? 0 : that . selectpicker . current . data [ that . selectpicker . view . position0 - 1 ] . position ) ;
marginBottom = ( that . selectpicker . view . position1 > size - 1 ? 0 : that . selectpicker . current . data [ size - 1 ] . position - that . selectpicker . current . data [ that . selectpicker . view . position1 - 1 ] . position ) ;
menuInner . firstChild . style . marginTop = marginTop + 'px' ;
menuInner . firstChild . style . marginBottom = marginBottom + 'px' ;
} else {
menuInner . firstChild . style . marginTop = 0 ;
menuInner . firstChild . style . marginBottom = 0 ;
}
menuInner . firstChild . appendChild ( menuFragment ) ;
// if an option is encountered that is wider than the current menu width, update the menu width accordingly
// switch to ResizeObserver with increased browser support
if ( isVirtual === true && that . sizeInfo . hasScrollBar ) {
var menuInnerInnerWidth = menuInner . firstChild . offsetWidth ;
if ( init && menuInnerInnerWidth < that . sizeInfo . menuInnerInnerWidth && that . sizeInfo . totalMenuWidth > that . sizeInfo . selectWidth ) {
menuInner . firstChild . style . minWidth = that . sizeInfo . menuInnerInnerWidth + 'px' ;
} else if ( menuInnerInnerWidth > that . sizeInfo . menuInnerInnerWidth ) {
// set to 0 to get actual width of menu
that . $menu [ 0 ] . style . minWidth = 0 ;
var actualMenuWidth = menuInner . firstChild . offsetWidth ;
if ( actualMenuWidth > that . sizeInfo . menuInnerInnerWidth ) {
that . sizeInfo . menuInnerInnerWidth = actualMenuWidth ;
menuInner . firstChild . style . minWidth = that . sizeInfo . menuInnerInnerWidth + 'px' ;
}
// reset to default CSS styling
that . $menu [ 0 ] . style . minWidth = '' ;
}
}
}
2022-04-13 12:34:14 +02:00
2022-07-08 11:28:27 +02:00
if ( ( ! isSearching && that . options . source . data || isSearching && that . options . source . search ) && that . selectpicker . current . hasMore && currentChunk === chunkCount - 1 ) {
// Don't load the next chunk until scrolling has started
// This prevents unnecessary requests while the user is typing if pageSize is <= chunkSize
if ( scrollTop > 0 ) {
// Chunks use 0-based indexing, but pages use 1-based. Add 1 to convert and add 1 again to get next page
var page = Math . floor ( ( currentChunk * that . options . chunkSize ) / that . options . source . pageSize ) + 2 ;
that . fetchData ( function ( ) {
that . render ( ) ;
that . buildList ( size , isSearching ) ;
that . setPositionData ( ) ;
scroll ( scrollTop ) ;
} , isSearching ? 'search' : 'data' , page , isSearching ? that . selectpicker . search . previousValue : undefined ) ;
}
2022-04-13 12:34:14 +02:00
}
2022-03-30 07:54:07 +02:00
}
2022-07-08 11:28:27 +02:00
that . prevActiveElement = that . activeElement ;
2022-03-30 07:54:07 +02:00
if ( ! that . options . liveSearch ) {
that . $menuInner . trigger ( 'focus' ) ;
} else if ( isSearching && init ) {
var index = 0 ,
newActive ;
if ( ! that . selectpicker . view . canHighlight [ index ] ) {
index = 1 + that . selectpicker . view . canHighlight . slice ( 1 ) . indexOf ( true ) ;
}
newActive = that . selectpicker . view . visibleElements [ index ] ;
that . defocusItem ( that . selectpicker . view . currentActive ) ;
2022-07-08 11:28:27 +02:00
that . activeElement = ( that . selectpicker . current . data [ index ] || { } ) . element ;
2022-03-30 07:54:07 +02:00
that . focusItem ( newActive ) ;
}
}
$ ( window )
. off ( 'resize' + EVENT _KEY + '.' + this . selectId + '.createView' )
. on ( 'resize' + EVENT _KEY + '.' + this . selectId + '.createView' , function ( ) {
var isActive = that . $newElement . hasClass ( classNames . SHOW ) ;
if ( isActive ) scroll ( that . $menuInner [ 0 ] . scrollTop ) ;
} ) ;
} ,
focusItem : function ( li , liData , noStyle ) {
if ( li ) {
2022-07-08 11:28:27 +02:00
liData = liData || this . selectpicker . current . data [ this . selectpicker . current . elements . indexOf ( this . activeElement ) ] ;
2022-03-30 07:54:07 +02:00
var a = li . firstChild ;
if ( a ) {
a . setAttribute ( 'aria-setsize' , this . selectpicker . view . size ) ;
a . setAttribute ( 'aria-posinset' , liData . posinset ) ;
if ( noStyle !== true ) {
this . focusedParent . setAttribute ( 'aria-activedescendant' , a . id ) ;
li . classList . add ( 'active' ) ;
a . classList . add ( 'active' ) ;
}
}
}
} ,
defocusItem : function ( li ) {
if ( li ) {
li . classList . remove ( 'active' ) ;
if ( li . firstChild ) li . firstChild . classList . remove ( 'active' ) ;
}
} ,
setPlaceholder : function ( ) {
2022-04-13 12:34:14 +02:00
var that = this ,
updateIndex = false ;
2022-03-30 07:54:07 +02:00
2022-04-13 12:34:14 +02:00
if ( ( this . options . placeholder || this . options . allowClear ) && ! this . multiple ) {
2022-03-30 07:54:07 +02:00
if ( ! this . selectpicker . view . titleOption ) this . selectpicker . view . titleOption = document . createElement ( 'option' ) ;
// this option doesn't create a new <li> element, but does add a new option at the start,
// so startIndex should increase to prevent having to check every option for the bs-title-option class
updateIndex = true ;
var element = this . $element [ 0 ] ,
2022-04-13 12:34:14 +02:00
selectTitleOption = false ,
titleNotAppended = ! this . selectpicker . view . titleOption . parentNode ,
selectedIndex = element . selectedIndex ,
selectedOption = element . options [ selectedIndex ] ,
firstSelectable = element . querySelector ( 'select > *:not(:disabled)' ) ,
firstSelectableIndex = firstSelectable ? firstSelectable . index : 0 ,
navigation = window . performance && window . performance . getEntriesByType ( 'navigation' ) ,
// Safari doesn't support getEntriesByType('navigation') - fall back to performance.navigation
isNotBackForward = ( navigation && navigation . length ) ? navigation [ 0 ] . type !== 'back_forward' : window . performance . navigation . type !== 2 ;
2022-03-30 07:54:07 +02:00
if ( titleNotAppended ) {
// Use native JS to prepend option (faster)
this . selectpicker . view . titleOption . className = 'bs-title-option' ;
this . selectpicker . view . titleOption . value = '' ;
// Check if selected or data-selected attribute is already set on an option. If not, select the titleOption option.
// the selected item may have been changed by user or programmatically before the bootstrap select plugin runs,
// if so, the select will have the data-selected attribute
2022-04-13 12:34:14 +02:00
selectTitleOption = ! selectedOption || ( selectedIndex === firstSelectableIndex && selectedOption . defaultSelected === false && this . $element . data ( 'selected' ) === undefined ) ;
2022-03-30 07:54:07 +02:00
}
if ( titleNotAppended || this . selectpicker . view . titleOption . index !== 0 ) {
element . insertBefore ( this . selectpicker . view . titleOption , element . firstChild ) ;
}
// Set selected *after* appending to select,
// otherwise the option doesn't get selected in IE
// set using selectedIndex, as setting the selected attr to true here doesn't work in IE11
2022-04-13 12:34:14 +02:00
if ( selectTitleOption && isNotBackForward ) {
element . selectedIndex = 0 ;
} else if ( document . readyState !== 'complete' ) {
// if navigation type is back_forward, there's a chance the select will have its value set by BFCache
// wait for that value to be set, then run render again
window . addEventListener ( 'pageshow' , function ( ) {
if ( that . selectpicker . view . displayedValue !== element . value ) that . render ( ) ;
} ) ;
}
2022-03-30 07:54:07 +02:00
}
return updateIndex ;
} ,
2022-04-13 12:34:14 +02:00
fetchData : function ( callback , type , page , searchValue ) {
2022-07-08 11:28:27 +02:00
page = page || 1 ;
2022-04-13 12:34:14 +02:00
type = type || 'data' ;
var that = this ,
data = this . options . source [ type ] ,
builtData ;
if ( data ) {
this . options . virtualScroll = true ;
if ( typeof data === 'function' ) {
data . call (
this ,
2022-07-08 11:28:27 +02:00
function ( data , more , totalItems ) {
var current = that . selectpicker [ type === 'search' ? 'search' : 'main' ] ;
current . hasMore = more ;
current . totalItems = totalItems ;
2022-04-13 12:34:14 +02:00
builtData = that . buildData ( data , type ) ;
callback . call ( that , builtData ) ;
2022-07-08 11:28:27 +02:00
that . $element . trigger ( 'fetched' + EVENT _KEY ) ;
2022-04-13 12:34:14 +02:00
} ,
page ,
searchValue
) ;
} else if ( Array . isArray ( data ) ) {
builtData = that . buildData ( data , type ) ;
callback . call ( that , builtData ) ;
}
} else {
builtData = this . buildData ( false , type ) ;
callback . call ( that , builtData ) ;
}
} ,
buildData : function ( data , type ) {
2022-07-08 11:28:27 +02:00
var that = this ;
2022-04-13 12:34:14 +02:00
var dataGetter = data === false ? getOptionData . fromOption : getOptionData . fromDataSource ;
2022-07-08 11:28:27 +02:00
var optionSelector = ':not([hidden]):not([data-hidden="true"]):not([style*="display: none"])' ,
2022-03-30 07:54:07 +02:00
mainData = [ ] ,
2022-07-08 11:28:27 +02:00
startLen = this . selectpicker . main . data ? this . selectpicker . main . data . length : 0 ,
2022-03-30 07:54:07 +02:00
optID = 0 ,
2022-04-13 12:34:14 +02:00
startIndex = this . setPlaceholder ( ) && ! data ? 1 : 0 ; // append the titleOption if necessary and skip the first option in the loop
2022-07-08 11:28:27 +02:00
if ( type === 'search' ) {
2022-04-13 12:34:14 +02:00
startLen = this . selectpicker . search . data . length ;
}
2022-03-30 07:54:07 +02:00
if ( this . options . hideDisabled ) optionSelector += ':not(:disabled)' ;
2022-04-13 12:34:14 +02:00
var selectOptions = data ? data . filter ( filterHidden , this ) : this . $element [ 0 ] . querySelectorAll ( 'select > *' + optionSelector ) ;
2022-03-30 07:54:07 +02:00
function addDivider ( config ) {
var previousData = mainData [ mainData . length - 1 ] ;
// ensure optgroup doesn't create back-to-back dividers
if (
previousData &&
previousData . type === 'divider' &&
( previousData . optID || config . optID )
) {
return ;
}
config = config || { } ;
config . type = 'divider' ;
mainData . push ( config ) ;
}
2022-04-13 12:34:14 +02:00
function addOption ( item , config ) {
2022-03-30 07:54:07 +02:00
config = config || { } ;
2022-04-13 12:34:14 +02:00
config . divider = dataGetter ( item , 'divider' ) ;
2022-03-30 07:54:07 +02:00
2022-04-13 12:34:14 +02:00
if ( config . divider === true ) {
2022-03-30 07:54:07 +02:00
addDivider ( {
optID : config . optID
} ) ;
} else {
2022-04-13 12:34:14 +02:00
var liIndex = mainData . length + startLen ,
cssText = dataGetter ( item , 'style' ) ,
2022-03-30 07:54:07 +02:00
inlineStyle = cssText ? htmlEscape ( cssText ) : '' ,
2022-04-13 12:34:14 +02:00
optionClass = ( item . className || '' ) + ( config . optgroupClass || '' ) ;
2022-03-30 07:54:07 +02:00
if ( config . optID ) optionClass = 'opt ' + optionClass ;
config . optionClass = optionClass . trim ( ) ;
config . inlineStyle = inlineStyle ;
2022-04-13 12:34:14 +02:00
config . text = dataGetter ( item , 'text' ) ;
2022-07-08 11:28:27 +02:00
config . title = dataGetter ( item , 'title' ) ;
2022-04-13 12:34:14 +02:00
config . content = dataGetter ( item , 'content' ) ;
config . tokens = dataGetter ( item , 'tokens' ) ;
config . subtext = dataGetter ( item , 'subtext' ) ;
config . icon = dataGetter ( item , 'icon' ) ;
2022-03-30 07:54:07 +02:00
config . display = config . content || config . text ;
2022-04-13 12:34:14 +02:00
config . value = item . value === undefined ? item . text : item . value ;
2022-03-30 07:54:07 +02:00
config . type = 'option' ;
config . index = liIndex ;
2022-04-13 12:34:14 +02:00
config . option = ! item . option ? item : item . option ; // reference option element if it exists
config . option . liIndex = liIndex ;
config . selected = ! ! item . selected ;
config . disabled = config . disabled || ! ! item . disabled ;
2022-03-30 07:54:07 +02:00
2022-07-08 11:28:27 +02:00
if ( data !== false ) {
if ( that . selectpicker . optionValuesDataMap [ config . value ] ) {
config = $ . extend ( that . selectpicker . optionValuesDataMap [ config . value ] , config ) ;
} else {
that . selectpicker . optionValuesDataMap [ config . value ] = config ;
}
}
2022-03-30 07:54:07 +02:00
mainData . push ( config ) ;
}
}
function addOptgroup ( index , selectOptions ) {
var optgroup = selectOptions [ index ] ,
2022-04-13 12:34:14 +02:00
// skip placeholder option
previous = index - 1 < startIndex ? false : selectOptions [ index - 1 ] ,
2022-03-30 07:54:07 +02:00
next = selectOptions [ index + 1 ] ,
2022-04-13 12:34:14 +02:00
options = data ? optgroup . children . filter ( filterHidden , this ) : optgroup . querySelectorAll ( 'option' + optionSelector ) ;
2022-03-30 07:54:07 +02:00
if ( ! options . length ) return ;
var config = {
2022-04-13 12:34:14 +02:00
display : htmlEscape ( dataGetter ( item , 'label' ) ) ,
subtext : dataGetter ( optgroup , 'subtext' ) ,
icon : dataGetter ( optgroup , 'icon' ) ,
2022-03-30 07:54:07 +02:00
type : 'optgroup-label' ,
2022-07-08 11:28:27 +02:00
optgroupClass : ' ' + ( optgroup . className || '' ) ,
optgroup : optgroup
2022-03-30 07:54:07 +02:00
} ,
headerIndex ,
lastIndex ;
optID ++ ;
if ( previous ) {
addDivider ( { optID : optID } ) ;
}
config . optID = optID ;
mainData . push ( config ) ;
for ( var j = 0 , len = options . length ; j < len ; j ++ ) {
var option = options [ j ] ;
if ( j === 0 ) {
headerIndex = mainData . length - 1 ;
lastIndex = headerIndex + len ;
}
addOption ( option , {
headerIndex : headerIndex ,
lastIndex : lastIndex ,
optID : config . optID ,
optgroupClass : config . optgroupClass ,
disabled : optgroup . disabled
} ) ;
}
if ( next ) {
addDivider ( { optID : optID } ) ;
}
}
2022-04-13 12:34:14 +02:00
for ( var len = selectOptions . length , i = startIndex ; i < len ; i ++ ) {
var item = selectOptions [ i ] ,
children = item . children ;
2022-03-30 07:54:07 +02:00
2022-04-13 12:34:14 +02:00
if ( children && children . length ) {
2022-07-08 11:28:27 +02:00
addOptgroup . call ( this , i , selectOptions ) ;
2022-03-30 07:54:07 +02:00
} else {
2022-04-13 12:34:14 +02:00
addOption . call ( this , item , { } ) ;
}
}
switch ( type ) {
case 'data' : {
2022-07-08 11:28:27 +02:00
if ( ! this . selectpicker . main . data ) {
this . selectpicker . main . data = [ ] ;
}
2022-04-13 12:34:14 +02:00
Array . prototype . push . apply ( this . selectpicker . main . data , mainData ) ;
this . selectpicker . current . data = this . selectpicker . main . data ;
break ;
}
case 'search' : {
Array . prototype . push . apply ( this . selectpicker . search . data , mainData ) ;
break ;
2022-03-30 07:54:07 +02:00
}
}
2022-04-13 12:34:14 +02:00
return mainData ;
2022-03-30 07:54:07 +02:00
} ,
2022-04-13 12:34:14 +02:00
buildList : function ( size , searching ) {
2022-03-30 07:54:07 +02:00
var that = this ,
2022-04-13 12:34:14 +02:00
selectData = searching ? this . selectpicker . search . data : this . selectpicker . main . data ,
2022-03-30 07:54:07 +02:00
mainElements = [ ] ,
widestOptionLength = 0 ;
if ( ( that . options . showTick || that . multiple ) && ! elementTemplates . checkMark . parentNode ) {
elementTemplates . checkMark . className = this . options . iconBase + ' ' + that . options . tickIcon + ' check-mark' ;
elementTemplates . a . appendChild ( elementTemplates . checkMark ) ;
}
2022-04-13 12:34:14 +02:00
function buildElement ( mainElements , item ) {
2022-03-30 07:54:07 +02:00
var liElement ,
combinedLength = 0 ;
switch ( item . type ) {
case 'divider' :
liElement = generateOption . li (
false ,
classNames . DIVIDER ,
( item . optID ? item . optID + 'div' : undefined )
) ;
break ;
case 'option' :
liElement = generateOption . li (
generateOption . a (
generateOption . text . call ( that , item ) ,
item . optionClass ,
item . inlineStyle
) ,
'' ,
item . optID
) ;
if ( liElement . firstChild ) {
liElement . firstChild . id = that . selectId + '-' + item . index ;
}
break ;
case 'optgroup-label' :
liElement = generateOption . li (
generateOption . label . call ( that , item ) ,
'dropdown-header' + item . optgroupClass ,
item . optID
) ;
break ;
}
2022-07-08 11:28:27 +02:00
if ( ! item . element ) {
item . element = liElement ;
} else {
item . element . innerHTML = liElement . innerHTML ;
}
mainElements . push ( item . element ) ;
2022-03-30 07:54:07 +02:00
// count the number of characters in the option - not perfect, but should work in most cases
if ( item . display ) combinedLength += item . display . length ;
if ( item . subtext ) combinedLength += item . subtext . length ;
// if there is an icon, ensure this option's width is checked
if ( item . icon ) combinedLength += 1 ;
if ( combinedLength > widestOptionLength ) {
widestOptionLength = combinedLength ;
// guess which option is the widest
// use this when calculating menu width
// not perfect, but it's fast, and the width will be updating accordingly when scrolling
that . selectpicker . view . widestOption = mainElements [ mainElements . length - 1 ] ;
}
}
2022-04-13 12:34:14 +02:00
var startIndex = size || 0 ;
for ( var len = selectData . length , i = startIndex ; i < len ; i ++ ) {
2022-03-30 07:54:07 +02:00
var item = selectData [ i ] ;
2022-04-13 12:34:14 +02:00
buildElement ( mainElements , item ) ;
2022-03-30 07:54:07 +02:00
}
2022-04-13 12:34:14 +02:00
if ( size ) {
if ( searching ) {
Array . prototype . push . apply ( this . selectpicker . search . elements , mainElements ) ;
} else {
Array . prototype . push . apply ( this . selectpicker . main . elements , mainElements ) ;
this . selectpicker . current . elements = this . selectpicker . main . elements ;
}
} else {
if ( searching ) {
this . selectpicker . search . elements = mainElements ;
} else {
this . selectpicker . main . elements = this . selectpicker . current . elements = mainElements ;
}
}
2022-03-30 07:54:07 +02:00
} ,
findLis : function ( ) {
return this . $menuInner . find ( '.inner > li' ) ;
} ,
2022-04-13 12:34:14 +02:00
render : function ( init ) {
2022-03-30 07:54:07 +02:00
var that = this ,
element = this . $element [ 0 ] ,
// ensure titleOption is appended and selected (if necessary) before getting selectedOptions
placeholderSelected = this . setPlaceholder ( ) && element . selectedIndex === 0 ,
2022-04-13 12:34:14 +02:00
selectedOptions = getSelectedOptions . call ( this ) ,
2022-03-30 07:54:07 +02:00
selectedCount = selectedOptions . length ,
2022-04-13 12:34:14 +02:00
selectedValues = getSelectValues . call ( this , selectedOptions ) ,
2022-03-30 07:54:07 +02:00
button = this . $button [ 0 ] ,
buttonInner = button . querySelector ( '.filter-option-inner-inner' ) ,
multipleSeparator = document . createTextNode ( this . options . multipleSeparator ) ,
titleFragment = elementTemplates . fragment . cloneNode ( false ) ,
showCount ,
countMax ,
hasContent = false ;
2022-04-13 12:34:14 +02:00
function createSelected ( item ) {
if ( item . selected ) {
that . createOption ( item , true ) ;
} else if ( item . children && item . children . length ) {
item . children . map ( createSelected ) ;
}
}
2022-03-30 07:54:07 +02:00
2022-04-13 12:34:14 +02:00
// create selected option elements to ensure select value is correct
if ( this . options . source . data && init ) {
selectedOptions . map ( createSelected ) ;
element . appendChild ( this . selectpicker . main . optionQueue ) ;
if ( placeholderSelected ) placeholderSelected = element . selectedIndex === 0 ;
}
button . classList . toggle ( 'bs-placeholder' , that . multiple ? ! selectedCount : ! selectedValues && selectedValues !== 0 ) ;
if ( ! that . multiple && selectedOptions . length === 1 ) {
that . selectpicker . view . displayedValue = selectedValues ;
}
2022-03-30 07:54:07 +02:00
if ( this . options . selectedTextFormat === 'static' ) {
2022-04-13 12:34:14 +02:00
titleFragment = generateOption . text . call ( this , { text : this . options . placeholder } , true ) ;
2022-03-30 07:54:07 +02:00
} else {
2022-07-08 11:28:27 +02:00
showCount = this . multiple && this . options . selectedTextFormat . indexOf ( 'count' ) !== - 1 && selectedCount > 0 ;
2022-03-30 07:54:07 +02:00
// determine if the number of selected options will be shown (showCount === true)
if ( showCount ) {
countMax = this . options . selectedTextFormat . split ( '>' ) ;
showCount = ( countMax . length > 1 && selectedCount > countMax [ 1 ] ) || ( countMax . length === 1 && selectedCount >= 2 ) ;
}
// only loop through all selected options if the count won't be shown
if ( showCount === false ) {
if ( ! placeholderSelected ) {
for ( var selectedIndex = 0 ; selectedIndex < selectedCount ; selectedIndex ++ ) {
if ( selectedIndex < 50 ) {
var option = selectedOptions [ selectedIndex ] ,
titleOptions = { } ;
2022-04-13 12:34:14 +02:00
if ( option ) {
if ( this . multiple && selectedIndex > 0 ) {
titleFragment . appendChild ( multipleSeparator . cloneNode ( false ) ) ;
}
2022-03-30 07:54:07 +02:00
2022-04-13 12:34:14 +02:00
if ( option . title ) {
titleOptions . text = option . title ;
} else if ( option . content && that . options . showContent ) {
titleOptions . content = option . content . toString ( ) ;
2022-03-30 07:54:07 +02:00
hasContent = true ;
} else {
if ( that . options . showIcon ) {
2022-04-13 12:34:14 +02:00
titleOptions . icon = option . icon ;
2022-03-30 07:54:07 +02:00
}
2022-04-13 12:34:14 +02:00
if ( that . options . showSubtext && ! that . multiple && option . subtext ) titleOptions . subtext = ' ' + option . subtext ;
titleOptions . text = option . text . trim ( ) ;
2022-03-30 07:54:07 +02:00
}
2022-04-13 12:34:14 +02:00
titleFragment . appendChild ( generateOption . text . call ( this , titleOptions , true ) ) ;
}
2022-03-30 07:54:07 +02:00
} else {
break ;
}
}
// add ellipsis
if ( selectedCount > 49 ) {
titleFragment . appendChild ( document . createTextNode ( '...' ) ) ;
}
}
} else {
2022-07-08 11:28:27 +02:00
var optionSelector = ':not([hidden]):not([data-hidden="true"]):not([data-divider="true"]):not([style*="display: none"])' ;
2022-03-30 07:54:07 +02:00
if ( this . options . hideDisabled ) optionSelector += ':not(:disabled)' ;
// If this is a multiselect, and selectedTextFormat is count, then show 1 of 2 selected, etc.
var totalCount = this . $element [ 0 ] . querySelectorAll ( 'select > option' + optionSelector + ', optgroup' + optionSelector + ' option' + optionSelector ) . length ,
tr8nText = ( typeof this . options . countSelectedText === 'function' ) ? this . options . countSelectedText ( selectedCount , totalCount ) : this . options . countSelectedText ;
titleFragment = generateOption . text . call ( this , {
text : tr8nText . replace ( '{0}' , selectedCount . toString ( ) ) . replace ( '{1}' , totalCount . toString ( ) )
} , true ) ;
}
}
// If the select doesn't have a title, then use the default, or if nothing is set at all, use noneSelectedText
if ( ! titleFragment . childNodes . length ) {
titleFragment = generateOption . text . call ( this , {
2022-04-13 12:34:14 +02:00
text : this . options . placeholder ? this . options . placeholder : this . options . noneSelectedText
2022-03-30 07:54:07 +02:00
} , true ) ;
}
2022-04-13 12:34:14 +02:00
// if the select has a title, apply it to the button, and if not, apply titleFragment text
2022-03-30 07:54:07 +02:00
// strip all HTML tags and trim the result, then unescape any escaped tags
button . title = titleFragment . textContent . replace ( /<[^>]*>?/g , '' ) . trim ( ) ;
if ( this . options . sanitize && hasContent ) {
sanitizeHtml ( [ titleFragment ] , that . options . whiteList , that . options . sanitizeFn ) ;
}
buttonInner . innerHTML = '' ;
buttonInner . appendChild ( titleFragment ) ;
if ( version . major < 4 && this . $newElement [ 0 ] . classList . contains ( 'bs3-has-addon' ) ) {
var filterExpand = button . querySelector ( '.filter-expand' ) ,
clone = buttonInner . cloneNode ( true ) ;
clone . className = 'filter-expand' ;
if ( filterExpand ) {
button . replaceChild ( clone , filterExpand ) ;
} else {
button . appendChild ( clone ) ;
}
}
this . $element . trigger ( 'rendered' + EVENT _KEY ) ;
} ,
/ * *
* @ param [ style ]
* @ param [ status ]
* /
setStyle : function ( newStyle , status ) {
var button = this . $button [ 0 ] ,
newElement = this . $newElement [ 0 ] ,
style = this . options . style . trim ( ) ,
buttonClass ;
if ( this . $element . attr ( 'class' ) ) {
this . $newElement . addClass ( this . $element . attr ( 'class' ) . replace ( /selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi , '' ) ) ;
}
if ( version . major < 4 ) {
newElement . classList . add ( 'bs3' ) ;
2022-04-13 12:34:14 +02:00
if ( newElement . parentNode . classList && newElement . parentNode . classList . contains ( 'input-group' ) &&
2022-03-30 07:54:07 +02:00
( newElement . previousElementSibling || newElement . nextElementSibling ) &&
( newElement . previousElementSibling || newElement . nextElementSibling ) . classList . contains ( 'input-group-addon' )
) {
newElement . classList . add ( 'bs3-has-addon' ) ;
}
}
if ( newStyle ) {
buttonClass = newStyle . trim ( ) ;
} else {
buttonClass = style ;
}
if ( status == 'add' ) {
if ( buttonClass ) button . classList . add . apply ( button . classList , buttonClass . split ( ' ' ) ) ;
} else if ( status == 'remove' ) {
if ( buttonClass ) button . classList . remove . apply ( button . classList , buttonClass . split ( ' ' ) ) ;
} else {
if ( style ) button . classList . remove . apply ( button . classList , style . split ( ' ' ) ) ;
if ( buttonClass ) button . classList . add . apply ( button . classList , buttonClass . split ( ' ' ) ) ;
}
} ,
liHeight : function ( refresh ) {
if ( ! refresh && ( this . options . size === false || Object . keys ( this . sizeInfo ) . length ) ) return ;
2022-04-13 12:34:14 +02:00
var newElement = elementTemplates . div . cloneNode ( false ) ,
menu = elementTemplates . div . cloneNode ( false ) ,
menuInner = elementTemplates . div . cloneNode ( false ) ,
2022-03-30 07:54:07 +02:00
menuInnerInner = document . createElement ( 'ul' ) ,
2022-04-13 12:34:14 +02:00
divider = elementTemplates . li . cloneNode ( false ) ,
dropdownHeader = elementTemplates . li . cloneNode ( false ) ,
li ,
a = elementTemplates . a . cloneNode ( false ) ,
text = elementTemplates . span . cloneNode ( false ) ,
2022-03-30 07:54:07 +02:00
header = this . options . header && this . $menu . find ( '.' + classNames . POPOVERHEADER ) . length > 0 ? this . $menu . find ( '.' + classNames . POPOVERHEADER ) [ 0 ] . cloneNode ( true ) : null ,
2022-04-13 12:34:14 +02:00
search = this . options . liveSearch ? elementTemplates . div . cloneNode ( false ) : null ,
2022-03-30 07:54:07 +02:00
actions = this . options . actionsBox && this . multiple && this . $menu . find ( '.bs-actionsbox' ) . length > 0 ? this . $menu . find ( '.bs-actionsbox' ) [ 0 ] . cloneNode ( true ) : null ,
doneButton = this . options . doneButton && this . multiple && this . $menu . find ( '.bs-donebutton' ) . length > 0 ? this . $menu . find ( '.bs-donebutton' ) [ 0 ] . cloneNode ( true ) : null ,
2022-04-13 12:34:14 +02:00
firstOption = this . $element [ 0 ] . options [ 0 ] ;
2022-03-30 07:54:07 +02:00
this . sizeInfo . selectWidth = this . $newElement [ 0 ] . offsetWidth ;
text . className = 'text' ;
a . className = 'dropdown-item ' + ( firstOption ? firstOption . className : '' ) ;
newElement . className = this . $menu [ 0 ] . parentNode . className + ' ' + classNames . SHOW ;
newElement . style . width = 0 ; // ensure button width doesn't affect natural width of menu when calculating
if ( this . options . width === 'auto' ) menu . style . minWidth = 0 ;
menu . className = classNames . MENU + ' ' + classNames . SHOW ;
menuInner . className = 'inner ' + classNames . SHOW ;
2022-04-13 12:34:14 +02:00
menuInnerInner . className = classNames . MENU + ' inner ' + ( version . major >= '4' ? classNames . SHOW : '' ) ;
2022-03-30 07:54:07 +02:00
divider . className = classNames . DIVIDER ;
dropdownHeader . className = 'dropdown-header' ;
text . appendChild ( document . createTextNode ( '\u200b' ) ) ;
2022-04-13 12:34:14 +02:00
if ( this . selectpicker . current . data . length ) {
for ( var i = 0 ; i < this . selectpicker . current . data . length ; i ++ ) {
var data = this . selectpicker . current . data [ i ] ;
2022-07-08 11:28:27 +02:00
if ( data . type === 'option' && $ ( data . element . firstChild ) . css ( 'display' ) !== 'none' ) {
2022-04-13 12:34:14 +02:00
li = data . element ;
break ;
}
}
} else {
li = elementTemplates . li . cloneNode ( false ) ;
a . appendChild ( text ) ;
li . appendChild ( a ) ;
}
2022-03-30 07:54:07 +02:00
dropdownHeader . appendChild ( text . cloneNode ( true ) ) ;
if ( this . selectpicker . view . widestOption ) {
menuInnerInner . appendChild ( this . selectpicker . view . widestOption . cloneNode ( true ) ) ;
}
menuInnerInner . appendChild ( li ) ;
menuInnerInner . appendChild ( divider ) ;
menuInnerInner . appendChild ( dropdownHeader ) ;
if ( header ) menu . appendChild ( header ) ;
if ( search ) {
var input = document . createElement ( 'input' ) ;
search . className = 'bs-searchbox' ;
input . className = 'form-control' ;
search . appendChild ( input ) ;
menu . appendChild ( search ) ;
}
if ( actions ) menu . appendChild ( actions ) ;
menuInner . appendChild ( menuInnerInner ) ;
menu . appendChild ( menuInner ) ;
if ( doneButton ) menu . appendChild ( doneButton ) ;
newElement . appendChild ( menu ) ;
document . body . appendChild ( newElement ) ;
var liHeight = li . offsetHeight ,
dropdownHeaderHeight = dropdownHeader ? dropdownHeader . offsetHeight : 0 ,
headerHeight = header ? header . offsetHeight : 0 ,
searchHeight = search ? search . offsetHeight : 0 ,
actionsHeight = actions ? actions . offsetHeight : 0 ,
doneButtonHeight = doneButton ? doneButton . offsetHeight : 0 ,
dividerHeight = $ ( divider ) . outerHeight ( true ) ,
2022-04-13 12:34:14 +02:00
menuStyle = window . getComputedStyle ( menu ) ,
2022-03-30 07:54:07 +02:00
menuWidth = menu . offsetWidth ,
menuPadding = {
2022-04-13 12:34:14 +02:00
vert : toInteger ( menuStyle . paddingTop ) +
toInteger ( menuStyle . paddingBottom ) +
toInteger ( menuStyle . borderTopWidth ) +
toInteger ( menuStyle . borderBottomWidth ) ,
horiz : toInteger ( menuStyle . paddingLeft ) +
toInteger ( menuStyle . paddingRight ) +
toInteger ( menuStyle . borderLeftWidth ) +
toInteger ( menuStyle . borderRightWidth )
2022-03-30 07:54:07 +02:00
} ,
menuExtras = {
vert : menuPadding . vert +
2022-04-13 12:34:14 +02:00
toInteger ( menuStyle . marginTop ) +
toInteger ( menuStyle . marginBottom ) + 2 ,
2022-03-30 07:54:07 +02:00
horiz : menuPadding . horiz +
2022-04-13 12:34:14 +02:00
toInteger ( menuStyle . marginLeft ) +
toInteger ( menuStyle . marginRight ) + 2
2022-03-30 07:54:07 +02:00
} ,
scrollBarWidth ;
menuInner . style . overflowY = 'scroll' ;
scrollBarWidth = menu . offsetWidth - menuWidth ;
document . body . removeChild ( newElement ) ;
this . sizeInfo . liHeight = liHeight ;
this . sizeInfo . dropdownHeaderHeight = dropdownHeaderHeight ;
this . sizeInfo . headerHeight = headerHeight ;
this . sizeInfo . searchHeight = searchHeight ;
this . sizeInfo . actionsHeight = actionsHeight ;
this . sizeInfo . doneButtonHeight = doneButtonHeight ;
this . sizeInfo . dividerHeight = dividerHeight ;
this . sizeInfo . menuPadding = menuPadding ;
this . sizeInfo . menuExtras = menuExtras ;
this . sizeInfo . menuWidth = menuWidth ;
this . sizeInfo . menuInnerInnerWidth = menuWidth - menuPadding . horiz ;
this . sizeInfo . totalMenuWidth = this . sizeInfo . menuWidth ;
this . sizeInfo . scrollBarWidth = scrollBarWidth ;
this . sizeInfo . selectHeight = this . $newElement [ 0 ] . offsetHeight ;
this . setPositionData ( ) ;
} ,
getSelectPosition : function ( ) {
var that = this ,
$window = $ ( window ) ,
pos = that . $newElement . offset ( ) ,
$container = $ ( that . options . container ) ,
containerPos ;
if ( that . options . container && $container . length && ! $container . is ( 'body' ) ) {
containerPos = $container . offset ( ) ;
containerPos . top += parseInt ( $container . css ( 'borderTopWidth' ) ) ;
containerPos . left += parseInt ( $container . css ( 'borderLeftWidth' ) ) ;
} else {
containerPos = { top : 0 , left : 0 } ;
}
var winPad = that . options . windowPadding ;
this . sizeInfo . selectOffsetTop = pos . top - containerPos . top - $window . scrollTop ( ) ;
this . sizeInfo . selectOffsetBot = $window . height ( ) - this . sizeInfo . selectOffsetTop - this . sizeInfo . selectHeight - containerPos . top - winPad [ 2 ] ;
this . sizeInfo . selectOffsetLeft = pos . left - containerPos . left - $window . scrollLeft ( ) ;
this . sizeInfo . selectOffsetRight = $window . width ( ) - this . sizeInfo . selectOffsetLeft - this . sizeInfo . selectWidth - containerPos . left - winPad [ 1 ] ;
this . sizeInfo . selectOffsetTop -= winPad [ 0 ] ;
this . sizeInfo . selectOffsetLeft -= winPad [ 3 ] ;
} ,
setMenuSize : function ( isAuto ) {
this . getSelectPosition ( ) ;
var selectWidth = this . sizeInfo . selectWidth ,
liHeight = this . sizeInfo . liHeight ,
headerHeight = this . sizeInfo . headerHeight ,
searchHeight = this . sizeInfo . searchHeight ,
actionsHeight = this . sizeInfo . actionsHeight ,
doneButtonHeight = this . sizeInfo . doneButtonHeight ,
divHeight = this . sizeInfo . dividerHeight ,
menuPadding = this . sizeInfo . menuPadding ,
menuInnerHeight ,
menuHeight ,
divLength = 0 ,
minHeight ,
_minHeight ,
maxHeight ,
menuInnerMinHeight ,
estimate ,
isDropup ;
if ( this . options . dropupAuto ) {
// Get the estimated height of the menu without scrollbars.
// This is useful for smaller menus, where there might be plenty of room
// below the button without setting dropup, but we can't know
// the exact height of the menu until createView is called later
2022-04-13 12:34:14 +02:00
estimate = liHeight * this . selectpicker . current . data . length + menuPadding . vert ;
2022-03-30 07:54:07 +02:00
isDropup = this . sizeInfo . selectOffsetTop - this . sizeInfo . selectOffsetBot > this . sizeInfo . menuExtras . vert && estimate + this . sizeInfo . menuExtras . vert + 50 > this . sizeInfo . selectOffsetBot ;
// ensure dropup doesn't change while searching (so menu doesn't bounce back and forth)
if ( this . selectpicker . isSearching === true ) {
isDropup = this . selectpicker . dropup ;
}
this . $newElement . toggleClass ( classNames . DROPUP , isDropup ) ;
this . selectpicker . dropup = isDropup ;
}
if ( this . options . size === 'auto' ) {
2022-04-13 12:34:14 +02:00
_minHeight = this . selectpicker . current . data . length > 3 ? this . sizeInfo . liHeight * 3 + this . sizeInfo . menuExtras . vert - 2 : 0 ;
2022-03-30 07:54:07 +02:00
menuHeight = this . sizeInfo . selectOffsetBot - this . sizeInfo . menuExtras . vert ;
minHeight = _minHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight ;
menuInnerMinHeight = Math . max ( _minHeight - menuPadding . vert , 0 ) ;
if ( this . $newElement . hasClass ( classNames . DROPUP ) ) {
menuHeight = this . sizeInfo . selectOffsetTop - this . sizeInfo . menuExtras . vert ;
}
maxHeight = menuHeight ;
menuInnerHeight = menuHeight - headerHeight - searchHeight - actionsHeight - doneButtonHeight - menuPadding . vert ;
} else if ( this . options . size && this . options . size != 'auto' && this . selectpicker . current . elements . length > this . options . size ) {
for ( var i = 0 ; i < this . options . size ; i ++ ) {
if ( this . selectpicker . current . data [ i ] . type === 'divider' ) divLength ++ ;
}
menuHeight = liHeight * this . options . size + divLength * divHeight + menuPadding . vert ;
menuInnerHeight = menuHeight - menuPadding . vert ;
maxHeight = menuHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight ;
minHeight = menuInnerMinHeight = '' ;
}
this . $menu . css ( {
'max-height' : maxHeight + 'px' ,
'overflow' : 'hidden' ,
'min-height' : minHeight + 'px'
} ) ;
this . $menuInner . css ( {
'max-height' : menuInnerHeight + 'px' ,
2022-07-08 11:28:27 +02:00
'overflow' : 'hidden auto' ,
2022-03-30 07:54:07 +02:00
'min-height' : menuInnerMinHeight + 'px'
} ) ;
// ensure menuInnerHeight is always a positive number to prevent issues calculating chunkSize in createView
this . sizeInfo . menuInnerHeight = Math . max ( menuInnerHeight , 1 ) ;
if ( this . selectpicker . current . data . length && this . selectpicker . current . data [ this . selectpicker . current . data . length - 1 ] . position > this . sizeInfo . menuInnerHeight ) {
this . sizeInfo . hasScrollBar = true ;
this . sizeInfo . totalMenuWidth = this . sizeInfo . menuWidth + this . sizeInfo . scrollBarWidth ;
}
if ( this . options . dropdownAlignRight === 'auto' ) {
this . $menu . toggleClass ( classNames . MENURIGHT , this . sizeInfo . selectOffsetLeft > this . sizeInfo . selectOffsetRight && this . sizeInfo . selectOffsetRight < ( this . sizeInfo . totalMenuWidth - selectWidth ) ) ;
}
if ( this . dropdown && this . dropdown . _popper ) this . dropdown . _popper . update ( ) ;
} ,
setSize : function ( refresh ) {
this . liHeight ( refresh ) ;
if ( this . options . header ) this . $menu . css ( 'padding-top' , 0 ) ;
if ( this . options . size !== false ) {
var that = this ,
$window = $ ( window ) ;
this . setMenuSize ( ) ;
if ( this . options . liveSearch ) {
this . $searchbox
. off ( 'input.setMenuSize propertychange.setMenuSize' )
. on ( 'input.setMenuSize propertychange.setMenuSize' , function ( ) {
return that . setMenuSize ( ) ;
} ) ;
}
if ( this . options . size === 'auto' ) {
$window
. off ( 'resize' + EVENT _KEY + '.' + this . selectId + '.setMenuSize' + ' scroll' + EVENT _KEY + '.' + this . selectId + '.setMenuSize' )
. on ( 'resize' + EVENT _KEY + '.' + this . selectId + '.setMenuSize' + ' scroll' + EVENT _KEY + '.' + this . selectId + '.setMenuSize' , function ( ) {
return that . setMenuSize ( ) ;
} ) ;
} else if ( this . options . size && this . options . size != 'auto' && this . selectpicker . current . elements . length > this . options . size ) {
$window . off ( 'resize' + EVENT _KEY + '.' + this . selectId + '.setMenuSize' + ' scroll' + EVENT _KEY + '.' + this . selectId + '.setMenuSize' ) ;
}
}
this . createView ( false , true , refresh ) ;
} ,
setWidth : function ( ) {
var that = this ;
if ( this . options . width === 'auto' ) {
requestAnimationFrame ( function ( ) {
that . $menu . css ( 'min-width' , '0' ) ;
that . $element . on ( 'loaded' + EVENT _KEY , function ( ) {
that . liHeight ( ) ;
that . setMenuSize ( ) ;
// Get correct width if element is hidden
var $selectClone = that . $newElement . clone ( ) . appendTo ( 'body' ) ,
btnWidth = $selectClone . css ( 'width' , 'auto' ) . children ( 'button' ) . outerWidth ( ) ;
$selectClone . remove ( ) ;
// Set width to whatever's larger, button title or longest option
that . sizeInfo . selectWidth = Math . max ( that . sizeInfo . totalMenuWidth , btnWidth ) ;
that . $newElement . css ( 'width' , that . sizeInfo . selectWidth + 'px' ) ;
} ) ;
} ) ;
} else if ( this . options . width === 'fit' ) {
// Remove inline min-width so width can be changed from 'auto'
this . $menu . css ( 'min-width' , '' ) ;
this . $newElement . css ( 'width' , '' ) . addClass ( 'fit-width' ) ;
} else if ( this . options . width ) {
// Remove inline min-width so width can be changed from 'auto'
this . $menu . css ( 'min-width' , '' ) ;
this . $newElement . css ( 'width' , this . options . width ) ;
} else {
// Remove inline min-width/width so width can be changed
this . $menu . css ( 'min-width' , '' ) ;
this . $newElement . css ( 'width' , '' ) ;
}
// Remove fit-width class if width is changed programmatically
if ( this . $newElement . hasClass ( 'fit-width' ) && this . options . width !== 'fit' ) {
this . $newElement [ 0 ] . classList . remove ( 'fit-width' ) ;
}
} ,
selectPosition : function ( ) {
this . $bsContainer = $ ( '<div class="bs-container" />' ) ;
var that = this ,
$container = $ ( this . options . container ) ,
pos ,
containerPos ,
actualHeight ,
getPlacement = function ( $element ) {
var containerPosition = { } ,
// fall back to dropdown's default display setting if display is not manually set
display = that . options . display || (
// Bootstrap 3 doesn't have $.fn.dropdown.Constructor.Default
$ . fn . dropdown . Constructor . Default ? $ . fn . dropdown . Constructor . Default . display
: false
) ;
that . $bsContainer . addClass ( $element . attr ( 'class' ) . replace ( /form-control|fit-width/gi , '' ) ) . toggleClass ( classNames . DROPUP , $element . hasClass ( classNames . DROPUP ) ) ;
pos = $element . offset ( ) ;
if ( ! $container . is ( 'body' ) ) {
containerPos = $container . offset ( ) ;
containerPos . top += parseInt ( $container . css ( 'borderTopWidth' ) ) - $container . scrollTop ( ) ;
containerPos . left += parseInt ( $container . css ( 'borderLeftWidth' ) ) - $container . scrollLeft ( ) ;
} else {
containerPos = { top : 0 , left : 0 } ;
}
actualHeight = $element . hasClass ( classNames . DROPUP ) ? 0 : $element [ 0 ] . offsetHeight ;
// Bootstrap 4+ uses Popper for menu positioning
if ( version . major < 4 || display === 'static' ) {
containerPosition . top = pos . top - containerPos . top + actualHeight ;
containerPosition . left = pos . left - containerPos . left ;
}
containerPosition . width = $element [ 0 ] . offsetWidth ;
that . $bsContainer . css ( containerPosition ) ;
} ;
this . $button . on ( 'click.bs.dropdown.data-api' , function ( ) {
if ( that . isDisabled ( ) ) {
return ;
}
getPlacement ( that . $newElement ) ;
that . $bsContainer
. appendTo ( that . options . container )
. toggleClass ( classNames . SHOW , ! that . $button . hasClass ( classNames . SHOW ) )
. append ( that . $menu ) ;
} ) ;
$ ( window )
. off ( 'resize' + EVENT _KEY + '.' + this . selectId + ' scroll' + EVENT _KEY + '.' + this . selectId )
. on ( 'resize' + EVENT _KEY + '.' + this . selectId + ' scroll' + EVENT _KEY + '.' + this . selectId , function ( ) {
var isActive = that . $newElement . hasClass ( classNames . SHOW ) ;
if ( isActive ) getPlacement ( that . $newElement ) ;
} ) ;
this . $element . on ( 'hide' + EVENT _KEY , function ( ) {
that . $menu . data ( 'height' , that . $menu . height ( ) ) ;
that . $bsContainer . detach ( ) ;
} ) ;
} ,
2022-04-13 12:34:14 +02:00
createOption : function ( data , init ) {
var optionData = ! data . option ? data : data . option ;
if ( optionData && optionData . nodeType !== 1 ) {
var option = ( init ? elementTemplates . selectedOption : elementTemplates . option ) . cloneNode ( true ) ;
if ( optionData . value !== undefined ) option . value = optionData . value ;
option . textContent = optionData . text ;
option . selected = true ;
if ( optionData . liIndex !== undefined ) {
option . liIndex = optionData . liIndex ;
} else if ( ! init ) {
option . liIndex = data . index ;
}
data . option = option ;
this . selectpicker . main . optionQueue . appendChild ( option ) ;
}
} ,
2022-03-30 07:54:07 +02:00
setOptionStatus : function ( selectedOnly ) {
var that = this ;
that . noScroll = false ;
if ( that . selectpicker . view . visibleElements && that . selectpicker . view . visibleElements . length ) {
for ( var i = 0 ; i < that . selectpicker . view . visibleElements . length ; i ++ ) {
var liData = that . selectpicker . current . data [ i + that . selectpicker . view . position0 ] ,
option = liData . option ;
if ( option ) {
if ( selectedOnly !== true ) {
2022-04-13 12:34:14 +02:00
that . setDisabled ( liData ) ;
2022-03-30 07:54:07 +02:00
}
2022-04-13 12:34:14 +02:00
that . setSelected ( liData ) ;
2022-03-30 07:54:07 +02:00
}
}
2022-04-13 12:34:14 +02:00
// append optionQueue (documentFragment with option elements for select options)
if ( this . options . source . data ) this . $element [ 0 ] . appendChild ( this . selectpicker . main . optionQueue ) ;
2022-03-30 07:54:07 +02:00
}
} ,
/ * *
2022-07-08 11:28:27 +02:00
* @ param { Object } liData - the option object that is being changed
2022-03-30 07:54:07 +02:00
* @ param { boolean } selected - true if the option is being selected , false if being deselected
* /
2022-04-13 12:34:14 +02:00
setSelected : function ( liData , selected ) {
selected = selected === undefined ? liData . selected : selected ;
2022-07-08 11:28:27 +02:00
var li = liData . element ,
activeElementIsSet = this . activeElement !== undefined ,
thisIsActive = this . activeElement === li ,
2022-03-30 07:54:07 +02:00
prevActive ,
a ,
// if current option is already active
// OR
// if the current option is being selected, it's NOT multiple, and
2022-07-08 11:28:27 +02:00
// activeElement is undefined:
2022-03-30 07:54:07 +02:00
// - when the menu is first being opened, OR
// - after a search has been performed, OR
2022-07-08 11:28:27 +02:00
// - when retainActive is false when selecting a new option (i.e. index of the newly selected option is not the same as the current activeElement)
keepActive = thisIsActive || ( selected && ! this . multiple && ! activeElementIsSet ) ;
2022-03-30 07:54:07 +02:00
2022-04-13 12:34:14 +02:00
if ( ! li ) return ;
if ( selected !== undefined ) {
liData . selected = selected ;
if ( liData . option ) liData . option . selected = selected ;
}
if ( selected && this . options . source . data ) {
this . createOption ( liData , false ) ;
}
2022-03-30 07:54:07 +02:00
a = li . firstChild ;
if ( selected ) {
2022-07-08 11:28:27 +02:00
this . selectedElement = li ;
2022-03-30 07:54:07 +02:00
}
li . classList . toggle ( 'selected' , selected ) ;
if ( keepActive ) {
this . focusItem ( li , liData ) ;
this . selectpicker . view . currentActive = li ;
2022-07-08 11:28:27 +02:00
this . activeElement = li ;
2022-03-30 07:54:07 +02:00
} else {
this . defocusItem ( li ) ;
}
if ( a ) {
a . classList . toggle ( 'selected' , selected ) ;
if ( selected ) {
a . setAttribute ( 'aria-selected' , true ) ;
} else {
if ( this . multiple ) {
a . setAttribute ( 'aria-selected' , false ) ;
} else {
a . removeAttribute ( 'aria-selected' ) ;
}
}
}
2022-07-08 11:28:27 +02:00
if ( ! keepActive && ! activeElementIsSet && selected && this . prevActiveElement !== undefined ) {
prevActive = this . prevActiveElement ;
2022-03-30 07:54:07 +02:00
this . defocusItem ( prevActive ) ;
}
} ,
/ * *
* @ param { number } index - the index of the option that is being disabled
* @ param { boolean } disabled - true if the option is being disabled , false if being enabled
* /
2022-04-13 12:34:14 +02:00
setDisabled : function ( liData ) {
var disabled = liData . disabled ,
li = liData . element ,
2022-03-30 07:54:07 +02:00
a ;
2022-04-13 12:34:14 +02:00
if ( ! li ) return ;
2022-03-30 07:54:07 +02:00
a = li . firstChild ;
li . classList . toggle ( classNames . DISABLED , disabled ) ;
if ( a ) {
2022-04-13 12:34:14 +02:00
if ( version . major >= '4' ) a . classList . toggle ( classNames . DISABLED , disabled ) ;
2022-03-30 07:54:07 +02:00
if ( disabled ) {
a . setAttribute ( 'aria-disabled' , disabled ) ;
a . setAttribute ( 'tabindex' , - 1 ) ;
} else {
a . removeAttribute ( 'aria-disabled' ) ;
a . setAttribute ( 'tabindex' , 0 ) ;
}
}
} ,
isDisabled : function ( ) {
return this . $element [ 0 ] . disabled ;
} ,
checkDisabled : function ( ) {
if ( this . isDisabled ( ) ) {
this . $newElement [ 0 ] . classList . add ( classNames . DISABLED ) ;
2022-04-13 12:34:14 +02:00
this . $button . addClass ( classNames . DISABLED ) . attr ( 'aria-disabled' , true ) ;
2022-03-30 07:54:07 +02:00
} else {
if ( this . $button [ 0 ] . classList . contains ( classNames . DISABLED ) ) {
this . $newElement [ 0 ] . classList . remove ( classNames . DISABLED ) ;
this . $button . removeClass ( classNames . DISABLED ) . attr ( 'aria-disabled' , false ) ;
}
}
} ,
clickListener : function ( ) {
var that = this ,
$document = $ ( document ) ;
$document . data ( 'spaceSelect' , false ) ;
this . $button . on ( 'keyup' , function ( e ) {
if ( /(32)/ . test ( e . keyCode . toString ( 10 ) ) && $document . data ( 'spaceSelect' ) ) {
e . preventDefault ( ) ;
$document . data ( 'spaceSelect' , false ) ;
}
} ) ;
this . $newElement . on ( 'show.bs.dropdown' , function ( ) {
2022-04-13 12:34:14 +02:00
if ( ! that . dropdown && version . major === '4' ) {
2022-03-30 07:54:07 +02:00
that . dropdown = that . $button . data ( 'bs.dropdown' ) ;
that . dropdown . _menu = that . $menu [ 0 ] ;
}
} ) ;
2022-04-13 12:34:14 +02:00
function clearSelection ( e ) {
if ( that . multiple ) {
that . deselectAll ( ) ;
} else {
var element = that . $element [ 0 ] ,
prevValue = element . value ,
prevIndex = element . selectedIndex ,
prevOption = element . options [ prevIndex ] ,
prevData = prevOption ? that . selectpicker . main . data [ prevOption . liIndex ] : false ;
if ( prevData ) {
that . setSelected ( prevData , false ) ;
}
element . selectedIndex = 0 ;
changedArguments = [ prevIndex , false , prevValue ] ;
that . $element . triggerNative ( 'change' ) ;
}
// remove selected styling if menu is open
if ( that . $newElement . hasClass ( classNames . SHOW ) ) {
if ( that . options . liveSearch ) {
that . $searchbox . trigger ( 'focus' ) ;
}
that . createView ( false ) ;
}
}
this . $button . on ( 'click.bs.dropdown.data-api' , function ( e ) {
if ( that . options . allowClear ) {
var target = e . target ,
clearButton = that . $clearButton [ 0 ] ;
// IE doesn't support event listeners on child elements of buttons
if ( /MSIE|Trident/ . test ( window . navigator . userAgent ) ) {
target = document . elementFromPoint ( e . clientX , e . clientY ) ;
}
if ( target === clearButton || target . parentElement === clearButton ) {
e . stopImmediatePropagation ( ) ;
clearSelection ( e ) ;
}
}
2022-03-30 07:54:07 +02:00
if ( ! that . $newElement . hasClass ( classNames . SHOW ) ) {
that . setSize ( ) ;
}
} ) ;
function setFocus ( ) {
if ( that . options . liveSearch ) {
that . $searchbox . trigger ( 'focus' ) ;
} else {
that . $menuInner . trigger ( 'focus' ) ;
}
}
function checkPopperExists ( ) {
2022-04-13 12:34:14 +02:00
if ( that . dropdown && that . dropdown . _popper && that . dropdown . _popper . state ) {
2022-03-30 07:54:07 +02:00
setFocus ( ) ;
} else {
requestAnimationFrame ( checkPopperExists ) ;
}
}
this . $element . on ( 'shown' + EVENT _KEY , function ( ) {
if ( that . $menuInner [ 0 ] . scrollTop !== that . selectpicker . view . scrollTop ) {
that . $menuInner [ 0 ] . scrollTop = that . selectpicker . view . scrollTop ;
}
if ( version . major > 3 ) {
requestAnimationFrame ( checkPopperExists ) ;
} else {
setFocus ( ) ;
}
} ) ;
// ensure posinset and setsize are correct before selecting an option via a click
this . $menuInner . on ( 'mouseenter' , 'li a' , function ( e ) {
var hoverLi = this . parentElement ,
position0 = that . isVirtual ( ) ? that . selectpicker . view . position0 : 0 ,
index = Array . prototype . indexOf . call ( hoverLi . parentElement . children , hoverLi ) ,
hoverData = that . selectpicker . current . data [ index + position0 ] ;
that . focusItem ( hoverLi , hoverData , true ) ;
} ) ;
this . $menuInner . on ( 'click' , 'li a' , function ( e , retainActive ) {
var $this = $ ( this ) ,
element = that . $element [ 0 ] ,
position0 = that . isVirtual ( ) ? that . selectpicker . view . position0 : 0 ,
clickedData = that . selectpicker . current . data [ $this . parent ( ) . index ( ) + position0 ] ,
2022-07-08 11:28:27 +02:00
clickedElement = clickedData . element ,
2022-04-13 12:34:14 +02:00
prevValue = getSelectValues . call ( that ) ,
2022-03-30 07:54:07 +02:00
prevIndex = element . selectedIndex ,
prevOption = element . options [ prevIndex ] ,
2022-04-13 12:34:14 +02:00
prevData = prevOption ? that . selectpicker . main . data [ prevOption . liIndex ] : false ,
2022-03-30 07:54:07 +02:00
triggerChange = true ;
// Don't close on multi choice menu
if ( that . multiple && that . options . maxOptions !== 1 ) {
e . stopPropagation ( ) ;
}
e . preventDefault ( ) ;
// Don't run if the select is disabled
if ( ! that . isDisabled ( ) && ! $this . parent ( ) . hasClass ( classNames . DISABLED ) ) {
var option = clickedData . option ,
$option = $ ( option ) ,
state = option . selected ,
2022-07-08 11:28:27 +02:00
optgroupData = that . selectpicker . current . data . find ( function ( datum ) {
return datum . optID === clickedData . optID && datum . type === 'optgroup-label' ;
} ) ,
optgroup = optgroupData ? optgroupData . optgroup : undefined ,
dataGetter = optgroup instanceof Element ? getOptionData . fromOption : getOptionData . fromDataSource ,
optgroupOptions = optgroup && optgroup . children ,
maxOptions = parseInt ( that . options . maxOptions ) ,
maxOptionsGrp = optgroup && parseInt ( dataGetter ( optgroup , 'maxOptions' ) ) || false ;
2022-03-30 07:54:07 +02:00
2022-07-08 11:28:27 +02:00
if ( clickedElement === that . activeElement ) retainActive = true ;
2022-03-30 07:54:07 +02:00
if ( ! retainActive ) {
2022-07-08 11:28:27 +02:00
that . prevActiveElement = that . activeElement ;
that . activeElement = undefined ;
2022-03-30 07:54:07 +02:00
}
2022-07-08 11:28:27 +02:00
if ( ! that . multiple || maxOptions === 1 ) { // Deselect previous option if not multi select
2022-04-13 12:34:14 +02:00
if ( prevData ) that . setSelected ( prevData , false ) ;
that . setSelected ( clickedData , true ) ;
} else { // Toggle the clicked option if multi select.
that . setSelected ( clickedData , ! state ) ;
that . focusedParent . focus ( ) ;
2022-03-30 07:54:07 +02:00
if ( maxOptions !== false || maxOptionsGrp !== false ) {
2022-04-13 12:34:14 +02:00
var maxReached = maxOptions < getSelectedOptions . call ( that ) . length ,
2022-07-08 11:28:27 +02:00
selectedGroupOptions = 0 ;
if ( optgroup && optgroup . children ) {
for ( var i = 0 ; i < optgroup . children . length ; i ++ ) {
if ( optgroup . children [ i ] . selected ) selectedGroupOptions ++ ;
}
}
var maxReachedGrp = maxOptionsGrp < selectedGroupOptions ;
2022-03-30 07:54:07 +02:00
if ( ( maxOptions && maxReached ) || ( maxOptionsGrp && maxReachedGrp ) ) {
2022-07-08 11:28:27 +02:00
if ( maxOptions && maxOptions === 1 ) {
2022-03-30 07:54:07 +02:00
element . selectedIndex = - 1 ;
that . setOptionStatus ( true ) ;
2022-07-08 11:28:27 +02:00
} else if ( maxOptionsGrp && maxOptionsGrp === 1 ) {
for ( var i = 0 ; i < optgroupOptions . length ; i ++ ) {
var _option = optgroupOptions [ i ] ;
that . setSelected ( that . selectpicker . current . data [ _option . liIndex ] , false ) ;
2022-03-30 07:54:07 +02:00
}
2022-07-08 11:28:27 +02:00
that . setSelected ( clickedData , true ) ;
2022-03-30 07:54:07 +02:00
} else {
var maxOptionsText = typeof that . options . maxOptionsText === 'string' ? [ that . options . maxOptionsText , that . options . maxOptionsText ] : that . options . maxOptionsText ,
maxOptionsArr = typeof maxOptionsText === 'function' ? maxOptionsText ( maxOptions , maxOptionsGrp ) : maxOptionsText ,
maxTxt = maxOptionsArr [ 0 ] . replace ( '{n}' , maxOptions ) ,
maxTxtGrp = maxOptionsArr [ 1 ] . replace ( '{n}' , maxOptionsGrp ) ,
$notify = $ ( '<div class="notify"></div>' ) ;
// If {var} is set in array, replace it
/** @deprecated */
if ( maxOptionsArr [ 2 ] ) {
maxTxt = maxTxt . replace ( '{var}' , maxOptionsArr [ 2 ] [ maxOptions > 1 ? 0 : 1 ] ) ;
maxTxtGrp = maxTxtGrp . replace ( '{var}' , maxOptionsArr [ 2 ] [ maxOptionsGrp > 1 ? 0 : 1 ] ) ;
}
that . $menu . append ( $notify ) ;
if ( maxOptions && maxReached ) {
$notify . append ( $ ( '<div>' + maxTxt + '</div>' ) ) ;
triggerChange = false ;
that . $element . trigger ( 'maxReached' + EVENT _KEY ) ;
}
if ( maxOptionsGrp && maxReachedGrp ) {
$notify . append ( $ ( '<div>' + maxTxtGrp + '</div>' ) ) ;
triggerChange = false ;
that . $element . trigger ( 'maxReachedGrp' + EVENT _KEY ) ;
}
setTimeout ( function ( ) {
2022-07-08 11:28:27 +02:00
that . setSelected ( clickedData , false ) ;
2022-03-30 07:54:07 +02:00
} , 10 ) ;
$notify [ 0 ] . classList . add ( 'fadeOut' ) ;
setTimeout ( function ( ) {
$notify . remove ( ) ;
} , 1050 ) ;
}
}
}
}
2022-04-13 12:34:14 +02:00
if ( that . options . source . data ) that . $element [ 0 ] . appendChild ( that . selectpicker . main . optionQueue ) ;
2022-03-30 07:54:07 +02:00
if ( ! that . multiple || ( that . multiple && that . options . maxOptions === 1 ) ) {
that . $button . trigger ( 'focus' ) ;
} else if ( that . options . liveSearch ) {
that . $searchbox . trigger ( 'focus' ) ;
}
// Trigger select 'change'
if ( triggerChange ) {
if ( that . multiple || prevIndex !== element . selectedIndex ) {
// $option.prop('selected') is current option state (selected/unselected). prevValue is the value of the select prior to being changed.
changedArguments = [ option . index , $option . prop ( 'selected' ) , prevValue ] ;
that . $element
. triggerNative ( 'change' ) ;
}
}
}
} ) ;
this . $menu . on ( 'click' , 'li.' + classNames . DISABLED + ' a, .' + classNames . POPOVERHEADER + ', .' + classNames . POPOVERHEADER + ' :not(.close)' , function ( e ) {
if ( e . currentTarget == this ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
if ( that . options . liveSearch && ! $ ( e . target ) . hasClass ( 'close' ) ) {
that . $searchbox . trigger ( 'focus' ) ;
} else {
that . $button . trigger ( 'focus' ) ;
}
}
} ) ;
this . $menuInner . on ( 'click' , '.divider, .dropdown-header' , function ( e ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
if ( that . options . liveSearch ) {
that . $searchbox . trigger ( 'focus' ) ;
} else {
that . $button . trigger ( 'focus' ) ;
}
} ) ;
this . $menu . on ( 'click' , '.' + classNames . POPOVERHEADER + ' .close' , function ( ) {
that . $button . trigger ( 'click' ) ;
} ) ;
this . $searchbox . on ( 'click' , function ( e ) {
e . stopPropagation ( ) ;
} ) ;
this . $menu . on ( 'click' , '.actions-btn' , function ( e ) {
if ( that . options . liveSearch ) {
that . $searchbox . trigger ( 'focus' ) ;
} else {
that . $button . trigger ( 'focus' ) ;
}
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
if ( $ ( this ) . hasClass ( 'bs-select-all' ) ) {
that . selectAll ( ) ;
} else {
that . deselectAll ( ) ;
}
} ) ;
2022-04-13 12:34:14 +02:00
this . $button
. on ( 'focus' + EVENT _KEY , function ( e ) {
var tabindex = that . $element [ 0 ] . getAttribute ( 'tabindex' ) ;
// only change when button is actually focused
if ( tabindex !== undefined && e . originalEvent && e . originalEvent . isTrusted ) {
// apply select element's tabindex to ensure correct order is followed when tabbing to the next element
this . setAttribute ( 'tabindex' , tabindex ) ;
// set element's tabindex to -1 to allow for reverse tabbing
that . $element [ 0 ] . setAttribute ( 'tabindex' , - 1 ) ;
that . selectpicker . view . tabindex = tabindex ;
}
} )
. on ( 'blur' + EVENT _KEY , function ( e ) {
// revert everything to original tabindex
if ( that . selectpicker . view . tabindex !== undefined && e . originalEvent && e . originalEvent . isTrusted ) {
that . $element [ 0 ] . setAttribute ( 'tabindex' , that . selectpicker . view . tabindex ) ;
this . setAttribute ( 'tabindex' , - 1 ) ;
that . selectpicker . view . tabindex = undefined ;
}
} ) ;
2022-03-30 07:54:07 +02:00
this . $element
. on ( 'change' + EVENT _KEY , function ( ) {
that . render ( ) ;
that . $element . trigger ( 'changed' + EVENT _KEY , changedArguments ) ;
changedArguments = null ;
} )
. on ( 'focus' + EVENT _KEY , function ( ) {
2022-04-13 12:34:14 +02:00
if ( ! that . options . mobile ) that . $button [ 0 ] . focus ( ) ;
2022-03-30 07:54:07 +02:00
} ) ;
} ,
liveSearchListener : function ( ) {
2022-04-13 12:34:14 +02:00
var that = this ;
2022-03-30 07:54:07 +02:00
this . $button . on ( 'click.bs.dropdown.data-api' , function ( ) {
if ( ! ! that . $searchbox . val ( ) ) {
that . $searchbox . val ( '' ) ;
2022-04-13 12:34:14 +02:00
that . selectpicker . search . previousValue = undefined ;
2022-03-30 07:54:07 +02:00
}
} ) ;
this . $searchbox . on ( 'click.bs.dropdown.data-api focus.bs.dropdown.data-api touchend.bs.dropdown.data-api' , function ( e ) {
e . stopPropagation ( ) ;
} ) ;
this . $searchbox . on ( 'input propertychange' , function ( ) {
2022-04-13 12:34:14 +02:00
var searchValue = that . $searchbox [ 0 ] . value ;
2022-03-30 07:54:07 +02:00
that . selectpicker . search . elements = [ ] ;
that . selectpicker . search . data = [ ] ;
if ( searchValue ) {
2022-07-08 11:28:27 +02:00
that . selectpicker . search . previousValue = searchValue ;
2022-04-13 12:34:14 +02:00
if ( that . options . source . search ) {
that . fetchData ( function ( builtData ) {
that . render ( ) ;
that . buildList ( undefined , true ) ;
2022-07-08 11:28:27 +02:00
that . noScroll = true ;
that . $menuInner . scrollTop ( 0 ) ;
2022-04-13 12:34:14 +02:00
that . createView ( true ) ;
showNoResults . call ( that , builtData , searchValue ) ;
} , 'search' , 0 , searchValue ) ;
} else {
var i ,
searchMatch = [ ] ,
q = searchValue . toUpperCase ( ) ,
cache = { } ,
cacheArr = [ ] ,
searchStyle = that . _searchStyle ( ) ,
normalizeSearch = that . options . liveSearchNormalize ;
2022-03-30 07:54:07 +02:00
2022-04-13 12:34:14 +02:00
if ( normalizeSearch ) q = normalizeToBase ( q ) ;
2022-03-30 07:54:07 +02:00
2022-04-13 12:34:14 +02:00
for ( var i = 0 ; i < that . selectpicker . main . data . length ; i ++ ) {
var li = that . selectpicker . main . data [ i ] ;
2022-03-30 07:54:07 +02:00
2022-04-13 12:34:14 +02:00
if ( ! cache [ i ] ) {
cache [ i ] = stringSearch ( li , q , searchStyle , normalizeSearch ) ;
2022-03-30 07:54:07 +02:00
}
2022-04-13 12:34:14 +02:00
if ( cache [ i ] && li . headerIndex !== undefined && cacheArr . indexOf ( li . headerIndex ) === - 1 ) {
if ( li . headerIndex > 0 ) {
cache [ li . headerIndex - 1 ] = true ;
cacheArr . push ( li . headerIndex - 1 ) ;
}
2022-03-30 07:54:07 +02:00
2022-04-13 12:34:14 +02:00
cache [ li . headerIndex ] = true ;
cacheArr . push ( li . headerIndex ) ;
2022-03-30 07:54:07 +02:00
2022-04-13 12:34:14 +02:00
cache [ li . lastIndex + 1 ] = true ;
}
2022-03-30 07:54:07 +02:00
2022-04-13 12:34:14 +02:00
if ( cache [ i ] && li . type !== 'optgroup-label' ) cacheArr . push ( i ) ;
2022-03-30 07:54:07 +02:00
}
2022-04-13 12:34:14 +02:00
for ( var i = 0 , cacheLen = cacheArr . length ; i < cacheLen ; i ++ ) {
var index = cacheArr [ i ] ,
prevIndex = cacheArr [ i - 1 ] ,
li = that . selectpicker . main . data [ index ] ,
liPrev = that . selectpicker . main . data [ prevIndex ] ;
2022-03-30 07:54:07 +02:00
2022-04-13 12:34:14 +02:00
if ( li . type !== 'divider' || ( li . type === 'divider' && liPrev && liPrev . type !== 'divider' && cacheLen - 1 !== i ) ) {
that . selectpicker . search . data . push ( li ) ;
searchMatch . push ( that . selectpicker . main . elements [ index ] ) ;
}
}
2022-07-08 11:28:27 +02:00
that . activeElement = undefined ;
2022-04-13 12:34:14 +02:00
that . noScroll = true ;
that . $menuInner . scrollTop ( 0 ) ;
that . selectpicker . search . elements = searchMatch ;
that . createView ( true ) ;
showNoResults . call ( that , searchMatch , searchValue ) ;
2022-03-30 07:54:07 +02:00
}
2022-04-13 12:34:14 +02:00
} else if ( that . selectpicker . search . previousValue ) { // for IE11 (#2402)
2022-03-30 07:54:07 +02:00
that . $menuInner . scrollTop ( 0 ) ;
that . createView ( false ) ;
}
} ) ;
} ,
_searchStyle : function ( ) {
return this . options . liveSearchStyle || 'contains' ;
} ,
val : function ( value ) {
var element = this . $element [ 0 ] ;
if ( typeof value !== 'undefined' ) {
2022-04-13 12:34:14 +02:00
var selectedOptions = getSelectedOptions . call ( this ) ,
prevValue = getSelectValues . call ( this , selectedOptions ) ;
2022-03-30 07:54:07 +02:00
changedArguments = [ null , null , prevValue ] ;
2022-04-13 12:34:14 +02:00
if ( ! Array . isArray ( value ) ) value = [ value ] ;
value . map ( String ) ;
for ( var i = 0 ; i < selectedOptions . length ; i ++ ) {
var item = selectedOptions [ i ] ;
if ( item && value . indexOf ( String ( item . value ) ) === - 1 ) {
this . setSelected ( item , false ) ;
}
}
// only update selected value if it matches an existing option
this . selectpicker . main . data . filter ( function ( item ) {
if ( value . indexOf ( String ( item . value ) ) !== - 1 ) {
this . setSelected ( item , true ) ;
return true ;
}
return false ;
} , this ) ;
if ( this . options . source . data ) element . appendChild ( this . selectpicker . main . optionQueue ) ;
this . $element . trigger ( 'changed' + EVENT _KEY , changedArguments ) ;
2022-03-30 07:54:07 +02:00
if ( this . $newElement . hasClass ( classNames . SHOW ) ) {
if ( this . multiple ) {
this . setOptionStatus ( true ) ;
} else {
var liSelectedIndex = ( element . options [ element . selectedIndex ] || { } ) . liIndex ;
if ( typeof liSelectedIndex === 'number' ) {
2022-07-08 11:28:27 +02:00
this . setSelected ( this . selectpicker . current . data [ liSelectedIndex ] , true ) ;
2022-03-30 07:54:07 +02:00
}
}
}
this . render ( ) ;
changedArguments = null ;
return this . $element ;
} else {
return this . $element . val ( ) ;
}
} ,
changeAll : function ( status ) {
if ( ! this . multiple ) return ;
if ( typeof status === 'undefined' ) status = true ;
var element = this . $element [ 0 ] ,
previousSelected = 0 ,
currentSelected = 0 ,
2022-04-13 12:34:14 +02:00
prevValue = getSelectValues . call ( this ) ;
2022-03-30 07:54:07 +02:00
element . classList . add ( 'bs-select-hidden' ) ;
for ( var i = 0 , data = this . selectpicker . current . data , len = data . length ; i < len ; i ++ ) {
var liData = data [ i ] ,
option = liData . option ;
if ( option && ! liData . disabled && liData . type !== 'divider' ) {
if ( liData . selected ) previousSelected ++ ;
option . selected = status ;
2022-04-13 12:34:14 +02:00
liData . selected = status ;
2022-03-30 07:54:07 +02:00
if ( status === true ) currentSelected ++ ;
}
}
element . classList . remove ( 'bs-select-hidden' ) ;
if ( previousSelected === currentSelected ) return ;
this . setOptionStatus ( ) ;
changedArguments = [ null , null , prevValue ] ;
this . $element
. triggerNative ( 'change' ) ;
} ,
selectAll : function ( ) {
return this . changeAll ( true ) ;
} ,
deselectAll : function ( ) {
return this . changeAll ( false ) ;
} ,
2022-04-13 12:34:14 +02:00
toggle : function ( e , state ) {
var isActive ,
triggerClick = state === undefined ;
2022-03-30 07:54:07 +02:00
e = e || window . event ;
if ( e ) e . stopPropagation ( ) ;
2022-04-13 12:34:14 +02:00
if ( triggerClick === false ) {
isActive = this . $newElement [ 0 ] . classList . contains ( classNames . SHOW ) ;
triggerClick = state === true && isActive === false || state === false && isActive === true ;
}
if ( triggerClick ) this . $button . trigger ( 'click.bs.dropdown.data-api' ) ;
} ,
open : function ( e ) {
this . toggle ( e , true ) ;
} ,
close : function ( e ) {
this . toggle ( e , false ) ;
2022-03-30 07:54:07 +02:00
} ,
keydown : function ( e ) {
var $this = $ ( this ) ,
isToggle = $this . hasClass ( 'dropdown-toggle' ) ,
$parent = isToggle ? $this . closest ( '.dropdown' ) : $this . closest ( Selector . MENU ) ,
that = $parent . data ( 'this' ) ,
$items = that . findLis ( ) ,
index ,
isActive ,
liActive ,
activeLi ,
offset ,
updateScroll = false ,
downOnTab = e . which === keyCodes . TAB && ! isToggle && ! that . options . selectOnTab ,
isArrowKey = REGEXP _ARROW . test ( e . which ) || downOnTab ,
scrollTop = that . $menuInner [ 0 ] . scrollTop ,
isVirtual = that . isVirtual ( ) ,
position0 = isVirtual === true ? that . selectpicker . view . position0 : 0 ;
// do nothing if a function key is pressed
if ( e . which >= 112 && e . which <= 123 ) return ;
2022-04-13 12:34:14 +02:00
isActive = that . $menu . hasClass ( classNames . SHOW ) ;
2022-03-30 07:54:07 +02:00
if (
! isActive &&
(
isArrowKey ||
( e . which >= 48 && e . which <= 57 ) ||
( e . which >= 96 && e . which <= 105 ) ||
( e . which >= 65 && e . which <= 90 )
)
) {
that . $button . trigger ( 'click.bs.dropdown.data-api' ) ;
if ( that . options . liveSearch ) {
that . $searchbox . trigger ( 'focus' ) ;
return ;
}
}
if ( e . which === keyCodes . ESCAPE && isActive ) {
e . preventDefault ( ) ;
that . $button . trigger ( 'click.bs.dropdown.data-api' ) . trigger ( 'focus' ) ;
}
if ( isArrowKey ) { // if up or down
if ( ! $items . length ) return ;
2022-07-08 11:28:27 +02:00
liActive = that . activeElement ;
2022-03-30 07:54:07 +02:00
index = liActive ? Array . prototype . indexOf . call ( liActive . parentElement . children , liActive ) : - 1 ;
if ( index !== - 1 ) {
that . defocusItem ( liActive ) ;
}
if ( e . which === keyCodes . ARROW _UP ) { // up
if ( index !== - 1 ) index -- ;
if ( index + position0 < 0 ) index += $items . length ;
if ( ! that . selectpicker . view . canHighlight [ index + position0 ] ) {
index = that . selectpicker . view . canHighlight . slice ( 0 , index + position0 ) . lastIndexOf ( true ) - position0 ;
if ( index === - 1 ) index = $items . length - 1 ;
}
} else if ( e . which === keyCodes . ARROW _DOWN || downOnTab ) { // down
index ++ ;
2022-04-13 12:34:14 +02:00
if ( index + position0 >= that . selectpicker . view . canHighlight . length ) index = that . selectpicker . view . firstHighlightIndex ;
2022-03-30 07:54:07 +02:00
if ( ! that . selectpicker . view . canHighlight [ index + position0 ] ) {
index = index + 1 + that . selectpicker . view . canHighlight . slice ( index + position0 + 1 ) . indexOf ( true ) ;
}
}
e . preventDefault ( ) ;
var liActiveIndex = position0 + index ;
if ( e . which === keyCodes . ARROW _UP ) { // up
// scroll to bottom and highlight last option
if ( position0 === 0 && index === $items . length - 1 ) {
that . $menuInner [ 0 ] . scrollTop = that . $menuInner [ 0 ] . scrollHeight ;
liActiveIndex = that . selectpicker . current . elements . length - 1 ;
} else {
activeLi = that . selectpicker . current . data [ liActiveIndex ] ;
2022-07-08 11:28:27 +02:00
// could be undefined if no results exist
if ( activeLi ) {
offset = activeLi . position - activeLi . height ;
updateScroll = offset < scrollTop ;
}
2022-03-30 07:54:07 +02:00
}
} else if ( e . which === keyCodes . ARROW _DOWN || downOnTab ) { // down
// scroll to top and highlight first option
2022-04-13 12:34:14 +02:00
if ( index === that . selectpicker . view . firstHighlightIndex ) {
2022-03-30 07:54:07 +02:00
that . $menuInner [ 0 ] . scrollTop = 0 ;
2022-04-13 12:34:14 +02:00
liActiveIndex = that . selectpicker . view . firstHighlightIndex ;
2022-03-30 07:54:07 +02:00
} else {
activeLi = that . selectpicker . current . data [ liActiveIndex ] ;
2022-07-08 11:28:27 +02:00
// could be undefined if no results exist
if ( activeLi ) {
offset = activeLi . position - that . sizeInfo . menuInnerHeight ;
updateScroll = offset > scrollTop ;
}
2022-03-30 07:54:07 +02:00
}
}
liActive = that . selectpicker . current . elements [ liActiveIndex ] ;
2022-07-08 11:28:27 +02:00
that . activeElement = ( that . selectpicker . current . data [ liActiveIndex ] || { } ) . element ;
2022-03-30 07:54:07 +02:00
that . focusItem ( liActive ) ;
that . selectpicker . view . currentActive = liActive ;
if ( updateScroll ) that . $menuInner [ 0 ] . scrollTop = offset ;
if ( that . options . liveSearch ) {
that . $searchbox . trigger ( 'focus' ) ;
} else {
$this . trigger ( 'focus' ) ;
}
} else if (
( ! $this . is ( 'input' ) && ! REGEXP _TAB _OR _ESCAPE . test ( e . which ) ) ||
( e . which === keyCodes . SPACE && that . selectpicker . keydown . keyHistory )
) {
var searchMatch ,
matches = [ ] ,
keyHistory ;
e . preventDefault ( ) ;
that . selectpicker . keydown . keyHistory += keyCodeMap [ e . which ] ;
if ( that . selectpicker . keydown . resetKeyHistory . cancel ) clearTimeout ( that . selectpicker . keydown . resetKeyHistory . cancel ) ;
that . selectpicker . keydown . resetKeyHistory . cancel = that . selectpicker . keydown . resetKeyHistory . start ( ) ;
keyHistory = that . selectpicker . keydown . keyHistory ;
// if all letters are the same, set keyHistory to just the first character when searching
if ( /^(.)\1+$/ . test ( keyHistory ) ) {
keyHistory = keyHistory . charAt ( 0 ) ;
}
// find matches
for ( var i = 0 ; i < that . selectpicker . current . data . length ; i ++ ) {
var li = that . selectpicker . current . data [ i ] ,
hasMatch ;
hasMatch = stringSearch ( li , keyHistory , 'startsWith' , true ) ;
if ( hasMatch && that . selectpicker . view . canHighlight [ i ] ) {
2022-07-08 11:28:27 +02:00
matches . push ( li . element ) ;
2022-03-30 07:54:07 +02:00
}
}
if ( matches . length ) {
var matchIndex = 0 ;
$items . removeClass ( 'active' ) . find ( 'a' ) . removeClass ( 'active' ) ;
// either only one key has been pressed or they are all the same key
if ( keyHistory . length === 1 ) {
2022-07-08 11:28:27 +02:00
matchIndex = matches . indexOf ( that . activeElement ) ;
2022-03-30 07:54:07 +02:00
if ( matchIndex === - 1 || matchIndex === matches . length - 1 ) {
matchIndex = 0 ;
} else {
matchIndex ++ ;
}
}
searchMatch = matches [ matchIndex ] ;
activeLi = that . selectpicker . main . data [ searchMatch ] ;
if ( scrollTop - activeLi . position > 0 ) {
offset = activeLi . position - activeLi . height ;
updateScroll = true ;
} else {
offset = activeLi . position - that . sizeInfo . menuInnerHeight ;
// if the option is already visible at the current scroll position, just keep it the same
updateScroll = activeLi . position > scrollTop + that . sizeInfo . menuInnerHeight ;
}
liActive = that . selectpicker . main . elements [ searchMatch ] ;
2022-07-08 11:28:27 +02:00
that . activeElement = liActive ;
2022-03-30 07:54:07 +02:00
that . focusItem ( liActive ) ;
if ( liActive ) liActive . firstChild . focus ( ) ;
if ( updateScroll ) that . $menuInner [ 0 ] . scrollTop = offset ;
$this . trigger ( 'focus' ) ;
}
}
// Select focused option if "Enter", "Spacebar" or "Tab" (when selectOnTab is true) are pressed inside the menu.
if (
isActive &&
(
( e . which === keyCodes . SPACE && ! that . selectpicker . keydown . keyHistory ) ||
e . which === keyCodes . ENTER ||
( e . which === keyCodes . TAB && that . options . selectOnTab )
)
) {
if ( e . which !== keyCodes . SPACE ) e . preventDefault ( ) ;
if ( ! that . options . liveSearch || e . which !== keyCodes . SPACE ) {
that . $menuInner . find ( '.active a' ) . trigger ( 'click' , true ) ; // retain active class
$this . trigger ( 'focus' ) ;
if ( ! that . options . liveSearch ) {
// Prevent screen from scrolling if the user hits the spacebar
e . preventDefault ( ) ;
// Fixes spacebar selection of dropdown items in FF & IE
$ ( document ) . data ( 'spaceSelect' , true ) ;
}
}
}
} ,
mobile : function ( ) {
2022-04-13 12:34:14 +02:00
// ensure mobile is set to true if mobile function is called after init
this . options . mobile = true ;
2022-03-30 07:54:07 +02:00
this . $element [ 0 ] . classList . add ( 'mobile-device' ) ;
} ,
refresh : function ( ) {
2022-04-13 12:34:14 +02:00
var that = this ;
2022-03-30 07:54:07 +02:00
// update options if data attributes have been changed
2022-04-13 12:34:14 +02:00
var config = $ . extend ( { } , this . options , getAttributesObject ( this . $element ) , this . $element . data ( ) ) ; // in this order on refresh, as user may change attributes on select, and options object is not passed on refresh
2022-03-30 07:54:07 +02:00
this . options = config ;
2022-04-13 12:34:14 +02:00
if ( this . options . source . data ) {
this . render ( ) ;
this . buildList ( ) ;
} else {
this . fetchData ( function ( ) {
that . render ( ) ;
that . buildList ( ) ;
} ) ;
}
2022-03-30 07:54:07 +02:00
this . checkDisabled ( ) ;
this . setStyle ( ) ;
this . setWidth ( ) ;
this . setSize ( true ) ;
this . $element . trigger ( 'refreshed' + EVENT _KEY ) ;
} ,
hide : function ( ) {
this . $newElement . hide ( ) ;
} ,
show : function ( ) {
this . $newElement . show ( ) ;
} ,
remove : function ( ) {
this . $newElement . remove ( ) ;
this . $element . remove ( ) ;
} ,
destroy : function ( ) {
this . $newElement . before ( this . $element ) . remove ( ) ;
if ( this . $bsContainer ) {
this . $bsContainer . remove ( ) ;
} else {
this . $menu . remove ( ) ;
}
2022-04-13 12:34:14 +02:00
if ( this . selectpicker . view . titleOption && this . selectpicker . view . titleOption . parentNode ) {
this . selectpicker . view . titleOption . parentNode . removeChild ( this . selectpicker . view . titleOption ) ;
}
2022-03-30 07:54:07 +02:00
this . $element
. off ( EVENT _KEY )
. removeData ( 'selectpicker' )
2022-07-08 11:28:27 +02:00
. removeClass ( 'bs-select-hidden selectpicker mobile-device' ) ;
2022-03-30 07:54:07 +02:00
$ ( window ) . off ( EVENT _KEY + '.' + this . selectId ) ;
}
} ;
// SELECTPICKER PLUGIN DEFINITION
// ==============================
function Plugin ( option ) {
// get the args of the outer function..
var args = arguments ;
// The arguments of the function are explicitly re-defined from the argument list, because the shift causes them
// to get lost/corrupted in android 2.3 and IE9 #715 #775
var _option = option ;
[ ] . shift . apply ( args ) ;
// if the version was not set successfully
if ( ! version . success ) {
// try to retreive it again
try {
2022-04-13 12:34:14 +02:00
version . full = ( getVersion ( ) || '' ) . split ( ' ' ) [ 0 ] . split ( '.' ) ;
2022-03-30 07:54:07 +02:00
} catch ( err ) {
// fall back to use BootstrapVersion if set
if ( Selectpicker . BootstrapVersion ) {
version . full = Selectpicker . BootstrapVersion . split ( ' ' ) [ 0 ] . split ( '.' ) ;
} else {
version . full = [ version . major , '0' , '0' ] ;
console . warn (
'There was an issue retrieving Bootstrap\'s version. ' +
'Ensure Bootstrap is being loaded before bootstrap-select and there is no namespace collision. ' +
'If loading Bootstrap asynchronously, the version may need to be manually specified via $.fn.selectpicker.Constructor.BootstrapVersion.' ,
err
) ;
}
}
version . major = version . full [ 0 ] ;
version . success = true ;
}
2022-04-13 12:34:14 +02:00
if ( version . major >= '4' ) {
2022-03-30 07:54:07 +02:00
// some defaults need to be changed if using Bootstrap 4
// check to see if they have already been manually changed before forcing them to update
var toUpdate = [ ] ;
if ( Selectpicker . DEFAULTS . style === classNames . BUTTONCLASS ) toUpdate . push ( { name : 'style' , className : 'BUTTONCLASS' } ) ;
if ( Selectpicker . DEFAULTS . iconBase === classNames . ICONBASE ) toUpdate . push ( { name : 'iconBase' , className : 'ICONBASE' } ) ;
if ( Selectpicker . DEFAULTS . tickIcon === classNames . TICKICON ) toUpdate . push ( { name : 'tickIcon' , className : 'TICKICON' } ) ;
classNames . DIVIDER = 'dropdown-divider' ;
classNames . SHOW = 'show' ;
classNames . BUTTONCLASS = 'btn-light' ;
classNames . POPOVERHEADER = 'popover-header' ;
classNames . ICONBASE = '' ;
classNames . TICKICON = 'bs-ok-default' ;
for ( var i = 0 ; i < toUpdate . length ; i ++ ) {
var option = toUpdate [ i ] ;
Selectpicker . DEFAULTS [ option . name ] = classNames [ option . className ] ;
}
}
2022-04-13 12:34:14 +02:00
if ( version . major > '4' ) {
2022-07-08 11:28:27 +02:00
Selector . DATA _TOGGLE = 'data-bs-toggle="dropdown"' ;
2022-04-13 12:34:14 +02:00
}
2022-03-30 07:54:07 +02:00
var value ;
var chain = this . each ( function ( ) {
var $this = $ ( this ) ;
if ( $this . is ( 'select' ) ) {
var data = $this . data ( 'selectpicker' ) ,
options = typeof _option == 'object' && _option ;
2022-04-13 12:34:14 +02:00
// for backwards compatibility
// (using title as placeholder is deprecated - remove in v2.0.0)
if ( options . title ) options . placeholder = options . title ;
2022-03-30 07:54:07 +02:00
if ( ! data ) {
var dataAttributes = $this . data ( ) ;
for ( var dataAttr in dataAttributes ) {
2022-04-13 12:34:14 +02:00
if ( Object . prototype . hasOwnProperty . call ( dataAttributes , dataAttr ) && $ . inArray ( dataAttr , DISALLOWED _ATTRIBUTES ) !== - 1 ) {
2022-03-30 07:54:07 +02:00
delete dataAttributes [ dataAttr ] ;
}
}
2022-04-13 12:34:14 +02:00
var config = $ . extend ( { } , Selectpicker . DEFAULTS , $ . fn . selectpicker . defaults || { } , getAttributesObject ( $this ) , dataAttributes , options ) ; // this is correct order on initial render
2022-03-30 07:54:07 +02:00
config . template = $ . extend ( { } , Selectpicker . DEFAULTS . template , ( $ . fn . selectpicker . defaults ? $ . fn . selectpicker . defaults . template : { } ) , dataAttributes . template , options . template ) ;
2022-07-08 11:28:27 +02:00
config . source = $ . extend ( { } , Selectpicker . DEFAULTS . source , ( $ . fn . selectpicker . defaults ? $ . fn . selectpicker . defaults . source : { } ) , options . source ) ;
2022-03-30 07:54:07 +02:00
$this . data ( 'selectpicker' , ( data = new Selectpicker ( this , config ) ) ) ;
} else if ( options ) {
for ( var i in options ) {
2022-04-13 12:34:14 +02:00
if ( Object . prototype . hasOwnProperty . call ( options , i ) ) {
2022-03-30 07:54:07 +02:00
data . options [ i ] = options [ i ] ;
}
}
}
if ( typeof _option == 'string' ) {
if ( data [ _option ] instanceof Function ) {
value = data [ _option ] . apply ( data , args ) ;
} else {
value = data . options [ _option ] ;
}
}
}
} ) ;
if ( typeof value !== 'undefined' ) {
// noinspection JSUnusedAssignment
return value ;
} else {
return chain ;
}
}
var old = $ . fn . selectpicker ;
$ . fn . selectpicker = Plugin ;
$ . fn . selectpicker . Constructor = Selectpicker ;
// SELECTPICKER NO CONFLICT
// ========================
$ . fn . selectpicker . noConflict = function ( ) {
$ . fn . selectpicker = old ;
return this ;
} ;
// get Bootstrap's keydown event handler for either Bootstrap 4 or Bootstrap 3
2022-04-13 12:34:14 +02:00
function keydownHandler ( ) {
if ( version . major < 5 ) {
if ( $ . fn . dropdown ) {
// wait to define until function is called in case Bootstrap isn't loaded yet
var bootstrapKeydown = $ . fn . dropdown . Constructor . _dataApiKeydownHandler || $ . fn . dropdown . Constructor . prototype . keydown ;
return bootstrapKeydown . apply ( this , arguments ) ;
}
} else {
return Dropdown . dataApiKeydownHandler ;
}
}
2022-03-30 07:54:07 +02:00
$ ( document )
. off ( 'keydown.bs.dropdown.data-api' )
2022-04-13 12:34:14 +02:00
. on ( 'keydown.bs.dropdown.data-api' , ':not(.bootstrap-select) > [' + Selector . DATA _TOGGLE + ']' , keydownHandler )
. on ( 'keydown.bs.dropdown.data-api' , ':not(.bootstrap-select) > .dropdown-menu' , keydownHandler )
. on ( 'keydown' + EVENT _KEY , '.bootstrap-select [' + Selector . DATA _TOGGLE + '], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input' , Selectpicker . prototype . keydown )
. on ( 'focusin.modal' , '.bootstrap-select [' + Selector . DATA _TOGGLE + '], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input' , function ( e ) {
2022-03-30 07:54:07 +02:00
e . stopPropagation ( ) ;
} ) ;
// SELECTPICKER DATA-API
// =====================
2022-04-13 12:34:14 +02:00
document . addEventListener ( 'DOMContentLoaded' , function ( ) {
2022-03-30 07:54:07 +02:00
$ ( '.selectpicker' ) . each ( function ( ) {
var $selectpicker = $ ( this ) ;
Plugin . call ( $selectpicker , $selectpicker . data ( ) ) ;
2022-07-08 11:28:27 +02:00
} ) ;
2022-03-30 07:54:07 +02:00
} ) ;
} ) ( jQuery ) ;