PageSourceSearch

https://giving.nes.ru/assets/js/jquery.easytabs.js

js nes.ru collected 2026-10-01 06:27:53 UTC 31,572 bytes, 717 lines download raw bytes

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.