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.

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