{"version":3,"names":[],"mappings":"","sources":["tail.select.js"],"sourcesContent":["/*\r\n | tail.select - Another solution to make (multiple) select fields beautiful, written in vanillaJS!\r\n | @author SamBrishes@pytesNET\r\n | @version 0.4.2 - Beta\r\n | @website https://www.github.com/pytesNET/tail.select\r\n |\r\n | @license X11 / MIT License\r\n | @copyright Copyright © 2014 - 2018 SamBrishes, pytesNET \r\n */\r\n;(function(factory){\r\n if(typeof(define) == \"function\" && define.amd){\r\n define(factory);\r\n } else {\r\n if(typeof(window.tail) == \"undefined\"){\r\n window.tail = {};\r\n }\r\n window.tail.select = factory();\r\n\r\n // Assign to jQuery\r\n if(typeof(jQuery) != \"undefined\"){\r\n jQuery.fn.tailselect = function(options){\r\n var _r = [], instance;\r\n this.each(function(){\r\n if((instance = tail.select(this, options)) !== false){ _r.push(instance); }\r\n });\r\n return (_r.length === 1)? _r[0]: (_r.length === 0)? false: _r;\r\n }\r\n }\r\n\r\n // Assign to MooTools\r\n if(typeof(MooTools) !== \"undefined\"){\r\n Element.implement({\r\n tailselect: function(options){ return new tail.select(this, options); }\r\n });\r\n }\r\n }\r\n}(function(){\r\n \"use strict\";\r\n var w = window, d = window.document;\r\n\r\n /*\r\n | HELPER METHODs\r\n */\r\n var tail = {\r\n hasClass: function(el, name){\r\n return (new RegExp(\"(|\\s+)\" + name + \"(\\s+|)\")).test(el.className || \"\");\r\n },\r\n addClass: function(el, name){\r\n if(\"className\" in el && !(new RegExp(\"(|\\s+)\" + name + \"(\\s+|)\")).test(el.className)){\r\n el.className = (el.className.trim() + \" \" + name.trim()).trim();\r\n }\r\n return el;\r\n },\r\n removeClass: function(el, name){\r\n var regex = new RegExp(\"(|\\s+)(\" + name + \")(\\s+|)\");\r\n if(\"className\" in el && regex.test(el.className)){\r\n el.className = (el.className.replace(regex, \"$1$3\")).trim();\r\n }\r\n return el;\r\n },\r\n trigger: function(el, event, opt){\r\n if(CustomEvent && CustomEvent.name){\r\n var ev = new CustomEvent(event, opt);\r\n } else {\r\n var ev = d.createEvent(\"CustomEvent\");\r\n ev.initCustomEvent(event, !!opt.bubbles, !!opt.cancelable, opt.detail);\r\n }\r\n return el.dispatchEvent(ev);\r\n },\r\n clone: function(object, replace){\r\n replace = (typeof(replace) == \"object\")? replace: {};\r\n if(Object.assign){\r\n return Object.assign({}, object, replace);\r\n }\r\n var clone = object.constructor();\r\n for(var key in object){\r\n clone[key] = (key in replace)? replace[key]: object[key];\r\n }\r\n return clone;\r\n },\r\n animate: function(element, callback, delay, prevent){\r\n if(element.hasAttribute(\"data-tail-animation\")){\r\n if(!prevent){\r\n return;\r\n }\r\n clearInterval(tail.animation[element.getAttribute(\"data-tail-animation\")]);\r\n }\r\n element.setAttribute(\"data-tail-animation\", \"tail-\" + ++this.animationCounter);\r\n\r\n (function(e, func, delay){\r\n var tail = this;\r\n this.animation[\"tail-\" + this.animationCounter] = setInterval(function(){\r\n var animationID = e.getAttribute(\"data-tail-animation\");\r\n if(func.call(e, e) === false){\r\n clearInterval(tail.animation[animationID]);\r\n if(e.parentElement){\r\n e.removeAttribute(\"data-tail-animation\");\r\n }\r\n }\r\n }, delay);\r\n }).call(this, element, callback, delay);\r\n },\r\n animation: {},\r\n animationCounter: 0\r\n };\r\n\r\n /*\r\n | SELECT CONSTRUCTOR\r\n | @since 0.3.0\r\n | @update 0.4.0\r\n */\r\n var tailSelect = function(element, config){\r\n if(typeof(element) == \"string\"){\r\n element = d.querySelectorAll(element);\r\n }\r\n if(element instanceof NodeList || element instanceof HTMLCollection || element instanceof Array){\r\n for(var _r = [], l = element.length, i = 0; i < l; i++){\r\n _r.push(new tailSelect(element[i], config));\r\n }\r\n return (_r.length === 1)? _r[0]: ((_r.length === 0)? false: _r);\r\n }\r\n if(!(element.tagName && element.tagName == \"SELECT\")){\r\n return false;\r\n }\r\n if(typeof(this) == \"undefined\" || !this.init){\r\n return new tailSelect(element, config);\r\n }\r\n\r\n // Check Element\r\n if(tailSelect.inst[element.getAttribute(\"data-tail-select\")]){\r\n return tailSelect.inst[element.getAttribute(\"data-tail-select\")];\r\n }\r\n\r\n // Get Element Options\r\n config = (typeof(config) == \"object\")? config: {};\r\n if(element.hasAttribute(\"multiple\")){\r\n config.multiple = element.multiple;\r\n }\r\n if(element.hasAttribute(\"placeholder\")){\r\n config.placeholder = element.placeholder;\r\n } else if(element.hasAttribute(\"data-placeholder\")){\r\n config.placeholder = element.getAttribute(\"data-placeholder\");\r\n }\r\n if(config.width && config.width === \"auto\"){\r\n config.width = element.offsetWidth + 30;\r\n }\r\n\r\n // Init Instance\r\n this.e = element;\r\n this.id = ++tailSelect.count;\r\n this.con = tail.clone(tailSelect.defaults, config);\r\n tailSelect.inst[\"tail-\" + this.id] = this;\r\n return this.init();\r\n };\r\n tailSelect.version = \"0.4.2\";\r\n tailSelect.status = \"beta\";\r\n tailSelect.count = 0;\r\n tailSelect.inst = {};\r\n\r\n /*\r\n | OPTIONS CONSTRUCTOR\r\n | @since 0.3.0\r\n | @update 0.4.0\r\n */\r\n var tailOptions = function(select, parent){\r\n if(typeof(this) == \"undefined\" || !this.init){\r\n return new tailOptions(select, parent);\r\n }\r\n this.self = parent;\r\n this.select = select;\r\n return this;\r\n }\r\n\r\n /*\r\n | STORAGE :: DEFAULT OPTIONS\r\n */\r\n tailSelect.defaults = {\r\n width: null,\r\n height: 350,\r\n classNames: null,\r\n placeholder: null,\r\n deselect: false,\r\n animate: true,\r\n openAbove: null,\r\n stayOpen: false,\r\n startOpen: false,\r\n multiple: false,\r\n multiLimit: -1,\r\n multiShowCount: true,\r\n multiContainer: false,\r\n multiSelectAll: false, // NEW IN **0.4.0**\r\n multiSelectGroup: true, // NEW IN **0.4.0**\r\n descriptions: false,\r\n items: {},\r\n sortItems: false,\r\n sortGroups: false,\r\n search: false,\r\n searchFocus: true,\r\n searchMarked: true,\r\n csvOutput: false,\r\n csvSeparator: \",\",\r\n hideSelect: true,\r\n hideSelected: false,\r\n hideDisabled: false,\r\n bindSourceSelect: false,\r\n cbLoopItem: undefined, // NEW IN **0.4.0**\r\n cbLoopGroup: undefined // NEW IN **0.4.0**\r\n };\r\n\r\n /*\r\n | STORAGE :: STRINGS\r\n */\r\n tailSelect.strings = {\r\n all: \"All\",\r\n none: \"None\",\r\n actionAll: \"Select All\",\r\n actionNone: \"Unselect All\",\r\n empty: \"No Options available\",\r\n emptySearch: \"No Options found\",\r\n limit: \"You can't select more Options\",\r\n placeholder: \"Select an Option...\",\r\n placeholderMulti: \"Select up to :limit Options...\",\r\n search: \"Type in to search...\"\r\n };\r\n var __ = function(key){\r\n return (key in tailSelect.strings)? tailSelect.strings[key]: key;\r\n };\r\n\r\n /*\r\n | TAIL.SELECT HANDLER\r\n */\r\n tailSelect.prototype = {\r\n e: null, // The '\r\n + '
'\r\n + '';\r\n this.select = d.createElement(\"DIV\");\r\n this.select.innerHTML = tailHTML;\r\n this.select.className = classes.join(\" \");\r\n if(!isNaN(parseInt(this.con.width, 10))){\r\n this.select.style.width = parseInt(this.con.width, 10) + \"px\";\r\n }\r\n\r\n // Assign Label\r\n this.label = this.select.querySelector(\".tail-select-label\");\r\n this.label.addEventListener(\"click\", function(event){\r\n self.toggle.call(self);\r\n });\r\n if(!this.con.multiple || (this.con.multiple && !this.con.multiShowCount)){\r\n this.label.removeChild(this.label.querySelector(\".tail-label-count\"));\r\n }\r\n\r\n // Assign Dropdown\r\n this.dropdown = this.select.querySelector(\".tail-select-dropdown\");\r\n if(!isNaN(parseInt(this.con.width, 10))){\r\n this.dropdown.style.width = parseInt(this.con.width, 10) + \"px\";\r\n }\r\n if(!isNaN(parseInt(this.con.height, 10))){\r\n this.dropdown.style.maxHeight = parseInt(this.con.height, 10) + \"px\";\r\n }\r\n\r\n // Assign Search\r\n this.search = this.dropdown.querySelector(\".tail-dropdown-search\");\r\n this.search.querySelector(\"input\").setAttribute(\"placeholder\", __(\"search\"));\r\n this.search.querySelector(\"input\").addEventListener(\"input\", function(event){\r\n tail[(this.value.length > 2? \"add\": \"remove\") + \"Class\"](self.select, \"in-search\");\r\n self.build.call(self, (this.value.length > 2)? this.value: undefined,\r\n self.con.cbLoopItem, self.con.cbLoopGroup);\r\n });\r\n if(!this.con.search){\r\n this.dropdown.removeChild(this.search);\r\n }\r\n\r\n // Assign CSV Input\r\n this.csvInput = this.select.querySelector(\"input[type='hidden']\");\r\n this.csvInput.name = this.e.getAttribute(\"name\") || this.id;\r\n if(!this.csvInput){\r\n this.select.removeChild(this.csvInput);\r\n }\r\n\r\n // Prepare Container\r\n if(this.con.multiple && d.querySelector(this.con.multiContainer)){\r\n this.container = d.querySelector(this.con.multiContainer);\r\n this.container.className += \" tail-select-container\";\r\n }\r\n\r\n // Prepare Options\r\n this.options = new tailOptions(this.e, self).init();\r\n for(var key in this.con.items){\r\n if(typeof(this.con.items[key]) == \"string\"){\r\n this.con.items[key] = {value: this.con.items[key]};\r\n }\r\n this.options.add(key, this.con.items[key].value, this.con.items[key].group,\r\n this.con.items[key].selected, this.con.items[key].disabled,\r\n this.con.items[key].description);\r\n }\r\n this.build(null, this.con.cbLoopItem, this.con.cbLoopGroup);\r\n this.bind(false);\r\n\r\n // Append and Return\r\n this.e.parentElement.insertBefore(this.select, this.e);\r\n this.e.setAttribute(\"data-tail-select\", \"tail-\" + this.id);\r\n if(this.con.hideSelect){\r\n this.e.style.display = \"none\";\r\n }\r\n if(self.con.startOpen){\r\n this.open();\r\n }\r\n return this;\r\n },\r\n\r\n /*\r\n | INTERNAL :: BUILD DROPDOWN LIST\r\n | @since 0.3.0\r\n | @update 0.4.0\r\n */\r\n build: function(search, cb_item, cb_group){\r\n search = (typeof(search) == \"string\")? search: false;\r\n\r\n // Get Root\r\n var root = d.createElement(\"UL\");\r\n root.className = \"tail-dropdown\";\r\n root.setAttribute(\"data-group\", \"#\");\r\n\r\n // Walk\r\n var self = this, item, ul = root, li, a1, a2, func = (search)? \"finder\": \"walker\",\r\n args = (search)? [search]: [this.con.sortItems, this.con.sortGroups];\r\n while(item = this.options[func].apply(this.options, args)){\r\n if(item.group != ul.getAttribute(\"data-group\")){\r\n ul = (cb_group || this.createGroup).call(this, item.group, search);\r\n ul.setAttribute(\"data-group\", item.group);\r\n root.appendChild(ul);\r\n }\r\n\r\n // Create Item\r\n li = (cb_item || this.createItem).call(this, item, ul, search);\r\n li.setAttribute(\"data-key\", item.key);\r\n li.setAttribute(\"data-group\", item.group);\r\n li.addEventListener(\"click\", function(event){\r\n self.bind.call(self, event, this);\r\n });\r\n ul.appendChild(li);\r\n\r\n // Container\r\n if(item.selected){\r\n this.setContainer(item, \"select\");\r\n }\r\n }\r\n\r\n // Empty | Select All\r\n var count = root.querySelectorAll(\"*[data-key]\").length;\r\n if(count == 0){\r\n li = d.createElement(\"LI\");\r\n li.innerText = __(\"empty\");\r\n li.className = \"tail-dropdown-empty\";\r\n root.appendChild(li);\r\n } else if(this.con.multiple && this.con.multiLimit < 0 && this.con.multiSelectAll){\r\n a1 = d.createElement(\"BUTTON\"), a2 = d.createElement(\"BUTTON\");\r\n a1.innerText = __(\"actionAll\");\r\n a1.className = \"tail-all\";\r\n a1.addEventListener(\"click\", function(event){\r\n event.preventDefault();\r\n var items = this.parentElement.parentElement.querySelectorAll(\"*[data-key]\");\r\n for(var l = items.length, i = 0; i < l; i++){\r\n self.choose.call(self, \"select\", items[i].getAttribute(\"data-key\"), items[i].getAttribute(\"data-group\"));\r\n }\r\n })\r\n a2.innerText = __(\"actionNone\");\r\n a2.className = \"tail-none\";\r\n a2.addEventListener(\"click\", function(event){\r\n event.preventDefault();\r\n var items = this.parentElement.parentElement.querySelectorAll(\"*[data-key]\");\r\n for(var l = items.length, i = 0; i < l; i++){\r\n self.choose.call(self, \"unselect\", items[i].getAttribute(\"data-key\"), items[i].getAttribute(\"data-group\"));\r\n }\r\n })\r\n\r\n // Add Element\r\n li = d.createElement(\"LI\");\r\n li.className = \"tail-dropdown-action\";\r\n li.appendChild(a1);\r\n li.appendChild(a2);\r\n root.insertBefore(li, root.children[0]);\r\n }\r\n\r\n // Add and Return\r\n this.dropdown.querySelector(\".tail-dropdown-inner\").innerHTML = \"\";\r\n this.dropdown.querySelector(\".tail-dropdown-inner\").appendChild(root);\r\n this.setCSVInput().setCounter().setLabel();\r\n return this;\r\n },\r\n\r\n /*\r\n | INTERNAL :: EVENT LISTENER\r\n | @since 0.3.0\r\n | @update 0.4.0\r\n */\r\n bind: function(event, item){\r\n if(event !== false){\r\n if(!item.hasAttribute(\"data-key\")){\r\n return false;\r\n }\r\n var key = item.getAttribute(\"data-key\"), group = item.getAttribute(\"data-group\") || \"#\";\r\n\r\n // Select Option\r\n if(!this.choose(\"toggle\", key, group)){\r\n return false;\r\n }\r\n if(this.con.stayOpen || this.con.multiple){\r\n return true;\r\n }\r\n return this.close();\r\n }\r\n\r\n // Close\r\n var self = this;\r\n d.addEventListener(\"click\", function(ev){\r\n if(!tail.hasClass(self.select, \"active\") || tail.hasClass(self.select, \"idle\")){\r\n return false;\r\n }\r\n if(self.con.stayOpen){\r\n return false;\r\n }\r\n\r\n var targets = [self.e, self.select, self.container];\r\n for(var l = targets.length, i = 0; i < l; i++){\r\n if(targets[i] && (targets[i].contains(ev.target) || targets[i] == ev.target)){\r\n return false;\r\n }\r\n if(!ev.target.parentElement){\r\n return false;\r\n }\r\n }\r\n return self.close.call(self);\r\n });\r\n\r\n // Bind Source Select\r\n if(!this.con.bindSourceSelect){\r\n return true;\r\n }\r\n this.e.addEventListener(\"change\", function(event){\r\n var handle = function(options, selected){\r\n var i, l, o, item, compare = self.options.selected.slice(0);\r\n for(l = options.length, i = 0; i < l; i++){\r\n o = options[i];\r\n item = self.options.get(\r\n (o.value || o.innerText),\r\n (o.parentElement.tagName === \"OPTGROUP\")? o.parentElement.label: \"#\"\r\n );\r\n if(item == null){\r\n continue;\r\n }\r\n if(!self.options.is(\"selected\", item)){\r\n self.options.handle(\"select\", item)\r\n }\r\n if(compare.indexOf(o) >= 0){\r\n compare.splice(compare.indexOf(o), 1);\r\n }\r\n }\r\n for(i in compare){\r\n self.options.handle(\"unselect\", compare[i]);\r\n }\r\n };\r\n\r\n if(!this.multiple && this.selectedIndex){\r\n self.choose(\"select\", this.options[this.selectedIndex]);\r\n } else if(this.selectedOptions){\r\n handle(this.selectedOptions);\r\n } else {\r\n var selected = [];\r\n for(var l = this.options.length, i = 0; i < l; i++){\r\n if(this.options[i].selected){\r\n selected.push(this.options[i])\r\n }\r\n }\r\n handle(selected);\r\n }\r\n });\r\n return true;\r\n },\r\n\r\n /*\r\n | INTERNAL :: INTERNAL CALLBACK\r\n | @since 0.3.0\r\n | @update 0.4.0\r\n */\r\n callback: function(item, state){\r\n var self = this;\r\n if(state == \"rebuild\"){\r\n return this.build();\r\n }\r\n\r\n // Set Element-Item States\r\n var element = this.dropdown.querySelector(\"[data-key='\" + item.key + \"'][data-group='\" + item.group + \"']\");\r\n if(element){\r\n if(state == \"select\"){\r\n tail.addClass(element, \"selected\");\r\n } else if(state == \"unselect\"){\r\n tail.removeClass(element, \"selected\");\r\n } else if(state == \"disable\"){\r\n tail.addClass(element, \"disabled\");\r\n } else if(state == \"enable\"){\r\n tail.removeClass(element, \"disabled\");\r\n }\r\n }\r\n\r\n // Handle\r\n this.setLabel().setCounter().setContainer(item, state).setCSVInput();\r\n this.trigger(\"change\", item, state);\r\n return true;\r\n },\r\n\r\n /*\r\n | INTERNAL :: TRIGGER EVENT HANDLER\r\n | @since 0.4.0\r\n */\r\n trigger: function(event){\r\n tail.trigger(this.select, \"tail.select::\" + event, {\r\n bubbles: false, cancelable: true, detail: {args: arguments, self: this}\r\n });\r\n for(var l = (this.events[event] || []).length, i = 0; i < l; i++){\r\n this.events[event][i].cb.apply(this, (function(args, a, b){\r\n for(var l = a.length, i = 0; i < l; ++i){\r\n args[i-1] = a[i];\r\n }\r\n args[i] = b;\r\n return args;\r\n }(new Array(arguments.length), arguments, this.events[event][i].args)));\r\n }\r\n },\r\n\r\n /*\r\n | DEFAULT :: CALLBACK -> CREATE GROUP\r\n | @since 0.4.0\r\n */\r\n createGroup: function(group, search){\r\n var ul = d.createElement(\"UL\"), self = this;\r\n ul.className = \"tail-dropdown-optgroup\";\r\n ul.innerHTML = '
  • ' + group + '
  • ';\r\n if(this.con.multiple && this.con.multiLimit < 0 && this.con.multiSelectGroup){\r\n var a1 = d.createElement(\"BUTTON\"), a2 = d.createElement(\"BUTTON\");\r\n a1.innerText = __(\"none\");\r\n a1.className = \"tail-none\";\r\n a1.addEventListener(\"click\", function(event){\r\n event.preventDefault();\r\n var items = this.parentElement.parentElement.querySelectorAll(\"*[data-key]\");\r\n for(var l = items.length, i = 0; i < l; i++){\r\n self.choose.call(self, \"unselect\", items[i].getAttribute(\"data-key\"), items[i].getAttribute(\"data-group\"));\r\n }\r\n });\r\n a2.innerText = __(\"all\");\r\n a2.className = \"tail-all\";\r\n a2.addEventListener(\"click\", function(event){\r\n event.preventDefault();\r\n var items = this.parentElement.parentElement.querySelectorAll(\"*[data-key]\");\r\n for(var l = items.length, i = 0; i < l; i++){\r\n self.choose.call(self, \"select\", items[i].getAttribute(\"data-key\"), items[i].getAttribute(\"data-group\"));\r\n }\r\n });\r\n ul.children[0].appendChild(a1);\r\n ul.children[0].appendChild(a2);\r\n }\r\n return ul;\r\n },\r\n\r\n /*\r\n | DEFAULT :: CALLBACK -> CREATE ITEM\r\n | @since 0.4.0\r\n */\r\n createItem: function(item, optgroup, search){\r\n var li = d.createElement(\"LI\");\r\n li.className = \"tail-dropdown-option\" + ((item.selected)? \" selected\": \"\") + ((item.disabled)? \" disabled\": \"\");\r\n\r\n // Inner Text\r\n if(search && search.length > 0 && this.con.searchMarked){\r\n li.innerHTML = item.value.replace(new RegExp(\"(\" + search + \")\", \"i\"), \"$1\");\r\n } else {\r\n li.innerText = item.value;\r\n }\r\n\r\n // Inner Description\r\n if(this.con.descriptions && item.description){\r\n li.innerHTML += '' + item.description + '';\r\n }\r\n return li;\r\n },\r\n\r\n /*\r\n | PUBLIC :: SET / UPDATE LABEL\r\n | @since 0.3.0\r\n | @update 0.4.0\r\n |\r\n | @param multi The string or translation key (tailSelect.strings)\r\n | NULL / UNDEFINED to update the label automatically.\r\n */\r\n setLabel: function(string){\r\n if(typeof(string) != \"string\"){\r\n if(this.dropdown.querySelectorAll(\"*[data-key]\").length == 0){\r\n string = \"empty\" + (tail.hasClass(this.select, \"in-search\")? \"Search\": \"\");\r\n } else if(this.con.multiLimit >= 0 && this.con.multiLimit <= this.options.selected.length){\r\n string = \"limit\";\r\n } else if(this.con.multiple){\r\n if(typeof(this.con.placeholder) == \"string\" && this.con.placeholder.length > 0){\r\n string = this.con.placeholder;\r\n } else {\r\n string = \"placeholder\" + (this.con.multiple && this.con.multiLimit >= 0? \"Multi\": \"\");\r\n }\r\n } else if(this.options.selected.length == 0){\r\n string = \"placeholder\";\r\n } else {\r\n string = this.options.selected[0].innerText;\r\n }\r\n }\r\n string = __(string).replace(\":limit\", this.con.multiLimit);\r\n this.label.querySelector(\".tail-label-inner\").innerText = string;\r\n return this;\r\n },\r\n\r\n /*\r\n | PUBLIC :: SET / UPDATE COUNTER\r\n | @since 0.3.0\r\n | @update 0.4.0\r\n */\r\n setCounter: function(count){\r\n if(this.label.querySelector(\".tail-label-count\")){\r\n count = (count == undefined)? (this.options.selected || []).length: count;\r\n this.label.querySelector(\".tail-label-count\").innerText = count;\r\n }\r\n return this;\r\n },\r\n\r\n /*\r\n | PUBLIC :: SET / UPDATE CONTAINER\r\n | @since 0.3.0\r\n | @update 0.4.1\r\n */\r\n setContainer: function(item, state){\r\n if(this.container){\r\n var self = this, hndl = d.createElement(\"DIV\"), selector;\r\n if(state == \"select\"){\r\n hndl.innerText = item.value;\r\n hndl.className = \"tail-select-handle\";\r\n hndl.setAttribute(\"data-key\", item.key);\r\n hndl.setAttribute(\"data-group\", item.group);\r\n hndl.addEventListener(\"click\", function(event){\r\n event.preventDefault();\r\n self.choose.call(self, \"unselect\", this.getAttribute(\"data-key\"),\r\n this.getAttribute(\"data-group\"));\r\n });\r\n this.container.appendChild(hndl);\r\n } else {\r\n selector = \"[data-group='\" + item.group + \"'][data-key='\" + item.key + \"']\";\r\n if(hndl = this.container.querySelector(selector)){\r\n hndl.parentElement.removeChild(hndl);\r\n }\r\n }\r\n }\r\n return this;\r\n },\r\n\r\n /*\r\n | PUBLIC :: SET / UPDATE CSV INPUT FIELD\r\n | @since 0.4.0\r\n */\r\n setCSVInput: function(){\r\n if(this.csvInput && this.con.csvOutput && [\"select\", \"unselect\"].indexOf(state) >= 0){\r\n var selected = [];\r\n for(var l = this.options.selected.length, i = 0; i < l; i++){\r\n selected.push(this.options.selected[i].value);\r\n }\r\n this.csvInput.value = selected.join(this.con.csvSeparator || \",\");\r\n }\r\n return this;\r\n },\r\n\r\n /*\r\n | PUBLIC :: CHOOSE AN OPTION\r\n | @since 0.3.0\r\n | @update 0.4.0\r\n |\r\n | @param string The choosed state \"select\", \"unselect\" or \"toggle\"\r\n | \"disable\" or \"enable\"\r\n | @param multi \r\n | or Use a list of touples [(key, group), (key, group)], wait this is the\r\n | wrong language: Use an Array with (key[, group]) Arrays.\r\n | @param multi \r\n | or undefined if @param2 is an Array.\r\n */\r\n choose: function(state, key, group){\r\n if(key instanceof Array){\r\n for(var k in key){\r\n this.choose(state, key[k][0], key[k][1] || \"#\")\r\n }\r\n return this;\r\n }\r\n\r\n // Disable || Enable\r\n if(state == \"enable\" || state == \"disable\"){\r\n return this.options.handle(state, key, group);\r\n }\r\n\r\n // Select || Unselect || Toggle\r\n if(state == \"toggle\"){\r\n state = this.options.is(\"select\", key, group)? \"unselect\": \"select\";\r\n }\r\n return this.options.handle(state, key, group)\r\n },\r\n\r\n /*\r\n | PUBLIC :: OPEN DROPDOWN\r\n | @since 0.3.0\r\n | @update 0.4.0\r\n */\r\n open: function(animate){\r\n if(tail.hasClass(this.select, \"active\") || tail.hasClass(this.select, \"idle\")){\r\n return false;\r\n }\r\n\r\n // Calculate Dropdown Height\r\n var clone = this.dropdown.cloneNode(true);\r\n clone.style.cssText = \"height:auto;opacity:0;display:block;visibility:hidden;\";\r\n clone.style.maxHeight = this.con.height + \"px\";\r\n clone.className += \" cloned\";\r\n this.dropdown.parentElement.appendChild(clone);\r\n var height = this.con.height, search = 0;\r\n height = (height > clone.clientHeight)? clone.clientHeight: height;\r\n if(this.con.search){\r\n search = clone.querySelector(\".tail-dropdown-search\").clientHeight;\r\n }\r\n this.dropdown.parentElement.removeChild(clone);\r\n\r\n // Calculate Viewport\r\n var pos = this.select.getBoundingClientRect(),\r\n bottom = w.innerHeight-(pos.top+pos.height),\r\n view = ((height+search) > bottom)? pos.top > bottom: false;\r\n if(this.con.openAbove === true || (this.con.openAbove !== false && view)){\r\n view = true;\r\n height = Math.min((height), pos.top-10);\r\n tail.addClass(this.select, \"open-top\");\r\n } else {\r\n view = false;\r\n height = Math.min((height), bottom-10);\r\n tail.removeClass(this.select, \"open-top\");\r\n }\r\n this.dropdown.style.maxHeight = height + \"px\";\r\n this.dropdown.querySelector(\".tail-dropdown-inner\").style.maxHeight = height-search-2 + \"px\";\r\n\r\n // Final Function\r\n var final = function(){\r\n tail.addClass(tail.removeClass(self.select, \"idle\"), \"active\");\r\n this.dropdown.style.height = \"auto\";\r\n this.label.removeAttribute(\"style\");\r\n if(this.con.search && this.con.searchFocus){\r\n this.dropdown.querySelector(\"input\").focus();\r\n }\r\n this.trigger.call(this, \"open\");\r\n }, self = this;\r\n\r\n // Open\r\n if(this.con.animate && animate !== false){\r\n this.label.style.zIndex = 25;\r\n this.dropdown.style.cssText += \"height:0;display:block;overflow:hidden;\";\r\n\r\n tail.addClass(self.select, \"idle\");\r\n tail.animate(this.dropdown, function(){\r\n var h = parseInt(this.style.height, 10), m = parseInt(this.style.maxHeight, 10);\r\n if(h < m){\r\n this.style.height = ((h+50 > m)? m: h+50) + \"px\";\r\n return true;\r\n }\r\n final.call(self);\r\n return false;\r\n });\r\n } else {\r\n final.call(this);\r\n }\r\n return this;\r\n },\r\n\r\n /*\r\n | PUBLIC :: CLOSE DROPDOWN\r\n | @since 0.3.0\r\n | @update 0.4.0\r\n */\r\n close: function(animate){\r\n if(!tail.hasClass(this.select, \"active\") || tail.hasClass(this.select, \"idle\")){\r\n return false;\r\n }\r\n var final = function(){\r\n tail.removeClass(tail.removeClass(this.select, \"idle\"), \"active\");\r\n this.dropdown.removeAttribute(\"style\");\r\n this.dropdown.querySelector(\".tail-dropdown-inner\").removeAttribute(\"style\");\r\n this.trigger.call(this, \"close\");\r\n }, self = this;\r\n\r\n // Close\r\n if(this.con.animate && animate !== false){\r\n tail.addClass(this.select, \"idle\");\r\n tail.animate(this.dropdown, function(){\r\n if((parseInt(this.offsetHeight, 10)-50) > 0){\r\n this.style.height = (parseInt(this.offsetHeight, 10)-50) + \"px\";\r\n return true;\r\n }\r\n final.call(self);\r\n return false;\r\n }, 1, true);\r\n } else {\r\n final.call(this);\r\n }\r\n return this;\r\n },\r\n\r\n /*\r\n | PUBLIC :: TOGGLE DROPDOWN\r\n | @since 0.3.0\r\n | @update 0.4.0\r\n */\r\n toggle: function(animate){\r\n if(tail.hasClass(this.select, \"idle\")){\r\n return false;\r\n }\r\n if(!tail.hasClass(this.select, \"active\")){\r\n return this.open(animate);\r\n }\r\n return this.close(animate);\r\n },\r\n\r\n /*\r\n | PUBLIC :: REMOVE SELECT\r\n | @since 0.3.0\r\n | @update 0.4.0\r\n */\r\n remove: function(){\r\n this.e.style.removeProperty(\"display\");\r\n this.e.removeAttribute(\"data-tail-select\");\r\n this.select.parentElement.removeChild(this.select);\r\n if(this.container){\r\n var handles = this.container.querySelectorAll(selector);\r\n for(var l = handles.length, i = 0; i < l; i++){\r\n this.container.removeChild(handles[i]);\r\n }\r\n }\r\n return this;\r\n },\r\n\r\n /*\r\n | PUBLIC :: RELOAD SELECT\r\n | @since 0.3.0\r\n | @update 0.4.0\r\n */\r\n reload: function(){\r\n this.remove();\r\n return this.init();\r\n },\r\n\r\n /*\r\n | PUBLIC :: GET|SET CONFIG\r\n | @since 0.4.0\r\n */\r\n config: function(key, value){\r\n if(typeof(key) == \"undefined\"){\r\n return this.con;\r\n } else if(!(key in this.con)){\r\n return false;\r\n }\r\n\r\n // Set | Return\r\n if(typeof(value) == \"undefined\"){\r\n return this.con[key];\r\n }\r\n this.con[key] = value;\r\n return this;\r\n },\r\n\r\n /*\r\n | PUBLIC :: CUSTOM EVENT LISTENER\r\n | @since 0.4.0\r\n |\r\n | @param string 'open', 'close', 'change'\r\n | @param callb. A custom callback function.\r\n | @param array An array with own arguments, which should pass to the callback too.\r\n */\r\n on: function(event, callback, args){\r\n if([\"open\", \"close\", \"change\"].indexOf(event) < 0 || typeof(callback) != \"function\"){\r\n return false;\r\n }\r\n if(!(event in this.events)){\r\n this.events[event] = [];\r\n }\r\n this.events[event].push({cb: callback, args: (args instanceof Array)? args: []});\r\n return this;\r\n }\r\n };\r\n\r\n /*\r\n | TAIL.OPTIONS HANDLER\r\n */\r\n tailOptions.prototype = {\r\n /*\r\n | INTERNAL :: REPLACE TYPOs\r\n | @since 0.3.0\r\n | @update 0.4.0\r\n */\r\n _replaceTypo: function(state){\r\n return state.replace(\"disabled\", \"disable\").replace(\"enabled\", \"enable\")\r\n .replace(\"selected\", \"select\").replace(\"unselected\", \"unselect\");\r\n },\r\n\r\n /*\r\n | INIT OPTIONS CLASS\r\n | @since 0.3.0\r\n | @update 0.4.0\r\n */\r\n init: function(){\r\n this.length = 0;\r\n this.selected = [];\r\n this.disabled = [];\r\n this.items = {\"#\": {}};\r\n this.groups = {};\r\n\r\n // Set Items\r\n for(var l = this.select.options.length, i = 0; i < l; i++){\r\n this.set(this.select.options[i]);\r\n }\r\n if(!this.self.con.multiple && !this.self.con.deselect && this.selected.length == 0){\r\n\r\n }\r\n return this;\r\n },\r\n\r\n /*\r\n | GET AN EXISTING OPTION\r\n | @since 0.3.0\r\n |\r\n | @param multi The respective option key.\r\n | The