selectize.js 82 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059
  1. /**
  2. * selectize.js (v0.12.0)
  3. * Copyright (c) 2013–2015 Brian Reavis & contributors
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
  6. * file except in compliance with the License. You may obtain a copy of the License at:
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software distributed under
  10. * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
  11. * ANY KIND, either express or implied. See the License for the specific language
  12. * governing permissions and limitations under the License.
  13. *
  14. * @author Brian Reavis <brian@thirdroute.com>
  15. */
  16. /*jshint curly:false */
  17. /*jshint browser:true */
  18. (function(root, factory) {
  19. if (typeof define === 'function' && define.amd) {
  20. define(['jquery','sifter','microplugin'], factory);
  21. } else if (typeof exports === 'object') {
  22. module.exports = factory(require('jquery'), require('sifter'), require('microplugin'));
  23. } else {
  24. root.Selectize = factory(root.jQuery, root.Sifter, root.MicroPlugin);
  25. }
  26. }(this, function($, Sifter, MicroPlugin) {
  27. 'use strict';
  28. var highlight = function($element, pattern) {
  29. if (typeof pattern === 'string' && !pattern.length) return;
  30. var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern;
  31. var highlight = function(node) {
  32. var skip = 0;
  33. if (node.nodeType === 3) {
  34. var pos = node.data.search(regex);
  35. if (pos >= 0 && node.data.length > 0) {
  36. var match = node.data.match(regex);
  37. var spannode = document.createElement('span');
  38. spannode.className = 'highlight';
  39. var middlebit = node.splitText(pos);
  40. var endbit = middlebit.splitText(match[0].length);
  41. var middleclone = middlebit.cloneNode(true);
  42. spannode.appendChild(middleclone);
  43. middlebit.parentNode.replaceChild(spannode, middlebit);
  44. skip = 1;
  45. }
  46. } else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) {
  47. for (var i = 0; i < node.childNodes.length; ++i) {
  48. i += highlight(node.childNodes[i]);
  49. }
  50. }
  51. return skip;
  52. };
  53. return $element.each(function() {
  54. highlight(this);
  55. });
  56. };
  57. var MicroEvent = function() {};
  58. MicroEvent.prototype = {
  59. on: function(event, fct){
  60. this._events = this._events || {};
  61. this._events[event] = this._events[event] || [];
  62. this._events[event].push(fct);
  63. },
  64. off: function(event, fct){
  65. var n = arguments.length;
  66. if (n === 0) return delete this._events;
  67. if (n === 1) return delete this._events[event];
  68. this._events = this._events || {};
  69. if (event in this._events === false) return;
  70. this._events[event].splice(this._events[event].indexOf(fct), 1);
  71. },
  72. trigger: function(event /* , args... */){
  73. this._events = this._events || {};
  74. if (event in this._events === false) return;
  75. for (var i = 0; i < this._events[event].length; i++){
  76. this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
  77. }
  78. }
  79. };
  80. /**
  81. * Mixin will delegate all MicroEvent.js function in the destination object.
  82. *
  83. * - MicroEvent.mixin(Foobar) will make Foobar able to use MicroEvent
  84. *
  85. * @param {object} the object which will support MicroEvent
  86. */
  87. MicroEvent.mixin = function(destObject){
  88. var props = ['on', 'off', 'trigger'];
  89. for (var i = 0; i < props.length; i++){
  90. destObject.prototype[props[i]] = MicroEvent.prototype[props[i]];
  91. }
  92. };
  93. var IS_MAC = /Mac/.test(navigator.userAgent);
  94. var KEY_A = 65;
  95. var KEY_COMMA = 188;
  96. var KEY_RETURN = 13;
  97. var KEY_ESC = 27;
  98. var KEY_LEFT = 37;
  99. var KEY_UP = 38;
  100. var KEY_P = 80;
  101. var KEY_RIGHT = 39;
  102. var KEY_DOWN = 40;
  103. var KEY_N = 78;
  104. var KEY_BACKSPACE = 8;
  105. var KEY_DELETE = 46;
  106. var KEY_SHIFT = 16;
  107. var KEY_CMD = IS_MAC ? 91 : 17;
  108. var KEY_CTRL = IS_MAC ? 18 : 17;
  109. var KEY_TAB = 9;
  110. var TAG_SELECT = 1;
  111. var TAG_INPUT = 2;
  112. // for now, android support in general is too spotty to support validity
  113. var SUPPORTS_VALIDITY_API = !/android/i.test(window.navigator.userAgent) && !!document.createElement('form').validity;
  114. var isset = function(object) {
  115. return typeof object !== 'undefined';
  116. };
  117. /**
  118. * Converts a scalar to its best string representation
  119. * for hash keys and HTML attribute values.
  120. *
  121. * Transformations:
  122. * 'str' -> 'str'
  123. * null -> ''
  124. * undefined -> ''
  125. * true -> '1'
  126. * false -> '0'
  127. * 0 -> '0'
  128. * 1 -> '1'
  129. *
  130. * @param {string} value
  131. * @returns {string|null}
  132. */
  133. var hash_key = function(value) {
  134. if (typeof value === 'undefined' || value === null) return null;
  135. if (typeof value === 'boolean') return value ? '1' : '0';
  136. return value + '';
  137. };
  138. /**
  139. * Escapes a string for use within HTML.
  140. *
  141. * @param {string} str
  142. * @returns {string}
  143. */
  144. var escape_html = function(str) {
  145. return (str + '')
  146. .replace(/&/g, '&amp;')
  147. .replace(/</g, '&lt;')
  148. .replace(/>/g, '&gt;')
  149. .replace(/"/g, '&quot;');
  150. };
  151. /**
  152. * Escapes "$" characters in replacement strings.
  153. *
  154. * @param {string} str
  155. * @returns {string}
  156. */
  157. var escape_replace = function(str) {
  158. return (str + '').replace(/\$/g, '$$$$');
  159. };
  160. var hook = {};
  161. /**
  162. * Wraps `method` on `self` so that `fn`
  163. * is invoked before the original method.
  164. *
  165. * @param {object} self
  166. * @param {string} method
  167. * @param {function} fn
  168. */
  169. hook.before = function(self, method, fn) {
  170. var original = self[method];
  171. self[method] = function() {
  172. fn.apply(self, arguments);
  173. return original.apply(self, arguments);
  174. };
  175. };
  176. /**
  177. * Wraps `method` on `self` so that `fn`
  178. * is invoked after the original method.
  179. *
  180. * @param {object} self
  181. * @param {string} method
  182. * @param {function} fn
  183. */
  184. hook.after = function(self, method, fn) {
  185. var original = self[method];
  186. self[method] = function() {
  187. var result = original.apply(self, arguments);
  188. fn.apply(self, arguments);
  189. return result;
  190. };
  191. };
  192. /**
  193. * Wraps `fn` so that it can only be invoked once.
  194. *
  195. * @param {function} fn
  196. * @returns {function}
  197. */
  198. var once = function(fn) {
  199. var called = false;
  200. return function() {
  201. if (called) return;
  202. called = true;
  203. fn.apply(this, arguments);
  204. };
  205. };
  206. /**
  207. * Wraps `fn` so that it can only be called once
  208. * every `delay` milliseconds (invoked on the falling edge).
  209. *
  210. * @param {function} fn
  211. * @param {int} delay
  212. * @returns {function}
  213. */
  214. var debounce = function(fn, delay) {
  215. var timeout;
  216. return function() {
  217. var self = this;
  218. var args = arguments;
  219. window.clearTimeout(timeout);
  220. timeout = window.setTimeout(function() {
  221. fn.apply(self, args);
  222. }, delay);
  223. };
  224. };
  225. /**
  226. * Debounce all fired events types listed in `types`
  227. * while executing the provided `fn`.
  228. *
  229. * @param {object} self
  230. * @param {array} types
  231. * @param {function} fn
  232. */
  233. var debounce_events = function(self, types, fn) {
  234. var type;
  235. var trigger = self.trigger;
  236. var event_args = {};
  237. // override trigger method
  238. self.trigger = function() {
  239. var type = arguments[0];
  240. if (types.indexOf(type) !== -1) {
  241. event_args[type] = arguments;
  242. } else {
  243. return trigger.apply(self, arguments);
  244. }
  245. };
  246. // invoke provided function
  247. fn.apply(self, []);
  248. self.trigger = trigger;
  249. // trigger queued events
  250. for (type in event_args) {
  251. if (event_args.hasOwnProperty(type)) {
  252. trigger.apply(self, event_args[type]);
  253. }
  254. }
  255. };
  256. /**
  257. * A workaround for http://bugs.jquery.com/ticket/6696
  258. *
  259. * @param {object} $parent - Parent element to listen on.
  260. * @param {string} event - Event name.
  261. * @param {string} selector - Descendant selector to filter by.
  262. * @param {function} fn - Event handler.
  263. */
  264. var watchChildEvent = function($parent, event, selector, fn) {
  265. $parent.on(event, selector, function(e) {
  266. var child = e.target;
  267. while (child && child.parentNode !== $parent[0]) {
  268. child = child.parentNode;
  269. }
  270. e.currentTarget = child;
  271. return fn.apply(this, [e]);
  272. });
  273. };
  274. /**
  275. * Determines the current selection within a text input control.
  276. * Returns an object containing:
  277. * - start
  278. * - length
  279. *
  280. * @param {object} input
  281. * @returns {object}
  282. */
  283. var getSelection = function(input) {
  284. var result = {};
  285. if ('selectionStart' in input) {
  286. result.start = input.selectionStart;
  287. result.length = input.selectionEnd - result.start;
  288. } else if (document.selection) {
  289. input.focus();
  290. var sel = document.selection.createRange();
  291. var selLen = document.selection.createRange().text.length;
  292. sel.moveStart('character', -input.value.length);
  293. result.start = sel.text.length - selLen;
  294. result.length = selLen;
  295. }
  296. return result;
  297. };
  298. /**
  299. * Copies CSS properties from one element to another.
  300. *
  301. * @param {object} $from
  302. * @param {object} $to
  303. * @param {array} properties
  304. */
  305. var transferStyles = function($from, $to, properties) {
  306. var i, n, styles = {};
  307. if (properties) {
  308. for (i = 0, n = properties.length; i < n; i++) {
  309. styles[properties[i]] = $from.css(properties[i]);
  310. }
  311. } else {
  312. styles = $from.css();
  313. }
  314. $to.css(styles);
  315. };
  316. /**
  317. * Measures the width of a string within a
  318. * parent element (in pixels).
  319. *
  320. * @param {string} str
  321. * @param {object} $parent
  322. * @returns {int}
  323. */
  324. var measureString = function(str, $parent) {
  325. if (!str) {
  326. return 0;
  327. }
  328. var $test = $('<test>').css({
  329. position: 'absolute',
  330. top: -99999,
  331. left: -99999,
  332. width: 'auto',
  333. padding: 0,
  334. whiteSpace: 'pre'
  335. }).text(str).appendTo('body');
  336. transferStyles($parent, $test, [
  337. 'letterSpacing',
  338. 'fontSize',
  339. 'fontFamily',
  340. 'fontWeight',
  341. 'textTransform'
  342. ]);
  343. var width = $test.width();
  344. $test.remove();
  345. return width;
  346. };
  347. /**
  348. * Sets up an input to grow horizontally as the user
  349. * types. If the value is changed manually, you can
  350. * trigger the "update" handler to resize:
  351. *
  352. * $input.trigger('update');
  353. *
  354. * @param {object} $input
  355. */
  356. var autoGrow = function($input) {
  357. var currentWidth = null;
  358. var update = function(e, options) {
  359. var value, keyCode, printable, placeholder, width;
  360. var shift, character, selection;
  361. e = e || window.event || {};
  362. options = options || {};
  363. if (e.metaKey || e.altKey) return;
  364. if (!options.force && $input.data('grow') === false) return;
  365. value = $input.val();
  366. if (e.type && e.type.toLowerCase() === 'keydown') {
  367. keyCode = e.keyCode;
  368. printable = (
  369. (keyCode >= 97 && keyCode <= 122) || // a-z
  370. (keyCode >= 65 && keyCode <= 90) || // A-Z
  371. (keyCode >= 48 && keyCode <= 57) || // 0-9
  372. keyCode === 32 // space
  373. );
  374. if (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) {
  375. selection = getSelection($input[0]);
  376. if (selection.length) {
  377. value = value.substring(0, selection.start) + value.substring(selection.start + selection.length);
  378. } else if (keyCode === KEY_BACKSPACE && selection.start) {
  379. value = value.substring(0, selection.start - 1) + value.substring(selection.start + 1);
  380. } else if (keyCode === KEY_DELETE && typeof selection.start !== 'undefined') {
  381. value = value.substring(0, selection.start) + value.substring(selection.start + 1);
  382. }
  383. } else if (printable) {
  384. shift = e.shiftKey;
  385. character = String.fromCharCode(e.keyCode);
  386. if (shift) character = character.toUpperCase();
  387. else character = character.toLowerCase();
  388. value += character;
  389. }
  390. }
  391. placeholder = $input.attr('placeholder');
  392. if (!value && placeholder) {
  393. value = placeholder;
  394. }
  395. width = measureString(value, $input) + 4;
  396. if (width !== currentWidth) {
  397. currentWidth = width;
  398. $input.width(width);
  399. $input.triggerHandler('resize');
  400. }
  401. };
  402. $input.on('keydown keyup update blur', update);
  403. update();
  404. };
  405. var Selectize = function($input, settings) {
  406. var key, i, n, dir, input, self = this;
  407. input = $input[0];
  408. input.selectize = self;
  409. // detect rtl environment
  410. var computedStyle = window.getComputedStyle && window.getComputedStyle(input, null);
  411. dir = computedStyle ? computedStyle.getPropertyValue('direction') : input.currentStyle && input.currentStyle.direction;
  412. dir = dir || $input.parents('[dir]:first').attr('dir') || '';
  413. // setup default state
  414. $.extend(self, {
  415. order : 0,
  416. settings : settings,
  417. $input : $input,
  418. tabIndex : $input.attr('tabindex') || '',
  419. tagType : input.tagName.toLowerCase() === 'select' ? TAG_SELECT : TAG_INPUT,
  420. rtl : /rtl/i.test(dir),
  421. eventNS : '.selectize' + (++Selectize.count),
  422. highlightedValue : null,
  423. isOpen : false,
  424. isDisabled : false,
  425. isRequired : $input.is('[required]'),
  426. isInvalid : false,
  427. isLocked : false,
  428. isFocused : false,
  429. isInputHidden : false,
  430. isSetup : false,
  431. isShiftDown : false,
  432. isCmdDown : false,
  433. isCtrlDown : false,
  434. ignoreFocus : false,
  435. ignoreBlur : false,
  436. ignoreHover : false,
  437. hasOptions : false,
  438. currentResults : null,
  439. lastValue : '',
  440. caretPos : 0,
  441. loading : 0,
  442. loadedSearches : {},
  443. $activeOption : null,
  444. $activeItems : [],
  445. optgroups : {},
  446. options : {},
  447. userOptions : {},
  448. items : [],
  449. renderCache : {},
  450. onSearchChange : settings.loadThrottle === null ? self.onSearchChange : debounce(self.onSearchChange, settings.loadThrottle)
  451. });
  452. // search system
  453. self.sifter = new Sifter(this.options, {diacritics: settings.diacritics});
  454. // build options table
  455. if (self.settings.options) {
  456. for (i = 0, n = self.settings.options.length; i < n; i++) {
  457. self.registerOption(self.settings.options[i]);
  458. }
  459. delete self.settings.options;
  460. }
  461. // build optgroup table
  462. if (self.settings.optgroups) {
  463. for (i = 0, n = self.settings.optgroups.length; i < n; i++) {
  464. self.registerOptionGroup(self.settings.optgroups[i]);
  465. }
  466. delete self.settings.optgroups;
  467. }
  468. // option-dependent defaults
  469. self.settings.mode = self.settings.mode || (self.settings.maxItems === 1 ? 'single' : 'multi');
  470. if (typeof self.settings.hideSelected !== 'boolean') {
  471. self.settings.hideSelected = self.settings.mode === 'multi';
  472. }
  473. self.initializePlugins(self.settings.plugins);
  474. self.setupCallbacks();
  475. self.setupTemplates();
  476. self.setup();
  477. };
  478. // mixins
  479. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  480. MicroEvent.mixin(Selectize);
  481. MicroPlugin.mixin(Selectize);
  482. // methods
  483. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  484. $.extend(Selectize.prototype, {
  485. /**
  486. * Creates all elements and sets up event bindings.
  487. */
  488. setup: function() {
  489. var self = this;
  490. var settings = self.settings;
  491. var eventNS = self.eventNS;
  492. var $window = $(window);
  493. var $document = $(document);
  494. var $input = self.$input;
  495. var $wrapper;
  496. var $control;
  497. var $control_input;
  498. var $dropdown;
  499. var $dropdown_content;
  500. var $dropdown_parent;
  501. var inputMode;
  502. var timeout_blur;
  503. var timeout_focus;
  504. var classes;
  505. var classes_plugins;
  506. inputMode = self.settings.mode;
  507. classes = $input.attr('class') || '';
  508. $wrapper = $('<div>').addClass(settings.wrapperClass).addClass(classes).addClass(inputMode);
  509. $control = $('<div>').addClass(settings.inputClass).addClass('items').appendTo($wrapper);
  510. $control_input = $('<input type="text" autocomplete="off" />').appendTo($control).attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex);
  511. $dropdown_parent = $(settings.dropdownParent || $wrapper);
  512. $dropdown = $('<div>').addClass(settings.dropdownClass).addClass(inputMode).hide().appendTo($dropdown_parent);
  513. $dropdown_content = $('<div>').addClass(settings.dropdownContentClass).appendTo($dropdown);
  514. if(self.settings.copyClassesToDropdown) {
  515. $dropdown.addClass(classes);
  516. }
  517. $wrapper.css({
  518. width: $input[0].style.width
  519. });
  520. if (self.plugins.names.length) {
  521. classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-');
  522. $wrapper.addClass(classes_plugins);
  523. $dropdown.addClass(classes_plugins);
  524. }
  525. if ((settings.maxItems === null || settings.maxItems > 1) && self.tagType === TAG_SELECT) {
  526. $input.attr('multiple', 'multiple');
  527. }
  528. if (self.settings.placeholder) {
  529. $control_input.attr('placeholder', settings.placeholder);
  530. }
  531. // if splitOn was not passed in, construct it from the delimiter to allow pasting universally
  532. if (!self.settings.splitOn && self.settings.delimiter) {
  533. var delimiterEscaped = self.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
  534. self.settings.splitOn = new RegExp('\\s*' + delimiterEscaped + '+\\s*');
  535. }
  536. if ($input.attr('autocorrect')) {
  537. $control_input.attr('autocorrect', $input.attr('autocorrect'));
  538. }
  539. if ($input.attr('autocapitalize')) {
  540. $control_input.attr('autocapitalize', $input.attr('autocapitalize'));
  541. }
  542. self.$wrapper = $wrapper;
  543. self.$control = $control;
  544. self.$control_input = $control_input;
  545. self.$dropdown = $dropdown;
  546. self.$dropdown_content = $dropdown_content;
  547. $dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); });
  548. $dropdown.on('mousedown click', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); });
  549. watchChildEvent($control, 'mousedown', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); });
  550. autoGrow($control_input);
  551. $control.on({
  552. mousedown : function() { return self.onMouseDown.apply(self, arguments); },
  553. click : function() { return self.onClick.apply(self, arguments); }
  554. });
  555. $control_input.on({
  556. mousedown : function(e) { e.stopPropagation(); },
  557. keydown : function() { return self.onKeyDown.apply(self, arguments); },
  558. keyup : function() { return self.onKeyUp.apply(self, arguments); },
  559. keypress : function() { return self.onKeyPress.apply(self, arguments); },
  560. resize : function() { self.positionDropdown.apply(self, []); },
  561. blur : function() { return self.onBlur.apply(self, arguments); },
  562. focus : function() { self.ignoreBlur = false; return self.onFocus.apply(self, arguments); },
  563. paste : function() { return self.onPaste.apply(self, arguments); }
  564. });
  565. $document.on('keydown' + eventNS, function(e) {
  566. self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey'];
  567. self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey'];
  568. self.isShiftDown = e.shiftKey;
  569. });
  570. $document.on('keyup' + eventNS, function(e) {
  571. if (e.keyCode === KEY_CTRL) self.isCtrlDown = false;
  572. if (e.keyCode === KEY_SHIFT) self.isShiftDown = false;
  573. if (e.keyCode === KEY_CMD) self.isCmdDown = false;
  574. });
  575. $document.on('mousedown' + eventNS, function(e) {
  576. if (self.isFocused) {
  577. // prevent events on the dropdown scrollbar from causing the control to blur
  578. if (e.target === self.$dropdown[0] || e.target.parentNode === self.$dropdown[0]) {
  579. return false;
  580. }
  581. // blur on click outside
  582. if (!self.$control.has(e.target).length && e.target !== self.$control[0]) {
  583. self.blur(e.target);
  584. }
  585. }
  586. });
  587. $window.on(['scroll' + eventNS, 'resize' + eventNS].join(' '), function() {
  588. if (self.isOpen) {
  589. self.positionDropdown.apply(self, arguments);
  590. }
  591. });
  592. $window.on('mousemove' + eventNS, function() {
  593. self.ignoreHover = false;
  594. });
  595. // store original children and tab index so that they can be
  596. // restored when the destroy() method is called.
  597. this.revertSettings = {
  598. $children : $input.children().detach(),
  599. tabindex : $input.attr('tabindex')
  600. };
  601. $input.attr('tabindex', -1).hide().after(self.$wrapper);
  602. if ($.isArray(settings.items)) {
  603. self.setValue(settings.items);
  604. delete settings.items;
  605. }
  606. // feature detect for the validation API
  607. if (SUPPORTS_VALIDITY_API) {
  608. $input.on('invalid' + eventNS, function(e) {
  609. e.preventDefault();
  610. self.isInvalid = true;
  611. self.refreshState();
  612. });
  613. }
  614. self.updateOriginalInput();
  615. self.refreshItems();
  616. self.refreshState();
  617. self.updatePlaceholder();
  618. self.isSetup = true;
  619. if ($input.is(':disabled')) {
  620. self.disable();
  621. }
  622. self.on('change', this.onChange);
  623. $input.data('selectize', self);
  624. $input.addClass('selectized');
  625. self.trigger('initialize');
  626. // preload options
  627. if (settings.preload === true) {
  628. self.onSearchChange('');
  629. }
  630. },
  631. /**
  632. * Sets up default rendering functions.
  633. */
  634. setupTemplates: function() {
  635. var self = this;
  636. var field_label = self.settings.labelField;
  637. var field_optgroup = self.settings.optgroupLabelField;
  638. var templates = {
  639. 'optgroup': function(data) {
  640. return '<div class="optgroup">' + data.html + '</div>';
  641. },
  642. 'optgroup_header': function(data, escape) {
  643. return '<div class="optgroup-header">' + escape(data[field_optgroup]) + '</div>';
  644. },
  645. 'option': function(data, escape) {
  646. return '<div class="option">' + escape(data[field_label]) + '</div>';
  647. },
  648. 'item': function(data, escape) {
  649. return '<div class="item">' + escape(data[field_label]) + '</div>';
  650. },
  651. 'option_create': function(data, escape) {
  652. return '<div class="create">Add <strong>' + escape(data.input) + '</strong>&hellip;</div>';
  653. }
  654. };
  655. self.settings.render = $.extend({}, templates, self.settings.render);
  656. },
  657. /**
  658. * Maps fired events to callbacks provided
  659. * in the settings used when creating the control.
  660. */
  661. setupCallbacks: function() {
  662. var key, fn, callbacks = {
  663. 'initialize' : 'onInitialize',
  664. 'change' : 'onChange',
  665. 'item_add' : 'onItemAdd',
  666. 'item_remove' : 'onItemRemove',
  667. 'clear' : 'onClear',
  668. 'option_add' : 'onOptionAdd',
  669. 'option_remove' : 'onOptionRemove',
  670. 'option_clear' : 'onOptionClear',
  671. 'optgroup_add' : 'onOptionGroupAdd',
  672. 'optgroup_remove' : 'onOptionGroupRemove',
  673. 'optgroup_clear' : 'onOptionGroupClear',
  674. 'dropdown_open' : 'onDropdownOpen',
  675. 'dropdown_close' : 'onDropdownClose',
  676. 'type' : 'onType',
  677. 'load' : 'onLoad',
  678. 'focus' : 'onFocus',
  679. 'blur' : 'onBlur'
  680. };
  681. for (key in callbacks) {
  682. if (callbacks.hasOwnProperty(key)) {
  683. fn = this.settings[callbacks[key]];
  684. if (fn) this.on(key, fn);
  685. }
  686. }
  687. },
  688. /**
  689. * Triggered when the main control element
  690. * has a click event.
  691. *
  692. * @param {object} e
  693. * @return {boolean}
  694. */
  695. onClick: function(e) {
  696. var self = this;
  697. // necessary for mobile webkit devices (manual focus triggering
  698. // is ignored unless invoked within a click event)
  699. if (!self.isFocused) {
  700. self.focus();
  701. e.preventDefault();
  702. }
  703. },
  704. /**
  705. * Triggered when the main control element
  706. * has a mouse down event.
  707. *
  708. * @param {object} e
  709. * @return {boolean}
  710. */
  711. onMouseDown: function(e) {
  712. var self = this;
  713. var defaultPrevented = e.isDefaultPrevented();
  714. var $target = $(e.target);
  715. if (self.isFocused) {
  716. // retain focus by preventing native handling. if the
  717. // event target is the input it should not be modified.
  718. // otherwise, text selection within the input won't work.
  719. if (e.target !== self.$control_input[0]) {
  720. if (self.settings.mode === 'single') {
  721. // toggle dropdown
  722. self.isOpen ? self.close() : self.open();
  723. } else if (!defaultPrevented) {
  724. self.setActiveItem(null);
  725. }
  726. return false;
  727. }
  728. } else {
  729. // give control focus
  730. if (!defaultPrevented) {
  731. window.setTimeout(function() {
  732. self.focus();
  733. }, 0);
  734. }
  735. }
  736. },
  737. /**
  738. * Triggered when the value of the control has been changed.
  739. * This should propagate the event to the original DOM
  740. * input / select element.
  741. */
  742. onChange: function() {
  743. this.$input.trigger('change');
  744. },
  745. /**
  746. * Triggered on <input> paste.
  747. *
  748. * @param {object} e
  749. * @returns {boolean}
  750. */
  751. onPaste: function(e) {
  752. var self = this;
  753. if (self.isFull() || self.isInputHidden || self.isLocked) {
  754. e.preventDefault();
  755. } else {
  756. // If a regex or string is included, this will split the pasted
  757. // input and create Items for each separate value
  758. if (self.settings.splitOn) {
  759. setTimeout(function() {
  760. var splitInput = $.trim(self.$control_input.val() || '').split(self.settings.splitOn);
  761. for (var i = 0, n = splitInput.length; i < n; i++) {
  762. self.createItem(splitInput[i]);
  763. }
  764. }, 0);
  765. }
  766. }
  767. },
  768. /**
  769. * Triggered on <input> keypress.
  770. *
  771. * @param {object} e
  772. * @returns {boolean}
  773. */
  774. onKeyPress: function(e) {
  775. if (this.isLocked) return e && e.preventDefault();
  776. var character = String.fromCharCode(e.keyCode || e.which);
  777. if (this.settings.create && this.settings.mode === 'multi' && character === this.settings.delimiter) {
  778. this.createItem();
  779. e.preventDefault();
  780. return false;
  781. }
  782. },
  783. /**
  784. * Triggered on <input> keydown.
  785. *
  786. * @param {object} e
  787. * @returns {boolean}
  788. */
  789. onKeyDown: function(e) {
  790. var isInput = e.target === this.$control_input[0];
  791. var self = this;
  792. if (self.isLocked) {
  793. if (e.keyCode !== KEY_TAB) {
  794. e.preventDefault();
  795. }
  796. return;
  797. }
  798. switch (e.keyCode) {
  799. case KEY_A:
  800. if (self.isCmdDown) {
  801. self.selectAll();
  802. return;
  803. }
  804. break;
  805. case KEY_ESC:
  806. if (self.isOpen) {
  807. e.preventDefault();
  808. e.stopPropagation();
  809. self.close();
  810. }
  811. return;
  812. case KEY_N:
  813. if (!e.ctrlKey || e.altKey) break;
  814. case KEY_DOWN:
  815. if (!self.isOpen && self.hasOptions) {
  816. self.open();
  817. } else if (self.$activeOption) {
  818. self.ignoreHover = true;
  819. var $next = self.getAdjacentOption(self.$activeOption, 1);
  820. if ($next.length) self.setActiveOption($next, true, true);
  821. }
  822. e.preventDefault();
  823. return;
  824. case KEY_P:
  825. if (!e.ctrlKey || e.altKey) break;
  826. case KEY_UP:
  827. if (self.$activeOption) {
  828. self.ignoreHover = true;
  829. var $prev = self.getAdjacentOption(self.$activeOption, -1);
  830. if ($prev.length) self.setActiveOption($prev, true, true);
  831. }
  832. e.preventDefault();
  833. return;
  834. case KEY_RETURN:
  835. if (self.isOpen && self.$activeOption) {
  836. self.onOptionSelect({currentTarget: self.$activeOption});
  837. e.preventDefault();
  838. }
  839. return;
  840. case KEY_LEFT:
  841. self.advanceSelection(-1, e);
  842. return;
  843. case KEY_RIGHT:
  844. self.advanceSelection(1, e);
  845. return;
  846. case KEY_TAB:
  847. if (self.settings.selectOnTab && self.isOpen && self.$activeOption) {
  848. self.onOptionSelect({currentTarget: self.$activeOption});
  849. // Default behaviour is to jump to the next field, we only want this
  850. // if the current field doesn't accept any more entries
  851. if (!self.isFull()) {
  852. e.preventDefault();
  853. }
  854. }
  855. if (self.settings.create && self.createItem()) {
  856. e.preventDefault();
  857. }
  858. return;
  859. case KEY_BACKSPACE:
  860. case KEY_DELETE:
  861. self.deleteSelection(e);
  862. return;
  863. }
  864. if ((self.isFull() || self.isInputHidden) && !(IS_MAC ? e.metaKey : e.ctrlKey)) {
  865. e.preventDefault();
  866. return;
  867. }
  868. },
  869. /**
  870. * Triggered on <input> keyup.
  871. *
  872. * @param {object} e
  873. * @returns {boolean}
  874. */
  875. onKeyUp: function(e) {
  876. var self = this;
  877. if (self.isLocked) return e && e.preventDefault();
  878. var value = self.$control_input.val() || '';
  879. if (self.lastValue !== value) {
  880. self.lastValue = value;
  881. self.onSearchChange(value);
  882. self.refreshOptions();
  883. self.trigger('type', value);
  884. }
  885. },
  886. /**
  887. * Invokes the user-provide option provider / loader.
  888. *
  889. * Note: this function is debounced in the Selectize
  890. * constructor (by `settings.loadDelay` milliseconds)
  891. *
  892. * @param {string} value
  893. */
  894. onSearchChange: function(value) {
  895. var self = this;
  896. var fn = self.settings.load;
  897. if (!fn) return;
  898. if (self.loadedSearches.hasOwnProperty(value)) return;
  899. self.loadedSearches[value] = true;
  900. self.load(function(callback) {
  901. fn.apply(self, [value, callback]);
  902. });
  903. },
  904. /**
  905. * Triggered on <input> focus.
  906. *
  907. * @param {object} e (optional)
  908. * @returns {boolean}
  909. */
  910. onFocus: function(e) {
  911. var self = this;
  912. var wasFocused = self.isFocused;
  913. if (self.isDisabled) {
  914. self.blur();
  915. e && e.preventDefault();
  916. return false;
  917. }
  918. if (self.ignoreFocus) return;
  919. self.isFocused = true;
  920. if (self.settings.preload === 'focus') self.onSearchChange('');
  921. if (!wasFocused) self.trigger('focus');
  922. if (!self.$activeItems.length) {
  923. self.showInput();
  924. self.setActiveItem(null);
  925. self.refreshOptions(!!self.settings.openOnFocus);
  926. }
  927. self.refreshState();
  928. },
  929. /**
  930. * Triggered on <input> blur.
  931. *
  932. * @param {object} e
  933. * @param {Element} dest
  934. */
  935. onBlur: function(e, dest) {
  936. var self = this;
  937. if (!self.isFocused) return;
  938. self.isFocused = false;
  939. if (self.ignoreFocus) {
  940. return;
  941. } else if (!self.ignoreBlur && document.activeElement === self.$dropdown_content[0]) {
  942. // necessary to prevent IE closing the dropdown when the scrollbar is clicked
  943. self.ignoreBlur = true;
  944. self.onFocus(e);
  945. return;
  946. }
  947. var deactivate = function() {
  948. self.close();
  949. self.setTextboxValue('');
  950. self.setActiveItem(null);
  951. self.setActiveOption(null);
  952. self.setCaret(self.items.length);
  953. self.refreshState();
  954. // IE11 bug: element still marked as active
  955. (dest || document.body).focus();
  956. self.ignoreFocus = false;
  957. self.trigger('blur');
  958. };
  959. self.ignoreFocus = true;
  960. if (self.settings.create && self.settings.createOnBlur) {
  961. self.createItem(null, false, deactivate);
  962. } else {
  963. deactivate();
  964. }
  965. },
  966. /**
  967. * Triggered when the user rolls over
  968. * an option in the autocomplete dropdown menu.
  969. *
  970. * @param {object} e
  971. * @returns {boolean}
  972. */
  973. onOptionHover: function(e) {
  974. if (this.ignoreHover) return;
  975. this.setActiveOption(e.currentTarget, false);
  976. },
  977. /**
  978. * Triggered when the user clicks on an option
  979. * in the autocomplete dropdown menu.
  980. *
  981. * @param {object} e
  982. * @returns {boolean}
  983. */
  984. onOptionSelect: function(e) {
  985. var value, $target, $option, self = this;
  986. if (e.preventDefault) {
  987. e.preventDefault();
  988. e.stopPropagation();
  989. }
  990. $target = $(e.currentTarget);
  991. if ($target.hasClass('create')) {
  992. self.createItem(null, function() {
  993. if (self.settings.closeAfterSelect) {
  994. self.close();
  995. }
  996. });
  997. } else {
  998. value = $target.attr('data-value');
  999. if (typeof value !== 'undefined') {
  1000. self.lastQuery = null;
  1001. self.setTextboxValue('');
  1002. self.addItem(value);
  1003. if (self.settings.closeAfterSelect) {
  1004. self.close();
  1005. } else if (!self.settings.hideSelected && e.type && /mouse/.test(e.type)) {
  1006. self.setActiveOption(self.getOption(value));
  1007. }
  1008. }
  1009. }
  1010. },
  1011. /**
  1012. * Triggered when the user clicks on an item
  1013. * that has been selected.
  1014. *
  1015. * @param {object} e
  1016. * @returns {boolean}
  1017. */
  1018. onItemSelect: function(e) {
  1019. var self = this;
  1020. if (self.isLocked) return;
  1021. if (self.settings.mode === 'multi') {
  1022. e.preventDefault();
  1023. self.setActiveItem(e.currentTarget, e);
  1024. }
  1025. },
  1026. /**
  1027. * Invokes the provided method that provides
  1028. * results to a callback---which are then added
  1029. * as options to the control.
  1030. *
  1031. * @param {function} fn
  1032. */
  1033. load: function(fn) {
  1034. var self = this;
  1035. var $wrapper = self.$wrapper.addClass(self.settings.loadingClass);
  1036. self.loading++;
  1037. fn.apply(self, [function(results) {
  1038. self.loading = Math.max(self.loading - 1, 0);
  1039. if (results && results.length) {
  1040. self.addOption(results);
  1041. self.refreshOptions(self.isFocused && !self.isInputHidden);
  1042. }
  1043. if (!self.loading) {
  1044. $wrapper.removeClass(self.settings.loadingClass);
  1045. }
  1046. self.trigger('load', results);
  1047. }]);
  1048. },
  1049. /**
  1050. * Sets the input field of the control to the specified value.
  1051. *
  1052. * @param {string} value
  1053. */
  1054. setTextboxValue: function(value) {
  1055. var $input = this.$control_input;
  1056. var changed = $input.val() !== value;
  1057. if (changed) {
  1058. $input.val(value).triggerHandler('update');
  1059. this.lastValue = value;
  1060. }
  1061. },
  1062. /**
  1063. * Returns the value of the control. If multiple items
  1064. * can be selected (e.g. <select multiple>), this returns
  1065. * an array. If only one item can be selected, this
  1066. * returns a string.
  1067. *
  1068. * @returns {mixed}
  1069. */
  1070. getValue: function() {
  1071. if (this.tagType === TAG_SELECT && this.$input.attr('multiple')) {
  1072. return this.items;
  1073. } else {
  1074. return this.items.join(this.settings.delimiter);
  1075. }
  1076. },
  1077. /**
  1078. * Resets the selected items to the given value.
  1079. *
  1080. * @param {mixed} value
  1081. */
  1082. setValue: function(value, silent) {
  1083. var events = silent ? [] : ['change'];
  1084. debounce_events(this, events, function() {
  1085. this.clear();
  1086. this.addItems(value, silent);
  1087. });
  1088. },
  1089. /**
  1090. * Sets the selected item.
  1091. *
  1092. * @param {object} $item
  1093. * @param {object} e (optional)
  1094. */
  1095. setActiveItem: function($item, e) {
  1096. var self = this;
  1097. var eventName;
  1098. var i, idx, begin, end, item, swap;
  1099. var $last;
  1100. if (self.settings.mode === 'single') return;
  1101. $item = $($item);
  1102. // clear the active selection
  1103. if (!$item.length) {
  1104. $(self.$activeItems).removeClass('active');
  1105. self.$activeItems = [];
  1106. if (self.isFocused) {
  1107. self.showInput();
  1108. }
  1109. return;
  1110. }
  1111. // modify selection
  1112. eventName = e && e.type.toLowerCase();
  1113. if (eventName === 'mousedown' && self.isShiftDown && self.$activeItems.length) {
  1114. $last = self.$control.children('.active:last');
  1115. begin = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$last[0]]);
  1116. end = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$item[0]]);
  1117. if (begin > end) {
  1118. swap = begin;
  1119. begin = end;
  1120. end = swap;
  1121. }
  1122. for (i = begin; i <= end; i++) {
  1123. item = self.$control[0].childNodes[i];
  1124. if (self.$activeItems.indexOf(item) === -1) {
  1125. $(item).addClass('active');
  1126. self.$activeItems.push(item);
  1127. }
  1128. }
  1129. e.preventDefault();
  1130. } else if ((eventName === 'mousedown' && self.isCtrlDown) || (eventName === 'keydown' && this.isShiftDown)) {
  1131. if ($item.hasClass('active')) {
  1132. idx = self.$activeItems.indexOf($item[0]);
  1133. self.$activeItems.splice(idx, 1);
  1134. $item.removeClass('active');
  1135. } else {
  1136. self.$activeItems.push($item.addClass('active')[0]);
  1137. }
  1138. } else {
  1139. $(self.$activeItems).removeClass('active');
  1140. self.$activeItems = [$item.addClass('active')[0]];
  1141. }
  1142. // ensure control has focus
  1143. self.hideInput();
  1144. if (!this.isFocused) {
  1145. self.focus();
  1146. }
  1147. },
  1148. /**
  1149. * Sets the selected item in the dropdown menu
  1150. * of available options.
  1151. *
  1152. * @param {object} $object
  1153. * @param {boolean} scroll
  1154. * @param {boolean} animate
  1155. */
  1156. setActiveOption: function($option, scroll, animate) {
  1157. var height_menu, height_item, y;
  1158. var scroll_top, scroll_bottom;
  1159. var self = this;
  1160. if (self.$activeOption) self.$activeOption.removeClass('active');
  1161. self.$activeOption = null;
  1162. $option = $($option);
  1163. if (!$option.length) return;
  1164. self.$activeOption = $option.addClass('active');
  1165. if (scroll || !isset(scroll)) {
  1166. height_menu = self.$dropdown_content.height();
  1167. height_item = self.$activeOption.outerHeight(true);
  1168. scroll = self.$dropdown_content.scrollTop() || 0;
  1169. y = self.$activeOption.offset().top - self.$dropdown_content.offset().top + scroll;
  1170. scroll_top = y;
  1171. scroll_bottom = y - height_menu + height_item;
  1172. if (y + height_item > height_menu + scroll) {
  1173. self.$dropdown_content.stop().animate({scrollTop: scroll_bottom}, animate ? self.settings.scrollDuration : 0);
  1174. } else if (y < scroll) {
  1175. self.$dropdown_content.stop().animate({scrollTop: scroll_top}, animate ? self.settings.scrollDuration : 0);
  1176. }
  1177. }
  1178. },
  1179. /**
  1180. * Selects all items (CTRL + A).
  1181. */
  1182. selectAll: function() {
  1183. var self = this;
  1184. if (self.settings.mode === 'single') return;
  1185. self.$activeItems = Array.prototype.slice.apply(self.$control.children(':not(input)').addClass('active'));
  1186. if (self.$activeItems.length) {
  1187. self.hideInput();
  1188. self.close();
  1189. }
  1190. self.focus();
  1191. },
  1192. /**
  1193. * Hides the input element out of view, while
  1194. * retaining its focus.
  1195. */
  1196. hideInput: function() {
  1197. var self = this;
  1198. self.setTextboxValue('');
  1199. self.$control_input.css({opacity: 0, position: 'absolute', left: self.rtl ? 10000 : -10000});
  1200. self.isInputHidden = true;
  1201. },
  1202. /**
  1203. * Restores input visibility.
  1204. */
  1205. showInput: function() {
  1206. this.$control_input.css({opacity: 1, position: 'relative', left: 0});
  1207. this.isInputHidden = false;
  1208. },
  1209. /**
  1210. * Gives the control focus.
  1211. */
  1212. focus: function() {
  1213. var self = this;
  1214. if (self.isDisabled) return;
  1215. self.ignoreFocus = true;
  1216. self.$control_input[0].focus();
  1217. window.setTimeout(function() {
  1218. self.ignoreFocus = false;
  1219. self.onFocus();
  1220. }, 0);
  1221. },
  1222. /**
  1223. * Forces the control out of focus.
  1224. *
  1225. * @param {Element} dest
  1226. */
  1227. blur: function(dest) {
  1228. this.$control_input[0].blur();
  1229. this.onBlur(null, dest);
  1230. },
  1231. /**
  1232. * Returns a function that scores an object
  1233. * to show how good of a match it is to the
  1234. * provided query.
  1235. *
  1236. * @param {string} query
  1237. * @param {object} options
  1238. * @return {function}
  1239. */
  1240. getScoreFunction: function(query) {
  1241. return this.sifter.getScoreFunction(query, this.getSearchOptions());
  1242. },
  1243. /**
  1244. * Returns search options for sifter (the system
  1245. * for scoring and sorting results).
  1246. *
  1247. * @see https://github.com/brianreavis/sifter.js
  1248. * @return {object}
  1249. */
  1250. getSearchOptions: function() {
  1251. var settings = this.settings;
  1252. var sort = settings.sortField;
  1253. if (typeof sort === 'string') {
  1254. sort = [{field: sort}];
  1255. }
  1256. return {
  1257. fields : settings.searchField,
  1258. conjunction : settings.searchConjunction,
  1259. sort : sort
  1260. };
  1261. },
  1262. /**
  1263. * Searches through available options and returns
  1264. * a sorted array of matches.
  1265. *
  1266. * Returns an object containing:
  1267. *
  1268. * - query {string}
  1269. * - tokens {array}
  1270. * - total {int}
  1271. * - items {array}
  1272. *
  1273. * @param {string} query
  1274. * @returns {object}
  1275. */
  1276. search: function(query) {
  1277. var i, value, score, result, calculateScore;
  1278. var self = this;
  1279. var settings = self.settings;
  1280. var options = this.getSearchOptions();
  1281. // validate user-provided result scoring function
  1282. if (settings.score) {
  1283. calculateScore = self.settings.score.apply(this, [query]);
  1284. if (typeof calculateScore !== 'function') {
  1285. throw new Error('Selectize "score" setting must be a function that returns a function');
  1286. }
  1287. }
  1288. // perform search
  1289. if (query !== self.lastQuery) {
  1290. self.lastQuery = query;
  1291. result = self.sifter.search(query, $.extend(options, {score: calculateScore}));
  1292. self.currentResults = result;
  1293. } else {
  1294. result = $.extend(true, {}, self.currentResults);
  1295. }
  1296. // filter out selected items
  1297. if (settings.hideSelected) {
  1298. for (i = result.items.length - 1; i >= 0; i--) {
  1299. if (self.items.indexOf(hash_key(result.items[i].id)) !== -1) {
  1300. result.items.splice(i, 1);
  1301. }
  1302. }
  1303. }
  1304. return result;
  1305. },
  1306. /**
  1307. * Refreshes the list of available options shown
  1308. * in the autocomplete dropdown menu.
  1309. *
  1310. * @param {boolean} triggerDropdown
  1311. */
  1312. refreshOptions: function(triggerDropdown) {
  1313. var i, j, k, n, groups, groups_order, option, option_html, optgroup, optgroups, html, html_children, has_create_option;
  1314. var $active, $active_before, $create;
  1315. if (typeof triggerDropdown === 'undefined') {
  1316. triggerDropdown = true;
  1317. }
  1318. var self = this;
  1319. var query = $.trim(self.$control_input.val());
  1320. var results = self.search(query);
  1321. var $dropdown_content = self.$dropdown_content;
  1322. var active_before = self.$activeOption && hash_key(self.$activeOption.attr('data-value'));
  1323. // build markup
  1324. n = results.items.length;
  1325. if (typeof self.settings.maxOptions === 'number') {
  1326. n = Math.min(n, self.settings.maxOptions);
  1327. }
  1328. // render and group available options individually
  1329. groups = {};
  1330. groups_order = [];
  1331. for (i = 0; i < n; i++) {
  1332. option = self.options[results.items[i].id];
  1333. option_html = self.render('option', option);
  1334. optgroup = option[self.settings.optgroupField] || '';
  1335. optgroups = $.isArray(optgroup) ? optgroup : [optgroup];
  1336. for (j = 0, k = optgroups && optgroups.length; j < k; j++) {
  1337. optgroup = optgroups[j];
  1338. if (!self.optgroups.hasOwnProperty(optgroup)) {
  1339. optgroup = '';
  1340. }
  1341. if (!groups.hasOwnProperty(optgroup)) {
  1342. groups[optgroup] = [];
  1343. groups_order.push(optgroup);
  1344. }
  1345. groups[optgroup].push(option_html);
  1346. }
  1347. }
  1348. // sort optgroups
  1349. if (this.settings.lockOptgroupOrder) {
  1350. groups_order.sort(function(a, b) {
  1351. var a_order = self.optgroups[a].$order || 0;
  1352. var b_order = self.optgroups[b].$order || 0;
  1353. return a_order - b_order;
  1354. });
  1355. }
  1356. // render optgroup headers & join groups
  1357. html = [];
  1358. for (i = 0, n = groups_order.length; i < n; i++) {
  1359. optgroup = groups_order[i];
  1360. if (self.optgroups.hasOwnProperty(optgroup) && groups[optgroup].length) {
  1361. // render the optgroup header and options within it,
  1362. // then pass it to the wrapper template
  1363. html_children = self.render('optgroup_header', self.optgroups[optgroup]) || '';
  1364. html_children += groups[optgroup].join('');
  1365. html.push(self.render('optgroup', $.extend({}, self.optgroups[optgroup], {
  1366. html: html_children
  1367. })));
  1368. } else {
  1369. html.push(groups[optgroup].join(''));
  1370. }
  1371. }
  1372. $dropdown_content.html(html.join(''));
  1373. // highlight matching terms inline
  1374. if (self.settings.highlight && results.query.length && results.tokens.length) {
  1375. for (i = 0, n = results.tokens.length; i < n; i++) {
  1376. highlight($dropdown_content, results.tokens[i].regex);
  1377. }
  1378. }
  1379. // add "selected" class to selected options
  1380. if (!self.settings.hideSelected) {
  1381. for (i = 0, n = self.items.length; i < n; i++) {
  1382. self.getOption(self.items[i]).addClass('selected');
  1383. }
  1384. }
  1385. // add create option
  1386. has_create_option = self.canCreate(query);
  1387. if (has_create_option) {
  1388. $dropdown_content.prepend(self.render('option_create', {input: query}));
  1389. $create = $($dropdown_content[0].childNodes[0]);
  1390. }
  1391. // activate
  1392. self.hasOptions = results.items.length > 0 || has_create_option;
  1393. if (self.hasOptions) {
  1394. if (results.items.length > 0) {
  1395. $active_before = active_before && self.getOption(active_before);
  1396. if ($active_before && $active_before.length) {
  1397. $active = $active_before;
  1398. } else if (self.settings.mode === 'single' && self.items.length) {
  1399. $active = self.getOption(self.items[0]);
  1400. }
  1401. if (!$active || !$active.length) {
  1402. if ($create && !self.settings.addPrecedence) {
  1403. $active = self.getAdjacentOption($create, 1);
  1404. } else {
  1405. $active = $dropdown_content.find('[data-selectable]:first');
  1406. }
  1407. }
  1408. } else {
  1409. $active = $create;
  1410. }
  1411. self.setActiveOption($active);
  1412. if (triggerDropdown && !self.isOpen) { self.open(); }
  1413. } else {
  1414. self.setActiveOption(null);
  1415. if (triggerDropdown && self.isOpen) { self.close(); }
  1416. }
  1417. },
  1418. /**
  1419. * Adds an available option. If it already exists,
  1420. * nothing will happen. Note: this does not refresh
  1421. * the options list dropdown (use `refreshOptions`
  1422. * for that).
  1423. *
  1424. * Usage:
  1425. *
  1426. * this.addOption(data)
  1427. *
  1428. * @param {object|array} data
  1429. */
  1430. addOption: function(data) {
  1431. var i, n, value, self = this;
  1432. if ($.isArray(data)) {
  1433. for (i = 0, n = data.length; i < n; i++) {
  1434. self.addOption(data[i]);
  1435. }
  1436. return;
  1437. }
  1438. if (value = self.registerOption(data)) {
  1439. self.userOptions[value] = true;
  1440. self.lastQuery = null;
  1441. self.trigger('option_add', value, data);
  1442. }
  1443. },
  1444. /**
  1445. * Registers an option to the pool of options.
  1446. *
  1447. * @param {object} data
  1448. * @return {boolean|string}
  1449. */
  1450. registerOption: function(data) {
  1451. var key = hash_key(data[this.settings.valueField]);
  1452. if (!key || this.options.hasOwnProperty(key)) return false;
  1453. data.$order = data.$order || ++this.order;
  1454. this.options[key] = data;
  1455. return key;
  1456. },
  1457. /**
  1458. * Registers an option group to the pool of option groups.
  1459. *
  1460. * @param {object} data
  1461. * @return {boolean|string}
  1462. */
  1463. registerOptionGroup: function(data) {
  1464. var key = hash_key(data[this.settings.optgroupValueField]);
  1465. if (!key) return false;
  1466. data.$order = data.$order || ++this.order;
  1467. this.optgroups[key] = data;
  1468. return key;
  1469. },
  1470. /**
  1471. * Registers a new optgroup for options
  1472. * to be bucketed into.
  1473. *
  1474. * @param {string} id
  1475. * @param {object} data
  1476. */
  1477. addOptionGroup: function(id, data) {
  1478. data[this.settings.optgroupValueField] = id;
  1479. if (id = this.registerOptionGroup(data)) {
  1480. this.trigger('optgroup_add', id, data);
  1481. }
  1482. },
  1483. /**
  1484. * Removes an existing option group.
  1485. *
  1486. * @param {string} id
  1487. */
  1488. removeOptionGroup: function(id) {
  1489. if (this.optgroups.hasOwnProperty(id)) {
  1490. delete this.optgroups[id];
  1491. this.renderCache = {};
  1492. this.trigger('optgroup_remove', id);
  1493. }
  1494. },
  1495. /**
  1496. * Clears all existing option groups.
  1497. */
  1498. clearOptionGroups: function() {
  1499. this.optgroups = {};
  1500. this.renderCache = {};
  1501. this.trigger('optgroup_clear');
  1502. },
  1503. /**
  1504. * Updates an option available for selection. If
  1505. * it is visible in the selected items or options
  1506. * dropdown, it will be re-rendered automatically.
  1507. *
  1508. * @param {string} value
  1509. * @param {object} data
  1510. */
  1511. updateOption: function(value, data) {
  1512. var self = this;
  1513. var $item, $item_new;
  1514. var value_new, index_item, cache_items, cache_options, order_old;
  1515. value = hash_key(value);
  1516. value_new = hash_key(data[self.settings.valueField]);
  1517. // sanity checks
  1518. if (value === null) return;
  1519. if (!self.options.hasOwnProperty(value)) return;
  1520. if (typeof value_new !== 'string') throw new Error('Value must be set in option data');
  1521. order_old = self.options[value].$order;
  1522. // update references
  1523. if (value_new !== value) {
  1524. delete self.options[value];
  1525. index_item = self.items.indexOf(value);
  1526. if (index_item !== -1) {
  1527. self.items.splice(index_item, 1, value_new);
  1528. }
  1529. }
  1530. data.$order = data.$order || order_old;
  1531. self.options[value_new] = data;
  1532. // invalidate render cache
  1533. cache_items = self.renderCache['item'];
  1534. cache_options = self.renderCache['option'];
  1535. if (cache_items) {
  1536. delete cache_items[value];
  1537. delete cache_items[value_new];
  1538. }
  1539. if (cache_options) {
  1540. delete cache_options[value];
  1541. delete cache_options[value_new];
  1542. }
  1543. // update the item if it's selected
  1544. if (self.items.indexOf(value_new) !== -1) {
  1545. $item = self.getItem(value);
  1546. $item_new = $(self.render('item', data));
  1547. if ($item.hasClass('active')) $item_new.addClass('active');
  1548. $item.replaceWith($item_new);
  1549. }
  1550. // invalidate last query because we might have updated the sortField
  1551. self.lastQuery = null;
  1552. // update dropdown contents
  1553. if (self.isOpen) {
  1554. self.refreshOptions(false);
  1555. }
  1556. },
  1557. /**
  1558. * Removes a single option.
  1559. *
  1560. * @param {string} value
  1561. * @param {boolean} silent
  1562. */
  1563. removeOption: function(value, silent) {
  1564. var self = this;
  1565. value = hash_key(value);
  1566. var cache_items = self.renderCache['item'];
  1567. var cache_options = self.renderCache['option'];
  1568. if (cache_items) delete cache_items[value];
  1569. if (cache_options) delete cache_options[value];
  1570. delete self.userOptions[value];
  1571. delete self.options[value];
  1572. self.lastQuery = null;
  1573. self.trigger('option_remove', value);
  1574. self.removeItem(value, silent);
  1575. },
  1576. /**
  1577. * Clears all options.
  1578. */
  1579. clearOptions: function() {
  1580. var self = this;
  1581. self.loadedSearches = {};
  1582. self.userOptions = {};
  1583. self.renderCache = {};
  1584. self.options = self.sifter.items = {};
  1585. self.lastQuery = null;
  1586. self.trigger('option_clear');
  1587. self.clear();
  1588. },
  1589. /**
  1590. * Returns the jQuery element of the option
  1591. * matching the given value.
  1592. *
  1593. * @param {string} value
  1594. * @returns {object}
  1595. */
  1596. getOption: function(value) {
  1597. return this.getElementWithValue(value, this.$dropdown_content.find('[data-selectable]'));
  1598. },
  1599. /**
  1600. * Returns the jQuery element of the next or
  1601. * previous selectable option.
  1602. *
  1603. * @param {object} $option
  1604. * @param {int} direction can be 1 for next or -1 for previous
  1605. * @return {object}
  1606. */
  1607. getAdjacentOption: function($option, direction) {
  1608. var $options = this.$dropdown.find('[data-selectable]');
  1609. var index = $options.index($option) + direction;
  1610. return index >= 0 && index < $options.length ? $options.eq(index) : $();
  1611. },
  1612. /**
  1613. * Finds the first element with a "data-value" attribute
  1614. * that matches the given value.
  1615. *
  1616. * @param {mixed} value
  1617. * @param {object} $els
  1618. * @return {object}
  1619. */
  1620. getElementWithValue: function(value, $els) {
  1621. value = hash_key(value);
  1622. if (typeof value !== 'undefined' && value !== null) {
  1623. for (var i = 0, n = $els.length; i < n; i++) {
  1624. if ($els[i].getAttribute('data-value') === value) {
  1625. return $($els[i]);
  1626. }
  1627. }
  1628. }
  1629. return $();
  1630. },
  1631. /**
  1632. * Returns the jQuery element of the item
  1633. * matching the given value.
  1634. *
  1635. * @param {string} value
  1636. * @returns {object}
  1637. */
  1638. getItem: function(value) {
  1639. return this.getElementWithValue(value, this.$control.children());
  1640. },
  1641. /**
  1642. * "Selects" multiple items at once. Adds them to the list
  1643. * at the current caret position.
  1644. *
  1645. * @param {string} value
  1646. * @param {boolean} silent
  1647. */
  1648. addItems: function(values, silent) {
  1649. var items = $.isArray(values) ? values : [values];
  1650. for (var i = 0, n = items.length; i < n; i++) {
  1651. this.isPending = (i < n - 1);
  1652. this.addItem(items[i], silent);
  1653. }
  1654. },
  1655. /**
  1656. * "Selects" an item. Adds it to the list
  1657. * at the current caret position.
  1658. *
  1659. * @param {string} value
  1660. * @param {boolean} silent
  1661. */
  1662. addItem: function(value, silent) {
  1663. var events = silent ? [] : ['change'];
  1664. debounce_events(this, events, function() {
  1665. var $item, $option, $options;
  1666. var self = this;
  1667. var inputMode = self.settings.mode;
  1668. var i, active, value_next, wasFull;
  1669. value = hash_key(value);
  1670. if (self.items.indexOf(value) !== -1) {
  1671. if (inputMode === 'single') self.close();
  1672. return;
  1673. }
  1674. if (!self.options.hasOwnProperty(value)) return;
  1675. if (inputMode === 'single') self.clear();
  1676. if (inputMode === 'multi' && self.isFull()) return;
  1677. $item = $(self.render('item', self.options[value]));
  1678. wasFull = self.isFull();
  1679. self.items.splice(self.caretPos, 0, value);
  1680. self.insertAtCaret($item);
  1681. if (!self.isPending || (!wasFull && self.isFull())) {
  1682. self.refreshState();
  1683. }
  1684. if (self.isSetup) {
  1685. $options = self.$dropdown_content.find('[data-selectable]');
  1686. // update menu / remove the option (if this is not one item being added as part of series)
  1687. if (!self.isPending) {
  1688. $option = self.getOption(value);
  1689. value_next = self.getAdjacentOption($option, 1).attr('data-value');
  1690. self.refreshOptions(self.isFocused && inputMode !== 'single');
  1691. if (value_next) {
  1692. self.setActiveOption(self.getOption(value_next));
  1693. }
  1694. }
  1695. // hide the menu if the maximum number of items have been selected or no options are left
  1696. if (!$options.length || self.isFull()) {
  1697. self.close();
  1698. } else {
  1699. self.positionDropdown();
  1700. }
  1701. self.updatePlaceholder();
  1702. self.trigger('item_add', value, $item);
  1703. self.updateOriginalInput({silent: silent});
  1704. }
  1705. });
  1706. },
  1707. /**
  1708. * Removes the selected item matching
  1709. * the provided value.
  1710. *
  1711. * @param {string} value
  1712. */
  1713. removeItem: function(value, silent) {
  1714. var self = this;
  1715. var $item, i, idx;
  1716. $item = (typeof value === 'object') ? value : self.getItem(value);
  1717. value = hash_key($item.attr('data-value'));
  1718. i = self.items.indexOf(value);
  1719. if (i !== -1) {
  1720. $item.remove();
  1721. if ($item.hasClass('active')) {
  1722. idx = self.$activeItems.indexOf($item[0]);
  1723. self.$activeItems.splice(idx, 1);
  1724. }
  1725. self.items.splice(i, 1);
  1726. self.lastQuery = null;
  1727. if (!self.settings.persist && self.userOptions.hasOwnProperty(value)) {
  1728. self.removeOption(value, silent);
  1729. }
  1730. if (i < self.caretPos) {
  1731. self.setCaret(self.caretPos - 1);
  1732. }
  1733. self.refreshState();
  1734. self.updatePlaceholder();
  1735. self.updateOriginalInput({silent: silent});
  1736. self.positionDropdown();
  1737. self.trigger('item_remove', value, $item);
  1738. }
  1739. },
  1740. /**
  1741. * Invokes the `create` method provided in the
  1742. * selectize options that should provide the data
  1743. * for the new item, given the user input.
  1744. *
  1745. * Once this completes, it will be added
  1746. * to the item list.
  1747. *
  1748. * @param {string} value
  1749. * @param {boolean} [triggerDropdown]
  1750. * @param {function} [callback]
  1751. * @return {boolean}
  1752. */
  1753. createItem: function(input, triggerDropdown) {
  1754. var self = this;
  1755. var caret = self.caretPos;
  1756. input = input || $.trim(self.$control_input.val() || '');
  1757. var callback = arguments[arguments.length - 1];
  1758. if (typeof callback !== 'function') callback = function() {};
  1759. if (typeof triggerDropdown !== 'boolean') {
  1760. triggerDropdown = true;
  1761. }
  1762. if (!self.canCreate(input)) {
  1763. callback();
  1764. return false;
  1765. }
  1766. self.lock();
  1767. var setup = (typeof self.settings.create === 'function') ? this.settings.create : function(input) {
  1768. var data = {};
  1769. data[self.settings.labelField] = input;
  1770. data[self.settings.valueField] = input;
  1771. return data;
  1772. };
  1773. var create = once(function(data) {
  1774. self.unlock();
  1775. if (!data || typeof data !== 'object') return callback();
  1776. var value = hash_key(data[self.settings.valueField]);
  1777. if (typeof value !== 'string') return callback();
  1778. self.setTextboxValue('');
  1779. self.addOption(data);
  1780. self.setCaret(caret);
  1781. self.addItem(value);
  1782. self.refreshOptions(triggerDropdown && self.settings.mode !== 'single');
  1783. callback(data);
  1784. });
  1785. var output = setup.apply(this, [input, create]);
  1786. if (typeof output !== 'undefined') {
  1787. create(output);
  1788. }
  1789. return true;
  1790. },
  1791. /**
  1792. * Re-renders the selected item lists.
  1793. */
  1794. refreshItems: function() {
  1795. this.lastQuery = null;
  1796. if (this.isSetup) {
  1797. this.addItem(this.items);
  1798. }
  1799. this.refreshState();
  1800. this.updateOriginalInput();
  1801. },
  1802. /**
  1803. * Updates all state-dependent attributes
  1804. * and CSS classes.
  1805. */
  1806. refreshState: function() {
  1807. var invalid, self = this;
  1808. if (self.isRequired) {
  1809. if (self.items.length) self.isInvalid = false;
  1810. self.$control_input.prop('required', invalid);
  1811. }
  1812. self.refreshClasses();
  1813. },
  1814. /**
  1815. * Updates all state-dependent CSS classes.
  1816. */
  1817. refreshClasses: function() {
  1818. var self = this;
  1819. var isFull = self.isFull();
  1820. var isLocked = self.isLocked;
  1821. self.$wrapper
  1822. .toggleClass('rtl', self.rtl);
  1823. self.$control
  1824. .toggleClass('focus', self.isFocused)
  1825. .toggleClass('disabled', self.isDisabled)
  1826. .toggleClass('required', self.isRequired)
  1827. .toggleClass('invalid', self.isInvalid)
  1828. .toggleClass('locked', isLocked)
  1829. .toggleClass('full', isFull).toggleClass('not-full', !isFull)
  1830. .toggleClass('input-active', self.isFocused && !self.isInputHidden)
  1831. .toggleClass('dropdown-active', self.isOpen)
  1832. .toggleClass('has-options', !$.isEmptyObject(self.options))
  1833. .toggleClass('has-items', self.items.length > 0);
  1834. self.$control_input.data('grow', !isFull && !isLocked);
  1835. },
  1836. /**
  1837. * Determines whether or not more items can be added
  1838. * to the control without exceeding the user-defined maximum.
  1839. *
  1840. * @returns {boolean}
  1841. */
  1842. isFull: function() {
  1843. return this.settings.maxItems !== null && this.items.length >= this.settings.maxItems;
  1844. },
  1845. /**
  1846. * Refreshes the original <select> or <input>
  1847. * element to reflect the current state.
  1848. */
  1849. updateOriginalInput: function(opts) {
  1850. var i, n, options, label, self = this;
  1851. opts = opts || {};
  1852. if (self.tagType === TAG_SELECT) {
  1853. options = [];
  1854. for (i = 0, n = self.items.length; i < n; i++) {
  1855. label = self.options[self.items[i]][self.settings.labelField] || '';
  1856. options.push('<option value="' + escape_html(self.items[i]) + '" selected="selected">' + escape_html(label) + '</option>');
  1857. }
  1858. if (!options.length && !this.$input.attr('multiple')) {
  1859. options.push('<option value="" selected="selected"></option>');
  1860. }
  1861. self.$input.html(options.join(''));
  1862. } else {
  1863. self.$input.val(self.getValue());
  1864. self.$input.attr('value',self.$input.val());
  1865. }
  1866. if (self.isSetup) {
  1867. if (!opts.silent) {
  1868. self.trigger('change', self.$input.val());
  1869. }
  1870. }
  1871. },
  1872. /**
  1873. * Shows/hide the input placeholder depending
  1874. * on if there items in the list already.
  1875. */
  1876. updatePlaceholder: function() {
  1877. if (!this.settings.placeholder) return;
  1878. var $input = this.$control_input;
  1879. if (this.items.length) {
  1880. $input.removeAttr('placeholder');
  1881. } else {
  1882. $input.attr('placeholder', this.settings.placeholder);
  1883. }
  1884. $input.triggerHandler('update', {force: true});
  1885. },
  1886. /**
  1887. * Shows the autocomplete dropdown containing
  1888. * the available options.
  1889. */
  1890. open: function() {
  1891. var self = this;
  1892. if (self.isLocked || self.isOpen || (self.settings.mode === 'multi' && self.isFull())) return;
  1893. self.focus();
  1894. self.isOpen = true;
  1895. self.refreshState();
  1896. self.$dropdown.css({visibility: 'hidden', display: 'block'});
  1897. self.positionDropdown();
  1898. self.$dropdown.css({visibility: 'visible'});
  1899. self.trigger('dropdown_open', self.$dropdown);
  1900. },
  1901. /**
  1902. * Closes the autocomplete dropdown menu.
  1903. */
  1904. close: function() {
  1905. var self = this;
  1906. var trigger = self.isOpen;
  1907. if (self.settings.mode === 'single' && self.items.length) {
  1908. self.hideInput();
  1909. }
  1910. self.isOpen = false;
  1911. self.$dropdown.hide();
  1912. self.setActiveOption(null);
  1913. self.refreshState();
  1914. if (trigger) self.trigger('dropdown_close', self.$dropdown);
  1915. },
  1916. /**
  1917. * Calculates and applies the appropriate
  1918. * position of the dropdown.
  1919. */
  1920. positionDropdown: function() {
  1921. var $control = this.$control;
  1922. var offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position();
  1923. offset.top += $control.outerHeight(true);
  1924. this.$dropdown.css({
  1925. width : $control.outerWidth(),
  1926. top : offset.top,
  1927. left : offset.left
  1928. });
  1929. },
  1930. /**
  1931. * Resets / clears all selected items
  1932. * from the control.
  1933. *
  1934. * @param {boolean} silent
  1935. */
  1936. clear: function(silent) {
  1937. var self = this;
  1938. if (!self.items.length) return;
  1939. self.$control.children(':not(input)').remove();
  1940. self.items = [];
  1941. self.lastQuery = null;
  1942. self.setCaret(0);
  1943. self.setActiveItem(null);
  1944. self.updatePlaceholder();
  1945. self.updateOriginalInput({silent: silent});
  1946. self.refreshState();
  1947. self.showInput();
  1948. self.trigger('clear');
  1949. },
  1950. /**
  1951. * A helper method for inserting an element
  1952. * at the current caret position.
  1953. *
  1954. * @param {object} $el
  1955. */
  1956. insertAtCaret: function($el) {
  1957. var caret = Math.min(this.caretPos, this.items.length);
  1958. if (caret === 0) {
  1959. this.$control.prepend($el);
  1960. } else {
  1961. $(this.$control[0].childNodes[caret]).before($el);
  1962. }
  1963. this.setCaret(caret + 1);
  1964. },
  1965. /**
  1966. * Removes the current selected item(s).
  1967. *
  1968. * @param {object} e (optional)
  1969. * @returns {boolean}
  1970. */
  1971. deleteSelection: function(e) {
  1972. var i, n, direction, selection, values, caret, option_select, $option_select, $tail;
  1973. var self = this;
  1974. direction = (e && e.keyCode === KEY_BACKSPACE) ? -1 : 1;
  1975. selection = getSelection(self.$control_input[0]);
  1976. if (self.$activeOption && !self.settings.hideSelected) {
  1977. option_select = self.getAdjacentOption(self.$activeOption, -1).attr('data-value');
  1978. }
  1979. // determine items that will be removed
  1980. values = [];
  1981. if (self.$activeItems.length) {
  1982. $tail = self.$control.children('.active:' + (direction > 0 ? 'last' : 'first'));
  1983. caret = self.$control.children(':not(input)').index($tail);
  1984. if (direction > 0) { caret++; }
  1985. for (i = 0, n = self.$activeItems.length; i < n; i++) {
  1986. values.push($(self.$activeItems[i]).attr('data-value'));
  1987. }
  1988. if (e) {
  1989. e.preventDefault();
  1990. e.stopPropagation();
  1991. }
  1992. } else if ((self.isFocused || self.settings.mode === 'single') && self.items.length) {
  1993. if (direction < 0 && selection.start === 0 && selection.length === 0) {
  1994. values.push(self.items[self.caretPos - 1]);
  1995. } else if (direction > 0 && selection.start === self.$control_input.val().length) {
  1996. values.push(self.items[self.caretPos]);
  1997. }
  1998. }
  1999. // allow the callback to abort
  2000. if (!values.length || (typeof self.settings.onDelete === 'function' && self.settings.onDelete.apply(self, [values]) === false)) {
  2001. return false;
  2002. }
  2003. // perform removal
  2004. if (typeof caret !== 'undefined') {
  2005. self.setCaret(caret);
  2006. }
  2007. while (values.length) {
  2008. self.removeItem(values.pop());
  2009. }
  2010. self.showInput();
  2011. self.positionDropdown();
  2012. self.refreshOptions(true);
  2013. // select previous option
  2014. if (option_select) {
  2015. $option_select = self.getOption(option_select);
  2016. if ($option_select.length) {
  2017. self.setActiveOption($option_select);
  2018. }
  2019. }
  2020. return true;
  2021. },
  2022. /**
  2023. * Selects the previous / next item (depending
  2024. * on the `direction` argument).
  2025. *
  2026. * > 0 - right
  2027. * < 0 - left
  2028. *
  2029. * @param {int} direction
  2030. * @param {object} e (optional)
  2031. */
  2032. advanceSelection: function(direction, e) {
  2033. var tail, selection, idx, valueLength, cursorAtEdge, $tail;
  2034. var self = this;
  2035. if (direction === 0) return;
  2036. if (self.rtl) direction *= -1;
  2037. tail = direction > 0 ? 'last' : 'first';
  2038. selection = getSelection(self.$control_input[0]);
  2039. if (self.isFocused && !self.isInputHidden) {
  2040. valueLength = self.$control_input.val().length;
  2041. cursorAtEdge = direction < 0
  2042. ? selection.start === 0 && selection.length === 0
  2043. : selection.start === valueLength;
  2044. if (cursorAtEdge && !valueLength) {
  2045. self.advanceCaret(direction, e);
  2046. }
  2047. } else {
  2048. $tail = self.$control.children('.active:' + tail);
  2049. if ($tail.length) {
  2050. idx = self.$control.children(':not(input)').index($tail);
  2051. self.setActiveItem(null);
  2052. self.setCaret(direction > 0 ? idx + 1 : idx);
  2053. }
  2054. }
  2055. },
  2056. /**
  2057. * Moves the caret left / right.
  2058. *
  2059. * @param {int} direction
  2060. * @param {object} e (optional)
  2061. */
  2062. advanceCaret: function(direction, e) {
  2063. var self = this, fn, $adj;
  2064. if (direction === 0) return;
  2065. fn = direction > 0 ? 'next' : 'prev';
  2066. if (self.isShiftDown) {
  2067. $adj = self.$control_input[fn]();
  2068. if ($adj.length) {
  2069. self.hideInput();
  2070. self.setActiveItem($adj);
  2071. e && e.preventDefault();
  2072. }
  2073. } else {
  2074. self.setCaret(self.caretPos + direction);
  2075. }
  2076. },
  2077. /**
  2078. * Moves the caret to the specified index.
  2079. *
  2080. * @param {int} i
  2081. */
  2082. setCaret: function(i) {
  2083. var self = this;
  2084. if (self.settings.mode === 'single') {
  2085. i = self.items.length;
  2086. } else {
  2087. i = Math.max(0, Math.min(self.items.length, i));
  2088. }
  2089. if(!self.isPending) {
  2090. // the input must be moved by leaving it in place and moving the
  2091. // siblings, due to the fact that focus cannot be restored once lost
  2092. // on mobile webkit devices
  2093. var j, n, fn, $children, $child;
  2094. $children = self.$control.children(':not(input)');
  2095. for (j = 0, n = $children.length; j < n; j++) {
  2096. $child = $($children[j]).detach();
  2097. if (j < i) {
  2098. self.$control_input.before($child);
  2099. } else {
  2100. self.$control.append($child);
  2101. }
  2102. }
  2103. }
  2104. self.caretPos = i;
  2105. },
  2106. /**
  2107. * Disables user input on the control. Used while
  2108. * items are being asynchronously created.
  2109. */
  2110. lock: function() {
  2111. this.close();
  2112. this.isLocked = true;
  2113. this.refreshState();
  2114. },
  2115. /**
  2116. * Re-enables user input on the control.
  2117. */
  2118. unlock: function() {
  2119. this.isLocked = false;
  2120. this.refreshState();
  2121. },
  2122. /**
  2123. * Disables user input on the control completely.
  2124. * While disabled, it cannot receive focus.
  2125. */
  2126. disable: function() {
  2127. var self = this;
  2128. self.$input.prop('disabled', true);
  2129. self.$control_input.prop('disabled', true).prop('tabindex', -1);
  2130. self.isDisabled = true;
  2131. self.lock();
  2132. },
  2133. /**
  2134. * Enables the control so that it can respond
  2135. * to focus and user input.
  2136. */
  2137. enable: function() {
  2138. var self = this;
  2139. self.$input.prop('disabled', false);
  2140. self.$control_input.prop('disabled', false).prop('tabindex', self.tabIndex);
  2141. self.isDisabled = false;
  2142. self.unlock();
  2143. },
  2144. /**
  2145. * Completely destroys the control and
  2146. * unbinds all event listeners so that it can
  2147. * be garbage collected.
  2148. */
  2149. destroy: function() {
  2150. var self = this;
  2151. var eventNS = self.eventNS;
  2152. var revertSettings = self.revertSettings;
  2153. self.trigger('destroy');
  2154. self.off();
  2155. self.$wrapper.remove();
  2156. self.$dropdown.remove();
  2157. self.$input
  2158. .html('')
  2159. .append(revertSettings.$children)
  2160. .removeAttr('tabindex')
  2161. .removeClass('selectized')
  2162. .attr({tabindex: revertSettings.tabindex})
  2163. .show();
  2164. self.$control_input.removeData('grow');
  2165. self.$input.removeData('selectize');
  2166. $(window).off(eventNS);
  2167. $(document).off(eventNS);
  2168. $(document.body).off(eventNS);
  2169. delete self.$input[0].selectize;
  2170. },
  2171. /**
  2172. * A helper method for rendering "item" and
  2173. * "option" templates, given the data.
  2174. *
  2175. * @param {string} templateName
  2176. * @param {object} data
  2177. * @returns {string}
  2178. */
  2179. render: function(templateName, data) {
  2180. var value, id, label;
  2181. var html = '';
  2182. var cache = false;
  2183. var self = this;
  2184. var regex_tag = /^[\t \r\n]*<([a-z][a-z0-9\-_]*(?:\:[a-z][a-z0-9\-_]*)?)/i;
  2185. if (templateName === 'option' || templateName === 'item') {
  2186. value = hash_key(data[self.settings.valueField]);
  2187. cache = !!value;
  2188. }
  2189. // pull markup from cache if it exists
  2190. if (cache) {
  2191. if (!isset(self.renderCache[templateName])) {
  2192. self.renderCache[templateName] = {};
  2193. }
  2194. if (self.renderCache[templateName].hasOwnProperty(value)) {
  2195. return self.renderCache[templateName][value];
  2196. }
  2197. }
  2198. // render markup
  2199. html = self.settings.render[templateName].apply(this, [data, escape_html]);
  2200. // add mandatory attributes
  2201. if (templateName === 'option' || templateName === 'option_create') {
  2202. html = html.replace(regex_tag, '<$1 data-selectable');
  2203. }
  2204. if (templateName === 'optgroup') {
  2205. id = data[self.settings.optgroupValueField] || '';
  2206. html = html.replace(regex_tag, '<$1 data-group="' + escape_replace(escape_html(id)) + '"');
  2207. }
  2208. if (templateName === 'option' || templateName === 'item') {
  2209. html = html.replace(regex_tag, '<$1 data-value="' + escape_replace(escape_html(value || '')) + '"');
  2210. }
  2211. // update cache
  2212. if (cache) {
  2213. self.renderCache[templateName][value] = html;
  2214. }
  2215. return html;
  2216. },
  2217. /**
  2218. * Clears the render cache for a template. If
  2219. * no template is given, clears all render
  2220. * caches.
  2221. *
  2222. * @param {string} templateName
  2223. */
  2224. clearCache: function(templateName) {
  2225. var self = this;
  2226. if (typeof templateName === 'undefined') {
  2227. self.renderCache = {};
  2228. } else {
  2229. delete self.renderCache[templateName];
  2230. }
  2231. },
  2232. /**
  2233. * Determines whether or not to display the
  2234. * create item prompt, given a user input.
  2235. *
  2236. * @param {string} input
  2237. * @return {boolean}
  2238. */
  2239. canCreate: function(input) {
  2240. var self = this;
  2241. if (!self.settings.create) return false;
  2242. var filter = self.settings.createFilter;
  2243. return input.length
  2244. && (typeof filter !== 'function' || filter.apply(self, [input]))
  2245. && (typeof filter !== 'string' || new RegExp(filter).test(input))
  2246. && (!(filter instanceof RegExp) || filter.test(input));
  2247. }
  2248. });
  2249. Selectize.count = 0;
  2250. Selectize.defaults = {
  2251. options: [],
  2252. optgroups: [],
  2253. plugins: [],
  2254. delimiter: ',',
  2255. splitOn: null, // regexp or string for splitting up values from a paste command
  2256. persist: true,
  2257. diacritics: true,
  2258. create: false,
  2259. createOnBlur: false,
  2260. createFilter: null,
  2261. highlight: true,
  2262. openOnFocus: true,
  2263. maxOptions: 1000,
  2264. maxItems: null,
  2265. hideSelected: null,
  2266. addPrecedence: false,
  2267. selectOnTab: false,
  2268. preload: false,
  2269. allowEmptyOption: false,
  2270. closeAfterSelect: false,
  2271. scrollDuration: 60,
  2272. loadThrottle: 300,
  2273. loadingClass: 'loading',
  2274. dataAttr: 'data-data',
  2275. optgroupField: 'optgroup',
  2276. valueField: 'value',
  2277. labelField: 'text',
  2278. optgroupLabelField: 'label',
  2279. optgroupValueField: 'value',
  2280. lockOptgroupOrder: false,
  2281. sortField: '$order',
  2282. searchField: ['text'],
  2283. searchConjunction: 'and',
  2284. mode: null,
  2285. wrapperClass: 'selectize-control',
  2286. inputClass: 'selectize-input',
  2287. dropdownClass: 'selectize-dropdown',
  2288. dropdownContentClass: 'selectize-dropdown-content',
  2289. dropdownParent: null,
  2290. copyClassesToDropdown: true,
  2291. /*
  2292. load : null, // function(query, callback) { ... }
  2293. score : null, // function(search) { ... }
  2294. onInitialize : null, // function() { ... }
  2295. onChange : null, // function(value) { ... }
  2296. onItemAdd : null, // function(value, $item) { ... }
  2297. onItemRemove : null, // function(value) { ... }
  2298. onClear : null, // function() { ... }
  2299. onOptionAdd : null, // function(value, data) { ... }
  2300. onOptionRemove : null, // function(value) { ... }
  2301. onOptionClear : null, // function() { ... }
  2302. onOptionGroupAdd : null, // function(id, data) { ... }
  2303. onOptionGroupRemove : null, // function(id) { ... }
  2304. onOptionGroupClear : null, // function() { ... }
  2305. onDropdownOpen : null, // function($dropdown) { ... }
  2306. onDropdownClose : null, // function($dropdown) { ... }
  2307. onType : null, // function(str) { ... }
  2308. onDelete : null, // function(values) { ... }
  2309. */
  2310. render: {
  2311. /*
  2312. item: null,
  2313. optgroup: null,
  2314. optgroup_header: null,
  2315. option: null,
  2316. option_create: null
  2317. */
  2318. }
  2319. };
  2320. $.fn.selectize = function(settings_user) {
  2321. var defaults = $.fn.selectize.defaults;
  2322. var settings = $.extend({}, defaults, settings_user);
  2323. var attr_data = settings.dataAttr;
  2324. var field_label = settings.labelField;
  2325. var field_value = settings.valueField;
  2326. var field_optgroup = settings.optgroupField;
  2327. var field_optgroup_label = settings.optgroupLabelField;
  2328. var field_optgroup_value = settings.optgroupValueField;
  2329. var optionsMap = {};
  2330. /**
  2331. * Initializes selectize from a <input type="text"> element.
  2332. *
  2333. * @param {object} $input
  2334. * @param {object} settings_element
  2335. */
  2336. var init_textbox = function($input, settings_element) {
  2337. var i, n, values, option;
  2338. var data_raw = $input.attr(attr_data);
  2339. if (!data_raw) {
  2340. var value = $.trim($input.val() || '');
  2341. if (!settings.allowEmptyOption && !value.length) return;
  2342. values = value.split(settings.delimiter);
  2343. for (i = 0, n = values.length; i < n; i++) {
  2344. option = {};
  2345. option[field_label] = values[i];
  2346. option[field_value] = values[i];
  2347. settings_element.options.push(option);
  2348. }
  2349. settings_element.items = values;
  2350. } else {
  2351. settings_element.options = JSON.parse(data_raw);
  2352. for (i = 0, n = settings_element.options.length; i < n; i++) {
  2353. settings_element.items.push(settings_element.options[i][field_value]);
  2354. }
  2355. }
  2356. };
  2357. /**
  2358. * Initializes selectize from a <select> element.
  2359. *
  2360. * @param {object} $input
  2361. * @param {object} settings_element
  2362. */
  2363. var init_select = function($input, settings_element) {
  2364. var i, n, tagName, $children, order = 0;
  2365. var options = settings_element.options;
  2366. var readData = function($el) {
  2367. var data = attr_data && $el.attr(attr_data);
  2368. if (typeof data === 'string' && data.length) {
  2369. return JSON.parse(data);
  2370. }
  2371. return null;
  2372. };
  2373. var addOption = function($option, group) {
  2374. $option = $($option);
  2375. var value = hash_key($option.attr('value'));
  2376. if (!value && !settings.allowEmptyOption) return;
  2377. // if the option already exists, it's probably been
  2378. // duplicated in another optgroup. in this case, push
  2379. // the current group to the "optgroup" property on the
  2380. // existing option so that it's rendered in both places.
  2381. if (optionsMap.hasOwnProperty(value)) {
  2382. if (group) {
  2383. var arr = optionsMap[value][field_optgroup];
  2384. if (!arr) {
  2385. optionsMap[value][field_optgroup] = group;
  2386. } else if (!$.isArray(arr)) {
  2387. optionsMap[value][field_optgroup] = [arr, group];
  2388. } else {
  2389. arr.push(group);
  2390. }
  2391. }
  2392. return;
  2393. }
  2394. var option = readData($option) || {};
  2395. option[field_label] = option[field_label] || $option.text();
  2396. option[field_value] = option[field_value] || value;
  2397. option[field_optgroup] = option[field_optgroup] || group;
  2398. optionsMap[value] = option;
  2399. options.push(option);
  2400. if ($option.is(':selected')) {
  2401. settings_element.items.push(value);
  2402. }
  2403. };
  2404. var addGroup = function($optgroup) {
  2405. var i, n, id, optgroup, $options;
  2406. $optgroup = $($optgroup);
  2407. id = $optgroup.attr('label');
  2408. if (id) {
  2409. optgroup = readData($optgroup) || {};
  2410. optgroup[field_optgroup_label] = id;
  2411. optgroup[field_optgroup_value] = id;
  2412. settings_element.optgroups.push(optgroup);
  2413. }
  2414. $options = $('option', $optgroup);
  2415. for (i = 0, n = $options.length; i < n; i++) {
  2416. addOption($options[i], id);
  2417. }
  2418. };
  2419. settings_element.maxItems = $input.attr('multiple') ? null : 1;
  2420. $children = $input.children();
  2421. for (i = 0, n = $children.length; i < n; i++) {
  2422. tagName = $children[i].tagName.toLowerCase();
  2423. if (tagName === 'optgroup') {
  2424. addGroup($children[i]);
  2425. } else if (tagName === 'option') {
  2426. addOption($children[i]);
  2427. }
  2428. }
  2429. };
  2430. return this.each(function() {
  2431. if (this.selectize) return;
  2432. var instance;
  2433. var $input = $(this);
  2434. var tag_name = this.tagName.toLowerCase();
  2435. var placeholder = $input.attr('placeholder') || $input.attr('data-placeholder');
  2436. if (!placeholder && !settings.allowEmptyOption) {
  2437. placeholder = $input.children('option[value=""]').text();
  2438. }
  2439. var settings_element = {
  2440. 'placeholder' : placeholder,
  2441. 'options' : [],
  2442. 'optgroups' : [],
  2443. 'items' : []
  2444. };
  2445. if (tag_name === 'select') {
  2446. init_select($input, settings_element);
  2447. } else {
  2448. init_textbox($input, settings_element);
  2449. }
  2450. instance = new Selectize($input, $.extend(true, {}, defaults, settings_element, settings_user));
  2451. });
  2452. };
  2453. $.fn.selectize.defaults = Selectize.defaults;
  2454. $.fn.selectize.support = {
  2455. validity: SUPPORTS_VALIDITY_API
  2456. };
  2457. Selectize.define('drag_drop', function(options) {
  2458. if (!$.fn.sortable) throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".');
  2459. if (this.settings.mode !== 'multi') return;
  2460. var self = this;
  2461. self.lock = (function() {
  2462. var original = self.lock;
  2463. return function() {
  2464. var sortable = self.$control.data('sortable');
  2465. if (sortable) sortable.disable();
  2466. return original.apply(self, arguments);
  2467. };
  2468. })();
  2469. self.unlock = (function() {
  2470. var original = self.unlock;
  2471. return function() {
  2472. var sortable = self.$control.data('sortable');
  2473. if (sortable) sortable.enable();
  2474. return original.apply(self, arguments);
  2475. };
  2476. })();
  2477. self.setup = (function() {
  2478. var original = self.setup;
  2479. return function() {
  2480. original.apply(this, arguments);
  2481. var $control = self.$control.sortable({
  2482. items: '[data-value]',
  2483. forcePlaceholderSize: true,
  2484. disabled: self.isLocked,
  2485. start: function(e, ui) {
  2486. ui.placeholder.css('width', ui.helper.css('width'));
  2487. $control.css({overflow: 'visible'});
  2488. },
  2489. stop: function() {
  2490. $control.css({overflow: 'hidden'});
  2491. var active = self.$activeItems ? self.$activeItems.slice() : null;
  2492. var values = [];
  2493. $control.children('[data-value]').each(function() {
  2494. values.push($(this).attr('data-value'));
  2495. });
  2496. self.setValue(values);
  2497. self.setActiveItem(active);
  2498. }
  2499. });
  2500. };
  2501. })();
  2502. });
  2503. Selectize.define('dropdown_header', function(options) {
  2504. var self = this;
  2505. options = $.extend({
  2506. title : 'Untitled',
  2507. headerClass : 'selectize-dropdown-header',
  2508. titleRowClass : 'selectize-dropdown-header-title',
  2509. labelClass : 'selectize-dropdown-header-label',
  2510. closeClass : 'selectize-dropdown-header-close',
  2511. html: function(data) {
  2512. return (
  2513. '<div class="' + data.headerClass + '">' +
  2514. '<div class="' + data.titleRowClass + '">' +
  2515. '<span class="' + data.labelClass + '">' + data.title + '</span>' +
  2516. '<a href="javascript:void(0)" class="' + data.closeClass + '">&times;</a>' +
  2517. '</div>' +
  2518. '</div>'
  2519. );
  2520. }
  2521. }, options);
  2522. self.setup = (function() {
  2523. var original = self.setup;
  2524. return function() {
  2525. original.apply(self, arguments);
  2526. self.$dropdown_header = $(options.html(options));
  2527. self.$dropdown.prepend(self.$dropdown_header);
  2528. };
  2529. })();
  2530. });
  2531. Selectize.define('optgroup_columns', function(options) {
  2532. var self = this;
  2533. options = $.extend({
  2534. equalizeWidth : true,
  2535. equalizeHeight : true
  2536. }, options);
  2537. this.getAdjacentOption = function($option, direction) {
  2538. var $options = $option.closest('[data-group]').find('[data-selectable]');
  2539. var index = $options.index($option) + direction;
  2540. return index >= 0 && index < $options.length ? $options.eq(index) : $();
  2541. };
  2542. this.onKeyDown = (function() {
  2543. var original = self.onKeyDown;
  2544. return function(e) {
  2545. var index, $option, $options, $optgroup;
  2546. if (this.isOpen && (e.keyCode === KEY_LEFT || e.keyCode === KEY_RIGHT)) {
  2547. self.ignoreHover = true;
  2548. $optgroup = this.$activeOption.closest('[data-group]');
  2549. index = $optgroup.find('[data-selectable]').index(this.$activeOption);
  2550. if(e.keyCode === KEY_LEFT) {
  2551. $optgroup = $optgroup.prev('[data-group]');
  2552. } else {
  2553. $optgroup = $optgroup.next('[data-group]');
  2554. }
  2555. $options = $optgroup.find('[data-selectable]');
  2556. $option = $options.eq(Math.min($options.length - 1, index));
  2557. if ($option.length) {
  2558. this.setActiveOption($option);
  2559. }
  2560. return;
  2561. }
  2562. return original.apply(this, arguments);
  2563. };
  2564. })();
  2565. var getScrollbarWidth = function() {
  2566. var div;
  2567. var width = getScrollbarWidth.width;
  2568. var doc = document;
  2569. if (typeof width === 'undefined') {
  2570. div = doc.createElement('div');
  2571. div.innerHTML = '<div style="width:50px;height:50px;position:absolute;left:-50px;top:-50px;overflow:auto;"><div style="width:1px;height:100px;"></div></div>';
  2572. div = div.firstChild;
  2573. doc.body.appendChild(div);
  2574. width = getScrollbarWidth.width = div.offsetWidth - div.clientWidth;
  2575. doc.body.removeChild(div);
  2576. }
  2577. return width;
  2578. };
  2579. var equalizeSizes = function() {
  2580. var i, n, height_max, width, width_last, width_parent, $optgroups;
  2581. $optgroups = $('[data-group]', self.$dropdown_content);
  2582. n = $optgroups.length;
  2583. if (!n || !self.$dropdown_content.width()) return;
  2584. if (options.equalizeHeight) {
  2585. height_max = 0;
  2586. for (i = 0; i < n; i++) {
  2587. height_max = Math.max(height_max, $optgroups.eq(i).height());
  2588. }
  2589. $optgroups.css({height: height_max});
  2590. }
  2591. if (options.equalizeWidth) {
  2592. width_parent = self.$dropdown_content.innerWidth() - getScrollbarWidth();
  2593. width = Math.round(width_parent / n);
  2594. $optgroups.css({width: width});
  2595. if (n > 1) {
  2596. width_last = width_parent - width * (n - 1);
  2597. $optgroups.eq(n - 1).css({width: width_last});
  2598. }
  2599. }
  2600. };
  2601. if (options.equalizeHeight || options.equalizeWidth) {
  2602. hook.after(this, 'positionDropdown', equalizeSizes);
  2603. hook.after(this, 'refreshOptions', equalizeSizes);
  2604. }
  2605. });
  2606. Selectize.define('remove_button', function(options) {
  2607. if (this.settings.mode === 'single') return;
  2608. options = $.extend({
  2609. label : '&times;',
  2610. title : 'Remove',
  2611. className : 'remove',
  2612. append : true
  2613. }, options);
  2614. var self = this;
  2615. var html = '<a href="javascript:void(0)" class="' + options.className + '" tabindex="-1" title="' + escape_html(options.title) + '">' + options.label + '</a>';
  2616. /**
  2617. * Appends an element as a child (with raw HTML).
  2618. *
  2619. * @param {string} html_container
  2620. * @param {string} html_element
  2621. * @return {string}
  2622. */
  2623. var append = function(html_container, html_element) {
  2624. var pos = html_container.search(/(<\/[^>]+>\s*)$/);
  2625. return html_container.substring(0, pos) + html_element + html_container.substring(pos);
  2626. };
  2627. this.setup = (function() {
  2628. var original = self.setup;
  2629. return function() {
  2630. // override the item rendering method to add the button to each
  2631. if (options.append) {
  2632. var render_item = self.settings.render.item;
  2633. self.settings.render.item = function(data) {
  2634. return append(render_item.apply(this, arguments), html);
  2635. };
  2636. }
  2637. original.apply(this, arguments);
  2638. // add event listener
  2639. this.$control.on('click', '.' + options.className, function(e) {
  2640. e.preventDefault();
  2641. if (self.isLocked) return;
  2642. var $item = $(e.currentTarget).parent();
  2643. self.setActiveItem($item);
  2644. if (self.deleteSelection()) {
  2645. self.setCaret(self.items.length);
  2646. }
  2647. });
  2648. };
  2649. })();
  2650. });
  2651. Selectize.define('restore_on_backspace', function(options) {
  2652. var self = this;
  2653. options.text = options.text || function(option) {
  2654. return option[this.settings.labelField];
  2655. };
  2656. this.onKeyDown = (function() {
  2657. var original = self.onKeyDown;
  2658. return function(e) {
  2659. var index, option;
  2660. if (e.keyCode === KEY_BACKSPACE && this.$control_input.val() === '' && !this.$activeItems.length) {
  2661. index = this.caretPos - 1;
  2662. if (index >= 0 && index < this.items.length) {
  2663. option = this.options[this.items[index]];
  2664. if (this.deleteSelection(e)) {
  2665. this.setTextboxValue(options.text.apply(this, [option]));
  2666. this.refreshOptions(true);
  2667. }
  2668. e.preventDefault();
  2669. return;
  2670. }
  2671. }
  2672. return original.apply(this, arguments);
  2673. };
  2674. })();
  2675. });
  2676. return Selectize;
  2677. }));