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.

849 lines
26 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  1. /*
  2. * # Semantic - Dropdown
  3. * http://github.com/jlukic/semantic-ui/
  4. *
  5. *
  6. * Copyright 2013 Contributors
  7. * Released under the MIT license
  8. * http://opensource.org/licenses/MIT
  9. *
  10. */
  11. ;(function ( $, window, document, undefined ) {
  12. $.fn.dropdown = function(parameters) {
  13. var
  14. $allModules = $(this),
  15. $document = $(document),
  16. moduleSelector = $allModules.selector || '',
  17. hasTouch = ('ontouchstart' in document.documentElement),
  18. time = new Date().getTime(),
  19. performance = [],
  20. query = arguments[0],
  21. methodInvoked = (typeof query == 'string'),
  22. queryArguments = [].slice.call(arguments, 1),
  23. returnedValue
  24. ;
  25. $allModules
  26. .each(function() {
  27. var
  28. settings = ( $.isPlainObject(parameters) )
  29. ? $.extend(true, {}, $.fn.dropdown.settings, parameters)
  30. : $.extend({}, $.fn.dropdown.settings),
  31. className = settings.className,
  32. metadata = settings.metadata,
  33. namespace = settings.namespace,
  34. selector = settings.selector,
  35. error = settings.error,
  36. eventNamespace = '.' + namespace,
  37. moduleNamespace = 'module-' + namespace,
  38. $module = $(this),
  39. $item = $module.find(selector.item),
  40. $text = $module.find(selector.text),
  41. $input = $module.find(selector.input),
  42. $menu = $module.children(selector.menu),
  43. element = this,
  44. instance = $module.data(moduleNamespace),
  45. module
  46. ;
  47. module = {
  48. initialize: function() {
  49. module.debug('Initializing dropdown', settings);
  50. module.set.selected();
  51. if(hasTouch) {
  52. module.bind.touchEvents();
  53. }
  54. // no use detecting mouse events because touch devices emulate them
  55. module.bind.mouseEvents();
  56. module.instantiate();
  57. },
  58. instantiate: function() {
  59. module.verbose('Storing instance of dropdown', module);
  60. instance = module;
  61. $module
  62. .data(moduleNamespace, module)
  63. ;
  64. },
  65. destroy: function() {
  66. module.verbose('Destroying previous dropdown for', $module);
  67. $item
  68. .off(eventNamespace)
  69. ;
  70. $module
  71. .off(eventNamespace)
  72. .removeData(moduleNamespace)
  73. ;
  74. },
  75. bind: {
  76. touchEvents: function() {
  77. module.debug('Touch device detected binding touch events');
  78. $module
  79. .on('touchstart' + eventNamespace, module.event.test.toggle)
  80. ;
  81. $item
  82. .on('touchstart' + eventNamespace, module.event.item.mouseenter)
  83. .on('touchstart' + eventNamespace, module.event.item.click)
  84. ;
  85. },
  86. mouseEvents: function() {
  87. module.verbose('Mouse detected binding mouse events');
  88. if(settings.on == 'click') {
  89. $module
  90. .on('click' + eventNamespace, module.event.test.toggle)
  91. ;
  92. }
  93. else if(settings.on == 'hover') {
  94. $module
  95. .on('mouseenter' + eventNamespace, module.delay.show)
  96. .on('mouseleave' + eventNamespace, module.delay.hide)
  97. ;
  98. }
  99. else {
  100. $module
  101. .on(settings.on + eventNamespace, module.toggle)
  102. ;
  103. }
  104. $item
  105. .on('mouseenter' + eventNamespace, module.event.item.mouseenter)
  106. .on('mouseleave' + eventNamespace, module.event.item.mouseleave)
  107. .on('click' + eventNamespace, module.event.item.click)
  108. ;
  109. },
  110. intent: function() {
  111. module.verbose('Binding hide intent event to document');
  112. if(hasTouch) {
  113. $document
  114. .on('touchstart' + eventNamespace, module.event.test.touch)
  115. .on('touchmove' + eventNamespace, module.event.test.touch)
  116. ;
  117. }
  118. $document
  119. .on('click' + eventNamespace, module.event.test.hide)
  120. ;
  121. }
  122. },
  123. unbind: {
  124. intent: function() {
  125. module.verbose('Removing hide intent event from document');
  126. if(hasTouch) {
  127. $document
  128. .off('touchstart' + eventNamespace)
  129. ;
  130. }
  131. $document
  132. .off('click' + eventNamespace)
  133. ;
  134. }
  135. },
  136. event: {
  137. test: {
  138. toggle: function(event) {
  139. if( module.determine.intent(event, module.toggle) ) {
  140. event.preventDefault();
  141. }
  142. },
  143. touch: function(event) {
  144. module.determine.intent(event, function() {
  145. if(event.type == 'touchstart') {
  146. module.timer = setTimeout(module.hide, settings.delay.touch);
  147. }
  148. else if(event.type == 'touchmove') {
  149. clearTimeout(module.timer);
  150. }
  151. });
  152. event.stopPropagation();
  153. },
  154. hide: function(event) {
  155. module.determine.intent(event, module.hide);
  156. }
  157. },
  158. item: {
  159. mouseenter: function(event) {
  160. var
  161. $currentMenu = $(this).find(selector.menu),
  162. $otherMenus = $(this).siblings(selector.item).children(selector.menu)
  163. ;
  164. if( $currentMenu.size() > 0 ) {
  165. clearTimeout(module.itemTimer);
  166. module.itemTimer = setTimeout(function() {
  167. module.animate.hide(false, $otherMenus);
  168. module.verbose('Showing sub-menu', $currentMenu);
  169. module.animate.show(false, $currentMenu);
  170. }, settings.delay.show * 2);
  171. event.preventDefault();
  172. }
  173. },
  174. mouseleave: function(event) {
  175. var
  176. $currentMenu = $(this).find(selector.menu)
  177. ;
  178. if($currentMenu.size() > 0) {
  179. clearTimeout(module.itemTimer);
  180. module.itemTimer = setTimeout(function() {
  181. module.verbose('Hiding sub-menu', $currentMenu);
  182. module.animate.hide(false, $currentMenu);
  183. }, settings.delay.hide);
  184. }
  185. },
  186. click: function (event) {
  187. var
  188. $choice = $(this),
  189. text = ( $choice.data(metadata.text) !== undefined )
  190. ? $choice.data(metadata.text)
  191. : $choice.text(),
  192. value = ( $choice.data(metadata.value) !== undefined)
  193. ? $choice.data(metadata.value)
  194. : text.toLowerCase()
  195. ;
  196. if( $choice.find(selector.menu).size() === 0 ) {
  197. module.determine.selectAction(text, value);
  198. $.proxy(settings.onChange, element)(value, text);
  199. }
  200. }
  201. },
  202. resetStyle: function() {
  203. $(this).removeAttr('style');
  204. }
  205. },
  206. determine: {
  207. selectAction: function(text, value) {
  208. module.verbose('Determining action', settings.action);
  209. if( $.isFunction( module.action[settings.action] ) ) {
  210. module.verbose('Triggering preset action', settings.action, text, value);
  211. module.action[ settings.action ](text, value);
  212. }
  213. else if( $.isFunction(settings.action) ) {
  214. module.verbose('Triggering user action', settings.action, text, value);
  215. settings.action(text, value);
  216. }
  217. else {
  218. module.error(error.action, settings.action);
  219. }
  220. },
  221. intent: function(event, callback) {
  222. module.debug('Determining whether event occurred in dropdown', event.target);
  223. callback = callback || function(){};
  224. if( $(event.target).closest($menu).size() === 0 ) {
  225. module.verbose('Triggering event', callback);
  226. callback();
  227. return true;
  228. }
  229. else {
  230. module.verbose('Event occurred in dropdown, canceling callback');
  231. return false;
  232. }
  233. }
  234. },
  235. action: {
  236. nothing: function() {},
  237. hide: function() {
  238. module.hide();
  239. },
  240. activate: function(text, value) {
  241. value = (value !== undefined)
  242. ? value
  243. : text
  244. ;
  245. module.set.selected(value);
  246. module.set.value(value);
  247. module.hide();
  248. },
  249. /* Deprecated */
  250. auto: function(text, value) {
  251. value = (value !== undefined)
  252. ? value
  253. : text
  254. ;
  255. module.set.selected(value);
  256. module.set.value(value);
  257. module.hide();
  258. },
  259. /* Deprecated */
  260. changeText: function(text, value) {
  261. value = (value !== undefined)
  262. ? value
  263. : text
  264. ;
  265. module.set.selected(value);
  266. module.hide();
  267. },
  268. /* Deprecated */
  269. updateForm: function(text, value) {
  270. value = (value !== undefined)
  271. ? value
  272. : text
  273. ;
  274. module.set.selected(value);
  275. module.set.value(value);
  276. module.hide();
  277. }
  278. },
  279. get: {
  280. text: function() {
  281. return $text.text();
  282. },
  283. value: function() {
  284. return ($input.size() > 0)
  285. ? $input.val()
  286. : $module.data(metadata.value)
  287. ;
  288. },
  289. item: function(value) {
  290. var
  291. $selectedItem
  292. ;
  293. value = (value !== undefined)
  294. ? value
  295. : ( module.get.value() !== undefined)
  296. ? module.get.value()
  297. : module.get.text()
  298. ;
  299. if(value !== undefined) {
  300. $item
  301. .each(function() {
  302. var
  303. $choice = $(this),
  304. optionText = ( $choice.data(metadata.text) !== undefined )
  305. ? $choice.data(metadata.text)
  306. : $choice.text(),
  307. optionValue = ( $choice.data(metadata.value) !== undefined )
  308. ? $choice.data(metadata.value)
  309. : optionText.toLowerCase()
  310. ;
  311. if( optionValue == value || optionText == value ) {
  312. $selectedItem = $(this);
  313. return false;
  314. }
  315. })
  316. ;
  317. }
  318. else {
  319. value = module.get.text();
  320. }
  321. return $selectedItem || false;
  322. }
  323. },
  324. set: {
  325. text: function(text) {
  326. module.debug('Changing text', text, $text);
  327. $text.removeClass(className.placeholder);
  328. $text.text(text);
  329. },
  330. value: function(value) {
  331. module.debug('Adding selected value to hidden input', value, $input);
  332. if($input.size() > 0) {
  333. $input.val(value);
  334. }
  335. else {
  336. $module.data(metadata.value, value);
  337. }
  338. },
  339. active: function() {
  340. $module.addClass(className.active);
  341. },
  342. visible: function() {
  343. $module.addClass(className.visible);
  344. },
  345. selected: function(value) {
  346. var
  347. $selectedItem = module.get.item(value),
  348. selectedText
  349. ;
  350. if($selectedItem) {
  351. module.debug('Setting selected menu item to', $selectedItem);
  352. selectedText = ($selectedItem.data(metadata.text) !== undefined)
  353. ? $selectedItem.data(metadata.text)
  354. : $selectedItem.text()
  355. ;
  356. $item
  357. .removeClass(className.active)
  358. ;
  359. $selectedItem
  360. .addClass(className.active)
  361. ;
  362. module.set.text(selectedText);
  363. }
  364. }
  365. },
  366. remove: {
  367. active: function() {
  368. $module.removeClass(className.active);
  369. },
  370. visible: function() {
  371. $module.removeClass(className.visible);
  372. }
  373. },
  374. is: {
  375. selection: function() {
  376. return $module.hasClass(className.selection);
  377. },
  378. animated: function($subMenu) {
  379. return ($subMenu)
  380. ? $subMenu.is(':animated') || $subMenu.transition('is animating')
  381. : $menu.is(':animated') || $menu.transition('is animating')
  382. ;
  383. },
  384. visible: function($subMenu) {
  385. return ($subMenu)
  386. ? $subMenu.is(':visible')
  387. : $menu.is(':visible')
  388. ;
  389. },
  390. hidden: function($subMenu) {
  391. return ($subMenu)
  392. ? $subMenu.is(':not(:visible)')
  393. : $menu.is(':not(:visible)')
  394. ;
  395. }
  396. },
  397. can: {
  398. click: function() {
  399. return (hasTouch || settings.on == 'click');
  400. },
  401. show: function() {
  402. return !$module.hasClass(className.disabled);
  403. }
  404. },
  405. animate: {
  406. show: function(callback, $subMenu) {
  407. var
  408. $currentMenu = $subMenu || $menu
  409. ;
  410. callback = callback || function(){};
  411. if( module.is.hidden($currentMenu) ) {
  412. module.verbose('Doing menu show animation', $currentMenu);
  413. if(settings.transition == 'none') {
  414. callback();
  415. }
  416. else if($.fn.transition !== undefined && $module.transition('is supported')) {
  417. $currentMenu
  418. .transition({
  419. animation : settings.transition + ' in',
  420. duration : settings.duration,
  421. complete : callback,
  422. queue : false
  423. })
  424. ;
  425. }
  426. else if(settings.transition == 'slide down') {
  427. $currentMenu
  428. .hide()
  429. .clearQueue()
  430. .children()
  431. .clearQueue()
  432. .css('opacity', 0)
  433. .delay(50)
  434. .animate({
  435. opacity : 1
  436. }, settings.duration, 'easeOutQuad', module.event.resetStyle)
  437. .end()
  438. .slideDown(100, 'easeOutQuad', function() {
  439. $.proxy(module.event.resetStyle, this)();
  440. callback();
  441. })
  442. ;
  443. }
  444. else if(settings.transition == 'fade') {
  445. $currentMenu
  446. .hide()
  447. .clearQueue()
  448. .fadeIn(settings.duration, function() {
  449. $.proxy(module.event.resetStyle, this)();
  450. callback();
  451. })
  452. ;
  453. }
  454. else {
  455. module.error(error.transition, settings.transition);
  456. }
  457. }
  458. },
  459. hide: function(callback, $subMenu) {
  460. var
  461. $currentMenu = $subMenu || $menu
  462. ;
  463. callback = callback || function(){};
  464. if(module.is.visible($currentMenu) ) {
  465. module.verbose('Doing menu hide animation', $currentMenu);
  466. if($.fn.transition !== undefined && $module.transition('is supported')) {
  467. $currentMenu.transition({
  468. animation : settings.transition + ' out',
  469. duration : settings.duration,
  470. complete : callback,
  471. queue : false
  472. });
  473. }
  474. else if(settings.transition == 'none') {
  475. callback();
  476. }
  477. else if(settings.transition == 'slide down') {
  478. $currentMenu
  479. .show()
  480. .clearQueue()
  481. .children()
  482. .clearQueue()
  483. .css('opacity', 1)
  484. .animate({
  485. opacity : 0
  486. }, 100, 'easeOutQuad', module.event.resetStyle)
  487. .end()
  488. .delay(50)
  489. .slideUp(100, 'easeOutQuad', function() {
  490. $.proxy(module.event.resetStyle, this)();
  491. callback();
  492. })
  493. ;
  494. }
  495. else if(settings.transition == 'fade') {
  496. $currentMenu
  497. .show()
  498. .clearQueue()
  499. .fadeOut(150, function() {
  500. $.proxy(module.event.resetStyle, this)();
  501. callback();
  502. })
  503. ;
  504. }
  505. else {
  506. module.error(error.transition);
  507. }
  508. }
  509. }
  510. },
  511. show: function() {
  512. module.debug('Checking if dropdown can show');
  513. if( module.is.hidden() ) {
  514. module.hideOthers();
  515. module.set.active();
  516. module.animate.show(function() {
  517. if( module.can.click() ) {
  518. module.bind.intent();
  519. }
  520. module.set.visible();
  521. });
  522. $.proxy(settings.onShow, element)();
  523. }
  524. },
  525. hide: function() {
  526. if( !module.is.animated() && module.is.visible() ) {
  527. module.debug('Hiding dropdown');
  528. if( module.can.click() ) {
  529. module.unbind.intent();
  530. }
  531. module.remove.active();
  532. module.animate.hide(module.remove.visible);
  533. $.proxy(settings.onHide, element)();
  534. }
  535. },
  536. delay: {
  537. show: function() {
  538. module.verbose('Delaying show event to ensure user intent');
  539. clearTimeout(module.timer);
  540. module.timer = setTimeout(module.show, settings.delay.show);
  541. },
  542. hide: function() {
  543. module.verbose('Delaying hide event to ensure user intent');
  544. clearTimeout(module.timer);
  545. module.timer = setTimeout(module.hide, settings.delay.hide);
  546. }
  547. },
  548. hideOthers: function() {
  549. module.verbose('Finding other dropdowns to hide');
  550. $allModules
  551. .not($module)
  552. .has(selector.menu + ':visible')
  553. .dropdown('hide')
  554. ;
  555. },
  556. toggle: function() {
  557. module.verbose('Toggling menu visibility');
  558. if( module.is.hidden() ) {
  559. module.show();
  560. }
  561. else {
  562. module.hide();
  563. }
  564. },
  565. setting: function(name, value) {
  566. if( $.isPlainObject(name) ) {
  567. $.extend(true, settings, name);
  568. }
  569. else if(value !== undefined) {
  570. settings[name] = value;
  571. }
  572. else {
  573. return settings[name];
  574. }
  575. },
  576. internal: function(name, value) {
  577. if( $.isPlainObject(name) ) {
  578. $.extend(true, module, name);
  579. }
  580. else if(value !== undefined) {
  581. module[name] = value;
  582. }
  583. else {
  584. return module[name];
  585. }
  586. },
  587. debug: function() {
  588. if(settings.debug) {
  589. if(settings.performance) {
  590. module.performance.log(arguments);
  591. }
  592. else {
  593. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  594. module.debug.apply(console, arguments);
  595. }
  596. }
  597. },
  598. verbose: function() {
  599. if(settings.verbose && settings.debug) {
  600. if(settings.performance) {
  601. module.performance.log(arguments);
  602. }
  603. else {
  604. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  605. module.verbose.apply(console, arguments);
  606. }
  607. }
  608. },
  609. error: function() {
  610. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  611. module.error.apply(console, arguments);
  612. },
  613. performance: {
  614. log: function(message) {
  615. var
  616. currentTime,
  617. executionTime,
  618. previousTime
  619. ;
  620. if(settings.performance) {
  621. currentTime = new Date().getTime();
  622. previousTime = time || currentTime;
  623. executionTime = currentTime - previousTime;
  624. time = currentTime;
  625. performance.push({
  626. 'Element' : element,
  627. 'Name' : message[0],
  628. 'Arguments' : [].slice.call(message, 1) || '',
  629. 'Execution Time' : executionTime
  630. });
  631. }
  632. clearTimeout(module.performance.timer);
  633. module.performance.timer = setTimeout(module.performance.display, 100);
  634. },
  635. display: function() {
  636. var
  637. title = settings.name + ':',
  638. totalTime = 0
  639. ;
  640. time = false;
  641. clearTimeout(module.performance.timer);
  642. $.each(performance, function(index, data) {
  643. totalTime += data['Execution Time'];
  644. });
  645. title += ' ' + totalTime + 'ms';
  646. if(moduleSelector) {
  647. title += ' \'' + moduleSelector + '\'';
  648. }
  649. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  650. console.groupCollapsed(title);
  651. if(console.table) {
  652. console.table(performance);
  653. }
  654. else {
  655. $.each(performance, function(index, data) {
  656. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  657. });
  658. }
  659. console.groupEnd();
  660. }
  661. performance = [];
  662. }
  663. },
  664. invoke: function(query, passedArguments, context) {
  665. var
  666. object = instance,
  667. maxDepth,
  668. found,
  669. response
  670. ;
  671. passedArguments = passedArguments || queryArguments;
  672. context = element || context;
  673. if(typeof query == 'string' && object !== undefined) {
  674. query = query.split(/[\. ]/);
  675. maxDepth = query.length - 1;
  676. $.each(query, function(depth, value) {
  677. var camelCaseValue = (depth != maxDepth)
  678. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  679. : query
  680. ;
  681. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  682. object = object[camelCaseValue];
  683. }
  684. else if( object[camelCaseValue] !== undefined ) {
  685. found = object[camelCaseValue];
  686. return false;
  687. }
  688. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  689. object = object[value];
  690. }
  691. else if( object[value] !== undefined ) {
  692. found = object[value];
  693. return false;
  694. }
  695. else {
  696. module.error(error.method, query);
  697. return false;
  698. }
  699. });
  700. }
  701. if ( $.isFunction( found ) ) {
  702. response = found.apply(context, passedArguments);
  703. }
  704. else if(found !== undefined) {
  705. response = found;
  706. }
  707. if($.isArray(returnedValue)) {
  708. returnedValue.push(response);
  709. }
  710. else if(returnedValue !== undefined) {
  711. returnedValue = [returnedValue, response];
  712. }
  713. else if(response !== undefined) {
  714. returnedValue = response;
  715. }
  716. return found;
  717. }
  718. };
  719. if(methodInvoked) {
  720. if(instance === undefined) {
  721. module.initialize();
  722. }
  723. module.invoke(query);
  724. }
  725. else {
  726. if(instance !== undefined) {
  727. module.destroy();
  728. }
  729. module.initialize();
  730. }
  731. })
  732. ;
  733. return (returnedValue)
  734. ? returnedValue
  735. : this
  736. ;
  737. };
  738. $.fn.dropdown.settings = {
  739. name : 'Dropdown',
  740. namespace : 'dropdown',
  741. verbose : true,
  742. debug : true,
  743. performance : true,
  744. on : 'click',
  745. action : 'activate',
  746. delay: {
  747. show : 200,
  748. hide : 300,
  749. touch : 50
  750. },
  751. transition : 'slide down',
  752. duration : 250,
  753. onChange : function(value, text){},
  754. onShow : function(){},
  755. onHide : function(){},
  756. error : {
  757. action : 'You called a dropdown action that was not defined',
  758. method : 'The method you called is not defined.',
  759. transition : 'The requested transition was not found'
  760. },
  761. metadata: {
  762. text : 'text',
  763. value : 'value'
  764. },
  765. selector : {
  766. menu : '.menu',
  767. item : '.menu > .item',
  768. text : '> .text',
  769. input : '> input[type="hidden"]'
  770. },
  771. className : {
  772. active : 'active',
  773. placeholder : 'default',
  774. disabled : 'disabled',
  775. visible : 'visible',
  776. selection : 'selection'
  777. }
  778. };
  779. // Adds easing
  780. $.extend( $.easing, {
  781. easeOutQuad: function (x, t, b, c, d) {
  782. return -c *(t/=d)*(t-2) + b;
  783. },
  784. });
  785. })( jQuery, window , document );