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.

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