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.

817 lines
25 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
  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) || $choice.text(),
  190. value = $choice.data(metadata.value) || text.toLowerCase()
  191. ;
  192. if( $choice.find(selector.menu).size() === 0 ) {
  193. module.determine.selectAction(text, value);
  194. $.proxy(settings.onChange, element)(value, text);
  195. }
  196. }
  197. },
  198. resetStyle: function() {
  199. $(this).removeAttr('style');
  200. }
  201. },
  202. determine: {
  203. selectAction: function(text, value) {
  204. module.verbose('Determining action', settings.action);
  205. if( $.isFunction( module.action[settings.action] ) ) {
  206. module.verbose('Triggering preset action', settings.action, text, value);
  207. module.action[ settings.action ](text, value);
  208. }
  209. else if( $.isFunction(settings.action) ) {
  210. module.verbose('Triggering user action', settings.action, text, value);
  211. settings.action(text, value);
  212. }
  213. else {
  214. module.error(error.action, settings.action);
  215. }
  216. },
  217. intent: function(event, callback) {
  218. module.debug('Determining whether event occurred in dropdown', event.target);
  219. callback = callback || function(){};
  220. if( $(event.target).closest($menu).size() === 0 ) {
  221. module.verbose('Triggering event', callback);
  222. callback();
  223. return true;
  224. }
  225. else {
  226. module.verbose('Event occurred in dropdown, canceling callback');
  227. return false;
  228. }
  229. }
  230. },
  231. action: {
  232. nothing: function() {},
  233. hide: function() {
  234. module.hide();
  235. },
  236. activate: function(text, value) {
  237. value = (value !== undefined)
  238. ? value
  239. : text
  240. ;
  241. module.set.selected(value);
  242. module.set.value(value);
  243. module.hide();
  244. },
  245. /* Deprecated */
  246. auto: function(text, value) {
  247. value = (value !== undefined)
  248. ? value
  249. : text
  250. ;
  251. module.set.selected(value);
  252. module.set.value(value);
  253. module.hide();
  254. },
  255. /* Deprecated */
  256. changeText: function(text, value) {
  257. value = (value !== undefined)
  258. ? value
  259. : text
  260. ;
  261. module.set.selected(value);
  262. module.hide();
  263. },
  264. /* Deprecated */
  265. updateForm: function(text, value) {
  266. value = (value !== undefined)
  267. ? value
  268. : text
  269. ;
  270. module.set.selected(value);
  271. module.set.value(value);
  272. module.hide();
  273. }
  274. },
  275. get: {
  276. text: function() {
  277. return $text.text();
  278. },
  279. value: function() {
  280. return $input.val();
  281. },
  282. item: function(value) {
  283. var
  284. $selectedItem
  285. ;
  286. value = (value !== undefined)
  287. ? value
  288. : ( module.get.value() || module.get.text() )
  289. ;
  290. if(value) {
  291. $item
  292. .each(function() {
  293. var
  294. $choice = $(this),
  295. optionText = $choice.data(metadata.text) || $choice.text(),
  296. optionValue = $choice.data(metadata.value) || optionText.toLowerCase()
  297. ;
  298. if( optionValue == value || optionText == value ) {
  299. $selectedItem = $(this);
  300. return false;
  301. }
  302. })
  303. ;
  304. }
  305. else {
  306. value = module.get.text();
  307. }
  308. return $selectedItem || false;
  309. }
  310. },
  311. set: {
  312. text: function(text) {
  313. module.debug('Changing text', text, $text);
  314. $text.removeClass(className.placeholder);
  315. $text.text(text);
  316. },
  317. value: function(value) {
  318. module.debug('Adding selected value to hidden input', value, $input);
  319. $input.val(value);
  320. },
  321. active: function() {
  322. $module.addClass(className.active);
  323. },
  324. visible: function() {
  325. $module.addClass(className.visible);
  326. },
  327. selected: function(value) {
  328. var
  329. $selectedItem = module.get.item(value),
  330. selectedText
  331. ;
  332. if($selectedItem) {
  333. module.debug('Setting selected menu item to', $selectedItem);
  334. selectedText = $selectedItem.data(metadata.text) || $selectedItem.text();
  335. $item
  336. .removeClass(className.active)
  337. ;
  338. $selectedItem
  339. .addClass(className.active)
  340. ;
  341. module.set.text(selectedText);
  342. }
  343. }
  344. },
  345. remove: {
  346. active: function() {
  347. $module.removeClass(className.active);
  348. },
  349. visible: function() {
  350. $module.removeClass(className.visible);
  351. }
  352. },
  353. is: {
  354. selection: function() {
  355. return $module.hasClass(className.selection);
  356. },
  357. animated: function($subMenu) {
  358. return ($subMenu)
  359. ? $subMenu.is(':animated') || $subMenu.transition('is animating')
  360. : $menu.is(':animated') || $menu.transition('is animating')
  361. ;
  362. },
  363. visible: function($subMenu) {
  364. return ($subMenu)
  365. ? $subMenu.is(':visible')
  366. : $menu.is(':visible')
  367. ;
  368. },
  369. hidden: function($subMenu) {
  370. return ($subMenu)
  371. ? $subMenu.is(':not(:visible)')
  372. : $menu.is(':not(:visible)')
  373. ;
  374. }
  375. },
  376. can: {
  377. click: function() {
  378. return (hasTouch || settings.on == 'click');
  379. },
  380. show: function() {
  381. return !$module.hasClass(className.disabled);
  382. }
  383. },
  384. animate: {
  385. show: function(callback, $subMenu) {
  386. var
  387. $currentMenu = $subMenu || $menu
  388. ;
  389. callback = callback || function(){};
  390. if( module.is.hidden($currentMenu) ) {
  391. module.verbose('Doing menu show animation', $currentMenu);
  392. if(settings.transition == 'none') {
  393. callback();
  394. }
  395. else if($.fn.transition !== undefined && $module.transition('is supported')) {
  396. $currentMenu.transition({
  397. animation : settings.transition + ' in',
  398. duration : settings.duration,
  399. complete : callback,
  400. queue : false
  401. });
  402. }
  403. else if(settings.transition == 'slide down') {
  404. $currentMenu
  405. .hide()
  406. .clearQueue()
  407. .children()
  408. .clearQueue()
  409. .css('opacity', 0)
  410. .delay(50)
  411. .animate({
  412. opacity : 1
  413. }, settings.duration, 'easeOutQuad', module.event.resetStyle)
  414. .end()
  415. .slideDown(100, 'easeOutQuad', function() {
  416. $.proxy(module.event.resetStyle, this)();
  417. callback();
  418. })
  419. ;
  420. }
  421. else if(settings.transition == 'fade') {
  422. $currentMenu
  423. .hide()
  424. .clearQueue()
  425. .fadeIn(settings.duration, function() {
  426. $.proxy(module.event.resetStyle, this)();
  427. callback();
  428. })
  429. ;
  430. }
  431. else {
  432. module.error(error.transition, settings.transition);
  433. }
  434. }
  435. },
  436. hide: function(callback, $subMenu) {
  437. var
  438. $currentMenu = $subMenu || $menu
  439. ;
  440. callback = callback || function(){};
  441. if(module.is.visible($currentMenu) ) {
  442. module.verbose('Doing menu hide animation', $currentMenu);
  443. if($.fn.transition !== undefined && $module.transition('is supported')) {
  444. $currentMenu.transition({
  445. animation : settings.transition + ' out',
  446. duration : settings.duration,
  447. complete : callback,
  448. queue : false
  449. });
  450. }
  451. else if(settings.transition == 'none') {
  452. callback();
  453. }
  454. else if(settings.transition == 'slide down') {
  455. $currentMenu
  456. .show()
  457. .clearQueue()
  458. .children()
  459. .clearQueue()
  460. .css('opacity', 1)
  461. .animate({
  462. opacity : 0
  463. }, 100, 'easeOutQuad', module.event.resetStyle)
  464. .end()
  465. .delay(50)
  466. .slideUp(100, 'easeOutQuad', function() {
  467. $.proxy(module.event.resetStyle, this)();
  468. callback();
  469. })
  470. ;
  471. }
  472. else if(settings.transition == 'fade') {
  473. $currentMenu
  474. .show()
  475. .clearQueue()
  476. .fadeOut(150, function() {
  477. $.proxy(module.event.resetStyle, this)();
  478. callback();
  479. })
  480. ;
  481. }
  482. else {
  483. module.error(error.transition);
  484. }
  485. }
  486. }
  487. },
  488. show: function() {
  489. module.debug('Checking if dropdown can show');
  490. if( module.is.hidden() ) {
  491. module.hideOthers();
  492. module.set.active();
  493. module.animate.show(function() {
  494. if( module.can.click() ) {
  495. module.bind.intent();
  496. }
  497. module.set.visible();
  498. });
  499. $.proxy(settings.onShow, element)();
  500. }
  501. },
  502. hide: function() {
  503. if( !module.is.animated() && module.is.visible() ) {
  504. module.debug('Hiding dropdown');
  505. if( module.can.click() ) {
  506. module.unbind.intent();
  507. }
  508. module.remove.active();
  509. module.animate.hide(module.remove.visible);
  510. $.proxy(settings.onHide, element)();
  511. }
  512. },
  513. delay: {
  514. show: function() {
  515. module.verbose('Delaying show event to ensure user intent');
  516. clearTimeout(module.timer);
  517. module.timer = setTimeout(module.show, settings.delay.show);
  518. },
  519. hide: function() {
  520. module.verbose('Delaying hide event to ensure user intent');
  521. clearTimeout(module.timer);
  522. module.timer = setTimeout(module.hide, settings.delay.hide);
  523. }
  524. },
  525. hideOthers: function() {
  526. module.verbose('Finding other dropdowns to hide');
  527. $allModules
  528. .not($module)
  529. .has(selector.menu + ':visible')
  530. .dropdown('hide')
  531. ;
  532. },
  533. toggle: function() {
  534. module.verbose('Toggling menu visibility');
  535. if( module.is.hidden() ) {
  536. module.show();
  537. }
  538. else {
  539. module.hide();
  540. }
  541. },
  542. setting: function(name, value) {
  543. if( $.isPlainObject(name) ) {
  544. $.extend(true, settings, name);
  545. }
  546. else if(value !== undefined) {
  547. settings[name] = value;
  548. }
  549. else {
  550. return settings[name];
  551. }
  552. },
  553. internal: function(name, value) {
  554. if( $.isPlainObject(name) ) {
  555. $.extend(true, module, name);
  556. }
  557. else if(value !== undefined) {
  558. module[name] = value;
  559. }
  560. else {
  561. return module[name];
  562. }
  563. },
  564. debug: function() {
  565. if(settings.debug) {
  566. if(settings.performance) {
  567. module.performance.log(arguments);
  568. }
  569. else {
  570. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  571. module.debug.apply(console, arguments);
  572. }
  573. }
  574. },
  575. verbose: function() {
  576. if(settings.verbose && settings.debug) {
  577. if(settings.performance) {
  578. module.performance.log(arguments);
  579. }
  580. else {
  581. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  582. module.verbose.apply(console, arguments);
  583. }
  584. }
  585. },
  586. error: function() {
  587. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  588. module.error.apply(console, arguments);
  589. },
  590. performance: {
  591. log: function(message) {
  592. var
  593. currentTime,
  594. executionTime,
  595. previousTime
  596. ;
  597. if(settings.performance) {
  598. currentTime = new Date().getTime();
  599. previousTime = time || currentTime;
  600. executionTime = currentTime - previousTime;
  601. time = currentTime;
  602. performance.push({
  603. 'Element' : element,
  604. 'Name' : message[0],
  605. 'Arguments' : [].slice.call(message, 1) || '',
  606. 'Execution Time' : executionTime
  607. });
  608. }
  609. clearTimeout(module.performance.timer);
  610. module.performance.timer = setTimeout(module.performance.display, 100);
  611. },
  612. display: function() {
  613. var
  614. title = settings.name + ':',
  615. totalTime = 0
  616. ;
  617. time = false;
  618. clearTimeout(module.performance.timer);
  619. $.each(performance, function(index, data) {
  620. totalTime += data['Execution Time'];
  621. });
  622. title += ' ' + totalTime + 'ms';
  623. if(moduleSelector) {
  624. title += ' \'' + moduleSelector + '\'';
  625. }
  626. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  627. console.groupCollapsed(title);
  628. if(console.table) {
  629. console.table(performance);
  630. }
  631. else {
  632. $.each(performance, function(index, data) {
  633. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  634. });
  635. }
  636. console.groupEnd();
  637. }
  638. performance = [];
  639. }
  640. },
  641. invoke: function(query, passedArguments, context) {
  642. var
  643. maxDepth,
  644. found,
  645. response
  646. ;
  647. passedArguments = passedArguments || queryArguments;
  648. context = element || context;
  649. if(typeof query == 'string' && instance !== undefined) {
  650. query = query.split(/[\. ]/);
  651. maxDepth = query.length - 1;
  652. $.each(query, function(depth, value) {
  653. var camelCaseValue = (depth != maxDepth)
  654. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  655. : query
  656. ;
  657. if( $.isPlainObject( instance[camelCaseValue] ) && (depth != maxDepth) ) {
  658. instance = instance[camelCaseValue];
  659. }
  660. else if( instance[camelCaseValue] !== undefined ) {
  661. found = instance[camelCaseValue];
  662. return false;
  663. }
  664. else if( $.isPlainObject( instance[value] ) && (depth != maxDepth) ) {
  665. instance = instance[value];
  666. }
  667. else if( instance[value] !== undefined ) {
  668. found = instance[value];
  669. return false;
  670. }
  671. else {
  672. module.error(error.method, query);
  673. return false;
  674. }
  675. });
  676. }
  677. if ( $.isFunction( found ) ) {
  678. response = found.apply(context, passedArguments);
  679. }
  680. else if(found !== undefined) {
  681. response = found;
  682. }
  683. if($.isArray(returnedValue)) {
  684. returnedValue.push(response);
  685. }
  686. else if(returnedValue !== undefined) {
  687. returnedValue = [returnedValue, response];
  688. }
  689. else if(response !== undefined) {
  690. returnedValue = response;
  691. }
  692. return found;
  693. }
  694. };
  695. if(methodInvoked) {
  696. if(instance === undefined) {
  697. module.initialize();
  698. }
  699. module.invoke(query);
  700. }
  701. else {
  702. if(instance !== undefined) {
  703. module.destroy();
  704. }
  705. module.initialize();
  706. }
  707. })
  708. ;
  709. return (returnedValue)
  710. ? returnedValue
  711. : this
  712. ;
  713. };
  714. $.fn.dropdown.settings = {
  715. name : 'Dropdown',
  716. namespace : 'dropdown',
  717. verbose : true,
  718. debug : true,
  719. performance : true,
  720. on : 'click',
  721. action : 'activate',
  722. delay: {
  723. show : 200,
  724. hide : 300,
  725. touch : 50
  726. },
  727. transition : 'slide down',
  728. duration : 250,
  729. onChange : function(){},
  730. onShow : function(){},
  731. onHide : function(){},
  732. error : {
  733. action : 'You called a dropdown action that was not defined',
  734. method : 'The method you called is not defined.',
  735. transition : 'The requested transition was not found'
  736. },
  737. metadata: {
  738. text : 'text',
  739. value : 'value'
  740. },
  741. selector : {
  742. menu : '.menu',
  743. item : '.menu > .item',
  744. text : '> .text',
  745. input : '> input[type="hidden"]'
  746. },
  747. className : {
  748. active : 'active',
  749. placeholder : 'default',
  750. disabled : 'disabled',
  751. visible : 'visible',
  752. selection : 'selection'
  753. }
  754. };
  755. })( jQuery, window , document );