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