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.

1602 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
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. 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( text.match(exactRegExp) || value.match(exactRegExp) ) {
  344. $results = $results.add($choice);
  345. }
  346. else if(settings.fullTextSearch) {
  347. if( text.match(fullTextRegExp) || value.match(fullTextRegExp) ) {
  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. ;
  719. select.values = (settings.sortSelect)
  720. ? {} // properties will be sorted in object when re-accessed
  721. : [] // properties will keep original order in array
  722. ;
  723. $module
  724. .find('option')
  725. .each(function() {
  726. var
  727. name = $(this).html(),
  728. value = ( $(this).attr('value') !== undefined )
  729. ? $(this).attr('value')
  730. : name
  731. ;
  732. if(value === '') {
  733. select.placeholder = name;
  734. }
  735. else {
  736. if(settings.sortSelect) {
  737. select.values[value] = {
  738. name : name,
  739. value : value
  740. };
  741. }
  742. else {
  743. select.values.push({
  744. name: name,
  745. value: value
  746. });
  747. }
  748. }
  749. })
  750. ;
  751. if(settings.sortSelect) {
  752. module.debug('Retrieved and sorted values from select', select);
  753. }
  754. else {
  755. module.debug('Retreived values from select', select);
  756. }
  757. return select;
  758. },
  759. activeItem: function() {
  760. return $item.filter('.' + className.active);
  761. },
  762. item: function(value, strict) {
  763. var
  764. $selectedItem = false
  765. ;
  766. value = (value !== undefined)
  767. ? value
  768. : ( module.get.value() !== undefined)
  769. ? module.get.value()
  770. : module.get.text()
  771. ;
  772. strict = (value === '' || value === 0)
  773. ? true
  774. : strict || false
  775. ;
  776. if(value !== undefined) {
  777. $item
  778. .each(function() {
  779. var
  780. $choice = $(this),
  781. optionText = module.get.choiceText($choice),
  782. optionValue = module.get.choiceValue($choice, optionText)
  783. ;
  784. if(strict) {
  785. module.verbose('Ambiguous dropdown value using strict type check', $choice, value);
  786. if( optionValue === value ) {
  787. $selectedItem = $(this);
  788. }
  789. else if( !$selectedItem && optionText === value ) {
  790. $selectedItem = $(this);
  791. }
  792. }
  793. else {
  794. if( optionValue == value ) {
  795. module.verbose('Found select item by value', optionValue, value);
  796. $selectedItem = $(this);
  797. }
  798. else if( !$selectedItem && optionText == value ) {
  799. module.verbose('Found select item by text', optionText, value);
  800. $selectedItem = $(this);
  801. }
  802. }
  803. })
  804. ;
  805. }
  806. else {
  807. value = module.get.text();
  808. }
  809. return $selectedItem || false;
  810. },
  811. uniqueID: function() {
  812. return (Math.random().toString(16) + '000000000').substr(2,8);
  813. }
  814. },
  815. restore: {
  816. defaults: function() {
  817. module.restore.defaultText();
  818. module.restore.defaultValue();
  819. },
  820. defaultText: function() {
  821. var
  822. defaultText = $module.data(metadata.defaultText)
  823. ;
  824. module.debug('Restoring default text', defaultText);
  825. module.set.text(defaultText);
  826. },
  827. defaultValue: function() {
  828. var
  829. defaultValue = $module.data(metadata.defaultValue)
  830. ;
  831. if(defaultValue !== undefined) {
  832. module.debug('Restoring default value', defaultValue);
  833. module.set.selected(defaultValue);
  834. module.set.value(defaultValue);
  835. }
  836. }
  837. },
  838. save: {
  839. defaults: function() {
  840. module.save.defaultText();
  841. module.save.defaultValue();
  842. },
  843. defaultValue: function() {
  844. $module.data(metadata.defaultValue, module.get.value() );
  845. },
  846. defaultText: function() {
  847. $module.data(metadata.defaultText, $text.text() );
  848. }
  849. },
  850. set: {
  851. filtered: function() {
  852. var
  853. searchValue = $search.val(),
  854. hasSearchValue = (typeof searchValue === 'string' && searchValue.length > 0)
  855. ;
  856. if(hasSearchValue) {
  857. $text.addClass(className.filtered);
  858. }
  859. else {
  860. $text.removeClass(className.filtered);
  861. }
  862. },
  863. tabbable: function() {
  864. if( module.is.searchable() ) {
  865. module.debug('Searchable dropdown initialized');
  866. $search
  867. .val('')
  868. .attr('tabindex', 0)
  869. ;
  870. $menu
  871. .attr('tabindex', '-1')
  872. ;
  873. }
  874. else {
  875. module.debug('Simple selection dropdown initialized');
  876. if(!$module.attr('tabindex') ) {
  877. $module
  878. .attr('tabindex', 0)
  879. ;
  880. $menu
  881. .attr('tabindex', '-1')
  882. ;
  883. }
  884. }
  885. },
  886. scrollPosition: function($item, forceScroll) {
  887. var
  888. edgeTolerance = 5,
  889. hasActive,
  890. offset,
  891. itemHeight,
  892. itemOffset,
  893. menuOffset,
  894. menuScroll,
  895. menuHeight,
  896. abovePage,
  897. belowPage
  898. ;
  899. $item = $item || module.get.activeItem();
  900. hasActive = ($item && $item.size() > 0);
  901. forceScroll = (forceScroll !== undefined)
  902. ? forceScroll
  903. : false
  904. ;
  905. if($item && hasActive) {
  906. if(!$menu.hasClass(className.visible)) {
  907. $menu.addClass(className.loading);
  908. }
  909. menuHeight = $menu.height();
  910. itemHeight = $item.height();
  911. menuScroll = $menu.scrollTop();
  912. menuOffset = $menu.offset().top;
  913. itemOffset = $item.offset().top;
  914. offset = menuScroll - menuOffset + itemOffset;
  915. belowPage = menuScroll + menuHeight < (offset + edgeTolerance);
  916. abovePage = ((offset - edgeTolerance) < menuScroll);
  917. module.debug('Scrolling to active item', offset);
  918. if(abovePage || belowPage || forceScroll) {
  919. $menu
  920. .scrollTop(offset)
  921. .removeClass(className.loading)
  922. ;
  923. }
  924. }
  925. },
  926. text: function(text) {
  927. if(settings.action == 'combo') {
  928. module.debug('Changing combo button text', text, $combo);
  929. if(settings.preserveHTML) {
  930. $combo.html(text);
  931. }
  932. else {
  933. $combo.text(text);
  934. }
  935. }
  936. else if(settings.action !== 'select') {
  937. module.debug('Changing text', text, $text);
  938. $text
  939. .removeClass(className.filtered)
  940. .removeClass(className.placeholder)
  941. ;
  942. if(settings.preserveHTML) {
  943. $text.html(text);
  944. }
  945. else {
  946. $text.text(text);
  947. }
  948. }
  949. },
  950. value: function(value) {
  951. module.debug('Adding selected value to hidden input', value, $input);
  952. if($input.size() > 0) {
  953. $input
  954. .val(value)
  955. .trigger('change')
  956. ;
  957. }
  958. else {
  959. $module.data(metadata.value, value);
  960. }
  961. },
  962. active: function() {
  963. $module
  964. .addClass(className.active)
  965. ;
  966. },
  967. visible: function() {
  968. $module.addClass(className.visible);
  969. },
  970. selected: function(value) {
  971. var
  972. $selectedItem = module.get.item(value),
  973. selectedText
  974. ;
  975. if($selectedItem) {
  976. module.debug('Setting selected menu item to', $selectedItem);
  977. module.remove.activeItem();
  978. module.remove.selectedItem();
  979. $selectedItem
  980. .addClass(className.active)
  981. .addClass(className.selected)
  982. ;
  983. selectedText = module.get.choiceText($selectedItem);
  984. module.set.text(selectedText);
  985. $.proxy(settings.onChange, element)(value, selectedText, $selectedItem);
  986. }
  987. }
  988. },
  989. remove: {
  990. active: function() {
  991. $module.removeClass(className.active);
  992. },
  993. visible: function() {
  994. $module.removeClass(className.visible);
  995. },
  996. activeItem: function() {
  997. $item.removeClass(className.active);
  998. },
  999. filteredItem: function() {
  1000. $item.removeClass(className.filtered);
  1001. },
  1002. searchTerm: function() {
  1003. $search.val('');
  1004. },
  1005. selectedItem: function() {
  1006. $item.removeClass(className.selected);
  1007. },
  1008. tabbable: function() {
  1009. if( module.is.searchable() ) {
  1010. module.debug('Searchable dropdown initialized');
  1011. $search
  1012. .attr('tabindex', '-1')
  1013. ;
  1014. $menu
  1015. .attr('tabindex', '-1')
  1016. ;
  1017. }
  1018. else {
  1019. module.debug('Simple selection dropdown initialized');
  1020. $module
  1021. .attr('tabindex', '-1')
  1022. ;
  1023. $menu
  1024. .attr('tabindex', '-1')
  1025. ;
  1026. }
  1027. }
  1028. },
  1029. is: {
  1030. active: function() {
  1031. return $module.hasClass(className.active);
  1032. },
  1033. animating: function($subMenu) {
  1034. return ($subMenu)
  1035. ? $subMenu.is(':animated') || $subMenu.transition && $subMenu.transition('is animating')
  1036. : $menu.is(':animated') || $menu.transition && $menu.transition('is animating')
  1037. ;
  1038. },
  1039. allFiltered: function() {
  1040. return ($item.filter('.' + className.filtered).size() === $item.size());
  1041. },
  1042. hidden: function($subMenu) {
  1043. return ($subMenu)
  1044. ? $subMenu.is(':hidden')
  1045. : $menu.is(':hidden')
  1046. ;
  1047. },
  1048. search: function() {
  1049. return $module.hasClass(className.search);
  1050. },
  1051. searchable: function() {
  1052. return ($search.size() > 0);
  1053. },
  1054. searchSelection: function() {
  1055. return ( module.is.searchable() && $search.parent().is($module) );
  1056. },
  1057. selection: function() {
  1058. return $module.hasClass(className.selection);
  1059. },
  1060. visible: function($subMenu) {
  1061. return ($subMenu)
  1062. ? $subMenu.is(':visible')
  1063. : $menu.is(':visible')
  1064. ;
  1065. }
  1066. },
  1067. can: {
  1068. click: function() {
  1069. return (hasTouch || settings.on == 'click');
  1070. },
  1071. show: function() {
  1072. return !$module.hasClass(className.disabled);
  1073. }
  1074. },
  1075. animate: {
  1076. show: function(callback, $subMenu) {
  1077. var
  1078. $currentMenu = $subMenu || $menu,
  1079. start = ($subMenu)
  1080. ? function() {}
  1081. : function() {
  1082. module.hideSubMenus();
  1083. module.hideOthers();
  1084. module.set.active();
  1085. }
  1086. ;
  1087. callback = $.isFunction(callback)
  1088. ? callback
  1089. : function(){}
  1090. ;
  1091. module.set.scrollPosition(module.get.activeItem(), true);
  1092. module.verbose('Doing menu show animation', $currentMenu);
  1093. if( module.is.hidden($currentMenu) || module.is.animating($currentMenu) ) {
  1094. if(settings.transition == 'none') {
  1095. $.proxy(callback, element)();
  1096. }
  1097. else if($.fn.transition !== undefined && $module.transition('is supported')) {
  1098. $currentMenu
  1099. .transition({
  1100. animation : settings.transition + ' in',
  1101. debug : settings.debug,
  1102. verbose : settings.verbose,
  1103. duration : settings.duration,
  1104. queue : true,
  1105. onStart : start,
  1106. onComplete : function() {
  1107. $.proxy(callback, element)();
  1108. }
  1109. })
  1110. ;
  1111. }
  1112. else if(settings.transition == 'slide down') {
  1113. start();
  1114. $currentMenu
  1115. .hide()
  1116. .clearQueue()
  1117. .children()
  1118. .clearQueue()
  1119. .css('opacity', 0)
  1120. .delay(50)
  1121. .animate({
  1122. opacity : 1
  1123. }, settings.duration, 'easeOutQuad', module.event.resetStyle)
  1124. .end()
  1125. .slideDown(100, 'easeOutQuad', function() {
  1126. $.proxy(module.event.resetStyle, this)();
  1127. $.proxy(callback, element)();
  1128. })
  1129. ;
  1130. }
  1131. else if(settings.transition == 'fade') {
  1132. start();
  1133. $currentMenu
  1134. .hide()
  1135. .clearQueue()
  1136. .fadeIn(settings.duration, function() {
  1137. $.proxy(module.event.resetStyle, this)();
  1138. $.proxy(callback, element)();
  1139. })
  1140. ;
  1141. }
  1142. else {
  1143. module.error(error.transition, settings.transition);
  1144. }
  1145. }
  1146. },
  1147. hide: function(callback, $subMenu) {
  1148. var
  1149. $currentMenu = $subMenu || $menu,
  1150. duration = ($subMenu)
  1151. ? (settings.duration * 0.9)
  1152. : settings.duration,
  1153. start = ($subMenu)
  1154. ? function() {}
  1155. : function() {
  1156. if( module.can.click() ) {
  1157. module.unbind.intent();
  1158. }
  1159. module.focusSearch();
  1160. module.remove.active();
  1161. }
  1162. ;
  1163. callback = $.isFunction(callback)
  1164. ? callback
  1165. : function(){}
  1166. ;
  1167. if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) {
  1168. module.verbose('Doing menu hide animation', $currentMenu);
  1169. if(settings.transition == 'none') {
  1170. $.proxy(callback, element)();
  1171. }
  1172. else if($.fn.transition !== undefined && $module.transition('is supported')) {
  1173. $currentMenu
  1174. .transition({
  1175. animation : settings.transition + ' out',
  1176. duration : settings.duration,
  1177. debug : settings.debug,
  1178. verbose : settings.verbose,
  1179. queue : true,
  1180. onStart : start,
  1181. onComplete : function() {
  1182. $.proxy(callback, element)();
  1183. }
  1184. })
  1185. ;
  1186. }
  1187. else if(settings.transition == 'slide down') {
  1188. start();
  1189. $currentMenu
  1190. .show()
  1191. .clearQueue()
  1192. .children()
  1193. .clearQueue()
  1194. .css('opacity', 1)
  1195. .animate({
  1196. opacity : 0
  1197. }, 100, 'easeOutQuad', module.event.resetStyle)
  1198. .end()
  1199. .delay(50)
  1200. .slideUp(100, 'easeOutQuad', function() {
  1201. $.proxy(module.event.resetStyle, this)();
  1202. $.proxy(callback, element)();
  1203. })
  1204. ;
  1205. }
  1206. else if(settings.transition == 'fade') {
  1207. start();
  1208. $currentMenu
  1209. .show()
  1210. .clearQueue()
  1211. .fadeOut(150, function() {
  1212. $.proxy(module.event.resetStyle, this)();
  1213. $.proxy(callback, element)();
  1214. })
  1215. ;
  1216. }
  1217. else {
  1218. module.error(error.transition);
  1219. }
  1220. }
  1221. }
  1222. },
  1223. delay: {
  1224. show: function() {
  1225. module.verbose('Delaying show event to ensure user intent');
  1226. clearTimeout(module.timer);
  1227. module.timer = setTimeout(module.show, settings.delay.show);
  1228. },
  1229. hide: function() {
  1230. module.verbose('Delaying hide event to ensure user intent');
  1231. clearTimeout(module.timer);
  1232. module.timer = setTimeout(module.hide, settings.delay.hide);
  1233. }
  1234. },
  1235. setting: function(name, value) {
  1236. module.debug('Changing setting', name, value);
  1237. if( $.isPlainObject(name) ) {
  1238. $.extend(true, settings, name);
  1239. }
  1240. else if(value !== undefined) {
  1241. settings[name] = value;
  1242. }
  1243. else {
  1244. return settings[name];
  1245. }
  1246. },
  1247. internal: function(name, value) {
  1248. if( $.isPlainObject(name) ) {
  1249. $.extend(true, module, name);
  1250. }
  1251. else if(value !== undefined) {
  1252. module[name] = value;
  1253. }
  1254. else {
  1255. return module[name];
  1256. }
  1257. },
  1258. debug: function() {
  1259. if(settings.debug) {
  1260. if(settings.performance) {
  1261. module.performance.log(arguments);
  1262. }
  1263. else {
  1264. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  1265. module.debug.apply(console, arguments);
  1266. }
  1267. }
  1268. },
  1269. verbose: function() {
  1270. if(settings.verbose && settings.debug) {
  1271. if(settings.performance) {
  1272. module.performance.log(arguments);
  1273. }
  1274. else {
  1275. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  1276. module.verbose.apply(console, arguments);
  1277. }
  1278. }
  1279. },
  1280. error: function() {
  1281. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  1282. module.error.apply(console, arguments);
  1283. },
  1284. performance: {
  1285. log: function(message) {
  1286. var
  1287. currentTime,
  1288. executionTime,
  1289. previousTime
  1290. ;
  1291. if(settings.performance) {
  1292. currentTime = new Date().getTime();
  1293. previousTime = time || currentTime;
  1294. executionTime = currentTime - previousTime;
  1295. time = currentTime;
  1296. performance.push({
  1297. 'Name' : message[0],
  1298. 'Arguments' : [].slice.call(message, 1) || '',
  1299. 'Element' : element,
  1300. 'Execution Time' : executionTime
  1301. });
  1302. }
  1303. clearTimeout(module.performance.timer);
  1304. module.performance.timer = setTimeout(module.performance.display, 100);
  1305. },
  1306. display: function() {
  1307. var
  1308. title = settings.name + ':',
  1309. totalTime = 0
  1310. ;
  1311. time = false;
  1312. clearTimeout(module.performance.timer);
  1313. $.each(performance, function(index, data) {
  1314. totalTime += data['Execution Time'];
  1315. });
  1316. title += ' ' + totalTime + 'ms';
  1317. if(moduleSelector) {
  1318. title += ' \'' + moduleSelector + '\'';
  1319. }
  1320. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  1321. console.groupCollapsed(title);
  1322. if(console.table) {
  1323. console.table(performance);
  1324. }
  1325. else {
  1326. $.each(performance, function(index, data) {
  1327. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  1328. });
  1329. }
  1330. console.groupEnd();
  1331. }
  1332. performance = [];
  1333. }
  1334. },
  1335. invoke: function(query, passedArguments, context) {
  1336. var
  1337. object = instance,
  1338. maxDepth,
  1339. found,
  1340. response
  1341. ;
  1342. passedArguments = passedArguments || queryArguments;
  1343. context = element || context;
  1344. if(typeof query == 'string' && object !== undefined) {
  1345. query = query.split(/[\. ]/);
  1346. maxDepth = query.length - 1;
  1347. $.each(query, function(depth, value) {
  1348. var camelCaseValue = (depth != maxDepth)
  1349. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  1350. : query
  1351. ;
  1352. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  1353. object = object[camelCaseValue];
  1354. }
  1355. else if( object[camelCaseValue] !== undefined ) {
  1356. found = object[camelCaseValue];
  1357. return false;
  1358. }
  1359. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  1360. object = object[value];
  1361. }
  1362. else if( object[value] !== undefined ) {
  1363. found = object[value];
  1364. return false;
  1365. }
  1366. else {
  1367. module.error(error.method, query);
  1368. return false;
  1369. }
  1370. });
  1371. }
  1372. if ( $.isFunction( found ) ) {
  1373. response = found.apply(context, passedArguments);
  1374. }
  1375. else if(found !== undefined) {
  1376. response = found;
  1377. }
  1378. if($.isArray(returnedValue)) {
  1379. returnedValue.push(response);
  1380. }
  1381. else if(returnedValue !== undefined) {
  1382. returnedValue = [returnedValue, response];
  1383. }
  1384. else if(response !== undefined) {
  1385. returnedValue = response;
  1386. }
  1387. return found;
  1388. }
  1389. };
  1390. if(methodInvoked) {
  1391. if(instance === undefined) {
  1392. module.initialize();
  1393. }
  1394. module.invoke(query);
  1395. }
  1396. else {
  1397. if(instance !== undefined) {
  1398. module.destroy();
  1399. }
  1400. module.initialize();
  1401. }
  1402. })
  1403. ;
  1404. return (returnedValue !== undefined)
  1405. ? returnedValue
  1406. : this
  1407. ;
  1408. };
  1409. $.fn.dropdown.settings = {
  1410. debug : false,
  1411. verbose : true,
  1412. performance : true,
  1413. on : 'click',
  1414. action : 'activate',
  1415. allowTab : true,
  1416. fullTextSearch : false,
  1417. preserveHTML : true,
  1418. sortSelect : false,
  1419. delay : {
  1420. hide : 300,
  1421. show : 200,
  1422. search : 50,
  1423. touch : 50
  1424. },
  1425. transition : 'slide down',
  1426. duration : 250,
  1427. /* Callbacks */
  1428. onNoResults : function(searchTerm){},
  1429. onChange : function(value, text){},
  1430. onShow : function(){},
  1431. onHide : function(){},
  1432. /* Component */
  1433. name : 'Dropdown',
  1434. namespace : 'dropdown',
  1435. error : {
  1436. action : 'You called a dropdown action that was not defined',
  1437. method : 'The method you called is not defined.',
  1438. transition : 'The requested transition was not found'
  1439. },
  1440. metadata: {
  1441. defaultText : 'defaultText',
  1442. defaultValue : 'defaultValue',
  1443. text : 'text',
  1444. value : 'value'
  1445. },
  1446. selector : {
  1447. dropdown : '.ui.dropdown',
  1448. text : '> .text:not(.icon)',
  1449. input : '> input[type="hidden"], > select',
  1450. search : '> input.search, .menu > .search > input, .menu > input.search',
  1451. menu : '.menu',
  1452. item : '.item'
  1453. },
  1454. className : {
  1455. active : 'active',
  1456. animating : 'animating',
  1457. disabled : 'disabled',
  1458. dropdown : 'ui dropdown',
  1459. filtered : 'filtered',
  1460. loading : 'loading',
  1461. menu : 'menu',
  1462. placeholder : 'default',
  1463. search : 'search',
  1464. selected : 'selected',
  1465. selection : 'selection',
  1466. visible : 'visible'
  1467. }
  1468. };
  1469. /* Templates */
  1470. $.fn.dropdown.settings.templates = {
  1471. menu: function(select) {
  1472. var
  1473. placeholder = select.placeholder || false,
  1474. values = select.values || {},
  1475. html = ''
  1476. ;
  1477. $.each(select.values, function(index, option) {
  1478. html += '<div class="item" data-value="' + option.value + '">' + option.name + '</div>';
  1479. });
  1480. return html;
  1481. },
  1482. dropdown: function(select) {
  1483. var
  1484. placeholder = select.placeholder || false,
  1485. values = select.values || {},
  1486. html = ''
  1487. ;
  1488. html += '<i class="dropdown icon"></i>';
  1489. if(select.placeholder) {
  1490. html += '<div class="default text">' + placeholder + '</div>';
  1491. }
  1492. else {
  1493. html += '<div class="text"></div>';
  1494. }
  1495. html += '<div class="menu">';
  1496. $.each(select.values, function(index, option) {
  1497. html += '<div class="item" data-value="' + option.value + '">' + option.name + '</div>';
  1498. });
  1499. html += '</div>';
  1500. return html;
  1501. }
  1502. };
  1503. /* Dependencies */
  1504. $.extend( $.easing, {
  1505. easeOutQuad: function (x, t, b, c, d) {
  1506. return -c *(t/=d)*(t-2) + b;
  1507. },
  1508. });
  1509. })( jQuery, window , document );