vendor: 6,500 bytes, lines 1-247
1/** 2 * sifter.js 3 * Copyright (c) 2013â2020 Brian Reavis & contributors 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this 6 * file except in compliance with the License. You may obtain a copy of the License at: 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under 10 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 11 * ANY KIND, either express or implied. See the License for the specific language 12 * governing permissions and limitations under the License. 13 * 14 * @author Brian Reavis <[email protected]> 15 */ 16 17(function(root, factory) { 18 if (typeof define === 'function' && define.amd) { 19 define('sifter', factory); 20 } else if (typeof exports === 'object') { 21 module.exports = factory(); 22 } else { 23 root.Sifter = factory(); 24 } 25}(this, function() { 26 27 /** 28 * Textually searches arrays and hashes of objects 29 * by property (or multiple properties). Designed 30 * specifically for autocomplete. 31 * 32 * @constructor 33 * @param {array|object} items 34 * @param {object} items 35 */ 36 var Sifter = function(items, settings) { 37 this.items = items; 38 this.settings = settings || {diacritics: true}; 39 }; 40 41 /** 42 * Splits a search string into an array of individual 43 * regexps to be used to match results. 44 * 45 * @param {string} query 46 * @returns {array} 47 */ 48 Sifter.prototype.tokenize = function(query, respect_word_boundaries) { 49 query = trim(String(query || '').toLowerCase()); 50 if (!query || !query.length) return []; 51 52 var i, n, regex, letter; 53 var tokens = []; 54 var words = query.split(/ +/); 55 56 for (i = 0, n = words.length; i < n; i++) { 57 regex = escape_regex(words[i]); 58 if (this.settings.diacritics) { 59 for (letter in DIACRITICS) { 60 if (DIACRITICS.hasOwnProperty(letter)) { 61 regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]); 62 } 63 } 64 } 65 if (respect_word_boundaries) regex = "\\b"+regex 66 tokens.push({ 67 string : words[i], 68 regex : new RegExp(regex, 'i') 69 }); 70 } 71 72 return tokens; 73 }; 74 75 /** 76 * Iterates over arrays and hashes. 77 * 78 * ``` 79 * this.iterator(this.items, function(item, id) { 80 * // invoked for each item 81 * }); 82 * ``` 83 * 84 * @param {array|object} object 85 */ 86 Sifter.prototype.iterator = function(object, callback) { 87 var iterator; 88 if (is_array(object)) { 89 iterator = Array.prototype.forEach || function(callback) { 90 for (var i = 0, n = this.length; i < n; i++) { 91 callback(this[i], i, this); 92 } 93 }; 94 } else { 95 iterator = function(callback) { 96 for (var key in this) { 97 if (this.hasOwnProperty(key)) { 98 callback(this[key], key, this); 99 } 100 } 101 }; 102 } 103 104 iterator.apply(object, [callback]); 105 }; 106 107 /** 108 * Returns a function to be used to score individual results. 109 * 110 * Good matches will have a higher score than poor matches. 111 * If an item is not a match, 0 will be returned by the function. 112 * 113 * @param {object|string} search 114 * @param {object} options (optional) 115 * @returns {function} 116 */ 117 Sifter.prototype.getScoreFunction = function(search, options) { 118 var self, fields, tokens, token_count, nesting; 119 120 self = this; 121 search = self.prepareSearch(search, options); 122 tokens = search.tokens; 123 fields = search.options.fields; 124 token_count = tokens.length; 125 nesting = search.options.nesting; 126 127 /** 128 * Calculates how close of a match the 129 * given value is against a search token. 130 * 131 * @param {mixed} value 132 * @param {object} token 133 * @return {number} 134 */ 135 var scoreValue = function(value, token) { 136 var score, pos; 137 138 if (!value) return 0; 139 value = String(value || ''); 140 pos = value.search(token.regex); 141 if (pos === -1) return 0; 142 score = token.string.length / value.length; 143 if (pos === 0) score += 0.5; 144 return score; 145 }; 146 147 /** 148 * Calculates the score of an object 149 * against the search query. 150 * 151 * @param {object} token 152 * @param {object} data 153 * @return {number} 154 */ 155 var scoreObject = (function() { 156 var field_count = fields.length; 157 if (!field_count) { 158 return function() { return 0; }; 159 } 160 if (field_count === 1) { 161 return function(token, data) { 162 return scoreValue(getattr(data, fields[0], nesting), token); 163 }; 164 } 165 return function(token, data) { 166 for (var i = 0, sum = 0; i < field_count; i++) { 167 sum += scoreValue(getattr(data, fields[i], nesting), token); 168 } 169 return sum / field_count; 170 }; 171 })(); 172 173 if (!token_count) { 174 return function() { return 0; }; 175 } 176 if (token_count === 1) { 177 return function(data) { 178 return scoreObject(tokens[0], data); 179 }; 180 } 181 182 if (search.options.conjunction === 'and') { 183 return function(data) { 184 var score; 185 for (var i = 0, sum = 0; i < token_count; i++) { 186 score = scoreObject(tokens[i], data); 187 if (score <= 0) return 0; 188 sum += score; 189 } 190 return sum / token_count; 191 }; 192 } else { 193 return function(data) { 194 for (var i = 0, sum = 0; i < token_count; i++) { 195 sum += scoreObject(tokens[i], data); 196 } 197 return sum / token_count; 198 }; 199 } 200 }; 201 202 /** 203 * Returns a function that can be used to compare two 204 * results, for sorting purposes. If no sorting should 205 * be performed, `null` will be returned. 206 * 207 * @param {string|object} search 208 * @param {object} options 209 * @return function(a,b) 210 */ 211 Sifter.prototype.getSortFunction = function(search, options) { 212 var i, n, self, field, fields, fields_count, multiplier, multipliers, get_field, implicit_score, sort; 213 214 self = this; 215 search = self.prepareSearch(search, options); 216 sort = (!search.query && options.sort_empty) || options.sort; 217 218 /** 219 * Fetches the specified sort field value 220 * from a search result item. 221 * 222 * @param {string} name 223 * @param {object} result 224 * @return {mixed} 225 */ 226 get_field = function(name, result) { 227 if (name === '$score') return result.score; 228 return getattr(self.items[result.id], name, options.nesting); 229 }; 230 231 // parse options 232 fields = []; 233 if (sort) { 234 for (i = 0, n = sort.length; i < n; i++) { 235 if (search.query || sort[i].field !== '$score') { 236 fields.push(sort[i]); 237 } 238 } 239 } 240 241 // the "$score" field is implied to be the primary 242 // sort field, unless it's manually specified 243 if (search.query) { 244 implicit_score = true; 245 for (i = 0, n = fields.length; i < n; i++) { 246 if (fields[i].field === '$score') { 247 implicit_score = false;
vendor: 2,197 bytes, lines 248-338
248 break; 249 } 250 } 251 if (implicit_score) { 252 fields.unshift({field: '$score', direction: 'desc'}); 253 } 254 } else { 255 for (i = 0, n = fields.length; i < n; i++) { 256 if (fields[i].field === '$score') { 257 fields.splice(i, 1); 258 break; 259 } 260 } 261 } 262 263 multipliers = []; 264 for (i = 0, n = fields.length; i < n; i++) { 265 multipliers.push(fields[i].direction === 'desc' ? -1 : 1); 266 } 267 268 // build function 269 fields_count = fields.length; 270 if (!fields_count) { 271 return null; 272 } else if (fields_count === 1) { 273 field = fields[0].field; 274 multiplier = multipliers[0]; 275 return function(a, b) { 276 return multiplier * cmp( 277 get_field(field, a), 278 get_field(field, b) 279 ); 280 }; 281 } else { 282 return function(a, b) { 283 var i, result, a_value, b_value, field; 284 for (i = 0; i < fields_count; i++) { 285 field = fields[i].field; 286 result = multipliers[i] * cmp( 287 get_field(field, a), 288 get_field(field, b) 289 ); 290 if (result) return result; 291 } 292 return 0; 293 }; 294 } 295 }; 296 297 /** 298 * Parses a search query and returns an object 299 * with tokens and fields ready to be populated 300 * with results. 301 * 302 * @param {string} query 303 * @param {object} options 304 * @returns {object} 305 */ 306 Sifter.prototype.prepareSearch = function(query, options) { 307 if (typeof query === 'object') return query; 308 309 options = extend({}, options); 310 311 var option_fields = options.fields; 312 var option_sort = options.sort; 313 var option_sort_empty = options.sort_empty; 314 315 if (option_fields && !is_array(option_fields)) options.fields = [option_fields]; 316 if (option_sort && !is_array(option_sort)) options.sort = [option_sort]; 317 if (option_sort_empty && !is_array(option_sort_empty)) options.sort_empty = [option_sort_empty]; 318 319 return { 320 options : options, 321 query : String(query || '').toLowerCase(), 322 tokens : this.tokenize(query, options.respect_word_boundaries), 323 total : 0, 324 items : [] 325 }; 326 }; 327 328 /** 329 * Searches through all items and returns a sorted array of matches. 330 * 331 * The `options` parameter can contain: 332 * 333 * - fields {string|array} 334 * - sort {array} 335 * - score {function} 336 * - filter {bool} 337 * - limit {integer} 338 *
vendor: 10,301 bytes, lines 339-682
339 * Returns an object containing: 340 * 341 * - options {object} 342 * - query {string} 343 * - tokens {array} 344 * - total {int} 345 * - items {array} 346 * 347 * @param {string} query 348 * @param {object} options 349 * @returns {object} 350 */ 351 Sifter.prototype.search = function(query, options) { 352 var self = this, value, score, search, calculateScore; 353 var fn_sort; 354 var fn_score; 355 356 search = this.prepareSearch(query, options); 357 options = search.options; 358 query = search.query; 359 360 // generate result scoring function 361 fn_score = options.score || self.getScoreFunction(search); 362 363 // perform search and sort 364 if (query.length) { 365 self.iterator(self.items, function(item, id) { 366 score = fn_score(item); 367 if (options.filter === false || score > 0) { 368 search.items.push({'score': score, 'id': id}); 369 } 370 }); 371 } else { 372 self.iterator(self.items, function(item, id) { 373 search.items.push({'score': 1, 'id': id}); 374 }); 375 } 376 377 fn_sort = self.getSortFunction(search, options); 378 if (fn_sort) search.items.sort(fn_sort); 379 380 // apply limits 381 search.total = search.items.length; 382 if (typeof options.limit === 'number') { 383 search.items = search.items.slice(0, options.limit); 384 } 385 386 return search; 387 }; 388 389 // utilities 390 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 391 392 var cmp = function(a, b) { 393 if (typeof a === 'number' && typeof b === 'number') { 394 return a > b ? 1 : (a < b ? -1 : 0); 395 } 396 a = asciifold(String(a || '')); 397 b = asciifold(String(b || '')); 398 if (a > b) return 1; 399 if (b > a) return -1; 400 return 0; 401 }; 402 403 var extend = function(a, b) { 404 var i, n, k, object; 405 for (i = 1, n = arguments.length; i < n; i++) { 406 object = arguments[i]; 407 if (!object) continue; 408 for (k in object) { 409 if (object.hasOwnProperty(k)) { 410 a[k] = object[k]; 411 } 412 } 413 } 414 return a; 415 }; 416 417 /** 418 * A property getter resolving dot-notation 419 * @param {Object} obj The root object to fetch property on 420 * @param {String} name The optionally dotted property name to fetch 421 * @param {Boolean} nesting Handle nesting or not 422 * @return {Object} The resolved property value 423 */ 424 var getattr = function(obj, name, nesting) { 425 if (!obj || !name) return; 426 if (!nesting) return obj[name]; 427 var names = name.split("."); 428 while(names.length && (obj = obj[names.shift()])); 429 return obj; 430 }; 431 432 var trim = function(str) { 433 return (str + '').replace(/^\s+|\s+$|/g, ''); 434 }; 435 436 var escape_regex = function(str) { 437 return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); 438 }; 439 440 var is_array = Array.isArray || (typeof $ !== 'undefined' && $.isArray) || function(object) { 441 return Object.prototype.toString.call(object) === '[object Array]'; 442 }; 443 444 var DIACRITICS = { 445 'a': '[aá¸á¸ÄÄÃâÇÇȺⱥȦȧẠạÃäÃà ÃáÄÄÃÃ£Ã Ã¥Ä ÄÃÄ Ä]', 446 'b': '[bâ¢Î²ÎB฿ðá]', 447 'c': '[cÄÄÄÄÄÄÄÄCÌcÌÃçá¸á¸È»È¼ÆÆÉá´ï¼£ï½]', 448 'd': '[dÄÄá¸á¸á¸á¸á¸á¸á¸á¸á¸á¸ÄÄD̦d̦ÆÉÆÉÆÆáµá¶á¶È¡á´ Dï½Ã°]', 449 'e': '[eÃéÃèÃêá¸á¸ÄÄÄÄẼẽá¸á¸áººáº»ÄÄÃëÄÄȨȩÄÄá¶ÉÉÈÈ áº¾áº¿á»á»á»á» á»á»á¸á¸á¸á¸á¸á¸ÈÈẸẹá»á»â±¸á´ï¼¥ï½ ÉÇÆÆÎµ]', 450 'f': '[fÆÆá¸á¸]', 451 'g': '[gÉ¢â²Ç¤Ç¥ÄÄÄÄĢģÆÉ Ä Ä¡]', 452 'h': '[hĤĥĦħḨḩáºáºá¸¤á¸¥á¸¢á¸£É¦Ê°Ç¶Æ]', 453 'i': '[iÃÃÃìĬÄÃîÇÇÃïḮḯĨĩĮįĪīá»á»ÈÈÈÈá»á»á¸¬á¸ÆÉ¨É¨Ìáµ»á¶Ä°iIıɪIï½]', 454 'j': '[jȷĴĵÉÉÊÉʲ]', 455 'k': '[kÆÆêêḰḱǨǩḲḳḴḵκϰâ]', 456 'l': '[lÅÅĽľĻļĹĺḶḷḸḹḼḽḺḻĿÅȽÆâ± ⱡⱢɫɬᶠÉÈ´ÊLï½]', 457 'n': '[nÅÅǸǹÅÅÃñá¹á¹ Å Åá¹á¹á¹á¹á¹á¹NÌnÌÆÉ²È Æáµ°á¶É³ÈµÉ´ï¼®ï½ÅÅ]', 458 'o': '[oÃøÃöÃóÃòÃôÇÇÅÅÅÅȮȯá»á»ÆÉµÆ Æ¡á»á»ÅÅÃõǪǫÈÈÕÖ ]', 459 'p': '[pá¹á¹á¹á¹â±£áµ½Æ¤Æ¥áµ±]', 460 'q': '[qêêÊ ÉÉêêqÌ]', 461 'r': '[rÅÅÉÉÅÅÅÅá¹á¹ÈÈÈÈá¹á¹â±¤É½]', 462 's': '[sÅÅṠṡṢṣê¨ê©ÅÅÅ Å¡ÅÅÈÈSÌsÌ]', 463 't': '[tÅ¤Å¥á¹ªá¹«Å¢Å£á¹¬á¹Æ®ÊÈÈṰṱṮṯƬÆ]', 464 'u': '[uŬÅÉÊỤụÃüÃúÃùÃûÇÇŰűŬů¯Æ°á»¦á»§ÅªÅ«Å¨Å©Å²Å³ÈÈâª]', 465 'v': '[vṼṽṾṿƲÊêêâ±±Ê]', 466 'w': '[wáºáºáºáºÅ´Åµáºáº áºáºáºáº]', 467 'x': '[xáºáºáºáºÏ]', 468 'y': '[yÃýỲỳŶŷŸÿỸỹáºáºá»´á»µÉÉÆ³Æ´]', 469 'z': '[zŹźáºáºÅ½Å¾Å»Å¼áºáºáºáºÆµÆ¶]' 470 }; 471 472 var asciifold = (function() { 473 var i, n, k, chunk; 474 var foreignletters = ''; 475 var lookup = {}; 476 for (k in DIACRITICS) { 477 if (DIACRITICS.hasOwnProperty(k)) { 478 chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1); 479 foreignletters += chunk; 480 for (i = 0, n = chunk.length; i < n; i++) { 481 lookup[chunk.charAt(i)] = k; 482 } 483 } 484 } 485 var regexp = new RegExp('[' + foreignletters + ']', 'g'); 486 return function(str) { 487 return str.replace(regexp, function(foreignletter) { 488 return lookup[foreignletter]; 489 }).toLowerCase(); 490 }; 491 })(); 492 493 494 // export 495 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 496 497 return Sifter; 498})); 499 500 501/** 502 * microplugin.js 503 * Copyright (c) 2013 Brian Reavis & contributors 504 * 505 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this 506 * file except in compliance with the License. You may obtain a copy of the License at: 507 * http://www.apache.org/licenses/LICENSE-2.0 508 * 509 * Unless required by applicable law or agreed to in writing, software distributed under 510 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 511 * ANY KIND, either express or implied. See the License for the specific language 512 * governing permissions and limitations under the License. 513 * 514 * @author Brian Reavis <[email protected]> 515 */ 516 517(function(root, factory) { 518 if (typeof define === 'function' && define.amd) { 519 define('microplugin', factory); 520 } else if (typeof exports === 'object') { 521 module.exports = factory(); 522 } else { 523 root.MicroPlugin = factory(); 524 } 525}(this, function() { 526 var MicroPlugin = {}; 527 528 MicroPlugin.mixin = function(Interface) { 529 Interface.plugins = {}; 530 531 /** 532 * Initializes the listed plugins (with options). 533 * Acceptable formats: 534 * 535 * List (without options): 536 * ['a', 'b', 'c'] 537 * 538 * List (with options): 539 * [{'name': 'a', options: {}}, {'name': 'b', options: {}}] 540 * 541 * Hash (with options): 542 * {'a': { ... }, 'b': { ... }, 'c': { ... }} 543 * 544 * @param {mixed} plugins 545 */ 546 Interface.prototype.initializePlugins = function(plugins) { 547 var i, n, key; 548 var self = this; 549 var queue = []; 550 551 self.plugins = { 552 names : [], 553 settings : {}, 554 requested : {}, 555 loaded : {} 556 }; 557 558 if (utils.isArray(plugins)) { 559 for (i = 0, n = plugins.length; i < n; i++) { 560 if (typeof plugins[i] === 'string') { 561 queue.push(plugins[i]); 562 } else { 563 self.plugins.settings[plugins[i].name] = plugins[i].options; 564 queue.push(plugins[i].name); 565 } 566 } 567 } else if (plugins) { 568 for (key in plugins) { 569 if (plugins.hasOwnProperty(key)) { 570 self.plugins.settings[key] = plugins[key]; 571 queue.push(key); 572 } 573 } 574 } 575 576 while (queue.length) { 577 self.require(queue.shift()); 578 } 579 }; 580 581 Interface.prototype.loadPlugin = function(name) { 582 var self = this; 583 var plugins = self.plugins; 584 var plugin = Interface.plugins[name]; 585 586 if (!Interface.plugins.hasOwnProperty(name)) { 587 throw new Error('Unable to find "' + name + '" plugin'); 588 } 589 590 plugins.requested[name] = true; 591 plugins.loaded[name] = plugin.fn.apply(self, [self.plugins.settings[name] || {}]); 592 plugins.names.push(name); 593 }; 594 595 /** 596 * Initializes a plugin. 597 * 598 * @param {string} name 599 */ 600 Interface.prototype.require = function(name) { 601 var self = this; 602 var plugins = self.plugins; 603 604 if (!self.plugins.loaded.hasOwnProperty(name)) { 605 if (plugins.requested[name]) { 606 throw new Error('Plugin has circular dependency ("' + name + '")'); 607 } 608 self.loadPlugin(name); 609 } 610 611 return plugins.loaded[name]; 612 }; 613 614 /** 615 * Registers a plugin. 616 * 617 * @param {string} name 618 * @param {function} fn 619 */ 620 Interface.define = function(name, fn) { 621 Interface.plugins[name] = { 622 'name' : name, 623 'fn' : fn 624 }; 625 }; 626 }; 627 628 var utils = { 629 isArray: Array.isArray || function(vArg) { 630 return Object.prototype.toString.call(vArg) === '[object Array]'; 631 } 632 }; 633 634 return MicroPlugin; 635})); 636 637/** 638 * selectize.js (v0.13.6) 639 * Copyright (c) 2013â2015 Brian Reavis & contributors 640 * Copyright (c) 2020-2022 Selectize Team & contributors 641 * 642 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this 643 * file except in compliance with the License. You may obtain a copy of the License at: 644 * http://www.apache.org/licenses/LICENSE-2.0 645 * 646 * Unless required by applicable law or agreed to in writing, software distributed under 647 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 648 * ANY KIND, either express or implied. See the License for the specific language 649 * governing permissions and limitations under the License. 650 * 651 * @author Brian Reavis <[email protected]> 652 * @author Ris Adams <[email protected]> 653 */ 654 655/*jshint curly:false */ 656/*jshint browser:true */ 657 658(function(root, factory) { 659 if (typeof define === 'function' && define.amd) { 660 define('selectize', ['jquery','sifter','microplugin'], factory); 661 } else if (typeof module === 'object' && typeof module.exports === 'object') { 662 module.exports = factory(require('jquery'), require('sifter'), require('microplugin')); 663 } else { 664 root.Selectize = factory(root.jQuery, root.Sifter, root.MicroPlugin); 665 } 666}(this, function($, Sifter, MicroPlugin) { 667 'use strict'; 668 669 var highlight = function($element, pattern) { 670 if (typeof pattern === 'string' && !pattern.length) return; 671 var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern; 672 673 var highlight = function(node) { 674 var skip = 0; 675 // Wrap matching part of text node with highlighting <span>, e.g. 676 // Soccer -> <span class="highlight">Soc</span>cer for regex = /soc/i 677 if (node.nodeType === 3) { 678 var pos = node.data.search(regex); 679 if (pos >= 0 && node.data.length > 0) { 680 var match = node.data.match(regex); 681 var spannode = document.createElement('span'); 682 spannode.className = 'highlight';
vendor: 5,314 bytes, lines 683-885
683 var middlebit = node.splitText(pos); 684 var endbit = middlebit.splitText(match[0].length); 685 var middleclone = middlebit.cloneNode(true); 686 spannode.appendChild(middleclone); 687 middlebit.parentNode.replaceChild(spannode, middlebit); 688 skip = 1; 689 } 690 } 691 // Recurse element node, looking for child text nodes to highlight, unless element 692 // is childless, <script>, <style>, or already highlighted: <span class="highlight"> 693 else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName) && ( node.className !== 'highlight' || node.tagName !== 'SPAN' )) { 694 for (var i = 0; i < node.childNodes.length; ++i) { 695 i += highlight(node.childNodes[i]); 696 } 697 } 698 return skip; 699 }; 700 701 return $element.each(function() { 702 highlight(this); 703 }); 704 }; 705 706 /** 707 * removeHighlight fn copied from highlight v5 and 708 * edited to remove with() and pass js strict mode 709 */ 710 $.fn.removeHighlight = function() { 711 return this.find("span.highlight").each(function() { 712 this.parentNode.firstChild.nodeName; 713 var parent = this.parentNode; 714 parent.replaceChild(this.firstChild, this); 715 parent.normalize(); 716 }).end(); 717 }; 718 719 720 var MicroEvent = function() {}; 721 MicroEvent.prototype = { 722 on: function(event, fct){ 723 this._events = this._events || {}; 724 this._events[event] = this._events[event] || []; 725 this._events[event].push(fct); 726 }, 727 off: function(event, fct){ 728 var n = arguments.length; 729 if (n === 0) return delete this._events; 730 if (n === 1) return delete this._events[event]; 731 732 this._events = this._events || {}; 733 if (event in this._events === false) return; 734 this._events[event].splice(this._events[event].indexOf(fct), 1); 735 }, 736 trigger: function(event /* , args... */){ 737 this._events = this._events || {}; 738 if (event in this._events === false) return; 739 for (var i = 0; i < this._events[event].length; i++){ 740 this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1)); 741 } 742 } 743 }; 744 745 /** 746 * Mixin will delegate all MicroEvent.js function in the destination object. 747 * 748 * - MicroEvent.mixin(Foobar) will make Foobar able to use MicroEvent 749 * 750 * @param {object} the object which will support MicroEvent 751 */ 752 MicroEvent.mixin = function(destObject){ 753 var props = ['on', 'off', 'trigger']; 754 for (var i = 0; i < props.length; i++){ 755 destObject.prototype[props[i]] = MicroEvent.prototype[props[i]]; 756 } 757 }; 758 759 function uaDetect(platform, re) { 760 if (navigator.userAgentData) { 761 return platform === navigator.userAgentData.platform; 762 } 763 764 return re.test(navigator.userAgent); 765 } 766 767 var IS_MAC = uaDetect("macOS", /Mac/); 768 769 var KEY_A = 65; 770 var KEY_COMMA = 188; 771 var KEY_RETURN = 13; 772 var KEY_ESC = 27; 773 var KEY_LEFT = 37; 774 var KEY_UP = 38; 775 var KEY_P = 80; 776 var KEY_RIGHT = 39; 777 var KEY_DOWN = 40; 778 var KEY_N = 78; 779 var KEY_BACKSPACE = 8; 780 var KEY_DELETE = 46; 781 var KEY_SHIFT = 16; 782 var KEY_CMD = IS_MAC ? 91 : 17; 783 var KEY_CTRL = IS_MAC ? 18 : 17; 784 var KEY_TAB = 9; 785 786 var TAG_SELECT = 1; 787 var TAG_INPUT = 2; 788 789 // for now, android support in general is too spotty to support validity 790 var SUPPORTS_VALIDITY_API = !uaDetect("Android", /android/i) && !!document.createElement('input').validity; 791 792 793 var isset = function(object) { 794 return typeof object !== 'undefined'; 795 }; 796 797 /** 798 * Converts a scalar to its best string representation 799 * for hash keys and HTML attribute values. 800 * 801 * Transformations: 802 * 'str' -> 'str' 803 * null -> '' 804 * undefined -> '' 805 * true -> '1' 806 * false -> '0' 807 * 0 -> '0' 808 * 1 -> '1' 809 * 810 * @param {string} value 811 * @returns {string|null} 812 */ 813 var hash_key = function(value) { 814 if (typeof value === 'undefined' || value === null) return null; 815 if (typeof value === 'boolean') return value ? '1' : '0'; 816 return value + ''; 817 }; 818 819 /** 820 * Escapes a string for use within HTML. 821 * 822 * @param {string} str 823 * @returns {string} 824 */ 825 var escape_html = function(str) { 826 return (str + '') 827 .replace(/&/g, '&') 828 .replace(/</g, '<') 829 .replace(/>/g, '>') 830 .replace(/"/g, '"'); 831 }; 832 833 /** 834 * Escapes "$" characters in replacement strings. 835 * 836 * @param {string} str 837 * @returns {string} 838 */ 839 var escape_replace = function(str) { 840 return (str + '').replace(/\$/g, '$$$$'); 841 }; 842 843 var hook = {}; 844 845 /** 846 * Wraps `method` on `self` so that `fn` 847 * is invoked before the original method. 848 * 849 * @param {object} self 850 * @param {string} method 851 * @param {function} fn 852 */ 853 hook.before = function(self, method, fn) { 854 var original = self[method]; 855 self[method] = function() { 856 fn.apply(self, arguments); 857 return original.apply(self, arguments); 858 }; 859 }; 860 861 /** 862 * Wraps `method` on `self` so that `fn` 863 * is invoked after the original method. 864 * 865 * @param {object} self 866 * @param {string} method 867 * @param {function} fn 868 */ 869 hook.after = function(self, method, fn) { 870 var original = self[method]; 871 self[method] = function() { 872 var result = original.apply(self, arguments); 873 fn.apply(self, arguments); 874 return result; 875 }; 876 }; 877 878 /** 879 * Wraps `fn` so that it can only be invoked once. 880 * 881 * @param {function} fn 882 * @returns {function} 883 */ 884 var once = function(fn) { 885 var called = false;
vendor: 1,944 bytes, lines 886-968
886 return function() { 887 if (called) return; 888 called = true; 889 fn.apply(this, arguments); 890 }; 891 }; 892 893 /** 894 * Wraps `fn` so that it can only be called once 895 * every `delay` milliseconds (invoked on the falling edge). 896 * 897 * @param {function} fn 898 * @param {int} delay 899 * @returns {function} 900 */ 901 var debounce = function(fn, delay) { 902 var timeout; 903 return function() { 904 var self = this; 905 var args = arguments; 906 window.clearTimeout(timeout); 907 timeout = window.setTimeout(function() { 908 fn.apply(self, args); 909 }, delay); 910 }; 911 }; 912 913 /** 914 * Debounce all fired events types listed in `types` 915 * while executing the provided `fn`. 916 * 917 * @param {object} self 918 * @param {array} types 919 * @param {function} fn 920 */ 921 var debounce_events = function(self, types, fn) { 922 var type; 923 var trigger = self.trigger; 924 var event_args = {}; 925 926 // override trigger method 927 self.trigger = function() { 928 var type = arguments[0]; 929 if (types.indexOf(type) !== -1) { 930 event_args[type] = arguments; 931 } else { 932 return trigger.apply(self, arguments); 933 } 934 }; 935 936 // invoke provided function 937 fn.apply(self, []); 938 self.trigger = trigger; 939 940 // trigger queued events 941 for (type in event_args) { 942 if (event_args.hasOwnProperty(type)) { 943 trigger.apply(self, event_args[type]); 944 } 945 } 946 }; 947 948 /** 949 * A workaround for http://bugs.jquery.com/ticket/6696 950 * 951 * @param {object} $parent - Parent element to listen on. 952 * @param {string} event - Event name. 953 * @param {string} selector - Descendant selector to filter by. 954 * @param {function} fn - Event handler. 955 */ 956 var watchChildEvent = function($parent, event, selector, fn) { 957 $parent.on(event, selector, function(e) { 958 var child = e.target; 959 while (child && child.parentNode !== $parent[0]) { 960 child = child.parentNode; 961 } 962 e.currentTarget = child; 963 return fn.apply(this, [e]); 964 }); 965 }; 966 967 /** 968 * Determines the current selection within a text input control.
vendor: 8,672 bytes, lines 969-1285
969 * Returns an object containing: 970 * - start 971 * - length 972 * 973 * @param {object} input 974 * @returns {object} 975 */ 976 var getSelection = function(input) { 977 var result = {}; 978 if(input === undefined) { 979 console.warn('WARN getSelection cannot locate input control'); 980 return result; 981 } 982 if ('selectionStart' in input) { 983 result.start = input.selectionStart; 984 result.length = input.selectionEnd - result.start; 985 } else if (document.selection) { 986 input.focus(); 987 var sel = document.selection.createRange(); 988 var selLen = document.selection.createRange().text.length; 989 sel.moveStart('character', -input.value.length); 990 result.start = sel.text.length - selLen; 991 result.length = selLen; 992 } 993 return result; 994 }; 995 996 /** 997 * Copies CSS properties from one element to another. 998 * 999 * @param {object} $from 1000 * @param {object} $to 1001 * @param {array} properties 1002 */ 1003 var transferStyles = function($from, $to, properties) { 1004 var i, n, styles = {}; 1005 if (properties) { 1006 for (i = 0, n = properties.length; i < n; i++) { 1007 styles[properties[i]] = $from.css(properties[i]); 1008 } 1009 } else { 1010 styles = $from.css(); 1011 } 1012 $to.css(styles); 1013 }; 1014 1015 /** 1016 * Measures the width of a string within a 1017 * parent element (in pixels). 1018 * 1019 * @param {string} str 1020 * @param {object} $parent 1021 * @returns {int} 1022 */ 1023 var measureString = function(str, $parent) { 1024 if (!str) { 1025 return 0; 1026 } 1027 1028 if (!Selectize.$testInput) { 1029 Selectize.$testInput = $('<span />').css({ 1030 position: 'absolute', 1031 width: 'auto', 1032 padding: 0, 1033 whiteSpace: 'pre' 1034 }); 1035 1036 $('<div />').css({ 1037 position: 'absolute', 1038 width: 0, 1039 height: 0, 1040 overflow: 'hidden' 1041 }).append(Selectize.$testInput).appendTo('body'); 1042 } 1043 1044 Selectize.$testInput.text(str); 1045 1046 transferStyles($parent, Selectize.$testInput, [ 1047 'letterSpacing', 1048 'fontSize', 1049 'fontFamily', 1050 'fontWeight', 1051 'textTransform' 1052 ]); 1053 1054 return Selectize.$testInput.width(); 1055 }; 1056 1057 /** 1058 * Sets up an input to grow horizontally as the user 1059 * types. If the value is changed manually, you can 1060 * trigger the "update" handler to resize: 1061 * 1062 * $input.trigger('update'); 1063 * 1064 * @param {object} $input 1065 */ 1066 var autoGrow = function($input) { 1067 var currentWidth = null; 1068 1069 var update = function(e, options) { 1070 var value, keyCode, printable, width; 1071 var placeholder, placeholderWidth; 1072 var shift, character, selection; 1073 e = e || window.event || {}; 1074 options = options || {}; 1075 1076 if (e.metaKey || e.altKey) return; 1077 if (!options.force && $input.data('grow') === false) return; 1078 1079 value = $input.val(); 1080 if (e.type && e.type.toLowerCase() === 'keydown') { 1081 keyCode = e.keyCode; 1082 printable = ( 1083 (keyCode >= 48 && keyCode <= 57) || // 0-9 1084 (keyCode >= 65 && keyCode <= 90) || // a-z 1085 (keyCode >= 96 && keyCode <= 111) || // numpad 0-9, numeric operators 1086 (keyCode >= 186 && keyCode <= 222) || // semicolon, equal, comma, dash, etc. 1087 keyCode === 32 // space 1088 ); 1089 1090 if (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) { 1091 selection = getSelection($input[0]); 1092 if (selection.length) { 1093 value = value.substring(0, selection.start) + value.substring(selection.start + selection.length); 1094 } else if (keyCode === KEY_BACKSPACE && selection.start) { 1095 value = value.substring(0, selection.start - 1) + value.substring(selection.start + 1); 1096 } else if (keyCode === KEY_DELETE && typeof selection.start !== 'undefined') { 1097 value = value.substring(0, selection.start) + value.substring(selection.start + 1); 1098 } 1099 } else if (printable) { 1100 shift = e.shiftKey; 1101 character = String.fromCharCode(e.keyCode); 1102 if (shift) character = character.toUpperCase(); 1103 else character = character.toLowerCase(); 1104 value += character; 1105 } 1106 } 1107 1108 placeholder = $input.attr('placeholder'); 1109 if (placeholder) { 1110 placeholderWidth = measureString(placeholder, $input) + 4; 1111 } else { 1112 placeholderWidth = 0; 1113 } 1114 1115 width = Math.max(measureString(value, $input), placeholderWidth) + 4; 1116 if (width !== currentWidth) { 1117 currentWidth = width; 1118 $input.width(width); 1119 $input.triggerHandler('resize'); 1120 } 1121 }; 1122 1123 $input.on('keydown keyup update blur', update); 1124 update(); 1125 }; 1126 1127 var domToString = function(d) { 1128 var tmp = document.createElement('div'); 1129 1130 tmp.appendChild(d.cloneNode(true)); 1131 1132 return tmp.innerHTML; 1133 }; 1134 1135 var logError = function(message, options){ 1136 if(!options) options = {}; 1137 var component = "Selectize"; 1138 1139 console.error(component + ": " + message) 1140 1141 if(options.explanation){ 1142 // console.group is undefined in <IE11 1143 if(console.group) console.group(); 1144 console.error(options.explanation); 1145 if(console.group) console.groupEnd(); 1146 } 1147 } 1148 1149 1150 var Selectize = function($input, settings) { 1151 var key, i, n, dir, input, self = this; 1152 input = $input[0]; 1153 input.selectize = self; 1154 1155 // detect rtl environment 1156 var computedStyle = window.getComputedStyle && window.getComputedStyle(input, null); 1157 dir = computedStyle ? computedStyle.getPropertyValue('direction') : input.currentStyle && input.currentStyle.direction; 1158 dir = dir || $input.parents('[dir]:first').attr('dir') || ''; 1159 1160 // setup default state 1161 $.extend(self, { 1162 order : 0, 1163 settings : settings, 1164 $input : $input, 1165 tabIndex : $input.attr('tabindex') || '', 1166 tagType : input.tagName.toLowerCase() === 'select' ? TAG_SELECT : TAG_INPUT, 1167 rtl : /rtl/i.test(dir), 1168 1169 eventNS : '.selectize' + (++Selectize.count), 1170 highlightedValue : null, 1171 isBlurring : false, 1172 isOpen : false, 1173 isDisabled : false, 1174 isRequired : $input.is('[required]'), 1175 isInvalid : false, 1176 isLocked : false, 1177 isFocused : false, 1178 isInputHidden : false, 1179 isSetup : false, 1180 isShiftDown : false, 1181 isCmdDown : false, 1182 isCtrlDown : false, 1183 ignoreFocus : false, 1184 ignoreBlur : false, 1185 ignoreHover : false, 1186 hasOptions : false, 1187 currentResults : null, 1188 lastValue : '', 1189 lastValidValue : '', 1190 caretPos : 0, 1191 loading : 0, 1192 loadedSearches : {}, 1193 1194 $activeOption : null, 1195 $activeItems : [], 1196 1197 optgroups : {}, 1198 options : {}, 1199 userOptions : {}, 1200 items : [], 1201 renderCache : {}, 1202 onSearchChange : settings.loadThrottle === null ? self.onSearchChange : debounce(self.onSearchChange, settings.loadThrottle) 1203 }); 1204 1205 // search system 1206 self.sifter = new Sifter(this.options, {diacritics: settings.diacritics}); 1207 1208 // build options table 1209 if (self.settings.options) { 1210 for (i = 0, n = self.settings.options.length; i < n; i++) { 1211 self.registerOption(self.settings.options[i]); 1212 } 1213 delete self.settings.options; 1214 } 1215 1216 // build optgroup table 1217 if (self.settings.optgroups) { 1218 for (i = 0, n = self.settings.optgroups.length; i < n; i++) { 1219 self.registerOptionGroup(self.settings.optgroups[i]); 1220 } 1221 delete self.settings.optgroups; 1222 } 1223 1224 // option-dependent defaults 1225 self.settings.mode = self.settings.mode || (self.settings.maxItems === 1 ? 'single' : 'multi'); 1226 if (typeof self.settings.hideSelected !== 'boolean') { 1227 self.settings.hideSelected = self.settings.mode === 'multi'; 1228 } 1229 1230 self.initializePlugins(self.settings.plugins); 1231 self.setupCallbacks(); 1232 self.setupTemplates(); 1233 self.setup(); 1234 }; 1235 1236 // mixins 1237 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1238 1239 MicroEvent.mixin(Selectize); 1240 1241 if(typeof MicroPlugin !== "undefined"){ 1242 MicroPlugin.mixin(Selectize); 1243 }else{ 1244 logError("Dependency MicroPlugin is missing", 1245 {explanation: 1246 "Make sure you either: (1) are using the \"standalone\" "+ 1247 "version of Selectize, or (2) require MicroPlugin before you "+ 1248 "load Selectize."} 1249 ); 1250 } 1251 1252 1253 // methods 1254 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1255 1256 $.extend(Selectize.prototype, { 1257 1258 /** 1259 * Creates all elements and sets up event bindings. 1260 */ 1261 setup: function() { 1262 var self = this; 1263 var settings = self.settings; 1264 var eventNS = self.eventNS; 1265 var $window = $(window); 1266 var $document = $(document); 1267 var $input = self.$input; 1268 1269 var $wrapper; 1270 var $control; 1271 var $control_input; 1272 var $dropdown; 1273 var $dropdown_content; 1274 var $dropdown_parent; 1275 var inputMode; 1276 var timeout_blur; 1277 var timeout_focus; 1278 var classes; 1279 var classes_plugins; 1280 var inputId; 1281 1282 inputMode = self.settings.mode; 1283 classes = $input.attr('class') || ''; 1284 1285 $wrapper = $('<div>
vendor: 18,776 bytes, lines 1285-1952
1285').addClass(settings.wrapperClass).addClass(classes).addClass(inputMode); 1286 $control = $('<div>').addClass(settings.inputClass).addClass('items').appendTo($wrapper); 1287 $control_input = $('<input type="text" autocomplete="new-password" autofill="no" />').appendTo($control).attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex); 1288 $dropdown_parent = $(settings.dropdownParent || $wrapper); 1289 $dropdown = $('<div>').addClass(settings.dropdownClass).addClass(inputMode).hide().appendTo($dropdown_parent); 1290 $dropdown_content = $('<div>').addClass(settings.dropdownContentClass).attr('tabindex', '-1').appendTo($dropdown); 1291 1292 if(inputId = $input.attr('id')) { 1293 $control_input.attr('id', inputId + '-selectized'); 1294 $("label[for='"+inputId+"']").attr('for', inputId + '-selectized'); 1295 } 1296 1297 if(self.settings.copyClassesToDropdown) { 1298 $dropdown.addClass(classes); 1299 } 1300 1301 $wrapper.css({ 1302 width: $input[0].style.width 1303 }); 1304 1305 if (self.plugins.names.length) { 1306 classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-'); 1307 $wrapper.addClass(classes_plugins); 1308 $dropdown.addClass(classes_plugins); 1309 } 1310 1311 if ((settings.maxItems === null || settings.maxItems > 1) && self.tagType === TAG_SELECT) { 1312 $input.attr('multiple', 'multiple'); 1313 } 1314 1315 if (self.settings.placeholder) { 1316 $control_input.attr('placeholder', settings.placeholder); 1317 } 1318 1319 // if splitOn was not passed in, construct it from the delimiter to allow pasting universally 1320 if (!self.settings.splitOn && self.settings.delimiter) { 1321 var delimiterEscaped = self.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); 1322 self.settings.splitOn = new RegExp('\\s*' + delimiterEscaped + '+\\s*'); 1323 } 1324 1325 if ($input.attr('autocorrect')) { 1326 $control_input.attr('autocorrect', $input.attr('autocorrect')); 1327 } 1328 1329 if ($input.attr('autocapitalize')) { 1330 $control_input.attr('autocapitalize', $input.attr('autocapitalize')); 1331 } 1332 if ($input.is('input')) { 1333 $control_input[0].type = $input[0].type; 1334 } 1335 1336 self.$wrapper = $wrapper; 1337 self.$control = $control; 1338 self.$control_input = $control_input; 1339 self.$dropdown = $dropdown; 1340 self.$dropdown_content = $dropdown_content; 1341 1342 $dropdown.on('mouseenter mousedown click', '[data-disabled]>[data-selectable]', function(e) { e.stopImmediatePropagation(); }); 1343 $dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); }); 1344 $dropdown.on('mousedown click', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); }); 1345 watchChildEvent($control, 'mousedown', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); }); 1346 autoGrow($control_input); 1347 1348 $control.on({ 1349 mousedown : function() { return self.onMouseDown.apply(self, arguments); }, 1350 click : function() { return self.onClick.apply(self, arguments); } 1351 }); 1352 1353 $control_input.on({ 1354 mousedown : function(e) { e.stopPropagation(); }, 1355 keydown : function() { return self.onKeyDown.apply(self, arguments); }, 1356 keypress : function() { return self.onKeyPress.apply(self, arguments); }, 1357 input : function() { return self.onInput.apply(self, arguments); }, 1358 resize : function() { self.positionDropdown.apply(self, []); }, 1359 blur : function() { return self.onBlur.apply(self, arguments); }, 1360 focus : function() { self.ignoreBlur = false; return self.onFocus.apply(self, arguments); }, 1361 paste : function() { return self.onPaste.apply(self, arguments); } 1362 }); 1363 1364 $document.on('keydown' + eventNS, function(e) { 1365 self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey']; 1366 self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey']; 1367 self.isShiftDown = e.shiftKey; 1368 }); 1369 1370 $document.on('keyup' + eventNS, function(e) { 1371 if (e.keyCode === KEY_CTRL) self.isCtrlDown = false; 1372 if (e.keyCode === KEY_SHIFT) self.isShiftDown = false; 1373 if (e.keyCode === KEY_CMD) self.isCmdDown = false; 1374 }); 1375 1376 $document.on('mousedown' + eventNS, function(e) { 1377 if (self.isFocused) { 1378 // prevent events on the dropdown scrollbar from causing the control to blur 1379 if (e.target === self.$dropdown[0] || e.target.parentNode === self.$dropdown[0]) { 1380 return false; 1381 } 1382 // blur on click outside 1383 if (!self.$control.has(e.target).length && e.target !== self.$control[0]) { 1384 self.blur(e.target); 1385 } 1386 } 1387 }); 1388 1389 $window.on(['scroll' + eventNS, 'resize' + eventNS].join(' '), function() { 1390 if (self.isOpen) { 1391 self.positionDropdown.apply(self, arguments); 1392 } 1393 }); 1394 $window.on('mousemove' + eventNS, function() { 1395 self.ignoreHover = false; 1396 }); 1397 1398 // store original children and tab index so that they can be 1399 // restored when the destroy() method is called. 1400 this.revertSettings = { 1401 $children : $input.children().detach(), 1402 tabindex : $input.attr('tabindex') 1403 }; 1404 1405 $input.attr('tabindex', -1).hide().after(self.$wrapper); 1406 1407 if (Array.isArray(settings.items)) { 1408 self.lastValidValue = settings.items; 1409 self.setValue(settings.items); 1410 delete settings.items; 1411 } 1412 1413 // feature detect for the validation API 1414 if (SUPPORTS_VALIDITY_API) { 1415 $input.on('invalid' + eventNS, function(e) { 1416 e.preventDefault(); 1417 self.isInvalid = true; 1418 self.refreshState(); 1419 }); 1420 } 1421 1422 self.updateOriginalInput(); 1423 self.refreshItems(); 1424 self.refreshState(); 1425 self.updatePlaceholder(); 1426 self.isSetup = true; 1427 1428 if ($input.is(':disabled')) { 1429 self.disable(); 1430 } 1431 1432 self.on('change', this.onChange); 1433 1434 $input.data('selectize', self); 1435 $input.addClass('selectized'); 1436 self.trigger('initialize'); 1437 1438 // preload options 1439 if (settings.preload === true) { 1440 self.onSearchChange(''); 1441 } 1442 1443 }, 1444 1445 /** 1446 * Sets up default rendering functions. 1447 */ 1448 setupTemplates: function() { 1449 var self = this; 1450 var field_label = self.settings.labelField; 1451 var field_value = self.settings.valueField; 1452 var field_optgroup = self.settings.optgroupLabelField; 1453 1454 var templates = { 1455 'optgroup': function(data) { 1456 return '<div class="optgroup">' + data.html + '</div>'; 1457 }, 1458 'optgroup_header': function(data, escape) { 1459 return '<div class="optgroup-header">' + escape(data[field_optgroup]) + '</div>'; 1460 }, 1461 'option': function(data, escape) { 1462 return '<div class="option '+( data[field_value] === '' ? 'selectize-dropdown-emptyoptionlabel' : '')+'">' + escape(data[field_label]) + '</div>'; 1463 }, 1464 'item': function(data, escape) { 1465 return '<div class="item">' + escape(data[field_label]) + '</div>'; 1466 }, 1467 'option_create': function(data, escape) { 1468 return '<div class="create">Add <strong>' + escape(data.input) + '</strong>…</div>'; 1469 } 1470 }; 1471 1472 self.settings.render = $.extend({}, templates, self.settings.render); 1473 }, 1474 1475 /** 1476 * Maps fired events to callbacks provided 1477 * in the settings used when creating the control. 1478 */ 1479 setupCallbacks: function() { 1480 var key, fn, callbacks = { 1481 'initialize' : 'onInitialize', 1482 'change' : 'onChange', 1483 'item_add' : 'onItemAdd', 1484 'item_remove' : 'onItemRemove', 1485 'clear' : 'onClear', 1486 'option_add' : 'onOptionAdd', 1487 'option_remove' : 'onOptionRemove', 1488 'option_clear' : 'onOptionClear', 1489 'optgroup_add' : 'onOptionGroupAdd', 1490 'optgroup_remove' : 'onOptionGroupRemove', 1491 'optgroup_clear' : 'onOptionGroupClear', 1492 'dropdown_open' : 'onDropdownOpen', 1493 'dropdown_close' : 'onDropdownClose', 1494 'type' : 'onType', 1495 'load' : 'onLoad', 1496 'focus' : 'onFocus', 1497 'blur' : 'onBlur', 1498 'dropdown_item_activate' : 'onDropdownItemActivate', 1499 'dropdown_item_deactivate' : 'onDropdownItemDeactivate' 1500 }; 1501 1502 for (key in callbacks) { 1503 if (callbacks.hasOwnProperty(key)) { 1504 fn = this.settings[callbacks[key]]; 1505 if (fn) this.on(key, fn); 1506 } 1507 } 1508 }, 1509 1510 /** 1511 * Triggered when the main control element 1512 * has a click event. 1513 * 1514 * @param {object} e 1515 * @return {boolean} 1516 */ 1517 onClick: function(e) { 1518 var self = this; 1519 1520 // necessary for mobile webkit devices (manual focus triggering 1521 // is ignored unless invoked within a click event) 1522 // also necessary to reopen a dropdown that has been closed by 1523 // closeAfterSelect 1524 if (!self.isFocused || !self.isOpen) { 1525 self.focus(); 1526 e.preventDefault(); 1527 } 1528 }, 1529 1530 /** 1531 * Triggered when the main control element 1532 * has a mouse down event. 1533 * 1534 * @param {object} e 1535 * @return {boolean} 1536 */ 1537 onMouseDown: function(e) { 1538 var self = this; 1539 var defaultPrevented = e.isDefaultPrevented(); 1540 var $target = $(e.target); 1541 1542 if (self.isFocused) { 1543 // retain focus by preventing native handling. if the 1544 // event target is the input it should not be modified. 1545 // otherwise, text selection within the input won't work. 1546 if (e.target !== self.$control_input[0]) { 1547 if (self.settings.mode === 'single') { 1548 // toggle dropdown 1549 self.isOpen ? self.close() : self.open(); 1550 } else if (!defaultPrevented) { 1551 self.setActiveItem(null); 1552 } 1553 return false; 1554 } 1555 } else { 1556 // give control focus 1557 if (!defaultPrevented) { 1558 window.setTimeout(function() { 1559 self.focus(); 1560 }, 0); 1561 } 1562 } 1563 }, 1564 1565 /** 1566 * Triggered when the value of the control has been changed. 1567 * This should propagate the event to the original DOM 1568 * input / select element. 1569 */ 1570 onChange: function() { 1571 var self = this; 1572 if (self.getValue() !== "") { 1573 self.lastValidValue = self.getValue(); 1574 } 1575 this.$input.trigger('input'); 1576 this.$input.trigger('change'); 1577 }, 1578 1579 /** 1580 * Triggered on <input> paste. 1581 * 1582 * @param {object} e 1583 * @returns {boolean} 1584 */ 1585 onPaste: function(e) { 1586 var self = this; 1587 1588 if (self.isFull() || self.isInputHidden || self.isLocked) { 1589 e.preventDefault(); 1590 return; 1591 } 1592 1593 // If a regex or string is included, this will split the pasted 1594 // input and create Items for each separate value 1595 if (self.settings.splitOn) { 1596 1597 // Wait for pasted text to be recognized in value 1598 setTimeout(function() { 1599 var pastedText = self.$control_input.val(); 1600 if(!pastedText.match(self.settings.splitOn)){ return } 1601 1602 var splitInput = pastedText 1603 .trim() 1604 .split(self.settings.splitOn); 1605 for (var i = 0, n = splitInput.length; i < n; i++) { 1606 self.createItem(splitInput[i]); 1607 } 1608 }, 0); 1609 } 1610 }, 1611 1612 /** 1613 * Triggered on <input> keypress. 1614 * 1615 * @param {object} e 1616 * @returns {boolean} 1617 */ 1618 onKeyPress: function(e) { 1619 if (this.isLocked) return e && e.preventDefault(); 1620 var character = String.fromCharCode(e.keyCode || e.which); 1621 if (this.settings.create && this.settings.mode === 'multi' && character === this.settings.delimiter) { 1622 this.createItem(); 1623 e.preventDefault(); 1624 return false; 1625 } 1626 }, 1627 1628 /** 1629 * Triggered on <input> keydown. 1630 * 1631 * @param {object} e 1632 * @returns {boolean} 1633 */ 1634 onKeyDown: function(e) { 1635 var isInput = e.target === this.$control_input[0]; 1636 var self = this; 1637 1638 if (self.isLocked) { 1639 if (e.keyCode !== KEY_TAB) { 1640 e.preventDefault(); 1641 } 1642 return; 1643 } 1644 1645 switch (e.keyCode) { 1646 case KEY_A: 1647 if (self.isCmdDown) { 1648 self.selectAll(); 1649 return; 1650 } 1651 break; 1652 case KEY_ESC: 1653 if (self.isOpen) { 1654 e.preventDefault(); 1655 e.stopPropagation(); 1656 self.close(); 1657 } 1658 return; 1659 case KEY_N: 1660 if (!e.ctrlKey || e.altKey) break; 1661 case KEY_DOWN: 1662 if (!self.isOpen && self.hasOptions) { 1663 self.open(); 1664 } else if (self.$activeOption) { 1665 self.ignoreHover = true; 1666 var $next = self.getAdjacentOption(self.$activeOption, 1); 1667 if ($next.length) self.setActiveOption($next, true, true); 1668 } 1669 e.preventDefault(); 1670 return; 1671 case KEY_P: 1672 if (!e.ctrlKey || e.altKey) break; 1673 case KEY_UP: 1674 if (self.$activeOption) { 1675 self.ignoreHover = true; 1676 var $prev = self.getAdjacentOption(self.$activeOption, -1); 1677 if ($prev.length) self.setActiveOption($prev, true, true); 1678 } 1679 e.preventDefault(); 1680 return; 1681 case KEY_RETURN: 1682 if (self.isOpen && self.$activeOption) { 1683 self.onOptionSelect({currentTarget: self.$activeOption}); 1684 e.preventDefault(); 1685 } 1686 return; 1687 case KEY_LEFT: 1688 self.advanceSelection(-1, e); 1689 return; 1690 case KEY_RIGHT: 1691 self.advanceSelection(1, e); 1692 return; 1693 case KEY_TAB: 1694 if (self.settings.selectOnTab && self.isOpen && self.$activeOption) { 1695 self.onOptionSelect({currentTarget: self.$activeOption}); 1696 1697 // Default behaviour is to jump to the next field, we only want this 1698 // if the current field doesn't accept any more entries 1699 if (!self.isFull()) { 1700 e.preventDefault(); 1701 } 1702 } 1703 if (self.settings.create && self.createItem() && self.settings.showAddOptionOnCreate) { 1704 e.preventDefault(); 1705 } 1706 return; 1707 case KEY_BACKSPACE: 1708 case KEY_DELETE: 1709 self.deleteSelection(e); 1710 return; 1711 } 1712 1713 if ((self.isFull() || self.isInputHidden) && !(IS_MAC ? e.metaKey : e.ctrlKey)) { 1714 e.preventDefault(); 1715 return; 1716 } 1717 }, 1718 1719 /** 1720 * Triggered on <input> input. 1721 * 1722 * @param {object} e 1723 * @returns {boolean} 1724 */ 1725 onInput: function(e) { 1726 var self = this; 1727 1728 var value = self.$control_input.val() || ''; 1729 if (self.lastValue !== value) { 1730 self.lastValue = value; 1731 self.onSearchChange(value); 1732 self.refreshOptions(); 1733 self.trigger('type', value); 1734 } 1735 }, 1736 1737 /** 1738 * Invokes the user-provide option provider / loader. 1739 * 1740 * Note: this function is debounced in the Selectize 1741 * constructor (by `settings.loadThrottle` milliseconds) 1742 * 1743 * @param {string} value 1744 */ 1745 onSearchChange: function(value) { 1746 var self = this; 1747 var fn = self.settings.load; 1748 if (!fn) return; 1749 if (self.loadedSearches.hasOwnProperty(value)) return; 1750 self.loadedSearches[value] = true; 1751 self.load(function(callback) { 1752 fn.apply(self, [value, callback]); 1753 }); 1754 }, 1755 1756 /** 1757 * Triggered on <input> focus. 1758 * 1759 * @param {object} e (optional) 1760 * @returns {boolean} 1761 */ 1762 onFocus: function(e) { 1763 var self = this; 1764 var wasFocused = self.isFocused; 1765 1766 if (self.isDisabled) { 1767 self.blur(); 1768 e && e.preventDefault(); 1769 return false; 1770 } 1771 1772 if (self.ignoreFocus) return; 1773 self.isFocused = true; 1774 if (self.settings.preload === 'focus') self.onSearchChange(''); 1775 1776 if (!wasFocused) self.trigger('focus'); 1777 1778 if (!self.$activeItems.length) { 1779 self.showInput(); 1780 self.setActiveItem(null); 1781 self.refreshOptions(!!self.settings.openOnFocus); 1782 } 1783 1784 self.refreshState(); 1785 }, 1786 1787 /** 1788 * Triggered on <input> blur. 1789 * 1790 * @param {object} e 1791 * @param {Element} dest 1792 */ 1793 onBlur: function(e, dest) { 1794 var self = this; 1795 if (!self.isFocused) return; 1796 self.isFocused = false; 1797 1798 if (self.ignoreFocus) { 1799 return; 1800 } else if (!self.ignoreBlur && document.activeElement === self.$dropdown_content[0]) { 1801 // necessary to prevent IE closing the dropdown when the scrollbar is clicked 1802 self.ignoreBlur = true; 1803 self.onFocus(e); 1804 return; 1805 } 1806 1807 var deactivate = function() { 1808 self.close(); 1809 self.setTextboxValue(''); 1810 self.setActiveItem(null); 1811 self.setActiveOption(null); 1812 self.setCaret(self.items.length); 1813 self.refreshState(); 1814 1815 // IE11 bug: element still marked as active 1816 dest && dest.focus && dest.focus(); 1817 1818 self.isBlurring = false; 1819 self.ignoreFocus = false; 1820 self.trigger('blur'); 1821 }; 1822 1823 self.isBlurring = true; 1824 self.ignoreFocus = true; 1825 if (self.settings.create && self.settings.createOnBlur) { 1826 self.createItem(null, false, deactivate); 1827 } else { 1828 deactivate(); 1829 } 1830 }, 1831 1832 /** 1833 * Triggered when the user rolls over 1834 * an option in the autocomplete dropdown menu. 1835 * 1836 * @param {object} e 1837 * @returns {boolean} 1838 */ 1839 onOptionHover: function(e) { 1840 if (this.ignoreHover) return; 1841 this.setActiveOption(e.currentTarget, false); 1842 }, 1843 1844 /** 1845 * Triggered when the user clicks on an option 1846 * in the autocomplete dropdown menu. 1847 * 1848 * @param {object} e 1849 * @returns {boolean} 1850 */ 1851 onOptionSelect: function(e) { 1852 var value, $target, $option, self = this; 1853 1854 if (e.preventDefault) { 1855 e.preventDefault(); 1856 e.stopPropagation(); 1857 } 1858 1859 $target = $(e.currentTarget); 1860 if ($target.hasClass('create')) { 1861 self.createItem(null, function() { 1862 if (self.settings.closeAfterSelect) { 1863 self.close(); 1864 } 1865 }); 1866 } else { 1867 value = $target.attr('data-value'); 1868 if (typeof value !== 'undefined') { 1869 self.lastQuery = null; 1870 self.setTextboxValue(''); 1871 self.addItem(value); 1872 if (self.settings.closeAfterSelect) { 1873 self.close(); 1874 } else if (!self.settings.hideSelected && e.type && /mouse/.test(e.type)) { 1875 self.setActiveOption(self.getOption(value)); 1876 } 1877 } 1878 } 1879 }, 1880 1881 /** 1882 * Triggered when the user clicks on an item 1883 * that has been selected. 1884 * 1885 * @param {object} e 1886 * @returns {boolean} 1887 */ 1888 onItemSelect: function(e) { 1889 var self = this; 1890 1891 if (self.isLocked) return; 1892 if (self.settings.mode === 'multi') { 1893 e.preventDefault(); 1894 self.setActiveItem(e.currentTarget, e); 1895 } 1896 }, 1897 1898 /** 1899 * Invokes the provided method that provides 1900 * results to a callback---which are then added 1901 * as options to the control. 1902 * 1903 * @param {function} fn 1904 */ 1905 load: function(fn) { 1906 var self = this; 1907 var $wrapper = self.$wrapper.addClass(self.settings.loadingClass); 1908 1909 self.loading++; 1910 fn.apply(self, [function(results) { 1911 self.loading = Math.max(self.loading - 1, 0); 1912 if (results && results.length) { 1913 self.addOption(results); 1914 self.refreshOptions(self.isFocused && !self.isInputHidden); 1915 } 1916 if (!self.loading) { 1917 $wrapper.removeClass(self.settings.loadingClass); 1918 } 1919 self.trigger('load', results); 1920 }]); 1921 }, 1922 1923 /** 1924 * Gets the value of input field of the control. 1925 * 1926 * @returns {string} value 1927 */ 1928 getTextboxValue: function() { 1929 var $input = this.$control_input; 1930 return $input.val(); 1931 }, 1932 1933 /** 1934 * Sets the input field of the control to the specified value. 1935 * 1936 * @param {string} value 1937 */ 1938 setTextboxValue: function(value) { 1939 var $input = this.$control_input; 1940 var changed = $input.val() !== value; 1941 if (changed) { 1942 $input.val(value).triggerHandler('update'); 1943 this.lastValue = value; 1944 } 1945 }, 1946 1947 /** 1948 * Returns the value of the control. If multiple items 1949 * can be selected (e.g. <select multiple>), this returns 1950 * an array. If only one item can be selected, this 1951 * returns a string. 1952 *
vendor: 27,022 bytes, lines 1953-2940
1953 * @returns {mixed} 1954 */ 1955 getValue: function() { 1956 if (this.tagType === TAG_SELECT && this.$input.attr('multiple')) { 1957 return this.items; 1958 } else { 1959 return this.items.join(this.settings.delimiter); 1960 } 1961 }, 1962 1963 /** 1964 * Resets the selected items to the given value. 1965 * 1966 * @param {mixed} value 1967 */ 1968 setValue: function(value, silent) { 1969 var events = silent ? [] : ['change']; 1970 1971 debounce_events(this, events, function() { 1972 this.clear(silent); 1973 this.addItems(value, silent); 1974 }); 1975 }, 1976 1977 /** 1978 * Resets the number of max items to the given value 1979 * 1980 * @param {number} value 1981 */ 1982 setMaxItems: function(value){ 1983 if(value === 0) value = null; //reset to unlimited items. 1984 this.settings.maxItems = value; 1985 this.settings.mode = this.settings.mode || (this.settings.maxItems === 1 ? 'single' : 'multi'); 1986 this.refreshState(); 1987 }, 1988 1989 /** 1990 * Sets the selected item. 1991 * 1992 * @param {object} $item 1993 * @param {object} e (optional) 1994 */ 1995 setActiveItem: function($item, e) { 1996 var self = this; 1997 var eventName; 1998 var i, idx, begin, end, item, swap; 1999 var $last; 2000 2001 if (self.settings.mode === 'single') return; 2002 $item = $($item); 2003 2004 // clear the active selection 2005 if (!$item.length) { 2006 $(self.$activeItems).removeClass('active'); 2007 self.$activeItems = []; 2008 if (self.isFocused) { 2009 self.showInput(); 2010 } 2011 return; 2012 } 2013 2014 // modify selection 2015 eventName = e && e.type.toLowerCase(); 2016 2017 if (eventName === 'mousedown' && self.isShiftDown && self.$activeItems.length) { 2018 $last = self.$control.children('.active:last'); 2019 begin = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$last[0]]); 2020 end = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$item[0]]); 2021 if (begin > end) { 2022 swap = begin; 2023 begin = end; 2024 end = swap; 2025 } 2026 for (i = begin; i <= end; i++) { 2027 item = self.$control[0].childNodes[i]; 2028 if (self.$activeItems.indexOf(item) === -1) { 2029 $(item).addClass('active'); 2030 self.$activeItems.push(item); 2031 } 2032 } 2033 e.preventDefault(); 2034 } else if ((eventName === 'mousedown' && self.isCtrlDown) || (eventName === 'keydown' && this.isShiftDown)) { 2035 if ($item.hasClass('active')) { 2036 idx = self.$activeItems.indexOf($item[0]); 2037 self.$activeItems.splice(idx, 1); 2038 $item.removeClass('active'); 2039 } else { 2040 self.$activeItems.push($item.addClass('active')[0]); 2041 } 2042 } else { 2043 $(self.$activeItems).removeClass('active'); 2044 self.$activeItems = [$item.addClass('active')[0]]; 2045 } 2046 2047 // ensure control has focus 2048 self.hideInput(); 2049 if (!this.isFocused) { 2050 self.focus(); 2051 } 2052 }, 2053 2054 /** 2055 * Sets the selected item in the dropdown menu 2056 * of available options. 2057 * 2058 * @param {object} $object 2059 * @param {boolean} scroll 2060 * @param {boolean} animate 2061 */ 2062 setActiveOption: function($option, scroll, animate) { 2063 var height_menu, height_item, y; 2064 var scroll_top, scroll_bottom; 2065 var self = this; 2066 2067 if (self.$activeOption) { 2068 self.$activeOption.removeClass('active'); 2069 self.trigger('dropdown_item_deactivate', self.$activeOption.attr('data-value')); 2070 } 2071 self.$activeOption = null; 2072 2073 $option = $($option); 2074 if (!$option.length) return; 2075 2076 self.$activeOption = $option.addClass('active'); 2077 if (self.isOpen) self.trigger('dropdown_item_activate', self.$activeOption.attr('data-value')); 2078 2079 if (scroll || !isset(scroll)) { 2080 2081 height_menu = self.$dropdown_content.height(); 2082 height_item = self.$activeOption.outerHeight(true); 2083 scroll = self.$dropdown_content.scrollTop() || 0; 2084 y = self.$activeOption.offset().top - self.$dropdown_content.offset().top + scroll; 2085 scroll_top = y; 2086 scroll_bottom = y - height_menu + height_item; 2087 2088 if (y + height_item > height_menu + scroll) { 2089 self.$dropdown_content.stop().animate({scrollTop: scroll_bottom}, animate ? self.settings.scrollDuration : 0); 2090 } else if (y < scroll) { 2091 self.$dropdown_content.stop().animate({scrollTop: scroll_top}, animate ? self.settings.scrollDuration : 0); 2092 } 2093 2094 } 2095 }, 2096 2097 /** 2098 * Selects all items (CTRL + A). 2099 */ 2100 selectAll: function() { 2101 var self = this; 2102 if (self.settings.mode === 'single') return; 2103 2104 self.$activeItems = Array.prototype.slice.apply(self.$control.children(':not(input)').addClass('active')); 2105 if (self.$activeItems.length) { 2106 self.hideInput(); 2107 self.close(); 2108 } 2109 self.focus(); 2110 }, 2111 2112 /** 2113 * Hides the input element out of view, while 2114 * retaining its focus. 2115 */ 2116 hideInput: function() { 2117 var self = this; 2118 2119 self.setTextboxValue(''); 2120 self.$control_input.css({opacity: 0, position: 'absolute', left: self.rtl ? 10000 : -10000}); 2121 self.isInputHidden = true; 2122 }, 2123 2124 /** 2125 * Restores input visibility. 2126 */ 2127 showInput: function() { 2128 this.$control_input.css({opacity: 1, position: 'relative', left: 0}); 2129 this.isInputHidden = false; 2130 }, 2131 2132 /** 2133 * Gives the control focus. 2134 */ 2135 focus: function() { 2136 var self = this; 2137 if (self.isDisabled) return self; 2138 2139 self.ignoreFocus = true; 2140 self.$control_input[0].focus(); 2141 window.setTimeout(function() { 2142 self.ignoreFocus = false; 2143 self.onFocus(); 2144 }, 0); 2145 return self; 2146 }, 2147 2148 /** 2149 * Forces the control out of focus. 2150 * 2151 * @param {Element} dest 2152 */ 2153 blur: function(dest) { 2154 this.$control_input[0].blur(); 2155 this.onBlur(null, dest); 2156 return this; 2157 }, 2158 2159 /** 2160 * Returns a function that scores an object 2161 * to show how good of a match it is to the 2162 * provided query. 2163 * 2164 * @param {string} query 2165 * @param {object} options 2166 * @return {function} 2167 */ 2168 getScoreFunction: function(query) { 2169 return this.sifter.getScoreFunction(query, this.getSearchOptions()); 2170 }, 2171 2172 /** 2173 * Returns search options for sifter (the system 2174 * for scoring and sorting results). 2175 * 2176 * @see https://github.com/brianreavis/sifter.js 2177 * @return {object} 2178 */ 2179 getSearchOptions: function() { 2180 var settings = this.settings; 2181 var sort = settings.sortField; 2182 if (typeof sort === 'string') { 2183 sort = [{field: sort}]; 2184 } 2185 2186 return { 2187 fields : settings.searchField, 2188 conjunction : settings.searchConjunction, 2189 sort : sort, 2190 nesting : settings.nesting 2191 }; 2192 }, 2193 2194 /** 2195 * Searches through available options and returns 2196 * a sorted array of matches. 2197 * 2198 * Returns an object containing: 2199 * 2200 * - query {string} 2201 * - tokens {array} 2202 * - total {int} 2203 * - items {array} 2204 * 2205 * @param {string} query 2206 * @returns {object} 2207 */ 2208 search: function(query) { 2209 var i, value, score, result, calculateScore; 2210 var self = this; 2211 var settings = self.settings; 2212 var options = this.getSearchOptions(); 2213 2214 // validate user-provided result scoring function 2215 if (settings.score) { 2216 calculateScore = self.settings.score.apply(this, [query]); 2217 if (typeof calculateScore !== 'function') { 2218 throw new Error('Selectize "score" setting must be a function that returns a function'); 2219 } 2220 } 2221 2222 // perform search 2223 if (query !== self.lastQuery) { 2224 self.lastQuery = query; 2225 result = self.sifter.search(query, $.extend(options, {score: calculateScore})); 2226 self.currentResults = result; 2227 } else { 2228 result = $.extend(true, {}, self.currentResults); 2229 } 2230 2231 // filter out selected items 2232 if (settings.hideSelected) { 2233 for (i = result.items.length - 1; i >= 0; i--) { 2234 if (self.items.indexOf(hash_key(result.items[i].id)) !== -1) { 2235 result.items.splice(i, 1); 2236 } 2237 } 2238 } 2239 2240 return result; 2241 }, 2242 2243 /** 2244 * Refreshes the list of available options shown 2245 * in the autocomplete dropdown menu. 2246 * 2247 * @param {boolean} triggerDropdown 2248 */ 2249 refreshOptions: function(triggerDropdown) { 2250 var i, j, k, n, groups, groups_order, option, option_html, optgroup, optgroups, html, html_children, has_create_option; 2251 var $active, $active_before, $create; 2252 2253 if (typeof triggerDropdown === 'undefined') { 2254 triggerDropdown = true; 2255 } 2256 2257 var self = this; 2258 var query = (self.$control_input.val()).trim(); 2259 var results = self.search(query); 2260 var $dropdown_content = self.$dropdown_content; 2261 var active_before = self.$activeOption && hash_key(self.$activeOption.attr('data-value')); 2262 2263 // build markup 2264 n = results.items.length; 2265 if (typeof self.settings.maxOptions === 'number') { 2266 n = Math.min(n, self.settings.maxOptions); 2267 } 2268 2269 // render and group available options individually 2270 groups = {}; 2271 groups_order = []; 2272 2273 for (i = 0; i < n; i++) { 2274 option = self.options[results.items[i].id]; 2275 option_html = self.render('option', option); 2276 optgroup = option[self.settings.optgroupField] || ''; 2277 optgroups = Array.isArray(optgroup) ? optgroup : [optgroup]; 2278 2279 for (j = 0, k = optgroups && optgroups.length; j < k; j++) { 2280 optgroup = optgroups[j]; 2281 if (!self.optgroups.hasOwnProperty(optgroup)) { 2282 optgroup = ''; 2283 } 2284 if (!groups.hasOwnProperty(optgroup)) { 2285 groups[optgroup] = document.createDocumentFragment(); 2286 groups_order.push(optgroup); 2287 } 2288 groups[optgroup].appendChild(option_html); 2289 } 2290 } 2291 2292 // sort optgroups 2293 if (this.settings.lockOptgroupOrder) { 2294 groups_order.sort(function(a, b) { 2295 var a_order = self.optgroups[a] && self.optgroups[a].$order || 0; 2296 var b_order = self.optgroups[b] && self.optgroups[b].$order || 0; 2297 return a_order - b_order; 2298 }); 2299 } 2300 2301 // render optgroup headers & join groups 2302 html = document.createDocumentFragment(); 2303 for (i = 0, n = groups_order.length; i < n; i++) { 2304 optgroup = groups_order[i]; 2305 if (self.optgroups.hasOwnProperty(optgroup) && groups[optgroup].childNodes.length) { 2306 // render the optgroup header and options within it, 2307 // then pass it to the wrapper template 2308 html_children = document.createDocumentFragment(); 2309 html_children.appendChild(self.render('optgroup_header', self.optgroups[optgroup])); 2310 html_children.appendChild(groups[optgroup]); 2311 2312 html.appendChild(self.render('optgroup', $.extend({}, self.optgroups[optgroup], { 2313 html: domToString(html_children), 2314 dom: html_children 2315 }))); 2316 } else { 2317 html.appendChild(groups[optgroup]); 2318 } 2319 } 2320 2321 $dropdown_content.html(html); 2322 2323 // highlight matching terms inline 2324 if (self.settings.highlight) { 2325 $dropdown_content.removeHighlight(); 2326 if (results.query.length && results.tokens.length) { 2327 for (i = 0, n = results.tokens.length; i < n; i++) { 2328 highlight($dropdown_content, results.tokens[i].regex); 2329 } 2330 } 2331 } 2332 2333 // add "selected" class to selected options 2334 if (!self.settings.hideSelected) { 2335 // clear selection on all previously selected elements first 2336 self.$dropdown.find('.selected').removeClass('selected'); 2337 2338 for (i = 0, n = self.items.length; i < n; i++) { 2339 self.getOption(self.items[i]).addClass('selected'); 2340 } 2341 } 2342 2343 // add create option 2344 has_create_option = self.canCreate(query); 2345 if (has_create_option) { 2346 if(self.settings.showAddOptionOnCreate) { 2347 $dropdown_content.prepend(self.render('option_create', {input: query})); 2348 $create = $($dropdown_content[0].childNodes[0]); 2349 } 2350 } 2351 2352 // activate 2353 self.hasOptions = results.items.length > 0 || ( has_create_option && self.settings.showAddOptionOnCreate ); 2354 if (self.hasOptions) { 2355 if (results.items.length > 0) { 2356 $active_before = active_before && self.getOption(active_before); 2357 if (results.query !== "" && $active_before && $active_before.length) { 2358 $active = $active_before; 2359 } else if (self.settings.mode === 'single' && self.items.length) { 2360 $active = self.getOption(self.items[0]); 2361 } 2362 if (!$active || !$active.length) { 2363 if ($create && !self.settings.addPrecedence) { 2364 $active = self.getAdjacentOption($create, 1); 2365 } else { 2366 $active = $dropdown_content.find('[data-selectable]:first'); 2367 } 2368 } 2369 } else { 2370 $active = $create; 2371 } 2372 self.setActiveOption($active); 2373 if (triggerDropdown && !self.isOpen) { self.open(); } 2374 } else { 2375 self.setActiveOption(null); 2376 if (triggerDropdown && self.isOpen) { self.close(); } 2377 } 2378 }, 2379 2380 /** 2381 * Adds an available option. If it already exists, 2382 * nothing will happen. Note: this does not refresh 2383 * the options list dropdown (use `refreshOptions` 2384 * for that). 2385 * 2386 * Usage: 2387 * 2388 * this.addOption(data) 2389 * 2390 * @param {object|array} data 2391 */ 2392 addOption: function(data) { 2393 var i, n, value, self = this; 2394 2395 if (Array.isArray(data)) { 2396 for (i = 0, n = data.length; i < n; i++) { 2397 self.addOption(data[i]); 2398 } 2399 return; 2400 } 2401 2402 if (value = self.registerOption(data)) { 2403 self.userOptions[value] = true; 2404 self.lastQuery = null; 2405 self.trigger('option_add', value, data); 2406 } 2407 }, 2408 2409 /** 2410 * Registers an option to the pool of options. 2411 * 2412 * @param {object} data 2413 * @return {boolean|string} 2414 */ 2415 registerOption: function(data) { 2416 var key = hash_key(data[this.settings.valueField]); 2417 if (typeof key === 'undefined' || key === null || this.options.hasOwnProperty(key)) return false; 2418 data.$order = data.$order || ++this.order; 2419 this.options[key] = data; 2420 return key; 2421 }, 2422 2423 /** 2424 * Registers an option group to the pool of option groups. 2425 * 2426 * @param {object} data 2427 * @return {boolean|string} 2428 */ 2429 registerOptionGroup: function(data) { 2430 var key = hash_key(data[this.settings.optgroupValueField]); 2431 if (!key) return false; 2432 2433 data.$order = data.$order || ++this.order; 2434 this.optgroups[key] = data; 2435 return key; 2436 }, 2437 2438 /** 2439 * Registers a new optgroup for options 2440 * to be bucketed into. 2441 * 2442 * @param {string} id 2443 * @param {object} data 2444 */ 2445 addOptionGroup: function(id, data) { 2446 data[this.settings.optgroupValueField] = id; 2447 if (id = this.registerOptionGroup(data)) { 2448 this.trigger('optgroup_add', id, data); 2449 } 2450 }, 2451 2452 /** 2453 * Removes an existing option group. 2454 * 2455 * @param {string} id 2456 */ 2457 removeOptionGroup: function(id) { 2458 if (this.optgroups.hasOwnProperty(id)) { 2459 delete this.optgroups[id]; 2460 this.renderCache = {}; 2461 this.trigger('optgroup_remove', id); 2462 } 2463 }, 2464 2465 /** 2466 * Clears all existing option groups. 2467 */ 2468 clearOptionGroups: function() { 2469 this.optgroups = {}; 2470 this.renderCache = {}; 2471 this.trigger('optgroup_clear'); 2472 }, 2473 2474 /** 2475 * Updates an option available for selection. If 2476 * it is visible in the selected items or options 2477 * dropdown, it will be re-rendered automatically. 2478 * 2479 * @param {string} value 2480 * @param {object} data 2481 */ 2482 updateOption: function(value, data) { 2483 var self = this; 2484 var $item, $item_new; 2485 var value_new, index_item, cache_items, cache_options, order_old; 2486 2487 value = hash_key(value); 2488 value_new = hash_key(data[self.settings.valueField]); 2489 2490 // sanity checks 2491 if (value === null) return; 2492 if (!self.options.hasOwnProperty(value)) return; 2493 if (typeof value_new !== 'string') throw new Error('Value must be set in option data'); 2494 2495 order_old = self.options[value].$order; 2496 2497 // update references 2498 if (value_new !== value) { 2499 delete self.options[value]; 2500 index_item = self.items.indexOf(value); 2501 if (index_item !== -1) { 2502 self.items.splice(index_item, 1, value_new); 2503 } 2504 } 2505 data.$order = data.$order || order_old; 2506 self.options[value_new] = data; 2507 2508 // invalidate render cache 2509 cache_items = self.renderCache['item']; 2510 cache_options = self.renderCache['option']; 2511 2512 if (cache_items) { 2513 delete cache_items[value]; 2514 delete cache_items[value_new]; 2515 } 2516 if (cache_options) { 2517 delete cache_options[value]; 2518 delete cache_options[value_new]; 2519 } 2520 2521 // update the item if it's selected 2522 if (self.items.indexOf(value_new) !== -1) { 2523 $item = self.getItem(value); 2524 $item_new = $(self.render('item', data)); 2525 if ($item.hasClass('active')) $item_new.addClass('active'); 2526 $item.replaceWith($item_new); 2527 } 2528 2529 // invalidate last query because we might have updated the sortField 2530 self.lastQuery = null; 2531 2532 // update dropdown contents 2533 if (self.isOpen) { 2534 self.refreshOptions(false); 2535 } 2536 }, 2537 2538 /** 2539 * Removes a single option. 2540 * 2541 * @param {string} value 2542 * @param {boolean} silent 2543 */ 2544 removeOption: function(value, silent) { 2545 var self = this; 2546 value = hash_key(value); 2547 2548 var cache_items = self.renderCache['item']; 2549 var cache_options = self.renderCache['option']; 2550 if (cache_items) delete cache_items[value]; 2551 if (cache_options) delete cache_options[value]; 2552 2553 delete self.userOptions[value]; 2554 delete self.options[value]; 2555 self.lastQuery = null; 2556 self.trigger('option_remove', value); 2557 self.removeItem(value, silent); 2558 }, 2559 2560 /** 2561 * Clears all options. 2562 * 2563 * @param {boolean} silent 2564 */ 2565 clearOptions: function(silent) { 2566 var self = this; 2567 2568 self.loadedSearches = {}; 2569 self.userOptions = {}; 2570 self.renderCache = {}; 2571 var options = self.options; 2572 $.each(self.options, function(key, value) { 2573 if(self.items.indexOf(key) == -1) { 2574 delete options[key]; 2575 } 2576 }); 2577 self.options = self.sifter.items = options; 2578 self.lastQuery = null; 2579 self.trigger('option_clear'); 2580 self.clear(silent); 2581 }, 2582 2583 /** 2584 * Returns the jQuery element of the option 2585 * matching the given value. 2586 * 2587 * @param {string} value 2588 * @returns {object} 2589 */ 2590 getOption: function(value) { 2591 return this.getElementWithValue(value, this.$dropdown_content.find('[data-selectable]')); 2592 }, 2593 2594 /** 2595 * Returns the jQuery element of the first 2596 * selectable option. 2597 * 2598 * @return {object} 2599 */ 2600 getFirstOption: function() { 2601 var $options = this.$dropdown.find('[data-selectable]'); 2602 return $options.length > 0 ? $options.eq(0) : $(); 2603 }, 2604 2605 /** 2606 * Returns the jQuery element of the next or 2607 * previous selectable option. 2608 * 2609 * @param {object} $option 2610 * @param {int} direction can be 1 for next or -1 for previous 2611 * @return {object} 2612 */ 2613 getAdjacentOption: function($option, direction) { 2614 var $options = this.$dropdown.find('[data-selectable]'); 2615 var index = $options.index($option) + direction; 2616 2617 return index >= 0 && index < $options.length ? $options.eq(index) : $(); 2618 }, 2619 2620 /** 2621 * Finds the first element with a "data-value" attribute 2622 * that matches the given value. 2623 * 2624 * @param {mixed} value 2625 * @param {object} $els 2626 * @return {object} 2627 */ 2628 getElementWithValue: function(value, $els) { 2629 value = hash_key(value); 2630 2631 if (typeof value !== 'undefined' && value !== null) { 2632 for (var i = 0, n = $els.length; i < n; i++) { 2633 if ($els[i].getAttribute('data-value') === value) { 2634 return $($els[i]); 2635 } 2636 } 2637 } 2638 2639 return $(); 2640 }, 2641 2642 /** 2643 * Finds the first element with a "textContent" property 2644 * that matches the given textContent value. 2645 * 2646 * @param {mixed} textContent 2647 * @param {boolean} ignoreCase 2648 * @param {object} $els 2649 * @return {object} 2650 */ 2651 getElementWithTextContent: function(textContent, ignoreCase ,$els) { 2652 textContent = hash_key(textContent); 2653 2654 if (typeof textContent !== 'undefined' && textContent !== null) { 2655 for (var i = 0, n = $els.length; i < n; i++) { 2656 var eleTextContent = $els[i].textContent 2657 if (ignoreCase == true) { 2658 eleTextContent = (eleTextContent !== null) ? eleTextContent.toLowerCase() : null; 2659 textContent = textContent.toLowerCase(); 2660 } 2661 if (eleTextContent === textContent) { 2662 return $($els[i]); 2663 } 2664 } 2665 } 2666 2667 return $(); 2668 }, 2669 2670 /** 2671 * Returns the jQuery element of the item 2672 * matching the given value. 2673 * 2674 * @param {string} value 2675 * @returns {object} 2676 */ 2677 getItem: function(value) { 2678 return this.getElementWithValue(value, this.$control.children()); 2679 }, 2680 2681 /** 2682 * Returns the jQuery element of the item 2683 * matching the given textContent. 2684 * 2685 * @param {string} value 2686 * @param {boolean} ignoreCase 2687 * @returns {object} 2688 */ 2689 getFirstItemMatchedByTextContent: function(textContent, ignoreCase) { 2690 ignoreCase = (ignoreCase !== null && ignoreCase === true) ? true : false; 2691 return this.getElementWithTextContent(textContent, ignoreCase, this.$dropdown_content.find('[data-selectable]')); 2692 }, 2693 2694 /** 2695 * "Selects" multiple items at once. Adds them to the list 2696 * at the current caret position. 2697 * 2698 * @param {string} value 2699 * @param {boolean} silent 2700 */ 2701 addItems: function(values, silent) { 2702 this.buffer = document.createDocumentFragment(); 2703 2704 var childNodes = this.$control[0].childNodes; 2705 for (var i = 0; i < childNodes.length; i++) { 2706 this.buffer.appendChild(childNodes[i]); 2707 } 2708 2709 var items = Array.isArray(values) ? values : [values]; 2710 for (var i = 0, n = items.length; i < n; i++) { 2711 this.isPending = (i < n - 1); 2712 this.addItem(items[i], silent); 2713 } 2714 2715 var control = this.$control[0]; 2716 control.insertBefore(this.buffer, control.firstChild); 2717 2718 this.buffer = null; 2719 }, 2720 2721 /** 2722 * "Selects" an item. Adds it to the list 2723 * at the current caret position. 2724 * 2725 * @param {string} value 2726 * @param {boolean} silent 2727 */ 2728 addItem: function(value, silent) { 2729 var events = silent ? [] : ['change']; 2730 2731 debounce_events(this, events, function() { 2732 var $item, $option, $options; 2733 var self = this; 2734 var inputMode = self.settings.mode; 2735 var i, active, value_next, wasFull; 2736 value = hash_key(value); 2737 2738 if (self.items.indexOf(value) !== -1) { 2739 if (inputMode === 'single') self.close(); 2740 return; 2741 } 2742 2743 if (!self.options.hasOwnProperty(value)) return; 2744 if (inputMode === 'single') self.clear(silent); 2745 if (inputMode === 'multi' && self.isFull()) return; 2746 2747 $item = $(self.render('item', self.options[value])); 2748 wasFull = self.isFull(); 2749 self.items.splice(self.caretPos, 0, value); 2750 self.insertAtCaret($item); 2751 if (!self.isPending || (!wasFull && self.isFull())) { 2752 self.refreshState(); 2753 } 2754 2755 if (self.isSetup) { 2756 $options = self.$dropdown_content.find('[data-selectable]'); 2757 2758 // update menu / remove the option (if this is not one item being added as part of series) 2759 if (!self.isPending) { 2760 $option = self.getOption(value); 2761 value_next = self.getAdjacentOption($option, 1).attr('data-value'); 2762 self.refreshOptions(self.isFocused && inputMode !== 'single'); 2763 if (value_next) { 2764 self.setActiveOption(self.getOption(value_next)); 2765 } 2766 } 2767 2768 // hide the menu if the maximum number of items have been selected or no options are left 2769 if (!$options.length || self.isFull()) { 2770 self.close(); 2771 } else if (!self.isPending) { 2772 self.positionDropdown(); 2773 } 2774 2775 self.updatePlaceholder(); 2776 self.trigger('item_add', value, $item); 2777 2778 if (!self.isPending) { 2779 self.updateOriginalInput({silent: silent}); 2780 } 2781 } 2782 }); 2783 }, 2784 2785 /** 2786 * Removes the selected item matching 2787 * the provided value. 2788 * 2789 * @param {string} value 2790 */ 2791 removeItem: function(value, silent) { 2792 var self = this; 2793 var $item, i, idx; 2794 2795 $item = (value instanceof $) ? value : self.getItem(value); 2796 value = hash_key($item.attr('data-value')); 2797 i = self.items.indexOf(value); 2798 2799 if (i !== -1) { 2800 self.trigger('item_before_remove', value, $item); 2801 $item.remove(); 2802 if ($item.hasClass('active')) { 2803 idx = self.$activeItems.indexOf($item[0]); 2804 self.$activeItems.splice(idx, 1); 2805 } 2806 2807 self.items.splice(i, 1); 2808 self.lastQuery = null; 2809 if (!self.settings.persist && self.userOptions.hasOwnProperty(value)) { 2810 self.removeOption(value, silent); 2811 } 2812 2813 if (i < self.caretPos) { 2814 self.setCaret(self.caretPos - 1); 2815 } 2816 2817 self.refreshState(); 2818 self.updatePlaceholder(); 2819 self.updateOriginalInput({silent: silent}); 2820 self.positionDropdown(); 2821 self.trigger('item_remove', value, $item); 2822 } 2823 }, 2824 2825 /** 2826 * Invokes the `create` method provided in the 2827 * selectize options that should provide the data 2828 * for the new item, given the user input. 2829 * 2830 * Once this completes, it will be added 2831 * to the item list. 2832 * 2833 * @param {string} value 2834 * @param {boolean} [triggerDropdown] 2835 * @param {function} [callback] 2836 * @return {boolean} 2837 */ 2838 createItem: function(input, triggerDropdown) { 2839 var self = this; 2840 var caret = self.caretPos; 2841 input = input || (self.$control_input.val() || '').trim(); 2842 2843 var callback = arguments[arguments.length - 1]; 2844 if (typeof callback !== 'function') callback = function() {}; 2845 2846 if (typeof triggerDropdown !== 'boolean') { 2847 triggerDropdown = true; 2848 } 2849 2850 if (!self.canCreate(input)) { 2851 callback(); 2852 return false; 2853 } 2854 2855 self.lock(); 2856 2857 var setup = (typeof self.settings.create === 'function') ? this.settings.create : function(input) { 2858 var data = {}; 2859 data[self.settings.labelField] = input; 2860 var key = input; 2861 if ( self.settings.formatValueToKey && typeof self.settings.formatValueToKey === 'function' ) { 2862 key = self.settings.formatValueToKey.apply(this, [key]); 2863 if (key === null || typeof key === 'undefined' || typeof key === 'object' || typeof key === 'function') { 2864 throw new Error('Selectize "formatValueToKey" setting must be a function that returns a value other than object or function.'); 2865 } 2866 } 2867 data[self.settings.valueField] = key; 2868 return data; 2869 }; 2870 2871 var create = once(function(data) { 2872 self.unlock(); 2873 2874 if (!data || typeof data !== 'object') return callback(); 2875 var value = hash_key(data[self.settings.valueField]); 2876 if (typeof value !== 'string') return callback(); 2877 2878 self.setTextboxValue(''); 2879 self.addOption(data); 2880 self.setCaret(caret); 2881 self.addItem(value); 2882 self.refreshOptions(triggerDropdown && self.settings.mode !== 'single'); 2883 callback(data); 2884 }); 2885 2886 var output = setup.apply(this, [input, create]); 2887 if (typeof output !== 'undefined') { 2888 create(output); 2889 } 2890 2891 return true; 2892 }, 2893 2894 /** 2895 * Re-renders the selected item lists. 2896 */ 2897 refreshItems: function() { 2898 this.lastQuery = null; 2899 2900 if (this.isSetup) { 2901 this.addItem(this.items); 2902 } 2903 2904 this.refreshState(); 2905 this.updateOriginalInput(); 2906 }, 2907 2908 /** 2909 * Updates all state-dependent attributes 2910 * and CSS classes. 2911 */ 2912 refreshState: function() { 2913 this.refreshValidityState(); 2914 this.refreshClasses(); 2915 }, 2916 2917 /** 2918 * Update the `required` attribute of both input and control input. 2919 * 2920 * The `required` property needs to be activated on the control input 2921 * for the error to be displayed at the right place. `required` also 2922 * needs to be temporarily deactivated on the input since the input is 2923 * hidden and can't show errors. 2924 */ 2925 refreshValidityState: function() { 2926 if (!this.isRequired) return false; 2927 2928 var invalid = !this.items.length; 2929 2930 this.isInvalid = invalid; 2931 this.$control_input.prop('required', invalid); 2932 this.$input.prop('required', !invalid); 2933 }, 2934 2935 /** 2936 * Updates all state-dependent CSS classes. 2937 */ 2938 refreshClasses: function() { 2939 var self = this; 2940 var isFull = self.isFull();
vendor: 18,191 bytes, lines 2941-3611
2941 var isLocked = self.isLocked; 2942 2943 self.$wrapper 2944 .toggleClass('rtl', self.rtl); 2945 2946 self.$control 2947 .toggleClass('focus', self.isFocused) 2948 .toggleClass('disabled', self.isDisabled) 2949 .toggleClass('required', self.isRequired) 2950 .toggleClass('invalid', self.isInvalid) 2951 .toggleClass('locked', isLocked) 2952 .toggleClass('full', isFull).toggleClass('not-full', !isFull) 2953 .toggleClass('input-active', self.isFocused && !self.isInputHidden) 2954 .toggleClass('dropdown-active', self.isOpen) 2955 .toggleClass('has-options', !$.isEmptyObject(self.options)) 2956 .toggleClass('has-items', self.items.length > 0); 2957 2958 self.$control_input.data('grow', !isFull && !isLocked); 2959 }, 2960 2961 /** 2962 * Determines whether or not more items can be added 2963 * to the control without exceeding the user-defined maximum. 2964 * 2965 * @returns {boolean} 2966 */ 2967 isFull: function() { 2968 return this.settings.maxItems !== null && this.items.length >= this.settings.maxItems; 2969 }, 2970 2971 /** 2972 * Refreshes the original <select> or <input> 2973 * element to reflect the current state. 2974 */ 2975 updateOriginalInput: function(opts) { 2976 var i, n, options, label, self = this; 2977 opts = opts || {}; 2978 2979 if (self.tagType === TAG_SELECT) { 2980 options = []; 2981 for (i = 0, n = self.items.length; i < n; i++) { 2982 label = self.options[self.items[i]][self.settings.labelField] || ''; 2983 options.push('<option value="' + escape_html(self.items[i]) + '" selected="selected">' + escape_html(label) + '</option>'); 2984 } 2985 if (!options.length && !this.$input.attr('multiple')) { 2986 options.push('<option value="" selected="selected"></option>'); 2987 } 2988 self.$input.html(options.join('')); 2989 } else { 2990 self.$input.val(self.getValue()); 2991 self.$input.attr('value',self.$input.val()); 2992 } 2993 2994 if (self.isSetup) { 2995 if (!opts.silent) { 2996 self.trigger('change', self.$input.val()); 2997 } 2998 } 2999 }, 3000 3001 /** 3002 * Shows/hide the input placeholder depending 3003 * on if there items in the list already. 3004 */ 3005 updatePlaceholder: function() { 3006 if (!this.settings.placeholder) return; 3007 var $input = this.$control_input; 3008 3009 if (this.items.length) { 3010 $input.removeAttr('placeholder'); 3011 } else { 3012 $input.attr('placeholder', this.settings.placeholder); 3013 } 3014 $input.triggerHandler('update', {force: true}); 3015 }, 3016 3017 /** 3018 * Shows the autocomplete dropdown containing 3019 * the available options. 3020 */ 3021 open: function() { 3022 var self = this; 3023 3024 if (self.isLocked || self.isOpen || (self.settings.mode === 'multi' && self.isFull())) return; 3025 self.focus(); 3026 self.isOpen = true; 3027 self.refreshState(); 3028 self.$dropdown.css({visibility: 'hidden', display: 'block'}); 3029 self.positionDropdown(); 3030 self.$dropdown.css({visibility: 'visible'}); 3031 self.trigger('dropdown_open', self.$dropdown); 3032 }, 3033 3034 /** 3035 * Closes the autocomplete dropdown menu. 3036 */ 3037 close: function() { 3038 var self = this; 3039 var trigger = self.isOpen; 3040 3041 if (self.settings.mode === 'single' && self.items.length) { 3042 self.hideInput(); 3043 3044 // Do not trigger blur while inside a blur event, 3045 // this fixes some weird tabbing behavior in FF and IE. 3046 // See #1164 3047 if (self.isBlurring) { 3048 self.$control_input.blur(); // close keyboard on iOS 3049 } 3050 } 3051 3052 self.isOpen = false; 3053 self.$dropdown.hide(); 3054 self.setActiveOption(null); 3055 self.refreshState(); 3056 3057 if (trigger) self.trigger('dropdown_close', self.$dropdown); 3058 }, 3059 3060 /** 3061 * Calculates and applies the appropriate 3062 * position of the dropdown. 3063 */ 3064 positionDropdown: function() { 3065 var $control = this.$control; 3066 var offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position(); 3067 offset.top += $control.outerHeight(true); 3068 3069 this.$dropdown.css({ 3070 width : $control[0].getBoundingClientRect().width, 3071 top : offset.top, 3072 left : offset.left 3073 }); 3074 }, 3075 3076 /** 3077 * Resets / clears all selected items 3078 * from the control. 3079 * 3080 * @param {boolean} silent 3081 */ 3082 clear: function(silent) { 3083 var self = this; 3084 3085 if (!self.items.length) return; 3086 self.$control.children(':not(input)').remove(); 3087 self.items = []; 3088 self.lastQuery = null; 3089 self.setCaret(0); 3090 self.setActiveItem(null); 3091 self.updatePlaceholder(); 3092 self.updateOriginalInput({silent: silent}); 3093 self.refreshState(); 3094 self.showInput(); 3095 self.trigger('clear'); 3096 }, 3097 3098 /** 3099 * A helper method for inserting an element 3100 * at the current caret position. 3101 * 3102 * @param {object} $el 3103 */ 3104 insertAtCaret: function($el) { 3105 var caret = Math.min(this.caretPos, this.items.length); 3106 var el = $el[0]; 3107 var target = this.buffer || this.$control[0]; 3108 3109 if (caret === 0) { 3110 target.insertBefore(el, target.firstChild); 3111 } else { 3112 target.insertBefore(el, target.childNodes[caret]); 3113 } 3114 3115 this.setCaret(caret + 1); 3116 }, 3117 3118 /** 3119 * Removes the current selected item(s). 3120 * 3121 * @param {object} e (optional) 3122 * @returns {boolean} 3123 */ 3124 deleteSelection: function(e) { 3125 var i, n, direction, selection, values, caret, option_select, $option_select, $tail; 3126 var self = this; 3127 3128 direction = (e && e.keyCode === KEY_BACKSPACE) ? -1 : 1; 3129 selection = getSelection(self.$control_input[0]); 3130 3131 if (self.$activeOption && !self.settings.hideSelected) { 3132 if (typeof self.settings.deselectBehavior === 'string' && self.settings.deselectBehavior === 'top') { 3133 option_select = self.getFirstOption().attr('data-value'); 3134 } else { 3135 option_select = self.getAdjacentOption(self.$activeOption, -1).attr('data-value'); 3136 } 3137 } 3138 3139 // determine items that will be removed 3140 values = []; 3141 3142 if (self.$activeItems.length) { 3143 $tail = self.$control.children('.active:' + (direction > 0 ? 'last' : 'first')); 3144 caret = self.$control.children(':not(input)').index($tail); 3145 if (direction > 0) { caret++; } 3146 3147 for (i = 0, n = self.$activeItems.length; i < n; i++) { 3148 values.push($(self.$activeItems[i]).attr('data-value')); 3149 } 3150 if (e) { 3151 e.preventDefault(); 3152 e.stopPropagation(); 3153 } 3154 } else if ((self.isFocused || self.settings.mode === 'single') && self.items.length) { 3155 if (direction < 0 && selection.start === 0 && selection.length === 0) { 3156 values.push(self.items[self.caretPos - 1]); 3157 } else if (direction > 0 && selection.start === self.$control_input.val().length) { 3158 values.push(self.items[self.caretPos]); 3159 } 3160 } 3161 3162 // allow the callback to abort 3163 if (!values.length || (typeof self.settings.onDelete === 'function' && self.settings.onDelete.apply(self, [values]) === false)) { 3164 return false; 3165 } 3166 3167 // perform removal 3168 if (typeof caret !== 'undefined') { 3169 self.setCaret(caret); 3170 } 3171 while (values.length) { 3172 self.removeItem(values.pop()); 3173 } 3174 3175 self.showInput(); 3176 self.positionDropdown(); 3177 self.refreshOptions(true); 3178 3179 // select previous option 3180 if (option_select) { 3181 $option_select = self.getOption(option_select); 3182 if ($option_select.length) { 3183 self.setActiveOption($option_select); 3184 } 3185 } 3186 3187 return true; 3188 }, 3189 3190 /** 3191 * Selects the previous / next item (depending 3192 * on the `direction` argument). 3193 * 3194 * > 0 - right 3195 * < 0 - left 3196 * 3197 * @param {int} direction 3198 * @param {object} e (optional) 3199 */ 3200 advanceSelection: function(direction, e) { 3201 var tail, selection, idx, valueLength, cursorAtEdge, $tail; 3202 var self = this; 3203 3204 if (direction === 0) return; 3205 if (self.rtl) direction *= -1; 3206 3207 tail = direction > 0 ? 'last' : 'first'; 3208 selection = getSelection(self.$control_input[0]); 3209 3210 if (self.isFocused && !self.isInputHidden) { 3211 valueLength = self.$control_input.val().length; 3212 cursorAtEdge = direction < 0 3213 ? selection.start === 0 && selection.length === 0 3214 : selection.start === valueLength; 3215 3216 if (cursorAtEdge && !valueLength) { 3217 self.advanceCaret(direction, e); 3218 } 3219 } else { 3220 $tail = self.$control.children('.active:' + tail); 3221 if ($tail.length) { 3222 idx = self.$control.children(':not(input)').index($tail); 3223 self.setActiveItem(null); 3224 self.setCaret(direction > 0 ? idx + 1 : idx); 3225 } 3226 } 3227 }, 3228 3229 /** 3230 * Moves the caret left / right. 3231 * 3232 * @param {int} direction 3233 * @param {object} e (optional) 3234 */ 3235 advanceCaret: function(direction, e) { 3236 var self = this, fn, $adj; 3237 3238 if (direction === 0) return; 3239 3240 fn = direction > 0 ? 'next' : 'prev'; 3241 if (self.isShiftDown) { 3242 $adj = self.$control_input[fn](); 3243 if ($adj.length) { 3244 self.hideInput(); 3245 self.setActiveItem($adj); 3246 e && e.preventDefault(); 3247 } 3248 } else { 3249 self.setCaret(self.caretPos + direction); 3250 } 3251 }, 3252 3253 /** 3254 * Moves the caret to the specified index. 3255 * 3256 * @param {int} i 3257 */ 3258 setCaret: function(i) { 3259 var self = this; 3260 3261 if (self.settings.mode === 'single') { 3262 i = self.items.length; 3263 } else { 3264 i = Math.max(0, Math.min(self.items.length, i)); 3265 } 3266 3267 if(!self.isPending) { 3268 // the input must be moved by leaving it in place and moving the 3269 // siblings, due to the fact that focus cannot be restored once lost 3270 // on mobile webkit devices 3271 var j, n, fn, $children, $child; 3272 $children = self.$control.children(':not(input)'); 3273 for (j = 0, n = $children.length; j < n; j++) { 3274 $child = $($children[j]).detach(); 3275 if (j < i) { 3276 self.$control_input.before($child); 3277 } else { 3278 self.$control.append($child); 3279 } 3280 } 3281 } 3282 3283 self.caretPos = i; 3284 }, 3285 3286 /** 3287 * Disables user input on the control. Used while 3288 * items are being asynchronously created. 3289 */ 3290 lock: function() { 3291 this.close(); 3292 this.isLocked = true; 3293 this.refreshState(); 3294 }, 3295 3296 /** 3297 * Re-enables user input on the control. 3298 */ 3299 unlock: function() { 3300 this.isLocked = false; 3301 this.refreshState(); 3302 }, 3303 3304 /** 3305 * Disables user input on the control completely. 3306 * While disabled, it cannot receive focus. 3307 */ 3308 disable: function() { 3309 var self = this; 3310 self.$input.prop('disabled', true); 3311 self.$control_input.prop('disabled', true).prop('tabindex', -1); 3312 self.isDisabled = true; 3313 self.lock(); 3314 }, 3315 3316 /** 3317 * Enables the control so that it can respond 3318 * to focus and user input. 3319 */ 3320 enable: function() { 3321 var self = this; 3322 self.$input.prop('disabled', false); 3323 self.$control_input.prop('disabled', false).prop('tabindex', self.tabIndex); 3324 self.isDisabled = false; 3325 self.unlock(); 3326 }, 3327 3328 /** 3329 * Completely destroys the control and 3330 * unbinds all event listeners so that it can 3331 * be garbage collected. 3332 */ 3333 destroy: function() { 3334 var self = this; 3335 var eventNS = self.eventNS; 3336 var revertSettings = self.revertSettings; 3337 3338 self.trigger('destroy'); 3339 self.off(); 3340 self.$wrapper.remove(); 3341 self.$dropdown.remove(); 3342 3343 self.$input 3344 .html('') 3345 .append(revertSettings.$children) 3346 .removeAttr('tabindex') 3347 .removeClass('selectized') 3348 .attr({tabindex: revertSettings.tabindex}) 3349 .show(); 3350 3351 self.$control_input.removeData('grow'); 3352 self.$input.removeData('selectize'); 3353 3354 if (--Selectize.count == 0 && Selectize.$testInput) { 3355 Selectize.$testInput.remove(); 3356 Selectize.$testInput = undefined; 3357 } 3358 3359 $(window).off(eventNS); 3360 $(document).off(eventNS); 3361 $(document.body).off(eventNS); 3362 3363 delete self.$input[0].selectize; 3364 }, 3365 3366 /** 3367 * A helper method for rendering "item" and 3368 * "option" templates, given the data. 3369 * 3370 * @param {string} templateName 3371 * @param {object} data 3372 * @returns {string} 3373 */ 3374 render: function(templateName, data) { 3375 var value, id, label; 3376 var html = ''; 3377 var cache = false; 3378 var self = this; 3379 var regex_tag = /^[\t \r\n]*<([a-z][a-z0-9\-_]*(?:\:[a-z][a-z0-9\-_]*)?)/i; 3380 3381 if (templateName === 'option' || templateName === 'item') { 3382 value = hash_key(data[self.settings.valueField]); 3383 cache = !!value; 3384 } 3385 3386 // pull markup from cache if it exists 3387 if (cache) { 3388 if (!isset(self.renderCache[templateName])) { 3389 self.renderCache[templateName] = {}; 3390 } 3391 if (self.renderCache[templateName].hasOwnProperty(value)) { 3392 return self.renderCache[templateName][value]; 3393 } 3394 } 3395 3396 // render markup 3397 html = $(self.settings.render[templateName].apply(this, [data, escape_html])); 3398 3399 // add mandatory attributes 3400 if (templateName === 'option' || templateName === 'option_create') { 3401 if (!data[self.settings.disabledField]) { 3402 html.attr('data-selectable', ''); 3403 } 3404 } 3405 else if (templateName === 'optgroup') { 3406 id = data[self.settings.optgroupValueField] || ''; 3407 html.attr('data-group', id); 3408 if(data[self.settings.disabledField]) { 3409 html.attr('data-disabled', ''); 3410 } 3411 } 3412 if (templateName === 'option' || templateName === 'item') { 3413 html.attr('data-value', value || ''); 3414 } 3415 3416 // update cache 3417 if (cache) { 3418 self.renderCache[templateName][value] = html[0]; 3419 } 3420 3421 return html[0]; 3422 }, 3423 3424 /** 3425 * Clears the render cache for a template. If 3426 * no template is given, clears all render 3427 * caches. 3428 * 3429 * @param {string} templateName 3430 */ 3431 clearCache: function(templateName) { 3432 var self = this; 3433 if (typeof templateName === 'undefined') { 3434 self.renderCache = {}; 3435 } else { 3436 delete self.renderCache[templateName]; 3437 } 3438 }, 3439 3440 /** 3441 * Determines whether or not to display the 3442 * create item prompt, given a user input. 3443 * 3444 * @param {string} input 3445 * @return {boolean} 3446 */ 3447 canCreate: function(input) { 3448 var self = this; 3449 if (!self.settings.create) return false; 3450 var filter = self.settings.createFilter; 3451 return input.length 3452 && (typeof filter !== 'function' || filter.apply(self, [input])) 3453 && (typeof filter !== 'string' || new RegExp(filter).test(input)) 3454 && (!(filter instanceof RegExp) || filter.test(input)); 3455 } 3456 3457 }); 3458 3459 3460 Selectize.count = 0; 3461 Selectize.defaults = { 3462 options: [], 3463 optgroups: [], 3464 3465 plugins: [], 3466 delimiter: ',', 3467 splitOn: null, // regexp or string for splitting up values from a paste command 3468 persist: true, 3469 diacritics: true, 3470 create: false, 3471 showAddOptionOnCreate: true, 3472 createOnBlur: false, 3473 createFilter: null, 3474 highlight: true, 3475 openOnFocus: true, 3476 maxOptions: 1000, 3477 maxItems: null, 3478 hideSelected: null, 3479 addPrecedence: false, 3480 selectOnTab: true, 3481 preload: false, 3482 allowEmptyOption: false, 3483 showEmptyOptionInDropdown: false, 3484 emptyOptionLabel: '--', 3485 closeAfterSelect: false, 3486 3487 scrollDuration: 60, 3488 deselectBehavior: 'previous', //top, previous 3489 loadThrottle: 300, 3490 loadingClass: 'loading', 3491 3492 dataAttr: 'data-data', 3493 optgroupField: 'optgroup', 3494 valueField: 'value', 3495 labelField: 'text', 3496 disabledField: 'disabled', 3497 optgroupLabelField: 'label', 3498 optgroupValueField: 'value', 3499 lockOptgroupOrder: false, 3500 3501 sortField: '$order', 3502 searchField: ['text'], 3503 searchConjunction: 'and', 3504 3505 mode: null, 3506 wrapperClass: 'selectize-control', 3507 inputClass: 'selectize-input', 3508 dropdownClass: 'selectize-dropdown', 3509 dropdownContentClass: 'selectize-dropdown-content', 3510 3511 dropdownParent: null, 3512 3513 copyClassesToDropdown: true, 3514 3515 /* 3516 load : null, // function(query, callback) { ... } 3517 score : null, // function(search) { ... } 3518 formatValueToKey : null, // function(key) { ... } 3519 onInitialize : null, // function() { ... } 3520 onChange : null, // function(value) { ... } 3521 onItemAdd : null, // function(value, $item) { ... } 3522 onItemRemove : null, // function(value) { ... } 3523 onClear : null, // function() { ... } 3524 onOptionAdd : null, // function(value, data) { ... } 3525 onOptionRemove : null, // function(value) { ... } 3526 onOptionClear : null, // function() { ... } 3527 onOptionGroupAdd : null, // function(id, data) { ... } 3528 onOptionGroupRemove : null, // function(id) { ... } 3529 onOptionGroupClear : null, // function() { ... } 3530 onDropdownOpen : null, // function($dropdown) { ... } 3531 onDropdownClose : null, // function($dropdown) { ... } 3532 onType : null, // function(str) { ... } 3533 onDelete : null, // function(values) { ... } 3534 */ 3535 3536 render: { 3537 /* 3538 item: null, 3539 optgroup: null, 3540 optgroup_header: null, 3541 option: null, 3542 option_create: null 3543 */ 3544 } 3545 }; 3546 3547 3548 $.fn.selectize = function(settings_user) { 3549 var defaults = $.fn.selectize.defaults; 3550 var settings = $.extend({}, defaults, settings_user); 3551 var attr_data = settings.dataAttr; 3552 var field_label = settings.labelField; 3553 var field_value = settings.valueField; 3554 var field_disabled = settings.disabledField; 3555 var field_optgroup = settings.optgroupField; 3556 var field_optgroup_label = settings.optgroupLabelField; 3557 var field_optgroup_value = settings.optgroupValueField; 3558 3559 /** 3560 * Initializes selectize from a <input type="text"> element. 3561 * 3562 * @param {object} $input 3563 * @param {object} settings_element 3564 */ 3565 var init_textbox = function($input, settings_element) { 3566 var i, n, values, option; 3567 3568 var data_raw = $input.attr(attr_data); 3569 3570 if (!data_raw) { 3571 var value = $.trim($input.val() || ''); 3572 if (!settings.allowEmptyOption && !value.length) return; 3573 values = value.split(settings.delimiter); 3574 for (i = 0, n = values.length; i < n; i++) { 3575 option = {}; 3576 option[field_label] = values[i]; 3577 option[field_value] = values[i]; 3578 settings_element.options.push(option); 3579 } 3580 settings_element.items = values; 3581 } else { 3582 settings_element.options = JSON.parse(data_raw); 3583 for (i = 0, n = settings_element.options.length; i < n; i++) { 3584 settings_element.items.push(settings_element.options[i][field_value]); 3585 } 3586 } 3587 }; 3588 3589 /** 3590 * Initializes selectize from a <select> element. 3591 * 3592 * @param {object} $input 3593 * @param {object} settings_element 3594 */ 3595 var init_select = function($input, settings_element) { 3596 var i, n, tagName, $children, order = 0; 3597 var options = settings_element.options; 3598 var optionsMap = {}; 3599 3600 var readData = function($el) { 3601 var data = attr_data && $el.attr(attr_data); 3602 if (typeof data === 'string' && data.length) { 3603 return JSON.parse(data); 3604 } 3605 return null; 3606 }; 3607 3608 var addOption = function($option, group) { 3609 $option = $($option); 3610 3611 var value = hash_key($option.val());
vendor: 6,028 bytes, lines 3612-3814
3612 if (!value && !settings.allowEmptyOption) return; 3613 3614 // if the option already exists, it's probably been 3615 // duplicated in another optgroup. in this case, push 3616 // the current group to the "optgroup" property on the 3617 // existing option so that it's rendered in both places. 3618 if (optionsMap.hasOwnProperty(value)) { 3619 if (group) { 3620 var arr = optionsMap[value][field_optgroup]; 3621 if (!arr) { 3622 optionsMap[value][field_optgroup] = group; 3623 } else if (!$.isArray(arr)) { 3624 optionsMap[value][field_optgroup] = [arr, group]; 3625 } else { 3626 arr.push(group); 3627 } 3628 } 3629 return; 3630 } 3631 3632 var option = readData($option) || {}; 3633 option[field_label] = option[field_label] || $option.text(); 3634 option[field_value] = option[field_value] || value; 3635 option[field_disabled] = option[field_disabled] || $option.prop('disabled'); 3636 option[field_optgroup] = option[field_optgroup] || group; 3637 3638 optionsMap[value] = option; 3639 options.push(option); 3640 3641 if ($option.is(':selected')) { 3642 settings_element.items.push(value); 3643 } 3644 }; 3645 3646 var addGroup = function($optgroup) { 3647 var i, n, id, optgroup, $options; 3648 3649 $optgroup = $($optgroup); 3650 id = $optgroup.attr('label'); 3651 3652 if (id) { 3653 optgroup = readData($optgroup) || {}; 3654 optgroup[field_optgroup_label] = id; 3655 optgroup[field_optgroup_value] = id; 3656 optgroup[field_disabled] = $optgroup.prop('disabled'); 3657 settings_element.optgroups.push(optgroup); 3658 } 3659 3660 $options = $('option', $optgroup); 3661 for (i = 0, n = $options.length; i < n; i++) { 3662 addOption($options[i], id); 3663 } 3664 }; 3665 3666 settings_element.maxItems = $input.attr('multiple') ? null : 1; 3667 3668 $children = $input.children(); 3669 for (i = 0, n = $children.length; i < n; i++) { 3670 tagName = $children[i].tagName.toLowerCase(); 3671 if (tagName === 'optgroup') { 3672 addGroup($children[i]); 3673 } else if (tagName === 'option') { 3674 addOption($children[i]); 3675 } 3676 } 3677 }; 3678 3679 return this.each(function() { 3680 if (this.selectize) return; 3681 3682 var instance; 3683 var $input = $(this); 3684 var tag_name = this.tagName.toLowerCase(); 3685 var placeholder = $input.attr('placeholder') || $input.attr('data-placeholder'); 3686 if (!placeholder && !settings.allowEmptyOption) { 3687 placeholder = $input.children('option[value=""]').text(); 3688 } 3689 if (settings.allowEmptyOption && settings.showEmptyOptionInDropdown && !$input.children('option[value=""]').length) { 3690 var input_html = $input.html(); 3691 $input.html('<option value="">'+settings.emptyOptionLabel+'</option>'+input_html); 3692 } 3693 3694 var settings_element = { 3695 'placeholder' : placeholder, 3696 'options' : [], 3697 'optgroups' : [], 3698 'items' : [] 3699 }; 3700 3701 if (tag_name === 'select') { 3702 init_select($input, settings_element); 3703 } else { 3704 init_textbox($input, settings_element); 3705 } 3706 3707 instance = new Selectize($input, $.extend(true, {}, defaults, settings_element, settings_user)); 3708 instance.settings_user = settings_user; 3709 }); 3710 }; 3711 3712 $.fn.selectize.defaults = Selectize.defaults; 3713 $.fn.selectize.support = { 3714 validity: SUPPORTS_VALIDITY_API 3715 }; 3716 3717 3718 Selectize.define("auto_position", function () { 3719 var self = this; 3720 3721 const POSITION = { 3722 top: 'top', 3723 bottom: 'bottom', 3724 }; 3725 3726 self.positionDropdown = (function() { 3727 return function() { 3728 const $control = this.$control; 3729 const offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position(); 3730 offset.top += $control.outerHeight(true); 3731 3732 const dropdownHeight = this.$dropdown.prop('scrollHeight') + 5; // 5 - padding value; 3733 const controlPosTop = this.$control.get(0).getBoundingClientRect().top; 3734 const wrapperHeight = this.$wrapper.height(); 3735 const position = controlPosTop + dropdownHeight + wrapperHeight > window.innerHeight ? POSITION.top : POSITION.bottom; 3736 const styles = { 3737 width: $control.outerWidth(), 3738 left: offset.left 3739 }; 3740 3741 if (position === POSITION.top) { 3742 Object.assign(styles, {bottom: offset.top, top: 'unset', margin: '0 0 5px 0'}); 3743 this.$dropdown.addClass('selectize-position-top'); 3744 } else { 3745 Object.assign(styles, {top: offset.top, bottom: 'unset', margin: '5px 0 0 0'}); 3746 this.$dropdown.removeClass('selectize-position-top'); 3747 } 3748 3749 this.$dropdown.css(styles); 3750 } 3751 }()); 3752 }); 3753 3754 3755 Selectize.define('auto_select_on_type', function(options) { 3756 var self = this; 3757 3758 self.onBlur = (function() { 3759 var originalBlur = self.onBlur; 3760 return function(e) { 3761 var $matchedItem = self.getFirstItemMatchedByTextContent(self.lastValue, true); 3762 if (typeof $matchedItem.attr('data-value') !== 'undefined' && self.getValue() !== $matchedItem.attr('data-value')) 3763 { 3764 self.setValue($matchedItem.attr('data-value')); 3765 } 3766 return originalBlur.apply(this, arguments); 3767 } 3768 }()); 3769 }); 3770 3771 3772 Selectize.define("autofill_disable", function (options) { 3773 var self = this; 3774 3775 self.setup = (function () { 3776 var original = self.setup; 3777 return function () { 3778 original.apply(self, arguments); 3779 3780 // https://stackoverflow.com/questions/30053167/autocomplete-off-vs-false 3781 self.$control_input.attr({ autocomplete: "new-password", autofill: "no" }); 3782 }; 3783 })(); 3784 }); 3785 3786 3787 Selectize.define('drag_drop', function(options) { 3788 if (!$.fn.sortable) throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".'); 3789 if (this.settings.mode !== 'multi') return; 3790 var self = this; 3791 3792 self.lock = (function() { 3793 var original = self.lock; 3794 return function() { 3795 var sortable = self.$control.data('sortable'); 3796 if (sortable) sortable.disable(); 3797 return original.apply(self, arguments); 3798 }; 3799 })(); 3800 3801 self.unlock = (function() { 3802 var original = self.unlock; 3803 return function() { 3804 var sortable = self.$control.data('sortable'); 3805 if (sortable) sortable.enable(); 3806 return original.apply(self, arguments); 3807 }; 3808 })(); 3809 3810 self.setup = (function() { 3811 var original = self.setup; 3812 return function() { 3813 original.apply(this, arguments); 3814
vendor: 7,364 bytes, lines 3815-4068
3815 var $control = self.$control.sortable({ 3816 items: '[data-value]', 3817 forcePlaceholderSize: true, 3818 disabled: self.isLocked, 3819 start: function(e, ui) { 3820 ui.placeholder.css('width', ui.helper.css('width')); 3821 // $control.css({overflow: 'visible'}); 3822 $control.addClass('dragging'); 3823 }, 3824 stop: function() { 3825 // $control.css({overflow: 'hidden'}); 3826 $control.removeClass('dragging'); 3827 var active = self.$activeItems ? self.$activeItems.slice() : null; 3828 var values = []; 3829 $control.children('[data-value]').each(function() { 3830 values.push($(this).attr('data-value')); 3831 }); 3832 self.isFocused = false; 3833 self.setValue(values); 3834 self.isFocused = true; 3835 self.setActiveItem(active); 3836 self.positionDropdown(); 3837 } 3838 }); 3839 }; 3840 })(); 3841 3842 }); 3843 3844 3845 Selectize.define('dropdown_header', function(options) { 3846 var self = this; 3847 3848 options = $.extend({ 3849 title : 'Untitled', 3850 headerClass : 'selectize-dropdown-header', 3851 titleRowClass : 'selectize-dropdown-header-title', 3852 labelClass : 'selectize-dropdown-header-label', 3853 closeClass : 'selectize-dropdown-header-close', 3854 3855 html: function(data) { 3856 return ( 3857 '<div class="' + data.headerClass + '">' + 3858 '<div class="' + data.titleRowClass + '">' + 3859 '<span class="' + data.labelClass + '">' + data.title + '</span>' + 3860 '<a href="javascript:void(0)" class="' + data.closeClass + '">×</a>' + 3861 '</div>' + 3862 '</div>' 3863 ); 3864 } 3865 }, options); 3866 3867 self.setup = (function() { 3868 var original = self.setup; 3869 return function() { 3870 original.apply(self, arguments); 3871 self.$dropdown_header = $(options.html(options)); 3872 self.$dropdown.prepend(self.$dropdown_header); 3873 }; 3874 })(); 3875 3876 }); 3877 3878 3879 Selectize.define('optgroup_columns', function(options) { 3880 var self = this; 3881 3882 options = $.extend({ 3883 equalizeWidth : true, 3884 equalizeHeight : true 3885 }, options); 3886 3887 this.getAdjacentOption = function($option, direction) { 3888 var $options = $option.closest('[data-group]').find('[data-selectable]'); 3889 var index = $options.index($option) + direction; 3890 3891 return index >= 0 && index < $options.length ? $options.eq(index) : $(); 3892 }; 3893 3894 this.onKeyDown = (function() { 3895 var original = self.onKeyDown; 3896 return function(e) { 3897 var index, $option, $options, $optgroup; 3898 3899 if (this.isOpen && (e.keyCode === KEY_LEFT || e.keyCode === KEY_RIGHT)) { 3900 self.ignoreHover = true; 3901 $optgroup = this.$activeOption.closest('[data-group]'); 3902 index = $optgroup.find('[data-selectable]').index(this.$activeOption); 3903 3904 if(e.keyCode === KEY_LEFT) { 3905 $optgroup = $optgroup.prev('[data-group]'); 3906 } else { 3907 $optgroup = $optgroup.next('[data-group]'); 3908 } 3909 3910 $options = $optgroup.find('[data-selectable]'); 3911 $option = $options.eq(Math.min($options.length - 1, index)); 3912 if ($option.length) { 3913 this.setActiveOption($option); 3914 } 3915 return; 3916 } 3917 3918 return original.apply(this, arguments); 3919 }; 3920 })(); 3921 3922 var getScrollbarWidth = function() { 3923 var div; 3924 var width = getScrollbarWidth.width; 3925 var doc = document; 3926 3927 if (typeof width === 'undefined') { 3928 div = doc.createElement('div'); 3929 div.innerHTML = '<div style="width:50px;height:50px;position:absolute;left:-50px;top:-50px;overflow:auto;"><div style="width:1px;height:100px;"></div></div>'; 3930 div = div.firstChild; 3931 doc.body.appendChild(div); 3932 width = getScrollbarWidth.width = div.offsetWidth - div.clientWidth; 3933 doc.body.removeChild(div); 3934 } 3935 return width; 3936 }; 3937 3938 var equalizeSizes = function() { 3939 var i, n, height_max, width, width_last, width_parent, $optgroups; 3940 3941 $optgroups = $('[data-group]', self.$dropdown_content); 3942 n = $optgroups.length; 3943 if (!n || !self.$dropdown_content.width()) return; 3944 3945 if (options.equalizeHeight) { 3946 height_max = 0; 3947 for (i = 0; i < n; i++) { 3948 height_max = Math.max(height_max, $optgroups.eq(i).height()); 3949 } 3950 $optgroups.css({height: height_max}); 3951 } 3952 3953 if (options.equalizeWidth) { 3954 width_parent = self.$dropdown_content.innerWidth() - getScrollbarWidth(); 3955 width = Math.round(width_parent / n); 3956 $optgroups.css({width: width}); 3957 if (n > 1) { 3958 width_last = width_parent - width * (n - 1); 3959 $optgroups.eq(n - 1).css({width: width_last}); 3960 } 3961 } 3962 }; 3963 3964 if (options.equalizeHeight || options.equalizeWidth) { 3965 hook.after(this, 'positionDropdown', equalizeSizes); 3966 hook.after(this, 'refreshOptions', equalizeSizes); 3967 } 3968 3969 3970 }); 3971 3972 3973 Selectize.define('remove_button', function(options) { 3974 options = $.extend({ 3975 label : '×', 3976 title : 'Remove', 3977 className : 'remove', 3978 append : true 3979 }, options); 3980 3981 var singleClose = function(thisRef, options) { 3982 3983 options.className = 'remove-single'; 3984 3985 var self = thisRef; 3986 var html = '<a href="javascript:void(0)" class="' + options.className + '" tabindex="-1" title="' + escape_html(options.title) + '">' + options.label + '</a>'; 3987 3988 /** 3989 * Appends an element as a child (with raw HTML). 3990 * 3991 * @param {string} html_container 3992 * @param {string} html_element 3993 * @return {string} 3994 */ 3995 var append = function(html_container, html_element) { 3996 return $('<span>').append(html_container) 3997 .append(html_element); 3998 }; 3999 4000 thisRef.setup = (function() { 4001 var original = self.setup; 4002 return function() { 4003 // override the item rendering method to add the button to each 4004 if (options.append) { 4005 var id = $(self.$input.context).attr('id'); 4006 var selectizer = $('#'+id); 4007 4008 var render_item = self.settings.render.item; 4009 self.settings.render.item = function(data) { 4010 return append(render_item.apply(thisRef, arguments), html); 4011 }; 4012 } 4013 4014 original.apply(thisRef, arguments); 4015 4016 // add event listener 4017 thisRef.$control.on('click', '.' + options.className, function(e) { 4018 e.preventDefault(); 4019 if (self.isLocked) return; 4020 4021 self.clear(); 4022 }); 4023 4024 }; 4025 })(); 4026 }; 4027 4028 var multiClose = function(thisRef, options) { 4029 4030 var self = thisRef; 4031 var html = '<a href="javascript:void(0)" class="' + options.className + '" tabindex="-1" title="' + escape_html(options.title) + '">' + options.label + '</a>'; 4032 4033 /** 4034 * Appends an element as a child (with raw HTML). 4035 * 4036 * @param {string} html_container 4037 * @param {string} html_element 4038 * @return {string} 4039 */ 4040 var append = function(html_container, html_element) { 4041 var pos = html_container.search(/(<\/[^>]+>\s*)$/); 4042 return html_container.substring(0, pos) + html_element + html_container.substring(pos); 4043 }; 4044 4045 thisRef.setup = (function() { 4046 var original = self.setup; 4047 return function() { 4048 // override the item rendering method to add the button to each 4049 if (options.append) { 4050 var render_item = self.settings.render.item; 4051 self.settings.render.item = function(data) { 4052 return append(render_item.apply(thisRef, arguments), html); 4053 }; 4054 } 4055 4056 original.apply(thisRef, arguments); 4057 4058 // add event listener 4059 thisRef.$control.on('click', '.' + options.className, function(e) { 4060 e.preventDefault(); 4061 if (self.isLocked) return; 4062 4063 var $item = $(e.currentTarget).parent(); 4064 self.setActiveItem($item); 4065 if (self.deleteSelection()) { 4066 self.setCaret(self.items.length); 4067 } 4068 return false;
vendor: 2,990 bytes, lines 4069-4200
4069 }); 4070 4071 }; 4072 })(); 4073 }; 4074 4075 if (this.settings.mode === 'single') { 4076 singleClose(this, options); 4077 return; 4078 } else { 4079 multiClose(this, options); 4080 } 4081 }); 4082 4083 4084 Selectize.define('restore_on_backspace', function(options) { 4085 var self = this; 4086 4087 options.text = options.text || function(option) { 4088 return option[this.settings.labelField]; 4089 }; 4090 4091 this.onKeyDown = (function() { 4092 var original = self.onKeyDown; 4093 return function(e) { 4094 var index, option; 4095 if (e.keyCode === KEY_BACKSPACE && this.$control_input.val() === '' && !this.$activeItems.length) { 4096 index = this.caretPos - 1; 4097 if (index >= 0 && index < this.items.length) { 4098 option = this.options[this.items[index]]; 4099 if (this.deleteSelection(e)) { 4100 this.setTextboxValue(options.text.apply(this, [option])); 4101 this.refreshOptions(true); 4102 } 4103 e.preventDefault(); 4104 return; 4105 } 4106 } 4107 return original.apply(this, arguments); 4108 }; 4109 })(); 4110 }); 4111 4112 4113 Selectize.define('select_on_focus', function(options) { 4114 var self = this; 4115 4116 self.on('focus', function() { 4117 var originalFocus = self.onFocus; 4118 return function(e) { 4119 var value = self.getItem(self.getValue()).text(); 4120 self.clear(); 4121 self.setTextboxValue(value); 4122 self.$control_input.select(); 4123 setTimeout( function () { 4124 if (self.settings.selectOnTab) { 4125 self.setActiveOption(self.getFirstItemMatchedByTextContent(value)); 4126 } 4127 self.settings.score = null; 4128 },0); 4129 return originalFocus.apply(this, arguments); 4130 }; 4131 }()); 4132 4133 self.onBlur = (function() { 4134 var originalBlur = self.onBlur; 4135 return function(e) { 4136 if (self.getValue() === "" && self.lastValidValue !== self.getValue()) { 4137 self.setValue(self.lastValidValue); 4138 } 4139 setTimeout( function () { 4140 self.settings.score = function() { 4141 return function() { 4142 return 1; 4143 }; 4144 }; 4145 }, 0 ); 4146 return originalBlur.apply(this, arguments); 4147 } 4148 }()); 4149 self.settings.score = function() { 4150 return function() { return 1; }; 4151 }; 4152 4153 }); 4154 4155 4156 Selectize.define('tag_limit', function (options) { 4157 const self = this 4158 options.tagLimit = options.tagLimit 4159 this.onBlur = (function (e) { 4160 const original = self.onBlur 4161 4162 return function (e) { 4163 original.apply(this, e); 4164 if (!e) 4165 return 4166 const $control = this.$control 4167 const $items = $control.find('.item') 4168 const limit = options.tagLimit 4169 if (limit === undefined || $items.length <= limit) 4170 return 4171 4172 $items.toArray().forEach(function(item, index) { 4173 if (index < limit) 4174 return 4175 $(item).hide() 4176 }); 4177 4178 $control.append('<span><b>' + ($items.length - limit) + '</b></span>') 4179 }; 4180 })() 4181 4182 this.onFocus = (function (e) { 4183 const original = self.onFocus 4184 4185 return function (e) { 4186 original.apply(this, e); 4187 if (!e) 4188 return 4189 const $control = this.$control 4190 const $items = $control.find('.item') 4191 $items.show() 4192 $control.find('span').remove() 4193 4194 }; 4195 })() 4196 }); 4197 4198 4199 return Selectize; 4200}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.