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