You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

semanticDropdown.js 155KB

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