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.

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