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: "li:first-child", 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 // Instantiate panels as empty jquery object 154 plugin.panels = $(), 155 156 plugin.tabs.each(function(){ 157 var $tab = $(this), 158 $a = $tab.children('a'), 159 160 // targetId is the ID of the panel, which is either the 161 // `href` attribute for non-ajax tabs, or in the 162 // `data-target` attribute for ajax tabs since the `href` is 163 // the ajax URL 164 targetId = $tab.children('a').data('target'); 165 166 $tab.data('easytabs', {}); 167 168 // If the tab has a `data-target` attribute, and is thus an ajax tab 169 if ( targetId !== undefined && targetId !== null ) { 170 $tab.data('easytabs').ajax = $a.attr('href'); 171 } else { 172 targetId = $a.attr('href'); 173 } 174 targetId = targetId.match(/#([^\?]+)/)[1]; 175 176 $matchingPanel = settings.panelContext.find("#" + targetId); 177 178 // If tab has a matching panel, add it to panels 179 if ( $matchingPanel.length ) { 180 181 // Store panel height before hiding 182 $matchingPanel.data('easytabs', { 183 position: $matchingPanel.css('position'), 184 visibility: $matchingPanel.css('visibility') 185 }); 186 187 // Don't hide panel if it's active (allows `getTabs` to be called manually to re-instantiate tab collection) 188 $matchingPanel.not(settings.panelActiveClass).hide(); 189 190 plugin.panels = plugin.panels.add($matchingPanel); 191 192 $tab.data('easytabs').panel = $matchingPanel; 193 194 // Otherwise, remove tab from tabs collection 195 } else { 196 plugin.tabs = plugin.tabs.not($tab); 197 if ('console' in window) { 198 console.warn('Warning: tab without matching panel for selector \'#' + targetId +'\' removed from set'); 199 } 200 } 201 }); 202 }; 203 204 // Select tab and fire callback 205 plugin.selectTab = function($clicked, callback) { 206 var url = window.location, 207 hash = url.hash.match(/^[^\?]*/)[0], 208 $targetPanel = $clicked.parent().data('easytabs').panel, 209 ajaxUrl = $clicked.parent().data('easytabs').ajax; 210 211 // Tab is collapsible and active => toggle collapsed state 212 if( settings.collapsible && ! skipUpdateToHash && ($clicked.hasClass(settings.tabActiveClass) || $clicked.hasClass(settings.collapsedClass)) ) { 213 plugin.toggleTabCollapse($clicked, $targetPanel, ajaxUrl, callback); 214 215 // Tab is not active and panel is not active => select tab 216 } else if( ! $clicked.hasClass(settings.tabActiveClass) || ! $targetPanel.hasClass(settings.panelActiveClass) ){ 217 activateTab($clicked, $targetPanel, ajaxUrl, callback); 218 219 // Cache is disabled => reload (e.g reload an ajax tab). 220 } else if ( ! settings.cache ){ 221 activateTab($clicked, $targetPanel, ajaxUrl, callback); 222 } 223 224 }; 225
226 // Toggle tab collapsed state and fire callback 227 plugin.toggleTabCollapse = function($clicked, $targetPanel, ajaxUrl, callback) { 228 plugin.panels.stop(true,true); 229 230 if( fire($container,"easytabs:before", [$clicked, $targetPanel, settings]) ){ 231 plugin.tabs.filter("." + settings.tabActiveClass).removeClass(settings.tabActiveClass).children().removeClass(settings.tabActiveClass); 232 233 // If panel is collapsed, uncollapse it 234 if( $clicked.hasClass(settings.collapsedClass) ){ 235 236 // If ajax panel and not already cached 237 if( ajaxUrl && (!settings.cache || !$clicked.parent().data('easytabs').cached) ) { 238 $container.trigger('easytabs:ajax:beforeSend', [$clicked, $targetPanel]); 239 240 $targetPanel.load(ajaxUrl, function(response, status, xhr){ 241 $clicked.parent().data('easytabs').cached = true; 242 $container.trigger('easytabs:ajax:complete', [$clicked, $targetPanel, response, status, xhr]); 243 }); 244 } 245 246 // Update CSS classes of tab and panel 247 $clicked.parent() 248 .removeClass(settings.collapsedClass) 249 .addClass(settings.tabActiveClass) 250 .children() 251 .removeClass(settings.collapsedClass) 252 .addClass(settings.tabActiveClass); 253 254 $targetPanel 255 .addClass(settings.panelActiveClass) 256 [transitions.uncollapse](transitions.speed, settings.transitionUncollapseEasing, function(){ 257 $container.trigger('easytabs:midTransition', [$clicked, $targetPanel, settings]); 258 if(typeof callback == 'function') callback(); 259 }); 260 261 // Otherwise, collapse it 262 } else { 263 264 // Update CSS classes of tab and panel 265 $clicked.addClass(settings.collapsedClass) 266 .parent() 267 .addClass(settings.collapsedClass); 268 269 $targetPanel 270 .removeClass(settings.panelActiveClass) 271 [transitions.collapse](transitions.speed, settings.transitionCollapseEasing, function(){ 272 $container.trigger("easytabs:midTransition", [$clicked, $targetPanel, settings]); 273 if(typeof callback == 'function') callback(); 274 }); 275 } 276 } 277 }; 278 279 280 // Find tab with target panel matching value 281 plugin.matchTab = function(hash) { 282 return plugin.tabs.find("[href='" + hash + "'],[data-target='" + hash + "']").first(); 283 }; 284 285 // Find panel with `id` matching value 286 plugin.matchInPanel = function(hash) { 287 return ( hash && plugin.validId(hash) ? plugin.panels.filter(':has(' + hash + ')').first() : [] ); 288 }; 289 290 // Make sure hash is a valid id value (admittedly strict in that HTML5 allows almost anything without a space) 291 // but jQuery has issues with such id values anyway, so we can afford to be strict here. 292 plugin.validId = function(id) { 293 return id.substr(1).match(/^[A-Za-z]+[A-Za-z0-9\-_:\.].$/); 294 }; 295 296 // Select matching tab when URL hash changes 297 plugin.selectTabFromHashChange = function() { 298 var hash = window.location.hash.match(/^[^\?]*/)[0], 299 $tab = plugin.matchTab(hash), 300 $panel; 301 302 if ( settings.updateHash ) { 303 304 // If hash directly matches tab 305 if( $tab.length ){ 306 skipUpdateToHash = true; 307 plugin.selectTab( $tab ); 308 309 } else { 310 $panel = plugin.matchInPanel(hash); 311 312 // If panel contains element matching hash 313 if ( $panel.length ) { 314 hash = '#' + $panel.attr('id'); 315 $tab = plugin.matchTab(hash); 316 skipUpdateToHash = true; 317 plugin.selectTab( $tab ); 318 319 // If default tab is not active... 320 } else if ( ! $defaultTab.hasClass(settings.tabActiveClass) && ! settings.cycle ) { 321 322 // ...and hash is blank or matches a parent of the tab container or 323 // if the last tab (before the hash updated) was one of the other tabs in this container. 324 if ( hash === '' || plugin.matchTab(lastHash).length || $container.closest(hash).length ) { 325 skipUpdateToHash = true; 326 plugin.selectTab( $defaultTabLink ); 327 } 328 } 329 } 330 } 331 }; 332 333 // Cycle through tabs 334 plugin.cycleTabs = function(tabNumber){ 335 if(settings.cycle){ 336 tabNumber = tabNumber % plugin.tabs.length; 337 $tab = $( plugin.tabs[tabNumber] ).children("a").first(); 338 skipUpdateToHash = true; 339 plugin.selectTab( $tab, function() { 340 setTimeout(function(){ plugin.cycleTabs(tabNumber + 1); }, settings.cycle); 341 }); 342 } 343 }; 344 345 // Convenient public methods 346 plugin.publicMethods = { 347 select: function(tabSelector){ 348 var $tab; 349 350 // Find tab container that matches selector (like 'li#tab-one' which contains tab link) 351 if ( ($tab = plugin.tabs.filter(tabSelector)).length === 0 ) { 352 353 // Find direct tab link that matches href (like 'a[href="#panel-1"]') 354 if ( ($tab = plugin.tabs.find("a[href='" + tabSelector + "']")).length === 0 ) { 355 356 // Find direct tab link that matches selector (like 'a#tab-1') 357 if ( ($tab = plugin.tabs.find("a" + tabSelector)).length === 0 ) { 358 359 // Find direct tab link that matches data-target (lik 'a[data-target="#panel-1"]') 360 if ( ($tab = plugin.tabs.find("[data-target='" + tabSelector + "']")).length === 0 ) { 361 362 // 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) 363 if ( ($tab = plugin.tabs.find("a[href$='" + tabSelector + "']")).length === 0 ) { 364 365 $.error('Tab \'' + tabSelector + '\' does not exist in tab set'); 366 } 367 } 368 } 369 } 370 } else { 371 // Select the child tab link, since the first option finds the tab container (like <li>) 372 $tab = $tab.children("a").first(); 373 } 374 plugin.selectTab($tab); 375 } 376 }; 377 378 // ============================================================= 379 // Private functions 380 // ============================================================= 381 382 // Triggers an event on an element and returns the event result 383 var fire = function(obj, name, data) { 384 var event = $.Event(name); 385 obj.trigger(event, data); 386 return event.result !== false; 387 } 388 389 // Add CSS classes to markup (if specified), called by init 390 var addClasses = function() { 391 $container.addClass(settings.containerClass); 392 plugin.tabs.parent().addClass(settings.tabsClass); 393 plugin.tabs.addClass(settings.tabClass); 394 plugin.panels.addClass(settings.panelClass); 395 }; 396 397 // Set the default tab, whether from hash (bookmarked) or option, 398 // called by init 399 var setDefaultTab = function(){ 400 var hash = window.location.hash.match(/^[^\?]*/)[0], 401 $selectedTab = plugin.matchTab(hash).parent(), 402 $panel; 403 404 // If hash directly matches one of the tabs, active on page-load 405 if( $selectedTab.length === 1 ){ 406 $defaultTab = $selectedTab; 407 settings.cycle = false;
408 409 } else { 410 $panel = plugin.matchInPanel(hash); 411 412 // If one of the panels contains the element matching the hash, 413 // make it active on page-load 414 if ( $panel.length ) { 415 hash = '#' + $panel.attr('id'); 416 $defaultTab = plugin.matchTab(hash).parent(); 417 418 // Otherwise, make the default tab the one that's active on page-load 419 } else { 420 $defaultTab = plugin.tabs.parent().find(settings.defaultTab); 421 if ( $defaultTab.length === 0 ) { 422 $.error("The specified default tab ('" + settings.defaultTab + "') could not be found in the tab set ('" + settings.tabs + "') out of " + plugin.tabs.length + " tabs."); 423 } 424 } 425 } 426 427 $defaultTabLink = $defaultTab.children("a").first(); 428 429 activateDefaultTab($selectedTab); 430 }; 431 432 // Activate defaultTab (or collapse by default), called by setDefaultTab 433 var activateDefaultTab = function($selectedTab) { 434 var defaultPanel, 435 defaultAjaxUrl; 436 437 if ( settings.collapsible && $selectedTab.length === 0 && settings.collapsedByDefault ) { 438 $defaultTab 439 .addClass(settings.collapsedClass) 440 .children() 441 .addClass(settings.collapsedClass); 442 443 } else { 444 445 defaultPanel = $( $defaultTab.data('easytabs').panel ); 446 defaultAjaxUrl = $defaultTab.data('easytabs').ajax; 447 448 if ( defaultAjaxUrl && (!settings.cache || !$defaultTab.data('easytabs').cached) ) { 449 $container.trigger('easytabs:ajax:beforeSend', [$defaultTabLink, defaultPanel]); 450 defaultPanel.load(defaultAjaxUrl, function(response, status, xhr){ 451 $defaultTab.data('easytabs').cached = true; 452 $container.trigger('easytabs:ajax:complete', [$defaultTabLink, defaultPanel, response, status, xhr]); 453 }); 454 } 455 456 $defaultTab.data('easytabs').panel 457 .show() 458 .addClass(settings.panelActiveClass); 459 460 $defaultTab 461 .addClass(settings.tabActiveClass) 462 .children() 463 .addClass(settings.tabActiveClass); 464 } 465 466 // Fire event when the plugin is initialised 467 $container.trigger("easytabs:initialised", [$defaultTabLink, defaultPanel]); 468 }; 469 470 // Bind tab-select funtionality to namespaced click event, called by 471 // init 472 var bindToTabClicks = function() { 473 plugin.tabs.children("a").bind(settings.bind_str, function(e) { 474 475 // Stop cycling when a tab is clicked 476 settings.cycle = false; 477 478 // Hash will be updated when tab is clicked, 479 // don't cause tab to re-select when hash-change event is fired 480 skipUpdateToHash = false; 481 482 // Select the panel for the clicked tab 483 plugin.selectTab( $(this) ); 484 485 // Don't follow the link to the anchor 486 e.preventDefault ? e.preventDefault() : e.returnValue = false; 487 }); 488 }; 489 490 // Activate a given tab/panel, called from plugin.selectTab: 491 // 492 // * fire `easytabs:before` hook 493 // * get ajax if new tab is an uncached ajax tab 494 // * animate out previously-active panel 495 // * fire `easytabs:midTransition` hook 496 // * update URL hash 497 // * animate in newly-active panel 498 // * update CSS classes for inactive and active tabs/panels 499 // 500 // TODO: This could probably be broken out into many more modular 501 // functions 502 var activateTab = function($clicked, $targetPanel, ajaxUrl, callback) { 503 plugin.panels.stop(true,true); 504 505 if( fire($container,"easytabs:before", [$clicked, $targetPanel, settings]) ){ 506 var $visiblePanel = plugin.panels.filter(":visible"), 507 $panelContainer = $targetPanel.parent(), 508 targetHeight, 509 visibleHeight, 510 heightDifference, 511 showPanel, 512 hash = window.location.hash.match(/^[^\?]*/)[0]; 513 514 if (settings.animate) { 515 targetHeight = getHeightForHidden($targetPanel); 516 visibleHeight = $visiblePanel.length ? setAndReturnHeight($visiblePanel) : 0; 517 heightDifference = targetHeight - visibleHeight; 518 } 519 520 // Set lastHash to help indicate if defaultTab should be 521 // activated across multiple tab instances. 522 lastHash = hash; 523 524 // TODO: Move this function elsewhere 525 showPanel = function() { 526 // At this point, the previous panel is hidden, and the new one will be selected 527 $container.trigger("easytabs:midTransition", [$clicked, $targetPanel, settings]); 528 529 // Gracefully animate between panels of differing heights, start height change animation *after* panel change if panel needs to contract, 530 // so that there is no chance of making the visible panel overflowing the height of the target panel 531 if (settings.animate && settings.transitionIn == 'fadeIn') { 532 if (heightDifference < 0) 533 $panelContainer.animate({ 534 height: $panelContainer.height() + heightDifference 535 }, transitions.halfSpeed ).css({ 'min-height': '' }); 536 } 537 538 if ( settings.updateHash && ! skipUpdateToHash ) { 539 //window.location = url.toString().replace((url.pathname + hash), (url.pathname + $clicked.attr("href"))); 540 // Not sure why this behaves so differently, but it's more straight forward and seems to have less side-effects 541 window.location.hash = '#' + $targetPanel.attr('id'); 542 } else { 543 skipUpdateToHash = false;
544 } 545 546 $targetPanel 547 [transitions.show](transitions.speed, settings.transitionInEasing, function(){ 548 $panelContainer.css({height: '', 'min-height': ''}); // After the transition, unset the height 549 $container.trigger("easytabs:after", [$clicked, $targetPanel, settings]); 550 // callback only gets called if selectTab actually does something, since it's inside the if block 551 if(typeof callback == 'function'){ 552 callback(); 553 } 554 }); 555 }; 556 557 if ( ajaxUrl && (!settings.cache || !$clicked.parent().data('easytabs').cached) ) { 558 $container.trigger('easytabs:ajax:beforeSend', [$clicked, $targetPanel]); 559 $targetPanel.load(ajaxUrl, function(response, status, xhr){ 560 $clicked.parent().data('easytabs').cached = true; 561 $container.trigger('easytabs:ajax:complete', [$clicked, $targetPanel, response, status, xhr]); 562 }); 563 } 564 565 // Gracefully animate between panels of differing heights, start height change animation *before* panel change if panel needs to expand, 566 // so that there is no chance of making the target panel overflowing the height of the visible panel 567 if( settings.animate && settings.transitionOut == 'fadeOut' ) { 568 if( heightDifference > 0 ) { 569 $panelContainer.animate({ 570 height: ( $panelContainer.height() + heightDifference ) 571 }, transitions.halfSpeed ); 572 } else { 573 // Prevent height jumping before height transition is triggered at midTransition 574 $panelContainer.css({ 'min-height': $panelContainer.height() }); 575 } 576 } 577 578 // Change the active tab *first* to provide immediate feedback when the user clicks 579 plugin.tabs.filter("." + settings.tabActiveClass).removeClass(settings.tabActiveClass).children().removeClass(settings.tabActiveClass); 580 plugin.tabs.filter("." + settings.collapsedClass).removeClass(settings.collapsedClass).children().removeClass(settings.collapsedClass); 581 $clicked.parent().addClass(settings.tabActiveClass).children().addClass(settings.tabActiveClass); 582 583 plugin.panels.filter("." + settings.panelActiveClass).removeClass(settings.panelActiveClass); 584 $targetPanel.addClass(settings.panelActiveClass); 585 586 if( $visiblePanel.length ) { 587 $visiblePanel 588 [transitions.hide](transitions.speed, settings.transitionOutEasing, showPanel); 589 } else { 590 $targetPanel 591 [transitions.uncollapse](transitions.speed, settings.transitionUncollapseEasing, showPanel); 592 } 593 } 594 }; 595 596 // Get heights of panels to enable animation between panels of 597 // differing heights, called by activateTab 598 var getHeightForHidden = function($targetPanel){ 599 600 if ( $targetPanel.data('easytabs') && $targetPanel.data('easytabs').lastHeight ) { 601 return $targetPanel.data('easytabs').lastHeight; 602 } 603 604 // this is the only property easytabs changes, so we need to grab its value on each tab change 605 var display = $targetPanel.css('display'), 606 outerCloak, 607 height; 608 609 // Workaround with wrapping height, because firefox returns wrong 610 // height if element itself has absolute positioning. 611 // but try/catch block needed for IE7 and IE8 because they throw 612 // an "Unspecified error" when trying to create an element 613 // with the css position set. 614 try { 615 outerCloak = $('<div></div>', {'position': 'absolute', 'visibility': 'hidden', 'overflow': 'hidden'}); 616 } catch (e) { 617 outerCloak = $('<div></div>', {'visibility': 'hidden', 'overflow': 'hidden'}); 618 } 619 height = $targetPanel 620 .wrap(outerCloak) 621 .css({'position':'relative','visibility':'hidden','display':'block'}) 622 .outerHeight(); 623 624 $targetPanel.unwrap(); 625 626 // Return element to previous state 627 $targetPanel.css({ 628 position: $targetPanel.data('easytabs').position, 629 visibility: $targetPanel.data('easytabs').visibility, 630 display: display 631 }); 632 633 // Cache height 634 $targetPanel.data('easytabs').lastHeight = height; 635 636 return height; 637 }; 638 639 // Since the height of the visible panel may have been manipulated due to interaction, 640 // we want to re-cache the visible height on each tab change, called 641 // by activateTab 642 var setAndReturnHeight = function($visiblePanel) { 643 var height = $visiblePanel.outerHeight(); 644 645 if( $visiblePanel.data('easytabs') ) { 646 $visiblePanel.data('easytabs').lastHeight = height; 647 } else { 648 $visiblePanel.data('easytabs', {lastHeight: height}); 649 } 650 return height; 651 }; 652
653 // Setup hash-change callback for forward- and back-button 654 // functionality, called by init 655 var initHashChange = function(){ 656 657 // enabling back-button with jquery.hashchange plugin 658 // http://benalman.com/projects/jquery-hashchange-plugin/ 659 if(typeof $(window).hashchange === 'function'){ 660 $(window).hashchange( function(){ 661 plugin.selectTabFromHashChange(); 662 }); 663 } else if ($.address && typeof $.address.change === 'function') { // back-button with jquery.address plugin http://www.asual.com/jquery/address/docs/ 664 $.address.change( function(){ 665 plugin.selectTabFromHashChange(); 666 }); 667 } 668 }; 669 670 // Begin cycling if set in options, called by init 671 var initCycle = function(){ 672 var tabNumber; 673 if (settings.cycle) { 674 tabNumber = plugin.tabs.index($defaultTab); 675 setTimeout( function(){ plugin.cycleTabs(tabNumber + 1); }, settings.cycle); 676 } 677 }; 678 679 680 plugin.init(); 681 682 }; 683 684 $.fn.easytabs = function(options) { 685 var args = arguments; 686 687 return this.each(function() { 688 var $this = $(this), 689 plugin = $this.data('easytabs'); 690 691 // Initialization was called with $(el).easytabs( { options } ); 692 if (undefined === plugin) { 693 plugin = new $.easytabs(this, options); 694 $this.data('easytabs', plugin); 695 } 696 697 // User called public method 698 if ( plugin.publicMethods[options] ){ 699 return plugin.publicMethods[options](Array.prototype.slice.call( args, 1 )); 700 } 701 }); 702 }; 703 704})(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.