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.

1495 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
  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. $activeSubMenu = $subMenus.has(selector.item + '.' + className.active)
  196. ;
  197. $subMenus
  198. .not($activeSubMenu)
  199. .removeClass(className.visible)
  200. .removeAttr('style')
  201. ;
  202. },
  203. bind: {
  204. keyboardEvents: function() {
  205. module.debug('Binding keyboard events');
  206. $module
  207. .on('keydown' + eventNamespace, module.event.keydown)
  208. ;
  209. if( module.is.searchable() ) {
  210. $module
  211. .on(module.get.inputEvent(), selector.search, module.event.input)
  212. ;
  213. }
  214. },
  215. touchEvents: function() {
  216. module.debug('Touch device detected binding additional touch events');
  217. if( module.is.searchSelection() ) {
  218. // do nothing special yet
  219. }
  220. else {
  221. $module
  222. .on('touchstart' + eventNamespace, module.event.test.toggle)
  223. ;
  224. }
  225. $menu
  226. .on('touchstart' + eventNamespace, selector.item, module.event.item.mouseenter)
  227. ;
  228. },
  229. mouseEvents: function() {
  230. module.verbose('Mouse detected binding mouse events');
  231. if( module.is.searchSelection() ) {
  232. $module
  233. .on('mousedown' + eventNamespace, selector.menu, module.event.menu.activate)
  234. .on('mouseup' + eventNamespace, selector.menu, module.event.menu.deactivate)
  235. .on('focus' + eventNamespace, selector.search, module.event.searchFocus)
  236. .on('click' + eventNamespace, selector.search, module.show)
  237. .on('blur' + eventNamespace, selector.search, module.event.searchBlur)
  238. ;
  239. }
  240. else {
  241. if(settings.on == 'click') {
  242. $module
  243. .on('click' + eventNamespace, module.event.test.toggle)
  244. ;
  245. }
  246. else if(settings.on == 'hover') {
  247. $module
  248. .on('mouseenter' + eventNamespace, module.delay.show)
  249. .on('mouseleave' + eventNamespace, module.delay.hide)
  250. ;
  251. }
  252. else {
  253. $module
  254. .on(settings.on + eventNamespace, module.toggle)
  255. ;
  256. }
  257. $module
  258. .on('mousedown' + eventNamespace, module.event.mousedown)
  259. .on('mouseup' + eventNamespace, module.event.mouseup)
  260. .on('focus' + eventNamespace, module.event.focus)
  261. .on('blur' + eventNamespace, module.event.blur)
  262. ;
  263. }
  264. $menu
  265. .on('mouseenter' + eventNamespace, selector.item, module.event.item.mouseenter)
  266. .on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave)
  267. .on('click' + eventNamespace, selector.item, module.event.item.click)
  268. ;
  269. },
  270. intent: function() {
  271. module.verbose('Binding hide intent event to document');
  272. if(hasTouch) {
  273. $document
  274. .on('touchstart' + eventNamespace, module.event.test.touch)
  275. .on('touchmove' + eventNamespace, module.event.test.touch)
  276. ;
  277. }
  278. $document
  279. .on('click' + eventNamespace, module.event.test.hide)
  280. ;
  281. }
  282. },
  283. unbind: {
  284. intent: function() {
  285. module.verbose('Removing hide intent event from document');
  286. if(hasTouch) {
  287. $document
  288. .off('touchstart' + eventNamespace)
  289. .off('touchmove' + eventNamespace)
  290. ;
  291. }
  292. $document
  293. .off('click' + eventNamespace)
  294. ;
  295. }
  296. },
  297. filter: function(searchTerm) {
  298. var
  299. $results = $(),
  300. exactRegExp = new RegExp('(?:\s|^)' + searchTerm, 'i'),
  301. fullTextRegExp = new RegExp(searchTerm, 'i'),
  302. allItemsFiltered,
  303. $filteredItems
  304. ;
  305. $item
  306. .each(function(){
  307. var
  308. $choice = $(this),
  309. text = ( $choice.data(metadata.text) !== undefined )
  310. ? $choice.data(metadata.text)
  311. : (settings.preserveHTML)
  312. ? $choice.html()
  313. : $choice.text(),
  314. value = ( $choice.data(metadata.value) !== undefined)
  315. ? $choice.data(metadata.value)
  316. : (typeof text === 'string')
  317. ? text.toLowerCase()
  318. : text
  319. ;
  320. if( exactRegExp.test( text ) || exactRegExp.test( value ) ) {
  321. $results = $results.add($choice);
  322. }
  323. else if(settings.fullTextSearch) {
  324. if( fullTextRegExp.test( text ) || fullTextRegExp.test( value ) ) {
  325. $results = $results.add($choice);
  326. }
  327. }
  328. })
  329. ;
  330. $filteredItems = $item.not($results);
  331. allItemsFiltered = ($filteredItems.size() == $item.size());
  332. module.remove.filteredItem();
  333. module.remove.selectedItem();
  334. $filteredItems
  335. .addClass(className.filtered)
  336. ;
  337. $item
  338. .not('.' + className.filtered)
  339. .eq(0)
  340. .addClass(className.selected)
  341. ;
  342. if(allItemsFiltered) {
  343. module.hide();
  344. }
  345. },
  346. focusSearch: function() {
  347. if( module.is.search() ) {
  348. $search
  349. .focus()
  350. ;
  351. }
  352. },
  353. event: {
  354. // prevents focus callback from occuring on mousedown
  355. mousedown: function() {
  356. activated = true;
  357. },
  358. mouseup: function() {
  359. activated = false;
  360. },
  361. focus: function() {
  362. if(!activated && module.is.hidden()) {
  363. module.show();
  364. }
  365. },
  366. blur: function(event) {
  367. if(!activated) {
  368. module.hide();
  369. }
  370. },
  371. searchFocus: function() {
  372. activated = true;
  373. module.show();
  374. },
  375. searchBlur: function(event) {
  376. if(!itemActivated) {
  377. module.hide();
  378. }
  379. },
  380. input: function(event) {
  381. var
  382. query = $search.val()
  383. ;
  384. if(module.is.searchSelection()) {
  385. if( module.can.show() ) {
  386. module.show();
  387. }
  388. module.set.filtered();
  389. }
  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. module.hide();
  418. }
  419. // result shortcuts
  420. if(module.is.visible()) {
  421. if(pressedKey == keys.enter && hasSelectedItem) {
  422. module.verbose('Enter key pressed, choosing selected item');
  423. $.proxy(module.event.item.click, $selectedItem)(event);
  424. event.preventDefault();
  425. return false;
  426. }
  427. else if(pressedKey == keys.upArrow) {
  428. if(!hasSelectedItem) {
  429. $nextItem = $visibleItems.eq(0);
  430. }
  431. else {
  432. $nextItem = $selectedItem.prevAll(selector.item + ':not(.' + className.filtered + ')').eq(0);
  433. }
  434. if(currentIndex !== 0) {
  435. module.verbose('Up key pressed, changing active item');
  436. $item
  437. .removeClass(selectedClass)
  438. ;
  439. $nextItem
  440. .addClass(selectedClass)
  441. ;
  442. module.set.scrollPosition($nextItem);
  443. }
  444. event.preventDefault();
  445. }
  446. else if(pressedKey == keys.downArrow) {
  447. if(!hasSelectedItem) {
  448. $nextItem = $visibleItems.eq(0);
  449. }
  450. else {
  451. $nextItem = $selectedItem.nextAll(selector.item + ':not(.' + className.filtered + ')').eq(0);
  452. }
  453. if(currentIndex + 1 < $visibleItems.size() ) {
  454. module.verbose('Down key pressed, changing active item');
  455. $item
  456. .removeClass(selectedClass)
  457. ;
  458. $nextItem
  459. .addClass(selectedClass)
  460. ;
  461. module.set.scrollPosition($nextItem);
  462. }
  463. event.preventDefault();
  464. }
  465. }
  466. else {
  467. if(pressedKey == keys.enter) {
  468. module.show();
  469. }
  470. }
  471. },
  472. test: {
  473. toggle: function(event) {
  474. if( module.determine.eventInMenu(event, module.toggle) ) {
  475. event.preventDefault();
  476. }
  477. },
  478. touch: function(event) {
  479. module.determine.eventInMenu(event, function() {
  480. if(event.type == 'touchstart') {
  481. module.timer = setTimeout(module.hide, settings.delay.touch);
  482. }
  483. else if(event.type == 'touchmove') {
  484. clearTimeout(module.timer);
  485. }
  486. });
  487. event.stopPropagation();
  488. },
  489. hide: function(event) {
  490. module.determine.eventInModule(event, module.hide);
  491. }
  492. },
  493. menu: {
  494. activate: function() {
  495. itemActivated = true;
  496. },
  497. deactivate: function() {
  498. itemActivated = false;
  499. }
  500. },
  501. item: {
  502. mouseenter: function(event) {
  503. var
  504. $currentMenu = $(this).find(selector.menu),
  505. $otherMenus = $(this).siblings(selector.item).children(selector.menu)
  506. ;
  507. if( $currentMenu.size() > 0 ) {
  508. clearTimeout(module.itemTimer);
  509. module.itemTimer = setTimeout(function() {
  510. module.animate.hide(false, $otherMenus);
  511. module.verbose('Showing sub-menu', $currentMenu);
  512. module.animate.show(false, $currentMenu);
  513. }, settings.delay.show * 2);
  514. event.preventDefault();
  515. }
  516. },
  517. mouseleave: function(event) {
  518. var
  519. $currentMenu = $(this).find(selector.menu)
  520. ;
  521. if($currentMenu.size() > 0) {
  522. clearTimeout(module.itemTimer);
  523. module.itemTimer = setTimeout(function() {
  524. module.verbose('Hiding sub-menu', $currentMenu);
  525. module.animate.hide(false, $currentMenu);
  526. }, settings.delay.hide);
  527. }
  528. },
  529. click: function (event) {
  530. var
  531. $choice = $(this),
  532. text = ( $choice.data(metadata.text) !== undefined )
  533. ? $choice.data(metadata.text)
  534. : (settings.preserveHTML)
  535. ? $choice.html()
  536. : $choice.text(),
  537. value = ( $choice.data(metadata.value) !== undefined)
  538. ? $choice.data(metadata.value)
  539. : (typeof text === 'string')
  540. ? text.toLowerCase()
  541. : text,
  542. callback = function() {
  543. module.remove.searchTerm();
  544. module.remove.filteredItem();
  545. module.determine.selectAction(text, value);
  546. $.proxy(settings.onChange, element)(value, text, $choice);
  547. },
  548. openingSubMenu = ($choice.find(selector.menu).size() > 0)
  549. ;
  550. if( !openingSubMenu ) {
  551. callback();
  552. }
  553. }
  554. },
  555. resetStyle: function() {
  556. $(this).removeAttr('style');
  557. }
  558. },
  559. determine: {
  560. selectAction: function(text, value) {
  561. module.verbose('Determining action', settings.action);
  562. if( $.isFunction( module.action[settings.action] ) ) {
  563. module.verbose('Triggering preset action', settings.action, text, value);
  564. module.action[ settings.action ](text, value);
  565. }
  566. else if( $.isFunction(settings.action) ) {
  567. module.verbose('Triggering user action', settings.action, text, value);
  568. settings.action(text, value);
  569. }
  570. else {
  571. module.error(error.action, settings.action);
  572. }
  573. },
  574. eventInModule: function(event, callback) {
  575. callback = callback || function(){};
  576. if( $(event.target).closest($module).size() === 0 ) {
  577. module.verbose('Triggering event', callback);
  578. callback();
  579. return true;
  580. }
  581. else {
  582. module.verbose('Event occurred in dropdown, canceling callback');
  583. return false;
  584. }
  585. },
  586. eventInMenu: function(event, callback) {
  587. callback = callback || function(){};
  588. if( $(event.target).closest($menu).size() === 0 ) {
  589. module.verbose('Triggering event', callback);
  590. callback();
  591. return true;
  592. }
  593. else {
  594. module.verbose('Event occurred in dropdown menu, canceling callback');
  595. return false;
  596. }
  597. }
  598. },
  599. action: {
  600. nothing: function() {},
  601. hide: function() {
  602. module.hide();
  603. },
  604. select: function(text, value) {
  605. value = (value !== undefined)
  606. ? value
  607. : text
  608. ;
  609. module.set.selected(value);
  610. module.set.value(value);
  611. module.hide();
  612. },
  613. activate: 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. combo: 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. },
  632. get: {
  633. text: function() {
  634. return $text.text();
  635. },
  636. value: function() {
  637. return ($input.size() > 0)
  638. ? $input.val()
  639. : $module.data(metadata.value)
  640. ;
  641. },
  642. inputEvent: function() {
  643. var
  644. input = $search[0]
  645. ;
  646. if(input) {
  647. return (input.oninput !== undefined)
  648. ? 'input'
  649. : (input.onpropertychange !== undefined)
  650. ? 'propertychange'
  651. : 'keyup'
  652. ;
  653. }
  654. return false;
  655. },
  656. selectValues: function() {
  657. var
  658. select = {
  659. values : {}
  660. }
  661. ;
  662. $module
  663. .find('option')
  664. .each(function() {
  665. var
  666. name = $(this).html(),
  667. value = ( $(this).attr('value') !== undefined )
  668. ? $(this).attr('value')
  669. : name
  670. ;
  671. if(value === '') {
  672. select.placeholder = name;
  673. }
  674. else {
  675. select.values[value] = name;
  676. }
  677. })
  678. ;
  679. module.debug('Retrieved values from select', select);
  680. return select;
  681. },
  682. item: function(value, strict) {
  683. var
  684. $selectedItem = false
  685. ;
  686. value = (value !== undefined)
  687. ? value
  688. : ( module.get.value() !== undefined)
  689. ? module.get.value()
  690. : module.get.text()
  691. ;
  692. strict = (value === '' || value === 0)
  693. ? true
  694. : strict || false
  695. ;
  696. if(value !== undefined) {
  697. $item
  698. .each(function() {
  699. var
  700. $choice = $(this),
  701. optionText = ( $choice.data(metadata.text) !== undefined )
  702. ? $choice.data(metadata.text)
  703. : (settings.preserveHTML)
  704. ? $choice.html()
  705. : $choice.text(),
  706. optionValue = ( $choice.data(metadata.value) !== undefined )
  707. ? $choice.data(metadata.value)
  708. : (typeof optionText === 'string')
  709. ? optionText.toLowerCase()
  710. : optionText
  711. ;
  712. if(strict) {
  713. module.verbose('Ambiguous dropdown value using strict type check', $choice, value);
  714. if( optionValue === value ) {
  715. $selectedItem = $(this);
  716. }
  717. else if( !$selectedItem && optionText === value ) {
  718. $selectedItem = $(this);
  719. }
  720. }
  721. else {
  722. if( optionValue == value ) {
  723. module.verbose('Found select item by value', optionValue, value);
  724. $selectedItem = $(this);
  725. }
  726. else if( !$selectedItem && optionText == value ) {
  727. module.verbose('Found select item by text', optionText, value);
  728. $selectedItem = $(this);
  729. }
  730. }
  731. })
  732. ;
  733. }
  734. else {
  735. value = module.get.text();
  736. }
  737. return $selectedItem || false;
  738. }
  739. },
  740. restore: {
  741. defaults: function() {
  742. module.restore.defaultText();
  743. module.restore.defaultValue();
  744. },
  745. defaultText: function() {
  746. var
  747. defaultText = $module.data(metadata.defaultText)
  748. ;
  749. module.debug('Restoring default text', defaultText);
  750. module.set.text(defaultText);
  751. },
  752. defaultValue: function() {
  753. var
  754. defaultValue = $module.data(metadata.defaultValue)
  755. ;
  756. if(defaultValue !== undefined) {
  757. module.debug('Restoring default value', defaultValue);
  758. module.set.selected(defaultValue);
  759. module.set.value(defaultValue);
  760. }
  761. }
  762. },
  763. save: {
  764. defaults: function() {
  765. module.save.defaultText();
  766. module.save.defaultValue();
  767. },
  768. defaultValue: function() {
  769. $module.data(metadata.defaultValue, module.get.value() );
  770. },
  771. defaultText: function() {
  772. $module.data(metadata.defaultText, $text.text() );
  773. }
  774. },
  775. set: {
  776. filtered: function() {
  777. $text.addClass(className.filtered);
  778. },
  779. tabbable: function() {
  780. if( module.is.searchable() ) {
  781. module.debug('Searchable dropdown initialized');
  782. $search
  783. .val('')
  784. .attr('tabindex', 0)
  785. ;
  786. $menu
  787. .attr('tabindex', '-1')
  788. ;
  789. }
  790. else {
  791. module.debug('Simple selection dropdown initialized');
  792. if(!$module.attr('tabindex') ) {
  793. $module
  794. .attr('tabindex', 0)
  795. ;
  796. $menu
  797. .attr('tabindex', '-1')
  798. ;
  799. }
  800. }
  801. },
  802. scrollPosition: function($item) {
  803. var
  804. $item = $item || module.get.item(),
  805. hasActive = ($item && $item.size() > 0),
  806. edgeTolerance = 5,
  807. offset,
  808. itemHeight,
  809. itemOffset,
  810. menuOffset,
  811. menuScroll,
  812. menuHeight,
  813. abovePage,
  814. belowPage
  815. ;
  816. if($item && hasActive) {
  817. menuHeight = $menu.height();
  818. itemHeight = $item.height();
  819. menuScroll = $menu.scrollTop();
  820. menuOffset = $menu.offset().top;
  821. itemOffset = $item.offset().top;
  822. offset = menuScroll - menuOffset + itemOffset;
  823. belowPage = menuScroll + menuHeight < (offset + edgeTolerance);
  824. abovePage = ((offset - edgeTolerance) < menuScroll);
  825. if(abovePage || belowPage) {
  826. module.debug('Scrolling to active item');
  827. $menu
  828. .scrollTop(offset)
  829. ;
  830. }
  831. }
  832. },
  833. text: function(text) {
  834. if(settings.action == 'combo') {
  835. module.debug('Changing combo button text', text, $combo);
  836. if(settings.preserveHTML) {
  837. $combo.html(text);
  838. }
  839. else {
  840. $combo.text(text);
  841. }
  842. }
  843. else if(settings.action !== 'select') {
  844. module.debug('Changing text', text, $text);
  845. $text
  846. .removeClass(className.filtered)
  847. .removeClass(className.placeholder)
  848. ;
  849. if(settings.preserveHTML) {
  850. $text.html(text);
  851. }
  852. else {
  853. $text.text(text);
  854. }
  855. }
  856. },
  857. value: function(value) {
  858. module.debug('Adding selected value to hidden input', value, $input);
  859. if($input.size() > 0) {
  860. $input
  861. .val(value)
  862. .trigger('change')
  863. ;
  864. }
  865. else {
  866. $module.data(metadata.value, value);
  867. }
  868. },
  869. active: function() {
  870. $module
  871. .addClass(className.active)
  872. ;
  873. },
  874. visible: function() {
  875. $module.addClass(className.visible);
  876. },
  877. selected: function(value) {
  878. var
  879. $selectedItem = module.get.item(value),
  880. selectedText
  881. ;
  882. if($selectedItem) {
  883. module.debug('Setting selected menu item to', $selectedItem);
  884. selectedText = ($selectedItem.data(metadata.text) !== undefined)
  885. ? $selectedItem.data(metadata.text)
  886. : (settings.preserveHTML)
  887. ? $selectedItem.html()
  888. : $selectedItem.text()
  889. ;
  890. module.remove.activeItem();
  891. module.remove.selectedItem();
  892. $selectedItem
  893. .addClass(className.active)
  894. .addClass(className.selected)
  895. ;
  896. module.set.text(selectedText);
  897. }
  898. }
  899. },
  900. remove: {
  901. active: function() {
  902. $module.removeClass(className.active);
  903. },
  904. visible: function() {
  905. $module.removeClass(className.visible);
  906. },
  907. activeItem: function() {
  908. $item.removeClass(className.active);
  909. },
  910. filteredItem: function() {
  911. $item.removeClass(className.filtered);
  912. },
  913. searchTerm: function() {
  914. $search.val('');
  915. },
  916. selectedItem: function() {
  917. $item.removeClass(className.selected);
  918. },
  919. tabbable: function() {
  920. if( module.is.searchable() ) {
  921. module.debug('Searchable dropdown initialized');
  922. $search
  923. .attr('tabindex', '-1')
  924. ;
  925. $menu
  926. .attr('tabindex', '-1')
  927. ;
  928. }
  929. else {
  930. module.debug('Simple selection dropdown initialized');
  931. $module
  932. .attr('tabindex', '-1')
  933. ;
  934. $menu
  935. .attr('tabindex', '-1')
  936. ;
  937. }
  938. }
  939. },
  940. is: {
  941. search: function() {
  942. return $module.hasClass(className.search);
  943. },
  944. searchable: function() {
  945. return ($search.size() > 0);
  946. },
  947. searchSelection: function() {
  948. return ( module.is.searchable() && $search.parent().is($module) );
  949. },
  950. selection: function() {
  951. return $module.hasClass(className.selection);
  952. },
  953. animated: function($subMenu) {
  954. return ($subMenu)
  955. ? $subMenu.is(':animated') || $subMenu.transition && $subMenu.transition('is animating')
  956. : $menu.is(':animated') || $menu.transition && $menu.transition('is animating')
  957. ;
  958. },
  959. active: function() {
  960. return $module.hasClass(className.active);
  961. },
  962. visible: function($subMenu) {
  963. return ($subMenu)
  964. ? $subMenu.is(':visible')
  965. : $menu.is(':visible')
  966. ;
  967. },
  968. hidden: function($subMenu) {
  969. return ($subMenu)
  970. ? $subMenu.is(':hidden')
  971. : $menu.is(':hidden')
  972. ;
  973. }
  974. },
  975. can: {
  976. click: function() {
  977. return (hasTouch || settings.on == 'click');
  978. },
  979. show: function() {
  980. return !$module.hasClass(className.disabled);
  981. }
  982. },
  983. animate: {
  984. show: function(callback, $subMenu) {
  985. var
  986. $currentMenu = $subMenu || $menu,
  987. start = ($subMenu)
  988. ? function() {}
  989. : function() {
  990. module.hideOthers();
  991. module.set.active();
  992. module.set.scrollPosition();
  993. }
  994. ;
  995. callback = callback || function(){};
  996. module.verbose('Doing menu show animation', $currentMenu);
  997. if( module.is.hidden($currentMenu) ) {
  998. if(settings.transition == 'none') {
  999. $.proxy(callback, element)();
  1000. }
  1001. else if($.fn.transition !== undefined && $module.transition('is supported')) {
  1002. $currentMenu
  1003. .transition({
  1004. animation : settings.transition + ' in',
  1005. debug : settings.debug,
  1006. verbose : settings.verbose,
  1007. duration : settings.duration,
  1008. queue : true,
  1009. onStart : start,
  1010. onComplete : function() {
  1011. $.proxy(callback, element)();
  1012. }
  1013. })
  1014. ;
  1015. }
  1016. else if(settings.transition == 'slide down') {
  1017. start();
  1018. $currentMenu
  1019. .hide()
  1020. .clearQueue()
  1021. .children()
  1022. .clearQueue()
  1023. .css('opacity', 0)
  1024. .delay(50)
  1025. .animate({
  1026. opacity : 1
  1027. }, settings.duration, 'easeOutQuad', module.event.resetStyle)
  1028. .end()
  1029. .slideDown(100, 'easeOutQuad', function() {
  1030. $.proxy(module.event.resetStyle, this)();
  1031. $.proxy(callback, element)();
  1032. })
  1033. ;
  1034. }
  1035. else if(settings.transition == 'fade') {
  1036. start();
  1037. $currentMenu
  1038. .hide()
  1039. .clearQueue()
  1040. .fadeIn(settings.duration, function() {
  1041. $.proxy(module.event.resetStyle, this)();
  1042. $.proxy(callback, element)();
  1043. })
  1044. ;
  1045. }
  1046. else {
  1047. module.error(error.transition, settings.transition);
  1048. }
  1049. }
  1050. },
  1051. hide: function(callback, $subMenu) {
  1052. var
  1053. $currentMenu = $subMenu || $menu,
  1054. start = ($subMenu)
  1055. ? function() {}
  1056. : function() {
  1057. if( module.can.click() ) {
  1058. module.unbind.intent();
  1059. }
  1060. module.focusSearch();
  1061. module.hideSubMenus();
  1062. module.remove.active();
  1063. }
  1064. ;
  1065. callback = callback || function(){};
  1066. if( module.is.visible($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. if(value === name) {
  1374. html += '<div class="item">' + name + '</div>';
  1375. }
  1376. else {
  1377. html += '<div class="item" data-value="' + value + '">' + name + '</div>';
  1378. }
  1379. });
  1380. return html;
  1381. },
  1382. dropdown: function(select) {
  1383. var
  1384. placeholder = select.placeholder || false,
  1385. values = select.values || {},
  1386. html = ''
  1387. ;
  1388. html += '<i class="dropdown icon"></i>';
  1389. if(select.placeholder) {
  1390. html += '<div class="default text">' + placeholder + '</div>';
  1391. }
  1392. else {
  1393. html += '<div class="text"></div>';
  1394. }
  1395. html += '<div class="menu">';
  1396. $.each(select.values, function(value, name) {
  1397. if(value === name) {
  1398. html += '<div class="item">' + name + '</div>';
  1399. }
  1400. else {
  1401. html += '<div class="item" data-value="' + value + '">' + name + '</div>';
  1402. }
  1403. });
  1404. html += '</div>';
  1405. return html;
  1406. }
  1407. };
  1408. /* Dependencies */
  1409. $.extend( $.easing, {
  1410. easeOutQuad: function (x, t, b, c, d) {
  1411. return -c *(t/=d)*(t-2) + b;
  1412. },
  1413. });
  1414. })( jQuery, window , document );