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.

505 lines
15 KiB

10 years ago
10 years ago
10 years ago
10 years ago
  1. /*
  2. * # Semantic - Accordion
  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.accordion = function(parameters) {
  14. var
  15. $allModules = $(this),
  16. time = new Date().getTime(),
  17. performance = [],
  18. query = arguments[0],
  19. methodInvoked = (typeof query == 'string'),
  20. queryArguments = [].slice.call(arguments, 1),
  21. requestAnimationFrame = window.requestAnimationFrame
  22. || window.mozRequestAnimationFrame
  23. || window.webkitRequestAnimationFrame
  24. || window.msRequestAnimationFrame
  25. || function(callback) { setTimeout(callback, 0); },
  26. returnedValue
  27. ;
  28. $allModules
  29. .each(function() {
  30. var
  31. settings = ( $.isPlainObject(parameters) )
  32. ? $.extend(true, {}, $.fn.accordion.settings, parameters)
  33. : $.extend({}, $.fn.accordion.settings),
  34. className = settings.className,
  35. namespace = settings.namespace,
  36. selector = settings.selector,
  37. error = settings.error,
  38. eventNamespace = '.' + namespace,
  39. moduleNamespace = 'module-' + namespace,
  40. moduleSelector = $allModules.selector || '',
  41. $module = $(this),
  42. $title = $module.find(selector.title),
  43. $content = $module.find(selector.content),
  44. element = this,
  45. instance = $module.data(moduleNamespace),
  46. observer,
  47. module
  48. ;
  49. module = {
  50. initialize: function() {
  51. module.debug('Initializing accordion with bound events', $module);
  52. $module
  53. .on('click' + eventNamespace, selector.title, module.event.click)
  54. ;
  55. module.observeChanges();
  56. module.instantiate();
  57. },
  58. instantiate: function() {
  59. instance = module;
  60. $module
  61. .data(moduleNamespace, module)
  62. ;
  63. },
  64. destroy: function() {
  65. module.debug('Destroying previous accordion for', $module);
  66. $module
  67. .removeData(moduleNamespace)
  68. ;
  69. $title
  70. .off(eventNamespace)
  71. ;
  72. },
  73. refresh: function() {
  74. $title = $module.find(selector.title);
  75. $content = $module.find(selector.content);
  76. },
  77. observeChanges: function() {
  78. if('MutationObserver' in window) {
  79. observer = new MutationObserver(function(mutations) {
  80. module.debug('DOM tree modified, updating selector cache');
  81. module.refresh();
  82. });
  83. observer.observe(element, {
  84. childList : true,
  85. subtree : true
  86. });
  87. module.debug('Setting up mutation observer', observer);
  88. }
  89. },
  90. event: {
  91. click: function() {
  92. $.proxy(module.toggle, this)();
  93. }
  94. },
  95. toggle: function(query) {
  96. var
  97. $activeTitle = (query !== undefined)
  98. ? (typeof query === 'number')
  99. ? $title.eq(query)
  100. : $(query)
  101. : $(this),
  102. $activeContent = $activeTitle.next($content),
  103. contentIsOpen = $activeContent.is(':visible')
  104. ;
  105. module.debug('Toggling visibility of content', $activeTitle);
  106. if(contentIsOpen) {
  107. if(settings.collapsible) {
  108. $.proxy(module.close, $activeTitle)();
  109. }
  110. else {
  111. module.debug('Cannot close accordion content collapsing is disabled');
  112. }
  113. }
  114. else {
  115. $.proxy(module.open, $activeTitle)();
  116. }
  117. },
  118. open: function(query) {
  119. var
  120. $activeTitle = (query !== undefined)
  121. ? (typeof query === 'number')
  122. ? $title.eq(query)
  123. : $(query)
  124. : $(this),
  125. $activeContent = $activeTitle.next($content),
  126. currentlyAnimating = $activeContent.is(':animated'),
  127. currentlyActive = $activeContent.hasClass(className.active)
  128. ;
  129. if(!currentlyAnimating && !currentlyActive) {
  130. module.debug('Opening accordion content', $activeTitle);
  131. if(settings.exclusive) {
  132. $.proxy(module.closeOthers, $activeTitle)();
  133. }
  134. $activeTitle
  135. .addClass(className.active)
  136. ;
  137. $activeContent
  138. .stop()
  139. .children()
  140. .stop()
  141. .animate({
  142. opacity: 1
  143. }, settings.duration, module.reset.display)
  144. .end()
  145. .slideDown(settings.duration, settings.easing, function() {
  146. $activeContent
  147. .addClass(className.active)
  148. ;
  149. $.proxy(module.reset.display, this)();
  150. $.proxy(settings.onOpen, this)();
  151. $.proxy(settings.onChange, this)();
  152. })
  153. ;
  154. }
  155. },
  156. close: function(query) {
  157. var
  158. $activeTitle = (query !== undefined)
  159. ? (typeof query === 'number')
  160. ? $title.eq(query)
  161. : $(query)
  162. : $(this),
  163. $activeContent = $activeTitle.next($content),
  164. isActive = $activeContent.hasClass(className.active)
  165. ;
  166. if(isActive) {
  167. module.debug('Closing accordion content', $activeContent);
  168. $activeTitle
  169. .removeClass(className.active)
  170. ;
  171. $activeContent
  172. .removeClass(className.active)
  173. .show()
  174. .stop()
  175. .children()
  176. .stop()
  177. .animate({
  178. opacity: 0
  179. }, settings.duration, module.reset.opacity)
  180. .end()
  181. .slideUp(settings.duration, settings.easing, function() {
  182. $.proxy(module.reset.display, this)();
  183. $.proxy(settings.onClose, this)();
  184. $.proxy(settings.onChange, this)();
  185. })
  186. ;
  187. }
  188. },
  189. closeOthers: function(index) {
  190. var
  191. $activeTitle = (index !== undefined)
  192. ? $title.eq(index)
  193. : $(this),
  194. $parentTitles = $activeTitle.parents(selector.content).prev(selector.title),
  195. $activeAccordion = $activeTitle.closest(selector.accordion),
  196. activeSelector = selector.title + '.' + className.active + ':visible',
  197. activeContent = selector.content + '.' + className.active + ':visible',
  198. $openTitles,
  199. $nestedTitles,
  200. $openContents
  201. ;
  202. if(settings.closeNested) {
  203. $openTitles = $activeAccordion.find(activeSelector).not($parentTitles);
  204. $openContents = $openTitles.next($content);
  205. }
  206. else {
  207. $openTitles = $activeAccordion.find(activeSelector).not($parentTitles);
  208. $nestedTitles = $activeAccordion.find(activeContent).find(activeSelector).not($parentTitles);
  209. $openTitles = $openTitles.not($nestedTitles);
  210. $openContents = $openTitles.next($content);
  211. }
  212. if( ($openTitles.size() > 0) ) {
  213. module.debug('Exclusive enabled, closing other content', $openTitles);
  214. $openTitles
  215. .removeClass(className.active)
  216. ;
  217. $openContents
  218. .stop()
  219. .children()
  220. .stop()
  221. .animate({
  222. opacity: 0
  223. }, settings.duration, module.resetOpacity)
  224. .end()
  225. .slideUp(settings.duration , settings.easing, function() {
  226. $(this).removeClass(className.active);
  227. $.proxy(module.reset.display, this)();
  228. })
  229. ;
  230. }
  231. },
  232. reset: {
  233. display: function() {
  234. module.verbose('Removing inline display from element', this);
  235. $(this).css('display', '');
  236. if( $(this).attr('style') === '') {
  237. $(this)
  238. .attr('style', '')
  239. .removeAttr('style')
  240. ;
  241. }
  242. },
  243. opacity: function() {
  244. module.verbose('Removing inline opacity from element', this);
  245. $(this).css('opacity', '');
  246. if( $(this).attr('style') === '') {
  247. $(this)
  248. .attr('style', '')
  249. .removeAttr('style')
  250. ;
  251. }
  252. },
  253. },
  254. setting: function(name, value) {
  255. module.debug('Changing setting', name, value);
  256. if( $.isPlainObject(name) ) {
  257. $.extend(true, settings, name);
  258. }
  259. else if(value !== undefined) {
  260. settings[name] = value;
  261. }
  262. else {
  263. return settings[name];
  264. }
  265. },
  266. internal: function(name, value) {
  267. module.debug('Changing internal', name, value);
  268. if(value !== undefined) {
  269. if( $.isPlainObject(name) ) {
  270. $.extend(true, module, name);
  271. }
  272. else {
  273. module[name] = value;
  274. }
  275. }
  276. else {
  277. return module[name];
  278. }
  279. },
  280. debug: function() {
  281. if(settings.debug) {
  282. if(settings.performance) {
  283. module.performance.log(arguments);
  284. }
  285. else {
  286. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  287. module.debug.apply(console, arguments);
  288. }
  289. }
  290. },
  291. verbose: function() {
  292. if(settings.verbose && settings.debug) {
  293. if(settings.performance) {
  294. module.performance.log(arguments);
  295. }
  296. else {
  297. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  298. module.verbose.apply(console, arguments);
  299. }
  300. }
  301. },
  302. error: function() {
  303. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  304. module.error.apply(console, arguments);
  305. },
  306. performance: {
  307. log: function(message) {
  308. var
  309. currentTime,
  310. executionTime,
  311. previousTime
  312. ;
  313. if(settings.performance) {
  314. currentTime = new Date().getTime();
  315. previousTime = time || currentTime;
  316. executionTime = currentTime - previousTime;
  317. time = currentTime;
  318. performance.push({
  319. 'Name' : message[0],
  320. 'Arguments' : [].slice.call(message, 1) || '',
  321. 'Element' : element,
  322. 'Execution Time' : executionTime
  323. });
  324. }
  325. clearTimeout(module.performance.timer);
  326. module.performance.timer = setTimeout(module.performance.display, 100);
  327. },
  328. display: function() {
  329. var
  330. title = settings.name + ':',
  331. totalTime = 0
  332. ;
  333. time = false;
  334. clearTimeout(module.performance.timer);
  335. $.each(performance, function(index, data) {
  336. totalTime += data['Execution Time'];
  337. });
  338. title += ' ' + totalTime + 'ms';
  339. if(moduleSelector) {
  340. title += ' \'' + moduleSelector + '\'';
  341. }
  342. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  343. console.groupCollapsed(title);
  344. if(console.table) {
  345. console.table(performance);
  346. }
  347. else {
  348. $.each(performance, function(index, data) {
  349. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  350. });
  351. }
  352. console.groupEnd();
  353. }
  354. performance = [];
  355. }
  356. },
  357. invoke: function(query, passedArguments, context) {
  358. var
  359. object = instance,
  360. maxDepth,
  361. found,
  362. response
  363. ;
  364. passedArguments = passedArguments || queryArguments;
  365. context = element || context;
  366. if(typeof query == 'string' && object !== undefined) {
  367. query = query.split(/[\. ]/);
  368. maxDepth = query.length - 1;
  369. $.each(query, function(depth, value) {
  370. var camelCaseValue = (depth != maxDepth)
  371. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  372. : query
  373. ;
  374. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  375. object = object[camelCaseValue];
  376. }
  377. else if( object[camelCaseValue] !== undefined ) {
  378. found = object[camelCaseValue];
  379. return false;
  380. }
  381. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  382. object = object[value];
  383. }
  384. else if( object[value] !== undefined ) {
  385. found = object[value];
  386. return false;
  387. }
  388. else {
  389. module.error(error.method, query);
  390. return false;
  391. }
  392. });
  393. }
  394. if ( $.isFunction( found ) ) {
  395. response = found.apply(context, passedArguments);
  396. }
  397. else if(found !== undefined) {
  398. response = found;
  399. }
  400. if($.isArray(returnedValue)) {
  401. returnedValue.push(response);
  402. }
  403. else if(returnedValue !== undefined) {
  404. returnedValue = [returnedValue, response];
  405. }
  406. else if(response !== undefined) {
  407. returnedValue = response;
  408. }
  409. return found;
  410. }
  411. };
  412. if(methodInvoked) {
  413. if(instance === undefined) {
  414. module.initialize();
  415. }
  416. module.invoke(query);
  417. }
  418. else {
  419. if(instance !== undefined) {
  420. module.destroy();
  421. }
  422. module.initialize();
  423. }
  424. })
  425. ;
  426. return (returnedValue !== undefined)
  427. ? returnedValue
  428. : this
  429. ;
  430. };
  431. $.fn.accordion.settings = {
  432. name : 'Accordion',
  433. namespace : 'accordion',
  434. debug : false,
  435. verbose : true,
  436. performance : true,
  437. exclusive : true,
  438. collapsible : true,
  439. closeNested : false,
  440. duration : 500,
  441. easing : 'easeInOutQuint',
  442. onOpen : function(){},
  443. onClose : function(){},
  444. onChange : function(){},
  445. error: {
  446. method : 'The method you called is not defined'
  447. },
  448. className : {
  449. active : 'active'
  450. },
  451. selector : {
  452. accordion : '.accordion',
  453. title : '.title',
  454. content : '.content'
  455. }
  456. };
  457. // Adds easing
  458. $.extend( $.easing, {
  459. easeInOutQuint: function (x, t, b, c, d) {
  460. if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
  461. return c/2*((t-=2)*t*t*t*t + 2) + b;
  462. }
  463. });
  464. })( jQuery, window , document );