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.

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