tab.js 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  1. /*!
  2. * # Fomantic-UI - Tab
  3. * http://github.com/fomantic/Fomantic-UI/
  4. *
  5. *
  6. * Released under the MIT license
  7. * http://opensource.org/licenses/MIT
  8. *
  9. */
  10. ;(function ($, window, document, undefined) {
  11. 'use strict';
  12. $.isWindow = $.isWindow || function(obj) {
  13. return obj != null && obj === obj.window;
  14. };
  15. $.isFunction = $.isFunction || function(obj) {
  16. return typeof obj === "function" && typeof obj.nodeType !== "number";
  17. };
  18. window = (typeof window != 'undefined' && window.Math == Math)
  19. ? window
  20. : (typeof self != 'undefined' && self.Math == Math)
  21. ? self
  22. : Function('return this')()
  23. ;
  24. $.fn.tab = function(parameters) {
  25. var
  26. // use window context if none specified
  27. $allModules = $.isFunction(this)
  28. ? $(window)
  29. : $(this),
  30. moduleSelector = $allModules.selector || '',
  31. time = new Date().getTime(),
  32. performance = [],
  33. query = arguments[0],
  34. methodInvoked = (typeof query == 'string'),
  35. queryArguments = [].slice.call(arguments, 1),
  36. initializedHistory = false,
  37. returnedValue
  38. ;
  39. $allModules
  40. .each(function() {
  41. var
  42. settings = ( $.isPlainObject(parameters) )
  43. ? $.extend(true, {}, $.fn.tab.settings, parameters)
  44. : $.extend({}, $.fn.tab.settings),
  45. className = settings.className,
  46. metadata = settings.metadata,
  47. selector = settings.selector,
  48. error = settings.error,
  49. regExp = settings.regExp,
  50. eventNamespace = '.' + settings.namespace,
  51. moduleNamespace = 'module-' + settings.namespace,
  52. $module = $(this),
  53. $context,
  54. $tabs,
  55. cache = {},
  56. firstLoad = true,
  57. recursionDepth = 0,
  58. element = this,
  59. instance = $module.data(moduleNamespace),
  60. activeTabPath,
  61. parameterArray,
  62. module,
  63. historyEvent
  64. ;
  65. module = {
  66. initialize: function() {
  67. module.debug('Initializing tab menu item', $module);
  68. module.fix.callbacks();
  69. module.determineTabs();
  70. module.debug('Determining tabs', settings.context, $tabs);
  71. // set up automatic routing
  72. if(settings.auto) {
  73. module.set.auto();
  74. }
  75. module.bind.events();
  76. if(settings.history && !initializedHistory) {
  77. module.initializeHistory();
  78. initializedHistory = true;
  79. }
  80. if(instance === undefined && module.determine.activeTab() == null) {
  81. module.debug('No active tab detected, setting first tab active', module.get.initialPath());
  82. module.changeTab(module.get.initialPath());
  83. };
  84. module.instantiate();
  85. },
  86. instantiate: function () {
  87. module.verbose('Storing instance of module', module);
  88. instance = module;
  89. $module
  90. .data(moduleNamespace, module)
  91. ;
  92. },
  93. destroy: function() {
  94. module.debug('Destroying tabs', $module);
  95. $module
  96. .removeData(moduleNamespace)
  97. .off(eventNamespace)
  98. ;
  99. },
  100. bind: {
  101. events: function() {
  102. // if using $.tab don't add events
  103. if( !$.isWindow( element ) ) {
  104. module.debug('Attaching tab activation events to element', $module);
  105. $module
  106. .on('click' + eventNamespace, module.event.click)
  107. ;
  108. }
  109. }
  110. },
  111. determineTabs: function() {
  112. var
  113. $reference
  114. ;
  115. // determine tab context
  116. if(settings.context === 'parent') {
  117. if($module.closest(selector.ui).length > 0) {
  118. $reference = $module.closest(selector.ui);
  119. module.verbose('Using closest UI element as parent', $reference);
  120. }
  121. else {
  122. $reference = $module;
  123. }
  124. $context = $reference.parent();
  125. module.verbose('Determined parent element for creating context', $context);
  126. }
  127. else if(settings.context) {
  128. $context = $(settings.context);
  129. module.verbose('Using selector for tab context', settings.context, $context);
  130. }
  131. else {
  132. $context = $('body');
  133. }
  134. // find tabs
  135. if(settings.childrenOnly) {
  136. $tabs = $context.children(selector.tabs);
  137. module.debug('Searching tab context children for tabs', $context, $tabs);
  138. }
  139. else {
  140. $tabs = $context.find(selector.tabs);
  141. module.debug('Searching tab context for tabs', $context, $tabs);
  142. }
  143. },
  144. fix: {
  145. callbacks: function() {
  146. if( $.isPlainObject(parameters) && (parameters.onTabLoad || parameters.onTabInit) ) {
  147. if(parameters.onTabLoad) {
  148. parameters.onLoad = parameters.onTabLoad;
  149. delete parameters.onTabLoad;
  150. module.error(error.legacyLoad, parameters.onLoad);
  151. }
  152. if(parameters.onTabInit) {
  153. parameters.onFirstLoad = parameters.onTabInit;
  154. delete parameters.onTabInit;
  155. module.error(error.legacyInit, parameters.onFirstLoad);
  156. }
  157. settings = $.extend(true, {}, $.fn.tab.settings, parameters);
  158. }
  159. }
  160. },
  161. initializeHistory: function() {
  162. module.debug('Initializing page state');
  163. if( $.address === undefined ) {
  164. module.error(error.state);
  165. return false;
  166. }
  167. else {
  168. if(settings.historyType == 'state') {
  169. module.debug('Using HTML5 to manage state');
  170. if(settings.path !== false) {
  171. $.address
  172. .history(true)
  173. .state(settings.path)
  174. ;
  175. }
  176. else {
  177. module.error(error.path);
  178. return false;
  179. }
  180. }
  181. $.address
  182. .bind('change', module.event.history.change)
  183. ;
  184. }
  185. },
  186. event: {
  187. click: function(event) {
  188. var
  189. tabPath = $(this).data(metadata.tab)
  190. ;
  191. if(tabPath !== undefined) {
  192. if(settings.history) {
  193. module.verbose('Updating page state', event);
  194. $.address.value(tabPath);
  195. }
  196. else {
  197. module.verbose('Changing tab', event);
  198. module.changeTab(tabPath);
  199. }
  200. event.preventDefault();
  201. }
  202. else {
  203. module.debug('No tab specified');
  204. }
  205. },
  206. history: {
  207. change: function(event) {
  208. var
  209. tabPath = event.pathNames.join('/') || module.get.initialPath(),
  210. pageTitle = settings.templates.determineTitle(tabPath) || false
  211. ;
  212. module.performance.display();
  213. module.debug('History change event', tabPath, event);
  214. historyEvent = event;
  215. if(tabPath !== undefined) {
  216. module.changeTab(tabPath);
  217. }
  218. if(pageTitle) {
  219. $.address.title(pageTitle);
  220. }
  221. }
  222. }
  223. },
  224. refresh: function() {
  225. if(activeTabPath) {
  226. module.debug('Refreshing tab', activeTabPath);
  227. module.changeTab(activeTabPath);
  228. }
  229. },
  230. cache: {
  231. read: function(cacheKey) {
  232. return (cacheKey !== undefined)
  233. ? cache[cacheKey]
  234. : false
  235. ;
  236. },
  237. add: function(cacheKey, content) {
  238. cacheKey = cacheKey || activeTabPath;
  239. module.debug('Adding cached content for', cacheKey);
  240. cache[cacheKey] = content;
  241. },
  242. remove: function(cacheKey) {
  243. cacheKey = cacheKey || activeTabPath;
  244. module.debug('Removing cached content for', cacheKey);
  245. delete cache[cacheKey];
  246. }
  247. },
  248. escape: {
  249. string: function(text) {
  250. text = String(text);
  251. return text.replace(regExp.escape, '\\$&');
  252. }
  253. },
  254. set: {
  255. auto: function() {
  256. var
  257. url = (typeof settings.path == 'string')
  258. ? settings.path.replace(/\/$/, '') + '/{$tab}'
  259. : '/{$tab}'
  260. ;
  261. module.verbose('Setting up automatic tab retrieval from server', url);
  262. if($.isPlainObject(settings.apiSettings)) {
  263. settings.apiSettings.url = url;
  264. }
  265. else {
  266. settings.apiSettings = {
  267. url: url
  268. };
  269. }
  270. },
  271. loading: function(tabPath) {
  272. var
  273. $tab = module.get.tabElement(tabPath),
  274. isLoading = $tab.hasClass(className.loading)
  275. ;
  276. if(!isLoading) {
  277. module.verbose('Setting loading state for', $tab);
  278. $tab
  279. .addClass(className.loading)
  280. .siblings($tabs)
  281. .removeClass(className.active + ' ' + className.loading)
  282. ;
  283. if($tab.length > 0) {
  284. settings.onRequest.call($tab[0], tabPath);
  285. }
  286. }
  287. },
  288. state: function(state) {
  289. $.address.value(state);
  290. }
  291. },
  292. changeTab: function(tabPath) {
  293. var
  294. pushStateAvailable = (window.history && window.history.pushState),
  295. shouldIgnoreLoad = (pushStateAvailable && settings.ignoreFirstLoad && firstLoad),
  296. remoteContent = (settings.auto || $.isPlainObject(settings.apiSettings) ),
  297. // only add default path if not remote content
  298. pathArray = (remoteContent && !shouldIgnoreLoad)
  299. ? module.utilities.pathToArray(tabPath)
  300. : module.get.defaultPathArray(tabPath)
  301. ;
  302. tabPath = module.utilities.arrayToPath(pathArray);
  303. $.each(pathArray, function(index, tab) {
  304. var
  305. currentPathArray = pathArray.slice(0, index + 1),
  306. currentPath = module.utilities.arrayToPath(currentPathArray),
  307. isTab = module.is.tab(currentPath),
  308. isLastIndex = (index + 1 == pathArray.length),
  309. $tab = module.get.tabElement(currentPath),
  310. $anchor,
  311. nextPathArray,
  312. nextPath,
  313. isLastTab
  314. ;
  315. module.verbose('Looking for tab', tab);
  316. if(isTab) {
  317. module.verbose('Tab was found', tab);
  318. // scope up
  319. activeTabPath = currentPath;
  320. parameterArray = module.utilities.filterArray(pathArray, currentPathArray);
  321. if(isLastIndex) {
  322. isLastTab = true;
  323. }
  324. else {
  325. nextPathArray = pathArray.slice(0, index + 2);
  326. nextPath = module.utilities.arrayToPath(nextPathArray);
  327. isLastTab = ( !module.is.tab(nextPath) );
  328. if(isLastTab) {
  329. module.verbose('Tab parameters found', nextPathArray);
  330. }
  331. }
  332. if(isLastTab && remoteContent) {
  333. if(!shouldIgnoreLoad) {
  334. module.activate.navigation(currentPath);
  335. module.fetch.content(currentPath, tabPath);
  336. }
  337. else {
  338. module.debug('Ignoring remote content on first tab load', currentPath);
  339. firstLoad = false;
  340. module.cache.add(tabPath, $tab.html());
  341. module.activate.all(currentPath);
  342. settings.onFirstLoad.call($tab[0], currentPath, parameterArray, historyEvent);
  343. settings.onLoad.call($tab[0], currentPath, parameterArray, historyEvent);
  344. }
  345. return false;
  346. }
  347. else {
  348. module.debug('Opened local tab', currentPath);
  349. module.activate.all(currentPath);
  350. if( !module.cache.read(currentPath) ) {
  351. module.cache.add(currentPath, true);
  352. module.debug('First time tab loaded calling tab init');
  353. settings.onFirstLoad.call($tab[0], currentPath, parameterArray, historyEvent);
  354. }
  355. settings.onLoad.call($tab[0], currentPath, parameterArray, historyEvent);
  356. }
  357. }
  358. else if(tabPath.search('/') == -1 && tabPath !== '') {
  359. // look for in page anchor
  360. tabPath = module.escape.string(tabPath);
  361. $anchor = $('#' + tabPath + ', a[name="' + tabPath + '"]');
  362. currentPath = $anchor.closest('[data-tab]').data(metadata.tab);
  363. $tab = module.get.tabElement(currentPath);
  364. // if anchor exists use parent tab
  365. if($anchor && $anchor.length > 0 && currentPath) {
  366. module.debug('Anchor link used, opening parent tab', $tab, $anchor);
  367. if( !$tab.hasClass(className.active) ) {
  368. setTimeout(function() {
  369. module.scrollTo($anchor);
  370. }, 0);
  371. }
  372. module.activate.all(currentPath);
  373. if( !module.cache.read(currentPath) ) {
  374. module.cache.add(currentPath, true);
  375. module.debug('First time tab loaded calling tab init');
  376. settings.onFirstLoad.call($tab[0], currentPath, parameterArray, historyEvent);
  377. }
  378. settings.onLoad.call($tab[0], currentPath, parameterArray, historyEvent);
  379. return false;
  380. }
  381. }
  382. else {
  383. module.error(error.missingTab, $module, $context, currentPath);
  384. return false;
  385. }
  386. });
  387. },
  388. scrollTo: function($element) {
  389. var
  390. scrollOffset = ($element && $element.length > 0)
  391. ? $element.offset().top
  392. : false
  393. ;
  394. if(scrollOffset !== false) {
  395. module.debug('Forcing scroll to an in-page link in a hidden tab', scrollOffset, $element);
  396. $(document).scrollTop(scrollOffset);
  397. }
  398. },
  399. update: {
  400. content: function(tabPath, html, evaluateScripts) {
  401. var
  402. $tab = module.get.tabElement(tabPath),
  403. tab = $tab[0]
  404. ;
  405. evaluateScripts = (evaluateScripts !== undefined)
  406. ? evaluateScripts
  407. : settings.evaluateScripts
  408. ;
  409. if(typeof settings.cacheType == 'string' && settings.cacheType.toLowerCase() == 'dom' && typeof html !== 'string') {
  410. $tab
  411. .empty()
  412. .append($(html).clone(true))
  413. ;
  414. }
  415. else {
  416. if(evaluateScripts) {
  417. module.debug('Updating HTML and evaluating inline scripts', tabPath, html);
  418. $tab.html(html);
  419. }
  420. else {
  421. module.debug('Updating HTML', tabPath, html);
  422. tab.innerHTML = html;
  423. }
  424. }
  425. }
  426. },
  427. fetch: {
  428. content: function(tabPath, fullTabPath) {
  429. var
  430. $tab = module.get.tabElement(tabPath),
  431. apiSettings = {
  432. dataType : 'html',
  433. encodeParameters : false,
  434. on : 'now',
  435. cache : settings.alwaysRefresh,
  436. headers : {
  437. 'X-Remote': true
  438. },
  439. onSuccess : function(response) {
  440. if(settings.cacheType == 'response') {
  441. module.cache.add(fullTabPath, response);
  442. }
  443. module.update.content(tabPath, response);
  444. if(tabPath == activeTabPath) {
  445. module.debug('Content loaded', tabPath);
  446. module.activate.tab(tabPath);
  447. }
  448. else {
  449. module.debug('Content loaded in background', tabPath);
  450. }
  451. settings.onFirstLoad.call($tab[0], tabPath, parameterArray, historyEvent);
  452. settings.onLoad.call($tab[0], tabPath, parameterArray, historyEvent);
  453. if(settings.loadOnce) {
  454. module.cache.add(fullTabPath, true);
  455. }
  456. else if(typeof settings.cacheType == 'string' && settings.cacheType.toLowerCase() == 'dom' && $tab.children().length > 0) {
  457. setTimeout(function() {
  458. var
  459. $clone = $tab.children().clone(true)
  460. ;
  461. $clone = $clone.not('script');
  462. module.cache.add(fullTabPath, $clone);
  463. }, 0);
  464. }
  465. else {
  466. module.cache.add(fullTabPath, $tab.html());
  467. }
  468. },
  469. urlData: {
  470. tab: fullTabPath
  471. }
  472. },
  473. request = $tab.api('get request') || false,
  474. existingRequest = ( request && request.state() === 'pending' ),
  475. requestSettings,
  476. cachedContent
  477. ;
  478. fullTabPath = fullTabPath || tabPath;
  479. cachedContent = module.cache.read(fullTabPath);
  480. if(settings.cache && cachedContent) {
  481. module.activate.tab(tabPath);
  482. module.debug('Adding cached content', fullTabPath);
  483. if(!settings.loadOnce) {
  484. if(settings.evaluateScripts == 'once') {
  485. module.update.content(tabPath, cachedContent, false);
  486. }
  487. else {
  488. module.update.content(tabPath, cachedContent);
  489. }
  490. }
  491. settings.onLoad.call($tab[0], tabPath, parameterArray, historyEvent);
  492. }
  493. else if(existingRequest) {
  494. module.set.loading(tabPath);
  495. module.debug('Content is already loading', fullTabPath);
  496. }
  497. else if($.api !== undefined) {
  498. requestSettings = $.extend(true, {}, settings.apiSettings, apiSettings);
  499. module.debug('Retrieving remote content', fullTabPath, requestSettings);
  500. module.set.loading(tabPath);
  501. $tab.api(requestSettings);
  502. }
  503. else {
  504. module.error(error.api);
  505. }
  506. }
  507. },
  508. activate: {
  509. all: function(tabPath) {
  510. module.activate.tab(tabPath);
  511. module.activate.navigation(tabPath);
  512. },
  513. tab: function(tabPath) {
  514. var
  515. $tab = module.get.tabElement(tabPath),
  516. $deactiveTabs = (settings.deactivate == 'siblings')
  517. ? $tab.siblings($tabs)
  518. : $tabs.not($tab),
  519. isActive = $tab.hasClass(className.active)
  520. ;
  521. module.verbose('Showing tab content for', $tab);
  522. if(!isActive) {
  523. $tab
  524. .addClass(className.active)
  525. ;
  526. $deactiveTabs
  527. .removeClass(className.active + ' ' + className.loading)
  528. ;
  529. if($tab.length > 0) {
  530. settings.onVisible.call($tab[0], tabPath);
  531. }
  532. }
  533. },
  534. navigation: function(tabPath) {
  535. var
  536. $navigation = module.get.navElement(tabPath),
  537. $deactiveNavigation = (settings.deactivate == 'siblings')
  538. ? $navigation.siblings($allModules)
  539. : $allModules.not($navigation),
  540. isActive = $navigation.hasClass(className.active)
  541. ;
  542. module.verbose('Activating tab navigation for', $navigation, tabPath);
  543. if(!isActive) {
  544. $navigation
  545. .addClass(className.active)
  546. ;
  547. $deactiveNavigation
  548. .removeClass(className.active + ' ' + className.loading)
  549. ;
  550. }
  551. }
  552. },
  553. deactivate: {
  554. all: function() {
  555. module.deactivate.navigation();
  556. module.deactivate.tabs();
  557. },
  558. navigation: function() {
  559. $allModules
  560. .removeClass(className.active)
  561. ;
  562. },
  563. tabs: function() {
  564. $tabs
  565. .removeClass(className.active + ' ' + className.loading)
  566. ;
  567. }
  568. },
  569. is: {
  570. tab: function(tabName) {
  571. return (tabName !== undefined)
  572. ? ( module.get.tabElement(tabName).length > 0 )
  573. : false
  574. ;
  575. }
  576. },
  577. get: {
  578. initialPath: function() {
  579. return $allModules.eq(0).data(metadata.tab) || $tabs.eq(0).data(metadata.tab);
  580. },
  581. path: function() {
  582. return $.address.value();
  583. },
  584. // adds default tabs to tab path
  585. defaultPathArray: function(tabPath) {
  586. return module.utilities.pathToArray( module.get.defaultPath(tabPath) );
  587. },
  588. defaultPath: function(tabPath) {
  589. var
  590. $defaultNav = $allModules.filter('[data-' + metadata.tab + '^="' + module.escape.string(tabPath) + '/"]').eq(0),
  591. defaultTab = $defaultNav.data(metadata.tab) || false
  592. ;
  593. if( defaultTab ) {
  594. module.debug('Found default tab', defaultTab);
  595. if(recursionDepth < settings.maxDepth) {
  596. recursionDepth++;
  597. return module.get.defaultPath(defaultTab);
  598. }
  599. module.error(error.recursion);
  600. }
  601. else {
  602. module.debug('No default tabs found for', tabPath, $tabs);
  603. }
  604. recursionDepth = 0;
  605. return tabPath;
  606. },
  607. navElement: function(tabPath) {
  608. tabPath = tabPath || activeTabPath;
  609. return $allModules.filter('[data-' + metadata.tab + '="' + module.escape.string(tabPath) + '"]');
  610. },
  611. tabElement: function(tabPath) {
  612. var
  613. $fullPathTab,
  614. $simplePathTab,
  615. tabPathArray,
  616. lastTab
  617. ;
  618. tabPath = tabPath || activeTabPath;
  619. tabPathArray = module.utilities.pathToArray(tabPath);
  620. lastTab = module.utilities.last(tabPathArray);
  621. $fullPathTab = $tabs.filter('[data-' + metadata.tab + '="' + module.escape.string(tabPath) + '"]');
  622. $simplePathTab = $tabs.filter('[data-' + metadata.tab + '="' + module.escape.string(lastTab) + '"]');
  623. return ($fullPathTab.length > 0)
  624. ? $fullPathTab
  625. : $simplePathTab
  626. ;
  627. },
  628. tab: function() {
  629. return activeTabPath;
  630. }
  631. },
  632. determine: {
  633. activeTab: function() {
  634. var activeTab = null;
  635. $tabs.each(function(_index, tab) {
  636. var $tab = $(tab);
  637. if( $tab.hasClass(className.active) ) {
  638. var
  639. tabPath = $(this).data(metadata.tab),
  640. $anchor = $allModules.filter('[data-' + metadata.tab + '="' + module.escape.string(tabPath) + '"]')
  641. ;
  642. if( $anchor.hasClass(className.active) ) {
  643. activeTab = tabPath;
  644. }
  645. }
  646. });
  647. return activeTab;
  648. }
  649. },
  650. utilities: {
  651. filterArray: function(keepArray, removeArray) {
  652. return $.grep(keepArray, function(keepValue) {
  653. return ( $.inArray(keepValue, removeArray) == -1);
  654. });
  655. },
  656. last: function(array) {
  657. return Array.isArray(array)
  658. ? array[ array.length - 1]
  659. : false
  660. ;
  661. },
  662. pathToArray: function(pathName) {
  663. if(pathName === undefined) {
  664. pathName = activeTabPath;
  665. }
  666. return typeof pathName == 'string'
  667. ? pathName.split('/')
  668. : [pathName]
  669. ;
  670. },
  671. arrayToPath: function(pathArray) {
  672. return Array.isArray(pathArray)
  673. ? pathArray.join('/')
  674. : false
  675. ;
  676. }
  677. },
  678. setting: function(name, value) {
  679. module.debug('Changing setting', name, value);
  680. if( $.isPlainObject(name) ) {
  681. $.extend(true, settings, name);
  682. }
  683. else if(value !== undefined) {
  684. if($.isPlainObject(settings[name])) {
  685. $.extend(true, settings[name], value);
  686. }
  687. else {
  688. settings[name] = value;
  689. }
  690. }
  691. else {
  692. return settings[name];
  693. }
  694. },
  695. internal: function(name, value) {
  696. if( $.isPlainObject(name) ) {
  697. $.extend(true, module, name);
  698. }
  699. else if(value !== undefined) {
  700. module[name] = value;
  701. }
  702. else {
  703. return module[name];
  704. }
  705. },
  706. debug: function() {
  707. if(!settings.silent && settings.debug) {
  708. if(settings.performance) {
  709. module.performance.log(arguments);
  710. }
  711. else {
  712. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  713. module.debug.apply(console, arguments);
  714. }
  715. }
  716. },
  717. verbose: function() {
  718. if(!settings.silent && settings.verbose && settings.debug) {
  719. if(settings.performance) {
  720. module.performance.log(arguments);
  721. }
  722. else {
  723. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  724. module.verbose.apply(console, arguments);
  725. }
  726. }
  727. },
  728. error: function() {
  729. if(!settings.silent) {
  730. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  731. module.error.apply(console, arguments);
  732. }
  733. },
  734. performance: {
  735. log: function(message) {
  736. var
  737. currentTime,
  738. executionTime,
  739. previousTime
  740. ;
  741. if(settings.performance) {
  742. currentTime = new Date().getTime();
  743. previousTime = time || currentTime;
  744. executionTime = currentTime - previousTime;
  745. time = currentTime;
  746. performance.push({
  747. 'Name' : message[0],
  748. 'Arguments' : [].slice.call(message, 1) || '',
  749. 'Element' : element,
  750. 'Execution Time' : executionTime
  751. });
  752. }
  753. clearTimeout(module.performance.timer);
  754. module.performance.timer = setTimeout(module.performance.display, 500);
  755. },
  756. display: function() {
  757. var
  758. title = settings.name + ':',
  759. totalTime = 0
  760. ;
  761. time = false;
  762. clearTimeout(module.performance.timer);
  763. $.each(performance, function(index, data) {
  764. totalTime += data['Execution Time'];
  765. });
  766. title += ' ' + totalTime + 'ms';
  767. if(moduleSelector) {
  768. title += ' \'' + moduleSelector + '\'';
  769. }
  770. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  771. console.groupCollapsed(title);
  772. if(console.table) {
  773. console.table(performance);
  774. }
  775. else {
  776. $.each(performance, function(index, data) {
  777. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  778. });
  779. }
  780. console.groupEnd();
  781. }
  782. performance = [];
  783. }
  784. },
  785. invoke: function(query, passedArguments, context) {
  786. var
  787. object = instance,
  788. maxDepth,
  789. found,
  790. response
  791. ;
  792. passedArguments = passedArguments || queryArguments;
  793. context = element || context;
  794. if(typeof query == 'string' && object !== undefined) {
  795. query = query.split(/[\. ]/);
  796. maxDepth = query.length - 1;
  797. $.each(query, function(depth, value) {
  798. var camelCaseValue = (depth != maxDepth)
  799. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  800. : query
  801. ;
  802. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  803. object = object[camelCaseValue];
  804. }
  805. else if( object[camelCaseValue] !== undefined ) {
  806. found = object[camelCaseValue];
  807. return false;
  808. }
  809. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  810. object = object[value];
  811. }
  812. else if( object[value] !== undefined ) {
  813. found = object[value];
  814. return false;
  815. }
  816. else {
  817. module.error(error.method, query);
  818. return false;
  819. }
  820. });
  821. }
  822. if ( $.isFunction( found ) ) {
  823. response = found.apply(context, passedArguments);
  824. }
  825. else if(found !== undefined) {
  826. response = found;
  827. }
  828. if(Array.isArray(returnedValue)) {
  829. returnedValue.push(response);
  830. }
  831. else if(returnedValue !== undefined) {
  832. returnedValue = [returnedValue, response];
  833. }
  834. else if(response !== undefined) {
  835. returnedValue = response;
  836. }
  837. return found;
  838. }
  839. };
  840. if(methodInvoked) {
  841. if(instance === undefined) {
  842. module.initialize();
  843. }
  844. module.invoke(query);
  845. }
  846. else {
  847. if(instance !== undefined) {
  848. instance.invoke('destroy');
  849. }
  850. module.initialize();
  851. }
  852. })
  853. ;
  854. return (returnedValue !== undefined)
  855. ? returnedValue
  856. : this
  857. ;
  858. };
  859. // shortcut for tabbed content with no defined navigation
  860. $.tab = function() {
  861. $(window).tab.apply(this, arguments);
  862. };
  863. $.fn.tab.settings = {
  864. name : 'Tab',
  865. namespace : 'tab',
  866. silent : false,
  867. debug : false,
  868. verbose : false,
  869. performance : true,
  870. auto : false, // uses pjax style endpoints fetching content from same url with remote-content headers
  871. history : false, // use browser history
  872. historyType : 'hash', // #/ or html5 state
  873. path : false, // base path of url
  874. context : false, // specify a context that tabs must appear inside
  875. childrenOnly : false, // use only tabs that are children of context
  876. maxDepth : 25, // max depth a tab can be nested
  877. deactivate : 'siblings', // whether tabs should deactivate sibling menu elements or all elements initialized together
  878. alwaysRefresh : false, // load tab content new every tab click
  879. cache : true, // cache the content requests to pull locally
  880. loadOnce : false, // Whether tab data should only be loaded once when using remote content
  881. cacheType : 'response', // Whether to cache exact response, or to html cache contents after scripts execute
  882. ignoreFirstLoad : false, // don't load remote content on first load
  883. apiSettings : false, // settings for api call
  884. evaluateScripts : 'once', // whether inline scripts should be parsed (true/false/once). Once will not re-evaluate on cached content
  885. onFirstLoad : function(tabPath, parameterArray, historyEvent) {}, // called first time loaded
  886. onLoad : function(tabPath, parameterArray, historyEvent) {}, // called on every load
  887. onVisible : function(tabPath, parameterArray, historyEvent) {}, // called every time tab visible
  888. onRequest : function(tabPath, parameterArray, historyEvent) {}, // called ever time a tab beings loading remote content
  889. templates : {
  890. determineTitle: function(tabArray) {} // returns page title for path
  891. },
  892. error: {
  893. api : 'You attempted to load content without API module',
  894. method : 'The method you called is not defined',
  895. missingTab : 'Activated tab cannot be found. Tabs are case-sensitive.',
  896. noContent : 'The tab you specified is missing a content url.',
  897. path : 'History enabled, but no path was specified',
  898. recursion : 'Max recursive depth reached',
  899. legacyInit : 'onTabInit has been renamed to onFirstLoad in 2.0, please adjust your code.',
  900. legacyLoad : 'onTabLoad has been renamed to onLoad in 2.0. Please adjust your code',
  901. state : 'History requires Asual\'s Address library <https://github.com/asual/jquery-address>'
  902. },
  903. regExp : {
  904. escape : /[-[\]{}()*+?.,\\^$|#\s:=@]/g
  905. },
  906. metadata : {
  907. tab : 'tab',
  908. loaded : 'loaded',
  909. promise: 'promise'
  910. },
  911. className : {
  912. loading : 'loading',
  913. active : 'active'
  914. },
  915. selector : {
  916. tabs : '.ui.tab',
  917. ui : '.ui'
  918. }
  919. };
  920. })( jQuery, window, document );