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