1/* 2 | tail.select - The vanilla solution to make your HTML select fields AWESOME! 3 | @file ./js/tail.select.js 4 | @author wolffe <[email protected]> 5 | @author SamBrishes <[email protected]> 6 | @version 0.5.20 7 | 8 | @website https://github.com/wolffe/tail.select.js 9 | @license X11 / MIT License 10 | @copyright Copyright © 2020 - 2021 wolffe <[email protected]> 11 | @copyright Copyright © 2014 - 2019 SamBrishes, pytesNET <[email protected]> 12 */ 13;(function(root, factory){ 14 if(typeof define === "function" && define.amd){ 15 define(function(){ return factory(root); }); 16 } else if(typeof module === "object" && module.exports){ 17 module.exports = factory(root); 18 } else { 19 if(typeof root.tail === "undefined"){ 20 root.tail = {}; 21 } 22 root.tail.select = factory(root); 23 24 // jQuery Support 25 if(typeof jQuery !== "undefined"){ 26 jQuery.fn.tailselect = function(o){ 27 var r = [], i; 28 this.each(function(){ if((i = tail.select(this, o)) !== false){ r.push(i); } }); 29 return (r.length === 1)? r[0]: (r.length === 0)? false: r; 30 }; 31 } 32 33 // MooTools Support 34 if(typeof(MooTools) != "undefined"){ 35 Element.implement({ tailselect: function(o){ return new tail.select(this, o); } }); 36 } 37 } 38}(window, function(root){ 39 "use strict"; 40 var w = root, d = root.document; 41 42 // Internal Helper Methods 43 function cHAS(el, name){ 44 return (el && "classList" in el)? el.classList.contains(name): false; 45 } 46 function cADD(el, name){ 47 return (el && "classList" in el)? el.classList.add(name): undefined; 48 } 49 function cREM(el, name){ 50 return (el && "classList" in el)? el.classList.remove(name): undefined; 51 } 52 function trigger(el, event, opt){ 53 if(CustomEvent && CustomEvent.name){ 54 var ev = new CustomEvent(event, opt); 55 } else { 56 var ev = d.createEvent("CustomEvent"); 57 ev.initCustomEvent(event, !!opt.bubbles, !!opt.cancelable, opt.detail); 58 } 59 return el.dispatchEvent(ev); 60 } 61 function clone(obj, rep){ 62 if(typeof Object.assign === "function"){ 63 return Object.assign({}, obj, rep || {}); 64 } 65 var clone = Object.constructor(); 66 for(var key in obj){ 67 clone[key] = (key in rep)? rep[key]: obj[key]; 68 } 69 return clone; 70 } 71 function create(tag, classes){ 72 var r = d.createElement(tag); 73 r.className = (classes && classes.join)? classes.join(" "): classes || ""; 74 return r; 75 } 76 77 /* 78 | SELECT CONSTRUCTOR 79 | @since 0.5.12 [0.3.0] 80 */ 81 var select = function(el, config){ 82 el = (typeof(el) == "string")? d.querySelectorAll(el): el; 83 if(el instanceof NodeList || el instanceof HTMLCollection || el instanceof Array){ 84 for(var _r = [], l = el.length, i = 0; i < l; i++){ 85 _r.push(new select(el[i], clone(config, {}))); 86 } 87 return (_r.length === 1)? _r[0]: ((_r.length === 0)? false: _r); 88 } 89 if(!(el instanceof Element) || !(this instanceof select)){ 90 return !(el instanceof Element)? false: new select(el, config); 91 } 92 93 // Check Element 94 if(select.inst[el.getAttribute("data-tail-select")]){ 95 return select.inst[el.getAttribute("data-tail-select")]; 96 } 97 if(el.getAttribute("data-select")){ 98 var test = JSON.parse(el.getAttribute("data-select").replace(/\'/g, '"')); 99 if(test instanceof Object){ 100 config = clone(config, test); // This is a unofficial function ;3 101 } 102 } 103 104 // Get Element Options 105 var placeholder = el.getAttribute("placeholder") || el.getAttribute("data-placeholder"), 106 fb1 = "bindSourceSelect", fb2 = "sourceHide"; // Fallbacks 107 config = (typeof(config) == "object")? config: {}; 108 config.multiple = ("multiple" in config)? config.multiple: el.multiple; 109 config.disabled = ("disabled" in config)? config.disabled: el.disabled; 110 config.placeholder = placeholder || config.placeholder || null; 111 config.width = (config.width === "auto")? el.offsetWidth + 50: config.width; 112 config.sourceBind = (fb1 in config)? config[fb1]: config.sourceBind || false; 113 config.sourceHide = (fb2 in config)? config[fb2]: config.sourceHide || true; 114 config.multiLimit = (config.multiLimit >= 0)? config.multiLimit: Infinity; 115 116 // Init Instance 117 this.e = el; 118 this.id = ++select.count; 119 this.con = clone(select.defaults, config); 120 this.events = {}; 121 select.inst["tail-" + this.id] = this; 122 123 var ret = this.init().bind(); 124 125 return ret; 126 }, options; 127 select.count = 0; 128 select.inst = {}; 129 130 /* 131 | STORAGE :: DEFAULT OPTIONS 132 */ 133 select.defaults = { 134 animate: true, // [0.3.0] Boolean 135 classNames: null, // [0.2.0] Boolean, String, Array, null 136 csvOutput: false, // [0.3.4] Boolean 137 csvSeparator: ",", // [0.3.4] String 138 descriptions: false, // [0.3.0] Boolean 139 deselect: false, // [0.3.0] Boolean 140 disabled: false, // [0.5.0] Boolean 141 height: 350, // [0.2.0] Integer, null 142 hideDisabled: false, // [0.3.0] Boolean 143 hideSelected: false, // [0.3.0] Boolean 144 items: {}, // [0.3.0] Object 145 locale: "en", // [0.5.0] String 146 linguisticRules: { // [0.5.9] Object 147 "е": "Ñ", 148 "a": "ä", 149 "o": "ö", 150 "u": "ü", 151 "ss": "Ã" 152 }, 153 multiple: false, // [0.2.0] Boolean 154 multiLimit: Infinity, // [0.3.0] Integer, Infinity 155 multiPinSelected: false, // [0.5.0] Boolean 156 multiContainer: false, // [0.3.0] Boolean, String 157 multiShowCount: true, // [0.3.0] Boolean 158 multiShowLimit: false, // [0.5.0] Boolean 159 multiSelectAll: false, // [0.4.0] Boolean 160 multiSelectGroup: true, // [0.4.0] Boolean 161 openAbove: null, // [0.3.0] Boolean, null 162 placeholder: null, // [0.2.0] String, null 163 search: false, // [0.3.0] Boolean 164 searchConfig: [ // [0.5.13] Array 165 "text", "value" 166 ], 167 searchFocus: true, // [0.3.0] Boolean 168 searchMarked: true, // [0.3.0] Boolean 169 searchMinLength: 1, // [0.5.13] Integer 170 searchDisabled: true, // [0.5.5] Boolean 171 sortItems: false, // [0.3.0] String, Function, false 172 sortGroups: false, // [0.3.0] String, Function, false 173 sourceBind: false, // [0.5.0] Boolean 174 sourceHide: true, // [0.5.0] Boolean 175 startOpen: false, // [0.3.0] Boolean 176 stayOpen: false, // [0.3.0] Boolean 177 width: null, // [0.2.0] Integer, String, null 178 cbComplete: undefined, // [0.5.0] Function 179 cbEmpty: undefined, // [0.5.0] Function 180 cbLoopItem: undefined, // [0.4.0] Function 181 cbLoopGroup: undefined // [0.4.0] Function 182 }; 183 184 /* 185 | STORAGE :: STRINGS 186 */ 187 select.strings = { 188 en: { 189 all: "All", 190 none: "None", 191 empty: "No Options available",
192 emptySearch: "No Options found", 193 limit: "You can't select more Options", 194 placeholder: "Select an Option...", 195 placeholderMulti: "Select up to :limit Options...", 196 search: "Type in to search...", 197 disabled: "This Field is disabled" 198 }, 199 modify: function(locale, id, string){ 200 if(!(locale in this)){ 201 return false; 202 } 203 if((id instanceof Object)){ 204 for(var key in id){ 205 this.modify(locale, key, id[key]); 206 } 207 } else { 208 this[locale][id] = (typeof(string) == "string")? string: this[locale][id]; 209 } 210 return true; 211 }, 212 register: function(locale, object){ 213 if(typeof(locale) != "string" || !(object instanceof Object)){ 214 return false; 215 } 216 this[locale] = object; 217 return true; 218 } 219 }; 220 221 /* 222 | TAIL.SELECT HANDLER 223 */ 224 select.prototype = { 225 /* 226 | INERNAL :: TRANSLATE 227 | @since 0.5.8 [0.5.8] 228 */ 229 _e: function(string, replace, def){ 230 if(!(string in this.__)){ 231 return (!def)? string: def; 232 } 233 234 var string = this.__[string]; 235 if(typeof(string) === "function"){ 236 string = string.call(this, replace); 237 } 238 if(typeof(replace) === "object"){ 239 for(var key in replace){ 240 string = string.replace(key, replace[key]); 241 } 242 } 243 return string; 244 }, 245 246 /* 247 | INTERNAL :: INIT SELECT FIELD 248 | @since 0.5.13 [0.3.0] 249 */ 250 init: function(){ 251 var self = this, classes = ["tail-select"], con = this.con, 252 regexp = /^[0-9.]+(?:cm|mm|in|px|pt|pc|em|ex|ch|rem|vw|vh|vmin|vmax|\%)$/i; 253 254 // Init ClassNames 255 var c = (con.classNames === true)? this.e.className: con.classNames; 256 classes.push((c && c.push)? c.join(" "): (c && c.split)? c: "no-classe
256s"); 257 if(con.hideSelected){ classes.push("hide-selected"); } 258 if(con.hideDisabled){ classes.push("hide-disabled"); } 259 if(con.multiLimit == 0){ classes.push("disabled"); } 260 if(con.multiple){ classes.push("multiple"); } 261 if(con.deselect){ classes.push("deselect"); } 262 if(con.disabled){ classes.push("disabled"); } 263 264 // Init Variables 265 this.__ = clone(select.strings.en, select.strings[con.locale] || {}); 266 this._init = true; 267 this._query = false; 268 this.select = create("DIV", classes); 269 this.label = create("DIV", "select-label"); 270 this.dropdown = create("DIV", "select-dropdown"); 271 this.search = create("DIV", "dropdown-search"); 272 this.csvInput = create("INPUT", "select-search"); 273 274 // Build :: Select 275 if(this.e.getAttribute("tabindex") !== null){ 276 this.select.setAttribute("tabindex", this.e.getAttribute("tabindex")); 277 } else { 278 this.select.setAttribute("tabindex", 0); 279 } 280 if(con.width && regexp.test(con.width)){ 281 this.select.style.width = con.width; 282 } else if(con.width && !isNaN(parseFloat(con.width, 10))){ 283 this.select.style.width = con.width + "px"; 284 } 285 286 // Build :: Label 287 this.label.addEventListener("click", function(event){ 288 self.toggle.call(self, self.con.animate); 289 }); 290 this.select.appendChild(this.label); 291 292 // Build :: Dropdown 293 if(!isNaN(parseInt(con.height, 10))){ 294 this.dropdown.style.maxHeight = parseInt(con.height, 10) + "px"; 295 } 296 if(con.search){ 297 this.search.innerHTML = '<input type="text" class="search-input" />'; 298 this.search.children[0].placeholder = this._e("search"); 299 this.search.children[0].addEventListener("input", function(event){ 300 self.query.call(self, (this.value.length > con.searchMinLength)? this.value: undefined); 301 }); 302 this.dropdown.appendChild(this.search); 303 } 304 this.select.appendChild(this.dropdown); 305 306 // Build :: CSV Input 307 this.csvInput.type = "hidden"; 308 if(con.csvOutput){
309 this.csvInput.name = this.e.name; 310 this.e.removeAttribute("name"); 311 this.select.appendChild(this.csvInput); 312 } 313 314 // Prepare Container 315 if(con.multiple && con.multiContainer){ 316 if(d.querySelector(con.multiContainer)){ 317 this.container = d.querySelector(con.multiContainer); 318 this.container.className += " tail-select-container"; 319 } else if(con.multiContainer === true){ 320 this.container = this.label; 321 this.container.className += " tail-select-container"; 322 } 323 } 324 325 // Prepare Options 326 this.options = new options(this.e, this); 327 for(var l = this.e.options.length, i = 0; i < l; i++){ 328 this.options.set(this.e.options[i], false); 329 } 330 for(var key in con.items){ 331 if(typeof(con.items[key]) == "string"){ 332 con.items[key] = {value: con.items[key]}; 333 } 334 this.options.add(con.items[key].key || key, con.items[key].value, 335 con.items[key].group, con.items[key].selected, 336 con.items[key].disabled, con.items[key].description); 337 } 338 this.query(); 339 340 // Append and Return 341 if(this.e.nextElementSibling){ 342 this.e.parentElement.insertBefore(this.select, this.e.nextElementSibling); 343 } else { 344 this.e.parentElement.appendChild(this.select); 345 } 346 if(con.sourceHide){ 347 if(this.e.style.display == "none"){ 348 this.select.style.display = "none"; 349 this.e.setAttribute("data-select-hidden", "display"); 350 } else if(this.e.style.visibility == "hidden"){ 351 this.select.style.visibiltiy = "hidden"; 352 this.e.setAttribute("data-select-hidden", "visibility"); 353 } else { 354 this.e.style.display = "none"; 355 this.e.setAttribute("data-select-hidden", "0"); 356 } 357 } 358 this.e.setAttribute("data-tail-select", "tail-" + this.id); 359 if(self.con.startOpen){ 360 this.open(con.animate); 361 } 362 (con.cbComplete || function(){ }).call(this, this.select); 363 364 return (this._init = false)? this: this; 365 }, 366 367 /* 368 | INTERNAL :: EVENT LISTENER 369 | @since 0.5.13 [0.3.0] 370 */ 371 bind: function(){ 372 var self = this; 373 374 /* Seems to be last call from init */ 375 if (typeof this.options_initial === 'undefined') { 376 var init_selected = []; 377 378 for (var idx = 0; idx < this.options.selected.length; ++idx) { 379 init_selected.push(this.options.selected[idx]); 380 } 381 382 this.options_initial = { 383 selected: init_selected 384 } 385 } 386 387 // Keys Listener 388 d.addEventListener("keydown", function(event){ 389 var key = (event.keyCode || event.which), opt, inner, e, temp, tmp; 390 var space = (key == 32 && self.select === document.activeElement); 391 if(!space && (!cHAS(self.select, "active") || [13, 27, 38, 40].indexOf(key) < 0)){ 392 return false; 393 } 394 event.preventDefault(); 395 event.stopPropagation(); 396 397 // Space 398 if(key === 32){ 399 return self.open(self.con.animate); 400 } 401 402 // Enter || Escape 403 if(key == 13){ 404 if((opt = self.dropdown.querySelector(".dropdown-option.hover:not(.disabled)"))){ 405 self.options.select.call(self.options, opt); 406 } 407 } 408 if(key == 27 || key == 13){ 409 return self.close(self.con.animate); 410 } 411 412 // Top || Down 413 if((opt = self.dropdown.querySelector(".dropdown-option.hover:not(.disabled)"))){ 414 cREM(opt, "hover"); e = [((key == 40)? "next": "previous") + "ElementSibling"]; 415 do { 416 if((temp = opt[e]) !== null && opt.tagName == "LI"){ 417 opt = temp; 418 } else if((temp = opt.parentElement[e]) !== null && temp.children.length > 0 && temp.tagName == "UL"){ 419 opt = temp.children[(key == 40)? 0: temp.children.length-1]; 420 } else { 421 opt = false; 422 } 423 if(opt && (!cHAS(opt, "dropdown-option") || cHAS(opt, "disabled"))){ 424 continue; 425 } 426 break; 427 } while(true); 428 }
429 if(!opt && key == 40){ 430 opt = self.dropdown.querySelector(".dropdown-option:not(.disabled)"); 431 } else if(!opt && key == 38){ 432 tmp = self.dropdown.querySelectorAll(".dropdown-option:not(.disabled)"); 433 opt = tmp[tmp.length - 1]; 434 } 435 if(opt && (inner = self.dropdown.querySelector(".dropdown-inner"))){ 436 var pos = (function(el){ 437 var _r = {top: el.offsetTop, height: el.offsetHeight}; 438 while((el = el.parentElement) != inner){ 439 _r.top += el.offsetTop; 440 } 441 return _r; 442 })(opt); 443 cADD(opt, "hover"); 444 inner.scrollTop = Math.max(0, pos.top - (pos.height * 2)); 445 } 446 return true; 447 }); 448 449 // Close 450 d.addEventListener("click", function(ev){ 451 if(!cHAS(self.select, "active") || cHAS(self.select, "idle")){ return false; } 452 if(self.con.stayOpen === true){ return false; } 453 454 var targets = [self.e, self.select, self.container]; 455 for(var l = targets.length, i = 0; i < l; i++){ 456 if(targets[i] && (targets[i].contains(ev.target) || targets[i] == ev.target)){ 457 return false; 458 } 459 if(!ev.target.parentElement){ return false; } 460 } 461 return self.close.call(self, self.con.animate); 462 }); 463 464 // Bind Source Select 465 if(!this.con.sourceBind){ 466 return true; 467 } 468 this.e.addEventListener("change", function(event){ 469 if(event.detail != undefined){ 470 return false; 471 } 472 event.preventDefault(); 473 event.stopPropagation(); 474 if(!this.multiple && this.selectedIndex){ 475 self.options.select.call(self.options, this.options[this.selectedIndex]); 476 } else { 477 var u = [].concat(self.options.selected); 478 var s = [].filter.call(this.querySelectorAll("option:checked"), function(item){ 479 if(u.indexOf(item) >= 0){ 480 u.splice(u.indexOf(item), 1); 481 return false; 482 } 483 return true; 484 }); 485 self.options.walk.call(self.options, "unselect", u); 486 self.options.walk.call(self.options, "select", s); 487 } 488 }); 489 return true; 490 }, 491 492 /* 493 | INTERNAL :: INTERNAL CALLBACK 494 | @since 0.5.14 [0.3.0] 495 */ 496 callback: function(item, state, _force){ 497 var rkey = item.key.replace(/('|\\)/g, "\\$1"), 498 rgrp = item.group.replace(/('|\\)/g, "\\$1"), 499 rsel = "[data-key='" + rkey + "'][data-group='" + rgrp + "']"; 500 if(state == "rebuild"){ return this.query(); } 501 502 // Set Element-Item States 503 var element = this.dropdown.querySelector(rsel); 504 if(element && ["select", "disable"].indexOf(state) >= 0){ 505 cADD(element, (state == "select"? "selected": "disabled")); 506 } else if(element && ["unselect", "enable"].indexOf(state) >= 0){ 507 cREM(element, (state == "unselect"? "selected": "disabled")); 508 } 509 510 // Handle 511 this.update(item); 512 return (_force === true)? true: this.trigger("change", item, state); 513 }, 514 515 /* 516 | INTERNAL :: TRIGGER EVENT HANDLER 517 | @since 0.5.2 [0.4.0] 518 */ 519 trigger: function(event){ 520 if(this._init){ return false; } 521 var obj = {bubbles: false, cancelable: true, detail: {args: arguments, self: this}}; 522 if(event == "change" && arguments[2] && arguments[2].indexOf("select") >= 0){ 523 trigger(this.e, "input", obj); 524 trigger(this.e, "change", obj); 525 } 526 trigger(this.select, "tail::" + event, obj); 527 528 var args = [], pass; 529 Array.prototype.map.call(arguments, function(item, i){ 530 if(i > 0){ args.push(item); } 531 });
532 (this.events[event] || []).forEach(function(item){ 533 pass = [].concat(args); 534 pass.push(item.args || null); 535 (item.cb || function(){ }).apply(obj.detail.self, pass); 536 }); 537 return true; 538 }, 539 540 /* 541 | INTERNAL :: CALCULATE DROPDOWN 542 | @since 0.5.4 [0.5.0] 543 */ 544 calc: function(){ 545 var clone = this.dropdown.cloneNode(true), height = this.con.height, search = 0, 546 inner = this.dropdown.querySelector(".dropdown-inner"); 547 548 // Calculate Dropdown Height 549 clone = this.dropdown.cloneNode(true); 550 clone.style.cssText = "height:auto;min-height:auto;max-height:none;opacity:0;display:block;visibility:hidden;"; 551 clone.style.maxHeight = this.con.height + "px"; 552 clone.className += " cloned"; 553 this.dropdown.parentElement.appendChild(clone); 554 height = (height > clone.clientHeight)? clone.clientHeight: height; 555 if(this.con.search){ 556 search = clone.querySelector(".dropdown-search").clientHeight; 557 } 558 this.dropdown.parentElement.removeChild(clone); 559 560 // Calculate Viewport 561 var pos = this.select.getBoundingClientRect(), 562 bottom = w.innerHeight-(pos.top+pos.height), 563 view = ((height+search) > bottom)? pos.top > bottom: false; 564 if(this.con.openAbove === true || (this.con.openAbove !== false && view)){ 565 view = true, height = Math.min((height), pos.top-10); 566 cADD(this.select, "open-top"); 567 } else { 568 view = false, height = Math.min((height), bottom-10); 569 cREM(this.select, "open-top"); 570 } 571 if(inner){ 572 this.dropdown.style.maxHeight = height + "px"; 573 inner.style.maxHeight = (height-search) + "px"; 574 } 575 return this; 576 }, 577 578 /* 579 | API :: QUERY OPTIONS 580 | @since 0.5.13 [0.5.0] 581 */ 582 query: function(search, conf){ 583 var item, tp, ul, li, a1, a2; // Pre-Definition 584 var self = this, con = this.con, g = "getAttribute"; // Shorties 585 var root = create("DIV", "dropdown-inner"), // Contexts 586 func = (!search)? "walker": "finder", 587 args = (!search)? [con.sortItems, con.sortGroups]: [search, conf]; 588 589 // Option Walker 590 this._query = (typeof(search) == "string")? search: false; 591 while(item = this.options[func].apply(this.options, args)){ 592 if(!ul || (ul && ul[g]("data-group") !== item.group)){ 593 tp = (con.cbLoopGroup || this.cbGroup).call(this, item.group, search, root); 594 if(tp instanceof Element){ 595 ul = tp; 596 ul.setAttribute("data-group", item.group); 597 root.appendChild(ul); 598 } else { break; } 599 } 600 601 // Create Item 602 if((li = (con.cbLoopItem || this.cbItem).call(this, item, ul, search, root)) === null){ 603 continue; 604 } 605 if(li === false){ break; } 606 li.setAttribute("data-key", item.key); 607 li.setAttribute("data-group", item.group); 608 li.addEventListener("click", function(event){ 609 if(!this.hasAttribute("data-key")){ return false; } 610 var key = this[g]("data-key"), group = this[g]("data-group") || "#"; 611 if(self.options.toggle.call(self.options, key, group)){ 612 if(self.con.stayOpen === false && !self.con.multiple){ 613 self.close.call(self, self.con.animate); 614 } 615 } 616 }); 617 ul.appendChild(li); 618 } 619 620 // Empty 621 var count = root.querySelectorAll("*[data-key]").length; 622 if(count == 0){ 623 (this.con.cbEmpty || function(element){ 624 var li = create("SPAN", "dropdown-empty"); 625 li.innerText = this._e("empty"); 626 element.appendChild(li); 627 }).call(this, root, search); 628 } 629 630 // Select All 631 if(count > 0 && con.multiple && con.multiLimit == Infinity && con.multiSelectAll){ 632 a1 = create("BUTTON", "tail-all"), a2 = create("BUTTON", "tail-none"); 633 a1.innerText = this._e("all"); 634 a1.addEventListener("click", function(event){ 635 event.preventDefault(); 636 var options = self.dropdown.querySelectorAll(".dropdown-inner .dropdown-option"); 637 self.options.walk.call(self.options, "select", options); 638 }) 639 a2.innerText = this._e("none"); 640 a2.addEventListener("click", function(event){ 641 event.preventDefault(); 642 var options = self.dropdown.querySelectorAll(".dropdown-inner .dropdown-option"); 643 self.options.walk.call(self.options, "unselect", options); 644 }) 645 646 // Add Element 647 li = create("SPAN", "dropdown-action"); 648 li.appendChild(a1); 649 li.appendChild(a2); 650 root.insertBefore(li, root.children[0]); 651 } 652 653 // Add and Return 654 var data = this.dropdown.querySelector(".dropdown-inner"); 655 this.dropdown[(data? "replace": "append") + "Child"](root, data); 656 if(cHAS(this.select, "active")){ 657 this.calc(); 658 } 659 return this.updateCSV().updateLabel(); 660 }, 661 662 /* 663 | API :: CALLBACK -> CREATE GROUP 664 | @since 0.5.8 [0.4.0] 665 */ 666 cbGroup: function(group, search){ 667 var ul = create("UL", "dropdown-optgroup"), self = this, a1, a2; 668 if(group == "#"){ return ul; } 669 ul.innerHTML = '<li class="optgroup-title"><b>' + group + '</b></li>'; 670 if(this.con.multiple && this.con.multiLimit == Infinity && this.con.multiSelectAll){ 671 a1 = create("BUTTON", "tail-none"), a2 = create("BUTTON", "tail-all"); 672 a1.innerText = this._e("none"); 673 a1.addEventListener("click", function(event){ 674 event.preventDefault(); 675 var grp = this.parentElement.parentElement.getAttribute("data-group"); 676 self.options.all.call(self.options, "unselect", grp); 677 }); 678 a2.innerText = this._e("all"); 679 a2.addEventListener("click", function(event){ 680 event.preventDefault(); 681 var grp = this.parentElement.parentElement.getAttribute("data-group"); 682 self.options.all.call(self.options, "select", grp); 683 }); 684 ul.children[0].appendChild(a1); 685 ul.children[0].appendChild(a2); 686 } 687 return ul; 688 }, 689 690 /* 691 | API :: CALLBACK -> CREATE ITEM 692 | @since 0.5.13 [0.4.0] 693 */ 694 cbItem: function(item, optgroup, search){ 695 var li = create("LI", "dropdown-option" + (item.selected? " selected": "") + (item.disabled? " disabled": "")); 696 li.title = item.option.title; 697 698 // Inner Text 699 if(search && search.length > 0 && this.con.searchMarked){ 700 search = this.options.applyLinguisticRules(search); 701 li.innerHTML = item.value.replace(new RegExp("(" + search + ")", "i"), "<mark>$1</mark>"); 702 } else { 703 li.innerText = item.value; 704 } 705 706 // Inner Description 707 if(this.con.descriptions && item.description){ 708 li.innerHTML += '<span class="option-description">' + item.description + '</span>'; 709 } 710 return li; 711 }, 712 713 /* 714 | API :: UPDATE EVERYTHING 715 | @since 0.5.0 [0.5.0] 716 */ 717 update: function(item){ 718 return this.updateLabel().updateContainer(item).updatePin(item).updateCSV(item); 719 }, 720 721 /* 722 | API :: UPDATE LABEL 723 | @since 0.5.8 [0.5.0] 724 */ 725 updateLabel: function(label){ 726 if(this.container == this.label && this.options.selected.length > 0){ 727 if(this.label.querySelector(".label-inner")){ 728 this.label.removeChild(this.label.querySelector(".label-inner")); 729 } 730 if(this.label.querySelector(".label-count")){ 731 this.label.removeChild(this.label.querySelector(".label-count"));
732 } 733 return this; 734 } 735 var c = this.con, len = this.options.selected.length, limit; 736 if(typeof(label) != "string"){ 737 if(c.disabled){ 738 label = "disabled"; 739 } else if(this.dropdown.querySelectorAll("*[data-key]").length == 0){ 740 label = "empty" + (cHAS(this.select, "in-search")? "Search": ""); 741 } else if(c.multiLimit <= len){ 742 label = "limit"; 743 } else if(!c.multiple && this.options.selected.length > 0){ 744 label = this.options.selected[0].innerText; 745 } else if(typeof(c.placeholder) == "string"){ 746 label = c.placeholder; 747 } else { 748 label = "placeholder" + (c.multiple && c.multiLimit < Infinity? "Multi": ""); 749 } 750 } 751 752 // Set HTML 753 label = this._e(label, {":limit": c.multiLimit}, label); 754 label = '<span class="label-inner">' + label + '</span>', 755 limit = (c.multiShowLimit && c.multiLimit < Infinity); 756 if(c.multiple && c.multiShowCount){ 757 var selected_class = (len) ? 'label-count--selected' : ''; 758 759 label = '<span class="label-count ' + selected_class + '">:c</span>' + label; 760 label = label.replace(":c", len + (limit? (" / " + c.multiLimit): "")); 761 } 762 this.label.innerHTML = label; 763 return this; 764 }, 765 766 /* 767 | API :: UPDATE CONTAINER 768 | @since 0.5.0 [0.5.0] 769 */ 770 updateContainer: function(item){ 771 if(!this.container || !this.con.multiContainer){ 772 return this; 773 } 774 var s = "[data-group='" + item.group + "'][data-key='" + item.key + "']"; 775 if(this.container.querySelector(s)){ 776 if(!item.selected){ 777 this.container.removeChild(this.container.querySelector(s)); 778 } 779 return this; 780 } 781 782 // Create Item 783 if(item.selected){ 784 var self = this, hndl = create("DIV", "select-handle"); 785 hndl.innerText = item.value; 786 hndl.setAttribute("data-key", item.key); 787 hndl.setAttribute("data-group", item.group); 788 hndl.addEventListener("click", function(event){ 789 event.preventDefault(); 790 event.stopPropagation(); 791 var key = this.getAttribute("data-key"), grp = this.getAttribute("data-group"); 792 self.options.unselect.call(self.options, key, grp); 793 }); 794 this.container.appendChild(hndl); 795 } 796 return this; 797 }, 798 799 /* 800 | API :: UPDATE PIN POSITION 801 | @since 0.5.3 [0.5.0] 802 */ 803 updatePin: function(item){ 804 var inner = this.dropdown.querySelector(".dropdown-inner ul"), 805 option = "li[data-key='" + item.key + "'][data-group='" + item.group + "']"; 806 if(!this.con.multiPinSelected || !inner || this._query !== false){ 807 return this; 808 } 809 810 // Create Item 811 option = this.dropdown.querySelector(option); 812 if(item.selected){ 813 inner.insertBefore(option, inner.children[0]); 814 } else { 815 var grp = this.dropdown.querySelector("ul[data-group='" + item.group + "']"), 816 prev = this.options[item.index-1], found = false; 817 while(prev && prev.group == item.group){ 818 if(found = grp.querySelector("li[data-key='" + prev.key + "']")){ 819 break; 820 } 821 prev = this.options[prev.index-1]; 822 } 823 if(found && found.nextElementSibling){ 824 grp.insertBefore(option, found.nextElementSibling); 825 } else { 826 grp.appendChild(option); 827 } 828 } 829 return this; 830 }, 831 832 /* 833 | API :: UPDATE CSV INPUT 834 | @since 0.5.0 [0.5.0] 835 */ 836 updateCSV: function(item){ 837 if(!this.csvInput || !this.con.csvOutput){
838 return this; 839 } 840 for(var selected = [], l = this.options.selected.length, i = 0; i < l; i++){ 841 selected.push(this.options.selected[i].value); 842 } 843 this.csvInput.value = selected.join(this.con.csvSeparator || ","); 844 return this; 845 }, 846 847 /* 848 | PUBLIC :: OPEN DROPDOWN 849 | @since 0.5.0 [0.3.0] 850 */ 851 open: function(animate){ 852 if(cHAS(this.select, "active") || cHAS(this.select, "idle") || this.con.disabled){ 853 return false; 854 } 855 this.calc(); 856 857 // Final Function 858 var final = function(){ 859 cADD(self.select, "active"); 860 cREM(self.select, "idle"); 861 this.dropdown.style.height = "auto"; 862 this.dropdown.style.overflow = "visible"; 863 864 this.label.removeAttribute("style"); 865 if(this.con.search && this.con.searchFocus){ 866 this.dropdown.querySelector("input").focus(); 867 } 868 this.trigger.call(this, "open"); 869 }, self = this, e = this.dropdown.style; 870 871 // Open 872 if(animate !== false){ 873 this.label.style.zIndex = 25; 874 this.dropdown.style.cssText += "height:0;display:block;overflow:hidden;"; 875 cADD(self.select, "idle"); 876 (function animate(){ 877 var h = parseInt(e.height, 10), m = parseInt(e.maxHeight, 10); 878 if(h >= m){ 879 return final.call(self); 880 } 881 e.height = ((h+50 > m)? m: h+50) + "px"; 882 setTimeout(animate, 20); 883 })(); 884 } else { 885 e.cssText = "height:" + e.maxHeight + ";display:block;overflow:hidden;"; 886 final.call(this); 887 } 888 return this; 889 }, 890 891 /* 892 | PUBLIC :: CLOSE DROPDOWN 893 | @since 0.5.0 [0.3.0] 894 */ 895 close: function(animate){ 896 if(!cHAS(this.select, "active") || cHAS(this.select, "idle")){ 897 return false; 898 } 899 var final = function(){ 900 cREM(this.select, "active"); 901 cREM(this.select, "idle"); 902 this.dropdown.removeAttribute("style"); 903 this.dropdown.querySelector(".dropdown-inner").removeAttribute("style"); 904 this.trigger.call(this, "close"); 905 }, self = this, e = this.dropdown; 906 907 // Close 908 if(animate !== false){ 909 cADD(this.select, "idle"); 910 this.dropdown.style.overflow = "hidden"; 911 (function animate(){ 912 if((parseInt(e.offsetHeight, 10)-50) <= 0){ 913 return final.call(self); 914 } 915 e.style.height = (parseInt(e.offsetHeight, 10)-50) + "px"; 916 setTimeout(animate, 20); 917 })(); 918 } else { 919 final.call(this); 920 } 921 return this; 922 }, 923 924 /* 925 | PUBLIC :: TOGGLE DROPDOWN 926 | @since 0.5.0 [0.3.0] 927 */ 928 toggle: function(animate){ 929 if(cHAS(this.select, "active")){ 930 return this.close(animate); 931 } 932 return !cHAS(this.select, "idle")? this.open(animate): this; 933 }, 934 935 /* 936 | PUBLIC :: REMOVE SELECT 937 | @since 0.5.3 [0.3.0] 938 */ 939 remove: function(){ 940 this.e.removeAttribute("data-tail-select"); 941 if(this.e.hasAttribute("data-select-hidden")){ 942 if(this.e.getAttribute("data-select-hidden") == "0"){ 943 this.e.style.removeProperty("display"); 944 } 945 this.e.removeAttribute("data-select-hidden"); 946 } 947 Array.prototype.map.call(this.e.querySelectorAll("[data-select-option='add']"), function(item){ 948 item.parentElement.removeChild(item); 949 }) 950 Array.prototype.map.call(this.e.querySelectorAll("[data-select-optgroup='add']"), function(item){ 951 item.parentElement.removeChild(item); 952 }) 953 this.e.name = (this.csvInput.hasAttribute("name"))? this.csvInput.name: this.e.name; 954 if(this.select.parentElement){ 955 this.select.parentElement.removeChild(this.select); 956 } 957 if(this.container){ 958 var handles = this.container.querySelectorAll(".select-handle"); 959 for(var l = handles.length, i = 0; i < l; i++){ 960 this.container.removeChild(handles[i]); 961 } 962 } 963 return this; 964 }, 965 966 /* 967 | PUBLIC :: RELOAD SELECT 968 | @since 0.5.0 [0.3.0] 969 */ 970 reload: function(){ 971 return this.remove().init(); 972 }, 973 974 /* 975 | PUBLIC :: RELOAD SELECT 976 | @since 0.5.20 [0.3.0] 977 */ 978 reset: function(){ 979 /* Set all to unselected */ 980 for(var idx = 0; idx < this.options.element.length; ++idx){ 981 this.options.element[idx].selected = false; 982 this.options.unselect(this.options.element[idx].value, "#", true); 983 } 984 985 /* Restore initial selection */ 986 for(var idx = 0; idx < this.options_initial.selected.length; ++idx){ 987 this.options_initial.selected[idx].selected = true; 988 this.options.select(this.options_initial.selected[idx].value, "#", true); 989 } 990 991 /* Reset input field */ 992 this.dropdown.querySelector('input').value = ''; 993 this.query.call(this, ''); 994 }, 995 996 /* 997 | PUBLIC :: GET|SET CONFIG 998 | @since 0.5.15 [0.4.0] 999 */ 1000 config: function(key, value, rebuild){ 1001 if(key instanceof Object){ 1002 for(var k in key){ this.config(k, key[k], false); } 1003 return this.reload()? this.con: this.con; 1004 }
1005 if(typeof(key) == "undefined"){ 1006 return this.con; 1007 } else if(!(key in this.con)){ 1008 return false; 1009 } 1010 1011 // Set | Return 1012 if(typeof(value) == "undefined"){ 1013 return this.con[key]; 1014 } 1015 this.con[key] = value; 1016 if(rebuild !== false){ 1017 this.reload(); 1018 } 1019 return this; 1020 }, 1021 enable: function(update){ 1022 cREM(this.select, "disabled"); 1023 this.e.disabled = false; 1024 this.con.disabled = false; 1025 return (update === false)? this: this.reload(); 1026 }, 1027 disable: function(update){ 1028 cADD(this.select, "disabled"); 1029 this.e.disabled = true; 1030 this.con.disabled = true; 1031 return (update === false)? this: this.reload(); 1032 }, 1033 1034 /* 1035 | PUBLIC :: CUSTOM EVENT LISTENER 1036 | @since 0.5.0 [0.4.0] 1037 */ 1038 on: function(event, callback, args){ 1039 if(["open", "close", "change"].indexOf(event) < 0 || typeof(callback) != "function"){ 1040 return false; 1041 } 1042 if(!(event in this.events)){ 1043 this.events[event] = []; 1044 } 1045 this.events[event].push({cb: callback, args: (args instanceof Array)? args: []}); 1046 return this; 1047 }, 1048 1049 /* 1050 | PUBLIC :: VALUE 1051 | @since 0.5.13 [0.5.13] 1052 */ 1053 value: function(){ 1054 if(this.options.selected.length == 0){ 1055 return null; 1056 } 1057 if(this.con.multiple){ 1058 return this.options.selected.map(function(opt){ 1059 return opt.value; 1060 }); 1061 } 1062 return this.options.selected[0].value; 1063 } 1064 }; 1065 1066 /* 1067 | OPTIONS CONSTRUCTOR 1068 | @since 0.5.12 [0.3.0] 1069 */ 1070 options = select.options = function(select, parent){ 1071 if(!(this instanceof options)){ 1072 return new options(select, parent); 1073 } 1074 this.self = parent; 1075 this.element = select; 1076 this.length = 0; 1077 this.selected = []; 1078 this.disabled = []; 1079 this.items = {"#": {}}; 1080 this.groups = {}; 1081 return this; 1082 } 1083 1084 /* 1085 | TAIL.OPTIONS HANDLER 1086 */ 1087 options.prototype = { 1088 /* 1089 | INTERNAL :: REPLACE TYPOs 1090 | @since 0.5.0 [0.3.0] 1091 */ 1092 _r: function(state){ 1093 return state.replace("disabled", "disable").replace("enabled", "enable") 1094 .replace("selected", "select").replace("unselected", "unselect"); 1095 }, 1096 1097 /* 1098 | GET AN EXISTING OPTION 1099 | @since 0.5.7 [0.3.0] 1100 */ 1101 get: function(key, grp){ 1102 var g = "getAttribute"; 1103 if(typeof(key) == "object" && key.key && key.group){ 1104 grp = key.group || grp; 1105 key = key.key; 1106 } else if(key instanceof Element){ 1107 if(key.tagName == "OPTION"){ 1108 grp = key.parentElement.label || "#"; 1109 key = key.value || key.innerText; 1110 } else if(key.hasAttribute("data-key")){ 1111 grp = key[g]("data-group") || key.parentElement[g]("data-group") || "#"; 1112 key = key[g]("data-key"); 1113 } 1114 } else if(typeof(key) != "string"){ 1115 return false; 1116 } 1117 key = (/^[0-9]+$/.test(key))? "_" + key: key; 1118 return (grp in this.items)? this.items[grp][key]: false; 1119 }, 1120 1121 /* 1122 | SET AN EXISTING OPTION 1123 | @since 0.5.15 [0.3.0] 1124 */ 1125 set: function(opt, rebuild){ 1126 var key = opt.value || opt.innerText, grp = opt.parentElement.label || "#"; 1127 if(!(grp in this.items)){ 1128 this.items[grp] = {}; 1129 this.groups[grp] = opt.parentElement; 1130 } 1131 if(key in this.items[grp]){ 1132 return false; 1133 } 1134 var id = (/^[0-9]+$/.test(key))? "_" + key: key; 1135 1136 // Validate Selection 1137 var con = this.self.con; 1138 if(con.multiple && this.selected.length >= con.multiLimit){ 1139 opt.selected = false; 1140 } 1141 if(opt.selected && con.deselect && (!opt.hasAttribute("selected") || con.multiLimit == 0)){ 1142 opt.selected = false; 1143 opt.parentElement.selectedIndex = -1; 1144 } 1145 1146 // Sanitize Description 1147 if(opt.hasAttribute("data-description")){ 1148 var span = create("SPAN"); 1149 span.innerHTML = opt.getAttribute("data-description"); 1150 opt.setAttribute("data-description", span.innerHTML); 1151 } 1152 1153 // Add Item 1154 this.items[grp][id] = { 1155 key: key, 1156 value: opt.text, 1157 description: opt.getAttribute("data-description") || null, 1158 group: grp, 1159 option: opt, 1160 optgroup: (grp != "#")? this.groups[grp]: undefined, 1161 selected: opt.selected, 1162 disabled: opt.disabled, 1163 hidden: opt.hidden || false 1164 }; 1165 this.length++; 1166 if(opt.selected){ this.select(this.items[grp][id]); } 1167 if(opt.disabled){ this.disable(this.items[grp][id]); } 1168 return (rebuild)? this.self.callback(this.items[grp][key], "rebuild"): true; 1169 }, 1170 1171 /* 1172 | CREATE A NEW OPTION 1173 | @since 0.5.13 [0.3.0] 1174 */ 1175 add: function(key, value, group, selected, disabled, description, rebuild){ 1176 if(key instanceof Object){ 1177 for(var k in key){ 1178 this.add(key[k].key || k, key[k].value, key[k].group, key[k].selected, key[k].disabled, key[k].description, false); 1179 } 1180 return this.self.query(); 1181 } 1182 if(this.get(key, group)){ 1183 return false; 1184 } 1185 1186 // Check Group 1187 group = (typeof(group) == "string")? group: "#"; 1188 if(group !== "#" && !(group in this.groups)){ 1189 var optgroup = create("OPTGROUP"); 1190 optgroup.label = group; 1191 optgroup.setAttribute("data-select-optgroup", "add"); 1192 this.element.appendChild(optgroup); 1193 this.items[group] = {}; 1194 this.groups[group] = optgroup; 1195 } 1196 1197 // Validate Selection 1198 if(this.self.con.multiple && this.selected.length >= this.self.con.multiLimit){ 1199 selected = false; 1200 } 1201 disabled = !!disabled; 1202 1203 // Create Option 1204 var option = d.createElement("OPTION"); 1205 option.value = key; 1206 option.selected = selected; 1207 option.disabled = disabled; 1208 option.innerText = value; 1209 option.setAttribute("data-select-option", "add"); 1210 if(description && description.length > 0){ 1211 option.setAttribute("data-description", description); 1212 } 1213 1214 // Add Option and Return 1215 ((group == "#")? this.element: this.groups[group]).appendChild(option); 1216 return this.set(option, rebuild); 1217 }, 1218 1219 /* 1220 | MOVE AN EXISTING OPTION 1221 | @since 0.5.0 [0.5.0] 1222 */ 1223 move: function(item, group, new_group, rebuild){ 1224 if(!(item = this.get(item, group))){ return false; } 1225 1226 // Create Group 1227 if(new_group !== "#" && !(new_group in this.groups)){ 1228 var optgroup = create("OPTGROUP"); 1229 optgroup.label = new_group; 1230 this.element.appendChild(optgroup); 1231 this.items[new_group] = {}; 1232 this.groups[new_group] = optgroup; 1233 this.groups[new_group].appendChild(item.option); 1234 } 1235 1236 // Move To Group 1237 delete this.items[item.group][item.key]; 1238 item.group = new_group; 1239 item.optgroup = this.groups[new_group] || undefined; 1240 this.items[new_group][item.key] = item; 1241 return (rebuild)? this.self.query(): true; 1242 }, 1243 1244 /* 1245 | REMOVE AN EXISTING OPTION 1246 | @since 0.5.7 [0.3.0] 1247 */ 1248 remove: function(item, group, rebuild){ 1249 if(!(item = this.get(item, group))){ return false; } 1250 if(item.selected){ this.unselect(item); } 1251 if(item.disabled){ this.enable(item); } 1252 1253 // Remove Data 1254 item.option.parentElement.removeChild(item.option); 1255 var id = (/^[0-9]+$/.test(item.key))? "_" + item.key: item.key; 1256 delete this.items[item.group][id]; 1257 this.length--; 1258 1259 // Remove Optgroup 1260 if(Object.keys(this.items[item.group]).length === 0){ 1261 delete this.items[item.group]; 1262 delete this.groups[item.group]; 1263 } 1264 return (rebuild)? this.self.query(): true; 1265 }, 1266 1267 /* 1268 | CHECK AN EXISTING OPTION 1269 | @since 0.5.0 [0.3.0] 1270 */ 1271 is: function(state, key, group){ 1272 var state = this._r(state), item = this.get(key, group); 1273 if(!item || ["select", "unselect", "disable", "enable"].indexOf(state) < 0){ 1274 return null; 1275 } 1276 if(state == "disable" || state == "enable"){ 1277 return (state == "disable")? item.disabled: !item.disabled; 1278 } else if(state == "select" || state == "unselect"){ 1279 return (state == "select")? item.selected: !item.selected; 1280 } 1281 return false; 1282 }, 1283 1284 /* 1285 | INTERACT WITH AN OPTION 1286 | @since 0.5.3 [0.3.0] 1287 */ 1288 handle: function(state, key, group, _force){ 1289 var item = this.get(key, group), state = this._r(state); 1290 if(!item || ["select", "unselect", "disable", "enable"].indexOf(state) < 0){ 1291 return null; 1292 } 1293 1294 // Disable || Enable 1295 if(state == "disable" || state == "enable"){ 1296 if(!(item.option in this.disabled) && state == "disable"){ 1297 this.disabled.push(item.option); 1298 } else if((item.option in this.disabled) && state == "enable"){ 1299 this.disabled.splice(this.disabled.indexOf(item.option), 1); 1300 } 1301 item.disabled = (state == "disable"); 1302 item.option.disabled = (state == "disable"); 1303 return this.self.callback.call(this.self, item, state); 1304 } 1305 1306 // Select || Unselect 1307 var dis = (cHAS(this.self.select, "disabled") || item.disabled || item.option.disabled), 1308 lmt = (this.self.con.multiple && this.self.con.multiLimit <= this.selected.length), 1309 sgl = (!this.self.con.multiple && this.selected.indexOf(item.option) > 0), 1310 del = (this.self.con.multiLimit == 0 && this.self.con.deselect == true), 1311 uns = (!this.self.con.multiple && !this.self.con.deselect && _force !== true); 1312 if(state == "select"){ 1313 if(dis || lmt || del || sgl){ 1314 return false; 1315 } 1316 if(!this.self.con.multiple){ 1317 for(var i in this.selected){ 1318 this.unselect(this.selected[i], undefined, true); 1319 } 1320 } 1321 if(this.selected.indexOf(item.option) < 0){ 1322 this.selected.push(item.option); 1323 } 1324 } else if(state == "unselect"){ 1325 if(dis || uns){ 1326 return false; 1327 } 1328 this.selected.splice(this.selected.indexOf(item.option), 1); 1329 } 1330 item.selected = (state == "select"); 1331 item.option.selected = (state == "select"); 1332 item.option[(state.length > 6? "remove": "set") + "Attribute"]("selected", "selected"); 1333 return this.self.callback.call(this.self, item, state, _force); 1334 }, 1335 enable: function(key, group){ 1336 return this.handle("enable", key, group, false); 1337 }, 1338 disable: function(key, group){ 1339 return this.handle("disable", key, group, false); 1340 }, 1341 select: function(key, group){ 1342 return this.handle("select", key, group, false); 1343 }, 1344 unselect: function(key, group, _force){ 1345 return this.handle("unselect", key, group, _force); 1346 }, 1347 toggle: function(item, group){ 1348 if(!(item = this.get(item, group))){ return false; } 1349 return this.handle((item.selected? "unselect": "select"), item, group, false); 1350 }, 1351 1352 /* 1353 | INVERT CURRENT <STATE> 1354 | @since 0.5.15 [0.3.0] 1355 */ 1356 invert: function(state){ 1357 state = this._r(state); 1358 if(["enable", "disable"].indexOf(state) >= 0){ 1359 var invert = this.disabled, action = (state == "enable")? "disable": "enable"; 1360 } else if(["select", "unselect"].indexOf(state) >= 0){ 1361 var invert = this.selected, action = (state == "select")? "unselect": "select"; 1362 } 1363 var convert = Array.prototype.filter.call(this, function(element){ 1364 return !(element in invert); 1365 }), self = this; 1366 1367 // Loop 1368 [].concat(invert).forEach(function(item){ 1369 self.handle.call(self, action, item); 1370 }); 1371 [].concat(convert).forEach(function(item){ 1372 self.handle.call(self, state, item); 1373 }); 1374 return true; 1375 }, 1376 1377 /* 1378 | SET <STATE> ON ALL OPTIONs 1379 | @since 0.5.0 [0.5.0] 1380 */ 1381 all: function(state, group){ 1382 var self = this, list = this; 1383 if(group in this.items){ 1384 list = Object.keys(this.items[group]); 1385 } else if(["unselect", "enable"].indexOf(state) >= 0){ 1386 list = [].concat((state == "unselect")? this.selected: this.disabled); 1387 } 1388 Array.prototype.forEach.call(list, function(item){ 1389 self.handle.call(self, state, item, group, false); 1390 }); 1391 return true; 1392 }, 1393 1394 /* 1395 | SET <STATE> FOR A BUNCH OF OPTIONs 1396 | @since 0.5.4 [0.5.3] 1397 */ 1398 walk: function(state, items, args){ 1399 if(items instanceof Array || items.length){ 1400 for(var l = items.length, i = 0; i < l; i++){ 1401 this.handle.apply(this, [state, items[i], null].concat(args)); 1402 } 1403 } else if(items instanceof Object){ 1404 var self = this;
1405 if(items.forEach){ 1406 items.forEach(function(value){ 1407 self.handle.apply(self, [state, value, null].concat(args)); 1408 }); 1409 } else { 1410 for(var key in items){ 1411 if(typeof(items[key]) != "string" && typeof(items[key]) != "number" && !(items[key] instanceof Element)){ 1412 continue; 1413 } 1414 this.handle.apply(this, [state, items[key], (key in this.items? key: null)]).concat(args); 1415 } 1416 } 1417 } 1418 return this; 1419 }, 1420 1421 /* 1422 | APPLY LINGUSTIC RULES 1423 | @since 0.5.13 [0.5.13] 1424 */ 1425 applyLinguisticRules: function(search, casesensitive){ 1426 var rules = this.self.con.linguisticRules, values = []; 1427 1428 // Prepare Rules 1429 Object.keys(rules).forEach(function(key){ 1430 values.push("(" + key + "|[" + rules[key] + "])"); 1431 }); 1432 if(casesensitive){ 1433 values = values.concat(values.map(function(s){ return s.toUpperCase(); })); 1434 } 1435 1436 return search.replace(new RegExp(values.join("|"), (casesensitive)? "g": "ig"), function(m){ 1437 return values[[].indexOf.call(arguments, m, 1) - 1]; 1438 }); 1439 }, 1440 1441 1442 /* 1443 | FIND SOME OPTIONs - ARRAY EDITION 1444 | @since 0.5.15 [0.3.0] 1445 */ 1446 find: function(search, config){ 1447 var self = this, matches, has = {}; 1448 1449 // Get Config 1450 if(!config){ 1451 config = this.self.con.searchConfig; 1452 } 1453 1454 // Config Callback 1455 if(typeof config === "function"){ 1456 matches = config.bind(this, search); 1457 } 1458 1459 // Config Handler 1460 else { 1461 config = (config instanceof Array)? config: [config]; 1462 config.forEach(function(c){ 1463 if(typeof(c) === "string"){ has[c] = true; } 1464 }); 1465 has.any = (!has.any)? has.attributes && has.value: has.any; 1466 1467 // Cleanup & Prepare 1468 if(!has.regex || has.text){ 1469 search = search.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); 1470 } 1471 if(!has.exactglyphes){ 1472 search = this.self.options.applyLinguisticRules(search, has.case); 1473 } 1474 if(has.word){ 1475 search = '\\b' + search + '\\b'; 1476 } 1477 1478 // Search 1479 var regex = new RegExp(search, (!has.case)? "mi": "m"), 1480 sfunc = function(opt){ return regex.test(opt.text || opt.value); }; 1481 1482 // Handle 1483 if(has.any){ 1484 matches = function(opt){ return sfunc(opt) || [].some.call(opt.attributes, sfunc); }; 1485 } else if(has.attributes){ 1486 matches = function(opt){ return [].some.call(opt.attributes, sfunc); }; 1487 } else { 1488 matches = sfunc; 1489 } 1490 1491 if(!this.self.con.searchDisabled){ 1492 var temp = matches; 1493 matches = function(opt){ return !opt.disabled && temp(opt); }; 1494 } 1495 } 1496 1497 // Hammer Time 1498 return [].filter.call(this.self.e.options, matches).map(function(opt){ 1499 return opt.hidden? false: self.get(opt) 1500 }); 1501 }, 1502 1503 /* 1504 | FIND SOME OPTIONs - WALKER EDITION 1505 | @since 0.5.5 [0.3.0] 1506 */ 1507 finder: function(search, config){ 1508 if(this._finderLoop === undefined){ 1509 this._finderLoop = this.find(search, config); 1510 } 1511 var item; 1512 while((item = this._finderLoop.shift()) !== undefined){ 1513 return item; 1514 } 1515 delete this._finderLoop; 1516 return false; 1517 }, 1518 1519 /* 1520 | NEW OPTIONS WALKER 1521 | @since 0.5.15 [0.4.0] 1522 */ 1523 walker: function(orderi, orderg){
1524 if(typeof(this._inLoop) != "undefined" && this._inLoop){ 1525 if(this._inItems.length > 0){ 1526 do { 1527 var temp = this.items[this._inGroup][this._inItems.shift()]; 1528 } while(temp.hidden === true); 1529 return temp; 1530 } 1531 1532 // Sort Items 1533 if(this._inGroups.length > 0){ 1534 while(this._inGroups.length > 0){ 1535 var group = this._inGroups.shift(); 1536 if(!(group in this.items)){ 1537 return false; 1538 } 1539 1540 var keys = Object.keys(this.items[group]); 1541 if(keys.length > 0){ 1542 break; 1543 } 1544 } 1545 if(orderi == "ASC"){ 1546 keys.sort(); 1547 } else if(orderi == "DESC"){ 1548 keys.sort().reverse(); 1549 } else if(typeof(orderi) == "function"){ 1550 keys = orderi.call(this, keys); 1551 } 1552 this._inItems = keys; 1553 this._inGroup = group; 1554 return this.walker(null, null); 1555 } 1556 1557 // Delete and Exit 1558 delete this._inLoop; 1559 delete this._inItems; 1560 delete this._inGroup; 1561 delete this._inGroups; 1562 return false; 1563 } 1564 1565 // Sort Groups 1566 var groups = Object.keys(this.groups) || []; 1567 if(orderg == "ASC"){ 1568 groups.sort(); 1569 } else if(orderg == "DESC"){ 1570 groups.sort().reverse(); 1571 } else if(typeof(orderg) == "function"){ 1572 groups = orderg.call(this, groups); 1573 } 1574 groups.unshift("#"); 1575 1576 // Init Loop 1577 this._inLoop = true; 1578 this._inItems = []; 1579 this._inGroups = groups; 1580 return this.walker(orderi, null); 1581 } 1582 }; 1583 1584 // Return 1585 return select; 1586}));
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.