dropdown.js 130 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741
  1. /*!
  2. * # Semantic UI 2.2.6 - Dropdown
  3. * http://github.com/semantic-org/semantic-ui/
  4. *
  5. *
  6. * Released under the MIT license
  7. * http://opensource.org/licenses/MIT
  8. *
  9. */
  10. ;(function ($, window, document, undefined) {
  11. "use strict";
  12. window = (typeof window != 'undefined' && window.Math == Math)
  13. ? window
  14. : (typeof self != 'undefined' && self.Math == Math)
  15. ? self
  16. : Function('return this')()
  17. ;
  18. $.fn.dropdown = function(parameters) {
  19. var
  20. $allModules = $(this),
  21. $document = $(document),
  22. moduleSelector = $allModules.selector || '',
  23. hasTouch = ('ontouchstart' in document.documentElement),
  24. time = new Date().getTime(),
  25. performance = [],
  26. query = arguments[0],
  27. methodInvoked = (typeof query == 'string'),
  28. queryArguments = [].slice.call(arguments, 1),
  29. returnedValue
  30. ;
  31. $allModules
  32. .each(function(elementIndex) {
  33. var
  34. settings = ( $.isPlainObject(parameters) )
  35. ? $.extend(true, {}, $.fn.dropdown.settings, parameters)
  36. : $.extend({}, $.fn.dropdown.settings),
  37. className = settings.className,
  38. message = settings.message,
  39. fields = settings.fields,
  40. keys = settings.keys,
  41. metadata = settings.metadata,
  42. namespace = settings.namespace,
  43. regExp = settings.regExp,
  44. selector = settings.selector,
  45. error = settings.error,
  46. templates = settings.templates,
  47. eventNamespace = '.' + namespace,
  48. moduleNamespace = 'module-' + namespace,
  49. $module = $(this),
  50. $context = $(settings.context),
  51. $text = $module.find(selector.text),
  52. $search = $module.find(selector.search),
  53. $sizer = $module.find(selector.sizer),
  54. $input = $module.find(selector.input),
  55. $icon = $module.find(selector.icon),
  56. $combo = ($module.prev().find(selector.text).length > 0)
  57. ? $module.prev().find(selector.text)
  58. : $module.prev(),
  59. $menu = $module.children(selector.menu),
  60. $item = $menu.find(selector.item),
  61. activated = false,
  62. itemActivated = false,
  63. internalChange = false,
  64. element = this,
  65. instance = $module.data(moduleNamespace),
  66. initialLoad,
  67. pageLostFocus,
  68. willRefocus,
  69. elementNamespace,
  70. id,
  71. selectObserver,
  72. menuObserver,
  73. module
  74. ;
  75. module = {
  76. initialize: function() {
  77. module.debug('Initializing dropdown', settings);
  78. if( module.is.alreadySetup() ) {
  79. module.setup.reference();
  80. }
  81. else {
  82. module.setup.layout();
  83. module.refreshData();
  84. module.save.defaults();
  85. module.restore.selected();
  86. module.create.id();
  87. module.bind.events();
  88. module.observeChanges();
  89. module.instantiate();
  90. }
  91. },
  92. instantiate: function() {
  93. module.verbose('Storing instance of dropdown', module);
  94. instance = module;
  95. $module
  96. .data(moduleNamespace, module)
  97. ;
  98. },
  99. destroy: function() {
  100. module.verbose('Destroying previous dropdown', $module);
  101. module.remove.tabbable();
  102. $module
  103. .off(eventNamespace)
  104. .removeData(moduleNamespace)
  105. ;
  106. $menu
  107. .off(eventNamespace)
  108. ;
  109. $document
  110. .off(elementNamespace)
  111. ;
  112. module.disconnect.menuObserver();
  113. module.disconnect.selectObserver();
  114. },
  115. observeChanges: function() {
  116. if('MutationObserver' in window) {
  117. selectObserver = new MutationObserver(module.event.select.mutation);
  118. menuObserver = new MutationObserver(module.event.menu.mutation);
  119. module.debug('Setting up mutation observer', selectObserver, menuObserver);
  120. module.observe.select();
  121. module.observe.menu();
  122. }
  123. },
  124. disconnect: {
  125. menuObserver: function() {
  126. if(menuObserver) {
  127. menuObserver.disconnect();
  128. }
  129. },
  130. selectObserver: function() {
  131. if(selectObserver) {
  132. selectObserver.disconnect();
  133. }
  134. }
  135. },
  136. observe: {
  137. select: function() {
  138. if(module.has.input()) {
  139. selectObserver.observe($input[0], {
  140. childList : true,
  141. subtree : true
  142. });
  143. }
  144. },
  145. menu: function() {
  146. if(module.has.menu()) {
  147. menuObserver.observe($menu[0], {
  148. childList : true,
  149. subtree : true
  150. });
  151. }
  152. }
  153. },
  154. create: {
  155. id: function() {
  156. id = (Math.random().toString(16) + '000000000').substr(2, 8);
  157. elementNamespace = '.' + id;
  158. module.verbose('Creating unique id for element', id);
  159. },
  160. userChoice: function(values) {
  161. var
  162. $userChoices,
  163. $userChoice,
  164. isUserValue,
  165. html
  166. ;
  167. values = values || module.get.userValues();
  168. if(!values) {
  169. return false;
  170. }
  171. values = $.isArray(values)
  172. ? values
  173. : [values]
  174. ;
  175. $.each(values, function(index, value) {
  176. if(module.get.item(value) === false) {
  177. html = settings.templates.addition( module.add.variables(message.addResult, value) );
  178. $userChoice = $('<div />')
  179. .html(html)
  180. .attr('data-' + metadata.value, value)
  181. .attr('data-' + metadata.text, value)
  182. .addClass(className.addition)
  183. .addClass(className.item)
  184. ;
  185. if(settings.hideAdditions) {
  186. $userChoice.addClass(className.hidden);
  187. }
  188. $userChoices = ($userChoices === undefined)
  189. ? $userChoice
  190. : $userChoices.add($userChoice)
  191. ;
  192. module.verbose('Creating user choices for value', value, $userChoice);
  193. }
  194. });
  195. return $userChoices;
  196. },
  197. userLabels: function(value) {
  198. var
  199. userValues = module.get.userValues()
  200. ;
  201. if(userValues) {
  202. module.debug('Adding user labels', userValues);
  203. $.each(userValues, function(index, value) {
  204. module.verbose('Adding custom user value');
  205. module.add.label(value, value);
  206. });
  207. }
  208. },
  209. menu: function() {
  210. $menu = $('<div />')
  211. .addClass(className.menu)
  212. .appendTo($module)
  213. ;
  214. },
  215. sizer: function() {
  216. $sizer = $('<span />')
  217. .addClass(className.sizer)
  218. .insertAfter($search)
  219. ;
  220. }
  221. },
  222. search: function(query) {
  223. query = (query !== undefined)
  224. ? query
  225. : module.get.query()
  226. ;
  227. module.verbose('Searching for query', query);
  228. if(module.has.minCharacters(query)) {
  229. module.filter(query);
  230. }
  231. else {
  232. module.hide();
  233. }
  234. },
  235. select: {
  236. firstUnfiltered: function() {
  237. module.verbose('Selecting first non-filtered element');
  238. module.remove.selectedItem();
  239. $item
  240. .not(selector.unselectable)
  241. .not(selector.addition + selector.hidden)
  242. .eq(0)
  243. .addClass(className.selected)
  244. ;
  245. },
  246. nextAvailable: function($selected) {
  247. $selected = $selected.eq(0);
  248. var
  249. $nextAvailable = $selected.nextAll(selector.item).not(selector.unselectable).eq(0),
  250. $prevAvailable = $selected.prevAll(selector.item).not(selector.unselectable).eq(0),
  251. hasNext = ($nextAvailable.length > 0)
  252. ;
  253. if(hasNext) {
  254. module.verbose('Moving selection to', $nextAvailable);
  255. $nextAvailable.addClass(className.selected);
  256. }
  257. else {
  258. module.verbose('Moving selection to', $prevAvailable);
  259. $prevAvailable.addClass(className.selected);
  260. }
  261. }
  262. },
  263. setup: {
  264. api: function() {
  265. var
  266. apiSettings = {
  267. debug : settings.debug,
  268. urlData : {
  269. value : module.get.value(),
  270. query : module.get.query()
  271. },
  272. on : false
  273. }
  274. ;
  275. module.verbose('First request, initializing API');
  276. $module
  277. .api(apiSettings)
  278. ;
  279. },
  280. layout: function() {
  281. if( $module.is('select') ) {
  282. module.setup.select();
  283. module.setup.returnedObject();
  284. }
  285. if( !module.has.menu() ) {
  286. module.create.menu();
  287. }
  288. if( module.is.search() && !module.has.search() ) {
  289. module.verbose('Adding search input');
  290. $search = $('<input />')
  291. .addClass(className.search)
  292. .prop('autocomplete', 'off')
  293. .insertBefore($text)
  294. ;
  295. }
  296. if( module.is.multiple() && module.is.searchSelection() && !module.has.sizer()) {
  297. module.create.sizer();
  298. }
  299. if(settings.allowTab) {
  300. module.set.tabbable();
  301. }
  302. },
  303. select: function() {
  304. var
  305. selectValues = module.get.selectValues()
  306. ;
  307. module.debug('Dropdown initialized on a select', selectValues);
  308. if( $module.is('select') ) {
  309. $input = $module;
  310. }
  311. // see if select is placed correctly already
  312. if($input.parent(selector.dropdown).length > 0) {
  313. module.debug('UI dropdown already exists. Creating dropdown menu only');
  314. $module = $input.closest(selector.dropdown);
  315. if( !module.has.menu() ) {
  316. module.create.menu();
  317. }
  318. $menu = $module.children(selector.menu);
  319. module.setup.menu(selectValues);
  320. }
  321. else {
  322. module.debug('Creating entire dropdown from select');
  323. $module = $('<div />')
  324. .attr('class', $input.attr('class') )
  325. .addClass(className.selection)
  326. .addClass(className.dropdown)
  327. .html( templates.dropdown(selectValues) )
  328. .insertBefore($input)
  329. ;
  330. if($input.hasClass(className.multiple) && $input.prop('multiple') === false) {
  331. module.error(error.missingMultiple);
  332. $input.prop('multiple', true);
  333. }
  334. if($input.is('[multiple]')) {
  335. module.set.multiple();
  336. }
  337. if ($input.prop('disabled')) {
  338. module.debug('Disabling dropdown');
  339. $module.addClass(className.disabled);
  340. }
  341. $input
  342. .removeAttr('class')
  343. .detach()
  344. .prependTo($module)
  345. ;
  346. }
  347. module.refresh();
  348. },
  349. menu: function(values) {
  350. $menu.html( templates.menu(values, fields));
  351. $item = $menu.find(selector.item);
  352. },
  353. reference: function() {
  354. module.debug('Dropdown behavior was called on select, replacing with closest dropdown');
  355. // replace module reference
  356. $module = $module.parent(selector.dropdown);
  357. module.refresh();
  358. module.setup.returnedObject();
  359. // invoke method in context of current instance
  360. if(methodInvoked) {
  361. instance = module;
  362. module.invoke(query);
  363. }
  364. },
  365. returnedObject: function() {
  366. var
  367. $firstModules = $allModules.slice(0, elementIndex),
  368. $lastModules = $allModules.slice(elementIndex + 1)
  369. ;
  370. // adjust all modules to use correct reference
  371. $allModules = $firstModules.add($module).add($lastModules);
  372. }
  373. },
  374. refresh: function() {
  375. module.refreshSelectors();
  376. module.refreshData();
  377. },
  378. refreshItems: function() {
  379. $item = $menu.find(selector.item);
  380. },
  381. refreshSelectors: function() {
  382. module.verbose('Refreshing selector cache');
  383. $text = $module.find(selector.text);
  384. $search = $module.find(selector.search);
  385. $input = $module.find(selector.input);
  386. $icon = $module.find(selector.icon);
  387. $combo = ($module.prev().find(selector.text).length > 0)
  388. ? $module.prev().find(selector.text)
  389. : $module.prev()
  390. ;
  391. $menu = $module.children(selector.menu);
  392. $item = $menu.find(selector.item);
  393. },
  394. refreshData: function() {
  395. module.verbose('Refreshing cached metadata');
  396. $item
  397. .removeData(metadata.text)
  398. .removeData(metadata.value)
  399. ;
  400. },
  401. clearData: function() {
  402. module.verbose('Clearing metadata');
  403. $item
  404. .removeData(metadata.text)
  405. .removeData(metadata.value)
  406. ;
  407. $module
  408. .removeData(metadata.defaultText)
  409. .removeData(metadata.defaultValue)
  410. .removeData(metadata.placeholderText)
  411. ;
  412. },
  413. toggle: function() {
  414. module.verbose('Toggling menu visibility');
  415. if( !module.is.active() ) {
  416. module.show();
  417. }
  418. else {
  419. module.hide();
  420. }
  421. },
  422. show: function(callback) {
  423. callback = $.isFunction(callback)
  424. ? callback
  425. : function(){}
  426. ;
  427. if( module.can.show() && !module.is.active() ) {
  428. module.debug('Showing dropdown');
  429. if(module.has.message() && !(module.has.maxSelections() || module.has.allResultsFiltered()) ) {
  430. module.remove.message();
  431. }
  432. if(module.is.allFiltered()) {
  433. return true;
  434. }
  435. if(settings.onShow.call(element) !== false) {
  436. module.animate.show(function() {
  437. if( module.can.click() ) {
  438. module.bind.intent();
  439. }
  440. if(module.has.menuSearch()) {
  441. module.focusSearch();
  442. }
  443. module.set.visible();
  444. callback.call(element);
  445. });
  446. }
  447. }
  448. },
  449. hide: function(callback) {
  450. callback = $.isFunction(callback)
  451. ? callback
  452. : function(){}
  453. ;
  454. if( module.is.active() ) {
  455. module.debug('Hiding dropdown');
  456. if(settings.onHide.call(element) !== false) {
  457. module.animate.hide(function() {
  458. module.remove.visible();
  459. callback.call(element);
  460. });
  461. }
  462. }
  463. },
  464. hideOthers: function() {
  465. module.verbose('Finding other dropdowns to hide');
  466. $allModules
  467. .not($module)
  468. .has(selector.menu + '.' + className.visible)
  469. .dropdown('hide')
  470. ;
  471. },
  472. hideMenu: function() {
  473. module.verbose('Hiding menu instantaneously');
  474. module.remove.active();
  475. module.remove.visible();
  476. $menu.transition('hide');
  477. },
  478. hideSubMenus: function() {
  479. var
  480. $subMenus = $menu.children(selector.item).find(selector.menu)
  481. ;
  482. module.verbose('Hiding sub menus', $subMenus);
  483. $subMenus.transition('hide');
  484. },
  485. bind: {
  486. events: function() {
  487. if(hasTouch) {
  488. module.bind.touchEvents();
  489. }
  490. module.bind.keyboardEvents();
  491. module.bind.inputEvents();
  492. module.bind.mouseEvents();
  493. },
  494. touchEvents: function() {
  495. module.debug('Touch device detected binding additional touch events');
  496. if( module.is.searchSelection() ) {
  497. // do nothing special yet
  498. }
  499. else if( module.is.single() ) {
  500. $module
  501. .on('touchstart' + eventNamespace, module.event.test.toggle)
  502. ;
  503. }
  504. $menu
  505. .on('touchstart' + eventNamespace, selector.item, module.event.item.mouseenter)
  506. ;
  507. },
  508. keyboardEvents: function() {
  509. module.verbose('Binding keyboard events');
  510. $module
  511. .on('keydown' + eventNamespace, module.event.keydown)
  512. ;
  513. if( module.has.search() ) {
  514. $module
  515. .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input)
  516. ;
  517. }
  518. if( module.is.multiple() ) {
  519. $document
  520. .on('keydown' + elementNamespace, module.event.document.keydown)
  521. ;
  522. }
  523. },
  524. inputEvents: function() {
  525. module.verbose('Binding input change events');
  526. $module
  527. .on('change' + eventNamespace, selector.input, module.event.change)
  528. ;
  529. },
  530. mouseEvents: function() {
  531. module.verbose('Binding mouse events');
  532. if(module.is.multiple()) {
  533. $module
  534. .on('click' + eventNamespace, selector.label, module.event.label.click)
  535. .on('click' + eventNamespace, selector.remove, module.event.remove.click)
  536. ;
  537. }
  538. if( module.is.searchSelection() ) {
  539. $module
  540. .on('mousedown' + eventNamespace, module.event.mousedown)
  541. .on('mouseup' + eventNamespace, module.event.mouseup)
  542. .on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown)
  543. .on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup)
  544. .on('click' + eventNamespace, selector.icon, module.event.icon.click)
  545. .on('focus' + eventNamespace, selector.search, module.event.search.focus)
  546. .on('click' + eventNamespace, selector.search, module.event.search.focus)
  547. .on('blur' + eventNamespace, selector.search, module.event.search.blur)
  548. .on('click' + eventNamespace, selector.text, module.event.text.focus)
  549. ;
  550. if(module.is.multiple()) {
  551. $module
  552. .on('click' + eventNamespace, module.event.click)
  553. ;
  554. }
  555. }
  556. else {
  557. if(settings.on == 'click') {
  558. $module
  559. .on('click' + eventNamespace, selector.icon, module.event.icon.click)
  560. .on('click' + eventNamespace, module.event.test.toggle)
  561. ;
  562. }
  563. else if(settings.on == 'hover') {
  564. $module
  565. .on('mouseenter' + eventNamespace, module.delay.show)
  566. .on('mouseleave' + eventNamespace, module.delay.hide)
  567. ;
  568. }
  569. else {
  570. $module
  571. .on(settings.on + eventNamespace, module.toggle)
  572. ;
  573. }
  574. $module
  575. .on('mousedown' + eventNamespace, module.event.mousedown)
  576. .on('mouseup' + eventNamespace, module.event.mouseup)
  577. .on('focus' + eventNamespace, module.event.focus)
  578. .on('blur' + eventNamespace, module.event.blur)
  579. ;
  580. }
  581. $menu
  582. .on('mouseenter' + eventNamespace, selector.item, module.event.item.mouseenter)
  583. .on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave)
  584. .on('click' + eventNamespace, selector.item, module.event.item.click)
  585. ;
  586. },
  587. intent: function() {
  588. module.verbose('Binding hide intent event to document');
  589. if(hasTouch) {
  590. $document
  591. .on('touchstart' + elementNamespace, module.event.test.touch)
  592. .on('touchmove' + elementNamespace, module.event.test.touch)
  593. ;
  594. }
  595. $document
  596. .on('click' + elementNamespace, module.event.test.hide)
  597. ;
  598. }
  599. },
  600. unbind: {
  601. intent: function() {
  602. module.verbose('Removing hide intent event from document');
  603. if(hasTouch) {
  604. $document
  605. .off('touchstart' + elementNamespace)
  606. .off('touchmove' + elementNamespace)
  607. ;
  608. }
  609. $document
  610. .off('click' + elementNamespace)
  611. ;
  612. }
  613. },
  614. filter: function(query) {
  615. var
  616. searchTerm = (query !== undefined)
  617. ? query
  618. : module.get.query(),
  619. afterFiltered = function() {
  620. if(module.is.multiple()) {
  621. module.filterActive();
  622. }
  623. module.select.firstUnfiltered();
  624. if( module.has.allResultsFiltered() ) {
  625. if( settings.onNoResults.call(element, searchTerm) ) {
  626. if(settings.allowAdditions) {
  627. if(settings.hideAdditions) {
  628. module.verbose('User addition with no menu, setting empty style');
  629. module.set.empty();
  630. module.hideMenu();
  631. }
  632. }
  633. else {
  634. module.verbose('All items filtered, showing message', searchTerm);
  635. module.add.message(message.noResults);
  636. }
  637. }
  638. else {
  639. module.verbose('All items filtered, hiding dropdown', searchTerm);
  640. module.hideMenu();
  641. }
  642. }
  643. else {
  644. module.remove.empty();
  645. module.remove.message();
  646. }
  647. if(settings.allowAdditions) {
  648. module.add.userSuggestion(query);
  649. }
  650. if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) {
  651. module.show();
  652. }
  653. }
  654. ;
  655. if(settings.useLabels && module.has.maxSelections()) {
  656. return;
  657. }
  658. if(settings.apiSettings) {
  659. if( module.can.useAPI() ) {
  660. module.queryRemote(searchTerm, function() {
  661. afterFiltered();
  662. });
  663. }
  664. else {
  665. module.error(error.noAPI);
  666. }
  667. }
  668. else {
  669. module.filterItems(searchTerm);
  670. afterFiltered();
  671. }
  672. },
  673. queryRemote: function(query, callback) {
  674. var
  675. apiSettings = {
  676. errorDuration : false,
  677. cache : 'local',
  678. throttle : settings.throttle,
  679. urlData : {
  680. query: query
  681. },
  682. onError: function() {
  683. module.add.message(message.serverError);
  684. callback();
  685. },
  686. onFailure: function() {
  687. module.add.message(message.serverError);
  688. callback();
  689. },
  690. onSuccess : function(response) {
  691. module.remove.message();
  692. module.setup.menu({
  693. values: response[fields.remoteValues]
  694. });
  695. callback();
  696. }
  697. }
  698. ;
  699. if( !$module.api('get request') ) {
  700. module.setup.api();
  701. }
  702. apiSettings = $.extend(true, {}, apiSettings, settings.apiSettings);
  703. $module
  704. .api('setting', apiSettings)
  705. .api('query')
  706. ;
  707. },
  708. filterItems: function(query) {
  709. var
  710. searchTerm = (query !== undefined)
  711. ? query
  712. : module.get.query(),
  713. results = null,
  714. escapedTerm = module.escape.regExp(searchTerm),
  715. beginsWithRegExp = new RegExp('^' + escapedTerm, 'igm')
  716. ;
  717. // avoid loop if we're matching nothing
  718. if( module.has.query() ) {
  719. results = [];
  720. module.verbose('Searching for matching values', searchTerm);
  721. $item
  722. .each(function(){
  723. var
  724. $choice = $(this),
  725. text,
  726. value
  727. ;
  728. if(settings.match == 'both' || settings.match == 'text') {
  729. text = String(module.get.choiceText($choice, false));
  730. if(text.search(beginsWithRegExp) !== -1) {
  731. results.push(this);
  732. return true;
  733. }
  734. else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text)) {
  735. results.push(this);
  736. return true;
  737. }
  738. else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, text)) {
  739. results.push(this);
  740. return true;
  741. }
  742. }
  743. if(settings.match == 'both' || settings.match == 'value') {
  744. value = String(module.get.choiceValue($choice, text));
  745. if(value.search(beginsWithRegExp) !== -1) {
  746. results.push(this);
  747. return true;
  748. }
  749. else if(settings.fullTextSearch && module.fuzzySearch(searchTerm, value)) {
  750. results.push(this);
  751. return true;
  752. }
  753. }
  754. })
  755. ;
  756. }
  757. module.debug('Showing only matched items', searchTerm);
  758. module.remove.filteredItem();
  759. if(results) {
  760. $item
  761. .not(results)
  762. .addClass(className.filtered)
  763. ;
  764. }
  765. },
  766. fuzzySearch: function(query, term) {
  767. var
  768. termLength = term.length,
  769. queryLength = query.length
  770. ;
  771. query = query.toLowerCase();
  772. term = term.toLowerCase();
  773. if(queryLength > termLength) {
  774. return false;
  775. }
  776. if(queryLength === termLength) {
  777. return (query === term);
  778. }
  779. search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) {
  780. var
  781. queryCharacter = query.charCodeAt(characterIndex)
  782. ;
  783. while(nextCharacterIndex < termLength) {
  784. if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) {
  785. continue search;
  786. }
  787. }
  788. return false;
  789. }
  790. return true;
  791. },
  792. exactSearch: function (query, term) {
  793. query = query.toLowerCase();
  794. term = term.toLowerCase();
  795. if(term.indexOf(query) > -1) {
  796. return true;
  797. }
  798. return false;
  799. },
  800. filterActive: function() {
  801. if(settings.useLabels) {
  802. $item.filter('.' + className.active)
  803. .addClass(className.filtered)
  804. ;
  805. }
  806. },
  807. focusSearch: function(skipHandler) {
  808. if( module.has.search() && !module.is.focusedOnSearch() ) {
  809. if(skipHandler) {
  810. $module.off('focus' + eventNamespace, selector.search);
  811. $search.focus();
  812. $module.on('focus' + eventNamespace, selector.search, module.event.search.focus);
  813. }
  814. else {
  815. $search.focus();
  816. }
  817. }
  818. },
  819. forceSelection: function() {
  820. var
  821. $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0),
  822. $activeItem = $item.not(className.filtered).filter('.' + className.active).eq(0),
  823. $selectedItem = ($currentlySelected.length > 0)
  824. ? $currentlySelected
  825. : $activeItem,
  826. hasSelected = ($selectedItem.length > 0)
  827. ;
  828. if(hasSelected) {
  829. module.debug('Forcing partial selection to selected item', $selectedItem);
  830. module.event.item.click.call($selectedItem, {}, true);
  831. return;
  832. }
  833. else {
  834. if(settings.allowAdditions) {
  835. module.set.selected(module.get.query());
  836. module.remove.searchTerm();
  837. }
  838. else {
  839. module.remove.searchTerm();
  840. }
  841. }
  842. },
  843. event: {
  844. change: function() {
  845. if(!internalChange) {
  846. module.debug('Input changed, updating selection');
  847. module.set.selected();
  848. }
  849. },
  850. focus: function() {
  851. if(settings.showOnFocus && !activated && module.is.hidden() && !pageLostFocus) {
  852. module.show();
  853. }
  854. },
  855. blur: function(event) {
  856. pageLostFocus = (document.activeElement === this);
  857. if(!activated && !pageLostFocus) {
  858. module.remove.activeLabel();
  859. module.hide();
  860. }
  861. },
  862. mousedown: function() {
  863. if(module.is.searchSelection()) {
  864. // prevent menu hiding on immediate re-focus
  865. willRefocus = true;
  866. }
  867. else {
  868. // prevents focus callback from occurring on mousedown
  869. activated = true;
  870. }
  871. },
  872. mouseup: function() {
  873. if(module.is.searchSelection()) {
  874. // prevent menu hiding on immediate re-focus
  875. willRefocus = false;
  876. }
  877. else {
  878. activated = false;
  879. }
  880. },
  881. click: function(event) {
  882. var
  883. $target = $(event.target)
  884. ;
  885. // focus search
  886. if($target.is($module)) {
  887. if(!module.is.focusedOnSearch()) {
  888. module.focusSearch();
  889. }
  890. else {
  891. module.show();
  892. }
  893. }
  894. },
  895. search: {
  896. focus: function() {
  897. activated = true;
  898. if(module.is.multiple()) {
  899. module.remove.activeLabel();
  900. }
  901. if(settings.showOnFocus) {
  902. module.search();
  903. }
  904. },
  905. blur: function(event) {
  906. pageLostFocus = (document.activeElement === this);
  907. if(!willRefocus) {
  908. if(!itemActivated && !pageLostFocus) {
  909. if(settings.forceSelection) {
  910. module.forceSelection();
  911. }
  912. module.hide();
  913. }
  914. }
  915. willRefocus = false;
  916. }
  917. },
  918. icon: {
  919. click: function(event) {
  920. module.toggle();
  921. }
  922. },
  923. text: {
  924. focus: function(event) {
  925. activated = true;
  926. module.focusSearch();
  927. }
  928. },
  929. input: function(event) {
  930. if(module.is.multiple() || module.is.searchSelection()) {
  931. module.set.filtered();
  932. }
  933. clearTimeout(module.timer);
  934. module.timer = setTimeout(module.search, settings.delay.search);
  935. },
  936. label: {
  937. click: function(event) {
  938. var
  939. $label = $(this),
  940. $labels = $module.find(selector.label),
  941. $activeLabels = $labels.filter('.' + className.active),
  942. $nextActive = $label.nextAll('.' + className.active),
  943. $prevActive = $label.prevAll('.' + className.active),
  944. $range = ($nextActive.length > 0)
  945. ? $label.nextUntil($nextActive).add($activeLabels).add($label)
  946. : $label.prevUntil($prevActive).add($activeLabels).add($label)
  947. ;
  948. if(event.shiftKey) {
  949. $activeLabels.removeClass(className.active);
  950. $range.addClass(className.active);
  951. }
  952. else if(event.ctrlKey) {
  953. $label.toggleClass(className.active);
  954. }
  955. else {
  956. $activeLabels.removeClass(className.active);
  957. $label.addClass(className.active);
  958. }
  959. settings.onLabelSelect.apply(this, $labels.filter('.' + className.active));
  960. }
  961. },
  962. remove: {
  963. click: function() {
  964. var
  965. $label = $(this).parent()
  966. ;
  967. if( $label.hasClass(className.active) ) {
  968. // remove all selected labels
  969. module.remove.activeLabels();
  970. }
  971. else {
  972. // remove this label only
  973. module.remove.activeLabels( $label );
  974. }
  975. }
  976. },
  977. test: {
  978. toggle: function(event) {
  979. var
  980. toggleBehavior = (module.is.multiple())
  981. ? module.show
  982. : module.toggle
  983. ;
  984. if(module.is.bubbledLabelClick(event) || module.is.bubbledIconClick(event)) {
  985. return;
  986. }
  987. if( module.determine.eventOnElement(event, toggleBehavior) ) {
  988. event.preventDefault();
  989. }
  990. },
  991. touch: function(event) {
  992. module.determine.eventOnElement(event, function() {
  993. if(event.type == 'touchstart') {
  994. module.timer = setTimeout(function() {
  995. module.hide();
  996. }, settings.delay.touch);
  997. }
  998. else if(event.type == 'touchmove') {
  999. clearTimeout(module.timer);
  1000. }
  1001. });
  1002. event.stopPropagation();
  1003. },
  1004. hide: function(event) {
  1005. module.determine.eventInModule(event, module.hide);
  1006. }
  1007. },
  1008. select: {
  1009. mutation: function(mutations) {
  1010. module.debug('<select> modified, recreating menu');
  1011. module.setup.select();
  1012. }
  1013. },
  1014. menu: {
  1015. mutation: function(mutations) {
  1016. var
  1017. mutation = mutations[0],
  1018. $addedNode = mutation.addedNodes
  1019. ? $(mutation.addedNodes[0])
  1020. : $(false),
  1021. $removedNode = mutation.removedNodes
  1022. ? $(mutation.removedNodes[0])
  1023. : $(false),
  1024. $changedNodes = $addedNode.add($removedNode),
  1025. isUserAddition = $changedNodes.is(selector.addition) || $changedNodes.closest(selector.addition).length > 0,
  1026. isMessage = $changedNodes.is(selector.message) || $changedNodes.closest(selector.message).length > 0
  1027. ;
  1028. if(isUserAddition || isMessage) {
  1029. module.debug('Updating item selector cache');
  1030. module.refreshItems();
  1031. }
  1032. else {
  1033. module.debug('Menu modified, updating selector cache');
  1034. module.refresh();
  1035. }
  1036. },
  1037. mousedown: function() {
  1038. itemActivated = true;
  1039. },
  1040. mouseup: function() {
  1041. itemActivated = false;
  1042. }
  1043. },
  1044. item: {
  1045. mouseenter: function(event) {
  1046. var
  1047. $target = $(event.target),
  1048. $item = $(this),
  1049. $subMenu = $item.children(selector.menu),
  1050. $otherMenus = $item.siblings(selector.item).children(selector.menu),
  1051. hasSubMenu = ($subMenu.length > 0),
  1052. isBubbledEvent = ($subMenu.find($target).length > 0)
  1053. ;
  1054. if( !isBubbledEvent && hasSubMenu ) {
  1055. clearTimeout(module.itemTimer);
  1056. module.itemTimer = setTimeout(function() {
  1057. module.verbose('Showing sub-menu', $subMenu);
  1058. $.each($otherMenus, function() {
  1059. module.animate.hide(false, $(this));
  1060. });
  1061. module.animate.show(false, $subMenu);
  1062. }, settings.delay.show);
  1063. event.preventDefault();
  1064. }
  1065. },
  1066. mouseleave: function(event) {
  1067. var
  1068. $subMenu = $(this).children(selector.menu)
  1069. ;
  1070. if($subMenu.length > 0) {
  1071. clearTimeout(module.itemTimer);
  1072. module.itemTimer = setTimeout(function() {
  1073. module.verbose('Hiding sub-menu', $subMenu);
  1074. module.animate.hide(false, $subMenu);
  1075. }, settings.delay.hide);
  1076. }
  1077. },
  1078. click: function (event, skipRefocus) {
  1079. var
  1080. $choice = $(this),
  1081. $target = (event)
  1082. ? $(event.target)
  1083. : $(''),
  1084. $subMenu = $choice.find(selector.menu),
  1085. text = module.get.choiceText($choice),
  1086. value = module.get.choiceValue($choice, text),
  1087. hasSubMenu = ($subMenu.length > 0),
  1088. isBubbledEvent = ($subMenu.find($target).length > 0)
  1089. ;
  1090. if(!isBubbledEvent && (!hasSubMenu || settings.allowCategorySelection)) {
  1091. if(module.is.searchSelection()) {
  1092. if(settings.allowAdditions) {
  1093. module.remove.userAddition();
  1094. }
  1095. module.remove.searchTerm();
  1096. if(!module.is.focusedOnSearch() && !(skipRefocus == true)) {
  1097. module.focusSearch(true);
  1098. }
  1099. }
  1100. if(!settings.useLabels) {
  1101. module.remove.filteredItem();
  1102. module.set.scrollPosition($choice);
  1103. }
  1104. module.determine.selectAction.call(this, text, value);
  1105. }
  1106. }
  1107. },
  1108. document: {
  1109. // label selection should occur even when element has no focus
  1110. keydown: function(event) {
  1111. var
  1112. pressedKey = event.which,
  1113. isShortcutKey = module.is.inObject(pressedKey, keys)
  1114. ;
  1115. if(isShortcutKey) {
  1116. var
  1117. $label = $module.find(selector.label),
  1118. $activeLabel = $label.filter('.' + className.active),
  1119. activeValue = $activeLabel.data(metadata.value),
  1120. labelIndex = $label.index($activeLabel),
  1121. labelCount = $label.length,
  1122. hasActiveLabel = ($activeLabel.length > 0),
  1123. hasMultipleActive = ($activeLabel.length > 1),
  1124. isFirstLabel = (labelIndex === 0),
  1125. isLastLabel = (labelIndex + 1 == labelCount),
  1126. isSearch = module.is.searchSelection(),
  1127. isFocusedOnSearch = module.is.focusedOnSearch(),
  1128. isFocused = module.is.focused(),
  1129. caretAtStart = (isFocusedOnSearch && module.get.caretPosition() === 0),
  1130. $nextLabel
  1131. ;
  1132. if(isSearch && !hasActiveLabel && !isFocusedOnSearch) {
  1133. return;
  1134. }
  1135. if(pressedKey == keys.leftArrow) {
  1136. // activate previous label
  1137. if((isFocused || caretAtStart) && !hasActiveLabel) {
  1138. module.verbose('Selecting previous label');
  1139. $label.last().addClass(className.active);
  1140. }
  1141. else if(hasActiveLabel) {
  1142. if(!event.shiftKey) {
  1143. module.verbose('Selecting previous label');
  1144. $label.removeClass(className.active);
  1145. }
  1146. else {
  1147. module.verbose('Adding previous label to selection');
  1148. }
  1149. if(isFirstLabel && !hasMultipleActive) {
  1150. $activeLabel.addClass(className.active);
  1151. }
  1152. else {
  1153. $activeLabel.prev(selector.siblingLabel)
  1154. .addClass(className.active)
  1155. .end()
  1156. ;
  1157. }
  1158. event.preventDefault();
  1159. }
  1160. }
  1161. else if(pressedKey == keys.rightArrow) {
  1162. // activate first label
  1163. if(isFocused && !hasActiveLabel) {
  1164. $label.first().addClass(className.active);
  1165. }
  1166. // activate next label
  1167. if(hasActiveLabel) {
  1168. if(!event.shiftKey) {
  1169. module.verbose('Selecting next label');
  1170. $label.removeClass(className.active);
  1171. }
  1172. else {
  1173. module.verbose('Adding next label to selection');
  1174. }
  1175. if(isLastLabel) {
  1176. if(isSearch) {
  1177. if(!isFocusedOnSearch) {
  1178. module.focusSearch();
  1179. }
  1180. else {
  1181. $label.removeClass(className.active);
  1182. }
  1183. }
  1184. else if(hasMultipleActive) {
  1185. $activeLabel.next(selector.siblingLabel).addClass(className.active);
  1186. }
  1187. else {
  1188. $activeLabel.addClass(className.active);
  1189. }
  1190. }
  1191. else {
  1192. $activeLabel.next(selector.siblingLabel).addClass(className.active);
  1193. }
  1194. event.preventDefault();
  1195. }
  1196. }
  1197. else if(pressedKey == keys.deleteKey || pressedKey == keys.backspace) {
  1198. if(hasActiveLabel) {
  1199. module.verbose('Removing active labels');
  1200. if(isLastLabel) {
  1201. if(isSearch && !isFocusedOnSearch) {
  1202. module.focusSearch();
  1203. }
  1204. }
  1205. $activeLabel.last().next(selector.siblingLabel).addClass(className.active);
  1206. module.remove.activeLabels($activeLabel);
  1207. event.preventDefault();
  1208. }
  1209. else if(caretAtStart && !hasActiveLabel && pressedKey == keys.backspace) {
  1210. module.verbose('Removing last label on input backspace');
  1211. $activeLabel = $label.last().addClass(className.active);
  1212. module.remove.activeLabels($activeLabel);
  1213. }
  1214. }
  1215. else {
  1216. $activeLabel.removeClass(className.active);
  1217. }
  1218. }
  1219. }
  1220. },
  1221. keydown: function(event) {
  1222. var
  1223. pressedKey = event.which,
  1224. isShortcutKey = module.is.inObject(pressedKey, keys)
  1225. ;
  1226. if(isShortcutKey) {
  1227. var
  1228. $currentlySelected = $item.not(selector.unselectable).filter('.' + className.selected).eq(0),
  1229. $activeItem = $menu.children('.' + className.active).eq(0),
  1230. $selectedItem = ($currentlySelected.length > 0)
  1231. ? $currentlySelected
  1232. : $activeItem,
  1233. $visibleItems = ($selectedItem.length > 0)
  1234. ? $selectedItem.siblings(':not(.' + className.filtered +')').addBack()
  1235. : $menu.children(':not(.' + className.filtered +')'),
  1236. $subMenu = $selectedItem.children(selector.menu),
  1237. $parentMenu = $selectedItem.closest(selector.menu),
  1238. inVisibleMenu = ($parentMenu.hasClass(className.visible) || $parentMenu.hasClass(className.animating) || $parentMenu.parent(selector.menu).length > 0),
  1239. hasSubMenu = ($subMenu.length> 0),
  1240. hasSelectedItem = ($selectedItem.length > 0),
  1241. selectedIsSelectable = ($selectedItem.not(selector.unselectable).length > 0),
  1242. delimiterPressed = (pressedKey == keys.delimiter && settings.allowAdditions && module.is.multiple()),
  1243. isAdditionWithoutMenu = (settings.allowAdditions && settings.hideAdditions && (pressedKey == keys.enter || delimiterPressed) && selectedIsSelectable),
  1244. $nextItem,
  1245. isSubMenuItem,
  1246. newIndex
  1247. ;
  1248. // allow selection with menu closed
  1249. if(isAdditionWithoutMenu) {
  1250. module.verbose('Selecting item from keyboard shortcut', $selectedItem);
  1251. module.event.item.click.call($selectedItem, event);
  1252. if(module.is.searchSelection()) {
  1253. module.remove.searchTerm();
  1254. }
  1255. }
  1256. // visible menu keyboard shortcuts
  1257. if( module.is.visible() ) {
  1258. // enter (select or open sub-menu)
  1259. if(pressedKey == keys.enter || delimiterPressed) {
  1260. if(pressedKey == keys.enter && hasSelectedItem && hasSubMenu && !settings.allowCategorySelection) {
  1261. module.verbose('Pressed enter on unselectable category, opening sub menu');
  1262. pressedKey = keys.rightArrow;
  1263. }
  1264. else if(selectedIsSelectable) {
  1265. module.verbose('Selecting item from keyboard shortcut', $selectedItem);
  1266. module.event.item.click.call($selectedItem, event);
  1267. if(module.is.searchSelection()) {
  1268. module.remove.searchTerm();
  1269. }
  1270. }
  1271. event.preventDefault();
  1272. }
  1273. // sub-menu actions
  1274. if(hasSelectedItem) {
  1275. if(pressedKey == keys.leftArrow) {
  1276. isSubMenuItem = ($parentMenu[0] !== $menu[0]);
  1277. if(isSubMenuItem) {
  1278. module.verbose('Left key pressed, closing sub-menu');
  1279. module.animate.hide(false, $parentMenu);
  1280. $selectedItem
  1281. .removeClass(className.selected)
  1282. ;
  1283. $parentMenu
  1284. .closest(selector.item)
  1285. .addClass(className.selected)
  1286. ;
  1287. event.preventDefault();
  1288. }
  1289. }
  1290. // right arrow (show sub-menu)
  1291. if(pressedKey == keys.rightArrow) {
  1292. if(hasSubMenu) {
  1293. module.verbose('Right key pressed, opening sub-menu');
  1294. module.animate.show(false, $subMenu);
  1295. $selectedItem
  1296. .removeClass(className.selected)
  1297. ;
  1298. $subMenu
  1299. .find(selector.item).eq(0)
  1300. .addClass(className.selected)
  1301. ;
  1302. event.preventDefault();
  1303. }
  1304. }
  1305. }
  1306. // up arrow (traverse menu up)
  1307. if(pressedKey == keys.upArrow) {
  1308. $nextItem = (hasSelectedItem && inVisibleMenu)
  1309. ? $selectedItem.prevAll(selector.item + ':not(' + selector.unselectable + ')').eq(0)
  1310. : $item.eq(0)
  1311. ;
  1312. if($visibleItems.index( $nextItem ) < 0) {
  1313. module.verbose('Up key pressed but reached top of current menu');
  1314. event.preventDefault();
  1315. return;
  1316. }
  1317. else {
  1318. module.verbose('Up key pressed, changing active item');
  1319. $selectedItem
  1320. .removeClass(className.selected)
  1321. ;
  1322. $nextItem
  1323. .addClass(className.selected)
  1324. ;
  1325. module.set.scrollPosition($nextItem);
  1326. if(settings.selectOnKeydown && module.is.single()) {
  1327. module.set.selectedItem($nextItem);
  1328. }
  1329. }
  1330. event.preventDefault();
  1331. }
  1332. // down arrow (traverse menu down)
  1333. if(pressedKey == keys.downArrow) {
  1334. $nextItem = (hasSelectedItem && inVisibleMenu)
  1335. ? $nextItem = $selectedItem.nextAll(selector.item + ':not(' + selector.unselectable + ')').eq(0)
  1336. : $item.eq(0)
  1337. ;
  1338. if($nextItem.length === 0) {
  1339. module.verbose('Down key pressed but reached bottom of current menu');
  1340. event.preventDefault();
  1341. return;
  1342. }
  1343. else {
  1344. module.verbose('Down key pressed, changing active item');
  1345. $item
  1346. .removeClass(className.selected)
  1347. ;
  1348. $nextItem
  1349. .addClass(className.selected)
  1350. ;
  1351. module.set.scrollPosition($nextItem);
  1352. if(settings.selectOnKeydown && module.is.single()) {
  1353. module.set.selectedItem($nextItem);
  1354. }
  1355. }
  1356. event.preventDefault();
  1357. }
  1358. // page down (show next page)
  1359. if(pressedKey == keys.pageUp) {
  1360. module.scrollPage('up');
  1361. event.preventDefault();
  1362. }
  1363. if(pressedKey == keys.pageDown) {
  1364. module.scrollPage('down');
  1365. event.preventDefault();
  1366. }
  1367. // escape (close menu)
  1368. if(pressedKey == keys.escape) {
  1369. module.verbose('Escape key pressed, closing dropdown');
  1370. module.hide();
  1371. }
  1372. }
  1373. else {
  1374. // delimiter key
  1375. if(delimiterPressed) {
  1376. event.preventDefault();
  1377. }
  1378. // down arrow (open menu)
  1379. if(pressedKey == keys.downArrow && !module.is.visible()) {
  1380. module.verbose('Down key pressed, showing dropdown');
  1381. module.select.firstUnfiltered();
  1382. module.show();
  1383. event.preventDefault();
  1384. }
  1385. }
  1386. }
  1387. else {
  1388. if( !module.has.search() ) {
  1389. module.set.selectedLetter( String.fromCharCode(pressedKey) );
  1390. }
  1391. }
  1392. }
  1393. },
  1394. trigger: {
  1395. change: function() {
  1396. var
  1397. events = document.createEvent('HTMLEvents'),
  1398. inputElement = $input[0]
  1399. ;
  1400. if(inputElement) {
  1401. module.verbose('Triggering native change event');
  1402. events.initEvent('change', true, false);
  1403. inputElement.dispatchEvent(events);
  1404. }
  1405. }
  1406. },
  1407. determine: {
  1408. selectAction: function(text, value) {
  1409. module.verbose('Determining action', settings.action);
  1410. if( $.isFunction( module.action[settings.action] ) ) {
  1411. module.verbose('Triggering preset action', settings.action, text, value);
  1412. module.action[ settings.action ].call(element, text, value, this);
  1413. }
  1414. else if( $.isFunction(settings.action) ) {
  1415. module.verbose('Triggering user action', settings.action, text, value);
  1416. settings.action.call(element, text, value, this);
  1417. }
  1418. else {
  1419. module.error(error.action, settings.action);
  1420. }
  1421. },
  1422. eventInModule: function(event, callback) {
  1423. var
  1424. $target = $(event.target),
  1425. inDocument = ($target.closest(document.documentElement).length > 0),
  1426. inModule = ($target.closest($module).length > 0)
  1427. ;
  1428. callback = $.isFunction(callback)
  1429. ? callback
  1430. : function(){}
  1431. ;
  1432. if(inDocument && !inModule) {
  1433. module.verbose('Triggering event', callback);
  1434. callback();
  1435. return true;
  1436. }
  1437. else {
  1438. module.verbose('Event occurred in dropdown, canceling callback');
  1439. return false;
  1440. }
  1441. },
  1442. eventOnElement: function(event, callback) {
  1443. var
  1444. $target = $(event.target),
  1445. $label = $target.closest(selector.siblingLabel),
  1446. inVisibleDOM = document.body.contains(event.target),
  1447. notOnLabel = ($module.find($label).length === 0),
  1448. notInMenu = ($target.closest($menu).length === 0)
  1449. ;
  1450. callback = $.isFunction(callback)
  1451. ? callback
  1452. : function(){}
  1453. ;
  1454. if(inVisibleDOM && notOnLabel && notInMenu) {
  1455. module.verbose('Triggering event', callback);
  1456. callback();
  1457. return true;
  1458. }
  1459. else {
  1460. module.verbose('Event occurred in dropdown menu, canceling callback');
  1461. return false;
  1462. }
  1463. }
  1464. },
  1465. action: {
  1466. nothing: function() {},
  1467. activate: function(text, value, element) {
  1468. value = (value !== undefined)
  1469. ? value
  1470. : text
  1471. ;
  1472. if( module.can.activate( $(element) ) ) {
  1473. module.set.selected(value, $(element));
  1474. if(module.is.multiple() && !module.is.allFiltered()) {
  1475. return;
  1476. }
  1477. else {
  1478. module.hideAndClear();
  1479. }
  1480. }
  1481. },
  1482. select: function(text, value, element) {
  1483. value = (value !== undefined)
  1484. ? value
  1485. : text
  1486. ;
  1487. if( module.can.activate( $(element) ) ) {
  1488. module.set.value(value, $(element));
  1489. if(module.is.multiple() && !module.is.allFiltered()) {
  1490. return;
  1491. }
  1492. else {
  1493. module.hideAndClear();
  1494. }
  1495. }
  1496. },
  1497. combo: function(text, value, element) {
  1498. value = (value !== undefined)
  1499. ? value
  1500. : text
  1501. ;
  1502. module.set.selected(value, $(element));
  1503. module.hideAndClear();
  1504. },
  1505. hide: function(text, value, element) {
  1506. module.set.value(value, text);
  1507. module.hideAndClear();
  1508. }
  1509. },
  1510. get: {
  1511. id: function() {
  1512. return id;
  1513. },
  1514. defaultText: function() {
  1515. return $module.data(metadata.defaultText);
  1516. },
  1517. defaultValue: function() {
  1518. return $module.data(metadata.defaultValue);
  1519. },
  1520. placeholderText: function() {
  1521. return $module.data(metadata.placeholderText) || '';
  1522. },
  1523. text: function() {
  1524. return $text.text();
  1525. },
  1526. query: function() {
  1527. return $.trim($search.val());
  1528. },
  1529. searchWidth: function(value) {
  1530. value = (value !== undefined)
  1531. ? value
  1532. : $search.val()
  1533. ;
  1534. $sizer.text(value);
  1535. // prevent rounding issues
  1536. return Math.ceil( $sizer.width() + 1);
  1537. },
  1538. selectionCount: function() {
  1539. var
  1540. values = module.get.values(),
  1541. count
  1542. ;
  1543. count = ( module.is.multiple() )
  1544. ? $.isArray(values)
  1545. ? values.length
  1546. : 0
  1547. : (module.get.value() !== '')
  1548. ? 1
  1549. : 0
  1550. ;
  1551. return count;
  1552. },
  1553. transition: function($subMenu) {
  1554. return (settings.transition == 'auto')
  1555. ? module.is.upward($subMenu)
  1556. ? 'slide up'
  1557. : 'slide down'
  1558. : settings.transition
  1559. ;
  1560. },
  1561. userValues: function() {
  1562. var
  1563. values = module.get.values()
  1564. ;
  1565. if(!values) {
  1566. return false;
  1567. }
  1568. values = $.isArray(values)
  1569. ? values
  1570. : [values]
  1571. ;
  1572. return $.grep(values, function(value) {
  1573. return (module.get.item(value) === false);
  1574. });
  1575. },
  1576. uniqueArray: function(array) {
  1577. return $.grep(array, function (value, index) {
  1578. return $.inArray(value, array) === index;
  1579. });
  1580. },
  1581. caretPosition: function() {
  1582. var
  1583. input = $search.get(0),
  1584. range,
  1585. rangeLength
  1586. ;
  1587. if('selectionStart' in input) {
  1588. return input.selectionStart;
  1589. }
  1590. else if (document.selection) {
  1591. input.focus();
  1592. range = document.selection.createRange();
  1593. rangeLength = range.text.length;
  1594. range.moveStart('character', -input.value.length);
  1595. return range.text.length - rangeLength;
  1596. }
  1597. },
  1598. value: function() {
  1599. var
  1600. value = ($input.length > 0)
  1601. ? $input.val()
  1602. : $module.data(metadata.value),
  1603. isEmptyMultiselect = ($.isArray(value) && value.length === 1 && value[0] === '')
  1604. ;
  1605. // prevents placeholder element from being selected when multiple
  1606. return (value === undefined || isEmptyMultiselect)
  1607. ? ''
  1608. : value
  1609. ;
  1610. },
  1611. values: function() {
  1612. var
  1613. value = module.get.value()
  1614. ;
  1615. if(value === '') {
  1616. return '';
  1617. }
  1618. return ( !module.has.selectInput() && module.is.multiple() )
  1619. ? (typeof value == 'string') // delimited string
  1620. ? value.split(settings.delimiter)
  1621. : ''
  1622. : value
  1623. ;
  1624. },
  1625. remoteValues: function() {
  1626. var
  1627. values = module.get.values(),
  1628. remoteValues = false
  1629. ;
  1630. if(values) {
  1631. if(typeof values == 'string') {
  1632. values = [values];
  1633. }
  1634. $.each(values, function(index, value) {
  1635. var
  1636. name = module.read.remoteData(value)
  1637. ;
  1638. module.verbose('Restoring value from session data', name, value);
  1639. if(name) {
  1640. if(!remoteValues) {
  1641. remoteValues = {};
  1642. }
  1643. remoteValues[value] = name;
  1644. }
  1645. });
  1646. }
  1647. return remoteValues;
  1648. },
  1649. choiceText: function($choice, preserveHTML) {
  1650. preserveHTML = (preserveHTML !== undefined)
  1651. ? preserveHTML
  1652. : settings.preserveHTML
  1653. ;
  1654. if($choice) {
  1655. if($choice.find(selector.menu).length > 0) {
  1656. module.verbose('Retrieving text of element with sub-menu');
  1657. $choice = $choice.clone();
  1658. $choice.find(selector.menu).remove();
  1659. $choice.find(selector.menuIcon).remove();
  1660. }
  1661. return ($choice.data(metadata.text) !== undefined)
  1662. ? $choice.data(metadata.text)
  1663. : (preserveHTML)
  1664. ? $.trim($choice.html())
  1665. : $.trim($choice.text())
  1666. ;
  1667. }
  1668. },
  1669. choiceValue: function($choice, choiceText) {
  1670. choiceText = choiceText || module.get.choiceText($choice);
  1671. if(!$choice) {
  1672. return false;
  1673. }
  1674. return ($choice.data(metadata.value) !== undefined)
  1675. ? String( $choice.data(metadata.value) )
  1676. : (typeof choiceText === 'string')
  1677. ? $.trim(choiceText.toLowerCase())
  1678. : String(choiceText)
  1679. ;
  1680. },
  1681. inputEvent: function() {
  1682. var
  1683. input = $search[0]
  1684. ;
  1685. if(input) {
  1686. return (input.oninput !== undefined)
  1687. ? 'input'
  1688. : (input.onpropertychange !== undefined)
  1689. ? 'propertychange'
  1690. : 'keyup'
  1691. ;
  1692. }
  1693. return false;
  1694. },
  1695. selectValues: function() {
  1696. var
  1697. select = {}
  1698. ;
  1699. select.values = [];
  1700. $module
  1701. .find('option')
  1702. .each(function() {
  1703. var
  1704. $option = $(this),
  1705. name = $option.html(),
  1706. disabled = $option.attr('disabled'),
  1707. value = ( $option.attr('value') !== undefined )
  1708. ? $option.attr('value')
  1709. : name
  1710. ;
  1711. if(settings.placeholder === 'auto' && value === '') {
  1712. select.placeholder = name;
  1713. }
  1714. else {
  1715. select.values.push({
  1716. name : name,
  1717. value : value,
  1718. disabled : disabled
  1719. });
  1720. }
  1721. })
  1722. ;
  1723. if(settings.placeholder && settings.placeholder !== 'auto') {
  1724. module.debug('Setting placeholder value to', settings.placeholder);
  1725. select.placeholder = settings.placeholder;
  1726. }
  1727. if(settings.sortSelect) {
  1728. select.values.sort(function(a, b) {
  1729. return (a.name > b.name)
  1730. ? 1
  1731. : -1
  1732. ;
  1733. });
  1734. module.debug('Retrieved and sorted values from select', select);
  1735. }
  1736. else {
  1737. module.debug('Retrieved values from select', select);
  1738. }
  1739. return select;
  1740. },
  1741. activeItem: function() {
  1742. return $item.filter('.' + className.active);
  1743. },
  1744. selectedItem: function() {
  1745. var
  1746. $selectedItem = $item.not(selector.unselectable).filter('.' + className.selected)
  1747. ;
  1748. return ($selectedItem.length > 0)
  1749. ? $selectedItem
  1750. : $item.eq(0)
  1751. ;
  1752. },
  1753. itemWithAdditions: function(value) {
  1754. var
  1755. $items = module.get.item(value),
  1756. $userItems = module.create.userChoice(value),
  1757. hasUserItems = ($userItems && $userItems.length > 0)
  1758. ;
  1759. if(hasUserItems) {
  1760. $items = ($items.length > 0)
  1761. ? $items.add($userItems)
  1762. : $userItems
  1763. ;
  1764. }
  1765. return $items;
  1766. },
  1767. item: function(value, strict) {
  1768. var
  1769. $selectedItem = false,
  1770. shouldSearch,
  1771. isMultiple
  1772. ;
  1773. value = (value !== undefined)
  1774. ? value
  1775. : ( module.get.values() !== undefined)
  1776. ? module.get.values()
  1777. : module.get.text()
  1778. ;
  1779. shouldSearch = (isMultiple)
  1780. ? (value.length > 0)
  1781. : (value !== undefined && value !== null)
  1782. ;
  1783. isMultiple = (module.is.multiple() && $.isArray(value));
  1784. strict = (value === '' || value === 0)
  1785. ? true
  1786. : strict || false
  1787. ;
  1788. if(shouldSearch) {
  1789. $item
  1790. .each(function() {
  1791. var
  1792. $choice = $(this),
  1793. optionText = module.get.choiceText($choice),
  1794. optionValue = module.get.choiceValue($choice, optionText)
  1795. ;
  1796. // safe early exit
  1797. if(optionValue === null || optionValue === undefined) {
  1798. return;
  1799. }
  1800. if(isMultiple) {
  1801. if($.inArray( String(optionValue), value) !== -1 || $.inArray(optionText, value) !== -1) {
  1802. $selectedItem = ($selectedItem)
  1803. ? $selectedItem.add($choice)
  1804. : $choice
  1805. ;
  1806. }
  1807. }
  1808. else if(strict) {
  1809. module.verbose('Ambiguous dropdown value using strict type check', $choice, value);
  1810. if( optionValue === value || optionText === value) {
  1811. $selectedItem = $choice;
  1812. return true;
  1813. }
  1814. }
  1815. else {
  1816. if( String(optionValue) == String(value) || optionText == value) {
  1817. module.verbose('Found select item by value', optionValue, value);
  1818. $selectedItem = $choice;
  1819. return true;
  1820. }
  1821. }
  1822. })
  1823. ;
  1824. }
  1825. return $selectedItem;
  1826. }
  1827. },
  1828. check: {
  1829. maxSelections: function(selectionCount) {
  1830. if(settings.maxSelections) {
  1831. selectionCount = (selectionCount !== undefined)
  1832. ? selectionCount
  1833. : module.get.selectionCount()
  1834. ;
  1835. if(selectionCount >= settings.maxSelections) {
  1836. module.debug('Maximum selection count reached');
  1837. if(settings.useLabels) {
  1838. $item.addClass(className.filtered);
  1839. module.add.message(message.maxSelections);
  1840. }
  1841. return true;
  1842. }
  1843. else {
  1844. module.verbose('No longer at maximum selection count');
  1845. module.remove.message();
  1846. module.remove.filteredItem();
  1847. if(module.is.searchSelection()) {
  1848. module.filterItems();
  1849. }
  1850. return false;
  1851. }
  1852. }
  1853. return true;
  1854. }
  1855. },
  1856. restore: {
  1857. defaults: function() {
  1858. module.clear();
  1859. module.restore.defaultText();
  1860. module.restore.defaultValue();
  1861. },
  1862. defaultText: function() {
  1863. var
  1864. defaultText = module.get.defaultText(),
  1865. placeholderText = module.get.placeholderText
  1866. ;
  1867. if(defaultText === placeholderText) {
  1868. module.debug('Restoring default placeholder text', defaultText);
  1869. module.set.placeholderText(defaultText);
  1870. }
  1871. else {
  1872. module.debug('Restoring default text', defaultText);
  1873. module.set.text(defaultText);
  1874. }
  1875. },
  1876. placeholderText: function() {
  1877. module.set.placeholderText();
  1878. },
  1879. defaultValue: function() {
  1880. var
  1881. defaultValue = module.get.defaultValue()
  1882. ;
  1883. if(defaultValue !== undefined) {
  1884. module.debug('Restoring default value', defaultValue);
  1885. if(defaultValue !== '') {
  1886. module.set.value(defaultValue);
  1887. module.set.selected();
  1888. }
  1889. else {
  1890. module.remove.activeItem();
  1891. module.remove.selectedItem();
  1892. }
  1893. }
  1894. },
  1895. labels: function() {
  1896. if(settings.allowAdditions) {
  1897. if(!settings.useLabels) {
  1898. module.error(error.labels);
  1899. settings.useLabels = true;
  1900. }
  1901. module.debug('Restoring selected values');
  1902. module.create.userLabels();
  1903. }
  1904. module.check.maxSelections();
  1905. },
  1906. selected: function() {
  1907. module.restore.values();
  1908. if(module.is.multiple()) {
  1909. module.debug('Restoring previously selected values and labels');
  1910. module.restore.labels();
  1911. }
  1912. else {
  1913. module.debug('Restoring previously selected values');
  1914. }
  1915. },
  1916. values: function() {
  1917. // prevents callbacks from occurring on initial load
  1918. module.set.initialLoad();
  1919. if(settings.apiSettings && settings.saveRemoteData && module.get.remoteValues()) {
  1920. module.restore.remoteValues();
  1921. }
  1922. else {
  1923. module.set.selected();
  1924. }
  1925. module.remove.initialLoad();
  1926. },
  1927. remoteValues: function() {
  1928. var
  1929. values = module.get.remoteValues()
  1930. ;
  1931. module.debug('Recreating selected from session data', values);
  1932. if(values) {
  1933. if( module.is.single() ) {
  1934. $.each(values, function(value, name) {
  1935. module.set.text(name);
  1936. });
  1937. }
  1938. else {
  1939. $.each(values, function(value, name) {
  1940. module.add.label(value, name);
  1941. });
  1942. }
  1943. }
  1944. }
  1945. },
  1946. read: {
  1947. remoteData: function(value) {
  1948. var
  1949. name
  1950. ;
  1951. if(window.Storage === undefined) {
  1952. module.error(error.noStorage);
  1953. return;
  1954. }
  1955. name = sessionStorage.getItem(value);
  1956. return (name !== undefined)
  1957. ? name
  1958. : false
  1959. ;
  1960. }
  1961. },
  1962. save: {
  1963. defaults: function() {
  1964. module.save.defaultText();
  1965. module.save.placeholderText();
  1966. module.save.defaultValue();
  1967. },
  1968. defaultValue: function() {
  1969. var
  1970. value = module.get.value()
  1971. ;
  1972. module.verbose('Saving default value as', value);
  1973. $module.data(metadata.defaultValue, value);
  1974. },
  1975. defaultText: function() {
  1976. var
  1977. text = module.get.text()
  1978. ;
  1979. module.verbose('Saving default text as', text);
  1980. $module.data(metadata.defaultText, text);
  1981. },
  1982. placeholderText: function() {
  1983. var
  1984. text
  1985. ;
  1986. if(settings.placeholder !== false && $text.hasClass(className.placeholder)) {
  1987. text = module.get.text();
  1988. module.verbose('Saving placeholder text as', text);
  1989. $module.data(metadata.placeholderText, text);
  1990. }
  1991. },
  1992. remoteData: function(name, value) {
  1993. if(window.Storage === undefined) {
  1994. module.error(error.noStorage);
  1995. return;
  1996. }
  1997. module.verbose('Saving remote data to session storage', value, name);
  1998. sessionStorage.setItem(value, name);
  1999. }
  2000. },
  2001. clear: function() {
  2002. if(module.is.multiple() && settings.useLabels) {
  2003. module.remove.labels();
  2004. }
  2005. else {
  2006. module.remove.activeItem();
  2007. module.remove.selectedItem();
  2008. }
  2009. module.set.placeholderText();
  2010. module.clearValue();
  2011. },
  2012. clearValue: function() {
  2013. module.set.value('');
  2014. },
  2015. scrollPage: function(direction, $selectedItem) {
  2016. var
  2017. $currentItem = $selectedItem || module.get.selectedItem(),
  2018. $menu = $currentItem.closest(selector.menu),
  2019. menuHeight = $menu.outerHeight(),
  2020. currentScroll = $menu.scrollTop(),
  2021. itemHeight = $item.eq(0).outerHeight(),
  2022. itemsPerPage = Math.floor(menuHeight / itemHeight),
  2023. maxScroll = $menu.prop('scrollHeight'),
  2024. newScroll = (direction == 'up')
  2025. ? currentScroll - (itemHeight * itemsPerPage)
  2026. : currentScroll + (itemHeight * itemsPerPage),
  2027. $selectableItem = $item.not(selector.unselectable),
  2028. isWithinRange,
  2029. $nextSelectedItem,
  2030. elementIndex
  2031. ;
  2032. elementIndex = (direction == 'up')
  2033. ? $selectableItem.index($currentItem) - itemsPerPage
  2034. : $selectableItem.index($currentItem) + itemsPerPage
  2035. ;
  2036. isWithinRange = (direction == 'up')
  2037. ? (elementIndex >= 0)
  2038. : (elementIndex < $selectableItem.length)
  2039. ;
  2040. $nextSelectedItem = (isWithinRange)
  2041. ? $selectableItem.eq(elementIndex)
  2042. : (direction == 'up')
  2043. ? $selectableItem.first()
  2044. : $selectableItem.last()
  2045. ;
  2046. if($nextSelectedItem.length > 0) {
  2047. module.debug('Scrolling page', direction, $nextSelectedItem);
  2048. $currentItem
  2049. .removeClass(className.selected)
  2050. ;
  2051. $nextSelectedItem
  2052. .addClass(className.selected)
  2053. ;
  2054. if(settings.selectOnKeydown && module.is.single()) {
  2055. module.set.selectedItem($nextSelectedItem);
  2056. }
  2057. $menu
  2058. .scrollTop(newScroll)
  2059. ;
  2060. }
  2061. },
  2062. set: {
  2063. filtered: function() {
  2064. var
  2065. isMultiple = module.is.multiple(),
  2066. isSearch = module.is.searchSelection(),
  2067. isSearchMultiple = (isMultiple && isSearch),
  2068. searchValue = (isSearch)
  2069. ? module.get.query()
  2070. : '',
  2071. hasSearchValue = (typeof searchValue === 'string' && searchValue.length > 0),
  2072. searchWidth = module.get.searchWidth(),
  2073. valueIsSet = searchValue !== ''
  2074. ;
  2075. if(isMultiple && hasSearchValue) {
  2076. module.verbose('Adjusting input width', searchWidth, settings.glyphWidth);
  2077. $search.css('width', searchWidth);
  2078. }
  2079. if(hasSearchValue || (isSearchMultiple && valueIsSet)) {
  2080. module.verbose('Hiding placeholder text');
  2081. $text.addClass(className.filtered);
  2082. }
  2083. else if(!isMultiple || (isSearchMultiple && !valueIsSet)) {
  2084. module.verbose('Showing placeholder text');
  2085. $text.removeClass(className.filtered);
  2086. }
  2087. },
  2088. empty: function() {
  2089. $module.addClass(className.empty);
  2090. },
  2091. loading: function() {
  2092. $module.addClass(className.loading);
  2093. },
  2094. placeholderText: function(text) {
  2095. text = text || module.get.placeholderText();
  2096. module.debug('Setting placeholder text', text);
  2097. module.set.text(text);
  2098. $text.addClass(className.placeholder);
  2099. },
  2100. tabbable: function() {
  2101. if( module.has.search() ) {
  2102. module.debug('Added tabindex to searchable dropdown');
  2103. $search
  2104. .val('')
  2105. .attr('tabindex', 0)
  2106. ;
  2107. $menu
  2108. .attr('tabindex', -1)
  2109. ;
  2110. }
  2111. else {
  2112. module.debug('Added tabindex to dropdown');
  2113. if( $module.attr('tabindex') === undefined) {
  2114. $module
  2115. .attr('tabindex', 0)
  2116. ;
  2117. $menu
  2118. .attr('tabindex', -1)
  2119. ;
  2120. }
  2121. }
  2122. },
  2123. initialLoad: function() {
  2124. module.verbose('Setting initial load');
  2125. initialLoad = true;
  2126. },
  2127. activeItem: function($item) {
  2128. if( settings.allowAdditions && $item.filter(selector.addition).length > 0 ) {
  2129. $item.addClass(className.filtered);
  2130. }
  2131. else {
  2132. $item.addClass(className.active);
  2133. }
  2134. },
  2135. partialSearch: function(text) {
  2136. var
  2137. length = module.get.query().length
  2138. ;
  2139. $search.val( text.substr(0 , length));
  2140. },
  2141. scrollPosition: function($item, forceScroll) {
  2142. var
  2143. edgeTolerance = 5,
  2144. $menu,
  2145. hasActive,
  2146. offset,
  2147. itemHeight,
  2148. itemOffset,
  2149. menuOffset,
  2150. menuScroll,
  2151. menuHeight,
  2152. abovePage,
  2153. belowPage
  2154. ;
  2155. $item = $item || module.get.selectedItem();
  2156. $menu = $item.closest(selector.menu);
  2157. hasActive = ($item && $item.length > 0);
  2158. forceScroll = (forceScroll !== undefined)
  2159. ? forceScroll
  2160. : false
  2161. ;
  2162. if($item && $menu.length > 0 && hasActive) {
  2163. itemOffset = $item.position().top;
  2164. $menu.addClass(className.loading);
  2165. menuScroll = $menu.scrollTop();
  2166. menuOffset = $menu.offset().top;
  2167. itemOffset = $item.offset().top;
  2168. offset = menuScroll - menuOffset + itemOffset;
  2169. if(!forceScroll) {
  2170. menuHeight = $menu.height();
  2171. belowPage = menuScroll + menuHeight < (offset + edgeTolerance);
  2172. abovePage = ((offset - edgeTolerance) < menuScroll);
  2173. }
  2174. module.debug('Scrolling to active item', offset);
  2175. if(forceScroll || abovePage || belowPage) {
  2176. $menu.scrollTop(offset);
  2177. }
  2178. $menu.removeClass(className.loading);
  2179. }
  2180. },
  2181. text: function(text) {
  2182. if(settings.action !== 'select') {
  2183. if(settings.action == 'combo') {
  2184. module.debug('Changing combo button text', text, $combo);
  2185. if(settings.preserveHTML) {
  2186. $combo.html(text);
  2187. }
  2188. else {
  2189. $combo.text(text);
  2190. }
  2191. }
  2192. else {
  2193. if(text !== module.get.placeholderText()) {
  2194. $text.removeClass(className.placeholder);
  2195. }
  2196. module.debug('Changing text', text, $text);
  2197. $text
  2198. .removeClass(className.filtered)
  2199. ;
  2200. if(settings.preserveHTML) {
  2201. $text.html(text);
  2202. }
  2203. else {
  2204. $text.text(text);
  2205. }
  2206. }
  2207. }
  2208. },
  2209. selectedItem: function($item) {
  2210. var
  2211. value = module.get.choiceValue($item),
  2212. text = module.get.choiceText($item, false)
  2213. ;
  2214. module.debug('Setting user selection to item', $item);
  2215. module.remove.activeItem();
  2216. module.set.partialSearch(text);
  2217. module.set.activeItem($item);
  2218. module.set.selected(value, $item);
  2219. module.set.text(text);
  2220. },
  2221. selectedLetter: function(letter) {
  2222. var
  2223. $selectedItem = $item.filter('.' + className.selected),
  2224. alreadySelectedLetter = $selectedItem.length > 0 && module.has.firstLetter($selectedItem, letter),
  2225. $nextValue = false,
  2226. $nextItem
  2227. ;
  2228. // check next of same letter
  2229. if(alreadySelectedLetter) {
  2230. $nextItem = $selectedItem.nextAll($item).eq(0);
  2231. if( module.has.firstLetter($nextItem, letter) ) {
  2232. $nextValue = $nextItem;
  2233. }
  2234. }
  2235. // check all values
  2236. if(!$nextValue) {
  2237. $item
  2238. .each(function(){
  2239. if(module.has.firstLetter($(this), letter)) {
  2240. $nextValue = $(this);
  2241. return false;
  2242. }
  2243. })
  2244. ;
  2245. }
  2246. // set next value
  2247. if($nextValue) {
  2248. module.verbose('Scrolling to next value with letter', letter);
  2249. module.set.scrollPosition($nextValue);
  2250. $selectedItem.removeClass(className.selected);
  2251. $nextValue.addClass(className.selected);
  2252. if(settings.selectOnKeydown && module.is.single()) {
  2253. module.set.selectedItem($nextValue);
  2254. }
  2255. }
  2256. },
  2257. direction: function($menu) {
  2258. if(settings.direction == 'auto') {
  2259. if(module.is.onScreen($menu)) {
  2260. module.remove.upward($menu);
  2261. }
  2262. else {
  2263. module.set.upward($menu);
  2264. }
  2265. }
  2266. else if(settings.direction == 'upward') {
  2267. module.set.upward($menu);
  2268. }
  2269. },
  2270. upward: function($menu) {
  2271. var $element = $menu || $module;
  2272. $element.addClass(className.upward);
  2273. },
  2274. value: function(value, text, $selected) {
  2275. var
  2276. escapedValue = module.escape.value(value),
  2277. hasInput = ($input.length > 0),
  2278. isAddition = !module.has.value(value),
  2279. currentValue = module.get.values(),
  2280. stringValue = (value !== undefined)
  2281. ? String(value)
  2282. : value,
  2283. newValue
  2284. ;
  2285. if(hasInput) {
  2286. if(!settings.allowReselection && stringValue == currentValue) {
  2287. module.verbose('Skipping value update already same value', value, currentValue);
  2288. if(!module.is.initialLoad()) {
  2289. return;
  2290. }
  2291. }
  2292. if( module.is.single() && module.has.selectInput() && module.can.extendSelect() ) {
  2293. module.debug('Adding user option', value);
  2294. module.add.optionValue(value);
  2295. }
  2296. module.debug('Updating input value', escapedValue, currentValue);
  2297. internalChange = true;
  2298. $input
  2299. .val(escapedValue)
  2300. ;
  2301. if(settings.fireOnInit === false && module.is.initialLoad()) {
  2302. module.debug('Input native change event ignored on initial load');
  2303. }
  2304. else {
  2305. module.trigger.change();
  2306. }
  2307. internalChange = false;
  2308. }
  2309. else {
  2310. module.verbose('Storing value in metadata', escapedValue, $input);
  2311. if(escapedValue !== currentValue) {
  2312. $module.data(metadata.value, stringValue);
  2313. }
  2314. }
  2315. if(settings.fireOnInit === false && module.is.initialLoad()) {
  2316. module.verbose('No callback on initial load', settings.onChange);
  2317. }
  2318. else {
  2319. settings.onChange.call(element, value, text, $selected);
  2320. }
  2321. },
  2322. active: function() {
  2323. $module
  2324. .addClass(className.active)
  2325. ;
  2326. },
  2327. multiple: function() {
  2328. $module.addClass(className.multiple);
  2329. },
  2330. visible: function() {
  2331. $module.addClass(className.visible);
  2332. },
  2333. exactly: function(value, $selectedItem) {
  2334. module.debug('Setting selected to exact values');
  2335. module.clear();
  2336. module.set.selected(value, $selectedItem);
  2337. },
  2338. selected: function(value, $selectedItem) {
  2339. var
  2340. isMultiple = module.is.multiple(),
  2341. $userSelectedItem
  2342. ;
  2343. $selectedItem = (settings.allowAdditions)
  2344. ? $selectedItem || module.get.itemWithAdditions(value)
  2345. : $selectedItem || module.get.item(value)
  2346. ;
  2347. if(!$selectedItem) {
  2348. return;
  2349. }
  2350. module.debug('Setting selected menu item to', $selectedItem);
  2351. if(module.is.multiple()) {
  2352. module.remove.searchWidth();
  2353. }
  2354. if(module.is.single()) {
  2355. module.remove.activeItem();
  2356. module.remove.selectedItem();
  2357. }
  2358. else if(settings.useLabels) {
  2359. module.remove.selectedItem();
  2360. }
  2361. // select each item
  2362. $selectedItem
  2363. .each(function() {
  2364. var
  2365. $selected = $(this),
  2366. selectedText = module.get.choiceText($selected),
  2367. selectedValue = module.get.choiceValue($selected, selectedText),
  2368. isFiltered = $selected.hasClass(className.filtered),
  2369. isActive = $selected.hasClass(className.active),
  2370. isUserValue = $selected.hasClass(className.addition),
  2371. shouldAnimate = (isMultiple && $selectedItem.length == 1)
  2372. ;
  2373. if(isMultiple) {
  2374. if(!isActive || isUserValue) {
  2375. if(settings.apiSettings && settings.saveRemoteData) {
  2376. module.save.remoteData(selectedText, selectedValue);
  2377. }
  2378. if(settings.useLabels) {
  2379. module.add.value(selectedValue, selectedText, $selected);
  2380. module.add.label(selectedValue, selectedText, shouldAnimate);
  2381. module.set.activeItem($selected);
  2382. module.filterActive();
  2383. module.select.nextAvailable($selectedItem);
  2384. }
  2385. else {
  2386. module.add.value(selectedValue, selectedText, $selected);
  2387. module.set.text(module.add.variables(message.count));
  2388. module.set.activeItem($selected);
  2389. }
  2390. }
  2391. else if(!isFiltered) {
  2392. module.debug('Selected active value, removing label');
  2393. module.remove.selected(selectedValue);
  2394. }
  2395. }
  2396. else {
  2397. if(settings.apiSettings && settings.saveRemoteData) {
  2398. module.save.remoteData(selectedText, selectedValue);
  2399. }
  2400. module.set.text(selectedText);
  2401. module.set.value(selectedValue, selectedText, $selected);
  2402. $selected
  2403. .addClass(className.active)
  2404. .addClass(className.selected)
  2405. ;
  2406. }
  2407. })
  2408. ;
  2409. }
  2410. },
  2411. add: {
  2412. label: function(value, text, shouldAnimate) {
  2413. var
  2414. $next = module.is.searchSelection()
  2415. ? $search
  2416. : $text,
  2417. escapedValue = module.escape.value(value),
  2418. $label
  2419. ;
  2420. $label = $('<a />')
  2421. .addClass(className.label)
  2422. .attr('data-value', escapedValue)
  2423. .html(templates.label(escapedValue, text))
  2424. ;
  2425. $label = settings.onLabelCreate.call($label, escapedValue, text);
  2426. if(module.has.label(value)) {
  2427. module.debug('Label already exists, skipping', escapedValue);
  2428. return;
  2429. }
  2430. if(settings.label.variation) {
  2431. $label.addClass(settings.label.variation);
  2432. }
  2433. if(shouldAnimate === true) {
  2434. module.debug('Animating in label', $label);
  2435. $label
  2436. .addClass(className.hidden)
  2437. .insertBefore($next)
  2438. .transition(settings.label.transition, settings.label.duration)
  2439. ;
  2440. }
  2441. else {
  2442. module.debug('Adding selection label', $label);
  2443. $label
  2444. .insertBefore($next)
  2445. ;
  2446. }
  2447. },
  2448. message: function(message) {
  2449. var
  2450. $message = $menu.children(selector.message),
  2451. html = settings.templates.message(module.add.variables(message))
  2452. ;
  2453. if($message.length > 0) {
  2454. $message
  2455. .html(html)
  2456. ;
  2457. }
  2458. else {
  2459. $message = $('<div/>')
  2460. .html(html)
  2461. .addClass(className.message)
  2462. .appendTo($menu)
  2463. ;
  2464. }
  2465. },
  2466. optionValue: function(value) {
  2467. var
  2468. escapedValue = module.escape.value(value),
  2469. $option = $input.find('option[value="' + escapedValue + '"]'),
  2470. hasOption = ($option.length > 0)
  2471. ;
  2472. if(hasOption) {
  2473. return;
  2474. }
  2475. // temporarily disconnect observer
  2476. module.disconnect.selectObserver();
  2477. if( module.is.single() ) {
  2478. module.verbose('Removing previous user addition');
  2479. $input.find('option.' + className.addition).remove();
  2480. }
  2481. $('<option/>')
  2482. .prop('value', escapedValue)
  2483. .addClass(className.addition)
  2484. .html(value)
  2485. .appendTo($input)
  2486. ;
  2487. module.verbose('Adding user addition as an <option>', value);
  2488. module.observe.select();
  2489. },
  2490. userSuggestion: function(value) {
  2491. var
  2492. $addition = $menu.children(selector.addition),
  2493. $existingItem = module.get.item(value),
  2494. alreadyHasValue = $existingItem && $existingItem.not(selector.addition).length,
  2495. hasUserSuggestion = $addition.length > 0,
  2496. html
  2497. ;
  2498. if(settings.useLabels && module.has.maxSelections()) {
  2499. return;
  2500. }
  2501. if(value === '' || alreadyHasValue) {
  2502. $addition.remove();
  2503. return;
  2504. }
  2505. if(hasUserSuggestion) {
  2506. $addition
  2507. .data(metadata.value, value)
  2508. .data(metadata.text, value)
  2509. .attr('data-' + metadata.value, value)
  2510. .attr('data-' + metadata.text, value)
  2511. .removeClass(className.filtered)
  2512. ;
  2513. if(!settings.hideAdditions) {
  2514. html = settings.templates.addition( module.add.variables(message.addResult, value) );
  2515. $addition
  2516. .html(html)
  2517. ;
  2518. }
  2519. module.verbose('Replacing user suggestion with new value', $addition);
  2520. }
  2521. else {
  2522. $addition = module.create.userChoice(value);
  2523. $addition
  2524. .prependTo($menu)
  2525. ;
  2526. module.verbose('Adding item choice to menu corresponding with user choice addition', $addition);
  2527. }
  2528. if(!settings.hideAdditions || module.is.allFiltered()) {
  2529. $addition
  2530. .addClass(className.selected)
  2531. .siblings()
  2532. .removeClass(className.selected)
  2533. ;
  2534. }
  2535. module.refreshItems();
  2536. },
  2537. variables: function(message, term) {
  2538. var
  2539. hasCount = (message.search('{count}') !== -1),
  2540. hasMaxCount = (message.search('{maxCount}') !== -1),
  2541. hasTerm = (message.search('{term}') !== -1),
  2542. values,
  2543. count,
  2544. query
  2545. ;
  2546. module.verbose('Adding templated variables to message', message);
  2547. if(hasCount) {
  2548. count = module.get.selectionCount();
  2549. message = message.replace('{count}', count);
  2550. }
  2551. if(hasMaxCount) {
  2552. count = module.get.selectionCount();
  2553. message = message.replace('{maxCount}', settings.maxSelections);
  2554. }
  2555. if(hasTerm) {
  2556. query = term || module.get.query();
  2557. message = message.replace('{term}', query);
  2558. }
  2559. return message;
  2560. },
  2561. value: function(addedValue, addedText, $selectedItem) {
  2562. var
  2563. currentValue = module.get.values(),
  2564. newValue
  2565. ;
  2566. if(addedValue === '') {
  2567. module.debug('Cannot select blank values from multiselect');
  2568. return;
  2569. }
  2570. // extend current array
  2571. if($.isArray(currentValue)) {
  2572. newValue = currentValue.concat([addedValue]);
  2573. newValue = module.get.uniqueArray(newValue);
  2574. }
  2575. else {
  2576. newValue = [addedValue];
  2577. }
  2578. // add values
  2579. if( module.has.selectInput() ) {
  2580. if(module.can.extendSelect()) {
  2581. module.debug('Adding value to select', addedValue, newValue, $input);
  2582. module.add.optionValue(addedValue);
  2583. }
  2584. }
  2585. else {
  2586. newValue = newValue.join(settings.delimiter);
  2587. module.debug('Setting hidden input to delimited value', newValue, $input);
  2588. }
  2589. if(settings.fireOnInit === false && module.is.initialLoad()) {
  2590. module.verbose('Skipping onadd callback on initial load', settings.onAdd);
  2591. }
  2592. else {
  2593. settings.onAdd.call(element, addedValue, addedText, $selectedItem);
  2594. }
  2595. module.set.value(newValue, addedValue, addedText, $selectedItem);
  2596. module.check.maxSelections();
  2597. }
  2598. },
  2599. remove: {
  2600. active: function() {
  2601. $module.removeClass(className.active);
  2602. },
  2603. activeLabel: function() {
  2604. $module.find(selector.label).removeClass(className.active);
  2605. },
  2606. empty: function() {
  2607. $module.removeClass(className.empty);
  2608. },
  2609. loading: function() {
  2610. $module.removeClass(className.loading);
  2611. },
  2612. initialLoad: function() {
  2613. initialLoad = false;
  2614. },
  2615. upward: function($menu) {
  2616. var $element = $menu || $module;
  2617. $element.removeClass(className.upward);
  2618. },
  2619. visible: function() {
  2620. $module.removeClass(className.visible);
  2621. },
  2622. activeItem: function() {
  2623. $item.removeClass(className.active);
  2624. },
  2625. filteredItem: function() {
  2626. if(settings.useLabels && module.has.maxSelections() ) {
  2627. return;
  2628. }
  2629. if(settings.useLabels && module.is.multiple()) {
  2630. $item.not('.' + className.active).removeClass(className.filtered);
  2631. }
  2632. else {
  2633. $item.removeClass(className.filtered);
  2634. }
  2635. module.remove.empty();
  2636. },
  2637. optionValue: function(value) {
  2638. var
  2639. escapedValue = module.escape.value(value),
  2640. $option = $input.find('option[value="' + escapedValue + '"]'),
  2641. hasOption = ($option.length > 0)
  2642. ;
  2643. if(!hasOption || !$option.hasClass(className.addition)) {
  2644. return;
  2645. }
  2646. // temporarily disconnect observer
  2647. if(selectObserver) {
  2648. selectObserver.disconnect();
  2649. module.verbose('Temporarily disconnecting mutation observer');
  2650. }
  2651. $option.remove();
  2652. module.verbose('Removing user addition as an <option>', escapedValue);
  2653. if(selectObserver) {
  2654. selectObserver.observe($input[0], {
  2655. childList : true,
  2656. subtree : true
  2657. });
  2658. }
  2659. },
  2660. message: function() {
  2661. $menu.children(selector.message).remove();
  2662. },
  2663. searchWidth: function() {
  2664. $search.css('width', '');
  2665. },
  2666. searchTerm: function() {
  2667. module.verbose('Cleared search term');
  2668. $search.val('');
  2669. module.set.filtered();
  2670. },
  2671. userAddition: function() {
  2672. $item.filter(selector.addition).remove();
  2673. },
  2674. selected: function(value, $selectedItem) {
  2675. $selectedItem = (settings.allowAdditions)
  2676. ? $selectedItem || module.get.itemWithAdditions(value)
  2677. : $selectedItem || module.get.item(value)
  2678. ;
  2679. if(!$selectedItem) {
  2680. return false;
  2681. }
  2682. $selectedItem
  2683. .each(function() {
  2684. var
  2685. $selected = $(this),
  2686. selectedText = module.get.choiceText($selected),
  2687. selectedValue = module.get.choiceValue($selected, selectedText)
  2688. ;
  2689. if(module.is.multiple()) {
  2690. if(settings.useLabels) {
  2691. module.remove.value(selectedValue, selectedText, $selected);
  2692. module.remove.label(selectedValue);
  2693. }
  2694. else {
  2695. module.remove.value(selectedValue, selectedText, $selected);
  2696. if(module.get.selectionCount() === 0) {
  2697. module.set.placeholderText();
  2698. }
  2699. else {
  2700. module.set.text(module.add.variables(message.count));
  2701. }
  2702. }
  2703. }
  2704. else {
  2705. module.remove.value(selectedValue, selectedText, $selected);
  2706. }
  2707. $selected
  2708. .removeClass(className.filtered)
  2709. .removeClass(className.active)
  2710. ;
  2711. if(settings.useLabels) {
  2712. $selected.removeClass(className.selected);
  2713. }
  2714. })
  2715. ;
  2716. },
  2717. selectedItem: function() {
  2718. $item.removeClass(className.selected);
  2719. },
  2720. value: function(removedValue, removedText, $removedItem) {
  2721. var
  2722. values = module.get.values(),
  2723. newValue
  2724. ;
  2725. if( module.has.selectInput() ) {
  2726. module.verbose('Input is <select> removing selected option', removedValue);
  2727. newValue = module.remove.arrayValue(removedValue, values);
  2728. module.remove.optionValue(removedValue);
  2729. }
  2730. else {
  2731. module.verbose('Removing from delimited values', removedValue);
  2732. newValue = module.remove.arrayValue(removedValue, values);
  2733. newValue = newValue.join(settings.delimiter);
  2734. }
  2735. if(settings.fireOnInit === false && module.is.initialLoad()) {
  2736. module.verbose('No callback on initial load', settings.onRemove);
  2737. }
  2738. else {
  2739. settings.onRemove.call(element, removedValue, removedText, $removedItem);
  2740. }
  2741. module.set.value(newValue, removedText, $removedItem);
  2742. module.check.maxSelections();
  2743. },
  2744. arrayValue: function(removedValue, values) {
  2745. if( !$.isArray(values) ) {
  2746. values = [values];
  2747. }
  2748. values = $.grep(values, function(value){
  2749. return (removedValue != value);
  2750. });
  2751. module.verbose('Removed value from delimited string', removedValue, values);
  2752. return values;
  2753. },
  2754. label: function(value, shouldAnimate) {
  2755. var
  2756. $labels = $module.find(selector.label),
  2757. $removedLabel = $labels.filter('[data-value="' + value +'"]')
  2758. ;
  2759. module.verbose('Removing label', $removedLabel);
  2760. $removedLabel.remove();
  2761. },
  2762. activeLabels: function($activeLabels) {
  2763. $activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active);
  2764. module.verbose('Removing active label selections', $activeLabels);
  2765. module.remove.labels($activeLabels);
  2766. },
  2767. labels: function($labels) {
  2768. $labels = $labels || $module.find(selector.label);
  2769. module.verbose('Removing labels', $labels);
  2770. $labels
  2771. .each(function(){
  2772. var
  2773. $label = $(this),
  2774. value = $label.data(metadata.value),
  2775. stringValue = (value !== undefined)
  2776. ? String(value)
  2777. : value,
  2778. isUserValue = module.is.userValue(stringValue)
  2779. ;
  2780. if(settings.onLabelRemove.call($label, value) === false) {
  2781. module.debug('Label remove callback cancelled removal');
  2782. return;
  2783. }
  2784. module.remove.message();
  2785. if(isUserValue) {
  2786. module.remove.value(stringValue);
  2787. module.remove.label(stringValue);
  2788. }
  2789. else {
  2790. // selected will also remove label
  2791. module.remove.selected(stringValue);
  2792. }
  2793. })
  2794. ;
  2795. },
  2796. tabbable: function() {
  2797. if( module.has.search() ) {
  2798. module.debug('Searchable dropdown initialized');
  2799. $search
  2800. .removeAttr('tabindex')
  2801. ;
  2802. $menu
  2803. .removeAttr('tabindex')
  2804. ;
  2805. }
  2806. else {
  2807. module.debug('Simple selection dropdown initialized');
  2808. $module
  2809. .removeAttr('tabindex')
  2810. ;
  2811. $menu
  2812. .removeAttr('tabindex')
  2813. ;
  2814. }
  2815. }
  2816. },
  2817. has: {
  2818. menuSearch: function() {
  2819. return (module.has.search() && $search.closest($menu).length > 0);
  2820. },
  2821. search: function() {
  2822. return ($search.length > 0);
  2823. },
  2824. sizer: function() {
  2825. return ($sizer.length > 0);
  2826. },
  2827. selectInput: function() {
  2828. return ( $input.is('select') );
  2829. },
  2830. minCharacters: function(searchTerm) {
  2831. if(settings.minCharacters) {
  2832. searchTerm = (searchTerm !== undefined)
  2833. ? String(searchTerm)
  2834. : String(module.get.query())
  2835. ;
  2836. return (searchTerm.length >= settings.minCharacters);
  2837. }
  2838. return true;
  2839. },
  2840. firstLetter: function($item, letter) {
  2841. var
  2842. text,
  2843. firstLetter
  2844. ;
  2845. if(!$item || $item.length === 0 || typeof letter !== 'string') {
  2846. return false;
  2847. }
  2848. text = module.get.choiceText($item, false);
  2849. letter = letter.toLowerCase();
  2850. firstLetter = String(text).charAt(0).toLowerCase();
  2851. return (letter == firstLetter);
  2852. },
  2853. input: function() {
  2854. return ($input.length > 0);
  2855. },
  2856. items: function() {
  2857. return ($item.length > 0);
  2858. },
  2859. menu: function() {
  2860. return ($menu.length > 0);
  2861. },
  2862. message: function() {
  2863. return ($menu.children(selector.message).length !== 0);
  2864. },
  2865. label: function(value) {
  2866. var
  2867. escapedValue = module.escape.value(value),
  2868. $labels = $module.find(selector.label)
  2869. ;
  2870. return ($labels.filter('[data-value="' + escapedValue +'"]').length > 0);
  2871. },
  2872. maxSelections: function() {
  2873. return (settings.maxSelections && module.get.selectionCount() >= settings.maxSelections);
  2874. },
  2875. allResultsFiltered: function() {
  2876. var
  2877. $normalResults = $item.not(selector.addition)
  2878. ;
  2879. return ($normalResults.filter(selector.unselectable).length === $normalResults.length);
  2880. },
  2881. userSuggestion: function() {
  2882. return ($menu.children(selector.addition).length > 0);
  2883. },
  2884. query: function() {
  2885. return (module.get.query() !== '');
  2886. },
  2887. value: function(value) {
  2888. var
  2889. values = module.get.values(),
  2890. hasValue = $.isArray(values)
  2891. ? values && ($.inArray(value, values) !== -1)
  2892. : (values == value)
  2893. ;
  2894. return (hasValue)
  2895. ? true
  2896. : false
  2897. ;
  2898. }
  2899. },
  2900. is: {
  2901. active: function() {
  2902. return $module.hasClass(className.active);
  2903. },
  2904. bubbledLabelClick: function(event) {
  2905. return $(event.target).is('select, input') && $module.closest('label').length > 0;
  2906. },
  2907. bubbledIconClick: function(event) {
  2908. return $(event.target).closest($icon).length > 0;
  2909. },
  2910. alreadySetup: function() {
  2911. return ($module.is('select') && $module.parent(selector.dropdown).length > 0 && $module.prev().length === 0);
  2912. },
  2913. animating: function($subMenu) {
  2914. return ($subMenu)
  2915. ? $subMenu.transition && $subMenu.transition('is animating')
  2916. : $menu.transition && $menu.transition('is animating')
  2917. ;
  2918. },
  2919. disabled: function() {
  2920. return $module.hasClass(className.disabled);
  2921. },
  2922. focused: function() {
  2923. return (document.activeElement === $module[0]);
  2924. },
  2925. focusedOnSearch: function() {
  2926. return (document.activeElement === $search[0]);
  2927. },
  2928. allFiltered: function() {
  2929. return( (module.is.multiple() || module.has.search()) && !(settings.hideAdditions == false && module.has.userSuggestion()) && !module.has.message() && module.has.allResultsFiltered() );
  2930. },
  2931. hidden: function($subMenu) {
  2932. return !module.is.visible($subMenu);
  2933. },
  2934. initialLoad: function() {
  2935. return initialLoad;
  2936. },
  2937. onScreen: function($subMenu) {
  2938. var
  2939. $currentMenu = $subMenu || $menu,
  2940. canOpenDownward = true,
  2941. onScreen = {},
  2942. calculations
  2943. ;
  2944. $currentMenu.addClass(className.loading);
  2945. calculations = {
  2946. context: {
  2947. scrollTop : $context.scrollTop(),
  2948. height : $context.outerHeight()
  2949. },
  2950. menu : {
  2951. offset: $currentMenu.offset(),
  2952. height: $currentMenu.outerHeight()
  2953. }
  2954. };
  2955. onScreen = {
  2956. above : (calculations.context.scrollTop) <= calculations.menu.offset.top - calculations.menu.height,
  2957. below : (calculations.context.scrollTop + calculations.context.height) >= calculations.menu.offset.top + calculations.menu.height
  2958. };
  2959. if(onScreen.below) {
  2960. module.verbose('Dropdown can fit in context downward', onScreen);
  2961. canOpenDownward = true;
  2962. }
  2963. else if(!onScreen.below && !onScreen.above) {
  2964. module.verbose('Dropdown cannot fit in either direction, favoring downward', onScreen);
  2965. canOpenDownward = true;
  2966. }
  2967. else {
  2968. module.verbose('Dropdown cannot fit below, opening upward', onScreen);
  2969. canOpenDownward = false;
  2970. }
  2971. $currentMenu.removeClass(className.loading);
  2972. return canOpenDownward;
  2973. },
  2974. inObject: function(needle, object) {
  2975. var
  2976. found = false
  2977. ;
  2978. $.each(object, function(index, property) {
  2979. if(property == needle) {
  2980. found = true;
  2981. return true;
  2982. }
  2983. });
  2984. return found;
  2985. },
  2986. multiple: function() {
  2987. return $module.hasClass(className.multiple);
  2988. },
  2989. single: function() {
  2990. return !module.is.multiple();
  2991. },
  2992. selectMutation: function(mutations) {
  2993. var
  2994. selectChanged = false
  2995. ;
  2996. $.each(mutations, function(index, mutation) {
  2997. if(mutation.target && $(mutation.target).is('select')) {
  2998. selectChanged = true;
  2999. return true;
  3000. }
  3001. });
  3002. return selectChanged;
  3003. },
  3004. search: function() {
  3005. return $module.hasClass(className.search);
  3006. },
  3007. searchSelection: function() {
  3008. return ( module.has.search() && $search.parent(selector.dropdown).length === 1 );
  3009. },
  3010. selection: function() {
  3011. return $module.hasClass(className.selection);
  3012. },
  3013. userValue: function(value) {
  3014. return ($.inArray(value, module.get.userValues()) !== -1);
  3015. },
  3016. upward: function($menu) {
  3017. var $element = $menu || $module;
  3018. return $element.hasClass(className.upward);
  3019. },
  3020. visible: function($subMenu) {
  3021. return ($subMenu)
  3022. ? $subMenu.hasClass(className.visible)
  3023. : $menu.hasClass(className.visible)
  3024. ;
  3025. }
  3026. },
  3027. can: {
  3028. activate: function($item) {
  3029. if(settings.useLabels) {
  3030. return true;
  3031. }
  3032. if(!module.has.maxSelections()) {
  3033. return true;
  3034. }
  3035. if(module.has.maxSelections() && $item.hasClass(className.active)) {
  3036. return true;
  3037. }
  3038. return false;
  3039. },
  3040. click: function() {
  3041. return (hasTouch || settings.on == 'click');
  3042. },
  3043. extendSelect: function() {
  3044. return settings.allowAdditions || settings.apiSettings;
  3045. },
  3046. show: function() {
  3047. return !module.is.disabled() && (module.has.items() || module.has.message());
  3048. },
  3049. useAPI: function() {
  3050. return $.fn.api !== undefined;
  3051. }
  3052. },
  3053. animate: {
  3054. show: function(callback, $subMenu) {
  3055. var
  3056. $currentMenu = $subMenu || $menu,
  3057. start = ($subMenu)
  3058. ? function() {}
  3059. : function() {
  3060. module.hideSubMenus();
  3061. module.hideOthers();
  3062. module.set.active();
  3063. },
  3064. transition
  3065. ;
  3066. callback = $.isFunction(callback)
  3067. ? callback
  3068. : function(){}
  3069. ;
  3070. module.verbose('Doing menu show animation', $currentMenu);
  3071. module.set.direction($subMenu);
  3072. transition = module.get.transition($subMenu);
  3073. if( module.is.selection() ) {
  3074. module.set.scrollPosition(module.get.selectedItem(), true);
  3075. }
  3076. if( module.is.hidden($currentMenu) || module.is.animating($currentMenu) ) {
  3077. if(transition == 'none') {
  3078. start();
  3079. $currentMenu.transition('show');
  3080. callback.call(element);
  3081. }
  3082. else if($.fn.transition !== undefined && $module.transition('is supported')) {
  3083. $currentMenu
  3084. .transition({
  3085. animation : transition + ' in',
  3086. debug : settings.debug,
  3087. verbose : settings.verbose,
  3088. duration : settings.duration,
  3089. queue : true,
  3090. onStart : start,
  3091. onComplete : function() {
  3092. callback.call(element);
  3093. }
  3094. })
  3095. ;
  3096. }
  3097. else {
  3098. module.error(error.noTransition, transition);
  3099. }
  3100. }
  3101. },
  3102. hide: function(callback, $subMenu) {
  3103. var
  3104. $currentMenu = $subMenu || $menu,
  3105. duration = ($subMenu)
  3106. ? (settings.duration * 0.9)
  3107. : settings.duration,
  3108. start = ($subMenu)
  3109. ? function() {}
  3110. : function() {
  3111. if( module.can.click() ) {
  3112. module.unbind.intent();
  3113. }
  3114. module.remove.active();
  3115. },
  3116. transition = module.get.transition($subMenu)
  3117. ;
  3118. callback = $.isFunction(callback)
  3119. ? callback
  3120. : function(){}
  3121. ;
  3122. if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) {
  3123. module.verbose('Doing menu hide animation', $currentMenu);
  3124. if(transition == 'none') {
  3125. start();
  3126. $currentMenu.transition('hide');
  3127. callback.call(element);
  3128. }
  3129. else if($.fn.transition !== undefined && $module.transition('is supported')) {
  3130. $currentMenu
  3131. .transition({
  3132. animation : transition + ' out',
  3133. duration : settings.duration,
  3134. debug : settings.debug,
  3135. verbose : settings.verbose,
  3136. queue : true,
  3137. onStart : start,
  3138. onComplete : function() {
  3139. if(settings.direction == 'auto') {
  3140. module.remove.upward($subMenu);
  3141. }
  3142. callback.call(element);
  3143. }
  3144. })
  3145. ;
  3146. }
  3147. else {
  3148. module.error(error.transition);
  3149. }
  3150. }
  3151. }
  3152. },
  3153. hideAndClear: function() {
  3154. module.remove.searchTerm();
  3155. if( module.has.maxSelections() ) {
  3156. return;
  3157. }
  3158. if(module.has.search()) {
  3159. module.hide(function() {
  3160. module.remove.filteredItem();
  3161. });
  3162. }
  3163. else {
  3164. module.hide();
  3165. }
  3166. },
  3167. delay: {
  3168. show: function() {
  3169. module.verbose('Delaying show event to ensure user intent');
  3170. clearTimeout(module.timer);
  3171. module.timer = setTimeout(module.show, settings.delay.show);
  3172. },
  3173. hide: function() {
  3174. module.verbose('Delaying hide event to ensure user intent');
  3175. clearTimeout(module.timer);
  3176. module.timer = setTimeout(module.hide, settings.delay.hide);
  3177. }
  3178. },
  3179. escape: {
  3180. value: function(value) {
  3181. var
  3182. multipleValues = $.isArray(value),
  3183. stringValue = (typeof value === 'string'),
  3184. isUnparsable = (!stringValue && !multipleValues),
  3185. hasQuotes = (stringValue && value.search(regExp.quote) !== -1),
  3186. values = []
  3187. ;
  3188. if(!module.has.selectInput() || isUnparsable || !hasQuotes) {
  3189. return value;
  3190. }
  3191. module.debug('Encoding quote values for use in select', value);
  3192. if(multipleValues) {
  3193. $.each(value, function(index, value){
  3194. values.push(value.replace(regExp.quote, '&quot;'));
  3195. });
  3196. return values;
  3197. }
  3198. return value.replace(regExp.quote, '&quot;');
  3199. },
  3200. regExp: function(text) {
  3201. text = String(text);
  3202. return text.replace(regExp.escape, '\\$&');
  3203. }
  3204. },
  3205. setting: function(name, value) {
  3206. module.debug('Changing setting', name, value);
  3207. if( $.isPlainObject(name) ) {
  3208. $.extend(true, settings, name);
  3209. }
  3210. else if(value !== undefined) {
  3211. if($.isPlainObject(settings[name])) {
  3212. $.extend(true, settings[name], value);
  3213. }
  3214. else {
  3215. settings[name] = value;
  3216. }
  3217. }
  3218. else {
  3219. return settings[name];
  3220. }
  3221. },
  3222. internal: function(name, value) {
  3223. if( $.isPlainObject(name) ) {
  3224. $.extend(true, module, name);
  3225. }
  3226. else if(value !== undefined) {
  3227. module[name] = value;
  3228. }
  3229. else {
  3230. return module[name];
  3231. }
  3232. },
  3233. debug: function() {
  3234. if(!settings.silent && settings.debug) {
  3235. if(settings.performance) {
  3236. module.performance.log(arguments);
  3237. }
  3238. else {
  3239. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  3240. module.debug.apply(console, arguments);
  3241. }
  3242. }
  3243. },
  3244. verbose: function() {
  3245. if(!settings.silent && settings.verbose && settings.debug) {
  3246. if(settings.performance) {
  3247. module.performance.log(arguments);
  3248. }
  3249. else {
  3250. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  3251. module.verbose.apply(console, arguments);
  3252. }
  3253. }
  3254. },
  3255. error: function() {
  3256. if(!settings.silent) {
  3257. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  3258. module.error.apply(console, arguments);
  3259. }
  3260. },
  3261. performance: {
  3262. log: function(message) {
  3263. var
  3264. currentTime,
  3265. executionTime,
  3266. previousTime
  3267. ;
  3268. if(settings.performance) {
  3269. currentTime = new Date().getTime();
  3270. previousTime = time || currentTime;
  3271. executionTime = currentTime - previousTime;
  3272. time = currentTime;
  3273. performance.push({
  3274. 'Name' : message[0],
  3275. 'Arguments' : [].slice.call(message, 1) || '',
  3276. 'Element' : element,
  3277. 'Execution Time' : executionTime
  3278. });
  3279. }
  3280. clearTimeout(module.performance.timer);
  3281. module.performance.timer = setTimeout(module.performance.display, 500);
  3282. },
  3283. display: function() {
  3284. var
  3285. title = settings.name + ':',
  3286. totalTime = 0
  3287. ;
  3288. time = false;
  3289. clearTimeout(module.performance.timer);
  3290. $.each(performance, function(index, data) {
  3291. totalTime += data['Execution Time'];
  3292. });
  3293. title += ' ' + totalTime + 'ms';
  3294. if(moduleSelector) {
  3295. title += ' \'' + moduleSelector + '\'';
  3296. }
  3297. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  3298. console.groupCollapsed(title);
  3299. if(console.table) {
  3300. console.table(performance);
  3301. }
  3302. else {
  3303. $.each(performance, function(index, data) {
  3304. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  3305. });
  3306. }
  3307. console.groupEnd();
  3308. }
  3309. performance = [];
  3310. }
  3311. },
  3312. invoke: function(query, passedArguments, context) {
  3313. var
  3314. object = instance,
  3315. maxDepth,
  3316. found,
  3317. response
  3318. ;
  3319. passedArguments = passedArguments || queryArguments;
  3320. context = element || context;
  3321. if(typeof query == 'string' && object !== undefined) {
  3322. query = query.split(/[\. ]/);
  3323. maxDepth = query.length - 1;
  3324. $.each(query, function(depth, value) {
  3325. var camelCaseValue = (depth != maxDepth)
  3326. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  3327. : query
  3328. ;
  3329. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  3330. object = object[camelCaseValue];
  3331. }
  3332. else if( object[camelCaseValue] !== undefined ) {
  3333. found = object[camelCaseValue];
  3334. return false;
  3335. }
  3336. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  3337. object = object[value];
  3338. }
  3339. else if( object[value] !== undefined ) {
  3340. found = object[value];
  3341. return false;
  3342. }
  3343. else {
  3344. module.error(error.method, query);
  3345. return false;
  3346. }
  3347. });
  3348. }
  3349. if ( $.isFunction( found ) ) {
  3350. response = found.apply(context, passedArguments);
  3351. }
  3352. else if(found !== undefined) {
  3353. response = found;
  3354. }
  3355. if($.isArray(returnedValue)) {
  3356. returnedValue.push(response);
  3357. }
  3358. else if(returnedValue !== undefined) {
  3359. returnedValue = [returnedValue, response];
  3360. }
  3361. else if(response !== undefined) {
  3362. returnedValue = response;
  3363. }
  3364. return found;
  3365. }
  3366. };
  3367. if(methodInvoked) {
  3368. if(instance === undefined) {
  3369. module.initialize();
  3370. }
  3371. module.invoke(query);
  3372. }
  3373. else {
  3374. if(instance !== undefined) {
  3375. instance.invoke('destroy');
  3376. }
  3377. module.initialize();
  3378. }
  3379. })
  3380. ;
  3381. return (returnedValue !== undefined)
  3382. ? returnedValue
  3383. : $allModules
  3384. ;
  3385. };
  3386. $.fn.dropdown.settings = {
  3387. silent : false,
  3388. debug : false,
  3389. verbose : false,
  3390. performance : true,
  3391. on : 'click', // what event should show menu action on item selection
  3392. action : 'activate', // action on item selection (nothing, activate, select, combo, hide, function(){})
  3393. apiSettings : false,
  3394. selectOnKeydown : true, // Whether selection should occur automatically when keyboard shortcuts used
  3395. minCharacters : 0, // Minimum characters required to trigger API call
  3396. saveRemoteData : true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh
  3397. throttle : 200, // How long to wait after last user input to search remotely
  3398. context : window, // Context to use when determining if on screen
  3399. direction : 'auto', // Whether dropdown should always open in one direction
  3400. keepOnScreen : true, // Whether dropdown should check whether it is on screen before showing
  3401. match : 'both', // what to match against with search selection (both, text, or label)
  3402. fullTextSearch : false, // search anywhere in value (set to 'exact' to require exact matches)
  3403. placeholder : 'auto', // whether to convert blank <select> values to placeholder text
  3404. preserveHTML : true, // preserve html when selecting value
  3405. sortSelect : false, // sort selection on init
  3406. forceSelection : true, // force a choice on blur with search selection
  3407. allowAdditions : false, // whether multiple select should allow user added values
  3408. hideAdditions : true, // whether or not to hide special message prompting a user they can enter a value
  3409. maxSelections : false, // When set to a number limits the number of selections to this count
  3410. useLabels : true, // whether multiple select should filter currently active selections from choices
  3411. delimiter : ',', // when multiselect uses normal <input> the values will be delimited with this character
  3412. showOnFocus : true, // show menu on focus
  3413. allowReselection : false, // whether current value should trigger callbacks when reselected
  3414. allowTab : true, // add tabindex to element
  3415. allowCategorySelection : false, // allow elements with sub-menus to be selected
  3416. fireOnInit : false, // Whether callbacks should fire when initializing dropdown values
  3417. transition : 'auto', // auto transition will slide down or up based on direction
  3418. duration : 200, // duration of transition
  3419. glyphWidth : 1.037, // widest glyph width in em (W is 1.037 em) used to calculate multiselect input width
  3420. // label settings on multi-select
  3421. label: {
  3422. transition : 'scale',
  3423. duration : 200,
  3424. variation : false
  3425. },
  3426. // delay before event
  3427. delay : {
  3428. hide : 300,
  3429. show : 200,
  3430. search : 20,
  3431. touch : 50
  3432. },
  3433. /* Callbacks */
  3434. onChange : function(value, text, $selected){},
  3435. onAdd : function(value, text, $selected){},
  3436. onRemove : function(value, text, $selected){},
  3437. onLabelSelect : function($selectedLabels){},
  3438. onLabelCreate : function(value, text) { return $(this); },
  3439. onLabelRemove : function(value) { return true; },
  3440. onNoResults : function(searchTerm) { return true; },
  3441. onShow : function(){},
  3442. onHide : function(){},
  3443. /* Component */
  3444. name : 'Dropdown',
  3445. namespace : 'dropdown',
  3446. message: {
  3447. addResult : 'Add <b>{term}</b>',
  3448. count : '{count} selected',
  3449. maxSelections : 'Max {maxCount} selections',
  3450. noResults : 'No results found.',
  3451. serverError : 'There was an error contacting the server'
  3452. },
  3453. error : {
  3454. action : 'You called a dropdown action that was not defined',
  3455. alreadySetup : 'Once a select has been initialized behaviors must be called on the created ui dropdown',
  3456. labels : 'Allowing user additions currently requires the use of labels.',
  3457. missingMultiple : '<select> requires multiple property to be set to correctly preserve multiple values',
  3458. method : 'The method you called is not defined.',
  3459. noAPI : 'The API module is required to load resources remotely',
  3460. noStorage : 'Saving remote data requires session storage',
  3461. noTransition : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>'
  3462. },
  3463. regExp : {
  3464. escape : /[-[\]{}()*+?.,\\^$|#\s]/g,
  3465. quote : /"/g
  3466. },
  3467. metadata : {
  3468. defaultText : 'defaultText',
  3469. defaultValue : 'defaultValue',
  3470. placeholderText : 'placeholder',
  3471. text : 'text',
  3472. value : 'value'
  3473. },
  3474. // property names for remote query
  3475. fields: {
  3476. remoteValues : 'results', // grouping for api results
  3477. values : 'values', // grouping for all dropdown values
  3478. disabled : 'disabled', // whether value should be disabled
  3479. name : 'name', // displayed dropdown text
  3480. value : 'value', // actual dropdown value
  3481. text : 'text' // displayed text when selected
  3482. },
  3483. keys : {
  3484. backspace : 8,
  3485. delimiter : 188, // comma
  3486. deleteKey : 46,
  3487. enter : 13,
  3488. escape : 27,
  3489. pageUp : 33,
  3490. pageDown : 34,
  3491. leftArrow : 37,
  3492. upArrow : 38,
  3493. rightArrow : 39,
  3494. downArrow : 40
  3495. },
  3496. selector : {
  3497. addition : '.addition',
  3498. dropdown : '.ui.dropdown',
  3499. hidden : '.hidden',
  3500. icon : '> .dropdown.icon',
  3501. input : '> input[type="hidden"], > select',
  3502. item : '.item',
  3503. label : '> .label',
  3504. remove : '> .label > .delete.icon',
  3505. siblingLabel : '.label',
  3506. menu : '.menu',
  3507. message : '.message',
  3508. menuIcon : '.dropdown.icon',
  3509. search : 'input.search, .menu > .search > input, .menu input.search',
  3510. sizer : '> input.sizer',
  3511. text : '> .text:not(.icon)',
  3512. unselectable : '.disabled, .filtered'
  3513. },
  3514. className : {
  3515. active : 'active',
  3516. addition : 'addition',
  3517. animating : 'animating',
  3518. disabled : 'disabled',
  3519. empty : 'empty',
  3520. dropdown : 'ui dropdown',
  3521. filtered : 'filtered',
  3522. hidden : 'hidden transition',
  3523. item : 'item',
  3524. label : 'ui label',
  3525. loading : 'loading',
  3526. menu : 'menu',
  3527. message : 'message',
  3528. multiple : 'multiple',
  3529. placeholder : 'default',
  3530. sizer : 'sizer',
  3531. search : 'search',
  3532. selected : 'selected',
  3533. selection : 'selection',
  3534. upward : 'upward',
  3535. visible : 'visible'
  3536. }
  3537. };
  3538. /* Templates */
  3539. $.fn.dropdown.settings.templates = {
  3540. // generates dropdown from select values
  3541. dropdown: function(select) {
  3542. var
  3543. placeholder = select.placeholder || false,
  3544. values = select.values || {},
  3545. html = ''
  3546. ;
  3547. html += '<i class="dropdown icon"></i>';
  3548. if(select.placeholder) {
  3549. html += '<div class="default text">' + placeholder + '</div>';
  3550. }
  3551. else {
  3552. html += '<div class="text"></div>';
  3553. }
  3554. html += '<div class="menu">';
  3555. $.each(select.values, function(index, option) {
  3556. html += (option.disabled)
  3557. ? '<div class="disabled item" data-value="' + option.value + '">' + option.name + '</div>'
  3558. : '<div class="item" data-value="' + option.value + '">' + option.name + '</div>'
  3559. ;
  3560. });
  3561. html += '</div>';
  3562. return html;
  3563. },
  3564. // generates just menu from select
  3565. menu: function(response, fields) {
  3566. var
  3567. values = response[fields.values] || {},
  3568. html = ''
  3569. ;
  3570. $.each(values, function(index, option) {
  3571. var
  3572. maybeText = (option[fields.text])
  3573. ? 'data-text="' + option[fields.text] + '"'
  3574. : '',
  3575. maybeDisabled = (option[fields.disabled])
  3576. ? 'disabled '
  3577. : ''
  3578. ;
  3579. html += '<div class="'+ maybeDisabled +'item" data-value="' + option[fields.value] + '"' + maybeText + '>'
  3580. html += option[fields.name];
  3581. html += '</div>';
  3582. });
  3583. return html;
  3584. },
  3585. // generates label for multiselect
  3586. label: function(value, text) {
  3587. return text + '<i class="delete icon"></i>';
  3588. },
  3589. // generates messages like "No results"
  3590. message: function(message) {
  3591. return message;
  3592. },
  3593. // generates user addition to selection menu
  3594. addition: function(choice) {
  3595. return choice;
  3596. }
  3597. };
  3598. })( jQuery, window, document );