Posted By

SamBrishes on 05/02/19


Tagged

form select replacement pure Vanilla jqueryless


Versions (?)

Who likes this?

1 person have marked this snippet as a favorite

SamBrishes


tail.select - A pure, vanilla JavaScript Replacement


 / Published in: JavaScript
 

URL: https://github.com/pytesNET/tail.select

Create beautiful, functional and extensive (Multi) Select Fields with pure, vanilla JavaScript. Check out the Demonstration :D

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

Report this snippet  

You need to login to post a comment.