dropdown.js 138 KB

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