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.

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