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.

1453 lines
46 KiB

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
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. /*
  2. * # Semantic - Dropdown
  3. * http://github.com/semantic-org/semantic-ui/
  4. *
  5. *
  6. * Copyright 2014 Contributor
  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. eventNamespace = '.' + namespace,
  38. moduleNamespace = 'module-' + namespace,
  39. $module = $(this),
  40. $text = $module.find(selector.text),
  41. $search = $module.find(selector.search),
  42. $input = $module.find(selector.input),
  43. $combo = ($module.prev().find(selector.text).size() > 0)
  44. ? $module.prev().find(selector.text)
  45. : $module.prev(),
  46. $menu = $module.children(selector.menu),
  47. $item = $menu.find(selector.item),
  48. activated = false,
  49. itemActivated = false,
  50. element = this,
  51. instance = $module.data(moduleNamespace),
  52. observer,
  53. module
  54. ;
  55. module = {
  56. initialize: function() {
  57. module.debug('Initializing dropdown', settings);
  58. module.setup.layout();
  59. module.save.defaults();
  60. module.set.selected();
  61. if(hasTouch) {
  62. module.bind.touchEvents();
  63. }
  64. module.bind.mouseEvents();
  65. module.bind.keyboardEvents();
  66. module.observeChanges();
  67. module.instantiate();
  68. },
  69. instantiate: function() {
  70. module.verbose('Storing instance of dropdown', module);
  71. instance = module;
  72. $module
  73. .data(moduleNamespace, module)
  74. ;
  75. },
  76. destroy: function() {
  77. module.verbose('Destroying previous dropdown for', $module);
  78. $module
  79. .off(eventNamespace)
  80. .removeData(moduleNamespace)
  81. ;
  82. },
  83. observeChanges: function() {
  84. if('MutationObserver' in window) {
  85. observer = new MutationObserver(function(mutations) {
  86. module.debug('DOM tree modified, updating selector cache');
  87. module.refresh();
  88. });
  89. observer.observe(element, {
  90. childList : true,
  91. subtree : true
  92. });
  93. module.debug('Setting up mutation observer', observer);
  94. }
  95. },
  96. setup: {
  97. layout: function() {
  98. if( $module.is('select') ) {
  99. module.setup.select();
  100. }
  101. if( module.is.search() && !module.is.searchable() ) {
  102. $search = $('<input />')
  103. .addClass(className.search)
  104. .insertBefore($text)
  105. ;
  106. }
  107. if(settings.allowTab) {
  108. if( module.is.searchable() ) {
  109. module.debug('Searchable dropdown initialized');
  110. $search
  111. .val('')
  112. .attr('tabindex', 0)
  113. ;
  114. $menu
  115. .attr('tabindex', '-1')
  116. ;
  117. }
  118. else {
  119. module.debug('Simple selection dropdown initialized');
  120. if(!$module.attr('tabindex') ) {
  121. $module
  122. .attr('tabindex', 0)
  123. ;
  124. $menu
  125. .attr('tabindex', '-1')
  126. ;
  127. }
  128. }
  129. }
  130. },
  131. select: function() {
  132. var
  133. selectValues = module.get.selectValues()
  134. ;
  135. module.debug('Dropdown initialized on a select', selectValues);
  136. // see if select exists inside a dropdown
  137. $input = $module;
  138. if($input.parents(selector.dropdown).size() > 0) {
  139. module.debug('Creating dropdown menu only from template');
  140. $module = $input.closest(selector.dropdown);
  141. if($module.find('.' + className.dropdown).size() === 0) {
  142. $('<div />')
  143. .addClass(className.menu)
  144. .html( settings.templates.menu( selectValues ))
  145. .appendTo($module)
  146. ;
  147. }
  148. }
  149. else {
  150. module.debug('Creating entire dropdown from template');
  151. $module = $('<div />')
  152. .attr('class', $input.attr('class') )
  153. .addClass(className.selection)
  154. .addClass(className.dropdown)
  155. .html( settings.templates.dropdown(selectValues) )
  156. .insertBefore($input)
  157. ;
  158. $input
  159. .removeAttr('class')
  160. .prependTo($module)
  161. ;
  162. }
  163. module.refresh();
  164. }
  165. },
  166. refresh: function() {
  167. $text = $module.find(selector.text);
  168. $search = $module.find(selector.search);
  169. $input = $module.find(selector.input);
  170. $menu = $module.children(selector.menu);
  171. $item = $menu.find(selector.item);
  172. },
  173. toggle: function() {
  174. module.verbose('Toggling menu visibility');
  175. if( !module.is.active() ) {
  176. module.show();
  177. }
  178. else {
  179. module.hide();
  180. }
  181. },
  182. show: function() {
  183. module.debug('Checking if dropdown can show');
  184. if( !module.is.active() ) {
  185. module.animate.show(function() {
  186. if( module.can.click() ) {
  187. module.bind.intent();
  188. }
  189. module.set.visible();
  190. });
  191. $.proxy(settings.onShow, element)();
  192. }
  193. },
  194. hide: function() {
  195. if( module.is.active() ) {
  196. module.debug('Hiding dropdown');
  197. module.animate.hide(function() {
  198. module.remove.filteredItem();
  199. module.remove.visible();
  200. });
  201. $.proxy(settings.onHide, element)();
  202. }
  203. },
  204. hideOthers: function() {
  205. module.verbose('Finding other dropdowns to hide');
  206. $allModules
  207. .not($module)
  208. .has(selector.menu + ':visible:not(.' + className.animating + ')')
  209. .dropdown('hide')
  210. ;
  211. },
  212. hideSubMenus: function() {
  213. var
  214. $subMenus = $menu.find(selector.menu),
  215. $activeSubMenu = $subMenus.has(selector.item + '.' + className.active)
  216. ;
  217. $subMenus
  218. .not($activeSubMenu)
  219. .removeClass(className.visible)
  220. .removeAttr('style')
  221. ;
  222. },
  223. bind: {
  224. keyboardEvents: function() {
  225. module.debug('Binding keyboard events');
  226. $module
  227. .on('keydown' + eventNamespace, module.event.keydown)
  228. ;
  229. if( module.is.searchable() ) {
  230. $module
  231. .on(module.get.inputEvent(), selector.search, module.event.input)
  232. ;
  233. }
  234. },
  235. touchEvents: function() {
  236. module.debug('Touch device detected binding additional touch events');
  237. if( module.is.searchSelection() ) {
  238. // do nothing special yet
  239. }
  240. else {
  241. $module
  242. .on('touchstart' + eventNamespace, module.event.test.toggle)
  243. ;
  244. }
  245. $menu
  246. .on('touchstart' + eventNamespace, selector.item, module.event.item.mouseenter)
  247. ;
  248. },
  249. mouseEvents: function() {
  250. module.verbose('Mouse detected binding mouse events');
  251. if( module.is.searchSelection() ) {
  252. $module
  253. .on('mousedown' + eventNamespace, selector.menu, module.event.menu.activate)
  254. .on('mouseup' + eventNamespace, selector.menu, module.event.menu.deactivate)
  255. .on('focus' + eventNamespace, selector.search, module.event.searchFocus)
  256. .on('blur' + eventNamespace, selector.search, module.event.searchBlur)
  257. ;
  258. }
  259. else {
  260. if(settings.on == 'click') {
  261. $module
  262. .on('click' + eventNamespace, module.event.test.toggle)
  263. ;
  264. }
  265. else if(settings.on == 'hover') {
  266. $module
  267. .on('mouseenter' + eventNamespace, module.delay.show)
  268. .on('mouseleave' + eventNamespace, module.delay.hide)
  269. ;
  270. }
  271. else {
  272. $module
  273. .on(settings.on + eventNamespace, module.toggle)
  274. ;
  275. }
  276. $module
  277. .on('mousedown' + eventNamespace, module.event.mousedown)
  278. .on('mouseup' + eventNamespace, module.event.mouseup)
  279. .on('focus' + eventNamespace, module.event.focus)
  280. .on('blur' + eventNamespace, module.event.blur)
  281. ;
  282. }
  283. $menu
  284. .on('mouseenter' + eventNamespace, selector.item, module.event.item.mouseenter)
  285. .on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave)
  286. .on('click' + eventNamespace, selector.item, module.event.item.click)
  287. ;
  288. },
  289. intent: function() {
  290. module.verbose('Binding hide intent event to document');
  291. if(hasTouch) {
  292. $document
  293. .on('touchstart' + eventNamespace, module.event.test.touch)
  294. .on('touchmove' + eventNamespace, module.event.test.touch)
  295. ;
  296. }
  297. $document
  298. .on('click' + eventNamespace, module.event.test.hide)
  299. ;
  300. }
  301. },
  302. unbind: {
  303. intent: function() {
  304. module.verbose('Removing hide intent event from document');
  305. if(hasTouch) {
  306. $document
  307. .off('touchstart' + eventNamespace)
  308. .off('touchmove' + eventNamespace)
  309. ;
  310. }
  311. $document
  312. .off('click' + eventNamespace)
  313. ;
  314. }
  315. },
  316. filter: function(searchTerm) {
  317. var
  318. $results = $(),
  319. exactRegExp = new RegExp('(?:\s|^)' + searchTerm, 'i'),
  320. fullTextRegExp = new RegExp(searchTerm, 'i'),
  321. $filteredItems
  322. ;
  323. $item
  324. .each(function(){
  325. var
  326. $choice = $(this),
  327. text = ( $choice.data(metadata.text) !== undefined )
  328. ? $choice.data(metadata.text)
  329. : (settings.preserveHTML)
  330. ? $choice.html()
  331. : $choice.text(),
  332. value = ( $choice.data(metadata.value) !== undefined)
  333. ? $choice.data(metadata.value)
  334. : (typeof text === 'string')
  335. ? text.toLowerCase()
  336. : text
  337. ;
  338. if( exactRegExp.test( text ) || exactRegExp.test( value ) ) {
  339. $results = $results.add($choice);
  340. }
  341. else if(settings.fullTextSearch) {
  342. if( fullTextRegExp.test( text ) || fullTextRegExp.test( value ) ) {
  343. $results = $results.add($choice);
  344. }
  345. }
  346. })
  347. ;
  348. $filteredItems = $item.not($results);
  349. module.remove.filteredItem();
  350. module.remove.selectedItem();
  351. $filteredItems
  352. .addClass(className.filtered)
  353. ;
  354. $item
  355. .not('.' + className.filtered)
  356. .eq(0)
  357. .addClass(className.selected)
  358. ;
  359. },
  360. focusSearch: function() {
  361. $search
  362. .focus()
  363. ;
  364. },
  365. event: {
  366. // prevents focus callback from occuring on mousedown
  367. mousedown: function() {
  368. activated = true;
  369. },
  370. mouseup: function() {
  371. activated = false;
  372. },
  373. focus: function() {
  374. if(!activated) {
  375. module.show();
  376. }
  377. },
  378. blur: function(event) {
  379. if(!activated) {
  380. module.hide();
  381. }
  382. },
  383. searchFocus: function() {
  384. activated = true;
  385. module.show();
  386. },
  387. searchBlur: function(event) {
  388. if(!itemActivated) {
  389. module.hide();
  390. }
  391. },
  392. input: function(event) {
  393. var
  394. query = $search.val()
  395. ;
  396. if(module.is.searchSelection()) {
  397. $text.addClass(className.filtered);
  398. }
  399. module.filter(query);
  400. },
  401. keydown: function(event) {
  402. var
  403. $selectedItem = $item.not(className.filtered).filter('.' + className.selected).eq(0),
  404. $visibleItems = $item.not('.' + className.filtered),
  405. pressedKey = event.which,
  406. keys = {
  407. enter : 13,
  408. escape : 27,
  409. upArrow : 38,
  410. downArrow : 40
  411. },
  412. selectedClass = className.selected,
  413. currentIndex = $visibleItems.index( $selectedItem ),
  414. hasSelectedItem = ($selectedItem.size() > 0),
  415. $nextItem,
  416. newIndex
  417. ;
  418. // default to activated choice if no selection present
  419. if(!hasSelectedItem) {
  420. $selectedItem = $item.filter('.' + className.active).eq(0);
  421. hasSelectedItem = ($selectedItem.size() > 0);
  422. }
  423. // close shortcuts
  424. if(pressedKey == keys.escape) {
  425. module.verbose('Escape key pressed, closing dropdown');
  426. module.hide();
  427. }
  428. // result shortcuts
  429. if(module.is.visible()) {
  430. if(pressedKey == keys.enter && hasSelectedItem) {
  431. module.verbose('Enter key pressed, choosing selected item');
  432. $.proxy(module.event.item.click, $selectedItem)(event);
  433. event.preventDefault();
  434. return false;
  435. }
  436. else if(pressedKey == keys.upArrow) {
  437. if(!hasSelectedItem) {
  438. $nextItem = $visibleItems.eq(0);
  439. }
  440. else {
  441. $nextItem = $selectedItem.prevAll(selector.item + ':not(.' + className.filtered + ')').eq(0);
  442. }
  443. if(currentIndex !== 0) {
  444. module.verbose('Up key pressed, changing active item');
  445. $item
  446. .removeClass(selectedClass)
  447. ;
  448. $nextItem
  449. .addClass(selectedClass)
  450. ;
  451. module.set.scrollPosition($nextItem);
  452. }
  453. event.preventDefault();
  454. }
  455. else if(pressedKey == keys.downArrow) {
  456. if(!hasSelectedItem) {
  457. $nextItem = $visibleItems.eq(0);
  458. }
  459. else {
  460. $nextItem = $selectedItem.nextAll(selector.item + ':not(.' + className.filtered + ')').eq(0);
  461. }
  462. if(currentIndex + 1 < $visibleItems.size() ) {
  463. module.verbose('Down key pressed, changing active item');
  464. $item
  465. .removeClass(selectedClass)
  466. ;
  467. $nextItem
  468. .addClass(selectedClass)
  469. ;
  470. module.set.scrollPosition($nextItem);
  471. }
  472. event.preventDefault();
  473. }
  474. }
  475. else {
  476. if(pressedKey == keys.enter) {
  477. module.show();
  478. }
  479. }
  480. },
  481. test: {
  482. toggle: function(event) {
  483. if( module.determine.eventInMenu(event, module.toggle) ) {
  484. event.preventDefault();
  485. }
  486. },
  487. touch: function(event) {
  488. module.determine.eventInMenu(event, function() {
  489. if(event.type == 'touchstart') {
  490. module.timer = setTimeout(module.hide, settings.delay.touch);
  491. }
  492. else if(event.type == 'touchmove') {
  493. clearTimeout(module.timer);
  494. }
  495. });
  496. event.stopPropagation();
  497. },
  498. hide: function(event) {
  499. module.determine.eventInModule(event, module.hide);
  500. }
  501. },
  502. menu: {
  503. activate: function() {
  504. itemActivated = true;
  505. },
  506. deactivate: function() {
  507. itemActivated = false;
  508. }
  509. },
  510. item: {
  511. mouseenter: function(event) {
  512. var
  513. $currentMenu = $(this).find(selector.menu),
  514. $otherMenus = $(this).siblings(selector.item).children(selector.menu)
  515. ;
  516. if( $currentMenu.size() > 0 ) {
  517. clearTimeout(module.itemTimer);
  518. module.itemTimer = setTimeout(function() {
  519. module.animate.hide(false, $otherMenus);
  520. module.verbose('Showing sub-menu', $currentMenu);
  521. module.animate.show(false, $currentMenu);
  522. }, settings.delay.show * 2);
  523. event.preventDefault();
  524. }
  525. },
  526. mouseleave: function(event) {
  527. var
  528. $currentMenu = $(this).find(selector.menu)
  529. ;
  530. if($currentMenu.size() > 0) {
  531. clearTimeout(module.itemTimer);
  532. module.itemTimer = setTimeout(function() {
  533. module.verbose('Hiding sub-menu', $currentMenu);
  534. module.animate.hide(false, $currentMenu);
  535. }, settings.delay.hide);
  536. }
  537. },
  538. click: function (event) {
  539. var
  540. $choice = $(this),
  541. text = ( $choice.data(metadata.text) !== undefined )
  542. ? $choice.data(metadata.text)
  543. : (settings.preserveHTML)
  544. ? $choice.html()
  545. : $choice.text(),
  546. value = ( $choice.data(metadata.value) !== undefined)
  547. ? $choice.data(metadata.value)
  548. : (typeof text === 'string')
  549. ? text.toLowerCase()
  550. : text,
  551. callback = function() {
  552. $search.val('');
  553. module.determine.selectAction(text, value);
  554. $.proxy(settings.onChange, element)(value, text, $choice);
  555. },
  556. openingSubMenu = ($choice.find(selector.menu).size() > 0)
  557. ;
  558. if( !openingSubMenu ) {
  559. callback();
  560. }
  561. }
  562. },
  563. resetStyle: function() {
  564. $(this).removeAttr('style');
  565. }
  566. },
  567. determine: {
  568. selectAction: function(text, value) {
  569. module.verbose('Determining action', settings.action);
  570. if( $.isFunction( module.action[settings.action] ) ) {
  571. module.verbose('Triggering preset action', settings.action, text, value);
  572. module.action[ settings.action ](text, value);
  573. }
  574. else if( $.isFunction(settings.action) ) {
  575. module.verbose('Triggering user action', settings.action, text, value);
  576. settings.action(text, value);
  577. }
  578. else {
  579. module.error(error.action, settings.action);
  580. }
  581. },
  582. eventInModule: function(event, callback) {
  583. callback = callback || function(){};
  584. if( $(event.target).closest($module).size() === 0 ) {
  585. module.verbose('Triggering event', callback);
  586. callback();
  587. return true;
  588. }
  589. else {
  590. module.verbose('Event occurred in dropdown, canceling callback');
  591. return false;
  592. }
  593. },
  594. eventInMenu: function(event, callback) {
  595. callback = callback || function(){};
  596. if( $(event.target).closest($menu).size() === 0 ) {
  597. module.verbose('Triggering event', callback);
  598. callback();
  599. return true;
  600. }
  601. else {
  602. module.verbose('Event occurred in dropdown menu, canceling callback');
  603. return false;
  604. }
  605. }
  606. },
  607. action: {
  608. nothing: function() {},
  609. hide: function() {
  610. module.hide();
  611. },
  612. select: function(text, value) {
  613. value = (value !== undefined)
  614. ? value
  615. : text
  616. ;
  617. module.set.selected(value);
  618. module.set.value(value);
  619. module.hide();
  620. },
  621. activate: function(text, value) {
  622. value = (value !== undefined)
  623. ? value
  624. : text
  625. ;
  626. module.set.selected(value);
  627. module.set.value(value);
  628. module.hide();
  629. },
  630. combo: function(text, value) {
  631. value = (value !== undefined)
  632. ? value
  633. : text
  634. ;
  635. module.set.selected(value);
  636. module.set.value(value);
  637. module.hide();
  638. }
  639. },
  640. get: {
  641. text: function() {
  642. return $text.text();
  643. },
  644. value: function() {
  645. return ($input.size() > 0)
  646. ? $input.val()
  647. : $module.data(metadata.value)
  648. ;
  649. },
  650. inputEvent: function() {
  651. var
  652. input = $search[0]
  653. ;
  654. if(input) {
  655. return (input.oninput !== undefined)
  656. ? 'input'
  657. : (input.onpropertychange !== undefined)
  658. ? 'propertychange'
  659. : 'keyup'
  660. ;
  661. }
  662. return false;
  663. },
  664. selectValues: function() {
  665. var
  666. select = {
  667. values : {}
  668. }
  669. ;
  670. $module
  671. .find('option')
  672. .each(function() {
  673. var
  674. name = $(this).html(),
  675. value = ( $(this).attr('value') !== undefined )
  676. ? $(this).attr('value')
  677. : name
  678. ;
  679. if(value === '') {
  680. select.placeholder = name;
  681. }
  682. else {
  683. select.values[value] = name;
  684. }
  685. })
  686. ;
  687. module.debug('Retrieved values from select', select);
  688. return select;
  689. },
  690. item: function(value, strict) {
  691. var
  692. $selectedItem = false
  693. ;
  694. value = (value !== undefined)
  695. ? value
  696. : ( module.get.value() !== undefined)
  697. ? module.get.value()
  698. : module.get.text()
  699. ;
  700. strict = (value === '' || value === 0)
  701. ? true
  702. : strict || false
  703. ;
  704. if(value !== undefined) {
  705. $item
  706. .each(function() {
  707. var
  708. $choice = $(this),
  709. optionText = ( $choice.data(metadata.text) !== undefined )
  710. ? $choice.data(metadata.text)
  711. : (settings.preserveHTML)
  712. ? $choice.html()
  713. : $choice.text(),
  714. optionValue = ( $choice.data(metadata.value) !== undefined )
  715. ? $choice.data(metadata.value)
  716. : (typeof optionText === 'string')
  717. ? optionText.toLowerCase()
  718. : optionText
  719. ;
  720. if(strict) {
  721. module.verbose('Ambiguous dropdown value using strict type check', $choice, value);
  722. if( optionValue === value ) {
  723. $selectedItem = $(this);
  724. }
  725. else if( !$selectedItem && optionText === value ) {
  726. $selectedItem = $(this);
  727. }
  728. }
  729. else {
  730. if( optionValue == value ) {
  731. module.verbose('Found select item by value', optionValue, value);
  732. $selectedItem = $(this);
  733. }
  734. else if( !$selectedItem && optionText == value ) {
  735. module.verbose('Found select item by text', optionText, value);
  736. $selectedItem = $(this);
  737. }
  738. }
  739. })
  740. ;
  741. }
  742. else {
  743. value = module.get.text();
  744. }
  745. return $selectedItem || false;
  746. }
  747. },
  748. restore: {
  749. defaults: function() {
  750. module.restore.defaultText();
  751. module.restore.defaultValue();
  752. },
  753. defaultText: function() {
  754. var
  755. defaultText = $module.data(metadata.defaultText)
  756. ;
  757. module.debug('Restoring default text', defaultText);
  758. module.set.text(defaultText);
  759. },
  760. defaultValue: function() {
  761. var
  762. defaultValue = $module.data(metadata.defaultValue)
  763. ;
  764. if(defaultValue !== undefined) {
  765. module.debug('Restoring default value', defaultValue);
  766. module.set.selected(defaultValue);
  767. module.set.value(defaultValue);
  768. }
  769. }
  770. },
  771. save: {
  772. defaults: function() {
  773. module.save.defaultText();
  774. module.save.defaultValue();
  775. },
  776. defaultValue: function() {
  777. $module.data(metadata.defaultValue, module.get.value() );
  778. },
  779. defaultText: function() {
  780. $module.data(metadata.defaultText, $text.text() );
  781. }
  782. },
  783. set: {
  784. scrollPosition: function($item) {
  785. var
  786. $item = $item || module.get.item(),
  787. hasActive = ($item && $item.size() > 0),
  788. edgeTolerance = 5,
  789. offset,
  790. itemHeight,
  791. itemOffset,
  792. menuOffset,
  793. menuScroll,
  794. menuHeight,
  795. abovePage,
  796. belowPage
  797. ;
  798. if($item && hasActive) {
  799. menuHeight = $menu.height();
  800. itemHeight = $item.height();
  801. menuScroll = $menu.scrollTop();
  802. menuOffset = $menu.offset().top;
  803. itemOffset = $item.offset().top;
  804. offset = menuScroll - menuOffset + itemOffset;
  805. belowPage = menuScroll + menuHeight < (offset + edgeTolerance);
  806. abovePage = ((offset - edgeTolerance) < menuScroll);
  807. if(abovePage || belowPage) {
  808. module.debug('Scrolling to active item');
  809. $menu
  810. .scrollTop(offset)
  811. ;
  812. }
  813. }
  814. },
  815. text: function(text) {
  816. if(settings.action == 'combo') {
  817. module.debug('Changing combo button text', text, $combo);
  818. if(settings.preserveHTML) {
  819. $combo.html(text);
  820. }
  821. else {
  822. $combo.text(text);
  823. }
  824. }
  825. else if(settings.action !== 'select') {
  826. module.debug('Changing text', text, $text);
  827. $text
  828. .removeClass(className.filtered)
  829. .removeClass(className.placeholder)
  830. ;
  831. if(settings.preserveHTML) {
  832. $text.html(text);
  833. }
  834. else {
  835. $text.text(text);
  836. }
  837. }
  838. },
  839. value: function(value) {
  840. module.debug('Adding selected value to hidden input', value, $input);
  841. if($input.size() > 0) {
  842. $input
  843. .val(value)
  844. .trigger('change')
  845. ;
  846. }
  847. else {
  848. $module.data(metadata.value, value);
  849. }
  850. },
  851. active: function() {
  852. $module
  853. .addClass(className.active)
  854. ;
  855. },
  856. visible: function() {
  857. $module.addClass(className.visible);
  858. },
  859. selected: function(value) {
  860. var
  861. $selectedItem = module.get.item(value),
  862. selectedText
  863. ;
  864. if($selectedItem) {
  865. module.debug('Setting selected menu item to', $selectedItem);
  866. selectedText = ($selectedItem.data(metadata.text) !== undefined)
  867. ? $selectedItem.data(metadata.text)
  868. : (settings.preserveHTML)
  869. ? $selectedItem.html()
  870. : $selectedItem.text()
  871. ;
  872. module.remove.activeItem();
  873. module.remove.selectedItem();
  874. $selectedItem
  875. .addClass(className.active)
  876. .addClass(className.selected)
  877. ;
  878. module.set.text(selectedText);
  879. }
  880. }
  881. },
  882. remove: {
  883. active: function() {
  884. $module.removeClass(className.active);
  885. },
  886. visible: function() {
  887. $module.removeClass(className.visible);
  888. },
  889. activeItem: function() {
  890. $item.removeClass(className.active);
  891. },
  892. filteredItem: function() {
  893. $item.removeClass(className.filtered);
  894. },
  895. selectedItem: function() {
  896. $item.removeClass(className.selected);
  897. }
  898. },
  899. is: {
  900. search: function() {
  901. return $module.hasClass(className.search);
  902. },
  903. searchable: function() {
  904. return ($search.size() > 0);
  905. },
  906. searchSelection: function() {
  907. return ( module.is.searchable() && $search.parent().is($module) );
  908. },
  909. selection: function() {
  910. return $module.hasClass(className.selection);
  911. },
  912. animated: function($subMenu) {
  913. return ($subMenu)
  914. ? $subMenu.is(':animated') || $subMenu.transition && $subMenu.transition('is animating')
  915. : $menu.is(':animated') || $menu.transition && $menu.transition('is animating')
  916. ;
  917. },
  918. active: function() {
  919. return $module.hasClass(className.active);
  920. },
  921. visible: function($subMenu) {
  922. return ($subMenu)
  923. ? $subMenu.is(':visible')
  924. : $menu.is(':visible')
  925. ;
  926. },
  927. hidden: function($subMenu) {
  928. return ($subMenu)
  929. ? $subMenu.is(':hidden')
  930. : $menu.is(':hidden')
  931. ;
  932. }
  933. },
  934. can: {
  935. click: function() {
  936. return (hasTouch || settings.on == 'click');
  937. },
  938. show: function() {
  939. return !$module.hasClass(className.disabled);
  940. }
  941. },
  942. animate: {
  943. show: function(callback, $subMenu) {
  944. var
  945. $currentMenu = $subMenu || $menu,
  946. start = ($subMenu)
  947. ? function() {}
  948. : function() {
  949. module.hideOthers();
  950. module.set.active();
  951. module.set.scrollPosition();
  952. }
  953. ;
  954. callback = callback || function(){};
  955. module.verbose('Doing menu show animation', $currentMenu);
  956. if( module.is.hidden($currentMenu) ) {
  957. if(settings.transition == 'none') {
  958. $.proxy(callback, element)();
  959. }
  960. else if($.fn.transition !== undefined && $module.transition('is supported')) {
  961. $currentMenu
  962. .transition({
  963. animation : settings.transition + ' in',
  964. debug : settings.debug,
  965. verbose : settings.verbose,
  966. duration : settings.duration,
  967. queue : true,
  968. onStart : start,
  969. onComplete : function() {
  970. $.proxy(callback, element)();
  971. }
  972. })
  973. ;
  974. }
  975. else if(settings.transition == 'slide down') {
  976. start();
  977. $currentMenu
  978. .hide()
  979. .clearQueue()
  980. .children()
  981. .clearQueue()
  982. .css('opacity', 0)
  983. .delay(50)
  984. .animate({
  985. opacity : 1
  986. }, settings.duration, 'easeOutQuad', module.event.resetStyle)
  987. .end()
  988. .slideDown(100, 'easeOutQuad', function() {
  989. $.proxy(module.event.resetStyle, this)();
  990. $.proxy(callback, element)();
  991. })
  992. ;
  993. }
  994. else if(settings.transition == 'fade') {
  995. start();
  996. $currentMenu
  997. .hide()
  998. .clearQueue()
  999. .fadeIn(settings.duration, function() {
  1000. $.proxy(module.event.resetStyle, this)();
  1001. $.proxy(callback, element)();
  1002. })
  1003. ;
  1004. }
  1005. else {
  1006. module.error(error.transition, settings.transition);
  1007. }
  1008. }
  1009. },
  1010. hide: function(callback, $subMenu) {
  1011. var
  1012. $currentMenu = $subMenu || $menu,
  1013. start = ($subMenu)
  1014. ? function() {}
  1015. : function() {
  1016. if( module.can.click() ) {
  1017. module.unbind.intent();
  1018. }
  1019. module.focusSearch();
  1020. module.hideSubMenus();
  1021. module.remove.active();
  1022. }
  1023. ;
  1024. callback = callback || function(){};
  1025. if( module.is.visible($currentMenu) ) {
  1026. module.verbose('Doing menu hide animation', $currentMenu);
  1027. if(settings.transition == 'none') {
  1028. $.proxy(callback, element)();
  1029. }
  1030. else if($.fn.transition !== undefined && $module.transition('is supported')) {
  1031. $currentMenu
  1032. .transition({
  1033. animation : settings.transition + ' out',
  1034. duration : settings.duration,
  1035. debug : settings.debug,
  1036. verbose : settings.verbose,
  1037. queue : true,
  1038. onStart : start,
  1039. onComplete : function() {
  1040. $.proxy(callback, element)();
  1041. }
  1042. })
  1043. ;
  1044. }
  1045. else if(settings.transition == 'slide down') {
  1046. start();
  1047. $currentMenu
  1048. .show()
  1049. .clearQueue()
  1050. .children()
  1051. .clearQueue()
  1052. .css('opacity', 1)
  1053. .animate({
  1054. opacity : 0
  1055. }, 100, 'easeOutQuad', module.event.resetStyle)
  1056. .end()
  1057. .delay(50)
  1058. .slideUp(100, 'easeOutQuad', function() {
  1059. $.proxy(module.event.resetStyle, this)();
  1060. $.proxy(callback, element)();
  1061. })
  1062. ;
  1063. }
  1064. else if(settings.transition == 'fade') {
  1065. start();
  1066. $currentMenu
  1067. .show()
  1068. .clearQueue()
  1069. .fadeOut(150, function() {
  1070. $.proxy(module.event.resetStyle, this)();
  1071. $.proxy(callback, element)();
  1072. })
  1073. ;
  1074. }
  1075. else {
  1076. module.error(error.transition);
  1077. }
  1078. }
  1079. }
  1080. },
  1081. delay: {
  1082. show: function() {
  1083. module.verbose('Delaying show event to ensure user intent');
  1084. clearTimeout(module.timer);
  1085. module.timer = setTimeout(module.show, settings.delay.show);
  1086. },
  1087. hide: function() {
  1088. module.verbose('Delaying hide event to ensure user intent');
  1089. clearTimeout(module.timer);
  1090. module.timer = setTimeout(module.hide, settings.delay.hide);
  1091. }
  1092. },
  1093. setting: function(name, value) {
  1094. module.debug('Changing setting', name, value);
  1095. if( $.isPlainObject(name) ) {
  1096. $.extend(true, settings, name);
  1097. }
  1098. else if(value !== undefined) {
  1099. settings[name] = value;
  1100. }
  1101. else {
  1102. return settings[name];
  1103. }
  1104. },
  1105. internal: function(name, value) {
  1106. if( $.isPlainObject(name) ) {
  1107. $.extend(true, module, name);
  1108. }
  1109. else if(value !== undefined) {
  1110. module[name] = value;
  1111. }
  1112. else {
  1113. return module[name];
  1114. }
  1115. },
  1116. debug: function() {
  1117. if(settings.debug) {
  1118. if(settings.performance) {
  1119. module.performance.log(arguments);
  1120. }
  1121. else {
  1122. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  1123. module.debug.apply(console, arguments);
  1124. }
  1125. }
  1126. },
  1127. verbose: function() {
  1128. if(settings.verbose && settings.debug) {
  1129. if(settings.performance) {
  1130. module.performance.log(arguments);
  1131. }
  1132. else {
  1133. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  1134. module.verbose.apply(console, arguments);
  1135. }
  1136. }
  1137. },
  1138. error: function() {
  1139. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  1140. module.error.apply(console, arguments);
  1141. },
  1142. performance: {
  1143. log: function(message) {
  1144. var
  1145. currentTime,
  1146. executionTime,
  1147. previousTime
  1148. ;
  1149. if(settings.performance) {
  1150. currentTime = new Date().getTime();
  1151. previousTime = time || currentTime;
  1152. executionTime = currentTime - previousTime;
  1153. time = currentTime;
  1154. performance.push({
  1155. 'Name' : message[0],
  1156. 'Arguments' : [].slice.call(message, 1) || '',
  1157. 'Element' : element,
  1158. 'Execution Time' : executionTime
  1159. });
  1160. }
  1161. clearTimeout(module.performance.timer);
  1162. module.performance.timer = setTimeout(module.performance.display, 100);
  1163. },
  1164. display: function() {
  1165. var
  1166. title = settings.name + ':',
  1167. totalTime = 0
  1168. ;
  1169. time = false;
  1170. clearTimeout(module.performance.timer);
  1171. $.each(performance, function(index, data) {
  1172. totalTime += data['Execution Time'];
  1173. });
  1174. title += ' ' + totalTime + 'ms';
  1175. if(moduleSelector) {
  1176. title += ' \'' + moduleSelector + '\'';
  1177. }
  1178. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  1179. console.groupCollapsed(title);
  1180. if(console.table) {
  1181. console.table(performance);
  1182. }
  1183. else {
  1184. $.each(performance, function(index, data) {
  1185. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  1186. });
  1187. }
  1188. console.groupEnd();
  1189. }
  1190. performance = [];
  1191. }
  1192. },
  1193. invoke: function(query, passedArguments, context) {
  1194. var
  1195. object = instance,
  1196. maxDepth,
  1197. found,
  1198. response
  1199. ;
  1200. passedArguments = passedArguments || queryArguments;
  1201. context = element || context;
  1202. if(typeof query == 'string' && object !== undefined) {
  1203. query = query.split(/[\. ]/);
  1204. maxDepth = query.length - 1;
  1205. $.each(query, function(depth, value) {
  1206. var camelCaseValue = (depth != maxDepth)
  1207. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  1208. : query
  1209. ;
  1210. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  1211. object = object[camelCaseValue];
  1212. }
  1213. else if( object[camelCaseValue] !== undefined ) {
  1214. found = object[camelCaseValue];
  1215. return false;
  1216. }
  1217. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  1218. object = object[value];
  1219. }
  1220. else if( object[value] !== undefined ) {
  1221. found = object[value];
  1222. return false;
  1223. }
  1224. else {
  1225. module.error(error.method, query);
  1226. return false;
  1227. }
  1228. });
  1229. }
  1230. if ( $.isFunction( found ) ) {
  1231. response = found.apply(context, passedArguments);
  1232. }
  1233. else if(found !== undefined) {
  1234. response = found;
  1235. }
  1236. if($.isArray(returnedValue)) {
  1237. returnedValue.push(response);
  1238. }
  1239. else if(returnedValue !== undefined) {
  1240. returnedValue = [returnedValue, response];
  1241. }
  1242. else if(response !== undefined) {
  1243. returnedValue = response;
  1244. }
  1245. return found;
  1246. }
  1247. };
  1248. if(methodInvoked) {
  1249. if(instance === undefined) {
  1250. module.initialize();
  1251. }
  1252. module.invoke(query);
  1253. }
  1254. else {
  1255. if(instance !== undefined) {
  1256. module.destroy();
  1257. }
  1258. module.initialize();
  1259. }
  1260. })
  1261. ;
  1262. return (returnedValue !== undefined)
  1263. ? returnedValue
  1264. : this
  1265. ;
  1266. };
  1267. $.fn.dropdown.settings = {
  1268. debug : false,
  1269. verbose : true,
  1270. performance : true,
  1271. on : 'click',
  1272. action : 'activate',
  1273. allowTab : true,
  1274. fullTextSearch : true,
  1275. preserveHTML : true,
  1276. delay : {
  1277. show : 200,
  1278. hide : 300,
  1279. touch : 50
  1280. },
  1281. transition : 'slide down',
  1282. duration : 250,
  1283. /* Callbacks */
  1284. onChange : function(value, text){},
  1285. onShow : function(){},
  1286. onHide : function(){},
  1287. /* Component */
  1288. name : 'Dropdown',
  1289. namespace : 'dropdown',
  1290. error : {
  1291. action : 'You called a dropdown action that was not defined',
  1292. method : 'The method you called is not defined.',
  1293. transition : 'The requested transition was not found'
  1294. },
  1295. metadata: {
  1296. defaultText : 'defaultText',
  1297. defaultValue : 'defaultValue',
  1298. text : 'text',
  1299. value : 'value'
  1300. },
  1301. selector : {
  1302. dropdown : '.ui.dropdown',
  1303. text : '> .text:not(.icon)',
  1304. input : '> input[type="hidden"], > select',
  1305. search : '> .search, .menu > .search > input, .menu > input.search',
  1306. menu : '.menu',
  1307. item : '.item'
  1308. },
  1309. className : {
  1310. active : 'active',
  1311. animating : 'animating',
  1312. disabled : 'disabled',
  1313. dropdown : 'ui dropdown',
  1314. filtered : 'filtered',
  1315. menu : 'menu',
  1316. placeholder : 'default',
  1317. search : 'search',
  1318. selected : 'selected',
  1319. selection : 'selection',
  1320. visible : 'visible'
  1321. }
  1322. };
  1323. /* Templates */
  1324. $.fn.dropdown.settings.templates = {
  1325. menu: function(select) {
  1326. var
  1327. placeholder = select.placeholder || false,
  1328. values = select.values || {},
  1329. html = ''
  1330. ;
  1331. $.each(select.values, function(value, name) {
  1332. if(value === name) {
  1333. html += '<div class="item">' + name + '</div>';
  1334. }
  1335. else {
  1336. html += '<div class="item" data-value="' + value + '">' + name + '</div>';
  1337. }
  1338. });
  1339. return html;
  1340. },
  1341. dropdown: function(select) {
  1342. var
  1343. placeholder = select.placeholder || false,
  1344. values = select.values || {},
  1345. html = ''
  1346. ;
  1347. html += '<i class="dropdown icon"></i>';
  1348. if(select.placeholder) {
  1349. html += '<div class="default text">' + placeholder + '</div>';
  1350. }
  1351. else {
  1352. html += '<div class="text"></div>';
  1353. }
  1354. html += '<div class="menu">';
  1355. $.each(select.values, function(value, name) {
  1356. if(value === name) {
  1357. html += '<div class="item">' + name + '</div>';
  1358. }
  1359. else {
  1360. html += '<div class="item" data-value="' + value + '">' + name + '</div>';
  1361. }
  1362. });
  1363. html += '</div>';
  1364. return html;
  1365. }
  1366. };
  1367. /* Dependencies */
  1368. $.extend( $.easing, {
  1369. easeOutQuad: function (x, t, b, c, d) {
  1370. return -c *(t/=d)*(t-2) + b;
  1371. },
  1372. });
  1373. })( jQuery, window , document );