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.

1439 lines
46 KiB

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