Files
OMS/app/desktop/statics/js/tail.select-full.js
2025-12-28 23:13:25 +08:00

1660 lines
64 KiB
JavaScript
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
| tail.select - The vanilla solution to make your HTML select fields AWESOME!
| @file ./js/tail.select.js
| @author SamBrishes <sam@pytes.net>
| @version 0.5.16 - Beta
|
| @website https://github.com/pytesNET/tail.select
| @license X11 / MIT License
| @copyright Copyright © 2014 - 2019 SamBrishes, pytesNET <info@pytes.net>
*/
;(function(root, factory){
if(typeof define === "function" && define.amd){
define(function(){ return factory(root); });
} else if(typeof module === "object" && module.exports){
module.exports = factory(root);
} else {
if(typeof root.tail === "undefined"){
root.tail = {};
}
root.tail.select = factory(root);
// jQuery Support
if(typeof jQuery !== "undefined"){
jQuery.fn.tailselect = function(o){
var r = [], i;
this.each(function(){ if((i = tail.select(this, o)) !== false){ r.push(i); } });
return (r.length === 1)? r[0]: (r.length === 0)? false: r;
};
}
// MooTools Support
if(typeof MooTools !== "undefined"){
Element.implement({ tailselect: function(o){ return new tail.select(this, o); } });
}
}
}(window, function(root){
"use strict";
var w = root, d = root.document;
// Internal Helper Methods
function cHAS(el, name){
return (el && "classList" in el)? el.classList.contains(name): false;
}
function cADD(el, name){
return (el && "classList" in el)? el.classList.add(name): undefined;
}
function cREM(el, name){
return (el && "classList" in el)? el.classList.remove(name): undefined;
}
function trigger(el, event, opt){
if(CustomEvent && CustomEvent.name){
var ev = new CustomEvent(event, opt);
} else {
var ev = d.createEvent("CustomEvent");
ev.initCustomEvent(event, !!opt.bubbles, !!opt.cancelable, opt.detail);
}
return el.dispatchEvent(ev);
}
function clone(obj, rep){
if(typeof Object.assign === "function"){
return Object.assign({}, obj, rep || {});
}
var clone = Object.constructor();
for(var key in obj){
clone[key] = (key in rep)? rep[key]: obj[key];
}
return clone;
}
function create(tag, classes){
var r = d.createElement(tag);
r.className = (classes && classes.join)? classes.join(" "): classes || "";
return r;
}
/*
| SELECT CONSTRUCTOR
| @since 0.5.16 [0.3.0]
*/
var select = function(el, config){
el = (typeof el === "string")? d.querySelectorAll(el): el;
if(el instanceof NodeList || el instanceof HTMLCollection || el instanceof Array){
for(var _r = [], l = el.length, i = 0; i < l; i++){
_r.push(new select(el[i], clone(config, {})));
}
return (_r.length === 1)? _r[0]: ((_r.length === 0)? false: _r);
}
if(!(el instanceof Element) || !(this instanceof select)){
return !(el instanceof Element)? false: new select(el, config);
}
// Check Element
if(select.inst[el.getAttribute("data-tail-select")]){
return select.inst[el.getAttribute("data-tail-select")];
}
if(el.getAttribute("data-select")){
var test = JSON.parse(el.getAttribute("data-select").replace(/\'/g, '"'));
if(test instanceof Object){
config = clone(config, test); // This is a unofficial function ;3
}
}
// Get Element Options
var placeholder = el.getAttribute("placeholder") || el.getAttribute("data-placeholder"),
fb1 = "bindSourceSelect", fb2 = "sourceHide"; // Fallbacks
config = (config instanceof Object)? config: {};
config.multiple = ("multiple" in config)? config.multiple: el.multiple;
config.disabled = ("disabled" in config)? config.disabled: el.disabled;
config.placeholder = placeholder || config.placeholder || null;
config.width = (config.width === "auto")? el.offsetWidth + 50: config.width;
config.sourceBind = (fb1 in config)? config[fb1]: config.sourceBind || false;
config.sourceHide = (fb2 in config)? config[fb2]: config.sourceHide || true;
config.multiLimit = (config.multiLimit >= 0)? config.multiLimit: Infinity;
// Init Instance
this.e = el;
this.id = ++select.count;
this.con = clone(select.defaults, config);
this.events = {};
select.inst["tail-" + this.id] = this;
return this.init().bind();
}, options;
select.version = "0.5.16";
select.status = "beta";
select.count = 0;
select.inst = {};
/*
| STORAGE :: DEFAULT OPTIONS
*/
select.defaults = {
animate: true, // [0.3.0] Boolean
classNames: null, // [0.2.0] Boolean, String, Array, null
csvOutput: false, // [0.3.4] Boolean
csvSeparator: ",", // [0.3.4] String
descriptions: false, // [0.3.0] Boolean
deselect: false, // [0.3.0] Boolean
disabled: false, // [0.5.0] Boolean
height: 350, // [0.2.0] Integer, null
hideDisabled: false, // [0.3.0] Boolean
hideSelected: false, // [0.3.0] Boolean
items: {}, // [0.3.0] Object
locale: "zh", // [0.5.0] String
linguisticRules: { // [0.5.9] Object
"е": "ё",
"a": "ä",
"o": "ö",
"u": "ü",
"ss": "ß"
},
multiple: false, // [0.2.0] Boolean
multiLimit: Infinity, // [0.3.0] Integer, Infinity
multiPinSelected: false, // [0.5.0] Boolean
multiContainer: false, // [0.3.0] Boolean, String
multiShowCount: true, // [0.3.0] Boolean
multiShowLimit: false, // [0.5.0] Boolean
multiSelectAll: false, // [0.4.0] Boolean
multiSelectGroup: true, // [0.4.0] Boolean
openAbove: null, // [0.3.0] Boolean, null
placeholder: null, // [0.2.0] String, null
search: false, // [0.3.0] Boolean
searchConfig: [ // [0.5.13] Array
"text", "value"
],
searchFocus: true, // [0.3.0] Boolean
searchMarked: true, // [0.3.0] Boolean
searchMinLength: 1, // [0.5.13] Integer
searchDisabled: true, // [0.5.5] Boolean
sortItems: false, // [0.3.0] String, Function, false
sortGroups: false, // [0.3.0] String, Function, false
sourceBind: false, // [0.5.0] Boolean
sourceHide: true, // [0.5.0] Boolean
startOpen: false, // [0.3.0] Boolean
stayOpen: false, // [0.3.0] Boolean
width: null, // [0.2.0] Integer, String, null
cbComplete: undefined, // [0.5.0] Function
cbEmpty: undefined, // [0.5.0] Function
cbLoopItem: undefined, // [0.4.0] Function
cbLoopGroup: undefined // [0.4.0] Function
};
/*
| STORAGE :: STRINGS
*/
select.strings = {
zh: {
all: "全部",
none: "没有",
empty: "没有可用的选项",
emptySearch: "找不到选项",
limit: "您无法选择更多选项",
placeholder: "选择一个选项...",
placeholderMulti: "选择多个选项...",
search: "输入要搜索...",
disabled: "此字段已禁用"
},
de: {
all: "Alle",
none: "Keine",
empty: "Keine Optionen verfügbar",
emptySearch: "Keine Optionen gefunden",
limit: "Keine weiteren Optionen wählbar",
placeholder: "Wähle eine Option...",
placeholderMulti: "Wähle bis zu :limit Optionen...",
search: "Tippen zum suchen",
disabled: "Dieses Feld ist deaktiviert"
},
en: {
all: "All",
none: "None",
empty: "No Options available",
emptySearch: "No Options found",
limit: "You can't select more Options",
placeholder: "Select an Option...",
placeholderMulti: "Select up to :limit Options...",
search: "Type in to search...",
disabled: "This Field is disabled"
},
es: {
all: "Todos",
none: "Ninguno",
empty: "No hay opciones disponibles",
emptySearch: "No se encontraron opciones",
limit: "No puedes seleccionar mas opciones",
placeholder: "Selecciona una opción...",
placeholderMulti: "Selecciona hasta :límite de opciones...",
search: "Escribe dentro para buscar...",
disabled: "Este campo esta deshabilitado"
},
fi: {
all: "Kaikki",
none: "Ei mitään",
empty: "Ei vaihtoehtoja",
emptySearch: "Etsimääsi vaihtoehtoa ei löytynyt",
limit: "Muita vaihtoehtoja ei voi valita",
placeholder: "Valitse...",
placeholderMulti: "Valitse maksimissaan :limit...",
search: "Hae tästä...",
disabled: "Kenttä on poissa käytöstä"
},
fr: {
all: "Tous",
none: "Aucun",
empty: "Aucune option disponible",
emptySearch: "Aucune option trouvée",
limit: "Aucune autre option sélectionnable",
placeholder: "Choisissez une option...",
placeholderMulti: "Choisissez jusqu'à :limit option(s)...",
search: "Rechercher...",
disabled: "Ce champs est désactivé"
},
it: {
all: "Tutti",
none: "Nessuno",
empty: "Nessuna voce disponibile",
emptySearch: "Nessuna voce trovata",
limit: "Non puoi selezionare più Voci",
placeholder: "Seleziona una Voce",
placeholderMulti: "Selezione limitata a :limit Voci...",
search: "Digita per cercare...",
disabled: "Questo Campo è disabilitato"
},
no: {
all: "Alle",
none: "Ingen",
empty: "Ingen valg tilgjengelig",
emptySearch: "Ingen valg funnet",
limit: "Du kan ikke velge flere",
placeholder: "Velg...",
placeholderMulti: "Velg opptil :limit...",
search: "Søk...",
disabled: "Dette feltet er deaktivert"
},
pt_BR: {
all: "Todas",
none: "Nenhuma",
empty: "Nenhuma opção disponível",
emptySearch: "Nenhuma opção encontrada",
limit: "Não é possível selecionar outra opção",
placeholder: "Escolha uma opção ...",
placeholderMulti: "Escolha até: :limit opção(ões) ...",
search: "Buscar ...",
disabled: "Campo desativado"
},
ru: {
all: "Все",
none: "Ничего",
empty: "Нет доступных вариантов",
emptySearch: "Ничего не найдено",
limit: "Вы не можете выбрать больше вариантов",
placeholder: "Выберите вариант...",
placeholderMulti: function(args){
var strings = ["варианта", "вариантов", "вариантов"], cases = [2, 0, 1, 1, 1, 2], num = args[":limit"];
var string = strings[(num%100 > 4 && num%100 < 20)? 2: cases[(num%10 < 5)? num%10: 5]];
return "Выбор до :limit " + string + " ...";
},
search: "Начните набирать для поиска ...",
disabled: "Поле отключено"
},
tr: {
all: "Tümü",
none: "Hiçbiri",
empty: "Seçenek yok",
emptySearch: "Seçenek bulunamadı",
limit: "Daha fazla Seçenek seçemezsiniz",
placeholder: "Bir Seçenek seçin...",
placeholderMulti: "En fazla :limit Seçenek seçin...",
search: "Aramak için yazın...",
disabled: "Bu Alan kullanılamaz"
},
modify: function(locale, id, string){
if(!(locale in this)){
return false;
}
if((id instanceof Object)){
for(var key in id){
this.modify(locale, key, id[key]);
}
} else {
this[locale][id] = (typeof string === "string")? string: this[locale][id];
}
return true;
},
register: function(locale, object){
if(typeof locale !== "string" || !(object instanceof Object)){
return false;
}
this[locale] = object;
return true;
}
};
/*
| TAIL.SELECT HANDLER
*/
select.prototype = {
/*
| INERNAL :: TRANSLATE
| @since 0.5.8 [0.5.8]
*/
_e: function(string, replace, def){
if(!(string in this.__)){
return (!def)? string: def;
}
var string = this.__[string];
if(typeof string === "function"){
string = string.call(this, replace);
}
if(typeof replace === "object"){
for(var key in replace){
string = string.replace(key, replace[key]);
}
}
return string;
},
/*
| INTERNAL :: INIT SELECT FIELD
| @since 0.5.13 [0.3.0]
*/
init: function(){
var self = this, classes = ["tail-select"], con = this.con,
regexp = /^[0-9.]+(?:cm|mm|in|px|pt|pc|em|ex|ch|rem|vw|vh|vmin|vmax|\%)$/i;
// Init ClassNames
var c = (con.classNames === true)? this.e.className: con.classNames;
classes.push((c && c.push)? c.join(" "): (c && c.split)? c: "no-classes");
if(con.hideSelected){ classes.push("hide-selected"); }
if(con.hideDisabled){ classes.push("hide-disabled"); }
if(con.multiLimit == 0){ classes.push("disabled"); }
if(con.multiple){ classes.push("multiple"); }
if(con.deselect){ classes.push("deselect"); }
if(con.disabled){ classes.push("disabled"); }
// Init Variables
this.__ = clone(select.strings.en, select.strings[con.locale] || {});
this._init = true;
this._query = false;
this.select = create("DIV", classes);
this.label = create("DIV", "select-label");
this.dropdown = create("DIV", "select-dropdown");
this.search = create("DIV", "dropdown-search");
this.csvInput = create("INPUT", "select-search");
// Build :: Select
if(this.e.getAttribute("tabindex") !== null){
this.select.setAttribute("tabindex", this.e.getAttribute("tabindex"));
} else {
this.select.setAttribute("tabindex", 0);
}
if(con.width && regexp.test(con.width)){
this.select.style.width = con.width;
} else if(con.width && !isNaN(parseFloat(con.width, 10))){
this.select.style.width = con.width + "px";
}
// Build :: Label
this.label.addEventListener("click", function(event){
self.toggle.call(self, self.con.animate);
});
this.select.appendChild(this.label);
// Build :: Dropdown
if(!isNaN(parseInt(con.height, 10))){
this.dropdown.style.maxHeight = parseInt(con.height, 10) + "px";
}
if(con.search){
this.search.innerHTML = '<input type="text" class="search-input" />';
this.search.children[0].placeholder = this._e("search");
this.search.children[0].addEventListener("input", function(event){
self.query.call(self, (this.value.length > con.searchMinLength)? this.value: undefined);
});
this.dropdown.appendChild(this.search);
}
this.select.appendChild(this.dropdown);
// Build :: CSV Input
this.csvInput.type = "hidden";
if(con.csvOutput){
this.csvInput.name = this.e.name;
this.e.removeAttribute("name");
this.select.appendChild(this.csvInput);
}
// Prepare Container
if(con.multiple && con.multiContainer){
if(d.querySelector(con.multiContainer)){
this.container = d.querySelector(con.multiContainer);
this.container.className += " tail-select-container";
} else if(con.multiContainer === true){
this.container = this.label;
this.container.className += " tail-select-container";
}
}
// Prepare Options
this.options = new options(this.e, this);
for(var l = this.e.options.length, i = 0; i < l; i++){
this.options.set(this.e.options[i], false);
}
for(var key in con.items){
if(typeof con.items[key] === "string"){
con.items[key] = {value: con.items[key]};
}
this.options.add(con.items[key].key || key, con.items[key].value,
con.items[key].group, con.items[key].selected,
con.items[key].disabled, con.items[key].description);
}
this.query();
// Append and Return
if(this.e.nextElementSibling){
this.e.parentElement.insertBefore(this.select, this.e.nextElementSibling);
} else {
this.e.parentElement.appendChild(this.select);
}
if(con.sourceHide){
if(this.e.style.display == "none"){
this.select.style.display = "none";
this.e.setAttribute("data-select-hidden", "display");
} else if(this.e.style.visibility == "hidden"){
this.select.style.visibiltiy = "hidden";
this.e.setAttribute("data-select-hidden", "visibility");
} else {
this.e.style.display = "none";
this.e.setAttribute("data-select-hidden", "0");
}
}
this.e.setAttribute("data-tail-select", "tail-" + this.id);
if(self.con.startOpen){
this.open(con.animate);
}
(con.cbComplete || function(){ }).call(this, this.select);
return (this._init = false)? this: this;
},
/*
| INTERNAL :: EVENT LISTENER
| @since 0.5.13 [0.3.0]
*/
bind: function(){
var self = this;
// Keys Listener
d.addEventListener("keydown", function(event){
var key = (event.keyCode || event.which), opt, inner, e, temp, tmp;
var space = (key == 32 && self.select === document.activeElement);
if(!space && (!cHAS(self.select, "active") || [13, 27, 38, 40].indexOf(key) < 0)){
return false;
}
event.preventDefault();
event.stopPropagation();
// Space
if(key === 32){
return self.open(self.con.animate);
}
// Enter || Escape
if(key == 13){
if((opt = self.dropdown.querySelector(".dropdown-option.hover:not(.disabled)"))){
self.options.select.call(self.options, opt);
}
}
if(key == 27 || key == 13){
return self.close(self.con.animate);
}
// Top || Down
if((opt = self.dropdown.querySelector(".dropdown-option.hover:not(.disabled)"))){
cREM(opt, "hover"); e = [((key == 40)? "next": "previous") + "ElementSibling"];
do {
if((temp = opt[e]) !== null && opt.tagName == "LI"){
opt = temp;
} else if((temp = opt.parentElement[e]) !== null && temp.children.length > 0 && temp.tagName == "UL"){
opt = temp.children[(key == 40)? 0: temp.children.length-1];
} else {
opt = false;
}
if(opt && (!cHAS(opt, "dropdown-option") || cHAS(opt, "disabled"))){
continue;
}
break;
} while(true);
}
if(!opt && key == 40){
opt = self.dropdown.querySelector(".dropdown-option:not(.disabled)");
} else if(!opt && key == 38){
tmp = self.dropdown.querySelectorAll(".dropdown-option:not(.disabled)");
opt = tmp[tmp.length - 1];
}
if(opt && (inner = self.dropdown.querySelector(".dropdown-inner"))){
var pos = (function(el){
var _r = {top: el.offsetTop, height: el.offsetHeight};
while((el = el.parentElement) != inner){
_r.top += el.offsetTop;
}
return _r;
})(opt);
cADD(opt, "hover");
inner.scrollTop = Math.max(0, pos.top - (pos.height * 2));
}
return true;
});
// Close
d.addEventListener("click", function(ev){
if(!cHAS(self.select, "active") || cHAS(self.select, "idle")){ return false; }
if(self.con.stayOpen === true){ return false; }
var targets = [self.e, self.select, self.container];
for(var l = targets.length, i = 0; i < l; i++){
if(targets[i] && (targets[i].contains(ev.target) || targets[i] == ev.target)){
return false;
}
if(!ev.target.parentElement){ return false; }
}
return self.close.call(self, self.con.animate);
});
// Bind Source Select
if(!this.con.sourceBind){
return true;
}
this.e.addEventListener("change", function(event){
if(event.detail != undefined){
return false;
}
event.preventDefault();
event.stopPropagation();
if(!this.multiple && this.selectedIndex){
self.options.select.call(self.options, this.options[this.selectedIndex]);
} else {
var u = [].concat(self.options.selected);
var s = [].filter.call(this.querySelectorAll("option:checked"), function(item){
if(u.indexOf(item) >= 0){
u.splice(u.indexOf(item), 1);
return false;
}
return true;
});
self.options.walk.call(self.options, "unselect", u);
self.options.walk.call(self.options, "select", s);
}
});
return true;
},
/*
| INTERNAL :: INTERNAL CALLBACK
| @since 0.5.14 [0.3.0]
*/
callback: function(item, state, _force){
var rkey = item.key.replace(/('|\\)/g, "\\$1"),
rgrp = item.group.replace(/('|\\)/g, "\\$1"),
rsel = "[data-key='" + rkey + "'][data-group='" + rgrp + "']";
if(state == "rebuild"){ return this.query(); }
// Set Element-Item States
var element = this.dropdown.querySelector(rsel);
if(element && ["select", "disable"].indexOf(state) >= 0){
cADD(element, (state == "select"? "selected": "disabled"));
} else if(element && ["unselect", "enable"].indexOf(state) >= 0){
cREM(element, (state == "unselect"? "selected": "disabled"));
}
// Handle
this.update(item);
return (_force === true)? true: this.trigger("change", item, state);
},
/*
| INTERNAL :: TRIGGER EVENT HANDLER
| @since 0.5.2 [0.4.0]
*/
trigger: function(event){
if(this._init){ return false; }
var obj = {bubbles: false, cancelable: true, detail: {args: arguments, self: this}};
if(event == "change" && arguments[2] && arguments[2].indexOf("select") >= 0){
trigger(this.e, "input", obj);
trigger(this.e, "change", obj);
}
trigger(this.select, "tail::" + event, obj);
var args = [], pass;
Array.prototype.map.call(arguments, function(item, i){
if(i > 0){ args.push(item); }
});
(this.events[event] || []).forEach(function(item){
pass = [].concat(args);
pass.push(item.args || null);
(item.cb || function(){ }).apply(obj.detail.self, pass);
});
return true;
},
/*
| INTERNAL :: CALCULATE DROPDOWN
| @since 0.5.4 [0.5.0]
*/
calc: function(){
var clone = this.dropdown.cloneNode(true), height = this.con.height, search = 0,
inner = this.dropdown.querySelector(".dropdown-inner");
// Calculate Dropdown Height
clone = this.dropdown.cloneNode(true);
clone.style.cssText = "height:auto;min-height:auto;max-height:none;opacity:0;display:block;visibility:hidden;";
clone.style.maxHeight = this.con.height + "px";
clone.className += " cloned";
this.dropdown.parentElement.appendChild(clone);
height = (height > clone.clientHeight)? clone.clientHeight: height;
if(this.con.search){
search = clone.querySelector(".dropdown-search").clientHeight;
}
this.dropdown.parentElement.removeChild(clone);
// Calculate Viewport
var pos = this.select.getBoundingClientRect(),
bottom = w.innerHeight-(pos.top+pos.height),
view = ((height+search) > bottom)? pos.top > bottom: false;
if(this.con.openAbove === true || (this.con.openAbove !== false && view)){
view = true, height = Math.min((height), pos.top-10);
cADD(this.select, "open-top");
} else {
view = false, height = Math.min((height), bottom-10);
cREM(this.select, "open-top");
}
if(inner){
this.dropdown.style.maxHeight = height + "px";
inner.style.maxHeight = (height-search) + "px";
}
return this;
},
/*
| API :: QUERY OPTIONS
| @since 0.5.13 [0.5.0]
*/
query: function(search, conf){
var item, tp, ul, li, a1, a2; // Pre-Definition
var self = this, con = this.con, g = "getAttribute"; // Shorties
var root = create("DIV", "dropdown-inner"), // Contexts
func = (!search)? "walker": "finder",
args = (!search)? [con.sortItems, con.sortGroups]: [search, conf];
// Option Walker
this._query = (typeof search === "string")? search: false;
while(item = this.options[func].apply(this.options, args)){
if(!ul || (ul && ul[g]("data-group") !== item.group)){
tp = (con.cbLoopGroup || this.cbGroup).call(this, item.group, search, root);
if(tp instanceof Element){
ul = tp;
ul.setAttribute("data-group", item.group);
root.appendChild(ul);
} else { break; }
}
// Create Item
if((li = (con.cbLoopItem || this.cbItem).call(this, item, ul, search, root)) === null){
continue;
}
if(li === false){ break; }
li.setAttribute("data-key", item.key);
li.setAttribute("data-group", item.group);
li.addEventListener("click", function(event){
if(!this.hasAttribute("data-key")){ return false; }
var key = this[g]("data-key"), group = this[g]("data-group") || "#";
if(self.options.toggle.call(self.options, key, group)){
if(self.con.stayOpen === false && !self.con.multiple){
self.close.call(self, self.con.animate);
}
}
});
ul.appendChild(li);
}
// Empty
var count = root.querySelectorAll("*[data-key]").length;
if(count == 0){
(this.con.cbEmpty || function(element){
var li = create("SPAN", "dropdown-empty");
li.innerText = this._e("empty");
element.appendChild(li);
}).call(this, root, search);
}
// Select All
if(count > 0 && con.multiple && con.multiLimit == Infinity && con.multiSelectAll){
a1 = create("BUTTON", "tail-all"), a2 = create("BUTTON", "tail-none");
a1.innerText = this._e("all");
a1.addEventListener("click", function(event){
event.preventDefault();
var options = self.dropdown.querySelectorAll(".dropdown-inner .dropdown-option");
self.options.walk.call(self.options, "select", options);
})
a2.innerText = this._e("none");
a2.addEventListener("click", function(event){
event.preventDefault();
var options = self.dropdown.querySelectorAll(".dropdown-inner .dropdown-option");
self.options.walk.call(self.options, "unselect", options);
})
// Add Element
li = create("SPAN", "dropdown-action");
li.appendChild(a1);
li.appendChild(a2);
root.insertBefore(li, root.children[0]);
}
// Add and Return
var data = this.dropdown.querySelector(".dropdown-inner");
this.dropdown[(data? "replace": "append") + "Child"](root, data);
if(cHAS(this.select, "active")){
this.calc();
}
return this.updateCSV().updateLabel();
},
/*
| API :: CALLBACK -> CREATE GROUP
| @since 0.5.8 [0.4.0]
*/
cbGroup: function(group, search){
var ul = create("UL", "dropdown-optgroup"), self = this, a1, a2;
if(group == "#"){ return ul; }
ul.innerHTML = '<li class="optgroup-title"><b>' + group + '</b></li>';
if(this.con.multiple && this.con.multiLimit == Infinity && this.con.multiSelectAll){
a1 = create("BUTTON", "tail-none"), a2 = create("BUTTON", "tail-all");
a1.innerText = this._e("none");
a1.addEventListener("click", function(event){
event.preventDefault();
var grp = this.parentElement.parentElement.getAttribute("data-group");
self.options.all.call(self.options, "unselect", grp);
});
a2.innerText = this._e("all");
a2.addEventListener("click", function(event){
event.preventDefault();
var grp = this.parentElement.parentElement.getAttribute("data-group");
self.options.all.call(self.options, "select", grp);
});
ul.children[0].appendChild(a1);
ul.children[0].appendChild(a2);
}
return ul;
},
/*
| API :: CALLBACK -> CREATE ITEM
| @since 0.5.13 [0.4.0]
*/
cbItem: function(item, optgroup, search){
var li = create("LI", "dropdown-option" + (item.selected? " selected": "") + (item.disabled? " disabled": ""));
// Inner Text
if(search && search.length > 0 && this.con.searchMarked){
search = this.options.applyLinguisticRules(search);
li.innerHTML = item.value.replace(new RegExp("(" + search + ")", "i"), "<mark>$1</mark>");
} else {
li.innerText = item.value;
}
// Inner Description
if(this.con.descriptions && item.description){
li.innerHTML += '<span class="option-description">' + item.description + '</span>';
}
return li;
},
/*
| API :: UPDATE EVERYTHING
| @since 0.5.0 [0.5.0]
*/
update: function(item){
return this.updateLabel().updateContainer(item).updatePin(item).updateCSV(item);
},
/*
| API :: UPDATE LABEL
| @since 0.5.8 [0.5.0]
*/
updateLabel: function(label){
if(this.container == this.label && this.options.selected.length > 0){
if(this.label.querySelector(".label-inner")){
this.label.removeChild(this.label.querySelector(".label-inner"));
}
if(this.label.querySelector(".label-count")){
this.label.removeChild(this.label.querySelector(".label-count"));
}
return this;
}
var c = this.con, len = this.options.selected.length, limit;
if(typeof label !== "string"){
if(c.disabled){
label = "disabled";
} else if(this.dropdown.querySelectorAll("*[data-key]").length == 0){
label = "empty" + (cHAS(this.select, "in-search")? "Search": "");
} else if(c.multiLimit <= len){
label = "limit";
} else if(!c.multiple && this.options.selected.length > 0){
label = this.options.selected[0].innerText;
} else if(typeof c.placeholder === "string"){
label = c.placeholder;
} else {
label = "placeholder" + (c.multiple && c.multiLimit < Infinity? "Multi": "");
}
}
// Set HTML
label = this._e(label, {":limit": c.multiLimit}, label);
label = '<span class="label-inner">' + label + '</span>',
limit = (c.multiShowLimit && c.multiLimit < Infinity);
if(c.multiple && c.multiShowCount){
label = '<span class="label-count">:c</span>' + label;
label = label.replace(":c", len + (limit? (" / " + c.multiLimit): ""));
}
this.label.innerHTML = label;
return this;
},
/*
| API :: UPDATE CONTAINER
| @since 0.5.0 [0.5.0]
*/
updateContainer: function(item){
if(!this.container || !this.con.multiContainer){
return this;
}
var s = "[data-group='" + item.group + "'][data-key='" + item.key + "']";
if(this.container.querySelector(s)){
if(!item.selected){
this.container.removeChild(this.container.querySelector(s));
}
return this;
}
// Create Item
if(item.selected){
var self = this, hndl = create("DIV", "select-handle");
hndl.innerText = item.value;
hndl.setAttribute("data-key", item.key);
hndl.setAttribute("data-group", item.group);
hndl.addEventListener("click", function(event){
event.preventDefault();
event.stopPropagation();
var key = this.getAttribute("data-key"), grp = this.getAttribute("data-group");
self.options.unselect.call(self.options, key, grp);
});
this.container.appendChild(hndl);
}
return this;
},
/*
| API :: UPDATE PIN POSITION
| @since 0.5.3 [0.5.0]
*/
updatePin: function(item){
var inner = this.dropdown.querySelector(".dropdown-inner ul"),
option = "li[data-key='" + item.key + "'][data-group='" + item.group + "']";
if(!this.con.multiPinSelected || !inner || this._query !== false){
return this;
}
// Create Item
option = this.dropdown.querySelector(option);
if(item.selected){
inner.insertBefore(option, inner.children[0]);
} else {
var grp = this.dropdown.querySelector("ul[data-group='" + item.group + "']"),
prev = this.options[item.index-1], found = false;
while(prev && prev.group == item.group){
if(found = grp.querySelector("li[data-key='" + prev.key + "']")){
break;
}
prev = this.options[prev.index-1];
}
if(found && found.nextElementSibling){
grp.insertBefore(option, found.nextElementSibling);
} else {
grp.appendChild(option);
}
}
return this;
},
/*
| API :: UPDATE CSV INPUT
| @since 0.5.0 [0.5.0]
*/
updateCSV: function(item){
if(!this.csvInput || !this.con.csvOutput){
return this;
}
for(var selected = [], l = this.options.selected.length, i = 0; i < l; i++){
selected.push(this.options.selected[i].value);
}
this.csvInput.value = selected.join(this.con.csvSeparator || ",");
return this;
},
/*
| PUBLIC :: OPEN DROPDOWN
| @since 0.5.0 [0.3.0]
*/
open: function(animate){
if(cHAS(this.select, "active") || cHAS(this.select, "idle") || this.con.disabled){
return false;
}
this.calc();
// Final Function
var final = function(){
cADD(self.select, "active");
cREM(self.select, "idle");
this.dropdown.style.height = "auto";
this.dropdown.style.overflow = "visible";
this.label.removeAttribute("style");
if(this.con.search && this.con.searchFocus){
this.dropdown.querySelector("input").focus();
}
this.trigger.call(this, "open");
}, self = this, e = this.dropdown.style;
// Open
if(animate !== false){
this.label.style.zIndex = 25;
this.dropdown.style.cssText += "height:0;display:block;overflow:hidden;";
cADD(self.select, "idle");
(function animate(){
var h = parseInt(e.height, 10), m = parseInt(e.maxHeight, 10);
if(h >= m){
return final.call(self);
}
e.height = ((h+50 > m)? m: h+50) + "px";
setTimeout(animate, 20);
})();
} else {
e.cssText = "height:" + e.maxHeight + ";display:block;overflow:hidden;";
final.call(this);
}
return this;
},
/*
| PUBLIC :: CLOSE DROPDOWN
| @since 0.5.0 [0.3.0]
*/
close: function(animate){
if(!cHAS(this.select, "active") || cHAS(this.select, "idle")){
return false;
}
var final = function(){
cREM(this.select, "active");
cREM(this.select, "idle");
this.dropdown.removeAttribute("style");
this.dropdown.querySelector(".dropdown-inner").removeAttribute("style");
this.trigger.call(this, "close");
}, self = this, e = this.dropdown;
// Close
if(animate !== false){
cADD(this.select, "idle");
this.dropdown.style.overflow = "hidden";
(function animate(){
if((parseInt(e.offsetHeight, 10)-50) <= 0){
return final.call(self);
}
e.style.height = (parseInt(e.offsetHeight, 10)-50) + "px";
setTimeout(animate, 20);
})();
} else {
final.call(this);
}
return this;
},
/*
| PUBLIC :: TOGGLE DROPDOWN
| @since 0.5.0 [0.3.0]
*/
toggle: function(animate){
if(cHAS(this.select, "active")){
return this.close(animate);
}
return !cHAS(this.select, "idle")? this.open(animate): this;
},
/*
| PUBLIC :: REMOVE SELECT
| @since 0.5.3 [0.3.0]
*/
remove: function(){
this.e.removeAttribute("data-tail-select");
if(this.e.hasAttribute("data-select-hidden")){
if(this.e.getAttribute("data-select-hidden") == "0"){
this.e.style.removeProperty("display");
}
this.e.removeAttribute("data-select-hidden");
}
Array.prototype.map.call(this.e.querySelectorAll("[data-select-option='add']"), function(item){
item.parentElement.removeChild(item);
})
Array.prototype.map.call(this.e.querySelectorAll("[data-select-optgroup='add']"), function(item){
item.parentElement.removeChild(item);
})
this.e.name = (this.csvInput.hasAttribute("name"))? this.csvInput.name: this.e.name;
if(this.select.parentElement){
this.select.parentElement.removeChild(this.select);
}
if(this.container){
var handles = this.container.querySelectorAll(".select-handle");
for(var l = handles.length, i = 0; i < l; i++){
this.container.removeChild(handles[i]);
}
}
return this;
},
/*
| PUBLIC :: RELOAD SELECT
| @since 0.5.0 [0.3.0]
*/
reload: function(){
return this.remove().init();
},
/*
| PUBLIC :: GET|SET CONFIG
| @since 0.5.15 [0.4.0]
*/
config: function(key, value, rebuild){
if(key instanceof Object){
for(var k in key){ this.config(k, key[k], false); }
return this.reload()? this.con: this.con;
}
if(key === void 0){
return this.con;
} else if(!(key in this.con)){
return false;
}
// Set | Return
if(value === void 0){
return this.con[key];
}
this.con[key] = value;
if(rebuild !== false){
this.reload();
}
return this;
},
enable: function(update){
cREM(this.select, "disabled");
this.e.disabled = false;
this.con.disabled = false;
return (update === false)? this: this.reload();
},
disable: function(update){
cADD(this.select, "disabled");
this.e.disabled = true;
this.con.disabled = true;
return (update === false)? this: this.reload();
},
/*
| PUBLIC :: CUSTOM EVENT LISTENER
| @since 0.5.0 [0.4.0]
*/
on: function(event, callback, args){
if(["open", "close", "change"].indexOf(event) < 0 || typeof callback !== "function"){
return false;
}
if(!(event in this.events)){
this.events[event] = [];
}
this.events[event].push({cb: callback, args: (args instanceof Array)? args: []});
return this;
},
/*
| PUBLIC :: VALUE
| @since 0.5.13 [0.5.13]
*/
value: function(){
if(this.options.selected.length == 0){
return null;
}
if(this.con.multiple){
return this.options.selected.map(function(opt){
return opt.value;
});
}
return this.options.selected[0].value;
}
};
/*
| OPTIONS CONSTRUCTOR
| @since 0.5.12 [0.3.0]
*/
options = select.options = function(select, parent){
if(!(this instanceof options)){
return new options(select, parent);
}
this.self = parent;
this.element = select;
this.length = 0;
this.selected = [];
this.disabled = [];
this.items = {"#": {}};
this.groups = {};
return this;
}
/*
| TAIL.OPTIONS HANDLER
*/
options.prototype = {
/*
| INTERNAL :: REPLACE TYPOs
| @since 0.5.0 [0.3.0]
*/
_r: function(state){
return state.replace("disabled", "disable").replace("enabled", "enable")
.replace("selected", "select").replace("unselected", "unselect");
},
/*
| GET AN EXISTING OPTION
| @since 0.5.7 [0.3.0]
*/
get: function(key, grp){
var g = "getAttribute";
if(typeof key === "object" && key.key && key.group){
grp = key.group || grp;
key = key.key;
} else if(key instanceof Element){
if(key.tagName == "OPTION"){
grp = key.parentElement.label || "#";
key = key.value || key.innerText;
} else if(key.hasAttribute("data-key")){
grp = key[g]("data-group") || key.parentElement[g]("data-group") || "#";
key = key[g]("data-key");
}
} else if(typeof key !== "string"){
return false;
}
key = (/^[0-9]+$/.test(key))? "_" + key: key;
return (grp in this.items)? this.items[grp][key]: false;
},
/*
| SET AN EXISTING OPTION
| @since 0.5.15 [0.3.0]
*/
set: function(opt, rebuild){
var key = opt.value || opt.innerText, grp = opt.parentElement.label || "#";
if(!(grp in this.items)){
this.items[grp] = {};
this.groups[grp] = opt.parentElement;
}
if(key in this.items[grp]){
return false;
}
var id = (/^[0-9]+$/.test(key))? "_" + key: key;
// Validate Selection
var con = this.self.con;
if(con.multiple && this.selected.length >= con.multiLimit){
opt.selected = false;
}
if(opt.selected && con.deselect && (!opt.hasAttribute("selected") || con.multiLimit == 0)){
opt.selected = false;
opt.parentElement.selectedIndex = -1;
}
// Sanitize Description
if(opt.hasAttribute("data-description")){
var span = create("SPAN");
span.innerHTML = opt.getAttribute("data-description");
opt.setAttribute("data-description", span.innerHTML);
}
// Add Item
this.items[grp][id] = {
key: key,
value: opt.text,
description: opt.getAttribute("data-description") || null,
group: grp,
option: opt,
optgroup: (grp != "#")? this.groups[grp]: undefined,
selected: opt.selected,
disabled: opt.disabled,
hidden: opt.hidden || false
};
this.length++;
if(opt.selected){ this.select(this.items[grp][id]); }
if(opt.disabled){ this.disable(this.items[grp][id]); }
return (rebuild)? this.self.callback(this.items[grp][key], "rebuild"): true;
},
/*
| CREATE A NEW OPTION
| @since 0.5.13 [0.3.0]
*/
add: function(key, value, group, selected, disabled, description, rebuild){
if(key instanceof Object){
for(var k in key){
this.add(key[k].key || k, key[k].value, key[k].group, key[k].selected, key[k].disabled, key[k].description, false);
}
return this.self.query();
}
if(this.get(key, group)){
return false;
}
// Check Group
group = (typeof group === "string")? group: "#";
if(group !== "#" && !(group in this.groups)){
var optgroup = create("OPTGROUP");
optgroup.label = group;
optgroup.setAttribute("data-select-optgroup", "add");
this.element.appendChild(optgroup);
this.items[group] = {};
this.groups[group] = optgroup;
}
// Validate Selection
if(this.self.con.multiple && this.selected.length >= this.self.con.multiLimit){
selected = false;
}
disabled = !!disabled;
// Create Option
var option = d.createElement("OPTION");
option.value = key;
option.selected = selected;
option.disabled = disabled;
option.innerText = value;
option.setAttribute("data-select-option", "add");
if(description && description.length > 0){
option.setAttribute("data-description", description);
}
// Add Option and Return
((group == "#")? this.element: this.groups[group]).appendChild(option);
return this.set(option, rebuild);
},
/*
| MOVE AN EXISTING OPTION
| @since 0.5.0 [0.5.0]
*/
move: function(item, group, new_group, rebuild){
if(!(item = this.get(item, group))){ return false; }
// Create Group
if(new_group !== "#" && !(new_group in this.groups)){
var optgroup = create("OPTGROUP");
optgroup.label = new_group;
this.element.appendChild(optgroup);
this.items[new_group] = {};
this.groups[new_group] = optgroup;
this.groups[new_group].appendChild(item.option);
}
// Move To Group
delete this.items[item.group][item.key];
item.group = new_group;
item.optgroup = this.groups[new_group] || undefined;
this.items[new_group][item.key] = item;
return (rebuild)? this.self.query(): true;
},
/*
| REMOVE AN EXISTING OPTION
| @since 0.5.7 [0.3.0]
*/
remove: function(item, group, rebuild){
if(!(item = this.get(item, group))){ return false; }
if(item.selected){ this.unselect(item); }
if(item.disabled){ this.enable(item); }
// Remove Data
item.option.parentElement.removeChild(item.option);
var id = (/^[0-9]+$/.test(item.key))? "_" + item.key: item.key;
delete this.items[item.group][id];
this.length--;
// Remove Optgroup
if(Object.keys(this.items[item.group]).length === 0){
delete this.items[item.group];
delete this.groups[item.group];
}
return (rebuild)? this.self.query(): true;
},
/*
| CHECK AN EXISTING OPTION
| @since 0.5.0 [0.3.0]
*/
is: function(state, key, group){
var state = this._r(state), item = this.get(key, group);
if(!item || ["select", "unselect", "disable", "enable"].indexOf(state) < 0){
return null;
}
if(state == "disable" || state == "enable"){
return (state == "disable")? item.disabled: !item.disabled;
} else if(state == "select" || state == "unselect"){
return (state == "select")? item.selected: !item.selected;
}
return false;
},
/*
| INTERACT WITH AN OPTION
| @since 0.5.3 [0.3.0]
*/
handle: function(state, key, group, _force){
var item = this.get(key, group), state = this._r(state);
if(!item || ["select", "unselect", "disable", "enable"].indexOf(state) < 0){
return null;
}
// Disable || Enable
if(state == "disable" || state == "enable"){
if(!(item.option in this.disabled) && state == "disable"){
this.disabled.push(item.option);
} else if((item.option in this.disabled) && state == "enable"){
this.disabled.splice(this.disabled.indexOf(item.option), 1);
}
item.disabled = (state == "disable");
item.option.disabled = (state == "disable");
return this.self.callback.call(this.self, item, state);
}
// Select || Unselect
var dis = (cHAS(this.self.select, "disabled") || item.disabled || item.option.disabled),
lmt = (this.self.con.multiple && this.self.con.multiLimit <= this.selected.length),
sgl = (!this.self.con.multiple && this.selected.indexOf(item.option) > 0),
del = (this.self.con.multiLimit == 0 && this.self.con.deselect == true),
uns = (!this.self.con.multiple && !this.self.con.deselect && _force !== true);
if(state == "select"){
if(dis || lmt || del || sgl){
return false;
}
if(!this.self.con.multiple){
for(var i in this.selected){
this.unselect(this.selected[i], undefined, true);
}
}
if(this.selected.indexOf(item.option) < 0){
this.selected.push(item.option);
}
} else if(state == "unselect"){
if(dis || uns){
return false;
}
this.selected.splice(this.selected.indexOf(item.option), 1);
}
item.selected = (state == "select");
item.option.selected = (state == "select");
item.option[(state.length > 6? "remove": "set") + "Attribute"]("selected", "selected");
return this.self.callback.call(this.self, item, state, _force);
},
enable: function(key, group){
return this.handle("enable", key, group, false);
},
disable: function(key, group){
return this.handle("disable", key, group, false);
},
select: function(key, group){
return this.handle("select", key, group, false);
},
unselect: function(key, group, _force){
return this.handle("unselect", key, group, _force);
},
toggle: function(item, group){
if(!(item = this.get(item, group))){ return false; }
return this.handle((item.selected? "unselect": "select"), item, group, false);
},
/*
| INVERT CURRENT <STATE>
| @since 0.5.15 [0.3.0]
*/
invert: function(state){
state = this._r(state);
if(["enable", "disable"].indexOf(state) >= 0){
var invert = this.disabled, action = (state == "enable")? "disable": "enable";
} else if(["select", "unselect"].indexOf(state) >= 0){
var invert = this.selected, action = (state == "select")? "unselect": "select";
}
var convert = Array.prototype.filter.call(this, function(element){
return !(element in invert);
}), self = this;
// Loop
[].concat(invert).forEach(function(item){
self.handle.call(self, action, item);
});
[].concat(convert).forEach(function(item){
self.handle.call(self, state, item);
});
return true;
},
/*
| SET <STATE> ON ALL OPTIONs
| @since 0.5.0 [0.5.0]
*/
all: function(state, group){
var self = this, list = this;
if(group in this.items){
list = Object.keys(this.items[group]);
} else if(["unselect", "enable"].indexOf(state) >= 0){
list = [].concat((state == "unselect")? this.selected: this.disabled);
}
Array.prototype.forEach.call(list, function(item){
self.handle.call(self, state, item, group, false);
});
return true;
},
/*
| SET <STATE> FOR A BUNCH OF OPTIONs
| @since 0.5.4 [0.5.3]
*/
walk: function(state, items, args){
if(items instanceof Array || items.length){
for(var l = items.length, i = 0; i < l; i++){
this.handle.apply(this, [state, items[i], null].concat(args));
}
} else if(items instanceof Object){
var self = this;
if(items.forEach){
items.forEach(function(value){
self.handle.apply(self, [state, value, null].concat(args));
});
} else {
for(var key in items){
if(typeof items[key] !== "string" && typeof items[key] !== "number" && !(items[key] instanceof Element)){
continue;
}
this.handle.apply(this, [state, items[key], (key in this.items? key: null)]).concat(args);
}
}
}
return this;
},
/*
| APPLY LINGUSTIC RULES
| @since 0.5.13 [0.5.13]
*/
applyLinguisticRules: function(search, casesensitive){
var rules = this.self.con.linguisticRules, values = [];
// Prepare Rules
Object.keys(rules).forEach(function(key){
values.push("(" + key + "|[" + rules[key] + "])");
});
if(casesensitive){
values = values.concat(values.map(function(s){ return s.toUpperCase(); }));
}
return search.replace(new RegExp(values.join("|"), (casesensitive)? "g": "ig"), function(m){
return values[[].indexOf.call(arguments, m, 1) - 1];
});
},
/*
| FIND SOME OPTIONs - ARRAY EDITION
| @since 0.5.15 [0.3.0]
*/
find: function(search, config){
var self = this, matches, has = {};
// Get Config
if(!config){
config = this.self.con.searchConfig;
}
// Config Callback
if(typeof config === "function"){
matches = config.bind(this, search);
}
// Config Handler
else {
config = (config instanceof Array)? config: [config];
config.forEach(function(c){
if(typeof c === "string"){ has[c] = true; }
});
has.any = (!has.any)? has.attributes && has.value: has.any;
// Cleanup & Prepare
if(!has.regex || has.text){
search = search.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
if(!has.exactglyphes){
search = this.self.options.applyLinguisticRules(search, has.case);
}
if(has.word){
search = '\\b' + search + '\\b';
}
// Search
var regex = new RegExp(search, (!has.case)? "mi": "m"),
sfunc = function(opt){ return regex.test(opt.text || opt.value); };
// Handle
if(has.any){
matches = function(opt){ return sfunc(opt) || [].some.call(opt.attributes, sfunc); };
} else if(has.attributes){
matches = function(opt){ return [].some.call(opt.attributes, sfunc); };
} else {
matches = sfunc;
}
if(!this.self.con.searchDisabled){
var temp = matches;
matches = function(opt){ return !opt.disabled && temp(opt); };
}
}
// Hammer Time
return [].filter.call(this.self.e.options, matches).map(function(opt){
return opt.hidden? false: self.get(opt)
});
},
/*
| FIND SOME OPTIONs - WALKER EDITION
| @since 0.5.5 [0.3.0]
*/
finder: function(search, config){
if(this._finderLoop === undefined){
this._finderLoop = this.find(search, config);
}
var item;
while((item = this._finderLoop.shift()) !== undefined){
return item;
}
delete this._finderLoop;
return false;
},
/*
| NEW OPTIONS WALKER
| @since 0.5.15 [0.4.0]
*/
walker: function(orderi, orderg){
if(typeof this._inLoop !== "undefined" && this._inLoop){
if(this._inItems.length > 0){
do {
var temp = this.items[this._inGroup][this._inItems.shift()];
} while(temp.hidden === true);
return temp;
}
// Sort Items
if(this._inGroups.length > 0){
while(this._inGroups.length > 0){
var group = this._inGroups.shift();
if(!(group in this.items)){
return false;
}
var keys = Object.keys(this.items[group]);
if(keys.length > 0){
break;
}
}
if(orderi == "ASC"){
keys.sort();
} else if(orderi == "DESC"){
keys.sort().reverse();
} else if(typeof orderi === "function"){
keys = orderi.call(this, keys);
}
this._inItems = keys;
this._inGroup = group;
return this.walker(null, null);
}
// Delete and Exit
delete this._inLoop;
delete this._inItems;
delete this._inGroup;
delete this._inGroups;
return false;
}
// Sort Groups
var groups = Object.keys(this.groups) || [];
if(orderg == "ASC"){
groups.sort();
} else if(orderg == "DESC"){
groups.sort().reverse();
} else if(typeof orderg === "function"){
groups = orderg.call(this, groups);
}
groups.unshift("#");
// Init Loop
this._inLoop = true;
this._inItems = [];
this._inGroups = groups;
return this.walker(orderi, null);
}
};
// Return
return select;
}));