1/* 2 * jQuery EasyTabs plugin 3.2.0 3 * 4 * Copyright (c) 2010-2011 Steve Schwartz (JangoSteve) 5 * 6 * Dual licensed under the MIT and GPL licenses: 7 * http://www.opensource.org/licenses/mit-license.php 8 * http://www.gnu.org/licenses/gpl.html 9 * 10 * Date: Thu May 09 17:30:00 2013 -0500 11 */ 12( function($) { 13 14 $.easytabs = function(container, options) { 15 16 // Attach to plugin anything that should be available via 17 // the $container.data('easytabs') object 18 var plugin = this, 19 $container = $(container), 20 21 defaults = { 22 animate: true, 23 panelActiveClass: "active", 24 tabActiveClass: "active", 25 defaultTab: "", 26 animationSpeed: "normal", 27 tabs: "> ul > li", 28 updateHash: true, 29 cycle: false, 30 collapsible: false, 31 collapsedClass: "collapsed", 32 collapsedByDefault: true, 33 uiTabs: false, 34 transitionIn: 'fadeIn', 35 transitionOut: 'fadeOut', 36 transitionInEasing: 'swing', 37 transitionOutEasing: 'swing', 38 transitionCollapse: 'slideUp', 39 transitionUncollapse: 'slideDown', 40 transitionCollapseEasing: 'swing', 41 transitionUncollapseEasing: 'swing', 42 containerClass: "", 43 tabsClass: "", 44 tabClass: "", 45 panelClass: "", 46 cache: true, 47 event: 'click', 48 panelContext: $container 49 }, 50 51 // Internal instance variables 52 // (not available via easytabs object) 53 $defaultTab, 54 $defaultTabLink, 55 transitions, 56 lastHash, 57 skipUpdateToHash, 58 animationSpeeds = { 59 fast: 200, 60 normal: 400, 61 slow: 600 62 }, 63 64 // Shorthand variable so that we don't need to call 65 // plugin.settings throughout the plugin code 66 settings; 67 68 // ============================================================= 69 // Functions available via easytabs object 70 // ============================================================= 71 72 plugin.init = function() { 73 74 plugin.settings = settings = $.extend({}, defaults, options); 75 settings.bind_str = settings.event+".easytabs"; 76 77 // Add jQuery UI's crazy class names to markup, 78 // so that markup will match theme CSS 79 if ( settings.uiTabs ) { 80 settings.tabActiveClass = 'ui-tabs-selected'; 81 settings.containerClass = 'ui-tabs ui-widget ui-widget-content ui-corner-all'; 82 settings.tabsClass = 'ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all'; 83 settings.tabClass = 'ui-state-default ui-corner-top'; 84 settings.panelClass = 'ui-tabs-panel ui-widget-content ui-corner-bottom'; 85 } 86 87 // If collapsible is true and defaultTab specified, assume user wants defaultTab showing (not collapsed) 88 if ( settings.collapsible && options.defaultTab !== undefined && options.collpasedByDefault === undefined ) { 89 settings.collapsedByDefault = false;
90 } 91 92 // Convert 'normal', 'fast', and 'slow' animation speed settings to their respective speed in milliseconds 93 if ( typeof(settings.animationSpeed) === 'string' ) { 94 settings.animationSpeed = animationSpeeds[settings.animationSpeed]; 95 } 96 97 $('a.anchor').remove().prependTo('body'); 98 99 // Store easytabs object on container so we can easily set 100 // properties throughout 101 $container.data('easytabs', {}); 102 103 plugin.setTransitions(); 104 105 plugin.getTabs(); 106 107 addClasses(); 108 109 setDefaultTab(); 110 111 bindToTabClicks(); 112 113 initHashChange(); 114 115 initCycle(); 116 117 // Append data-easytabs HTML attribute to make easy to query for 118 // easytabs instances via CSS pseudo-selector 119 $container.attr('data-easytabs', true); 120 }; 121 122 // Set transitions for switching between tabs based on options. 123 // Could be used to update transitions if settings are changes. 124 plugin.setTransitions = function() { 125 transitions = ( settings.animate ) ? { 126 show: settings.transitionIn, 127 hide: settings.transitionOut, 128 speed: settings.animationSpeed, 129 collapse: settings.transitionCollapse, 130 uncollapse: settings.transitionUncollapse, 131 halfSpeed: settings.animationSpeed / 2 132 } : 133 { 134 show: "show", 135 hide: "hide", 136 speed: 0, 137 collapse: "hide", 138 uncollapse: "show", 139 halfSpeed: 0 140 }; 141 }; 142 143 // Find and instantiate tabs and panels. 144 // Could be used to reset tab and panel collection if markup is 145 // modified. 146 plugin.getTabs = function() { 147 var $matchingPanel; 148 149 // Find the initial set of elements matching the setting.tabs 150 // CSS selector within the container 151 plugin.tabs = $container.find(settings.tabs), 152 153 154 // Instantiate panels as empty jquery object 155 plugin.panels = $(), 156 157 plugin.tabs.each(function(){ 158 var $tab = $(this), 159 $a = $tab.children('a'), 160 161 // targetId is the ID of the panel, which is either the 162 // `href` attribute for non-ajax tabs, or in the 163 // `data-target` attribute for ajax tabs since the `href` is 164 // the ajax URL 165 targetId = $tab.children('a').data('target'); 166 167 $tab.data('easytabs', {}); 168 169 // If the tab has a `data-target` attribute, and is thus an ajax tab 170 if ( targetId !== undefined && targetId !== null ) { 171 $tab.data('easytabs').ajax = $a.attr('href'); 172 } else { 173 targetId = $a.attr('href'); 174 } 175 targetId = targetId.match(/#([^\?]+)/)[1]; 176 177 $matchingPanel = settings.panelContext.find("#" + targetId); 178 179 // If tab has a matching panel, add it to panels 180 if ( $matchingPanel.length ) { 181 182 // Store panel height before hiding 183 $matchingPanel.data('easytabs', { 184 position: $matchingPanel.css('position'), 185 visibility: $matchingPanel.css('visibility') 186 }); 187 188 // Don't hide panel if it's active (allows `getTabs` to be called manually to re-instantiate tab collection) 189 $matchingPanel.not(settings.panelActiveClass).hide(); 190 191 plugin.panels = plugin.panels.add($matchingPanel); 192 193 $tab.data('easytabs').panel = $matchingPanel; 194 195 // Otherwise, remove tab from tabs collection 196 } else { 197 plugin.tabs = plugin.tabs.not($tab); 198 if ('console' in window) { 199 console.warn('Warning: tab without matching panel for selector \'#' + targetId +'\' removed from set'); 200 } 201 } 202 }); 203 }; 204 205 // Select tab and fire callback 206 plugin.selectTab = function($clicked, callback) { 207 var url = window.location, 208 hash = url.hash.match(/^[^\?]*/)[0], 209 $targetPanel = $clicked.parent().data('easytabs').panel, 210 ajaxUrl = $clicked.parent().data('easytabs').ajax; 211 212 // Tab is collapsible and active => toggle collapsed state 213 if( settings.collapsible && ! skipUpdateToHash && ($clicked.hasClass(settings.tabActiveClass) || $clicked.hasClass(settings.collapsedClass)) ) { 214 plugin.toggleTabCollapse($clicked, $targetPanel, ajaxUrl, callback); 215 216 // Tab is not active and panel is not active => select tab 217 } else if( ! $clicked.hasClass(settings.tabActiveClass) || ! $targetPanel.hasClass(settings.panelActiveClass) ){ 218 activateTab($clicked, $targetPanel, ajaxUrl, callback); 219 220 // Cache is disabled => reload (e.g reload an ajax tab). 221 } else if ( ! settings.cache ){ 222 activateTab($clicked, $targetPanel, ajaxUrl, callback); 223 } 224 225 }; 226
227 // Toggle tab collapsed state and fire callback 228 plugin.toggleTabCollapse = function($clicked, $targetPanel, ajaxUrl, callback) { 229 plugin.panels.stop(true,true); 230 231 if( fire($container,"easytabs:before", [$clicked, $targetPanel, settings]) ){ 232 plugin.tabs.filter("." + settings.tabActiveClass).removeClass(settings.tabActiveClass).children().removeClass(settings.tabActiveClass); 233 234 // If panel is collapsed, uncollapse it 235 if( $clicked.hasClass(settings.collapsedClass) ){ 236 237 // If ajax panel and not already cached 238 if( ajaxUrl && (!settings.cache || !$clicked.parent().data('easytabs').cached) ) { 239 $container.trigger('easytabs:ajax:beforeSend', [$clicked, $targetPanel]); 240 241 $targetPanel.load(ajaxUrl, function(response, status, xhr){ 242 $clicked.parent().data('easytabs').cached = true; 243 $container.trigger('easytabs:ajax:complete', [$clicked, $targetPanel, response, status, xhr]); 244 }); 245 } 246 247 // Update CSS classes of tab and panel 248 $clicked.parent() 249 .removeClass(settings.collapsedClass) 250 .addClass(settings.tabActiveClass) 251 .children() 252 .removeClass(settings.collapsedClass) 253 .addClass(settings.tabActiveClass); 254 255 $targetPanel 256 .addClass(settings.panelActiveClass) 257 [transitions.uncollapse](transitions.speed, settings.transitionUncollapseEasing, function(){ 258 $container.trigger('easytabs:midTransition', [$clicked, $targetPanel, settings]); 259 if(typeof callback == 'function') callback(); 260 }); 261 262 // Otherwise, collapse it 263 } else { 264 265 // Update CSS classes of tab and panel 266 $clicked.addClass(settings.collapsedClass) 267 .parent() 268 .addClass(settings.collapsedClass); 269 270 $targetPanel 271 .removeClass(settings.panelActiveClass) 272 [transitions.collapse](transitions.speed, settings.transitionCollapseEasing, function(){ 273 $container.trigger("easytabs:midTransition", [$clicked, $targetPanel, settings]); 274 if(typeof callback == 'function') callback(); 275 }); 276 } 277 } 278 }; 279 280 281 // Find tab with target panel matching value 282 plugin.matchTab = function(hash) { 283 return plugin.tabs.find("[href='" + hash + "'],[data-target='" + hash + "']").first(); 284 }; 285 286 // Find panel with `id` matching value 287 plugin.matchInPanel = function(hash) { 288 return ( hash && plugin.validId(hash) ? plugin.panels.filter(':has(' + hash + ')').first() : [] ); 289 }; 290 291 // Make sure hash is a valid id value (admittedly strict in that HTML5 allows almost anything without a space) 292 // but jQuery has issues with such id values anyway, so we can afford to be strict here. 293 plugin.validId = function(id) { 294 return id.substr(1).match(/^[A-Za-z][A-Za-z0-9\-_:\.]*$/); 295 }; 296 297 // Select matching tab when URL hash changes 298 plugin.selectTabFromHashChange = function() { 299 var hash = window.location.hash.match(/^[^\?]*/)[0], 300 $tab = plugin.matchTab(hash), 301 $panel; 302 303 if ( settings.updateHash ) { 304 305 // If hash directly matches tab 306 if( $tab.length ){ 307 skipUpdateToHash = true; 308 plugin.selectTab( $tab ); 309 310 } else { 311 $panel = plugin.matchInPanel(hash); 312 313 // If panel contains element matching hash 314 if ( $panel.length ) { 315 hash = '#' + $panel.attr('id'); 316 $tab = plugin.matchTab(hash); 317 skipUpdateToHash = true; 318 plugin.selectTab( $tab ); 319 320 // If default tab is not active... 321 } else if ( ! $defaultTab.hasClass(settings.tabActiveClass) && ! settings.cycle ) { 322 323 // ...and hash is blank or matches a parent of the tab container or 324 // if the last tab (before the hash updated) was one of the other tabs in this container. 325 if ( hash === '' || plugin.matchTab(lastHash).length || $container.closest(hash).length ) { 326 skipUpdateToHash = true; 327 plugin.selectTab( $defaultTabLink ); 328 } 329 } 330 } 331 } 332 }; 333 334 // Cycle through tabs 335 plugin.cycleTabs = function(tabNumber){ 336 if(settings.cycle){ 337 tabNumber = tabNumber % plugin.tabs.length; 338 $tab = $( plugin.tabs[tabNumber] ).children("a").first(); 339 skipUpdateToHash = true; 340 plugin.selectTab( $tab, function() { 341 setTimeout(function(){ plugin.cycleTabs(tabNumber + 1); }, settings.cycle); 342 }); 343 } 344 }; 345 346 // Convenient public methods 347 plugin.publicMethods = { 348 select: function(tabSelector){ 349 var $tab; 350 351 // Find tab container that matches selector (like 'li#tab-one' which contains tab link) 352 if ( ($tab = plugin.tabs.filter(tabSelector)).length === 0 ) { 353 354 // Find direct tab link that matches href (like 'a[href="#panel-1"]') 355 if ( ($tab = plugin.tabs.find("a[href='" + tabSelector + "']")).length === 0 ) { 356 357 // Find direct tab link that matches selector (like 'a#tab-1') 358 if ( ($tab = plugin.tabs.find("a" + tabSelector)).length === 0 ) { 359 360 // Find direct tab link that matches data-target (lik 'a[data-target="#panel-1"]') 361 if ( ($tab = plugin.tabs.find("[data-target='" + tabSelector + "']")).length === 0 ) { 362 363 // Find direct tab link that ends in the matching href (like 'a[href$="#panel-1"]', which would also match http://example.com/currentpage/#panel-1) 364 if ( ($tab = plugin.tabs.find("a[href$='" + tabSelector + "']")).length === 0 ) { 365 366 $.error('Tab \'' + tabSelector + '\' does not exist in tab set'); 367 } 368 } 369 } 370 } 371 } else { 372 // Select the child tab link, since the first option finds the tab container (like <li>) 373 $tab = $tab.children("a").first(); 374 } 375 plugin.selectTab($tab); 376 } 377 }; 378 379 // ============================================================= 380 // Private functions 381 // ============================================================= 382 383 // Triggers an event on an element and returns the event result 384 var fire = function(obj, name, data) { 385 var event = $.Event(name); 386 obj.trigger(event, data); 387 return event.result !== false; 388 } 389 390 // Add CSS classes to markup (if specified), called by init 391 var addClasses = function() { 392 $container.addClass(settings.containerClass); 393 plugin.tabs.parent().addClass(settings.tabsClass); 394 plugin.tabs.addClass(settings.tabClass); 395 plugin.panels.addClass(settings.panelClass); 396 }; 397 398 // Set the default tab, whether from hash (bookmarked) or option, 399 // called by init 400 var setDefaultTab = function(){ 401 var hash = window.location.hash.match(/^[^\?]*/)[0], 402 $selectedTab = plugin.matchTab(hash).parent(), 403 $panel; 404 405 // If hash directly matches one of the tabs, active on page-load 406 if( $selectedTab.length === 1 ){ 407 $defaultTab = $selectedTab; 408 settings.cycle = false;
409 410 } else { 411 $panel = plugin.matchInPanel(hash); 412 413 // If one of the panels contains the element matching the hash, 414 // make it active on page-load 415 if ( $panel.length ) { 416 hash = '#' + $panel.attr('id'); 417 $defaultTab = plugin.matchTab(hash).parent(); 418 419 // Otherwise, make the default tab the one that's active on page-load 420 } else { 421 $defaultTab = plugin.tabs.parent().find(settings.defaultTab); 422 if ( $defaultTab.length === 0 ) { 423 $.error("The specified default tab ('" + settings.defaultTab + "') could not be found in the tab set ('" + settings.tabs + "') out of " + plugin.tabs.length + " tabs."); 424 } 425 } 426 } 427 428 $defaultTabLink = $defaultTab.children("a").first(); 429 430 activateDefaultTab($selectedTab); 431 }; 432 433 // Activate defaultTab (or collapse by default), called by setDefaultTab 434 var activateDefaultTab = function($selectedTab) { 435 var defaultPanel, 436 defaultAjaxUrl; 437 438 if ( settings.collapsible && $selectedTab.length === 0 && settings.collapsedByDefault ) { 439 $defaultTab 440 .addClass(settings.collapsedClass) 441 .children() 442 .addClass(settings.collapsedClass); 443 444 } else { 445 446 defaultPanel = $( $defaultTab.data('easytabs').panel ); 447 defaultAjaxUrl = $defaultTab.data('easytabs').ajax; 448 449 if ( defaultAjaxUrl && (!settings.cache || !$defaultTab.data('easytabs').cached) ) { 450 $container.trigger('easytabs:ajax:beforeSend', [$defaultTabLink, defaultPanel]); 451 defaultPanel.load(defaultAjaxUrl, function(response, status, xhr){ 452 $defaultTab.data('easytabs').cached = true; 453 $container.trigger('easytabs:ajax:complete', [$defaultTabLink, defaultPanel, response, status, xhr]); 454 }); 455 } 456 457 $defaultTab.data('easytabs').panel 458 .show() 459 .addClass(settings.panelActiveClass); 460 461 $defaultTab 462 .addClass(settings.tabActiveClass) 463 .children() 464 .addClass(settings.tabActiveClass); 465 } 466 467 // Fire event when the plugin is initialised 468 $container.trigger("easytabs:initialised", [$defaultTabLink, defaultPanel]); 469 }; 470 471 // Bind tab-select funtionality to namespaced click event, called by 472 // init 473 var bindToTabClicks = function() { 474 plugin.tabs.children("a").bind(settings.bind_str, function(e) { 475 476 // Stop cycling when a tab is clicked 477 settings.cycle = false; 478 479 // Hash will be updated when tab is clicked, 480 // don't cause tab to re-select when hash-change event is fired 481 skipUpdateToHash = false; 482 483 // Select the panel for the clicked tab 484 plugin.selectTab( $(this) ); 485 486 // Don't follow the link to the anchor 487 e.preventDefault ? e.preventDefault() : e.returnValue = false; 488 }); 489 }; 490 491 // Activate a given tab/panel, called from plugin.selectTab: 492 // 493 // * fire `easytabs:before` hook 494 // * get ajax if new tab is an uncached ajax tab 495 // * animate out previously-active panel 496 // * fire `easytabs:midTransition` hook 497 // * update URL hash 498 // * animate in newly-active panel 499 // * update CSS classes for inactive and active tabs/panels 500 // 501 // TODO: This could probably be broken out into many more modular 502 // functions 503 var activateTab = function($clicked, $targetPanel, ajaxUrl, callback) { 504 plugin.panels.stop(true,true); 505 506 if( fire($container,"easytabs:before", [$clicked, $targetPanel, settings]) ){ 507 var $visiblePanel = plugin.panels.filter(":visible"), 508 $panelContainer = $targetPanel.parent(), 509 targetHeight, 510 visibleHeight, 511 heightDifference, 512 showPanel, 513 hash = window.location.hash.match(/^[^\?]*/)[0]; 514 515 if (settings.animate) { 516 targetHeight = getHeightForHidden($targetPanel); 517 visibleHeight = $visiblePanel.length ? setAndReturnHeight($visiblePanel) : 0; 518 heightDifference = targetHeight - visibleHeight; 519 } 520 521 // Set lastHash to help indicate if defaultTab should be 522 // activated across multiple tab instances. 523 lastHash = hash; 524 525 // TODO: Move this function elsewhere 526 showPanel = function() { 527 // At this point, the previous panel is hidden, and the new one will be selected 528 $container.trigger("easytabs:midTransition", [$clicked, $targetPanel, settings]); 529 530 // Gracefully animate between panels of differing heights, start height change animation *after* panel change if panel needs to contract, 531 // so that there is no chance of making the visible panel overflowing the height of the target panel 532 if (settings.animate && settings.transitionIn == 'fadeIn') { 533 if (heightDifference < 0) 534 $panelContainer.animate({ 535 height: $panelContainer.height() + heightDifference 536 }, transitions.halfSpeed ).css({ 'min-height': '' }); 537 } 538 539 if ( settings.updateHash && ! skipUpdateToHash ) { 540 //window.location = url.toString().replace((url.pathname + hash), (url.pathname + $clicked.attr("href"))); 541 // Not sure why this behaves so differently, but it's more straight forward and seems to have less side-effects 542 if (window.history.pushState) { 543 window.history.pushState(null, null, '#' + $targetPanel.attr('id')); 544 } 545 else { 546 window.location.hash = '#' + $targetPanel.attr('id'); 547 } 548 } else { 549 skipUpdateToHash = false;
550 } 551 552 $targetPanel 553 [transitions.show](transitions.speed, settings.transitionInEasing, function(){ 554 $panelContainer.css({height: '', 'min-height': ''}); // After the transition, unset the height 555 $container.trigger("easytabs:after", [$clicked, $targetPanel, settings]); 556 // callback only gets called if selectTab actually does something, since it's inside the if block 557 if(typeof callback == 'function'){ 558 callback(); 559 } 560 }); 561 }; 562 563 if ( ajaxUrl && (!settings.cache || !$clicked.parent().data('easytabs').cached) ) { 564 $container.trigger('easytabs:ajax:beforeSend', [$clicked, $targetPanel]); 565 $targetPanel.load(ajaxUrl, function(response, status, xhr){ 566 $clicked.parent().data('easytabs').cached = true; 567 $container.trigger('easytabs:ajax:complete', [$clicked, $targetPanel, response, status, xhr]); 568 }); 569 } 570 571 // Gracefully animate between panels of differing heights, start height change animation *before* panel change if panel needs to expand, 572 // so that there is no chance of making the target panel overflowing the height of the visible panel 573 if( settings.animate && settings.transitionOut == 'fadeOut' ) { 574 if( heightDifference > 0 ) { 575 $panelContainer.animate({ 576 height: ( $panelContainer.height() + heightDifference ) 577 }, transitions.halfSpeed ); 578 } else { 579 // Prevent height jumping before height transition is triggered at midTransition 580 $panelContainer.css({ 'min-height': $panelContainer.height() }); 581 } 582 } 583 584 // Change the active tab *first* to provide immediate feedback when the user clicks 585 plugin.tabs.filter("." + settings.tabActiveClass).removeClass(settings.tabActiveClass).children().removeClass(settings.tabActiveClass); 586 plugin.tabs.filter("." + settings.collapsedClass).removeClass(settings.collapsedClass).children().removeClass(settings.collapsedClass); 587 $clicked.parent().addClass(settings.tabActiveClass).children().addClass(settings.tabActiveClass); 588 589 plugin.panels.filter("." + settings.panelActiveClass).removeClass(settings.panelActiveClass); 590 $targetPanel.addClass(settings.panelActiveClass); 591 592 if( $visiblePanel.length ) { 593 $visiblePanel 594 [transitions.hide](transitions.speed, settings.transitionOutEasing, showPanel); 595 } else { 596 $targetPanel 597 [transitions.uncollapse](transitions.speed, settings.transitionUncollapseEasing, showPanel); 598 } 599 } 600 }; 601 602 // Get heights of panels to enable animation between panels of 603 // differing heights, called by activateTab 604 var getHeightForHidden = function($targetPanel){ 605 606 if ( $targetPanel.data('easytabs') && $targetPanel.data('easytabs').lastHeight ) { 607 return $targetPanel.data('easytabs').lastHeight; 608 } 609 610 // this is the only property easytabs changes, so we need to grab its value on each tab change 611 var display = $targetPanel.css('display'), 612 outerCloak, 613 height; 614 615 // Workaround with wrapping height, because firefox returns wrong 616 // height if element itself has absolute positioning. 617 // but try/catch block needed for IE7 and IE8 because they throw 618 // an "Unspecified error" when trying to create an element 619 // with the css position set. 620 try { 621 outerCloak = $('<div></div>', {'position': 'absolute', 'visibility': 'hidden', 'overflow': 'hidden'}); 622 } catch (e) { 623 outerCloak = $('<div></div>', {'visibility': 'hidden', 'overflow': 'hidden'}); 624 } 625 height = $targetPanel 626 .wrap(outerCloak) 627 .css({'position':'relative','visibility':'hidden','display':'block'}) 628 .outerHeight(); 629 630 $targetPanel.unwrap(); 631 632 // Return element to previous state 633 $targetPanel.css({ 634 position: $targetPanel.data('easytabs').position, 635 visibility: $targetPanel.data('easytabs').visibility, 636 display: display 637 }); 638 639 // Cache height 640 $targetPanel.data('easytabs').lastHeight = height; 641 642 return height; 643 }; 644 645 // Since the height of the visible panel may have been manipulated due to interaction, 646 // we want to re-cache the visible height on each tab change, called 647 // by activateTab 648 var setAndReturnHeight = function($visiblePanel) { 649 var height = $visiblePanel.outerHeight(); 650 651 if( $visiblePanel.data('easytabs') ) { 652 $visiblePanel.data('easytabs').lastHeight = height; 653 } else { 654 $visiblePanel.data('easytabs', {lastHeight: height}); 655 } 656 return height; 657 }; 658
659 // Setup hash-change callback for forward- and back-button 660 // functionality, called by init 661 var initHashChange = function(){ 662 663 // enabling back-button with jquery.hashchange plugin 664 // http://benalman.com/projects/jquery-hashchange-plugin/ 665 if(typeof $(window).hashchange === 'function'){ 666 $(window).hashchange( function(){ 667 plugin.selectTabFromHashChange(); 668 }); 669 } else if ($.address && typeof $.address.change === 'function') { // back-button with jquery.address plugin http://www.asual.com/jquery/address/docs/ 670 $.address.change( function(){ 671 plugin.selectTabFromHashChange(); 672 }); 673 } 674 }; 675 676 // Begin cycling if set in options, called by init 677 var initCycle = function(){ 678 var tabNumber; 679 if (settings.cycle) { 680 tabNumber = plugin.tabs.index($defaultTab); 681 setTimeout( function(){ plugin.cycleTabs(tabNumber + 1); }, settings.cycle); 682 } 683 }; 684 685 686 plugin.init(); 687 688 }; 689 690 $.fn.easytabs = function(options) { 691 var args = arguments; 692 693 return this.each(function() { 694 var $this = $(this), 695 plugin = $this.data('easytabs'); 696 697 // Initialization was called with $(el).easytabs( { options } ); 698 if (undefined === plugin) { 699 plugin = new $.easytabs(this, options); 700 $this.data('easytabs', plugin); 701 } 702 703 // User called public method 704 if ( plugin.publicMethods[options] ){ 705 return plugin.publicMethods[options](Array.prototype.slice.call( args, 1 )); 706 } 707 }); 708 }; 709 710})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.