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.

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