dropdown.js 149 KB

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