Built files from Bizgaze WebServer
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

selectize.js 104KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891
  1. /**
  2. * sifter.js
  3. * Copyright (c) 2013 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. (function(root, factory) {
  17. if (typeof define === 'function' && define.amd) {
  18. define('sifter', factory);
  19. } else if (typeof exports === 'object') {
  20. module.exports = factory();
  21. } else {
  22. root.Sifter = factory();
  23. }
  24. }(this, function() {
  25. /**
  26. * Textually searches arrays and hashes of objects
  27. * by property (or multiple properties). Designed
  28. * specifically for autocomplete.
  29. *
  30. * @constructor
  31. * @param {array|object} items
  32. * @param {object} items
  33. */
  34. var Sifter = function(items, settings) {
  35. this.items = items;
  36. this.settings = settings || {diacritics: true};
  37. };
  38. /**
  39. * Splits a search string into an array of individual
  40. * regexps to be used to match results.
  41. *
  42. * @param {string} query
  43. * @returns {array}
  44. */
  45. Sifter.prototype.tokenize = function(query) {
  46. query = trim(String(query || '').toLowerCase());
  47. if (!query || !query.length) return [];
  48. var i, n, regex, letter;
  49. var tokens = [];
  50. var words = query.split(/ +/);
  51. for (i = 0, n = words.length; i < n; i++) {
  52. regex = escape_regex(words[i]);
  53. if (this.settings.diacritics) {
  54. for (letter in DIACRITICS) {
  55. if (DIACRITICS.hasOwnProperty(letter)) {
  56. regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]);
  57. }
  58. }
  59. }
  60. tokens.push({
  61. string : words[i],
  62. regex : new RegExp(regex, 'i')
  63. });
  64. }
  65. return tokens;
  66. };
  67. /**
  68. * Iterates over arrays and hashes.
  69. *
  70. * ```
  71. * this.iterator(this.items, function(item, id) {
  72. * // invoked for each item
  73. * });
  74. * ```
  75. *
  76. * @param {array|object} object
  77. */
  78. Sifter.prototype.iterator = function(object, callback) {
  79. var iterator;
  80. if (is_array(object)) {
  81. iterator = Array.prototype.forEach || function(callback) {
  82. for (var i = 0, n = this.length; i < n; i++) {
  83. callback(this[i], i, this);
  84. }
  85. };
  86. } else {
  87. iterator = function(callback) {
  88. for (var key in this) {
  89. if (this.hasOwnProperty(key)) {
  90. callback(this[key], key, this);
  91. }
  92. }
  93. };
  94. }
  95. iterator.apply(object, [callback]);
  96. };
  97. /**
  98. * Returns a function to be used to score individual results.
  99. *
  100. * Good matches will have a higher score than poor matches.
  101. * If an item is not a match, 0 will be returned by the function.
  102. *
  103. * @param {object|string} search
  104. * @param {object} options (optional)
  105. * @returns {function}
  106. */
  107. Sifter.prototype.getScoreFunction = function(search, options) {
  108. var self, fields, tokens, token_count, nesting;
  109. self = this;
  110. search = self.prepareSearch(search, options);
  111. tokens = search.tokens;
  112. fields = search.options.fields;
  113. token_count = tokens.length;
  114. nesting = search.options.nesting;
  115. /**
  116. * Calculates how close of a match the
  117. * given value is against a search token.
  118. *
  119. * @param {mixed} value
  120. * @param {object} token
  121. * @return {number}
  122. */
  123. var scoreValue = function(value, token) {
  124. var score, pos;
  125. if (!value) return 0;
  126. value = String(value || '');
  127. pos = value.search(token.regex);
  128. if (pos === -1) return 0;
  129. score = token.string.length / value.length;
  130. if (pos === 0) score += 0.5;
  131. return score;
  132. };
  133. /**
  134. * Calculates the score of an object
  135. * against the search query.
  136. *
  137. * @param {object} token
  138. * @param {object} data
  139. * @return {number}
  140. */
  141. var scoreObject = (function() {
  142. var field_count = fields.length;
  143. if (!field_count) {
  144. return function() { return 0; };
  145. }
  146. if (field_count === 1) {
  147. return function(token, data) {
  148. return scoreValue(getattr(data, fields[0], nesting), token);
  149. };
  150. }
  151. return function(token, data) {
  152. for (var i = 0, sum = 0; i < field_count; i++) {
  153. sum += scoreValue(getattr(data, fields[i], nesting), token);
  154. }
  155. return sum / field_count;
  156. };
  157. })();
  158. if (!token_count) {
  159. return function() { return 0; };
  160. }
  161. if (token_count === 1) {
  162. return function(data) {
  163. return scoreObject(tokens[0], data);
  164. };
  165. }
  166. if (search.options.conjunction === 'and') {
  167. return function(data) {
  168. var score;
  169. for (var i = 0, sum = 0; i < token_count; i++) {
  170. score = scoreObject(tokens[i], data);
  171. if (score <= 0) return 0;
  172. sum += score;
  173. }
  174. return sum / token_count;
  175. };
  176. } else {
  177. return function(data) {
  178. for (var i = 0, sum = 0; i < token_count; i++) {
  179. sum += scoreObject(tokens[i], data);
  180. }
  181. return sum / token_count;
  182. };
  183. }
  184. };
  185. /**
  186. * Returns a function that can be used to compare two
  187. * results, for sorting purposes. If no sorting should
  188. * be performed, `null` will be returned.
  189. *
  190. * @param {string|object} search
  191. * @param {object} options
  192. * @return function(a,b)
  193. */
  194. Sifter.prototype.getSortFunction = function(search, options) {
  195. var i, n, self, field, fields, fields_count, multiplier, multipliers, get_field, implicit_score, sort;
  196. self = this;
  197. search = self.prepareSearch(search, options);
  198. sort = (!search.query && options.sort_empty) || options.sort;
  199. /**
  200. * Fetches the specified sort field value
  201. * from a search result item.
  202. *
  203. * @param {string} name
  204. * @param {object} result
  205. * @return {mixed}
  206. */
  207. get_field = function(name, result) {
  208. if (name === '$score') return result.score;
  209. return getattr(self.items[result.id], name, options.nesting);
  210. };
  211. // parse options
  212. fields = [];
  213. if (sort) {
  214. for (i = 0, n = sort.length; i < n; i++) {
  215. if (search.query || sort[i].field !== '$score') {
  216. fields.push(sort[i]);
  217. }
  218. }
  219. }
  220. // the "$score" field is implied to be the primary
  221. // sort field, unless it's manually specified
  222. if (search.query) {
  223. implicit_score = true;
  224. for (i = 0, n = fields.length; i < n; i++) {
  225. if (fields[i].field === '$score') {
  226. implicit_score = false;
  227. break;
  228. }
  229. }
  230. if (implicit_score) {
  231. fields.unshift({field: '$score', direction: 'desc'});
  232. }
  233. } else {
  234. for (i = 0, n = fields.length; i < n; i++) {
  235. if (fields[i].field === '$score') {
  236. fields.splice(i, 1);
  237. break;
  238. }
  239. }
  240. }
  241. multipliers = [];
  242. for (i = 0, n = fields.length; i < n; i++) {
  243. multipliers.push(fields[i].direction === 'desc' ? -1 : 1);
  244. }
  245. // build function
  246. fields_count = fields.length;
  247. if (!fields_count) {
  248. return null;
  249. } else if (fields_count === 1) {
  250. field = fields[0].field;
  251. multiplier = multipliers[0];
  252. return function(a, b) {
  253. return multiplier * cmp(
  254. get_field(field, a),
  255. get_field(field, b)
  256. );
  257. };
  258. } else {
  259. return function(a, b) {
  260. var i, result, a_value, b_value, field;
  261. for (i = 0; i < fields_count; i++) {
  262. field = fields[i].field;
  263. result = multipliers[i] * cmp(
  264. get_field(field, a),
  265. get_field(field, b)
  266. );
  267. if (result) return result;
  268. }
  269. return 0;
  270. };
  271. }
  272. };
  273. /**
  274. * Parses a search query and returns an object
  275. * with tokens and fields ready to be populated
  276. * with results.
  277. *
  278. * @param {string} query
  279. * @param {object} options
  280. * @returns {object}
  281. */
  282. Sifter.prototype.prepareSearch = function(query, options) {
  283. if (typeof query === 'object') return query;
  284. options = extend({}, options);
  285. var option_fields = options.fields;
  286. var option_sort = options.sort;
  287. var option_sort_empty = options.sort_empty;
  288. if (option_fields && !is_array(option_fields)) options.fields = [option_fields];
  289. if (option_sort && !is_array(option_sort)) options.sort = [option_sort];
  290. if (option_sort_empty && !is_array(option_sort_empty)) options.sort_empty = [option_sort_empty];
  291. return {
  292. options : options,
  293. query : String(query || '').toLowerCase(),
  294. tokens : this.tokenize(query),
  295. total : 0,
  296. items : []
  297. };
  298. };
  299. /**
  300. * Searches through all items and returns a sorted array of matches.
  301. *
  302. * The `options` parameter can contain:
  303. *
  304. * - fields {string|array}
  305. * - sort {array}
  306. * - score {function}
  307. * - filter {bool}
  308. * - limit {integer}
  309. *
  310. * Returns an object containing:
  311. *
  312. * - options {object}
  313. * - query {string}
  314. * - tokens {array}
  315. * - total {int}
  316. * - items {array}
  317. *
  318. * @param {string} query
  319. * @param {object} options
  320. * @returns {object}
  321. */
  322. Sifter.prototype.search = function(query, options) {
  323. var self = this, value, score, search, calculateScore;
  324. var fn_sort;
  325. var fn_score;
  326. search = this.prepareSearch(query, options);
  327. options = search.options;
  328. query = search.query;
  329. // generate result scoring function
  330. fn_score = options.score || self.getScoreFunction(search);
  331. // perform search and sort
  332. if (query.length) {
  333. self.iterator(self.items, function(item, id) {
  334. score = fn_score(item);
  335. if (options.filter === false || score > 0) {
  336. search.items.push({'score': score, 'id': id});
  337. }
  338. });
  339. } else {
  340. self.iterator(self.items, function(item, id) {
  341. search.items.push({'score': 1, 'id': id});
  342. });
  343. }
  344. fn_sort = self.getSortFunction(search, options);
  345. if (fn_sort) search.items.sort(fn_sort);
  346. // apply limits
  347. search.total = search.items.length;
  348. if (typeof options.limit === 'number') {
  349. search.items = search.items.slice(0, options.limit);
  350. }
  351. return search;
  352. };
  353. // utilities
  354. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  355. var cmp = function(a, b) {
  356. if (typeof a === 'number' && typeof b === 'number') {
  357. return a > b ? 1 : (a < b ? -1 : 0);
  358. }
  359. a = asciifold(String(a || ''));
  360. b = asciifold(String(b || ''));
  361. if (a > b) return 1;
  362. if (b > a) return -1;
  363. return 0;
  364. };
  365. var extend = function(a, b) {
  366. var i, n, k, object;
  367. for (i = 1, n = arguments.length; i < n; i++) {
  368. object = arguments[i];
  369. if (!object) continue;
  370. for (k in object) {
  371. if (object.hasOwnProperty(k)) {
  372. a[k] = object[k];
  373. }
  374. }
  375. }
  376. return a;
  377. };
  378. /**
  379. * A property getter resolving dot-notation
  380. * @param {Object} obj The root object to fetch property on
  381. * @param {String} name The optionally dotted property name to fetch
  382. * @param {Boolean} nesting Handle nesting or not
  383. * @return {Object} The resolved property value
  384. */
  385. var getattr = function(obj, name, nesting) {
  386. if (!obj || !name) return;
  387. if (!nesting) return obj[name];
  388. var names = name.split(".");
  389. while(names.length && (obj = obj[names.shift()]));
  390. return obj;
  391. };
  392. var trim = function(str) {
  393. return (str + '').replace(/^\s+|\s+$|/g, '');
  394. };
  395. var escape_regex = function(str) {
  396. return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
  397. };
  398. var is_array = Array.isArray || (typeof $ !== 'undefined' && $.isArray) || function(object) {
  399. return Object.prototype.toString.call(object) === '[object Array]';
  400. };
  401. var DIACRITICS = {
  402. 'a': '[aḀḁĂăÂâǍǎȺⱥȦȧẠạÄäÀàÁáĀāÃãÅåąĄÃąĄ]',
  403. 'b': '[b␢βΒB฿𐌁ᛒ]',
  404. 'c': '[cĆćĈĉČčĊċC̄c̄ÇçḈḉȻȼƇƈɕᴄCc]',
  405. 'd': '[dĎďḊḋḐḑḌḍḒḓḎḏĐđD̦d̦ƉɖƊɗƋƌᵭᶁᶑȡᴅDdð]',
  406. 'e': '[eÉéÈèÊêḘḙĚěĔĕẼẽḚḛẺẻĖėËëĒēȨȩĘęᶒɆɇȄȅẾếỀềỄễỂểḜḝḖḗḔḕȆȇẸẹỆệⱸᴇEeɘǝƏƐε]',
  407. 'f': '[fƑƒḞḟ]',
  408. 'g': '[gɢ₲ǤǥĜĝĞğĢģƓɠĠġ]',
  409. 'h': '[hĤĥĦħḨḩẖẖḤḥḢḣɦʰǶƕ]',
  410. 'i': '[iÍíÌìĬĭÎîǏǐÏïḮḯĨĩĮįĪīỈỉȈȉȊȋỊịḬḭƗɨɨ̆ᵻᶖİiIıɪIi]',
  411. 'j': '[jȷĴĵɈɉʝɟʲ]',
  412. 'k': '[kƘƙꝀꝁḰḱǨǩḲḳḴḵκϰ₭]',
  413. 'l': '[lŁłĽľĻļĹĺḶḷḸḹḼḽḺḻĿŀȽƚⱠⱡⱢɫɬᶅɭȴʟLl]',
  414. 'n': '[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲȠƞᵰᶇɳȵɴNnŊŋ]',
  415. 'o': '[oØøÖöÓóÒòÔôǑǒŐőŎŏȮȯỌọƟɵƠơỎỏŌōÕõǪǫȌȍՕօ]',
  416. 'p': '[pṔṕṖṗⱣᵽƤƥᵱ]',
  417. 'q': '[qꝖꝗʠɊɋꝘꝙq̃]',
  418. 'r': '[rŔŕɌɍŘřŖŗṘṙȐȑȒȓṚṛⱤɽ]',
  419. 's': '[sŚśṠṡṢṣꞨꞩŜŝŠšŞşȘșS̈s̈]',
  420. 't': '[tŤťṪṫŢţṬṭƮʈȚțṰṱṮṯƬƭ]',
  421. 'u': '[uŬŭɄʉỤụÜüÚúÙùÛûǓǔŰűŬŭƯưỦủŪūŨũŲųȔȕ∪]',
  422. 'v': '[vṼṽṾṿƲʋꝞꝟⱱʋ]',
  423. 'w': '[wẂẃẀẁŴŵẄẅẆẇẈẉ]',
  424. 'x': '[xẌẍẊẋχ]',
  425. 'y': '[yÝýỲỳŶŷŸÿỸỹẎẏỴỵɎɏƳƴ]',
  426. 'z': '[zŹźẐẑŽžŻżẒẓẔẕƵƶ]'
  427. };
  428. var asciifold = (function() {
  429. var i, n, k, chunk;
  430. var foreignletters = '';
  431. var lookup = {};
  432. for (k in DIACRITICS) {
  433. if (DIACRITICS.hasOwnProperty(k)) {
  434. chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1);
  435. foreignletters += chunk;
  436. for (i = 0, n = chunk.length; i < n; i++) {
  437. lookup[chunk.charAt(i)] = k;
  438. }
  439. }
  440. }
  441. var regexp = new RegExp('[' + foreignletters + ']', 'g');
  442. return function(str) {
  443. return str.replace(regexp, function(foreignletter) {
  444. return lookup[foreignletter];
  445. }).toLowerCase();
  446. };
  447. })();
  448. // export
  449. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  450. return Sifter;
  451. }));
  452. /**
  453. * microplugin.js
  454. * Copyright (c) 2013 Brian Reavis & contributors
  455. *
  456. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
  457. * file except in compliance with the License. You may obtain a copy of the License at:
  458. * http://www.apache.org/licenses/LICENSE-2.0
  459. *
  460. * Unless required by applicable law or agreed to in writing, software distributed under
  461. * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
  462. * ANY KIND, either express or implied. See the License for the specific language
  463. * governing permissions and limitations under the License.
  464. *
  465. * @author Brian Reavis <brian@thirdroute.com>
  466. */
  467. (function(root, factory) {
  468. if (typeof define === 'function' && define.amd) {
  469. define('microplugin', factory);
  470. } else if (typeof exports === 'object') {
  471. module.exports = factory();
  472. } else {
  473. root.MicroPlugin = factory();
  474. }
  475. }(this, function() {
  476. var MicroPlugin = {};
  477. MicroPlugin.mixin = function(Interface) {
  478. Interface.plugins = {};
  479. /**
  480. * Initializes the listed plugins (with options).
  481. * Acceptable formats:
  482. *
  483. * List (without options):
  484. * ['a', 'b', 'c']
  485. *
  486. * List (with options):
  487. * [{'name': 'a', options: {}}, {'name': 'b', options: {}}]
  488. *
  489. * Hash (with options):
  490. * {'a': { ... }, 'b': { ... }, 'c': { ... }}
  491. *
  492. * @param {mixed} plugins
  493. */
  494. Interface.prototype.initializePlugins = function(plugins) {
  495. var i, n, key;
  496. var self = this;
  497. var queue = [];
  498. self.plugins = {
  499. names : [],
  500. settings : {},
  501. requested : {},
  502. loaded : {}
  503. };
  504. if (utils.isArray(plugins)) {
  505. for (i = 0, n = plugins.length; i < n; i++) {
  506. if (typeof plugins[i] === 'string') {
  507. queue.push(plugins[i]);
  508. } else {
  509. self.plugins.settings[plugins[i].name] = plugins[i].options;
  510. queue.push(plugins[i].name);
  511. }
  512. }
  513. } else if (plugins) {
  514. for (key in plugins) {
  515. if (plugins.hasOwnProperty(key)) {
  516. self.plugins.settings[key] = plugins[key];
  517. queue.push(key);
  518. }
  519. }
  520. }
  521. while (queue.length) {
  522. self.require(queue.shift());
  523. }
  524. };
  525. Interface.prototype.loadPlugin = function(name) {
  526. var self = this;
  527. var plugins = self.plugins;
  528. var plugin = Interface.plugins[name];
  529. if (!Interface.plugins.hasOwnProperty(name)) {
  530. throw new Error('Unable to find "' + name + '" plugin');
  531. }
  532. plugins.requested[name] = true;
  533. plugins.loaded[name] = plugin.fn.apply(self, [self.plugins.settings[name] || {}]);
  534. plugins.names.push(name);
  535. };
  536. /**
  537. * Initializes a plugin.
  538. *
  539. * @param {string} name
  540. */
  541. Interface.prototype.require = function(name) {
  542. var self = this;
  543. var plugins = self.plugins;
  544. if (!self.plugins.loaded.hasOwnProperty(name)) {
  545. if (plugins.requested[name]) {
  546. throw new Error('Plugin has circular dependency ("' + name + '")');
  547. }
  548. self.loadPlugin(name);
  549. }
  550. return plugins.loaded[name];
  551. };
  552. /**
  553. * Registers a plugin.
  554. *
  555. * @param {string} name
  556. * @param {function} fn
  557. */
  558. Interface.define = function(name, fn) {
  559. Interface.plugins[name] = {
  560. 'name' : name,
  561. 'fn' : fn
  562. };
  563. };
  564. };
  565. var utils = {
  566. isArray: Array.isArray || function(vArg) {
  567. return Object.prototype.toString.call(vArg) === '[object Array]';
  568. }
  569. };
  570. return MicroPlugin;
  571. }));
  572. /**
  573. * selectize.js (v0.12.6)
  574. * Copyright (c) 2013–2015 Brian Reavis & contributors
  575. *
  576. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
  577. * file except in compliance with the License. You may obtain a copy of the License at:
  578. * http://www.apache.org/licenses/LICENSE-2.0
  579. *
  580. * Unless required by applicable law or agreed to in writing, software distributed under
  581. * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
  582. * ANY KIND, either express or implied. See the License for the specific language
  583. * governing permissions and limitations under the License.
  584. *
  585. * @author Brian Reavis <brian@thirdroute.com>
  586. */
  587. /*jshint curly:false */
  588. /*jshint browser:true */
  589. (function(root, factory) {
  590. if (typeof define === 'function' && define.amd) {
  591. define('selectize', ['jquery','sifter','microplugin'], factory);
  592. } else if (typeof exports === 'object') {
  593. module.exports = factory(require('jquery'), require('sifter'), require('microplugin'));
  594. } else {
  595. root.Selectize = factory(root.jQuery, root.Sifter, root.MicroPlugin);
  596. }
  597. }(this, function($, Sifter, MicroPlugin) {
  598. 'use strict';
  599. var highlight = function($element, pattern) {
  600. if (typeof pattern === 'string' && !pattern.length) return;
  601. var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern;
  602. var highlight = function(node) {
  603. var skip = 0;
  604. // Wrap matching part of text node with highlighting <span>, e.g.
  605. // Soccer -> <span class="highlight">Soc</span>cer for regex = /soc/i
  606. if (node.nodeType === 3) {
  607. var pos = node.data.search(regex);
  608. if (pos >= 0 && node.data.length > 0) {
  609. var match = node.data.match(regex);
  610. var spannode = document.createElement('span');
  611. spannode.className = 'highlight';
  612. var middlebit = node.splitText(pos);
  613. var endbit = middlebit.splitText(match[0].length);
  614. var middleclone = middlebit.cloneNode(true);
  615. spannode.appendChild(middleclone);
  616. middlebit.parentNode.replaceChild(spannode, middlebit);
  617. skip = 1;
  618. }
  619. }
  620. // Recurse element node, looking for child text nodes to highlight, unless element
  621. // is childless, <script>, <style>, or already highlighted: <span class="hightlight">
  622. else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName) && ( node.className !== 'highlight' || node.tagName !== 'SPAN' )) {
  623. for (var i = 0; i < node.childNodes.length; ++i) {
  624. i += highlight(node.childNodes[i]);
  625. }
  626. }
  627. return skip;
  628. };
  629. return $element.each(function() {
  630. highlight(this);
  631. });
  632. };
  633. /**
  634. * removeHighlight fn copied from highlight v5 and
  635. * edited to remove with() and pass js strict mode
  636. */
  637. $.fn.removeHighlight = function() {
  638. return this.find("span.highlight").each(function() {
  639. this.parentNode.firstChild.nodeName;
  640. var parent = this.parentNode;
  641. parent.replaceChild(this.firstChild, this);
  642. parent.normalize();
  643. }).end();
  644. };
  645. var MicroEvent = function() {};
  646. MicroEvent.prototype = {
  647. on: function(event, fct){
  648. this._events = this._events || {};
  649. this._events[event] = this._events[event] || [];
  650. this._events[event].push(fct);
  651. },
  652. off: function(event, fct){
  653. var n = arguments.length;
  654. if (n === 0) return delete this._events;
  655. if (n === 1) return delete this._events[event];
  656. this._events = this._events || {};
  657. if (event in this._events === false) return;
  658. this._events[event].splice(this._events[event].indexOf(fct), 1);
  659. },
  660. trigger: function(event /* , args... */){
  661. this._events = this._events || {};
  662. if (event in this._events === false) return;
  663. for (var i = 0; i < this._events[event].length; i++){
  664. this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
  665. }
  666. }
  667. };
  668. /**
  669. * Mixin will delegate all MicroEvent.js function in the destination object.
  670. *
  671. * - MicroEvent.mixin(Foobar) will make Foobar able to use MicroEvent
  672. *
  673. * @param {object} the object which will support MicroEvent
  674. */
  675. MicroEvent.mixin = function(destObject){
  676. var props = ['on', 'off', 'trigger'];
  677. for (var i = 0; i < props.length; i++){
  678. destObject.prototype[props[i]] = MicroEvent.prototype[props[i]];
  679. }
  680. };
  681. var IS_MAC = /Mac/.test(navigator.userAgent);
  682. var KEY_A = 65;
  683. var KEY_COMMA = 188;
  684. var KEY_RETURN = 13;
  685. var KEY_ESC = 27;
  686. var KEY_LEFT = 37;
  687. var KEY_UP = 38;
  688. var KEY_P = 80;
  689. var KEY_RIGHT = 39;
  690. var KEY_DOWN = 40;
  691. var KEY_N = 78;
  692. var KEY_BACKSPACE = 8;
  693. var KEY_DELETE = 46;
  694. var KEY_SHIFT = 16;
  695. var KEY_CMD = IS_MAC ? 91 : 17;
  696. var KEY_CTRL = IS_MAC ? 18 : 17;
  697. var KEY_TAB = 9;
  698. var TAG_SELECT = 1;
  699. var TAG_INPUT = 2;
  700. // for now, android support in general is too spotty to support validity
  701. var SUPPORTS_VALIDITY_API = !/android/i.test(window.navigator.userAgent) && !!document.createElement('input').validity;
  702. var isset = function(object) {
  703. return typeof object !== 'undefined';
  704. };
  705. /**
  706. * Converts a scalar to its best string representation
  707. * for hash keys and HTML attribute values.
  708. *
  709. * Transformations:
  710. * 'str' -> 'str'
  711. * null -> ''
  712. * undefined -> ''
  713. * true -> '1'
  714. * false -> '0'
  715. * 0 -> '0'
  716. * 1 -> '1'
  717. *
  718. * @param {string} value
  719. * @returns {string|null}
  720. */
  721. var hash_key = function(value) {
  722. if (typeof value === 'undefined' || value === null) return null;
  723. if (typeof value === 'boolean') return value ? '1' : '0';
  724. return value + '';
  725. };
  726. /**
  727. * Escapes a string for use within HTML.
  728. *
  729. * @param {string} str
  730. * @returns {string}
  731. */
  732. var escape_html = function(str) {
  733. return (str + '')
  734. .replace(/&/g, '&amp;')
  735. .replace(/</g, '&lt;')
  736. .replace(/>/g, '&gt;')
  737. .replace(/"/g, '&quot;');
  738. };
  739. /**
  740. * Escapes "$" characters in replacement strings.
  741. *
  742. * @param {string} str
  743. * @returns {string}
  744. */
  745. var escape_replace = function(str) {
  746. return (str + '').replace(/\$/g, '$$$$');
  747. };
  748. var hook = {};
  749. /**
  750. * Wraps `method` on `self` so that `fn`
  751. * is invoked before the original method.
  752. *
  753. * @param {object} self
  754. * @param {string} method
  755. * @param {function} fn
  756. */
  757. hook.before = function(self, method, fn) {
  758. var original = self[method];
  759. self[method] = function() {
  760. fn.apply(self, arguments);
  761. return original.apply(self, arguments);
  762. };
  763. };
  764. /**
  765. * Wraps `method` on `self` so that `fn`
  766. * is invoked after the original method.
  767. *
  768. * @param {object} self
  769. * @param {string} method
  770. * @param {function} fn
  771. */
  772. hook.after = function(self, method, fn) {
  773. var original = self[method];
  774. self[method] = function() {
  775. var result = original.apply(self, arguments);
  776. fn.apply(self, arguments);
  777. return result;
  778. };
  779. };
  780. /**
  781. * Wraps `fn` so that it can only be invoked once.
  782. *
  783. * @param {function} fn
  784. * @returns {function}
  785. */
  786. var once = function(fn) {
  787. var called = false;
  788. return function() {
  789. if (called) return;
  790. called = true;
  791. fn.apply(this, arguments);
  792. };
  793. };
  794. /**
  795. * Wraps `fn` so that it can only be called once
  796. * every `delay` milliseconds (invoked on the falling edge).
  797. *
  798. * @param {function} fn
  799. * @param {int} delay
  800. * @returns {function}
  801. */
  802. var debounce = function(fn, delay) {
  803. var timeout;
  804. return function() {
  805. var self = this;
  806. var args = arguments;
  807. window.clearTimeout(timeout);
  808. timeout = window.setTimeout(function() {
  809. fn.apply(self, args);
  810. }, delay);
  811. };
  812. };
  813. /**
  814. * Debounce all fired events types listed in `types`
  815. * while executing the provided `fn`.
  816. *
  817. * @param {object} self
  818. * @param {array} types
  819. * @param {function} fn
  820. */
  821. var debounce_events = function(self, types, fn) {
  822. var type;
  823. var trigger = self.trigger;
  824. var event_args = {};
  825. // override trigger method
  826. self.trigger = function() {
  827. var type = arguments[0];
  828. if (types.indexOf(type) !== -1) {
  829. event_args[type] = arguments;
  830. } else {
  831. return trigger.apply(self, arguments);
  832. }
  833. };
  834. // invoke provided function
  835. fn.apply(self, []);
  836. self.trigger = trigger;
  837. // trigger queued events
  838. for (type in event_args) {
  839. if (event_args.hasOwnProperty(type)) {
  840. trigger.apply(self, event_args[type]);
  841. }
  842. }
  843. };
  844. /**
  845. * A workaround for http://bugs.jquery.com/ticket/6696
  846. *
  847. * @param {object} $parent - Parent element to listen on.
  848. * @param {string} event - Event name.
  849. * @param {string} selector - Descendant selector to filter by.
  850. * @param {function} fn - Event handler.
  851. */
  852. var watchChildEvent = function($parent, event, selector, fn) {
  853. $parent.on(event, selector, function(e) {
  854. var child = e.target;
  855. while (child && child.parentNode !== $parent[0]) {
  856. child = child.parentNode;
  857. }
  858. e.currentTarget = child;
  859. return fn.apply(this, [e]);
  860. });
  861. };
  862. /**
  863. * Determines the current selection within a text input control.
  864. * Returns an object containing:
  865. * - start
  866. * - length
  867. *
  868. * @param {object} input
  869. * @returns {object}
  870. */
  871. var getSelection = function(input) {
  872. var result = {};
  873. if ('selectionStart' in input) {
  874. result.start = input.selectionStart;
  875. result.length = input.selectionEnd - result.start;
  876. } else if (document.selection) {
  877. input.focus();
  878. var sel = document.selection.createRange();
  879. var selLen = document.selection.createRange().text.length;
  880. sel.moveStart('character', -input.value.length);
  881. result.start = sel.text.length - selLen;
  882. result.length = selLen;
  883. }
  884. return result;
  885. };
  886. /**
  887. * Copies CSS properties from one element to another.
  888. *
  889. * @param {object} $from
  890. * @param {object} $to
  891. * @param {array} properties
  892. */
  893. var transferStyles = function($from, $to, properties) {
  894. var i, n, styles = {};
  895. if (properties) {
  896. for (i = 0, n = properties.length; i < n; i++) {
  897. styles[properties[i]] = $from.css(properties[i]);
  898. }
  899. } else {
  900. styles = $from.css();
  901. }
  902. $to.css(styles);
  903. };
  904. /**
  905. * Measures the width of a string within a
  906. * parent element (in pixels).
  907. *
  908. * @param {string} str
  909. * @param {object} $parent
  910. * @returns {int}
  911. */
  912. var measureString = function(str, $parent) {
  913. if (!str) {
  914. return 0;
  915. }
  916. if (!Selectize.$testInput) {
  917. Selectize.$testInput = $('<span />').css({
  918. position: 'absolute',
  919. top: -99999,
  920. left: -99999,
  921. width: 'auto',
  922. padding: 0,
  923. whiteSpace: 'pre'
  924. }).appendTo('body');
  925. }
  926. Selectize.$testInput.text(str);
  927. transferStyles($parent, Selectize.$testInput, [
  928. 'letterSpacing',
  929. 'fontSize',
  930. 'fontFamily',
  931. 'fontWeight',
  932. 'textTransform'
  933. ]);
  934. return Selectize.$testInput.width();
  935. };
  936. /**
  937. * Sets up an input to grow horizontally as the user
  938. * types. If the value is changed manually, you can
  939. * trigger the "update" handler to resize:
  940. *
  941. * $input.trigger('update');
  942. *
  943. * @param {object} $input
  944. */
  945. var autoGrow = function($input) {
  946. var currentWidth = null;
  947. var update = function(e, options) {
  948. var value, keyCode, printable, placeholder, width;
  949. var shift, character, selection;
  950. e = e || window.event || {};
  951. options = options || {};
  952. if (e.metaKey || e.altKey) return;
  953. if (!options.force && $input.data('grow') === false) return;
  954. value = $input.val();
  955. if (e.type && e.type.toLowerCase() === 'keydown') {
  956. keyCode = e.keyCode;
  957. printable = (
  958. (keyCode >= 48 && keyCode <= 57) || // 0-9
  959. (keyCode >= 65 && keyCode <= 90) || // a-z
  960. (keyCode >= 96 && keyCode <= 111) || // numpad 0-9, numeric operators
  961. (keyCode >= 186 && keyCode <= 222) || // semicolon, equal, comma, dash, etc.
  962. keyCode === 32 // space
  963. );
  964. if (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) {
  965. selection = getSelection($input[0]);
  966. if (selection.length) {
  967. value = value.substring(0, selection.start) + value.substring(selection.start + selection.length);
  968. } else if (keyCode === KEY_BACKSPACE && selection.start) {
  969. value = value.substring(0, selection.start - 1) + value.substring(selection.start + 1);
  970. } else if (keyCode === KEY_DELETE && typeof selection.start !== 'undefined') {
  971. value = value.substring(0, selection.start) + value.substring(selection.start + 1);
  972. }
  973. } else if (printable) {
  974. shift = e.shiftKey;
  975. character = String.fromCharCode(e.keyCode);
  976. if (shift) character = character.toUpperCase();
  977. else character = character.toLowerCase();
  978. value += character;
  979. }
  980. }
  981. placeholder = $input.attr('placeholder');
  982. if (!value && placeholder) {
  983. value = placeholder;
  984. }
  985. width = measureString(value, $input) + 4;
  986. if (width !== currentWidth) {
  987. currentWidth = width;
  988. $input.width(width);
  989. $input.triggerHandler('resize');
  990. }
  991. };
  992. $input.on('keydown keyup update blur', update);
  993. update();
  994. };
  995. var domToString = function(d) {
  996. var tmp = document.createElement('div');
  997. tmp.appendChild(d.cloneNode(true));
  998. return tmp.innerHTML;
  999. };
  1000. var logError = function(message, options){
  1001. if(!options) options = {};
  1002. var component = "Selectize";
  1003. console.error(component + ": " + message)
  1004. if(options.explanation){
  1005. // console.group is undefined in <IE11
  1006. if(console.group) console.group();
  1007. console.error(options.explanation);
  1008. if(console.group) console.groupEnd();
  1009. }
  1010. }
  1011. var Selectize = function($input, settings) {
  1012. var key, i, n, dir, input, self = this;
  1013. input = $input[0];
  1014. input.selectize = self;
  1015. // detect rtl environment
  1016. var computedStyle = window.getComputedStyle && window.getComputedStyle(input, null);
  1017. dir = computedStyle ? computedStyle.getPropertyValue('direction') : input.currentStyle && input.currentStyle.direction;
  1018. dir = dir || $input.parents('[dir]:first').attr('dir') || '';
  1019. // setup default state
  1020. $.extend(self, {
  1021. order : 0,
  1022. settings : settings,
  1023. $input : $input,
  1024. tabIndex : $input.attr('tabindex') || '',
  1025. tagType : input.tagName.toLowerCase() === 'select' ? TAG_SELECT : TAG_INPUT,
  1026. rtl : /rtl/i.test(dir),
  1027. eventNS : '.selectize' + (++Selectize.count),
  1028. highlightedValue : null,
  1029. isBlurring : false,
  1030. isOpen : false,
  1031. isDisabled : false,
  1032. isRequired : $input.is('[required]'),
  1033. isInvalid : false,
  1034. isLocked : false,
  1035. isFocused : false,
  1036. isInputHidden : false,
  1037. isSetup : false,
  1038. isShiftDown : false,
  1039. isCmdDown : false,
  1040. isCtrlDown : false,
  1041. ignoreFocus : false,
  1042. ignoreBlur : false,
  1043. ignoreHover : false,
  1044. hasOptions : false,
  1045. currentResults : null,
  1046. lastValue : '',
  1047. caretPos : 0,
  1048. loading : 0,
  1049. loadedSearches : {},
  1050. $activeOption : null,
  1051. $activeItems : [],
  1052. optgroups : {},
  1053. options : {},
  1054. userOptions : {},
  1055. items : [],
  1056. renderCache : {},
  1057. onSearchChange : settings.loadThrottle === null ? self.onSearchChange : debounce(self.onSearchChange, settings.loadThrottle)
  1058. });
  1059. // search system
  1060. self.sifter = new Sifter(this.options, {diacritics: settings.diacritics});
  1061. // build options table
  1062. if (self.settings.options) {
  1063. for (i = 0, n = self.settings.options.length; i < n; i++) {
  1064. self.registerOption(self.settings.options[i]);
  1065. }
  1066. delete self.settings.options;
  1067. }
  1068. // build optgroup table
  1069. if (self.settings.optgroups) {
  1070. for (i = 0, n = self.settings.optgroups.length; i < n; i++) {
  1071. self.registerOptionGroup(self.settings.optgroups[i]);
  1072. }
  1073. delete self.settings.optgroups;
  1074. }
  1075. // option-dependent defaults
  1076. self.settings.mode = self.settings.mode || (self.settings.maxItems === 1 ? 'single' : 'multi');
  1077. if (typeof self.settings.hideSelected !== 'boolean') {
  1078. self.settings.hideSelected = self.settings.mode === 'multi';
  1079. }
  1080. self.initializePlugins(self.settings.plugins);
  1081. self.setupCallbacks();
  1082. self.setupTemplates();
  1083. self.setup();
  1084. };
  1085. // mixins
  1086. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  1087. MicroEvent.mixin(Selectize);
  1088. if(typeof MicroPlugin !== "undefined"){
  1089. MicroPlugin.mixin(Selectize);
  1090. }else{
  1091. logError("Dependency MicroPlugin is missing",
  1092. {explanation:
  1093. "Make sure you either: (1) are using the \"standalone\" "+
  1094. "version of Selectize, or (2) require MicroPlugin before you "+
  1095. "load Selectize."}
  1096. );
  1097. }
  1098. // methods
  1099. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  1100. $.extend(Selectize.prototype, {
  1101. /**
  1102. * Creates all elements and sets up event bindings.
  1103. */
  1104. setup: function() {
  1105. var self = this;
  1106. var settings = self.settings;
  1107. var eventNS = self.eventNS;
  1108. var $window = $(window);
  1109. var $document = $(document);
  1110. var $input = self.$input;
  1111. var $wrapper;
  1112. var $control;
  1113. var $control_input;
  1114. var $dropdown;
  1115. var $dropdown_content;
  1116. var $dropdown_parent;
  1117. var inputMode;
  1118. var timeout_blur;
  1119. var timeout_focus;
  1120. var classes;
  1121. var classes_plugins;
  1122. var inputId;
  1123. inputMode = self.settings.mode;
  1124. classes = $input.attr('class') || '';
  1125. $wrapper = $('<div>').addClass(settings.wrapperClass).addClass(classes).addClass(inputMode);
  1126. $control = $('<div>').addClass(settings.inputClass).addClass('items').appendTo($wrapper);
  1127. $control_input = $('<input type="text" autocomplete="off" />').appendTo($control).attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex);
  1128. $dropdown_parent = $(settings.dropdownParent || $wrapper);
  1129. $dropdown = $('<div>').addClass(settings.dropdownClass).addClass(inputMode).hide().appendTo($dropdown_parent);
  1130. $dropdown_content = $('<div>').addClass(settings.dropdownContentClass).appendTo($dropdown);
  1131. if(inputId = $input.attr('id')) {
  1132. $control_input.attr('id', inputId + '-selectized');
  1133. $("label[for='"+inputId+"']").attr('for', inputId + '-selectized');
  1134. }
  1135. if(self.settings.copyClassesToDropdown) {
  1136. $dropdown.addClass(classes);
  1137. }
  1138. $wrapper.css({
  1139. width: $input[0].style.width
  1140. });
  1141. if (self.plugins.names.length) {
  1142. classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-');
  1143. $wrapper.addClass(classes_plugins);
  1144. $dropdown.addClass(classes_plugins);
  1145. }
  1146. if ((settings.maxItems === null || settings.maxItems > 1) && self.tagType === TAG_SELECT) {
  1147. $input.attr('multiple', 'multiple');
  1148. }
  1149. if (self.settings.placeholder) {
  1150. $control_input.attr('placeholder', settings.placeholder);
  1151. }
  1152. // if splitOn was not passed in, construct it from the delimiter to allow pasting universally
  1153. if (!self.settings.splitOn && self.settings.delimiter) {
  1154. var delimiterEscaped = self.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
  1155. self.settings.splitOn = new RegExp('\\s*' + delimiterEscaped + '+\\s*');
  1156. }
  1157. if ($input.attr('autocorrect')) {
  1158. $control_input.attr('autocorrect', $input.attr('autocorrect'));
  1159. }
  1160. if ($input.attr('autocapitalize')) {
  1161. $control_input.attr('autocapitalize', $input.attr('autocapitalize'));
  1162. }
  1163. $control_input[0].type = $input[0].type;
  1164. self.$wrapper = $wrapper;
  1165. self.$control = $control;
  1166. self.$control_input = $control_input;
  1167. self.$dropdown = $dropdown;
  1168. self.$dropdown_content = $dropdown_content;
  1169. $dropdown.on('mouseenter mousedown click', '[data-disabled]>[data-selectable]', function(e) { e.stopImmediatePropagation(); });
  1170. $dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); });
  1171. $dropdown.on('mousedown click', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); });
  1172. watchChildEvent($control, 'mousedown', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); });
  1173. autoGrow($control_input);
  1174. $control.on({
  1175. mousedown : function() { return self.onMouseDown.apply(self, arguments); },
  1176. click : function() { return self.onClick.apply(self, arguments); }
  1177. });
  1178. $control_input.on({
  1179. mousedown : function(e) { e.stopPropagation(); },
  1180. keydown : function() { return self.onKeyDown.apply(self, arguments); },
  1181. keyup : function() { return self.onKeyUp.apply(self, arguments); },
  1182. keypress : function() { return self.onKeyPress.apply(self, arguments); },
  1183. resize : function() { self.positionDropdown.apply(self, []); },
  1184. blur : function() { return self.onBlur.apply(self, arguments); },
  1185. focus : function() { self.ignoreBlur = false; return self.onFocus.apply(self, arguments); },
  1186. paste : function() { return self.onPaste.apply(self, arguments); }
  1187. });
  1188. $document.on('keydown' + eventNS, function(e) {
  1189. self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey'];
  1190. self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey'];
  1191. self.isShiftDown = e.shiftKey;
  1192. });
  1193. $document.on('keyup' + eventNS, function(e) {
  1194. if (e.keyCode === KEY_CTRL) self.isCtrlDown = false;
  1195. if (e.keyCode === KEY_SHIFT) self.isShiftDown = false;
  1196. if (e.keyCode === KEY_CMD) self.isCmdDown = false;
  1197. });
  1198. $document.on('mousedown' + eventNS, function(e) {
  1199. if (self.isFocused) {
  1200. // prevent events on the dropdown scrollbar from causing the control to blur
  1201. if (e.target === self.$dropdown[0] || e.target.parentNode === self.$dropdown[0]) {
  1202. return false;
  1203. }
  1204. // blur on click outside
  1205. if (!self.$control.has(e.target).length && e.target !== self.$control[0]) {
  1206. self.blur(e.target);
  1207. }
  1208. }
  1209. });
  1210. $window.on(['scroll' + eventNS, 'resize' + eventNS].join(' '), function() {
  1211. if (self.isOpen) {
  1212. self.positionDropdown.apply(self, arguments);
  1213. }
  1214. });
  1215. $window.on('mousemove' + eventNS, function() {
  1216. self.ignoreHover = false;
  1217. });
  1218. // store original children and tab index so that they can be
  1219. // restored when the destroy() method is called.
  1220. this.revertSettings = {
  1221. $children : $input.children().detach(),
  1222. tabindex : $input.attr('tabindex')
  1223. };
  1224. $input.attr('tabindex', -1).hide().after(self.$wrapper);
  1225. if ($.isArray(settings.items)) {
  1226. self.setValue(settings.items);
  1227. delete settings.items;
  1228. }
  1229. // feature detect for the validation API
  1230. if (SUPPORTS_VALIDITY_API) {
  1231. $input.on('invalid' + eventNS, function(e) {
  1232. e.preventDefault();
  1233. self.isInvalid = true;
  1234. self.refreshState();
  1235. });
  1236. }
  1237. self.updateOriginalInput();
  1238. self.refreshItems();
  1239. self.refreshState();
  1240. self.updatePlaceholder();
  1241. self.isSetup = true;
  1242. if ($input.is(':disabled')) {
  1243. self.disable();
  1244. }
  1245. self.on('change', this.onChange);
  1246. $input.data('selectize', self);
  1247. $input.addClass('selectized');
  1248. self.trigger('initialize');
  1249. // preload options
  1250. if (settings.preload === true) {
  1251. self.onSearchChange('');
  1252. }
  1253. },
  1254. /**
  1255. * Sets up default rendering functions.
  1256. */
  1257. setupTemplates: function() {
  1258. var self = this;
  1259. var field_label = self.settings.labelField;
  1260. var field_optgroup = self.settings.optgroupLabelField;
  1261. var templates = {
  1262. 'optgroup': function(data) {
  1263. return '<div class="optgroup">' + data.html + '</div>';
  1264. },
  1265. 'optgroup_header': function(data, escape) {
  1266. return '<div class="optgroup-header">' + escape(data[field_optgroup]) + '</div>';
  1267. },
  1268. 'option': function(data, escape) {
  1269. return '<div class="option">' + escape(data[field_label]) + '</div>';
  1270. },
  1271. 'item': function(data, escape) {
  1272. return '<div class="item">' + escape(data[field_label]) + '</div>';
  1273. },
  1274. 'option_create': function(data, escape) {
  1275. return '<div class="create">Add <strong>' + escape(data.input) + '</strong>&hellip;</div>';
  1276. }
  1277. };
  1278. self.settings.render = $.extend({}, templates, self.settings.render);
  1279. },
  1280. /**
  1281. * Maps fired events to callbacks provided
  1282. * in the settings used when creating the control.
  1283. */
  1284. setupCallbacks: function() {
  1285. var key, fn, callbacks = {
  1286. 'initialize' : 'onInitialize',
  1287. 'change' : 'onChange',
  1288. 'item_add' : 'onItemAdd',
  1289. 'item_remove' : 'onItemRemove',
  1290. 'clear' : 'onClear',
  1291. 'option_add' : 'onOptionAdd',
  1292. 'option_remove' : 'onOptionRemove',
  1293. 'option_clear' : 'onOptionClear',
  1294. 'optgroup_add' : 'onOptionGroupAdd',
  1295. 'optgroup_remove' : 'onOptionGroupRemove',
  1296. 'optgroup_clear' : 'onOptionGroupClear',
  1297. 'dropdown_open' : 'onDropdownOpen',
  1298. 'dropdown_close' : 'onDropdownClose',
  1299. 'type' : 'onType',
  1300. 'load' : 'onLoad',
  1301. 'focus' : 'onFocus',
  1302. 'blur' : 'onBlur'
  1303. };
  1304. for (key in callbacks) {
  1305. if (callbacks.hasOwnProperty(key)) {
  1306. fn = this.settings[callbacks[key]];
  1307. if (fn) this.on(key, fn);
  1308. }
  1309. }
  1310. },
  1311. /**
  1312. * Triggered when the main control element
  1313. * has a click event.
  1314. *
  1315. * @param {object} e
  1316. * @return {boolean}
  1317. */
  1318. onClick: function(e) {
  1319. var self = this;
  1320. // necessary for mobile webkit devices (manual focus triggering
  1321. // is ignored unless invoked within a click event)
  1322. // also necessary to reopen a dropdown that has been closed by
  1323. // closeAfterSelect
  1324. if (!self.isFocused || !self.isOpen) {
  1325. self.focus();
  1326. e.preventDefault();
  1327. }
  1328. },
  1329. /**
  1330. * Triggered when the main control element
  1331. * has a mouse down event.
  1332. *
  1333. * @param {object} e
  1334. * @return {boolean}
  1335. */
  1336. onMouseDown: function(e) {
  1337. var self = this;
  1338. var defaultPrevented = e.isDefaultPrevented();
  1339. var $target = $(e.target);
  1340. if (self.isFocused) {
  1341. // retain focus by preventing native handling. if the
  1342. // event target is the input it should not be modified.
  1343. // otherwise, text selection within the input won't work.
  1344. if (e.target !== self.$control_input[0]) {
  1345. if (self.settings.mode === 'single') {
  1346. // toggle dropdown
  1347. self.isOpen ? self.close() : self.open();
  1348. } else if (!defaultPrevented) {
  1349. self.setActiveItem(null);
  1350. }
  1351. return false;
  1352. }
  1353. } else {
  1354. // give control focus
  1355. if (!defaultPrevented) {
  1356. window.setTimeout(function() {
  1357. self.focus();
  1358. }, 0);
  1359. }
  1360. }
  1361. },
  1362. /**
  1363. * Triggered when the value of the control has been changed.
  1364. * This should propagate the event to the original DOM
  1365. * input / select element.
  1366. */
  1367. onChange: function() {
  1368. this.$input.trigger('change');
  1369. },
  1370. /**
  1371. * Triggered on <input> paste.
  1372. *
  1373. * @param {object} e
  1374. * @returns {boolean}
  1375. */
  1376. onPaste: function(e) {
  1377. var self = this;
  1378. if (self.isFull() || self.isInputHidden || self.isLocked) {
  1379. e.preventDefault();
  1380. return;
  1381. }
  1382. // If a regex or string is included, this will split the pasted
  1383. // input and create Items for each separate value
  1384. if (self.settings.splitOn) {
  1385. // Wait for pasted text to be recognized in value
  1386. setTimeout(function() {
  1387. var pastedText = self.$control_input.val();
  1388. if(!pastedText.match(self.settings.splitOn)){ return }
  1389. var splitInput = $.trim(pastedText).split(self.settings.splitOn);
  1390. for (var i = 0, n = splitInput.length; i < n; i++) {
  1391. self.createItem(splitInput[i]);
  1392. }
  1393. }, 0);
  1394. }
  1395. },
  1396. /**
  1397. * Triggered on <input> keypress.
  1398. *
  1399. * @param {object} e
  1400. * @returns {boolean}
  1401. */
  1402. onKeyPress: function(e) {
  1403. if (this.isLocked) return e && e.preventDefault();
  1404. var character = String.fromCharCode(e.keyCode || e.which);
  1405. if (this.settings.create && this.settings.mode === 'multi' && character === this.settings.delimiter) {
  1406. this.createItem();
  1407. e.preventDefault();
  1408. return false;
  1409. }
  1410. },
  1411. /**
  1412. * Triggered on <input> keydown.
  1413. *
  1414. * @param {object} e
  1415. * @returns {boolean}
  1416. */
  1417. onKeyDown: function(e) {
  1418. var isInput = e.target === this.$control_input[0];
  1419. var self = this;
  1420. if (self.isLocked) {
  1421. if (e.keyCode !== KEY_TAB) {
  1422. e.preventDefault();
  1423. }
  1424. return;
  1425. }
  1426. switch (e.keyCode) {
  1427. case KEY_A:
  1428. if (self.isCmdDown) {
  1429. self.selectAll();
  1430. return;
  1431. }
  1432. break;
  1433. case KEY_ESC:
  1434. if (self.isOpen) {
  1435. e.preventDefault();
  1436. e.stopPropagation();
  1437. self.close();
  1438. }
  1439. return;
  1440. case KEY_N:
  1441. if (!e.ctrlKey || e.altKey) break;
  1442. case KEY_DOWN:
  1443. if (!self.isOpen && self.hasOptions) {
  1444. self.open();
  1445. } else if (self.$activeOption) {
  1446. self.ignoreHover = true;
  1447. var $next = self.getAdjacentOption(self.$activeOption, 1);
  1448. if ($next.length) self.setActiveOption($next, true, true);
  1449. }
  1450. e.preventDefault();
  1451. return;
  1452. case KEY_P:
  1453. if (!e.ctrlKey || e.altKey) break;
  1454. case KEY_UP:
  1455. if (self.$activeOption) {
  1456. self.ignoreHover = true;
  1457. var $prev = self.getAdjacentOption(self.$activeOption, -1);
  1458. if ($prev.length) self.setActiveOption($prev, true, true);
  1459. }
  1460. e.preventDefault();
  1461. return;
  1462. case KEY_RETURN:
  1463. if (self.isOpen && self.$activeOption) {
  1464. self.onOptionSelect({currentTarget: self.$activeOption});
  1465. e.preventDefault();
  1466. }
  1467. return;
  1468. case KEY_LEFT:
  1469. self.advanceSelection(-1, e);
  1470. return;
  1471. case KEY_RIGHT:
  1472. self.advanceSelection(1, e);
  1473. return;
  1474. case KEY_TAB:
  1475. if (self.settings.selectOnTab && self.isOpen && self.$activeOption) {
  1476. self.onOptionSelect({currentTarget: self.$activeOption});
  1477. // Default behaviour is to jump to the next field, we only want this
  1478. // if the current field doesn't accept any more entries
  1479. if (!self.isFull()) {
  1480. e.preventDefault();
  1481. }
  1482. }
  1483. if (self.settings.create && self.createItem()) {
  1484. e.preventDefault();
  1485. }
  1486. return;
  1487. case KEY_BACKSPACE:
  1488. case KEY_DELETE:
  1489. self.deleteSelection(e);
  1490. return;
  1491. }
  1492. if ((self.isFull() || self.isInputHidden) && !(IS_MAC ? e.metaKey : e.ctrlKey)) {
  1493. e.preventDefault();
  1494. return;
  1495. }
  1496. },
  1497. /**
  1498. * Triggered on <input> keyup.
  1499. *
  1500. * @param {object} e
  1501. * @returns {boolean}
  1502. */
  1503. onKeyUp: function(e) {
  1504. var self = this;
  1505. if (self.isLocked) return e && e.preventDefault();
  1506. var value = self.$control_input.val() || '';
  1507. if (self.lastValue !== value) {
  1508. self.lastValue = value;
  1509. self.onSearchChange(value);
  1510. self.refreshOptions();
  1511. self.trigger('type', value);
  1512. }
  1513. },
  1514. /**
  1515. * Invokes the user-provide option provider / loader.
  1516. *
  1517. * Note: this function is debounced in the Selectize
  1518. * constructor (by `settings.loadThrottle` milliseconds)
  1519. *
  1520. * @param {string} value
  1521. */
  1522. onSearchChange: function(value) {
  1523. var self = this;
  1524. var fn = self.settings.load;
  1525. if (!fn) return;
  1526. if (self.loadedSearches.hasOwnProperty(value)) return;
  1527. self.loadedSearches[value] = true;
  1528. self.load(function(callback) {
  1529. fn.apply(self, [value, callback]);
  1530. });
  1531. },
  1532. /**
  1533. * Triggered on <input> focus.
  1534. *
  1535. * @param {object} e (optional)
  1536. * @returns {boolean}
  1537. */
  1538. onFocus: function(e) {
  1539. var self = this;
  1540. var wasFocused = self.isFocused;
  1541. if (self.isDisabled) {
  1542. self.blur();
  1543. e && e.preventDefault();
  1544. return false;
  1545. }
  1546. if (self.ignoreFocus) return;
  1547. self.isFocused = true;
  1548. if (self.settings.preload === 'focus') self.onSearchChange('');
  1549. if (!wasFocused) self.trigger('focus');
  1550. if (!self.$activeItems.length) {
  1551. self.showInput();
  1552. self.setActiveItem(null);
  1553. self.refreshOptions(!!self.settings.openOnFocus);
  1554. }
  1555. self.refreshState();
  1556. },
  1557. /**
  1558. * Triggered on <input> blur.
  1559. *
  1560. * @param {object} e
  1561. * @param {Element} dest
  1562. */
  1563. onBlur: function(e, dest) {
  1564. var self = this;
  1565. if (!self.isFocused) return;
  1566. self.isFocused = false;
  1567. if (self.ignoreFocus) {
  1568. return;
  1569. } else if (!self.ignoreBlur && document.activeElement === self.$dropdown_content[0]) {
  1570. // necessary to prevent IE closing the dropdown when the scrollbar is clicked
  1571. self.ignoreBlur = true;
  1572. self.onFocus(e);
  1573. return;
  1574. }
  1575. var deactivate = function() {
  1576. self.close();
  1577. self.setTextboxValue('');
  1578. self.setActiveItem(null);
  1579. self.setActiveOption(null);
  1580. self.setCaret(self.items.length);
  1581. self.refreshState();
  1582. // IE11 bug: element still marked as active
  1583. dest && dest.focus && dest.focus();
  1584. self.isBlurring = false;
  1585. self.ignoreFocus = false;
  1586. self.trigger('blur');
  1587. };
  1588. self.isBlurring = true;
  1589. self.ignoreFocus = true;
  1590. if (self.settings.create && self.settings.createOnBlur) {
  1591. self.createItem(null, false, deactivate);
  1592. } else {
  1593. deactivate();
  1594. }
  1595. },
  1596. /**
  1597. * Triggered when the user rolls over
  1598. * an option in the autocomplete dropdown menu.
  1599. *
  1600. * @param {object} e
  1601. * @returns {boolean}
  1602. */
  1603. onOptionHover: function(e) {
  1604. if (this.ignoreHover) return;
  1605. this.setActiveOption(e.currentTarget, false);
  1606. },
  1607. /**
  1608. * Triggered when the user clicks on an option
  1609. * in the autocomplete dropdown menu.
  1610. *
  1611. * @param {object} e
  1612. * @returns {boolean}
  1613. */
  1614. onOptionSelect: function(e) {
  1615. var value, $target, $option, self = this;
  1616. if (e.preventDefault) {
  1617. e.preventDefault();
  1618. e.stopPropagation();
  1619. }
  1620. $target = $(e.currentTarget);
  1621. if ($target.hasClass('create')) {
  1622. self.createItem(null, function() {
  1623. if (self.settings.closeAfterSelect) {
  1624. self.close();
  1625. }
  1626. });
  1627. } else {
  1628. value = $target.attr('data-value');
  1629. if (typeof value !== 'undefined') {
  1630. self.lastQuery = null;
  1631. self.setTextboxValue('');
  1632. self.addItem(value);
  1633. if (self.settings.closeAfterSelect) {
  1634. self.close();
  1635. } else if (!self.settings.hideSelected && e.type && /mouse/.test(e.type)) {
  1636. self.setActiveOption(self.getOption(value));
  1637. }
  1638. }
  1639. }
  1640. },
  1641. /**
  1642. * Triggered when the user clicks on an item
  1643. * that has been selected.
  1644. *
  1645. * @param {object} e
  1646. * @returns {boolean}
  1647. */
  1648. onItemSelect: function(e) {
  1649. var self = this;
  1650. if (self.isLocked) return;
  1651. if (self.settings.mode === 'multi') {
  1652. e.preventDefault();
  1653. self.setActiveItem(e.currentTarget, e);
  1654. }
  1655. },
  1656. /**
  1657. * Invokes the provided method that provides
  1658. * results to a callback---which are then added
  1659. * as options to the control.
  1660. *
  1661. * @param {function} fn
  1662. */
  1663. load: function(fn) {
  1664. var self = this;
  1665. var $wrapper = self.$wrapper.addClass(self.settings.loadingClass);
  1666. self.loading++;
  1667. fn.apply(self, [function(results) {
  1668. self.loading = Math.max(self.loading - 1, 0);
  1669. if (results && results.length) {
  1670. self.addOption(results);
  1671. self.refreshOptions(self.isFocused && !self.isInputHidden);
  1672. }
  1673. if (!self.loading) {
  1674. $wrapper.removeClass(self.settings.loadingClass);
  1675. }
  1676. self.trigger('load', results);
  1677. }]);
  1678. },
  1679. /**
  1680. * Sets the input field of the control to the specified value.
  1681. *
  1682. * @param {string} value
  1683. */
  1684. setTextboxValue: function(value) {
  1685. var $input = this.$control_input;
  1686. var changed = $input.val() !== value;
  1687. if (changed) {
  1688. $input.val(value).triggerHandler('update');
  1689. this.lastValue = value;
  1690. }
  1691. },
  1692. /**
  1693. * Returns the value of the control. If multiple items
  1694. * can be selected (e.g. <select multiple>), this returns
  1695. * an array. If only one item can be selected, this
  1696. * returns a string.
  1697. *
  1698. * @returns {mixed}
  1699. */
  1700. getValue: function() {
  1701. if (this.tagType === TAG_SELECT && this.$input.attr('multiple')) {
  1702. return this.items;
  1703. } else {
  1704. return this.items.join(this.settings.delimiter);
  1705. }
  1706. },
  1707. /**
  1708. * Resets the selected items to the given value.
  1709. *
  1710. * @param {mixed} value
  1711. */
  1712. setValue: function(value, silent) {
  1713. var events = silent ? [] : ['change'];
  1714. debounce_events(this, events, function() {
  1715. this.clear(silent);
  1716. this.addItems(value, silent);
  1717. });
  1718. },
  1719. /**
  1720. * Sets the selected item.
  1721. *
  1722. * @param {object} $item
  1723. * @param {object} e (optional)
  1724. */
  1725. setActiveItem: function($item, e) {
  1726. var self = this;
  1727. var eventName;
  1728. var i, idx, begin, end, item, swap;
  1729. var $last;
  1730. if (self.settings.mode === 'single') return;
  1731. $item = $($item);
  1732. // clear the active selection
  1733. if (!$item.length) {
  1734. $(self.$activeItems).removeClass('active');
  1735. self.$activeItems = [];
  1736. if (self.isFocused) {
  1737. self.showInput();
  1738. }
  1739. return;
  1740. }
  1741. // modify selection
  1742. eventName = e && e.type.toLowerCase();
  1743. if (eventName === 'mousedown' && self.isShiftDown && self.$activeItems.length) {
  1744. $last = self.$control.children('.active:last');
  1745. begin = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$last[0]]);
  1746. end = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$item[0]]);
  1747. if (begin > end) {
  1748. swap = begin;
  1749. begin = end;
  1750. end = swap;
  1751. }
  1752. for (i = begin; i <= end; i++) {
  1753. item = self.$control[0].childNodes[i];
  1754. if (self.$activeItems.indexOf(item) === -1) {
  1755. $(item).addClass('active');
  1756. self.$activeItems.push(item);
  1757. }
  1758. }
  1759. e.preventDefault();
  1760. } else if ((eventName === 'mousedown' && self.isCtrlDown) || (eventName === 'keydown' && this.isShiftDown)) {
  1761. if ($item.hasClass('active')) {
  1762. idx = self.$activeItems.indexOf($item[0]);
  1763. self.$activeItems.splice(idx, 1);
  1764. $item.removeClass('active');
  1765. } else {
  1766. self.$activeItems.push($item.addClass('active')[0]);
  1767. }
  1768. } else {
  1769. $(self.$activeItems).removeClass('active');
  1770. self.$activeItems = [$item.addClass('active')[0]];
  1771. }
  1772. // ensure control has focus
  1773. self.hideInput();
  1774. if (!this.isFocused) {
  1775. self.focus();
  1776. }
  1777. },
  1778. /**
  1779. * Sets the selected item in the dropdown menu
  1780. * of available options.
  1781. *
  1782. * @param {object} $object
  1783. * @param {boolean} scroll
  1784. * @param {boolean} animate
  1785. */
  1786. setActiveOption: function($option, scroll, animate) {
  1787. var height_menu, height_item, y;
  1788. var scroll_top, scroll_bottom;
  1789. var self = this;
  1790. if (self.$activeOption) self.$activeOption.removeClass('active');
  1791. self.$activeOption = null;
  1792. $option = $($option);
  1793. if (!$option.length) return;
  1794. self.$activeOption = $option.addClass('active');
  1795. if (scroll || !isset(scroll)) {
  1796. height_menu = self.$dropdown_content.height();
  1797. height_item = self.$activeOption.outerHeight(true);
  1798. scroll = self.$dropdown_content.scrollTop() || 0;
  1799. y = self.$activeOption.offset().top - self.$dropdown_content.offset().top + scroll;
  1800. scroll_top = y;
  1801. scroll_bottom = y - height_menu + height_item;
  1802. if (y + height_item > height_menu + scroll) {
  1803. self.$dropdown_content.stop().animate({scrollTop: scroll_bottom}, animate ? self.settings.scrollDuration : 0);
  1804. } else if (y < scroll) {
  1805. self.$dropdown_content.stop().animate({scrollTop: scroll_top}, animate ? self.settings.scrollDuration : 0);
  1806. }
  1807. }
  1808. },
  1809. /**
  1810. * Selects all items (CTRL + A).
  1811. */
  1812. selectAll: function() {
  1813. var self = this;
  1814. if (self.settings.mode === 'single') return;
  1815. self.$activeItems = Array.prototype.slice.apply(self.$control.children(':not(input)').addClass('active'));
  1816. if (self.$activeItems.length) {
  1817. self.hideInput();
  1818. self.close();
  1819. }
  1820. self.focus();
  1821. },
  1822. /**
  1823. * Hides the input element out of view, while
  1824. * retaining its focus.
  1825. */
  1826. hideInput: function() {
  1827. var self = this;
  1828. self.setTextboxValue('');
  1829. self.$control_input.css({opacity: 0, position: 'absolute', left: self.rtl ? 10000 : -10000});
  1830. self.isInputHidden = true;
  1831. },
  1832. /**
  1833. * Restores input visibility.
  1834. */
  1835. showInput: function() {
  1836. this.$control_input.css({opacity: 1, position: 'relative', left: 0});
  1837. this.isInputHidden = false;
  1838. },
  1839. /**
  1840. * Gives the control focus.
  1841. */
  1842. focus: function() {
  1843. var self = this;
  1844. if (self.isDisabled) return;
  1845. self.ignoreFocus = true;
  1846. self.$control_input[0].focus();
  1847. window.setTimeout(function() {
  1848. self.ignoreFocus = false;
  1849. self.onFocus();
  1850. }, 0);
  1851. },
  1852. /**
  1853. * Forces the control out of focus.
  1854. *
  1855. * @param {Element} dest
  1856. */
  1857. blur: function(dest) {
  1858. this.$control_input[0].blur();
  1859. this.onBlur(null, dest);
  1860. },
  1861. /**
  1862. * Returns a function that scores an object
  1863. * to show how good of a match it is to the
  1864. * provided query.
  1865. *
  1866. * @param {string} query
  1867. * @param {object} options
  1868. * @return {function}
  1869. */
  1870. getScoreFunction: function(query) {
  1871. return this.sifter.getScoreFunction(query, this.getSearchOptions());
  1872. },
  1873. /**
  1874. * Returns search options for sifter (the system
  1875. * for scoring and sorting results).
  1876. *
  1877. * @see https://github.com/brianreavis/sifter.js
  1878. * @return {object}
  1879. */
  1880. getSearchOptions: function() {
  1881. var settings = this.settings;
  1882. var sort = settings.sortField;
  1883. if (typeof sort === 'string') {
  1884. sort = [{field: sort}];
  1885. }
  1886. return {
  1887. fields : settings.searchField,
  1888. conjunction : settings.searchConjunction,
  1889. sort : sort,
  1890. nesting : settings.nesting
  1891. };
  1892. },
  1893. /**
  1894. * Searches through available options and returns
  1895. * a sorted array of matches.
  1896. *
  1897. * Returns an object containing:
  1898. *
  1899. * - query {string}
  1900. * - tokens {array}
  1901. * - total {int}
  1902. * - items {array}
  1903. *
  1904. * @param {string} query
  1905. * @returns {object}
  1906. */
  1907. search: function(query) {
  1908. var i, value, score, result, calculateScore;
  1909. var self = this;
  1910. var settings = self.settings;
  1911. var options = this.getSearchOptions();
  1912. // validate user-provided result scoring function
  1913. if (settings.score) {
  1914. calculateScore = self.settings.score.apply(this, [query]);
  1915. if (typeof calculateScore !== 'function') {
  1916. throw new Error('Selectize "score" setting must be a function that returns a function');
  1917. }
  1918. }
  1919. // perform search
  1920. if (query !== self.lastQuery) {
  1921. self.lastQuery = query;
  1922. result = self.sifter.search(query, $.extend(options, {score: calculateScore}));
  1923. self.currentResults = result;
  1924. } else {
  1925. result = $.extend(true, {}, self.currentResults);
  1926. }
  1927. // filter out selected items
  1928. if (settings.hideSelected) {
  1929. for (i = result.items.length - 1; i >= 0; i--) {
  1930. if (self.items.indexOf(hash_key(result.items[i].id)) !== -1) {
  1931. result.items.splice(i, 1);
  1932. }
  1933. }
  1934. }
  1935. return result;
  1936. },
  1937. /**
  1938. * Refreshes the list of available options shown
  1939. * in the autocomplete dropdown menu.
  1940. *
  1941. * @param {boolean} triggerDropdown
  1942. */
  1943. refreshOptions: function(triggerDropdown) {
  1944. var i, j, k, n, groups, groups_order, option, option_html, optgroup, optgroups, html, html_children, has_create_option;
  1945. var $active, $active_before, $create;
  1946. if (typeof triggerDropdown === 'undefined') {
  1947. triggerDropdown = true;
  1948. }
  1949. var self = this;
  1950. var query = $.trim(self.$control_input.val());
  1951. var results = self.search(query);
  1952. var $dropdown_content = self.$dropdown_content;
  1953. var active_before = self.$activeOption && hash_key(self.$activeOption.attr('data-value'));
  1954. // build markup
  1955. n = results.items.length;
  1956. if (typeof self.settings.maxOptions === 'number') {
  1957. n = Math.min(n, self.settings.maxOptions);
  1958. }
  1959. // render and group available options individually
  1960. groups = {};
  1961. groups_order = [];
  1962. for (i = 0; i < n; i++) {
  1963. option = self.options[results.items[i].id];
  1964. option_html = self.render('option', option);
  1965. optgroup = option[self.settings.optgroupField] || '';
  1966. optgroups = $.isArray(optgroup) ? optgroup : [optgroup];
  1967. for (j = 0, k = optgroups && optgroups.length; j < k; j++) {
  1968. optgroup = optgroups[j];
  1969. if (!self.optgroups.hasOwnProperty(optgroup)) {
  1970. optgroup = '';
  1971. }
  1972. if (!groups.hasOwnProperty(optgroup)) {
  1973. groups[optgroup] = document.createDocumentFragment();
  1974. groups_order.push(optgroup);
  1975. }
  1976. groups[optgroup].appendChild(option_html);
  1977. }
  1978. }
  1979. // sort optgroups
  1980. if (this.settings.lockOptgroupOrder) {
  1981. groups_order.sort(function(a, b) {
  1982. var a_order = self.optgroups[a].$order || 0;
  1983. var b_order = self.optgroups[b].$order || 0;
  1984. return a_order - b_order;
  1985. });
  1986. }
  1987. // render optgroup headers & join groups
  1988. html = document.createDocumentFragment();
  1989. for (i = 0, n = groups_order.length; i < n; i++) {
  1990. optgroup = groups_order[i];
  1991. if (self.optgroups.hasOwnProperty(optgroup) && groups[optgroup].childNodes.length) {
  1992. // render the optgroup header and options within it,
  1993. // then pass it to the wrapper template
  1994. html_children = document.createDocumentFragment();
  1995. html_children.appendChild(self.render('optgroup_header', self.optgroups[optgroup]));
  1996. html_children.appendChild(groups[optgroup]);
  1997. html.appendChild(self.render('optgroup', $.extend({}, self.optgroups[optgroup], {
  1998. html: domToString(html_children),
  1999. dom: html_children
  2000. })));
  2001. } else {
  2002. html.appendChild(groups[optgroup]);
  2003. }
  2004. }
  2005. $dropdown_content.html(html);
  2006. // highlight matching terms inline
  2007. if (self.settings.highlight) {
  2008. $dropdown_content.removeHighlight();
  2009. if (results.query.length && results.tokens.length) {
  2010. for (i = 0, n = results.tokens.length; i < n; i++) {
  2011. highlight($dropdown_content, results.tokens[i].regex);
  2012. }
  2013. }
  2014. }
  2015. // add "selected" class to selected options
  2016. if (!self.settings.hideSelected) {
  2017. for (i = 0, n = self.items.length; i < n; i++) {
  2018. self.getOption(self.items[i]).addClass('selected');
  2019. }
  2020. }
  2021. // add create option
  2022. has_create_option = self.canCreate(query);
  2023. if (has_create_option) {
  2024. $dropdown_content.prepend(self.render('option_create', {input: query}));
  2025. $create = $($dropdown_content[0].childNodes[0]);
  2026. }
  2027. // activate
  2028. self.hasOptions = results.items.length > 0 || has_create_option;
  2029. if (self.hasOptions) {
  2030. if (results.items.length > 0) {
  2031. $active_before = active_before && self.getOption(active_before);
  2032. if ($active_before && $active_before.length) {
  2033. $active = $active_before;
  2034. } else if (self.settings.mode === 'single' && self.items.length) {
  2035. $active = self.getOption(self.items[0]);
  2036. }
  2037. if (!$active || !$active.length) {
  2038. if ($create && !self.settings.addPrecedence) {
  2039. $active = self.getAdjacentOption($create, 1);
  2040. } else {
  2041. $active = $dropdown_content.find('[data-selectable]:first');
  2042. }
  2043. }
  2044. } else {
  2045. $active = $create;
  2046. }
  2047. self.setActiveOption($active);
  2048. if (triggerDropdown && !self.isOpen) { self.open(); }
  2049. } else {
  2050. self.setActiveOption(null);
  2051. if (triggerDropdown && self.isOpen) { self.close(); }
  2052. }
  2053. },
  2054. /**
  2055. * Adds an available option. If it already exists,
  2056. * nothing will happen. Note: this does not refresh
  2057. * the options list dropdown (use `refreshOptions`
  2058. * for that).
  2059. *
  2060. * Usage:
  2061. *
  2062. * this.addOption(data)
  2063. *
  2064. * @param {object|array} data
  2065. */
  2066. addOption: function(data) {
  2067. var i, n, value, self = this;
  2068. if ($.isArray(data)) {
  2069. for (i = 0, n = data.length; i < n; i++) {
  2070. self.addOption(data[i]);
  2071. }
  2072. return;
  2073. }
  2074. if (value = self.registerOption(data)) {
  2075. self.userOptions[value] = true;
  2076. self.lastQuery = null;
  2077. self.trigger('option_add', value, data);
  2078. }
  2079. },
  2080. /**
  2081. * Registers an option to the pool of options.
  2082. *
  2083. * @param {object} data
  2084. * @return {boolean|string}
  2085. */
  2086. registerOption: function(data) {
  2087. var key = hash_key(data[this.settings.valueField]);
  2088. if (typeof key === 'undefined' || key === null || this.options.hasOwnProperty(key)) return false;
  2089. data.$order = data.$order || ++this.order;
  2090. this.options[key] = data;
  2091. return key;
  2092. },
  2093. /**
  2094. * Registers an option group to the pool of option groups.
  2095. *
  2096. * @param {object} data
  2097. * @return {boolean|string}
  2098. */
  2099. registerOptionGroup: function(data) {
  2100. var key = hash_key(data[this.settings.optgroupValueField]);
  2101. if (!key) return false;
  2102. data.$order = data.$order || ++this.order;
  2103. this.optgroups[key] = data;
  2104. return key;
  2105. },
  2106. /**
  2107. * Registers a new optgroup for options
  2108. * to be bucketed into.
  2109. *
  2110. * @param {string} id
  2111. * @param {object} data
  2112. */
  2113. addOptionGroup: function(id, data) {
  2114. data[this.settings.optgroupValueField] = id;
  2115. if (id = this.registerOptionGroup(data)) {
  2116. this.trigger('optgroup_add', id, data);
  2117. }
  2118. },
  2119. /**
  2120. * Removes an existing option group.
  2121. *
  2122. * @param {string} id
  2123. */
  2124. removeOptionGroup: function(id) {
  2125. if (this.optgroups.hasOwnProperty(id)) {
  2126. delete this.optgroups[id];
  2127. this.renderCache = {};
  2128. this.trigger('optgroup_remove', id);
  2129. }
  2130. },
  2131. /**
  2132. * Clears all existing option groups.
  2133. */
  2134. clearOptionGroups: function() {
  2135. this.optgroups = {};
  2136. this.renderCache = {};
  2137. this.trigger('optgroup_clear');
  2138. },
  2139. /**
  2140. * Updates an option available for selection. If
  2141. * it is visible in the selected items or options
  2142. * dropdown, it will be re-rendered automatically.
  2143. *
  2144. * @param {string} value
  2145. * @param {object} data
  2146. */
  2147. updateOption: function(value, data) {
  2148. var self = this;
  2149. var $item, $item_new;
  2150. var value_new, index_item, cache_items, cache_options, order_old;
  2151. value = hash_key(value);
  2152. value_new = hash_key(data[self.settings.valueField]);
  2153. // sanity checks
  2154. if (value === null) return;
  2155. if (!self.options.hasOwnProperty(value)) return;
  2156. if (typeof value_new !== 'string') throw new Error('Value must be set in option data');
  2157. order_old = self.options[value].$order;
  2158. // update references
  2159. if (value_new !== value) {
  2160. delete self.options[value];
  2161. index_item = self.items.indexOf(value);
  2162. if (index_item !== -1) {
  2163. self.items.splice(index_item, 1, value_new);
  2164. }
  2165. }
  2166. data.$order = data.$order || order_old;
  2167. self.options[value_new] = data;
  2168. // invalidate render cache
  2169. cache_items = self.renderCache['item'];
  2170. cache_options = self.renderCache['option'];
  2171. if (cache_items) {
  2172. delete cache_items[value];
  2173. delete cache_items[value_new];
  2174. }
  2175. if (cache_options) {
  2176. delete cache_options[value];
  2177. delete cache_options[value_new];
  2178. }
  2179. // update the item if it's selected
  2180. if (self.items.indexOf(value_new) !== -1) {
  2181. $item = self.getItem(value);
  2182. $item_new = $(self.render('item', data));
  2183. if ($item.hasClass('active')) $item_new.addClass('active');
  2184. $item.replaceWith($item_new);
  2185. }
  2186. // invalidate last query because we might have updated the sortField
  2187. self.lastQuery = null;
  2188. // update dropdown contents
  2189. if (self.isOpen) {
  2190. self.refreshOptions(false);
  2191. }
  2192. },
  2193. /**
  2194. * Removes a single option.
  2195. *
  2196. * @param {string} value
  2197. * @param {boolean} silent
  2198. */
  2199. removeOption: function(value, silent) {
  2200. var self = this;
  2201. value = hash_key(value);
  2202. var cache_items = self.renderCache['item'];
  2203. var cache_options = self.renderCache['option'];
  2204. if (cache_items) delete cache_items[value];
  2205. if (cache_options) delete cache_options[value];
  2206. delete self.userOptions[value];
  2207. delete self.options[value];
  2208. self.lastQuery = null;
  2209. self.trigger('option_remove', value);
  2210. self.removeItem(value, silent);
  2211. },
  2212. /**
  2213. * Clears all options.
  2214. */
  2215. clearOptions: function() {
  2216. var self = this;
  2217. self.loadedSearches = {};
  2218. self.userOptions = {};
  2219. self.renderCache = {};
  2220. var options = self.options;
  2221. $.each(self.options, function(key, value) {
  2222. if(self.items.indexOf(key) == -1) {
  2223. delete options[key];
  2224. }
  2225. });
  2226. self.options = self.sifter.items = options;
  2227. self.lastQuery = null;
  2228. self.trigger('option_clear');
  2229. },
  2230. /**
  2231. * Returns the jQuery element of the option
  2232. * matching the given value.
  2233. *
  2234. * @param {string} value
  2235. * @returns {object}
  2236. */
  2237. getOption: function(value) {
  2238. return this.getElementWithValue(value, this.$dropdown_content.find('[data-selectable]'));
  2239. },
  2240. /**
  2241. * Returns the jQuery element of the next or
  2242. * previous selectable option.
  2243. *
  2244. * @param {object} $option
  2245. * @param {int} direction can be 1 for next or -1 for previous
  2246. * @return {object}
  2247. */
  2248. getAdjacentOption: function($option, direction) {
  2249. var $options = this.$dropdown.find('[data-selectable]');
  2250. var index = $options.index($option) + direction;
  2251. return index >= 0 && index < $options.length ? $options.eq(index) : $();
  2252. },
  2253. /**
  2254. * Finds the first element with a "data-value" attribute
  2255. * that matches the given value.
  2256. *
  2257. * @param {mixed} value
  2258. * @param {object} $els
  2259. * @return {object}
  2260. */
  2261. getElementWithValue: function(value, $els) {
  2262. value = hash_key(value);
  2263. if (typeof value !== 'undefined' && value !== null) {
  2264. for (var i = 0, n = $els.length; i < n; i++) {
  2265. if ($els[i].getAttribute('data-value') === value) {
  2266. return $($els[i]);
  2267. }
  2268. }
  2269. }
  2270. return $();
  2271. },
  2272. /**
  2273. * Returns the jQuery element of the item
  2274. * matching the given value.
  2275. *
  2276. * @param {string} value
  2277. * @returns {object}
  2278. */
  2279. getItem: function(value) {
  2280. return this.getElementWithValue(value, this.$control.children());
  2281. },
  2282. /**
  2283. * "Selects" multiple items at once. Adds them to the list
  2284. * at the current caret position.
  2285. *
  2286. * @param {string} value
  2287. * @param {boolean} silent
  2288. */
  2289. addItems: function(values, silent) {
  2290. this.buffer = document.createDocumentFragment();
  2291. var childNodes = this.$control[0].childNodes;
  2292. for (var i = 0; i < childNodes.length; i++) {
  2293. this.buffer.appendChild(childNodes[i]);
  2294. }
  2295. var items = $.isArray(values) ? values : [values];
  2296. for (var i = 0, n = items.length; i < n; i++) {
  2297. this.isPending = (i < n - 1);
  2298. this.addItem(items[i], silent);
  2299. }
  2300. var control = this.$control[0];
  2301. control.insertBefore(this.buffer, control.firstChild);
  2302. this.buffer = null;
  2303. },
  2304. /**
  2305. * "Selects" an item. Adds it to the list
  2306. * at the current caret position.
  2307. *
  2308. * @param {string} value
  2309. * @param {boolean} silent
  2310. */
  2311. addItem: function(value, silent) {
  2312. var events = silent ? [] : ['change'];
  2313. debounce_events(this, events, function() {
  2314. var $item, $option, $options;
  2315. var self = this;
  2316. var inputMode = self.settings.mode;
  2317. var i, active, value_next, wasFull;
  2318. value = hash_key(value);
  2319. if (self.items.indexOf(value) !== -1) {
  2320. if (inputMode === 'single') self.close();
  2321. return;
  2322. }
  2323. if (!self.options.hasOwnProperty(value)) return;
  2324. if (inputMode === 'single') self.clear(silent);
  2325. if (inputMode === 'multi' && self.isFull()) return;
  2326. $item = $(self.render('item', self.options[value]));
  2327. wasFull = self.isFull();
  2328. self.items.splice(self.caretPos, 0, value);
  2329. self.insertAtCaret($item);
  2330. if (!self.isPending || (!wasFull && self.isFull())) {
  2331. self.refreshState();
  2332. }
  2333. if (self.isSetup) {
  2334. $options = self.$dropdown_content.find('[data-selectable]');
  2335. // update menu / remove the option (if this is not one item being added as part of series)
  2336. if (!self.isPending) {
  2337. $option = self.getOption(value);
  2338. value_next = self.getAdjacentOption($option, 1).attr('data-value');
  2339. self.refreshOptions(self.isFocused && inputMode !== 'single');
  2340. if (value_next) {
  2341. self.setActiveOption(self.getOption(value_next));
  2342. }
  2343. }
  2344. // hide the menu if the maximum number of items have been selected or no options are left
  2345. if (!$options.length || self.isFull()) {
  2346. self.close();
  2347. } else if (!self.isPending) {
  2348. self.positionDropdown();
  2349. }
  2350. self.updatePlaceholder();
  2351. self.trigger('item_add', value, $item);
  2352. if (!self.isPending) {
  2353. self.updateOriginalInput({silent: silent});
  2354. }
  2355. }
  2356. });
  2357. },
  2358. /**
  2359. * Removes the selected item matching
  2360. * the provided value.
  2361. *
  2362. * @param {string} value
  2363. */
  2364. removeItem: function(value, silent) {
  2365. var self = this;
  2366. var $item, i, idx;
  2367. $item = (value instanceof $) ? value : self.getItem(value);
  2368. value = hash_key($item.attr('data-value'));
  2369. i = self.items.indexOf(value);
  2370. if (i !== -1) {
  2371. $item.remove();
  2372. if ($item.hasClass('active')) {
  2373. idx = self.$activeItems.indexOf($item[0]);
  2374. self.$activeItems.splice(idx, 1);
  2375. }
  2376. self.items.splice(i, 1);
  2377. self.lastQuery = null;
  2378. if (!self.settings.persist && self.userOptions.hasOwnProperty(value)) {
  2379. self.removeOption(value, silent);
  2380. }
  2381. if (i < self.caretPos) {
  2382. self.setCaret(self.caretPos - 1);
  2383. }
  2384. self.refreshState();
  2385. self.updatePlaceholder();
  2386. self.updateOriginalInput({silent: silent});
  2387. self.positionDropdown();
  2388. self.trigger('item_remove', value, $item);
  2389. }
  2390. },
  2391. /**
  2392. * Invokes the `create` method provided in the
  2393. * selectize options that should provide the data
  2394. * for the new item, given the user input.
  2395. *
  2396. * Once this completes, it will be added
  2397. * to the item list.
  2398. *
  2399. * @param {string} value
  2400. * @param {boolean} [triggerDropdown]
  2401. * @param {function} [callback]
  2402. * @return {boolean}
  2403. */
  2404. createItem: function(input, triggerDropdown) {
  2405. var self = this;
  2406. var caret = self.caretPos;
  2407. input = input || $.trim(self.$control_input.val() || '');
  2408. var callback = arguments[arguments.length - 1];
  2409. if (typeof callback !== 'function') callback = function() {};
  2410. if (typeof triggerDropdown !== 'boolean') {
  2411. triggerDropdown = true;
  2412. }
  2413. if (!self.canCreate(input)) {
  2414. callback();
  2415. return false;
  2416. }
  2417. self.lock();
  2418. var setup = (typeof self.settings.create === 'function') ? this.settings.create : function(input) {
  2419. var data = {};
  2420. data[self.settings.labelField] = input;
  2421. data[self.settings.valueField] = input;
  2422. return data;
  2423. };
  2424. var create = once(function(data) {
  2425. self.unlock();
  2426. if (!data || typeof data !== 'object') return callback();
  2427. var value = hash_key(data[self.settings.valueField]);
  2428. if (typeof value !== 'string') return callback();
  2429. self.setTextboxValue('');
  2430. self.addOption(data);
  2431. self.setCaret(caret);
  2432. self.addItem(value);
  2433. self.refreshOptions(triggerDropdown && self.settings.mode !== 'single');
  2434. callback(data);
  2435. });
  2436. var output = setup.apply(this, [input, create]);
  2437. if (typeof output !== 'undefined') {
  2438. create(output);
  2439. }
  2440. return true;
  2441. },
  2442. /**
  2443. * Re-renders the selected item lists.
  2444. */
  2445. refreshItems: function() {
  2446. this.lastQuery = null;
  2447. if (this.isSetup) {
  2448. this.addItem(this.items);
  2449. }
  2450. this.refreshState();
  2451. this.updateOriginalInput();
  2452. },
  2453. /**
  2454. * Updates all state-dependent attributes
  2455. * and CSS classes.
  2456. */
  2457. refreshState: function() {
  2458. this.refreshValidityState();
  2459. this.refreshClasses();
  2460. },
  2461. /**
  2462. * Update the `required` attribute of both input and control input.
  2463. *
  2464. * The `required` property needs to be activated on the control input
  2465. * for the error to be displayed at the right place. `required` also
  2466. * needs to be temporarily deactivated on the input since the input is
  2467. * hidden and can't show errors.
  2468. */
  2469. refreshValidityState: function() {
  2470. if (!this.isRequired) return false;
  2471. var invalid = !this.items.length;
  2472. this.isInvalid = invalid;
  2473. this.$control_input.prop('required', invalid);
  2474. this.$input.prop('required', !invalid);
  2475. },
  2476. /**
  2477. * Updates all state-dependent CSS classes.
  2478. */
  2479. refreshClasses: function() {
  2480. var self = this;
  2481. var isFull = self.isFull();
  2482. var isLocked = self.isLocked;
  2483. self.$wrapper
  2484. .toggleClass('rtl', self.rtl);
  2485. self.$control
  2486. .toggleClass('focus', self.isFocused)
  2487. .toggleClass('disabled', self.isDisabled)
  2488. .toggleClass('required', self.isRequired)
  2489. .toggleClass('invalid', self.isInvalid)
  2490. .toggleClass('locked', isLocked)
  2491. .toggleClass('full', isFull).toggleClass('not-full', !isFull)
  2492. .toggleClass('input-active', self.isFocused && !self.isInputHidden)
  2493. .toggleClass('dropdown-active', self.isOpen)
  2494. .toggleClass('has-options', !$.isEmptyObject(self.options))
  2495. .toggleClass('has-items', self.items.length > 0);
  2496. self.$control_input.data('grow', !isFull && !isLocked);
  2497. },
  2498. /**
  2499. * Determines whether or not more items can be added
  2500. * to the control without exceeding the user-defined maximum.
  2501. *
  2502. * @returns {boolean}
  2503. */
  2504. isFull: function() {
  2505. return this.settings.maxItems !== null && this.items.length >= this.settings.maxItems;
  2506. },
  2507. /**
  2508. * Refreshes the original <select> or <input>
  2509. * element to reflect the current state.
  2510. */
  2511. updateOriginalInput: function(opts) {
  2512. var i, n, options, label, self = this;
  2513. opts = opts || {};
  2514. if (self.tagType === TAG_SELECT) {
  2515. options = [];
  2516. for (i = 0, n = self.items.length; i < n; i++) {
  2517. label = self.options[self.items[i]][self.settings.labelField] || '';
  2518. options.push('<option value="' + escape_html(self.items[i]) + '" selected="selected">' + escape_html(label) + '</option>');
  2519. }
  2520. if (!options.length && !this.$input.attr('multiple')) {
  2521. options.push('<option value="" selected="selected"></option>');
  2522. }
  2523. self.$input.html(options.join(''));
  2524. } else {
  2525. self.$input.val(self.getValue());
  2526. self.$input.attr('value',self.$input.val());
  2527. }
  2528. if (self.isSetup) {
  2529. if (!opts.silent) {
  2530. self.trigger('change', self.$input.val());
  2531. }
  2532. }
  2533. },
  2534. /**
  2535. * Shows/hide the input placeholder depending
  2536. * on if there items in the list already.
  2537. */
  2538. updatePlaceholder: function() {
  2539. if (!this.settings.placeholder) return;
  2540. var $input = this.$control_input;
  2541. if (this.items.length) {
  2542. $input.removeAttr('placeholder');
  2543. } else {
  2544. $input.attr('placeholder', this.settings.placeholder);
  2545. }
  2546. $input.triggerHandler('update', {force: true});
  2547. },
  2548. /**
  2549. * Shows the autocomplete dropdown containing
  2550. * the available options.
  2551. */
  2552. open: function() {
  2553. var self = this;
  2554. if (self.isLocked || self.isOpen || (self.settings.mode === 'multi' && self.isFull())) return;
  2555. self.focus();
  2556. self.isOpen = true;
  2557. self.refreshState();
  2558. self.$dropdown.css({visibility: 'hidden', display: 'block'});
  2559. self.positionDropdown();
  2560. self.$dropdown.css({visibility: 'visible'});
  2561. self.trigger('dropdown_open', self.$dropdown);
  2562. },
  2563. /**
  2564. * Closes the autocomplete dropdown menu.
  2565. */
  2566. close: function() {
  2567. var self = this;
  2568. var trigger = self.isOpen;
  2569. if (self.settings.mode === 'single' && self.items.length) {
  2570. self.hideInput();
  2571. // Do not trigger blur while inside a blur event,
  2572. // this fixes some weird tabbing behavior in FF and IE.
  2573. // See #1164
  2574. if (!self.isBlurring) {
  2575. self.$control_input.blur(); // close keyboard on iOS
  2576. }
  2577. }
  2578. self.isOpen = false;
  2579. self.$dropdown.hide();
  2580. self.setActiveOption(null);
  2581. self.refreshState();
  2582. if (trigger) self.trigger('dropdown_close', self.$dropdown);
  2583. },
  2584. /**
  2585. * Calculates and applies the appropriate
  2586. * position of the dropdown.
  2587. */
  2588. positionDropdown: function() {
  2589. var $control = this.$control;
  2590. var offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position();
  2591. offset.top += $control.outerHeight(true);
  2592. this.$dropdown.css({
  2593. width : $control[0].getBoundingClientRect().width,
  2594. top : offset.top,
  2595. left : offset.left
  2596. });
  2597. },
  2598. /**
  2599. * Resets / clears all selected items
  2600. * from the control.
  2601. *
  2602. * @param {boolean} silent
  2603. */
  2604. clear: function(silent) {
  2605. var self = this;
  2606. if (!self.items.length) return;
  2607. self.$control.children(':not(input)').remove();
  2608. self.items = [];
  2609. self.lastQuery = null;
  2610. self.setCaret(0);
  2611. self.setActiveItem(null);
  2612. self.updatePlaceholder();
  2613. self.updateOriginalInput({silent: silent});
  2614. self.refreshState();
  2615. self.showInput();
  2616. self.trigger('clear');
  2617. },
  2618. /**
  2619. * A helper method for inserting an element
  2620. * at the current caret position.
  2621. *
  2622. * @param {object} $el
  2623. */
  2624. insertAtCaret: function($el) {
  2625. var caret = Math.min(this.caretPos, this.items.length);
  2626. var el = $el[0];
  2627. var target = this.buffer || this.$control[0];
  2628. if (caret === 0) {
  2629. target.insertBefore(el, target.firstChild);
  2630. } else {
  2631. target.insertBefore(el, target.childNodes[caret]);
  2632. }
  2633. this.setCaret(caret + 1);
  2634. },
  2635. /**
  2636. * Removes the current selected item(s).
  2637. *
  2638. * @param {object} e (optional)
  2639. * @returns {boolean}
  2640. */
  2641. deleteSelection: function(e) {
  2642. var i, n, direction, selection, values, caret, option_select, $option_select, $tail;
  2643. var self = this;
  2644. direction = (e && e.keyCode === KEY_BACKSPACE) ? -1 : 1;
  2645. selection = getSelection(self.$control_input[0]);
  2646. if (self.$activeOption && !self.settings.hideSelected) {
  2647. option_select = self.getAdjacentOption(self.$activeOption, -1).attr('data-value');
  2648. }
  2649. // determine items that will be removed
  2650. values = [];
  2651. if (self.$activeItems.length) {
  2652. $tail = self.$control.children('.active:' + (direction > 0 ? 'last' : 'first'));
  2653. caret = self.$control.children(':not(input)').index($tail);
  2654. if (direction > 0) { caret++; }
  2655. for (i = 0, n = self.$activeItems.length; i < n; i++) {
  2656. values.push($(self.$activeItems[i]).attr('data-value'));
  2657. }
  2658. if (e) {
  2659. e.preventDefault();
  2660. e.stopPropagation();
  2661. }
  2662. } else if ((self.isFocused || self.settings.mode === 'single') && self.items.length) {
  2663. if (direction < 0 && selection.start === 0 && selection.length === 0) {
  2664. values.push(self.items[self.caretPos - 1]);
  2665. } else if (direction > 0 && selection.start === self.$control_input.val().length) {
  2666. values.push(self.items[self.caretPos]);
  2667. }
  2668. }
  2669. // allow the callback to abort
  2670. if (!values.length || (typeof self.settings.onDelete === 'function' && self.settings.onDelete.apply(self, [values]) === false)) {
  2671. return false;
  2672. }
  2673. // perform removal
  2674. if (typeof caret !== 'undefined') {
  2675. self.setCaret(caret);
  2676. }
  2677. while (values.length) {
  2678. self.removeItem(values.pop());
  2679. }
  2680. self.showInput();
  2681. self.positionDropdown();
  2682. self.refreshOptions(true);
  2683. // select previous option
  2684. if (option_select) {
  2685. $option_select = self.getOption(option_select);
  2686. if ($option_select.length) {
  2687. self.setActiveOption($option_select);
  2688. }
  2689. }
  2690. return true;
  2691. },
  2692. /**
  2693. * Selects the previous / next item (depending
  2694. * on the `direction` argument).
  2695. *
  2696. * > 0 - right
  2697. * < 0 - left
  2698. *
  2699. * @param {int} direction
  2700. * @param {object} e (optional)
  2701. */
  2702. advanceSelection: function(direction, e) {
  2703. var tail, selection, idx, valueLength, cursorAtEdge, $tail;
  2704. var self = this;
  2705. if (direction === 0) return;
  2706. if (self.rtl) direction *= -1;
  2707. tail = direction > 0 ? 'last' : 'first';
  2708. selection = getSelection(self.$control_input[0]);
  2709. if (self.isFocused && !self.isInputHidden) {
  2710. valueLength = self.$control_input.val().length;
  2711. cursorAtEdge = direction < 0
  2712. ? selection.start === 0 && selection.length === 0
  2713. : selection.start === valueLength;
  2714. if (cursorAtEdge && !valueLength) {
  2715. self.advanceCaret(direction, e);
  2716. }
  2717. } else {
  2718. $tail = self.$control.children('.active:' + tail);
  2719. if ($tail.length) {
  2720. idx = self.$control.children(':not(input)').index($tail);
  2721. self.setActiveItem(null);
  2722. self.setCaret(direction > 0 ? idx + 1 : idx);
  2723. }
  2724. }
  2725. },
  2726. /**
  2727. * Moves the caret left / right.
  2728. *
  2729. * @param {int} direction
  2730. * @param {object} e (optional)
  2731. */
  2732. advanceCaret: function(direction, e) {
  2733. var self = this, fn, $adj;
  2734. if (direction === 0) return;
  2735. fn = direction > 0 ? 'next' : 'prev';
  2736. if (self.isShiftDown) {
  2737. $adj = self.$control_input[fn]();
  2738. if ($adj.length) {
  2739. self.hideInput();
  2740. self.setActiveItem($adj);
  2741. e && e.preventDefault();
  2742. }
  2743. } else {
  2744. self.setCaret(self.caretPos + direction);
  2745. }
  2746. },
  2747. /**
  2748. * Moves the caret to the specified index.
  2749. *
  2750. * @param {int} i
  2751. */
  2752. setCaret: function(i) {
  2753. var self = this;
  2754. if (self.settings.mode === 'single') {
  2755. i = self.items.length;
  2756. } else {
  2757. i = Math.max(0, Math.min(self.items.length, i));
  2758. }
  2759. if(!self.isPending) {
  2760. // the input must be moved by leaving it in place and moving the
  2761. // siblings, due to the fact that focus cannot be restored once lost
  2762. // on mobile webkit devices
  2763. var j, n, fn, $children, $child;
  2764. $children = self.$control.children(':not(input)');
  2765. for (j = 0, n = $children.length; j < n; j++) {
  2766. $child = $($children[j]).detach();
  2767. if (j < i) {
  2768. self.$control_input.before($child);
  2769. } else {
  2770. self.$control.append($child);
  2771. }
  2772. }
  2773. }
  2774. self.caretPos = i;
  2775. },
  2776. /**
  2777. * Disables user input on the control. Used while
  2778. * items are being asynchronously created.
  2779. */
  2780. lock: function() {
  2781. this.close();
  2782. this.isLocked = true;
  2783. this.refreshState();
  2784. },
  2785. /**
  2786. * Re-enables user input on the control.
  2787. */
  2788. unlock: function() {
  2789. this.isLocked = false;
  2790. this.refreshState();
  2791. },
  2792. /**
  2793. * Disables user input on the control completely.
  2794. * While disabled, it cannot receive focus.
  2795. */
  2796. disable: function() {
  2797. var self = this;
  2798. self.$input.prop('disabled', true);
  2799. self.$control_input.prop('disabled', true).prop('tabindex', -1);
  2800. self.isDisabled = true;
  2801. self.lock();
  2802. },
  2803. /**
  2804. * Enables the control so that it can respond
  2805. * to focus and user input.
  2806. */
  2807. enable: function() {
  2808. var self = this;
  2809. self.$input.prop('disabled', false);
  2810. self.$control_input.prop('disabled', false).prop('tabindex', self.tabIndex);
  2811. self.isDisabled = false;
  2812. self.unlock();
  2813. },
  2814. /**
  2815. * Completely destroys the control and
  2816. * unbinds all event listeners so that it can
  2817. * be garbage collected.
  2818. */
  2819. destroy: function() {
  2820. var self = this;
  2821. var eventNS = self.eventNS;
  2822. var revertSettings = self.revertSettings;
  2823. self.trigger('destroy');
  2824. self.off();
  2825. self.$wrapper.remove();
  2826. self.$dropdown.remove();
  2827. self.$input
  2828. .html('')
  2829. .append(revertSettings.$children)
  2830. .removeAttr('tabindex')
  2831. .removeClass('selectized')
  2832. .attr({tabindex: revertSettings.tabindex})
  2833. .show();
  2834. self.$control_input.removeData('grow');
  2835. self.$input.removeData('selectize');
  2836. if (--Selectize.count == 0 && Selectize.$testInput) {
  2837. Selectize.$testInput.remove();
  2838. Selectize.$testInput = undefined;
  2839. }
  2840. $(window).off(eventNS);
  2841. $(document).off(eventNS);
  2842. $(document.body).off(eventNS);
  2843. delete self.$input[0].selectize;
  2844. },
  2845. /**
  2846. * A helper method for rendering "item" and
  2847. * "option" templates, given the data.
  2848. *
  2849. * @param {string} templateName
  2850. * @param {object} data
  2851. * @returns {string}
  2852. */
  2853. render: function(templateName, data) {
  2854. var value, id, label;
  2855. var html = '';
  2856. var cache = false;
  2857. var self = this;
  2858. var regex_tag = /^[\t \r\n]*<([a-z][a-z0-9\-_]*(?:\:[a-z][a-z0-9\-_]*)?)/i;
  2859. if (templateName === 'option' || templateName === 'item') {
  2860. value = hash_key(data[self.settings.valueField]);
  2861. cache = !!value;
  2862. }
  2863. // pull markup from cache if it exists
  2864. if (cache) {
  2865. if (!isset(self.renderCache[templateName])) {
  2866. self.renderCache[templateName] = {};
  2867. }
  2868. if (self.renderCache[templateName].hasOwnProperty(value)) {
  2869. return self.renderCache[templateName][value];
  2870. }
  2871. }
  2872. // render markup
  2873. html = $(self.settings.render[templateName].apply(this, [data, escape_html]));
  2874. // add mandatory attributes
  2875. if (templateName === 'option' || templateName === 'option_create') {
  2876. if (!data[self.settings.disabledField]) {
  2877. html.attr('data-selectable', '');
  2878. }
  2879. }
  2880. else if (templateName === 'optgroup') {
  2881. id = data[self.settings.optgroupValueField] || '';
  2882. html.attr('data-group', id);
  2883. if(data[self.settings.disabledField]) {
  2884. html.attr('data-disabled', '');
  2885. }
  2886. }
  2887. if (templateName === 'option' || templateName === 'item') {
  2888. html.attr('data-value', value || '');
  2889. }
  2890. // update cache
  2891. if (cache) {
  2892. self.renderCache[templateName][value] = html[0];
  2893. }
  2894. return html[0];
  2895. },
  2896. /**
  2897. * Clears the render cache for a template. If
  2898. * no template is given, clears all render
  2899. * caches.
  2900. *
  2901. * @param {string} templateName
  2902. */
  2903. clearCache: function(templateName) {
  2904. var self = this;
  2905. if (typeof templateName === 'undefined') {
  2906. self.renderCache = {};
  2907. } else {
  2908. delete self.renderCache[templateName];
  2909. }
  2910. },
  2911. /**
  2912. * Determines whether or not to display the
  2913. * create item prompt, given a user input.
  2914. *
  2915. * @param {string} input
  2916. * @return {boolean}
  2917. */
  2918. canCreate: function(input) {
  2919. var self = this;
  2920. if (!self.settings.create) return false;
  2921. var filter = self.settings.createFilter;
  2922. return input.length
  2923. && (typeof filter !== 'function' || filter.apply(self, [input]))
  2924. && (typeof filter !== 'string' || new RegExp(filter).test(input))
  2925. && (!(filter instanceof RegExp) || filter.test(input));
  2926. }
  2927. });
  2928. Selectize.count = 0;
  2929. Selectize.defaults = {
  2930. options: [],
  2931. optgroups: [],
  2932. plugins: [],
  2933. delimiter: ',',
  2934. splitOn: null, // regexp or string for splitting up values from a paste command
  2935. persist: true,
  2936. diacritics: true,
  2937. create: false,
  2938. createOnBlur: false,
  2939. createFilter: null,
  2940. highlight: true,
  2941. openOnFocus: true,
  2942. maxOptions: 1000,
  2943. maxItems: null,
  2944. hideSelected: null,
  2945. addPrecedence: false,
  2946. selectOnTab: false,
  2947. preload: false,
  2948. allowEmptyOption: false,
  2949. closeAfterSelect: false,
  2950. scrollDuration: 60,
  2951. loadThrottle: 300,
  2952. loadingClass: 'loading',
  2953. dataAttr: 'data-data',
  2954. optgroupField: 'optgroup',
  2955. valueField: 'value',
  2956. labelField: 'text',
  2957. disabledField: 'disabled',
  2958. optgroupLabelField: 'label',
  2959. optgroupValueField: 'value',
  2960. lockOptgroupOrder: false,
  2961. sortField: '$order',
  2962. searchField: ['text'],
  2963. searchConjunction: 'and',
  2964. mode: null,
  2965. wrapperClass: 'selectize-control',
  2966. inputClass: 'selectize-input',
  2967. dropdownClass: 'selectize-dropdown',
  2968. dropdownContentClass: 'selectize-dropdown-content',
  2969. dropdownParent: null,
  2970. copyClassesToDropdown: true,
  2971. /*
  2972. load : null, // function(query, callback) { ... }
  2973. score : null, // function(search) { ... }
  2974. onInitialize : null, // function() { ... }
  2975. onChange : null, // function(value) { ... }
  2976. onItemAdd : null, // function(value, $item) { ... }
  2977. onItemRemove : null, // function(value) { ... }
  2978. onClear : null, // function() { ... }
  2979. onOptionAdd : null, // function(value, data) { ... }
  2980. onOptionRemove : null, // function(value) { ... }
  2981. onOptionClear : null, // function() { ... }
  2982. onOptionGroupAdd : null, // function(id, data) { ... }
  2983. onOptionGroupRemove : null, // function(id) { ... }
  2984. onOptionGroupClear : null, // function() { ... }
  2985. onDropdownOpen : null, // function($dropdown) { ... }
  2986. onDropdownClose : null, // function($dropdown) { ... }
  2987. onType : null, // function(str) { ... }
  2988. onDelete : null, // function(values) { ... }
  2989. */
  2990. render: {
  2991. /*
  2992. item: null,
  2993. optgroup: null,
  2994. optgroup_header: null,
  2995. option: null,
  2996. option_create: null
  2997. */
  2998. }
  2999. };
  3000. $.fn.selectize = function(settings_user) {
  3001. var defaults = $.fn.selectize.defaults;
  3002. var settings = $.extend({}, defaults, settings_user);
  3003. var attr_data = settings.dataAttr;
  3004. var field_label = settings.labelField;
  3005. var field_value = settings.valueField;
  3006. var field_disabled = settings.disabledField;
  3007. var field_optgroup = settings.optgroupField;
  3008. var field_optgroup_label = settings.optgroupLabelField;
  3009. var field_optgroup_value = settings.optgroupValueField;
  3010. /**
  3011. * Initializes selectize from a <input type="text"> element.
  3012. *
  3013. * @param {object} $input
  3014. * @param {object} settings_element
  3015. */
  3016. var init_textbox = function($input, settings_element) {
  3017. var i, n, values, option;
  3018. var data_raw = $input.attr(attr_data);
  3019. if (!data_raw) {
  3020. var value = $.trim($input.val() || '');
  3021. if (!settings.allowEmptyOption && !value.length) return;
  3022. values = value.split(settings.delimiter);
  3023. for (i = 0, n = values.length; i < n; i++) {
  3024. option = {};
  3025. option[field_label] = values[i];
  3026. option[field_value] = values[i];
  3027. settings_element.options.push(option);
  3028. }
  3029. settings_element.items = values;
  3030. } else {
  3031. settings_element.options = JSON.parse(data_raw);
  3032. for (i = 0, n = settings_element.options.length; i < n; i++) {
  3033. settings_element.items.push(settings_element.options[i][field_value]);
  3034. }
  3035. }
  3036. };
  3037. /**
  3038. * Initializes selectize from a <select> element.
  3039. *
  3040. * @param {object} $input
  3041. * @param {object} settings_element
  3042. */
  3043. var init_select = function($input, settings_element) {
  3044. var i, n, tagName, $children, order = 0;
  3045. var options = settings_element.options;
  3046. var optionsMap = {};
  3047. var readData = function($el) {
  3048. var data = attr_data && $el.attr(attr_data);
  3049. if (typeof data === 'string' && data.length) {
  3050. return JSON.parse(data);
  3051. }
  3052. return null;
  3053. };
  3054. var addOption = function($option, group) {
  3055. $option = $($option);
  3056. var value = hash_key($option.val());
  3057. if (!value && !settings.allowEmptyOption) return;
  3058. // if the option already exists, it's probably been
  3059. // duplicated in another optgroup. in this case, push
  3060. // the current group to the "optgroup" property on the
  3061. // existing option so that it's rendered in both places.
  3062. if (optionsMap.hasOwnProperty(value)) {
  3063. if (group) {
  3064. var arr = optionsMap[value][field_optgroup];
  3065. if (!arr) {
  3066. optionsMap[value][field_optgroup] = group;
  3067. } else if (!$.isArray(arr)) {
  3068. optionsMap[value][field_optgroup] = [arr, group];
  3069. } else {
  3070. arr.push(group);
  3071. }
  3072. }
  3073. return;
  3074. }
  3075. var option = readData($option) || {};
  3076. option[field_label] = option[field_label] || $option.text();
  3077. option[field_value] = option[field_value] || value;
  3078. option[field_disabled] = option[field_disabled] || $option.prop('disabled');
  3079. option[field_optgroup] = option[field_optgroup] || group;
  3080. optionsMap[value] = option;
  3081. options.push(option);
  3082. if ($option.is(':selected')) {
  3083. settings_element.items.push(value);
  3084. }
  3085. };
  3086. var addGroup = function($optgroup) {
  3087. var i, n, id, optgroup, $options;
  3088. $optgroup = $($optgroup);
  3089. id = $optgroup.attr('label');
  3090. if (id) {
  3091. optgroup = readData($optgroup) || {};
  3092. optgroup[field_optgroup_label] = id;
  3093. optgroup[field_optgroup_value] = id;
  3094. optgroup[field_disabled] = $optgroup.prop('disabled');
  3095. settings_element.optgroups.push(optgroup);
  3096. }
  3097. $options = $('option', $optgroup);
  3098. for (i = 0, n = $options.length; i < n; i++) {
  3099. addOption($options[i], id);
  3100. }
  3101. };
  3102. settings_element.maxItems = $input.attr('multiple') ? null : 1;
  3103. $children = $input.children();
  3104. for (i = 0, n = $children.length; i < n; i++) {
  3105. tagName = $children[i].tagName.toLowerCase();
  3106. if (tagName === 'optgroup') {
  3107. addGroup($children[i]);
  3108. } else if (tagName === 'option') {
  3109. addOption($children[i]);
  3110. }
  3111. }
  3112. };
  3113. return this.each(function() {
  3114. if (this.selectize) return;
  3115. var instance;
  3116. var $input = $(this);
  3117. var tag_name = this.tagName.toLowerCase();
  3118. var placeholder = $input.attr('placeholder') || $input.attr('data-placeholder');
  3119. if (!placeholder && !settings.allowEmptyOption) {
  3120. placeholder = $input.children('option[value=""]').text();
  3121. }
  3122. var settings_element = {
  3123. 'placeholder' : placeholder,
  3124. 'options' : [],
  3125. 'optgroups' : [],
  3126. 'items' : []
  3127. };
  3128. if (tag_name === 'select') {
  3129. init_select($input, settings_element);
  3130. } else {
  3131. init_textbox($input, settings_element);
  3132. }
  3133. instance = new Selectize($input, $.extend(true, {}, defaults, settings_element, settings_user));
  3134. });
  3135. };
  3136. $.fn.selectize.defaults = Selectize.defaults;
  3137. $.fn.selectize.support = {
  3138. validity: SUPPORTS_VALIDITY_API
  3139. };
  3140. Selectize.define('drag_drop', function(options) {
  3141. if (!$.fn.sortable) throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".');
  3142. if (this.settings.mode !== 'multi') return;
  3143. var self = this;
  3144. self.lock = (function() {
  3145. var original = self.lock;
  3146. return function() {
  3147. var sortable = self.$control.data('sortable');
  3148. if (sortable) sortable.disable();
  3149. return original.apply(self, arguments);
  3150. };
  3151. })();
  3152. self.unlock = (function() {
  3153. var original = self.unlock;
  3154. return function() {
  3155. var sortable = self.$control.data('sortable');
  3156. if (sortable) sortable.enable();
  3157. return original.apply(self, arguments);
  3158. };
  3159. })();
  3160. self.setup = (function() {
  3161. var original = self.setup;
  3162. return function() {
  3163. original.apply(this, arguments);
  3164. var $control = self.$control.sortable({
  3165. items: '[data-value]',
  3166. forcePlaceholderSize: true,
  3167. disabled: self.isLocked,
  3168. start: function(e, ui) {
  3169. ui.placeholder.css('width', ui.helper.css('width'));
  3170. $control.css({overflow: 'visible'});
  3171. },
  3172. stop: function() {
  3173. $control.css({overflow: 'hidden'});
  3174. var active = self.$activeItems ? self.$activeItems.slice() : null;
  3175. var values = [];
  3176. $control.children('[data-value]').each(function() {
  3177. values.push($(this).attr('data-value'));
  3178. });
  3179. self.setValue(values);
  3180. self.setActiveItem(active);
  3181. }
  3182. });
  3183. };
  3184. })();
  3185. });
  3186. Selectize.define('dropdown_header', function(options) {
  3187. var self = this;
  3188. options = $.extend({
  3189. title : 'Untitled',
  3190. headerClass : 'selectize-dropdown-header',
  3191. titleRowClass : 'selectize-dropdown-header-title',
  3192. labelClass : 'selectize-dropdown-header-label',
  3193. closeClass : 'selectize-dropdown-header-close',
  3194. html: function(data) {
  3195. return (
  3196. '<div class="' + data.headerClass + '">' +
  3197. '<div class="' + data.titleRowClass + '">' +
  3198. '<span class="' + data.labelClass + '">' + data.title + '</span>' +
  3199. '<a href="javascript:void(0)" class="' + data.closeClass + '">&times;</a>' +
  3200. '</div>' +
  3201. '</div>'
  3202. );
  3203. }
  3204. }, options);
  3205. self.setup = (function() {
  3206. var original = self.setup;
  3207. return function() {
  3208. original.apply(self, arguments);
  3209. self.$dropdown_header = $(options.html(options));
  3210. self.$dropdown.prepend(self.$dropdown_header);
  3211. };
  3212. })();
  3213. });
  3214. Selectize.define('optgroup_columns', function(options) {
  3215. var self = this;
  3216. options = $.extend({
  3217. equalizeWidth : true,
  3218. equalizeHeight : true
  3219. }, options);
  3220. this.getAdjacentOption = function($option, direction) {
  3221. var $options = $option.closest('[data-group]').find('[data-selectable]');
  3222. var index = $options.index($option) + direction;
  3223. return index >= 0 && index < $options.length ? $options.eq(index) : $();
  3224. };
  3225. this.onKeyDown = (function() {
  3226. var original = self.onKeyDown;
  3227. return function(e) {
  3228. var index, $option, $options, $optgroup;
  3229. if (this.isOpen && (e.keyCode === KEY_LEFT || e.keyCode === KEY_RIGHT)) {
  3230. self.ignoreHover = true;
  3231. $optgroup = this.$activeOption.closest('[data-group]');
  3232. index = $optgroup.find('[data-selectable]').index(this.$activeOption);
  3233. if(e.keyCode === KEY_LEFT) {
  3234. $optgroup = $optgroup.prev('[data-group]');
  3235. } else {
  3236. $optgroup = $optgroup.next('[data-group]');
  3237. }
  3238. $options = $optgroup.find('[data-selectable]');
  3239. $option = $options.eq(Math.min($options.length - 1, index));
  3240. if ($option.length) {
  3241. this.setActiveOption($option);
  3242. }
  3243. return;
  3244. }
  3245. return original.apply(this, arguments);
  3246. };
  3247. })();
  3248. var getScrollbarWidth = function() {
  3249. var div;
  3250. var width = getScrollbarWidth.width;
  3251. var doc = document;
  3252. if (typeof width === 'undefined') {
  3253. div = doc.createElement('div');
  3254. div.innerHTML = '<div style="width:50px;height:50px;position:absolute;left:-50px;top:-50px;overflow:auto;"><div style="width:1px;height:100px;"></div></div>';
  3255. div = div.firstChild;
  3256. doc.body.appendChild(div);
  3257. width = getScrollbarWidth.width = div.offsetWidth - div.clientWidth;
  3258. doc.body.removeChild(div);
  3259. }
  3260. return width;
  3261. };
  3262. var equalizeSizes = function() {
  3263. var i, n, height_max, width, width_last, width_parent, $optgroups;
  3264. $optgroups = $('[data-group]', self.$dropdown_content);
  3265. n = $optgroups.length;
  3266. if (!n || !self.$dropdown_content.width()) return;
  3267. if (options.equalizeHeight) {
  3268. height_max = 0;
  3269. for (i = 0; i < n; i++) {
  3270. height_max = Math.max(height_max, $optgroups.eq(i).height());
  3271. }
  3272. $optgroups.css({height: height_max});
  3273. }
  3274. if (options.equalizeWidth) {
  3275. width_parent = self.$dropdown_content.innerWidth() - getScrollbarWidth();
  3276. width = Math.round(width_parent / n);
  3277. $optgroups.css({width: width});
  3278. if (n > 1) {
  3279. width_last = width_parent - width * (n - 1);
  3280. $optgroups.eq(n - 1).css({width: width_last});
  3281. }
  3282. }
  3283. };
  3284. if (options.equalizeHeight || options.equalizeWidth) {
  3285. hook.after(this, 'positionDropdown', equalizeSizes);
  3286. hook.after(this, 'refreshOptions', equalizeSizes);
  3287. }
  3288. });
  3289. Selectize.define('remove_button', function(options) {
  3290. options = $.extend({
  3291. label : '&times;',
  3292. title : 'Remove',
  3293. className : 'remove',
  3294. append : true
  3295. }, options);
  3296. var singleClose = function(thisRef, options) {
  3297. options.className = 'remove-single';
  3298. var self = thisRef;
  3299. var html = '<a href="javascript:void(0)" class="' + options.className + '" tabindex="-1" title="' + escape_html(options.title) + '">' + options.label + '</a>';
  3300. /**
  3301. * Appends an element as a child (with raw HTML).
  3302. *
  3303. * @param {string} html_container
  3304. * @param {string} html_element
  3305. * @return {string}
  3306. */
  3307. var append = function(html_container, html_element) {
  3308. return $('<span>').append(html_container)
  3309. .append(html_element);
  3310. };
  3311. thisRef.setup = (function() {
  3312. var original = self.setup;
  3313. return function() {
  3314. // override the item rendering method to add the button to each
  3315. if (options.append) {
  3316. var id = $(self.$input.context).attr('id');
  3317. var selectizer = $('#'+id);
  3318. var render_item = self.settings.render.item;
  3319. self.settings.render.item = function(data) {
  3320. return append(render_item.apply(thisRef, arguments), html);
  3321. };
  3322. }
  3323. original.apply(thisRef, arguments);
  3324. // add event listener
  3325. thisRef.$control.on('click', '.' + options.className, function(e) {
  3326. e.preventDefault();
  3327. if (self.isLocked) return;
  3328. self.clear();
  3329. });
  3330. };
  3331. })();
  3332. };
  3333. var multiClose = function(thisRef, options) {
  3334. var self = thisRef;
  3335. var html = '<a href="javascript:void(0)" class="' + options.className + '" tabindex="-1" title="' + escape_html(options.title) + '">' + options.label + '</a>';
  3336. /**
  3337. * Appends an element as a child (with raw HTML).
  3338. *
  3339. * @param {string} html_container
  3340. * @param {string} html_element
  3341. * @return {string}
  3342. */
  3343. var append = function(html_container, html_element) {
  3344. var pos = html_container.search(/(<\/[^>]+>\s*)$/);
  3345. return html_container.substring(0, pos) + html_element + html_container.substring(pos);
  3346. };
  3347. thisRef.setup = (function() {
  3348. var original = self.setup;
  3349. return function() {
  3350. // override the item rendering method to add the button to each
  3351. if (options.append) {
  3352. var render_item = self.settings.render.item;
  3353. self.settings.render.item = function(data) {
  3354. return append(render_item.apply(thisRef, arguments), html);
  3355. };
  3356. }
  3357. original.apply(thisRef, arguments);
  3358. // add event listener
  3359. thisRef.$control.on('click', '.' + options.className, function(e) {
  3360. e.preventDefault();
  3361. if (self.isLocked) return;
  3362. var $item = $(e.currentTarget).parent();
  3363. self.setActiveItem($item);
  3364. if (self.deleteSelection()) {
  3365. self.setCaret(self.items.length);
  3366. }
  3367. });
  3368. };
  3369. })();
  3370. };
  3371. if (this.settings.mode === 'single') {
  3372. singleClose(this, options);
  3373. return;
  3374. } else {
  3375. multiClose(this, options);
  3376. }
  3377. });
  3378. Selectize.define('restore_on_backspace', function(options) {
  3379. var self = this;
  3380. options.text = options.text || function(option) {
  3381. return option[this.settings.labelField];
  3382. };
  3383. this.onKeyDown = (function() {
  3384. var original = self.onKeyDown;
  3385. return function(e) {
  3386. var index, option;
  3387. if (e.keyCode === KEY_BACKSPACE && this.$control_input.val() === '' && !this.$activeItems.length) {
  3388. index = this.caretPos - 1;
  3389. if (index >= 0 && index < this.items.length) {
  3390. option = this.options[this.items[index]];
  3391. if (this.deleteSelection(e)) {
  3392. this.setTextboxValue(options.text.apply(this, [option]));
  3393. this.refreshOptions(true);
  3394. }
  3395. e.preventDefault();
  3396. return;
  3397. }
  3398. }
  3399. return original.apply(this, arguments);
  3400. };
  3401. })();
  3402. });
  3403. return Selectize;
  3404. }));