dropdown.js 151 KB

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