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

2189 lines
72 KiB

9 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
9 years ago
10 years ago
9 years ago
9 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
9 years ago
9 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
9 years ago
9 years ago
10 years ago
10 years ago
9 years ago
9 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
9 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
9 years ago
10 years ago
  1. /*!
  2. * # Semantic UI 2.0.0 - Dropdown
  3. * http://github.com/semantic-org/semantic-ui/
  4. *
  5. *
  6. * Copyright 2014 Contributors
  7. * Released under the MIT license
  8. * http://opensource.org/licenses/MIT
  9. *
  10. */
  11. ;(function ( $, window, document, undefined ) {
  12. "use strict";
  13. $.fn.dropdown = function(parameters) {
  14. var
  15. $allModules = $(this),
  16. $document = $(document),
  17. moduleSelector = $allModules.selector || '',
  18. hasTouch = ('ontouchstart' in document.documentElement),
  19. time = new Date().getTime(),
  20. performance = [],
  21. query = arguments[0],
  22. methodInvoked = (typeof query == 'string'),
  23. queryArguments = [].slice.call(arguments, 1),
  24. returnedValue
  25. ;
  26. $allModules
  27. .each(function() {
  28. var
  29. settings = ( $.isPlainObject(parameters) )
  30. ? $.extend(true, {}, $.fn.dropdown.settings, parameters)
  31. : $.extend({}, $.fn.dropdown.settings),
  32. className = settings.className,
  33. metadata = settings.metadata,
  34. namespace = settings.namespace,
  35. selector = settings.selector,
  36. error = settings.error,
  37. templates = settings.templates,
  38. eventNamespace = '.' + namespace,
  39. moduleNamespace = 'module-' + namespace,
  40. $module = $(this),
  41. $text = $module.find(selector.text),
  42. $search = $module.find(selector.search),
  43. $input = $module.find(selector.input),
  44. $icon = $module.find(selector.icon),
  45. $combo = ($module.prev().find(selector.text).length > 0)
  46. ? $module.prev().find(selector.text)
  47. : $module.prev(),
  48. $menu = $module.children(selector.menu),
  49. $item = $menu.find(selector.item),
  50. activated = false,
  51. itemActivated = false,
  52. element = this,
  53. instance = $module.data(moduleNamespace),
  54. elementNamespace,
  55. id,
  56. selectObserver,
  57. menuObserver,
  58. module
  59. ;
  60. module = {
  61. initialize: function() {
  62. module.debug('Initializing dropdown', settings);
  63. if( module.is.alreadySetup() ) {
  64. module.setup.reference();
  65. }
  66. else {
  67. module.setup.layout();
  68. module.save.defaults();
  69. module.set.selected();
  70. module.create.id();
  71. if(hasTouch) {
  72. module.bind.touchEvents();
  73. }
  74. module.bind.mouseEvents();
  75. module.bind.keyboardEvents();
  76. module.observeChanges();
  77. module.instantiate();
  78. }
  79. },
  80. instantiate: function() {
  81. module.verbose('Storing instance of dropdown', module);
  82. instance = module;
  83. $module
  84. .data(moduleNamespace, module)
  85. ;
  86. },
  87. destroy: function() {
  88. module.verbose('Destroying previous dropdown for', $module);
  89. module.remove.tabbable();
  90. $module
  91. .off(eventNamespace)
  92. .removeData(moduleNamespace)
  93. ;
  94. $menu
  95. .off(eventNamespace)
  96. ;
  97. $document
  98. .off(elementNamespace)
  99. ;
  100. if(selectObserver) {
  101. selectObserver.disconnect();
  102. }
  103. if(menuObserver) {
  104. menuObserver.disconnect();
  105. }
  106. },
  107. observeChanges: function() {
  108. if('MutationObserver' in window) {
  109. selectObserver = new MutationObserver(function(mutations) {
  110. module.debug('<select> modified, recreating menu');
  111. module.setup.select();
  112. });
  113. menuObserver = new MutationObserver(function(mutations) {
  114. module.debug('Menu modified, updating selector cache');
  115. module.refresh();
  116. });
  117. if(module.has.input()) {
  118. selectObserver.observe($input[0], {
  119. childList : true,
  120. subtree : true
  121. });
  122. }
  123. if(module.has.menu()) {
  124. menuObserver.observe($menu[0], {
  125. childList : true,
  126. subtree : true
  127. });
  128. }
  129. module.debug('Setting up mutation observer', selectObserver, menuObserver);
  130. }
  131. },
  132. create: {
  133. id: function() {
  134. id = (Math.random().toString(16) + '000000000').substr(2, 8);
  135. elementNamespace = '.' + id;
  136. module.verbose('Creating unique id for element', id);
  137. }
  138. },
  139. search: function() {
  140. var
  141. query
  142. ;
  143. query = $search.val();
  144. module.verbose('Searching for query', query);
  145. module.filter(query);
  146. if(module.is.searchSelection() && module.can.show() ) {
  147. module.show();
  148. }
  149. },
  150. setup: {
  151. layout: function() {
  152. if( $module.is('select') ) {
  153. module.setup.select();
  154. }
  155. if( module.is.search() && !module.has.search() ) {
  156. $search = $('<input />')
  157. .addClass(className.search)
  158. .insertBefore($text)
  159. ;
  160. }
  161. if(settings.allowTab) {
  162. module.set.tabbable();
  163. }
  164. },
  165. select: function() {
  166. var
  167. selectValues = module.get.selectValues()
  168. ;
  169. module.debug('Dropdown initialized on a select', selectValues);
  170. if( $module.is('select') ) {
  171. $input = $module;
  172. }
  173. // see if select is placed correctly already
  174. if($input.parent(selector.dropdown).length > 0) {
  175. module.debug('UI dropdown already exists. Creating dropdown menu only');
  176. $module = $input.closest(selector.dropdown);
  177. $menu = $module.children(selector.menu);
  178. if($menu.length === 0) {
  179. $menu = $('<div />')
  180. .addClass(className.menu)
  181. .appendTo($module)
  182. ;
  183. }
  184. $menu.html( templates.menu( selectValues ));
  185. }
  186. else {
  187. module.debug('Creating entire dropdown from select');
  188. $module = $('<div />')
  189. .attr('class', $input.attr('class') )
  190. .addClass(className.selection)
  191. .addClass(className.dropdown)
  192. .html( templates.dropdown(selectValues) )
  193. .insertBefore($input)
  194. ;
  195. $input
  196. .removeAttr('class')
  197. .detach()
  198. .prependTo($module)
  199. ;
  200. }
  201. if($input.is('[multiple]')) {
  202. module.set.multiple();
  203. }
  204. module.refresh();
  205. },
  206. reference: function() {
  207. var
  208. index = $allModules.index($module),
  209. $firstModules,
  210. $lastModules
  211. ;
  212. module.debug('Dropdown behavior was called on select, replacing with closest dropdown');
  213. // replace module reference
  214. $module = $module.parent(selector.dropdown);
  215. module.refresh();
  216. // adjust all modules
  217. $firstModules = $allModules.slice(0, index);
  218. $lastModules = $allModules.slice(index + 1);
  219. $allModules = $firstModules.add($module).add($lastModules);
  220. }
  221. },
  222. refresh: function() {
  223. module.verbose('Refreshing selector cache');
  224. $text = $module.find(selector.text);
  225. $search = $module.find(selector.search);
  226. $input = $module.find(selector.input);
  227. $icon = $module.find(selector.icon);
  228. $combo = ($module.prev().find(selector.text).length > 0)
  229. ? $module.prev().find(selector.text)
  230. : $module.prev()
  231. ;
  232. $menu = $module.children(selector.menu);
  233. $item = $menu.find(selector.item);
  234. },
  235. toggle: function() {
  236. module.verbose('Toggling menu visibility');
  237. if( !module.is.active() ) {
  238. module.show();
  239. }
  240. else {
  241. module.hide();
  242. }
  243. },
  244. show: function(callback) {
  245. callback = $.isFunction(callback)
  246. ? callback
  247. : function(){}
  248. ;
  249. if( module.is.searchSelection() && module.is.allFiltered() ) {
  250. return;
  251. }
  252. if( module.can.show() && !module.is.active() ) {
  253. module.debug('Showing dropdown');
  254. if(module.is.multiple()) {
  255. module.filterActive();
  256. }
  257. module.animate.show(function() {
  258. if( module.can.click() ) {
  259. module.bind.intent();
  260. }
  261. module.set.visible();
  262. callback.call(element);
  263. });
  264. settings.onShow.call(element);
  265. }
  266. },
  267. hide: function(callback) {
  268. callback = $.isFunction(callback)
  269. ? callback
  270. : function(){}
  271. ;
  272. if( module.is.active() ) {
  273. module.debug('Hiding dropdown');
  274. module.animate.hide(function() {
  275. module.remove.visible();
  276. callback.call(element);
  277. });
  278. settings.onHide.call(element);
  279. }
  280. },
  281. hideOthers: function() {
  282. module.verbose('Finding other dropdowns to hide');
  283. $allModules
  284. .not($module)
  285. .has(selector.menu + ':visible:not(.' + className.animating + ')')
  286. .dropdown('hide')
  287. ;
  288. },
  289. hideSubMenus: function() {
  290. var
  291. $subMenus = $menu.children(selector.item).find(selector.menu)
  292. ;
  293. $subMenus.transition('hide');
  294. },
  295. bind: {
  296. keyboardEvents: function() {
  297. module.debug('Binding keyboard events');
  298. $module
  299. .on('keydown' + eventNamespace, module.event.keydown)
  300. ;
  301. if( module.has.search() ) {
  302. $module
  303. .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input)
  304. ;
  305. }
  306. if( module.is.multiple() ) {
  307. $document
  308. .on('keydown' + elementNamespace, module.event.document.keydown)
  309. ;
  310. }
  311. },
  312. touchEvents: function() {
  313. module.debug('Touch device detected binding additional touch events');
  314. if( module.is.searchSelection() ) {
  315. // do nothing special yet
  316. }
  317. else {
  318. $module
  319. .on('touchstart' + eventNamespace, module.event.test.toggle)
  320. ;
  321. }
  322. $menu
  323. .on('touchstart' + eventNamespace, selector.item, module.event.item.mouseenter)
  324. ;
  325. },
  326. mouseEvents: function() {
  327. module.verbose('Mouse detected binding mouse events');
  328. if( module.is.searchSelection() ) {
  329. $module
  330. .on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown)
  331. .on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup)
  332. .on('click' + eventNamespace, selector.search, module.show)
  333. .on('focus' + eventNamespace, selector.search, module.event.search.focus)
  334. .on('blur' + eventNamespace, selector.search, module.event.search.blur)
  335. .on('click' + eventNamespace, selector.text, module.event.text.focus)
  336. ;
  337. if(module.is.multiple()) {
  338. $module
  339. .on('click' + eventNamespace, module.event.click)
  340. .on('click' + eventNamespace, selector.label, module.event.label.click)
  341. .on('click' + eventNamespace, selector.remove, module.event.remove.click)
  342. ;
  343. }
  344. }
  345. else {
  346. if(settings.on == 'click') {
  347. $module
  348. .on('click' + eventNamespace, module.event.test.toggle)
  349. ;
  350. }
  351. else if(settings.on == 'hover') {
  352. $module
  353. .on('mouseenter' + eventNamespace, module.delay.show)
  354. .on('mouseleave' + eventNamespace, module.delay.hide)
  355. ;
  356. }
  357. else {
  358. $module
  359. .on(settings.on + eventNamespace, module.toggle)
  360. ;
  361. }
  362. $module
  363. .on('mousedown' + eventNamespace, module.event.mousedown)
  364. .on('mouseup' + eventNamespace, module.event.mouseup)
  365. .on('focus' + eventNamespace, module.event.focus)
  366. .on('blur' + eventNamespace, module.event.blur)
  367. ;
  368. }
  369. $menu
  370. .on('mouseenter' + eventNamespace, selector.item, module.event.item.mouseenter)
  371. .on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave)
  372. .on('click' + eventNamespace, selector.item, module.event.item.click)
  373. ;
  374. },
  375. intent: function() {
  376. module.verbose('Binding hide intent event to document');
  377. if(hasTouch) {
  378. $document
  379. .on('touchstart' + elementNamespace, module.event.test.touch)
  380. .on('touchmove' + elementNamespace, module.event.test.touch)
  381. ;
  382. }
  383. $document
  384. .on('click' + elementNamespace, module.event.test.hide)
  385. ;
  386. }
  387. },
  388. unbind: {
  389. intent: function() {
  390. module.verbose('Removing hide intent event from document');
  391. if(hasTouch) {
  392. $document
  393. .off('touchstart' + elementNamespace)
  394. .off('touchmove' + elementNamespace)
  395. ;
  396. }
  397. $document
  398. .off('click' + elementNamespace)
  399. ;
  400. }
  401. },
  402. filter: function(searchTerm) {
  403. var
  404. $results = $(),
  405. escapedTerm = module.escape.regExp(searchTerm),
  406. exactRegExp = new RegExp('^' + escapedTerm, 'igm'),
  407. fullTextRegExp = new RegExp(escapedTerm, 'ig'),
  408. allItemsFiltered
  409. ;
  410. module.verbose('Searching for matching values');
  411. $item
  412. .each(function(){
  413. var
  414. $choice = $(this),
  415. text = String(module.get.choiceText($choice, false)),
  416. value = String(module.get.choiceValue($choice, text))
  417. ;
  418. if( text.match(exactRegExp) || value.match(exactRegExp) ) {
  419. $results = $results.add($choice);
  420. }
  421. else if(settings.fullTextSearch) {
  422. if( text.match(fullTextRegExp) || value.match(fullTextRegExp) ) {
  423. $results = $results.add($choice);
  424. }
  425. }
  426. })
  427. ;
  428. module.debug('Setting filter', searchTerm);
  429. module.remove.filteredItem();
  430. $item
  431. .not($results)
  432. .addClass(className.filtered)
  433. ;
  434. if(module.is.multiple()) {
  435. module.filterActive();
  436. }
  437. module.verbose('Selecting first non-filtered element');
  438. module.remove.selectedItem();
  439. $item
  440. .not('.' + className.filtered)
  441. .eq(0)
  442. .addClass(className.selected)
  443. ;
  444. if( module.is.allFiltered() ) {
  445. module.debug('All items filtered, hiding dropdown', searchTerm);
  446. if(module.is.searchSelection()) {
  447. module.hide();
  448. }
  449. settings.onNoResults.call(element, searchTerm);
  450. }
  451. },
  452. filterActive: function() {
  453. $item.filter('.' + className.active)
  454. .addClass(className.filtered)
  455. ;
  456. },
  457. focusSearch: function() {
  458. if( module.is.search() && !module.is.focusedOnSearch() ) {
  459. $search
  460. .focus()
  461. ;
  462. }
  463. },
  464. forceSelection: function() {
  465. var
  466. $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0),
  467. $activeItem = $item.filter('.' + className.active).eq(0),
  468. $selectedItem = ($currentlySelected.length > 0)
  469. ? $currentlySelected
  470. : $activeItem,
  471. hasSelected = ($selectedItem.size() > 0)
  472. ;
  473. if(hasSelected) {
  474. module.debug('Forcing partial selection to selected item', $selectedItem);
  475. module.event.item.click.call($selectedItem);
  476. module.remove.filteredItem();
  477. }
  478. else {
  479. module.hide();
  480. }
  481. },
  482. event: {
  483. focus: function() {
  484. if(!activated && module.is.hidden()) {
  485. module.show();
  486. }
  487. },
  488. click: function(event) {
  489. var
  490. $target = $(event.target)
  491. ;
  492. // focus search
  493. if(($target.is($module) || $target.is($icon)) && !module.is.focusedOnSearch()) {
  494. $search.focus();
  495. }
  496. },
  497. blur: function(event) {
  498. var
  499. pageLostFocus = (document.activeElement === this)
  500. ;
  501. if(!activated && !pageLostFocus) {
  502. module.hide();
  503. }
  504. },
  505. // prevents focus callback from occuring on mousedown
  506. mousedown: function() {
  507. activated = true;
  508. },
  509. mouseup: function() {
  510. activated = false;
  511. },
  512. search: {
  513. focus: function() {
  514. activated = true;
  515. module.show();
  516. },
  517. blur: function(event) {
  518. var
  519. pageLostFocus = (document.activeElement === this)
  520. ;
  521. if(!itemActivated && !pageLostFocus) {
  522. if(module.is.multiple()) {
  523. module.remove.activeLabel();
  524. }
  525. else if(settings.forceSelection) {
  526. module.forceSelection();
  527. }
  528. else {
  529. module.hide();
  530. }
  531. }
  532. }
  533. },
  534. text: {
  535. focus: function(event) {
  536. activated = true;
  537. $search.focus();
  538. }
  539. },
  540. input: function(event) {
  541. if(module.is.multiple() || module.is.searchSelection()) {
  542. module.set.filtered();
  543. }
  544. clearTimeout(module.timer);
  545. module.timer = setTimeout(module.search, settings.delay.search);
  546. },
  547. label: {
  548. click: function(event) {
  549. var
  550. $label = $(this),
  551. $labels = $module.find(selector.label),
  552. $activeLabels = $labels.filter('.' + className.active),
  553. $nextActive = $label.nextAll('.' + className.active),
  554. $prevActive = $label.prevAll('.' + className.active),
  555. $range = ($nextActive.length > 0)
  556. ? $label.nextUntil($nextActive).add($activeLabels).add($label)
  557. : $label.prevUntil($prevActive).add($activeLabels).add($label)
  558. ;
  559. ;
  560. if(event.shiftKey) {
  561. $activeLabels.removeClass(className.active);
  562. $range.addClass(className.active);
  563. }
  564. else if(event.ctrlKey) {
  565. $label.toggleClass(className.active);
  566. }
  567. else {
  568. $activeLabels.removeClass(className.active);
  569. $label.addClass(className.active);
  570. }
  571. settings.onLabelClick.apply(this, $labels.filter('.' + className.active));
  572. }
  573. },
  574. remove: {
  575. click: function() {
  576. var
  577. $label = $(this).parent()
  578. ;
  579. if( $label.hasClass(className.active) ) {
  580. // remove all selected labels
  581. module.remove.labels();
  582. }
  583. else {
  584. // remove this label only
  585. module.remove.labels( $label );
  586. }
  587. }
  588. },
  589. test: {
  590. toggle: function(event) {
  591. if( module.determine.eventInMenu(event, module.toggle) ) {
  592. event.preventDefault();
  593. }
  594. },
  595. touch: function(event) {
  596. module.determine.eventInMenu(event, function() {
  597. if(event.type == 'touchstart') {
  598. module.timer = setTimeout(module.hide, settings.delay.touch);
  599. }
  600. else if(event.type == 'touchmove') {
  601. clearTimeout(module.timer);
  602. }
  603. });
  604. event.stopPropagation();
  605. },
  606. hide: function(event) {
  607. module.determine.eventInModule(event, module.hide);
  608. }
  609. },
  610. menu: {
  611. mousedown: function() {
  612. itemActivated = true;
  613. },
  614. mouseup: function() {
  615. itemActivated = false;
  616. }
  617. },
  618. item: {
  619. mouseenter: function(event) {
  620. var
  621. $subMenu = $(this).children(selector.menu),
  622. $otherMenus = $(this).siblings(selector.item).children(selector.menu)
  623. ;
  624. if( $subMenu.length > 0 ) {
  625. clearTimeout(module.itemTimer);
  626. module.itemTimer = setTimeout(function() {
  627. module.verbose('Showing sub-menu', $subMenu);
  628. $.each($otherMenus, function() {
  629. module.animate.hide(false, $(this));
  630. });
  631. module.animate.show(false, $subMenu);
  632. }, settings.delay.show);
  633. event.preventDefault();
  634. }
  635. },
  636. mouseleave: function(event) {
  637. var
  638. $subMenu = $(this).children(selector.menu)
  639. ;
  640. if($subMenu.length > 0) {
  641. clearTimeout(module.itemTimer);
  642. module.itemTimer = setTimeout(function() {
  643. module.verbose('Hiding sub-menu', $subMenu);
  644. module.animate.hide(false, $subMenu);
  645. }, settings.delay.hide);
  646. }
  647. },
  648. click: function (event) {
  649. var
  650. $choice = $(this),
  651. $target = (event)
  652. ? $(event.target)
  653. : $(''),
  654. $subMenu = $choice.find(selector.menu),
  655. text = module.get.choiceText($choice),
  656. value = module.get.choiceValue($choice, text),
  657. callback = function() {
  658. module.remove.searchTerm();
  659. module.determine.selectAction(text, value);
  660. },
  661. hasSubMenu = ($subMenu.length > 0),
  662. isBubbledEvent = ($subMenu.find($target).length > 0)
  663. ;
  664. if(!isBubbledEvent && (!hasSubMenu || settings.allowCategorySelection)) {
  665. callback();
  666. }
  667. }
  668. },
  669. document: {
  670. // label selection should occur even when element has no focus
  671. keydown: function(event) {
  672. var
  673. pressedKey = event.which,
  674. keys = module.get.shortcutKeys(),
  675. isShortcutKey = module.is.inObject(pressedKey, keys)
  676. ;
  677. if(isShortcutKey) {
  678. var
  679. $label = $module.find(selector.label),
  680. $activeLabel = $label.filter('.' + className.active),
  681. activeValue = $activeLabel.data('value'),
  682. labelIndex = $label.index($activeLabel),
  683. labelCount = $label.length,
  684. hasActiveLabel = ($activeLabel.length > 0),
  685. isFirstLabel = (labelIndex == 0),
  686. isLastLabel = (labelIndex + 1 == labelCount),
  687. isFocusedOnSearch = module.is.focusedOnSearch(),
  688. caretAtStart = (isFocusedOnSearch && module.get.caretPosition() == 0)
  689. ;
  690. if(isFocusedOnSearch && (pressedKey == keys.delimiter)) {
  691. // tokenize on comma
  692. if(module.is.visible()) {
  693. module.event.item.click.call($selectedItem, event);
  694. event.preventDefault();
  695. }
  696. }
  697. else if(pressedKey == keys.leftArrow) {
  698. // activate previous label
  699. if(caretAtStart && !hasActiveLabel) {
  700. $label.last().addClass(className.active);
  701. }
  702. else if(hasActiveLabel && !isFirstLabel) {
  703. if(!event.shiftKey) {
  704. $label.removeClass(className.active)
  705. }
  706. $activeLabel.prev()
  707. .addClass(className.active)
  708. .end()
  709. ;
  710. event.preventDefault();
  711. }
  712. }
  713. else if(pressedKey == keys.rightArrow) {
  714. // activate next label
  715. if(hasActiveLabel) {
  716. if(!event.shiftKey) {
  717. $label.removeClass(className.active)
  718. }
  719. $activeLabel.next()
  720. .addClass(className.active)
  721. .end()
  722. ;
  723. event.preventDefault();
  724. }
  725. }
  726. else if(pressedKey == keys.deleteKey || pressedKey == keys.backspace) {
  727. if(hasActiveLabel) {
  728. $activeLabel.last().next().addClass(className.active);
  729. module.remove.labels($activeLabel);
  730. }
  731. else if(caretAtStart && !hasActiveLabel && pressedKey == keys.backspace) {
  732. $activeLabel = $label.last().addClass(className.active);
  733. activeValue = $activeLabel.data('value');
  734. module.remove.selected(activeValue);
  735. }
  736. }
  737. else {
  738. $activeLabel.removeClass(className.active);
  739. }
  740. }
  741. }
  742. },
  743. keydown: function(event) {
  744. var
  745. pressedKey = event.which,
  746. keys = module.get.shortcutKeys(),
  747. isShortcutKey = module.is.inObject(pressedKey, keys)
  748. ;
  749. if(isShortcutKey) {
  750. var
  751. $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0),
  752. $activeItem = $menu.children('.' + className.active).eq(0),
  753. $selectedItem = ($currentlySelected.length > 0)
  754. ? $currentlySelected
  755. : $activeItem,
  756. $visibleItems = ($selectedItem.length > 0)
  757. ? $selectedItem.siblings(':not(.' + className.filtered +')').andSelf()
  758. : $menu.children(':not(.' + className.filtered +')'),
  759. $subMenu = $selectedItem.children(selector.menu),
  760. $parentMenu = $selectedItem.closest(selector.menu),
  761. inVisibleMenu = ($parentMenu.hasClass(className.visible) || $parentMenu.hasClass(className.animating)),
  762. hasSubMenu = ($subMenu.length> 0),
  763. hasSelectedItem = ($selectedItem.length > 0),
  764. $nextItem,
  765. isSubMenuItem,
  766. newIndex
  767. ;
  768. // visible menu keyboard shortcuts
  769. if( module.is.visible() ) {
  770. // enter (select or open sub-menu)
  771. if(pressedKey == keys.enter && hasSelectedItem) {
  772. if(hasSubMenu && !settings.allowCategorySelection) {
  773. module.verbose('Pressed enter on unselectable category, opening sub menu');
  774. pressedKey = keys.rightArrow;
  775. }
  776. else {
  777. module.verbose('Enter key pressed, choosing selected item');
  778. module.event.item.click.call($selectedItem, event);
  779. }
  780. }
  781. // left arrow (hide sub-menu)
  782. if(pressedKey == keys.leftArrow) {
  783. isSubMenuItem = ($parentMenu[0] !== $menu[0]);
  784. if(isSubMenuItem) {
  785. module.verbose('Left key pressed, closing sub-menu');
  786. module.animate.hide(false, $parentMenu);
  787. $selectedItem
  788. .removeClass(className.selected)
  789. ;
  790. $parentMenu
  791. .closest(selector.item)
  792. .addClass(className.selected)
  793. ;
  794. event.preventDefault();
  795. }
  796. }
  797. // right arrow (show sub-menu)
  798. if(pressedKey == keys.rightArrow) {
  799. if(hasSubMenu) {
  800. module.verbose('Right key pressed, opening sub-menu');
  801. module.animate.show(false, $subMenu);
  802. $selectedItem
  803. .removeClass(className.selected)
  804. ;
  805. $subMenu
  806. .find(selector.item).eq(0)
  807. .addClass(className.selected)
  808. ;
  809. event.preventDefault();
  810. }
  811. }
  812. // up arrow (traverse menu up)
  813. if(pressedKey == keys.upArrow) {
  814. $nextItem = (hasSelectedItem && inVisibleMenu)
  815. ? $selectedItem.prevAll(selector.item + ':not(.' + className.filtered + ')').eq(0)
  816. : $item.eq(0)
  817. ;
  818. if($visibleItems.index( $nextItem ) < 0) {
  819. module.verbose('Up key pressed but reached top of current menu');
  820. return;
  821. }
  822. else {
  823. module.verbose('Up key pressed, changing active item');
  824. $selectedItem
  825. .removeClass(className.selected)
  826. ;
  827. $nextItem
  828. .addClass(className.selected)
  829. ;
  830. module.set.scrollPosition($nextItem);
  831. }
  832. event.preventDefault();
  833. }
  834. // down arrow (traverse menu down)
  835. if(pressedKey == keys.downArrow) {
  836. $nextItem = (hasSelectedItem && inVisibleMenu)
  837. ? $nextItem = $selectedItem.nextAll(selector.item + ':not(.' + className.filtered + ')').eq(0)
  838. : $item.eq(0)
  839. ;
  840. if($nextItem.length === 0) {
  841. module.verbose('Down key pressed but reached bottom of current menu');
  842. return;
  843. }
  844. else {
  845. module.verbose('Down key pressed, changing active item');
  846. $item
  847. .removeClass(className.selected)
  848. ;
  849. $nextItem
  850. .addClass(className.selected)
  851. ;
  852. module.set.scrollPosition($nextItem);
  853. }
  854. event.preventDefault();
  855. }
  856. }
  857. else {
  858. // enter (open menu)
  859. if(pressedKey == keys.enter) {
  860. module.verbose('Enter key pressed, showing dropdown');
  861. module.show();
  862. }
  863. // escape (close menu)
  864. if(pressedKey == keys.escape) {
  865. module.verbose('Escape key pressed, closing dropdown');
  866. module.hide();
  867. }
  868. // down arrow (open menu)
  869. if(pressedKey == keys.downArrow) {
  870. module.verbose('Down key pressed, showing dropdown');
  871. module.show();
  872. }
  873. }
  874. }
  875. },
  876. resetStyle: function() {
  877. $(this).removeAttr('style');
  878. }
  879. },
  880. determine: {
  881. selectAction: function(text, value) {
  882. module.verbose('Determining action', settings.action);
  883. if( $.isFunction( module.action[settings.action] ) ) {
  884. module.verbose('Triggering preset action', settings.action, text, value);
  885. module.action[ settings.action ](text, value);
  886. }
  887. else if( $.isFunction(settings.action) ) {
  888. module.verbose('Triggering user action', settings.action, text, value);
  889. settings.action(text, value);
  890. }
  891. else {
  892. module.error(error.action, settings.action);
  893. }
  894. },
  895. eventInModule: function(event, callback) {
  896. callback = $.isFunction(callback)
  897. ? callback
  898. : function(){}
  899. ;
  900. if( $(event.target).closest($module).length === 0 ) {
  901. module.verbose('Triggering event', callback);
  902. callback();
  903. return true;
  904. }
  905. else {
  906. module.verbose('Event occurred in dropdown, canceling callback');
  907. return false;
  908. }
  909. },
  910. eventInMenu: function(event, callback) {
  911. callback = $.isFunction(callback)
  912. ? callback
  913. : function(){}
  914. ;
  915. if( $(event.target).closest($menu).length === 0 ) {
  916. module.verbose('Triggering event', callback);
  917. callback();
  918. return true;
  919. }
  920. else {
  921. module.verbose('Event occurred in dropdown menu, canceling callback');
  922. return false;
  923. }
  924. }
  925. },
  926. action: {
  927. nothing: function() {},
  928. activate: function(text, value) {
  929. value = (value !== undefined)
  930. ? value
  931. : text
  932. ;
  933. module.set.selected(value);
  934. module.hide(function() {
  935. module.remove.filteredItem();
  936. });
  937. },
  938. select: function(text, value) {
  939. value = (value !== undefined)
  940. ? value
  941. : text
  942. ;
  943. module.set.selected(value);
  944. module.hide(function() {
  945. module.remove.filteredItem();
  946. });
  947. },
  948. combo: function(text, value) {
  949. value = (value !== undefined)
  950. ? value
  951. : text
  952. ;
  953. module.set.selected(value);
  954. module.hide(function() {
  955. module.remove.filteredItem();
  956. });
  957. },
  958. hide: function() {
  959. module.hide(function() {
  960. module.remove.filteredItem();
  961. });
  962. }
  963. },
  964. get: {
  965. id: function() {
  966. return id;
  967. },
  968. text: function() {
  969. return $text.text();
  970. },
  971. uniqueArray: function(array) {
  972. return $.grep(array, function (value, index) {
  973. return $.inArray(value, array) === index;
  974. });
  975. },
  976. caretPosition: function() {
  977. var
  978. input = $search.get(0),
  979. range,
  980. rangeLength
  981. ;
  982. if ('selectionStart' in input) {
  983. return input.selectionStart;
  984. }
  985. else if (document.selection) {
  986. input.focus();
  987. range = document.selection.createRange();
  988. rangeLength = range.text.length;
  989. range.moveStart('character', -input.value.length);
  990. return range.text.length - rangeLength;
  991. }
  992. },
  993. shortcutKeys: function() {
  994. return {
  995. backspace : 8,
  996. delimiter : 188, // comma
  997. deleteKey : 46,
  998. enter : 13,
  999. escape : 27,
  1000. leftArrow : 37,
  1001. upArrow : 38,
  1002. rightArrow : 39,
  1003. downArrow : 40
  1004. };
  1005. },
  1006. value: function() {
  1007. return ($input.length > 0)
  1008. ? $input.val()
  1009. : $module.data(metadata.value)
  1010. ;
  1011. },
  1012. values: function() {
  1013. var
  1014. value = module.get.value()
  1015. ;
  1016. if(value == '') {
  1017. return '';
  1018. }
  1019. return (!$input.is('select') && module.is.multiple())
  1020. ? value.split(settings.delimiter)
  1021. : value
  1022. ;
  1023. },
  1024. choiceText: function($choice, preserveHTML) {
  1025. preserveHTML = (preserveHTML !== undefined)
  1026. ? preserveHTML
  1027. : settings.preserveHTML
  1028. ;
  1029. if($choice) {
  1030. if($choice.find(selector.menu).length > 0) {
  1031. module.verbose('Retreiving text of element with sub-menu');
  1032. $choice = $choice.clone();
  1033. $choice.find(selector.menu).remove();
  1034. $choice.find(selector.menuIcon).remove();
  1035. }
  1036. return ($choice.data(metadata.text) !== undefined)
  1037. ? $choice.data(metadata.text)
  1038. : (preserveHTML)
  1039. ? $choice.html().trim()
  1040. : $choice.text().trim()
  1041. ;
  1042. }
  1043. },
  1044. choiceValue: function($choice, choiceText) {
  1045. choiceText = choiceText || module.get.choiceText($choice);
  1046. if(!$choice) {
  1047. return false;
  1048. }
  1049. return ($choice.data(metadata.value) !== undefined)
  1050. ? $choice.data(metadata.value)
  1051. : (typeof choiceText === 'string')
  1052. ? choiceText.toLowerCase().trim()
  1053. : choiceText
  1054. ;
  1055. },
  1056. inputEvent: function() {
  1057. var
  1058. input = $search[0]
  1059. ;
  1060. if(input) {
  1061. return (input.oninput !== undefined)
  1062. ? 'input'
  1063. : (input.onpropertychange !== undefined)
  1064. ? 'propertychange'
  1065. : 'keyup'
  1066. ;
  1067. }
  1068. return false;
  1069. },
  1070. selectValues: function() {
  1071. var
  1072. select = {}
  1073. ;
  1074. select.values = (settings.sortSelect)
  1075. ? {} // properties will be sorted in object when re-accessed
  1076. : [] // properties will keep original order in array
  1077. ;
  1078. $module
  1079. .find('option')
  1080. .each(function() {
  1081. var
  1082. name = $(this).html(),
  1083. value = ( $(this).attr('value') !== undefined )
  1084. ? $(this).attr('value')
  1085. : name
  1086. ;
  1087. if(value === '') {
  1088. select.placeholder = name;
  1089. }
  1090. else {
  1091. if(settings.sortSelect) {
  1092. select.values[value] = {
  1093. name : name,
  1094. value : value
  1095. };
  1096. }
  1097. else {
  1098. select.values.push({
  1099. name: name,
  1100. value: value
  1101. });
  1102. }
  1103. }
  1104. })
  1105. ;
  1106. if(settings.sortSelect) {
  1107. module.debug('Retrieved and sorted values from select', select);
  1108. }
  1109. else {
  1110. module.debug('Retreived values from select', select);
  1111. }
  1112. return select;
  1113. },
  1114. activeItem: function() {
  1115. return $item.filter('.' + className.active);
  1116. },
  1117. item: function(value, strict) {
  1118. var
  1119. $selectedItem = false,
  1120. isMultiple
  1121. ;
  1122. value = (value !== undefined)
  1123. ? value
  1124. : ( module.get.values() !== undefined)
  1125. ? module.get.values()
  1126. : module.get.text()
  1127. ;
  1128. isMultiple = (module.is.multiple() && $.isArray(value));
  1129. strict = (value === '' || value === 0)
  1130. ? true
  1131. : strict || false
  1132. ;
  1133. if(value !== undefined) {
  1134. $item
  1135. .each(function() {
  1136. var
  1137. $choice = $(this),
  1138. optionText = module.get.choiceText($choice),
  1139. optionValue = module.get.choiceValue($choice, optionText)
  1140. ;
  1141. if(isMultiple) {
  1142. if($.inArray(optionValue, value) !== -1 || $.inArray(optionText, value) !== -1) {
  1143. $selectedItem = ($selectedItem)
  1144. ? $selectedItem.add($choice)
  1145. : $choice
  1146. ;
  1147. }
  1148. }
  1149. else if(strict) {
  1150. module.verbose('Ambiguous dropdown value using strict type check', $choice, value);
  1151. if( optionValue === value || optionText === value) {
  1152. $selectedItem = $choice;
  1153. return true;
  1154. }
  1155. }
  1156. else {
  1157. if( optionValue == value || optionText == value) {
  1158. module.verbose('Found select item by value', optionValue, value);
  1159. $selectedItem = $choice;
  1160. return true;
  1161. }
  1162. }
  1163. })
  1164. ;
  1165. }
  1166. return $selectedItem;
  1167. }
  1168. },
  1169. restore: {
  1170. defaults: function() {
  1171. module.restore.defaultText();
  1172. module.restore.defaultValue();
  1173. },
  1174. defaultText: function() {
  1175. var
  1176. defaultText = $module.data(metadata.defaultText)
  1177. ;
  1178. module.debug('Restoring default text', defaultText);
  1179. module.set.text(defaultText);
  1180. $text.addClass(className.placeholder);
  1181. },
  1182. defaultValue: function() {
  1183. var
  1184. defaultValue = $module.data(metadata.defaultValue)
  1185. ;
  1186. if(defaultValue !== undefined) {
  1187. module.debug('Restoring default value', defaultValue);
  1188. if(defaultValue.length) {
  1189. module.set.selected(defaultValue);
  1190. }
  1191. else {
  1192. module.remove.activeItem();
  1193. module.remove.selectedItem();
  1194. }
  1195. }
  1196. }
  1197. },
  1198. save: {
  1199. defaults: function() {
  1200. module.save.defaultText();
  1201. module.save.placeholderText();
  1202. module.save.defaultValue();
  1203. },
  1204. defaultValue: function() {
  1205. $module.data(metadata.defaultValue, module.get.value());
  1206. },
  1207. defaultText: function() {
  1208. $module.data(metadata.defaultText, $text.text() );
  1209. },
  1210. placeholderText: function() {
  1211. if($text.hasClass(className.placeholder)) {
  1212. $module.data(metadata.placeholderText, $text.text());
  1213. }
  1214. }
  1215. },
  1216. clear: function() {
  1217. module.set.placeholderText();
  1218. module.clearValue();
  1219. module.remove.activeItem();
  1220. module.remove.selectedItem();
  1221. },
  1222. clearValue: function() {
  1223. module.set.value('');
  1224. },
  1225. set: {
  1226. filtered: function() {
  1227. var
  1228. isMultiple = module.is.multiple(),
  1229. searchValue = $search.val(),
  1230. hasSearchValue = (typeof searchValue === 'string' && searchValue.length > 0),
  1231. searchWidth = (searchValue.length * settings.glyphWidth) + 'em',
  1232. valueIsSet = $input.val() != ''
  1233. ;
  1234. if(isMultiple && hasSearchValue) {
  1235. module.verbose('Adjusting input width', searchWidth, settings.glyphWidth)
  1236. $search.css('width', searchWidth);
  1237. }
  1238. if(hasSearchValue || (isMultiple && valueIsSet)) {
  1239. module.verbose('Hiding placeholder text');
  1240. $text.addClass(className.filtered);
  1241. }
  1242. else if(!isMultiple || (isMultiple && !valueIsSet)) {
  1243. module.verbose('Showing placeholder text');
  1244. $text.removeClass(className.filtered);
  1245. }
  1246. },
  1247. placeholderText: function(text) {
  1248. module.debug('Restoring placeholder text');
  1249. text = text || $module.data(metadata.placeholderText);
  1250. module.set.text(placeholderText);
  1251. $text.addClass(className.placeholder);
  1252. },
  1253. tabbable: function() {
  1254. if( module.has.search() ) {
  1255. module.debug('Searchable dropdown initialized');
  1256. $search
  1257. .val('')
  1258. .attr('tabindex', 0)
  1259. ;
  1260. $menu
  1261. .attr('tabindex', '-1')
  1262. ;
  1263. }
  1264. else {
  1265. module.debug('Simple selection dropdown initialized');
  1266. if(!$module.attr('tabindex') ) {
  1267. $module
  1268. .attr('tabindex', 0)
  1269. ;
  1270. $menu
  1271. .attr('tabindex', '-1')
  1272. ;
  1273. }
  1274. }
  1275. },
  1276. scrollPosition: function($item, forceScroll) {
  1277. var
  1278. edgeTolerance = 5,
  1279. hasActive,
  1280. offset,
  1281. itemHeight,
  1282. itemOffset,
  1283. menuOffset,
  1284. menuScroll,
  1285. menuHeight,
  1286. abovePage,
  1287. belowPage
  1288. ;
  1289. $item = $item || module.get.activeItem();
  1290. hasActive = ($item && $item.length > 0);
  1291. forceScroll = (forceScroll !== undefined)
  1292. ? forceScroll
  1293. : false
  1294. ;
  1295. if($item && hasActive) {
  1296. if(!$menu.hasClass(className.visible)) {
  1297. $menu.addClass(className.loading);
  1298. }
  1299. menuHeight = $menu.height();
  1300. itemHeight = $item.height();
  1301. menuScroll = $menu.scrollTop();
  1302. menuOffset = $menu.offset().top;
  1303. itemOffset = $item.offset().top;
  1304. offset = menuScroll - menuOffset + itemOffset;
  1305. belowPage = menuScroll + menuHeight < (offset + edgeTolerance);
  1306. abovePage = ((offset - edgeTolerance) < menuScroll);
  1307. module.debug('Scrolling to active item', offset);
  1308. if(abovePage || belowPage || forceScroll) {
  1309. $menu
  1310. .scrollTop(offset)
  1311. .removeClass(className.loading)
  1312. ;
  1313. }
  1314. }
  1315. },
  1316. text: function(text) {
  1317. if(settings.action !== 'select') {
  1318. module.debug('Changing text', text, $text);
  1319. $text
  1320. .removeClass(className.filtered)
  1321. .removeClass(className.placeholder)
  1322. ;
  1323. if(settings.preserveHTML) {
  1324. $text.html(text);
  1325. }
  1326. else {
  1327. $text.text(text);
  1328. }
  1329. }
  1330. else if(settings.action == 'combo') {
  1331. module.debug('Changing combo button text', text, $combo);
  1332. if(settings.preserveHTML) {
  1333. $combo.html(text);
  1334. }
  1335. else {
  1336. $combo.text(text);
  1337. }
  1338. }
  1339. },
  1340. value: function(value, text, $selected) {
  1341. var
  1342. hasInput = ($input.length > 0),
  1343. currentValue = module.get.values()
  1344. ;
  1345. if($input.length > 0) {
  1346. if( module.is.multiple() ) {
  1347. value = [value];
  1348. if($.isArray(currentValue)) {
  1349. value = currentValue.concat(value);
  1350. value = module.get.uniqueArray(value);
  1351. }
  1352. // set values
  1353. if( $input.is('select') ) {
  1354. module.debug('Setting multiple <select> values', value, $input);
  1355. }
  1356. else {
  1357. value = value.join(settings.delimiter);
  1358. module.debug('Setting hidden input to delimited values', value, $input);
  1359. }
  1360. }
  1361. if(value == currentValue) {
  1362. module.verbose('Skipping value update already same value', value, currentValue);
  1363. return;
  1364. }
  1365. module.debug('Updating input value', value, currentValue);
  1366. $input
  1367. .val(value)
  1368. .trigger('change')
  1369. ;
  1370. settings.onChange.call(element, value, text, $selected);
  1371. }
  1372. else {
  1373. module.verbose('Storing value in metadata', value, $input);
  1374. if(value !== currentValue) {
  1375. $module.data(metadata.value, value);
  1376. settings.onChange.call(element, value, text, $selected);
  1377. }
  1378. }
  1379. },
  1380. active: function() {
  1381. $module
  1382. .addClass(className.active)
  1383. ;
  1384. },
  1385. multiple: function() {
  1386. $module.addClass(className.multiple);
  1387. },
  1388. visible: function() {
  1389. $module.addClass(className.visible);
  1390. },
  1391. selected: function(value) {
  1392. var
  1393. $selectedItem = module.get.item(value),
  1394. isMultiple = module.is.multiple(),
  1395. shouldAnimate = (isMultiple && $selectedItem.length == 1),
  1396. selectedText,
  1397. selectedValue
  1398. ;
  1399. if($selectedItem && !$selectedItem.hasClass(className.active) ) {
  1400. module.debug('Setting selected menu item to', $selectedItem);
  1401. if(!module.is.multiple()) {
  1402. module.remove.activeItem();
  1403. }
  1404. module.remove.selectedItem();
  1405. $selectedItem
  1406. .addClass(className.active)
  1407. .addClass(className.selected)
  1408. .each(function() {
  1409. var
  1410. $selected = $(this)
  1411. ;
  1412. selectedText = module.get.choiceText($selected);
  1413. selectedValue = module.get.choiceValue($selected, selectedText);
  1414. if(isMultiple) {
  1415. module.add.label(selectedValue, selectedText, shouldAnimate);
  1416. module.set.value(selectedValue, selectedText, $selected);
  1417. module.set.filtered();
  1418. }
  1419. else {
  1420. module.set.value(selectedValue, selectedText, $selected);
  1421. module.set.text(selectedText);
  1422. }
  1423. })
  1424. ;
  1425. }
  1426. }
  1427. },
  1428. add: {
  1429. label: function(value, text, shouldAnimate) {
  1430. var
  1431. $label = $('<a />')
  1432. .addClass(className.label)
  1433. .attr('data-value', value)
  1434. .html(templates.label(value, text))
  1435. ;
  1436. if(settings.label.variation) {
  1437. $label.addClass(settings.label.variation);
  1438. }
  1439. if(shouldAnimate == true) {
  1440. module.debug('Animating in label', $label);
  1441. $label
  1442. .addClass(className.hidden)
  1443. .insertBefore($search)
  1444. .transition(settings.label.transition, settings.label.duration)
  1445. ;
  1446. }
  1447. else {
  1448. module.debug('Adding selection label', $label);
  1449. $label
  1450. .insertBefore($search)
  1451. ;
  1452. }
  1453. }
  1454. },
  1455. remove: {
  1456. active: function() {
  1457. $module.removeClass(className.active);
  1458. },
  1459. activeLabel: function() {
  1460. $module.find(selector.label).removeClass(className.active);
  1461. },
  1462. visible: function() {
  1463. $module.removeClass(className.visible);
  1464. },
  1465. activeItem: function() {
  1466. $item.removeClass(className.active);
  1467. },
  1468. filteredItem: function() {
  1469. $item.removeClass(className.filtered);
  1470. },
  1471. searchTerm: function() {
  1472. module.verbose('Cleared search term');
  1473. $search.val('');
  1474. },
  1475. selected: function(value) {
  1476. var
  1477. $selectedItem = module.get.item(value),
  1478. $option,
  1479. values = $input.val(),
  1480. selectedValue = module.get.choiceValue($selectedItem)
  1481. ;
  1482. if($selectedItem) {
  1483. if( $input.is('select') ) {
  1484. $input
  1485. .find('option[value="' + selectedValue + '"]')
  1486. .prop('selected', false)
  1487. ;
  1488. }
  1489. else {
  1490. values = module.remove.delimitedValue(selectedValue, values);
  1491. $input.val(values);
  1492. }
  1493. if(module.is.multiple()) {
  1494. module.remove.label(selectedValue);
  1495. module.set.filtered();
  1496. }
  1497. $selectedItem
  1498. .removeClass(className.active)
  1499. ;
  1500. }
  1501. },
  1502. selectedItem: function() {
  1503. $item.removeClass(className.selected);
  1504. },
  1505. delimitedValue: function(removedValue, values) {
  1506. if(typeof values != 'string') {
  1507. return false;
  1508. }
  1509. values = values.split(settings.delimiter);
  1510. values = $.grep(values, function(value){
  1511. return (removedValue != value);
  1512. });
  1513. values = values.join(settings.delimiter);
  1514. module.verbose('Removed value from delimited string', removedValue, values);
  1515. return values;
  1516. },
  1517. label: function(value) {
  1518. var
  1519. $labels = $module.find(selector.label),
  1520. $removedLabel = $labels.filter('[data-value="' + value +'"]'),
  1521. labelCount = $labels.length,
  1522. isLastLabel = ($labels.index($removedLabel) + 1 == labelCount),
  1523. isOnlyLabel = (labelCount == 1),
  1524. shouldAnimate = (isOnlyLabel || isLastLabel)
  1525. ;
  1526. if(shouldAnimate) {
  1527. module.verbose('Animating and removing label', $removedLabel);
  1528. $removedLabel
  1529. .transition(settings.label.transition, settings.label.duration, function() {
  1530. $removedLabel.remove();
  1531. })
  1532. ;
  1533. }
  1534. else {
  1535. module.verbose('Removing label', $removedLabel);
  1536. $removedLabel.remove();
  1537. }
  1538. },
  1539. labels: function($activeLabels) {
  1540. $activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active);
  1541. module.verbose('Removing active labels', $activeLabels);
  1542. $activeLabels
  1543. .each(function(){
  1544. module.remove.selected($(this).data('value'));
  1545. })
  1546. ;
  1547. },
  1548. tabbable: function() {
  1549. if( module.has.search() ) {
  1550. module.debug('Searchable dropdown initialized');
  1551. $search
  1552. .attr('tabindex', '-1')
  1553. ;
  1554. $menu
  1555. .attr('tabindex', '-1')
  1556. ;
  1557. }
  1558. else {
  1559. module.debug('Simple selection dropdown initialized');
  1560. $module
  1561. .attr('tabindex', '-1')
  1562. ;
  1563. $menu
  1564. .attr('tabindex', '-1')
  1565. ;
  1566. }
  1567. }
  1568. },
  1569. has: {
  1570. search: function() {
  1571. return ($search.length > 0);
  1572. },
  1573. input: function() {
  1574. return ($input.length > 0);
  1575. },
  1576. menu: function() {
  1577. return ($menu.length > 0);
  1578. }
  1579. },
  1580. is: {
  1581. active: function() {
  1582. return $module.hasClass(className.active);
  1583. },
  1584. alreadySetup: function() {
  1585. return ($module.is('select') && $module.parent(selector.dropdown).length > 0);
  1586. },
  1587. animating: function($subMenu) {
  1588. return ($subMenu)
  1589. ? $subMenu.transition && $subMenu.transition('is animating')
  1590. : $menu.transition && $menu.transition('is animating')
  1591. ;
  1592. },
  1593. focusedOnSearch: function() {
  1594. return (document.activeElement === $search[0]);
  1595. },
  1596. allFiltered: function() {
  1597. return ($item.filter('.' + className.filtered).length === $item.length);
  1598. },
  1599. hidden: function($subMenu) {
  1600. return ($subMenu)
  1601. ? $subMenu.is(':hidden')
  1602. : $menu.is(':hidden')
  1603. ;
  1604. },
  1605. inObject: function(needle, object) {
  1606. var
  1607. found = false
  1608. ;
  1609. $.each(object, function(index, property) {
  1610. if(property == needle) {
  1611. found = true;
  1612. return true;
  1613. }
  1614. });
  1615. return found;
  1616. },
  1617. multiple: function() {
  1618. return $module.hasClass(className.multiple);
  1619. },
  1620. selectMutation: function(mutations) {
  1621. var
  1622. selectChanged = false
  1623. ;
  1624. $.each(mutations, function(index, mutation) {
  1625. if(mutation.target && $(mutation.target).is('select')) {
  1626. selectChanged = true;
  1627. return true;
  1628. }
  1629. });
  1630. return selectChanged;
  1631. },
  1632. search: function() {
  1633. return $module.hasClass(className.search);
  1634. },
  1635. searchSelection: function() {
  1636. return ( module.has.search() && $search.closest(selector.menu).length == 0 );
  1637. },
  1638. selection: function() {
  1639. return $module.hasClass(className.selection);
  1640. },
  1641. upward: function() {
  1642. return $module.hasClass(className.upward);
  1643. },
  1644. visible: function($subMenu) {
  1645. return ($subMenu)
  1646. ? $subMenu.is(':visible')
  1647. : $menu.is(':visible')
  1648. ;
  1649. }
  1650. },
  1651. can: {
  1652. click: function() {
  1653. return (hasTouch || settings.on == 'click');
  1654. },
  1655. show: function() {
  1656. return !$module.hasClass(className.disabled);
  1657. }
  1658. },
  1659. animate: {
  1660. show: function(callback, $subMenu) {
  1661. var
  1662. $currentMenu = $subMenu || $menu,
  1663. start = ($subMenu)
  1664. ? function() {}
  1665. : function() {
  1666. module.hideSubMenus();
  1667. module.hideOthers();
  1668. module.set.active();
  1669. }
  1670. ;
  1671. callback = $.isFunction(callback)
  1672. ? callback
  1673. : function(){}
  1674. ;
  1675. if(!module.is.multiple()) {
  1676. module.set.scrollPosition(module.get.activeItem(), true);
  1677. }
  1678. module.verbose('Doing menu show animation', $currentMenu);
  1679. if( module.is.hidden($currentMenu) || module.is.animating($currentMenu) ) {
  1680. if(settings.transition == 'auto') {
  1681. settings.transition = module.is.upward()
  1682. ? 'slide up'
  1683. : 'slide down'
  1684. ;
  1685. module.verbose('Automatically determining animation based on animation direction', settings.transition);
  1686. }
  1687. if(settings.transition == 'none') {
  1688. callback.call(element);
  1689. }
  1690. else if($.fn.transition !== undefined && $module.transition('is supported')) {
  1691. $currentMenu
  1692. .transition({
  1693. animation : settings.transition + ' in',
  1694. debug : settings.debug,
  1695. verbose : settings.verbose,
  1696. duration : settings.duration,
  1697. queue : true,
  1698. onStart : start,
  1699. onComplete : function() {
  1700. callback.call(element);
  1701. }
  1702. })
  1703. ;
  1704. }
  1705. else {
  1706. module.error(error.noTransition, settings.transition);
  1707. }
  1708. }
  1709. },
  1710. hide: function(callback, $subMenu) {
  1711. var
  1712. $currentMenu = $subMenu || $menu,
  1713. duration = ($subMenu)
  1714. ? (settings.duration * 0.9)
  1715. : settings.duration,
  1716. start = ($subMenu)
  1717. ? function() {}
  1718. : function() {
  1719. if( module.can.click() ) {
  1720. module.unbind.intent();
  1721. }
  1722. module.focusSearch();
  1723. module.remove.active();
  1724. }
  1725. ;
  1726. callback = $.isFunction(callback)
  1727. ? callback
  1728. : function(){}
  1729. ;
  1730. if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) {
  1731. module.verbose('Doing menu hide animation', $currentMenu);
  1732. if(settings.transition == 'auto') {
  1733. settings.transition = module.is.upward()
  1734. ? 'slide up'
  1735. : 'slide down'
  1736. ;
  1737. }
  1738. if(settings.transition == 'none') {
  1739. callback.call(element);
  1740. }
  1741. else if($.fn.transition !== undefined && $module.transition('is supported')) {
  1742. $currentMenu
  1743. .transition({
  1744. animation : settings.transition + ' out',
  1745. duration : settings.duration,
  1746. debug : settings.debug,
  1747. verbose : settings.verbose,
  1748. queue : true,
  1749. onStart : start,
  1750. onComplete : function() {
  1751. callback.call(element);
  1752. }
  1753. })
  1754. ;
  1755. }
  1756. else {
  1757. module.error(error.transition);
  1758. }
  1759. }
  1760. }
  1761. },
  1762. delay: {
  1763. show: function() {
  1764. module.verbose('Delaying show event to ensure user intent');
  1765. clearTimeout(module.timer);
  1766. module.timer = setTimeout(module.show, settings.delay.show);
  1767. },
  1768. hide: function() {
  1769. module.verbose('Delaying hide event to ensure user intent');
  1770. clearTimeout(module.timer);
  1771. module.timer = setTimeout(module.hide, settings.delay.hide);
  1772. }
  1773. },
  1774. escape: {
  1775. regExp: function(text) {
  1776. text = String(text);
  1777. return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
  1778. }
  1779. },
  1780. setting: function(name, value) {
  1781. module.debug('Changing setting', name, value);
  1782. if( $.isPlainObject(name) ) {
  1783. $.extend(true, settings, name);
  1784. }
  1785. else if(value !== undefined) {
  1786. settings[name] = value;
  1787. }
  1788. else {
  1789. return settings[name];
  1790. }
  1791. },
  1792. internal: function(name, value) {
  1793. if( $.isPlainObject(name) ) {
  1794. $.extend(true, module, name);
  1795. }
  1796. else if(value !== undefined) {
  1797. module[name] = value;
  1798. }
  1799. else {
  1800. return module[name];
  1801. }
  1802. },
  1803. debug: function() {
  1804. if(settings.debug) {
  1805. if(settings.performance) {
  1806. module.performance.log(arguments);
  1807. }
  1808. else {
  1809. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  1810. module.debug.apply(console, arguments);
  1811. }
  1812. }
  1813. },
  1814. verbose: function() {
  1815. if(settings.verbose && settings.debug) {
  1816. if(settings.performance) {
  1817. module.performance.log(arguments);
  1818. }
  1819. else {
  1820. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  1821. module.verbose.apply(console, arguments);
  1822. }
  1823. }
  1824. },
  1825. error: function() {
  1826. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  1827. module.error.apply(console, arguments);
  1828. },
  1829. performance: {
  1830. log: function(message) {
  1831. var
  1832. currentTime,
  1833. executionTime,
  1834. previousTime
  1835. ;
  1836. if(settings.performance) {
  1837. currentTime = new Date().getTime();
  1838. previousTime = time || currentTime;
  1839. executionTime = currentTime - previousTime;
  1840. time = currentTime;
  1841. performance.push({
  1842. 'Name' : message[0],
  1843. 'Arguments' : [].slice.call(message, 1) || '',
  1844. 'Element' : element,
  1845. 'Execution Time' : executionTime
  1846. });
  1847. }
  1848. clearTimeout(module.performance.timer);
  1849. module.performance.timer = setTimeout(module.performance.display, 500);
  1850. },
  1851. display: function() {
  1852. var
  1853. title = settings.name + ':',
  1854. totalTime = 0
  1855. ;
  1856. time = false;
  1857. clearTimeout(module.performance.timer);
  1858. $.each(performance, function(index, data) {
  1859. totalTime += data['Execution Time'];
  1860. });
  1861. title += ' ' + totalTime + 'ms';
  1862. if(moduleSelector) {
  1863. title += ' \'' + moduleSelector + '\'';
  1864. }
  1865. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  1866. console.groupCollapsed(title);
  1867. if(console.table) {
  1868. console.table(performance);
  1869. }
  1870. else {
  1871. $.each(performance, function(index, data) {
  1872. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  1873. });
  1874. }
  1875. console.groupEnd();
  1876. }
  1877. performance = [];
  1878. }
  1879. },
  1880. invoke: function(query, passedArguments, context) {
  1881. var
  1882. object = instance,
  1883. maxDepth,
  1884. found,
  1885. response
  1886. ;
  1887. passedArguments = passedArguments || queryArguments;
  1888. context = element || context;
  1889. if(typeof query == 'string' && object !== undefined) {
  1890. query = query.split(/[\. ]/);
  1891. maxDepth = query.length - 1;
  1892. $.each(query, function(depth, value) {
  1893. var camelCaseValue = (depth != maxDepth)
  1894. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  1895. : query
  1896. ;
  1897. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  1898. object = object[camelCaseValue];
  1899. }
  1900. else if( object[camelCaseValue] !== undefined ) {
  1901. found = object[camelCaseValue];
  1902. return false;
  1903. }
  1904. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  1905. object = object[value];
  1906. }
  1907. else if( object[value] !== undefined ) {
  1908. found = object[value];
  1909. return false;
  1910. }
  1911. else {
  1912. module.error(error.method, query);
  1913. return false;
  1914. }
  1915. });
  1916. }
  1917. if ( $.isFunction( found ) ) {
  1918. response = found.apply(context, passedArguments);
  1919. }
  1920. else if(found !== undefined) {
  1921. response = found;
  1922. }
  1923. if($.isArray(returnedValue)) {
  1924. returnedValue.push(response);
  1925. }
  1926. else if(returnedValue !== undefined) {
  1927. returnedValue = [returnedValue, response];
  1928. }
  1929. else if(response !== undefined) {
  1930. returnedValue = response;
  1931. }
  1932. return found;
  1933. }
  1934. };
  1935. if(methodInvoked) {
  1936. if(instance === undefined) {
  1937. module.initialize();
  1938. }
  1939. module.invoke(query);
  1940. }
  1941. else {
  1942. if(instance !== undefined) {
  1943. instance.invoke('destroy');
  1944. }
  1945. module.initialize();
  1946. }
  1947. })
  1948. ;
  1949. return (returnedValue !== undefined)
  1950. ? returnedValue
  1951. : $allModules
  1952. ;
  1953. };
  1954. $.fn.dropdown.settings = {
  1955. debug : false,
  1956. verbose : true,
  1957. performance : true,
  1958. on : 'click',
  1959. action : 'activate',
  1960. allowTab : true,
  1961. fullTextSearch : false,
  1962. preserveHTML : true,
  1963. sortSelect : false,
  1964. label: {
  1965. transition : 'horizontal flip',
  1966. duration : 250,
  1967. variation : false
  1968. },
  1969. allowCategorySelection : false,
  1970. delay : {
  1971. hide : 300,
  1972. show : 200,
  1973. search : 50,
  1974. touch : 50
  1975. },
  1976. forceSelection : true,
  1977. // widest glyph width in em (W is 1.0714 em)
  1978. glyphWidth : 1.0714,
  1979. transition : 'auto',
  1980. delimiter : ',',
  1981. duration : 250,
  1982. /* Callbacks */
  1983. onLabelClick : function($selectedLabels){},
  1984. onNoResults : function(searchTerm){},
  1985. onChange : function(value, text, $selected){},
  1986. onShow : function(){},
  1987. onHide : function(){},
  1988. /* Component */
  1989. name : 'Dropdown',
  1990. namespace : 'dropdown',
  1991. error : {
  1992. action : 'You called a dropdown action that was not defined',
  1993. alreadySetup : 'Once a select has been initialized behaviors must be called on the created ui dropdown',
  1994. method : 'The method you called is not defined.',
  1995. noTransition : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>'
  1996. },
  1997. metadata: {
  1998. defaultText : 'defaultText',
  1999. defaultValue : 'defaultValue',
  2000. placeholderText : 'placeholderText',
  2001. text : 'text',
  2002. value : 'value'
  2003. },
  2004. selector : {
  2005. dropdown : '.ui.dropdown',
  2006. icon : '> .dropdown.icon',
  2007. input : '> input[type="hidden"], > select',
  2008. item : '.item',
  2009. label : '> .label',
  2010. remove : '> .label > .delete.icon',
  2011. menu : '.menu',
  2012. menuIcon : '.dropdown.icon',
  2013. search : 'input.search, .menu > .search > input',
  2014. text : '> .text:not(.icon)'
  2015. },
  2016. className : {
  2017. active : 'active',
  2018. animating : 'animating',
  2019. disabled : 'disabled',
  2020. dropdown : 'ui dropdown',
  2021. filtered : 'filtered',
  2022. hidden : 'hidden transition',
  2023. label : 'ui label',
  2024. loading : 'loading',
  2025. menu : 'menu',
  2026. multiple : 'multiple',
  2027. placeholder : 'default',
  2028. search : 'search',
  2029. selected : 'selected',
  2030. selection : 'selection',
  2031. upward : 'upward',
  2032. visible : 'visible'
  2033. }
  2034. };
  2035. /* Templates */
  2036. $.fn.dropdown.settings.templates = {
  2037. menu: function(select) {
  2038. var
  2039. placeholder = select.placeholder || false,
  2040. values = select.values || {},
  2041. html = ''
  2042. ;
  2043. $.each(select.values, function(index, option) {
  2044. html += '<div class="item" data-value="' + option.value + '">' + option.name + '</div>';
  2045. });
  2046. return html;
  2047. },
  2048. label: function(value, text) {
  2049. return text + '<i class="delete icon"></i>';
  2050. },
  2051. dropdown: function(select) {
  2052. var
  2053. placeholder = select.placeholder || false,
  2054. values = select.values || {},
  2055. html = ''
  2056. ;
  2057. html += '<i class="dropdown icon"></i>';
  2058. if(select.placeholder) {
  2059. html += '<div class="default text">' + placeholder + '</div>';
  2060. }
  2061. else {
  2062. html += '<div class="text"></div>';
  2063. }
  2064. html += '<div class="menu">';
  2065. $.each(select.values, function(index, option) {
  2066. html += '<div class="item" data-value="' + option.value + '">' + option.name + '</div>';
  2067. });
  2068. html += '</div>';
  2069. return html;
  2070. }
  2071. };
  2072. /* Dependencies */
  2073. $.extend( $.easing, {
  2074. easeOutQuad: function (x, t, b, c, d) {
  2075. return -c *(t/=d)*(t-2) + b;
  2076. },
  2077. });
  2078. })( jQuery, window , document );