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.

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