PageSourceSearch

https://www.ncmcs.org/common/controls/TabPortlet/jquery.scrolling-tabs.js

js ncmcs.org collected 2026-10-01 14:39:01 UTC 81,885 bytes, 2,019 lines download raw bytes

1/**
2 * jquery-bootstrap-scrolling-tabs
3 * @version v0.1.2
4 * @link https://github.com/mikejacobson/jquery-bootstrap-scrolling-tabs
5 * @author Mike Jacobson <[email protected]>
6 * @license MIT License, http://www.opensource.org/licenses/MIT
7 *
8 * jQuery plugin version of Angular directive angular-bootstrap-scrolling-tabs:
9 * https://github.com/mikejacobson/angular-bootstrap-scrolling-tabs
10 *
11 * Usage:
12 *
13 *    Use case #1: HTML-defined tabs
14 *    ------------------------------
15 *    Demo: http://plnkr.co/edit/thyD0grCxIjyU4PoTt4x?p=preview
16 *
17 *      Sample HTML:
18 *
19 *           <!-- Nav tabs -->
20 *           <ul class="nav nav-tabs" role="tablist">
21 *             <li role="presentation" class="active"><a href="#tab1" role="tab" data-toggle="tab">Tab Number 1</a></li>
22 *             <li role="presentation"><a href="#tab2" role="tab" data-toggle="tab">Tab Number 2</a></li>
23 *             <li role="presentation"><a href="#tab3" role="tab" data-toggle="tab">Tab Number 3</a></li>
24 *             <li role="presentation"><a href="#tab4" role="tab" data-toggle="tab">Tab Number 4</a></li>
25 *           </ul>
26 *
27 *           <!-- Tab panes -->
28 *           <div class="tab-content">
29 *             <div role="tabpanel" class="tab-pane active" id="tab1">Tab 1 content...</div>
30 *             <div role="tabpanel" class="tab-pane" id="tab2">Tab 2 content...</div>
31 *             <div role="tabpanel" class="tab-pane" id="tab3">Tab 3 content...</div>
32 *             <div role="tabpanel" class="tab-pane" id="tab4">Tab 4 content...</div>
33 *           </div>
34 *
35 *
36 *      JavaScript:
37 *
38 *            $('.nav-tabs').scrollingTabs();
39 *
40 *
41 *    Use Case #2: Data-driven tabs
42 *    -----------------------------
43 *    Demo: http://plnkr.co/edit/MWBjLnTvJeetjU3NEimg?p=preview
44 *
45 *      Sample HTML:
46 *
47 *          <!-- build .nav-tabs and .tab-content in here -->
48 *          <div id="tabs-inside-here"></div>
49 *
50 *
51 *      JavaScript:
52 *
53 *             $('#tabs-inside-here').scrollingTabs({
54 *               tabs: tabs, // required
55 *               propPaneId: 'paneId', // optional
56 *               propTitle: 'title', // optional
57 *               propActive: 'active', // optional
58 *               propDisabled: 'disabled', // optional
59 *               propContent: 'content', // optional
60 *               ignoreTabPanes: false, // optional
61 *               scrollToTabEdge: false, // optional
62 *               disableScrollArrowsOnFullyScrolled: false, // optional
63 *               reverseScroll: false // optional
64 *             });
65 *
66 *      Settings/Options:
67 *
68 *        tabs:             tabs data array
69 *        prop*:            name of your tab object's property name that
70 *                          corresponds to that required tab property if
71 *                          your property name is different than the
72 *                          standard name (paneId, title, etc.)
73 *        ignoreTabPanes:   relevant for data-driven tabs only--set to true if
74 *                          you want the plugin to only touch the tabs
75 *                          and to not generate the tab pane elements
76 *                          that go in .tab-content. By default, the plugin
77 *                          will generate the tab panes based on the content
78 *                          property in your tab data, if a content property
79 *                          is present.
80 *        scrollToTabEdge:  set to true if you want to force full-width tabs
81 *                          to display at the left scroll arrow. i.e., if the
82 *                          scrolling stops with only half a tab showing,
83 *                          it will snap the tab to its edge so the full tab
84 *                          shows.
85 *        disableScrollArrowsOnFullyScrolled:
86 *                          set to true if you want the left scroll arrow to
87 *                          disable when the tabs are scrolled fully left,
88 *                          and the right scroll arrow to disable when the tabs
89 *                          are scrolled fully right.
90 *        reverseScroll:
91 *                          set to true if you want the left scroll arrow to
92 *                          slide the tabs left instead of right, and the right
93 *                          scroll arrow to slide the tabs right.
94 *        widthMultiplier:
95 *                          set to a value less than 1 if you want the tabs
96 *                          container to be less than the full width of its
97 *                          parent element. For example, set it to 0.5 if you
98 *                          want the tabs container to be half the width of
99 *                          its parent.
100 *
101 *
102 *
103 *      On tabs data change:
104 *
105 *            $('#tabs-inside-here').scrollingTabs('refresh');
106 *
107 *      On tabs data change, if you want the active tab to be set based on
108 *      the updated tabs data (i.e., you want to override the current
109 *      active tab setting selected by the user), for example, if you
110 *      added a new tab and you want it to be the active tab:
111 *
112 *             $('#tabs-inside-here').scrollingTabs('refresh', {
113 *               forceActiveTab: true
114 *             });
115 *
116 *      Any options that can be passed into the plugin can be set on the
117 *      plugin's 'defaults' object instead so you don't have to pass them in:
118 *
119 *             $.fn.scrollingTabs.defaults.tabs = tabs;
120 *             $.fn.scrollingTabs.defaults.forceActiveTab = true;
121 *             $.fn.scrollingTabs.defaults.scrollToTabEdge = true;
122 *             $.fn.scrollingTabs.defaults.disableScrollArrowsOnFullyScrolled = true;
123 *             $.fn.scrollingTabs.defaults.reverseScroll = true;
124 *             $.fn.scrollingTabs.defaults.widthMultiplier = 0.5;
125 *
126 *
127 *    Methods
128 *    -----------------------------
129 *    - refresh
130 *    On window resize, the tabs should refresh themselves, but to force a refresh:
131 *
132 *      $('.nav-tabs').scrollingTabs('refresh');
133 *
134 *    - scrollToActiveTab
135 *    On window resize, the active tab will automatically be scrolled to
136 *    if it ends up offscreen, but you can also programmatically force a
137 *    scroll to the active tab any time (if, for example, you're
138 *    programmatically setting the active tab) by calling the
139 *    'scrollToActiveTab' method:
140 *
141 *    $('.nav-tabs').scrollingTabs('scrollToActiveTab');
142 *
143 *
144 *    Events
145 *    -----------------------------
146 *    The plugin triggers event 'ready.scrtabs' when the tabs have
147 *    been wrapped in the scroller and are ready for viewing:
148 *
149 *      $('.nav-tabs')
150 *        .scrollingTabs()
151 *        .on('ready.scrtabs', function() {
152 *          // tabs ready, do my other stuff...
153 *        });
154 *
155 *      $('#tabs-inside-here')
156 *        .scrollingTabs({ tabs: tabs })
157 *        .on('ready.scrtabs', function() {
158 *          // tabs ready, do my other stuff...
159 *        });
160 *
161 *
162 *    Destroying
163 *    -----------------------------
164 *    To destroy:
165 *
166 *      $('.nav-tabs').scrollingTabs('destroy');
167 *
168 *      $('#tabs-inside-here').scrollingTabs('destroy');
169 *
170 *    If you were wrapping markup, the markup will be restored; if your tabs
171 *    were data-driven, the tabs will be destroyed along with the plugin.
172 *
173 */
174; (function ($, window) {
175    'use strict';
176
177    var CONSTANTS = {
178        CONTINUOUS_SCROLLING_TIMEOUT_INTERVAL: 50, // timeout interval for repeatedly moving the tabs container
179        // by one increment while the mouse is held down--decrease to
180        // make mousedown continous scrolling faster
181        SCROLL_ARROW_WIDTH: 40,
182        SCROLL_OFFSET_FRACTION: 6, // each click moves the container this fraction of the fixed container--decrease
183        // to make the tabs scroll farther per click
184
185        DATA_KEY_DDMENU_MODIFIED: 'scrtabsddmenumodified',
186        DATA_KEY_IS_MOUSEDOWN: 'scrtabsismousedown',
187
188        CSS_CLASSES: {
189            SCROLL_ARROW_DISABLE: 'scrtabs-disable'
190        },
191
192        SLIDE_DIRECTION: {
193            LEFT: 1,
194            RIGHT: 2
195        },
196
197        EVENTS: {
198            CLICK: 'click.scrtabs',
199            KEYDOWN: 'keydown.scrtabs',
200            DROPDOWN_MENU_HIDE: 'hide.bs.dropdown.scrtabs',
201            DROPDOWN_MENU_SHOW: 'show.bs.dropdown.scrtabs',
202            FORCE_REFRESH: 'forcerefresh.scrtabs',
203            WINDOW_RESIZE: 'resize.scrtabs',
204            TABS_READY: 'ready.scrtabs'
205        }
206    };
207
208
209    // smartresize from Paul Irish (debounced window resize)
210    (function (sr) {
211        var debounce = function (func, threshold, execAsap) {
212            var timeout;
213
214            return function debounced() {
215                var obj = this, args = arguments;
216                function delayed() {
217                    if (!execAsap) {
218                        func.apply(obj, args);
219                    }
220                    timeout = null;
221                }
222
223                if (timeout) {
224                    clearTimeout(timeout);
225                } else if (execAsap) {
226                    func.apply(obj, args);
227                }
228
229                timeout = setTimeout(delayed, threshold || 100);
230            };
231        };
232        $.fn[sr] = function (fn) { return fn ? this.bind(CONSTANTS.EVENTS.WINDOW_RESIZE, debounce(fn)) : this.trigger(sr); };
233
234    })('smartresize');
235
236
237
238    /* ***********************************************************************************
239     * ElementsHandler - Class that each instance of ScrollingTabsControl will instantiate
240     * **********************************************************************************/
241    function ElementsHandler(scrollingTabsControl) {
242        var ehd = this;
243
244        ehd.stc = scrollingTabsControl;
245    }
246
247    // ElementsHandler prototype methods
248    (function (p) {
249        p.initElements = function (options) {
250            var ehd = this;
251
252            ehd.setElementReferences();
253            ehd.stc.$scrollArrows.hide();
254            ehd.stc.isTabMode = options.tabMode;
255            ehd.setEventListeners();
256        };
257
258        p.refreshAllElementSizes = function () {
259            var ehd = this,
260                stc = ehd.stc,
261                smv = stc.scrollMovement,
262                scrollArrowsWereVisible = stc.scrollArrowsVisible,
263                actionsTaken = {
264                    didScrollToActiveTab: false
265                },
266                isPerformingSlideAnim = false,
267                minPos;
268
269            stc.$tabsContainer.show();
270            if (stc.$accTabsContainer)
271                stc.$accTabsContainer.hide();
272
273            ehd.setElementWidths();
274            ehd.setScrollArrowVisibility();
275
276            // this could have been a window resize or the removal of a
277            // dynamic tab, so make sure the movable container is positioned
278            // correctly because, if it is far to the left and we increased the
279            // window width, it's possible that the tabs will be too far left,
280            // beyond the min pos.
281            if (stc.scrollArrowsVisible) {
282                // make sure container not too far left
283                minPos = smv.getMinPos();
284
285                isPerformingSlideAnim = smv.scrollToActiveTab({
286                    isOnWindowResize: true
287                });
288
289                if (!isPerformingSlideAnim) {
290                    smv.refreshScrollArrowsDisabledState();
291
292                    if (stc.movableContainerLeftPos < minPos) {
293                        smv.incrementScrollRight(minPos);
294                    }
295                }
296
297                actionsTaken.didScrollToActiveTab = true;
298
299            } else if (scrollArrowsWereVisible) {
300                // scroll arrows went away after resize, so position movable container at 0
301                stc.movableContainerLeftPos = 0;
302                smv.slideMovableContainerToLeftPos();
303            }
304            if (stc.isTabMode) {
305                if (stc.containerWidth <= 450 && stc.$accTabsContainer) {
306                    stc.$tabsContainer.hide();
307                    if (stc.$accTabsContainer) {
308                        stc.$accTabsContainer.show();
309                        var tabContentContainer = stc.$accTabsContainer.eq(0).parent();
310                        tabContentContainer.addClass("TA-accordionMode");
311                        tabContentContainer.attr("role", "tablist");
312                        //PRES-2679 //collapse all accordions on mobile
313                        if (stc.$accTabsContainer.length === 1) {
314                            stc.$accTabsContainer.eq(0).addClass('active');
315                            stc.$accTabsContainer.eq(0).find('a[role = "tab"]').attr({ "aria-selected": "true" });
316                            tabContentContainer.find('div[role="tabpanel"]').attr("aria-hidden", "false").addClass("active").show();
317                        } else if (window.isMobile) {
318                            tabContentContainer.find('div[role="tabpanel"]').attr("aria-hidden", "true").removeClass("active").hide();
319                            tabContentContainer.find('.active a[role="tab"]').attr({ "aria-selected": "false" });
320                            tabContentContainer.find('.active').removeClass("active");
321                        } else {
322                            stc.$accTabsContainer.removeClass("active");
323                            var selecttabcontentid = tabContentContainer.find('div[role="tabpanel"].active').attr("id");
324                            var selectedMenuItemAnc = tabContentContainer.find('a[href="#' + selecttabcontentid + '"]');
325                            selectedMenuItemAnc.attr({ "aria-selected": "true" });
326                            var selectedMenuItemLi = selectedMenuItemAnc.parent('div.accordionTabHead');
327                            selectedMenuItemLi.addClass('active');
328                        }
329                    }
330                }
331                else {
332                    stc.$tabsContainer.show();
333                    if (stc.$accTabsContainer) {
334                        stc.$accTabsContainer.hide();
335                        var tabContentContainer = stc.$accTabsContainer.eq(0).parent();
336                        tabContentContainer.removeClass("TA-accordionMode");
337                        var selecttabcontentid = tabContentContainer.find('div[role="tabpanel"].active').attr("id");
338                        if (!selecttabcontentid) {
339                            selecttabcontentid = tabContentContainer.find('div[role="tabpanel"]').eq(0).attr("id");
340                            tabContentContainer.find('div[role="tabpanel"]').eq(0).attr("aria-hidden", "false").addClass("active").show();
341                        }
342                        var selectedMenuItemAnc = stc.$tabsContainer.find('a[href="#' + selecttabcontentid + '"]');
343                        selectedMenuItemAnc.attr({ "aria-selected": "true" });
344                        stc.$tabsContainer.find("li.active").removeClass("active");
345                        var selectedMenuItemLi = selectedMenuItemAnc.parent('li');
346                        selectedMenuItemLi.addClass("active");
347                    }
348                }
349            }
350            else {
351                if (stc.$accTabsContainer) {
352                    stc.$tabsContainer.hide();
353                    if (stc.$accTabsContainer) {
354                        stc.$accTabsContainer.show();
355                        var tabContentContainer = stc.$accTabsContainer.eq(0).parent();
356                        tabContentContainer.addClass("TA-accordionMode");
357                        tabContentContainer.attr("role", "tablist");
358                        //PRES-2679:Use Case 3. 
359                        //collapse all accordions
360                        if (stc.$accTabsContainer.length === 1) {
361                            stc.$accTabsContainer.eq(0).addClass('active');
362                            stc.$accTabsContainer.eq(0).find('a[role = "tab"]').attr({ "aria-selected": "true" });
363                            tabContentContainer.find('div[role="tabpanel"]').attr("aria-hidden", "false").addClass("active").show();
364                        } else if (stc.$accTabsContainer.length > 1 && window.isMobile) {
365                            tabContentContainer.find('div[role="tabpanel"]').attr("aria-hidden", "true").removeClass("active").hide();
366                            tabContentContainer.find('.active a[role="tab"]').attr({ "aria-selected": "false" });
367                            tabContentContainer.find('.active').removeClass("active");
368                        }
369                    }
370                }
371                else {
372                    stc.$tabsContainer.show();
373                    if (stc.$accTabsContainer) {
374                        stc.$accTabsContainer.hide();
375                        stc.$accTabsContainer.eq(0).parent().removeClass("TA-accordionMode");
376                    }
377                }
378            }
379            return actionsTaken;
380        };
381
382        p.setElementReferences = function () {
383            var ehd = this,
384                stc = ehd.stc,
385                $tabsContainer = stc.$tabsContainer;
386
387            stc.isNavPills = false;
388
389            stc.$fixedContainer = $tabsContainer.find('.scrtabs-tabs-fixed-container');
390            stc.$movableContainer = $tabsContainer.find('.scrtabs-tabs-movable-container');
391            stc.$tabsUl = $tabsContainer.find('.TA-nav-tabs');
392
393            // check for pills
394            if (!stc.$tabsUl.length) {
395                stc.$tabsUl = $tabsContainer.find('.TA-nav-pills');
396
397                if (stc.$tabsUl.length) {
398                    stc.isNavPills = true;
399                }
400            }
401
402            stc.$tabsLiCollection = stc.$tabsUl.find('> li');
403            stc.$leftScrollArrow = $tabsContainer.find('.scrtabs-js-tab-scroll-arrow-left');
404            stc.$rightScrollArrow = $tabsContainer.find('.scrtabs-js-tab-scroll-arrow-right');
405            stc.$scrollArrows = stc.$leftScrollArrow.add(stc.$rightScrollArrow);
406
407            stc.$win = $(window);
408        };
409
410        p.setElementWidths = function () {
411            var ehd = this,
412                stc = ehd.stc;
413
414            if (stc.$tabsContainer.is(":visible"))
415                stc.containerWidth = stc.$tabsContainer.outerWidth();
416            else
417                stc.containerWidth = stc.$tabsContainer.parent().outerWidth();
418
419            stc.winWidth = stc.$win.width();
420
421            stc.scrollArrowsCombinedWidth = stc.$leftScrollArrow.outerWidth() + stc.$rightScrollArrow.outerWidth();
422
423            ehd.setFixedContainerWidth();
424            ehd.setMovableContainerWidth();
425        };
426
427        p.setEventListeners = function () {
428            var ehd = this,
429                stc = ehd.stc,
430                evh = stc.eventHandlers; // eventHandlers
431
432            stc.$leftScrollArrow.off('.scrtabs').on({
433                'mousedown.scrtabs': function (e) { evh.handleMousedownOnLeftScrollArrow.call(evh, e); },
434                'mouseup.scrtabs': function (e) { evh.handleMouseupOnLeftScrollArrow.call(evh, e); },
435                'click.scrtabs': function (e) { evh.handleClickOnLeftScrollArrow.call(evh, e); }
436            });
437
438            stc.$rightScrollArrow.off('.scrtabs').on({
439                'mousedown.scrtabs': function (e) { evh.handleMousedownOnRightScrollArrow.call(evh, e); },
440                'mouseup.scrtabs': function (e) { evh.handleMouseupOnRightScrollArrow.call(evh, e); },
441                'click.scrtabs': function (e) { evh.handleClickOnRightScrollArrow.call(evh, e); }
442            });
443
444            stc.$win.smartresize(function (e) { evh.handleWindowResize.call(evh, e); });
445
446            $('body').on(CONSTANTS.EVENTS.FORCE_REFRESH, stc.elementsHandler.refreshAllElementSizes.bind(stc.elementsHandler));
447        };
448
449        p.setFixedContainerWidth = function () {
450            var ehd = this,
451                stc = ehd.stc,
452                tabsContainerRect = stc.$tabsContainer.get(0).getBoundingClientRect();
453            /**
454             * @author poletaew
455             * It solves problem with rounding by jQuery.outerWidth
456             * If we have real width 100.5 px, jQuery.outerWidth returns us 101 px and we get layout's fail
457             */
458            stc.fixedContainerWidth = tabsContainerRect.width || (tabsContainerRect.right - tabsContainerRect.left);
459
460            stc.$fixedContainer.width(stc.fixedContainerWidth);
461        };
462
463        p.setFixedContainerWidthForHiddenScrollArrows = function () {
464            var ehd = this,
465                stc = ehd.stc;
466
467            stc.$fixedContainer.width(stc.fixedContainerWidth);
468        };
469
470        p.setFixedContainerWidthForVisibleScrollArrows = function () {
471            var ehd = this,
472                stc = ehd.stc;
473
474            stc.$fixedContainer.width(stc.fixedContainerWidth - stc.scrollArrowsCombinedWidth);
475        };
476
477        p.setMovableContainerWidth = function () {
478            var ehd = this,
479                stc = ehd.stc,
480                $tabLi = stc.$tabsUl.find('> li');
481
482            stc.movableContainerWidth = 0;
483
484            if ($tabLi.length) {
485
486                $tabLi.each(function __getLiWidth() {
487                    var $li = $(this),
488                        totalMargin = 0;
489
490                    if (stc.isNavPills) { // pills have a margin-left, tabs have no margin
491                        totalMargin = parseInt($li.css('margin-left'), 10) + parseInt($li.css('margin-right'), 10);
492                    }
493
494                    stc.movableContainerWidth += ($li.outerWidth() + totalMargin);
495                });
496
497                stc.movableContainerWidth += 3;
498
499                // if the tabs don't span the width of the page, force the
500                // movable container width to full page width so the bottom
501                // border spans the page width instead of just spanning the
502                // width of the tabs
503                if (stc.movableContainerWidth < stc.fixedContainerWidth) {
504                    stc.movableContainerWidth = stc.fixedContainerWidth;
505                }
506            }
507
508            stc.$movableContainer.width(stc.movableContainerWidth);
509        };
510
511        p.setScrollArrowVisibility = function () {
512            var ehd = this,
513                stc = ehd.stc,
514                shouldBeVisible = stc.movableContainerWidth > stc.fixedContainerWidth;
515
516            if (shouldBeVisible && !stc.scrollArrowsVisible) {
517                stc.$scrollArrows.show();
518                stc.scrollArrowsVisible = true;
519            } else if (!shouldBeVisible && stc.scrollArrowsVisible) {
520                stc.$scrollArrows.hide();
521                stc.scrollArrowsVisible = false;
522            }
523
524            if (stc.scrollArrowsVisible) {
525                ehd.setFixedContainerWidthForVisibleScrollArrows();
526            } else {
527                ehd.setFixedContainerWidthForHiddenScrollArrows();
528            }
529        };
530
531    }(ElementsHandler.prototype));
532
533
534
535
536    /* ***********************************************************************************
537     * EventHandlers - Class that each instance of ScrollingTabsControl will instantiate
538     * **********************************************************************************/
539    function EventHandlers(scrollingTabsControl) {
540        var evh = this;
541
542        evh.stc = scrollingTabsControl;
543    }
544
545    // prototype methods
546    (function (p) {
547        p.handleClickOnLeftScrollArrow = function (e) {
548            var evh = this,
549                stc = evh.stc;
550
551            stc.scrollMovement.incrementScrollLeft();
552        };
553
554        p.handleClickOnRightScrollArrow = function (e) {
555            var evh = this,
556                stc = evh.stc,
557                scrollMovement = stc.scrollMovement;
558
559            scrollMovement.incrementScrollRight(scrollMovement.getMinPos());
560        };
561
562        p.handleMousedownOnLeftScrollArrow = function (e) {
563            var evh = this,
564                stc = evh.stc;
565
566            stc.scrollMovement.startScrollLeft();
567        };
568
569        p.handleMousedownOnRightScrollArrow = function (e) {
570            var evh = this,
571                stc = evh.stc;
572
573            stc.scrollMovement.startScrollRight();
574        };
575
576        p.handleMouseupOnLeftScrollArrow = function (e) {
577            var evh = this,
578                stc = evh.stc;
579
580            stc.scrollMovement.stopScrollLeft();
581        };
582
583        p.handleMouseupOnRightScrollArrow = function (e) {
584            var evh = this,
585                stc = evh.stc;
586
587            stc.scrollMovement.stopScrollRight();
588        };
589
590        p.handleWindowResize = function (e) {
591            var evh = this,
592                stc = evh.stc,
593                newWinWidth = stc.$win.width();
594
595            if (newWinWidth === stc.winWidth) {
596                return false;
597            }
598
599            //console.log("newWinWidth " + newWinWidth);
600
601            stc.winWidth = newWinWidth;
602            stc.elementsHandler.refreshAllElementSizes();
603        };
604
605    }(EventHandlers.prototype));
606
607
608
609
610    /* ***********************************************************************************
611     * ScrollMovement - Class that each instance of ScrollingTabsControl will instantiate
612     * **********************************************************************************/
613    function ScrollMovement(scrollingTabsControl) {
614        var smv = this;
615
616        smv.stc = scrollingTabsControl;
617    }
618
619    // prototype methods
620    (function (p) {
621
622        p.continueScrollLeft = function () {
623            var smv = this,
624                stc = smv.stc;
625
626            setTimeout(function () {
627                if (stc.$leftScrollArrow.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN) && (stc.movableContainerLeftPos < 0)) {
628                    if (!smv.incrementScrollLeft()) { // scroll limit not reached, so keep scrolling
629                        smv.continueScrollLeft();
630                    }
631                }
632            }, CONSTANTS.CONTINUOUS_SCROLLING_TIMEOUT_INTERVAL);
633        };
634
635        p.continueScrollRight = function (minPos) {
636            var smv = this,
637                stc = smv.stc;
638
639            setTimeout(function () {
640                if (stc.$rightScrollArrow.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN) && (stc.movableContainerLeftPos > minPos)) {
641                    // slide tabs LEFT -> decrease movable container's left position
642                    // min value is (movableContainerWidth - $tabHeader width)
643                    if (!smv.incrementScrollRight(minPos)) {
644                        smv.continueScrollRight(minPos);
645                    }
646                }
647            }, CONSTANTS.CONTINUOUS_SCROLLING_TIMEOUT_INTERVAL);
648        };
649
650        p.decrementMovableContainerLeftPos = function (minPos) {
651            var smv = this,
652                stc = smv.stc;
653
654            stc.movableContainerLeftPos -= (stc.fixedContainerWidth / CONSTANTS.SCROLL_OFFSET_FRACTION);
655            if (stc.movableContainerLeftPos < minPos) {
656                stc.movableContainerLeftPos = minPos;
657            } else if (stc.scrollToTabEdge) {
658                smv.setMovableContainerLeftPosToTabEdge('right');
659
660                if (stc.movableContainerLeftPos < minPos) {
661                    stc.movableContainerLeftPos = minPos;
662                }
663            }
664        };
665
666        p.disableLeftScrollArrow = function () {
667            var smv = this,
668                stc = smv.stc;
669
670            if (!stc.disableScrollArrowsOnFullyScrolled || !stc.scrollArrowsVisible) {
671                return;
672            }
673
674            stc.$leftScrollArrow.addClass(CONSTANTS.CSS_CLASSES.SCROLL_ARROW_DISABLE);
675        };
676
677        p.disableRightScrollArrow = function () {
678            var smv = this,
679                stc = smv.stc;
680
681            if (!stc.disableScrollArrowsOnFullyScrolled || !stc.scrollArrowsVisible) {
682                return;
683            }
684
685            stc.$rightScrollArrow.addClass(CONSTANTS.CSS_CLASSES.SCROLL_ARROW_DISABLE);
686        };
687
688        p.enableLeftScrollArrow = function () {
689            var smv = this,
690                stc = smv.stc;
691
692            if (!stc.disableScrollArrowsOnFullyScrolled || !stc.scrollArrowsVisible) {
693                return;
694            }
695
696            stc.$leftScrollArrow.removeClass(CONSTANTS.CSS_CLASSES.SCROLL_ARROW_DISABLE);
697        };
698
699        p.enableRightScrollArrow = function () {
700            var smv = this,
701                stc = smv.stc;
702
703            if (!stc.disableScrollArrowsOnFullyScrolled || !stc.scrollArrowsVisible) {
704                return;
705            }
706
707            stc.$rightScrollArrow.removeClass(CONSTANTS.CSS_CLASSES.SCROLL_ARROW_DISABLE);
708        };
709
710        p.getMinPos = function () {
711            var smv = this,
712                stc = smv.stc;
713
714            return stc.scrollArrowsVisible ? (stc.fixedContainerWidth - stc.movableContainerWidth - stc.scrollArrowsCombinedWidth) : 0;
715        };
716
717        p.getMovableContainerCssLeftVal = function () {
718            var smv = this,
719                stc = smv.stc;
720            return (stc.movableContainerLeftPos === 0) ? '0' : stc.movableContainerLeftPos + 'px';
721        };
722
723        p.incrementScrollLeft = function () {
724            var smv = this,
725                stc = smv.stc;
726
727            stc.movableContainerLeftPos += (stc.fixedContainerWidth / CONSTANTS.SCROLL_OFFSET_FRACTION);
728
729            if (stc.movableContainerLeftPos > 0) {
730                stc.movableContainerLeftPos = 0;
731            } else if (stc.scrollToTabEdge) {
732                smv.setMovableContainerLeftPosToTabEdge('left');
733            }
734
735            smv.slideMovableContainerToLeftPos();
736            smv.enableRightScrollArrow();
737
738            return (stc.movableContainerLeftPos === 0); // indicates scroll limit reached
739        };
740
741        p.incrementScrollRight = function (minPos) {
742            var smv = this,
743                stc = smv.stc;
744
745            smv.decrementMovableContainerLeftPos(minPos);
746            smv.slideMovableContainerToLeftPos();
747
748            if (stc.movableContainerLeftPos !== 0) {
749                smv.enableLeftScrollArrow();
750            }
751
752            return (stc.movableContainerLeftPos === minPos);
753        };
754
755        p.refreshScrollArrowsDisabledState = function () {
756            var smv = this,
757                stc = smv.stc;
758
759            if (!stc.disableScrollArrowsOnFullyScrolled || !stc.scrollArrowsVisible) {
760                return;
761            }
762
763            if (!stc.movableContainerLeftPos) {
764                smv.disableLeftScrollArrow();
765                smv.enableRightScrollArrow();
766            } else {
767                smv.enableLeftScrollArrow();
768
769                if (stc.movableContainerLeftPos <= smv.getMinPos()) {
770                    smv.disableRightScrollArrow();
771                } else {
772                    smv.enableRightScrollArrow();
773                }
774            }
775        };
776
777        p.scrollToActiveTab = function (options) {
778            var smv = this,
779                stc = smv.stc,
780                RIGHT_OFFSET_BUFFER = 40,
781                $activeTab,
782                activeTabLeftPos,
783                activeTabRightPos,
784                rightArrowLeftPos;
785
786            if (!stc.scrollArrowsVisible) {
787                return;
788            }
789
790            $activeTab = stc.$tabsUl.find('li.active');
791            var marginLi = stc.$tabsUl.find('li').length * 5;
792
793            if (!$activeTab.length) {
794                return;
795            }
796
797            /**
798             * @author poletaew
799             * We need relative offset (depends on $fixedContainer), don't absolute
800             */
801            activeTabLeftPos = ($activeTab.offset().left + CONSTANTS.SCROLL_ARROW_WIDTH) - stc.$fixedContainer.offset().left;
802            activeTabRightPos = activeTabLeftPos + $activeTab.outerWidth();
803
804            rightArrowLeftPos = stc.fixedContainerWidth - RIGHT_OFFSET_BUFFER;
805
806            if (activeTabRightPos > rightArrowLeftPos) { // active tab off right side
807                stc.movableContainerLeftPos -= (activeTabRightPos - rightArrowLeftPos + CONSTANTS.SCROLL_ARROW_WIDTH);
808                smv.slideMovableContainerToLeftPos();
809                return true;
810            } else if (activeTabLeftPos < CONSTANTS.SCROLL_ARROW_WIDTH) { // active tab off left side
811                stc.movableContainerLeftPos += CONSTANTS.SCROLL_ARROW_WIDTH - activeTabLeftPos;
812                smv.slideMovableContainerToLeftPos();
813                return true;
814            }
815
816            return false;
817        };
818
819        p.setMovableContainerLeftPosToTabEdge = function (scrollArrowClicked) {
820            var smv = this,
821                stc = smv.stc,
822                offscreenWidth = -stc.movableContainerLeftPos,
823                totalTabWidth = 0;
824
825            // make sure LeftPos is set so that a tab edge will be against the
826            // left scroll arrow so we won't have a partial, cut-off tab
827            stc.$tabsLiCollection.each(function (index) {
828                var tabWidth = $(this).width() + 5;
829
830                totalTabWidth += tabWidth;
831
832                if (totalTabWidth > offscreenWidth) {
833                    stc.movableContainerLeftPos = (scrollArrowClicked === 'left') ? -(totalTabWidth - tabWidth) : -totalTabWidth;
834                    return false; // exit .each() loop
835                }
836
837            });
838        };
839
840        p.slideMovableContainerToLeftPos = function () {
841            var smv = this,
842                stc = smv.stc,
843                leftVal;
844
845            if (stc.movableContainerLeftPos > 0) {
846                stc.movableContainerLeftPos = 0;
847            } else if (stc.movableContainerLeftPos < smv.getMinPos()) {
848                stc.movableContainerLeftPos = smv.getMinPos();
849            }
850
851            stc.movableContainerLeftPos = stc.movableContainerLeftPos / 1;
852            leftVal = smv.getMovableContainerCssLeftVal();
853
854            smv.performingSlideAnim = true;
855
856            stc.$movableContainer.stop().animate({ "left": leftVal }, 'slow', function __slideAnimComplete() {
857                var newMinPos = smv.getMinPos();
858
859                smv.performingSlideAnim = false;
860
861                // if we slid past the min pos--which can happen if you resize the window
862                // quickly--move back into position
863                if (stc.movableContainerLeftPos < newMinPos) {
864                    smv.decrementMovableContainerLeftPos(newMinPos);
865                    stc.$movableContainer.stop().animate({ "left": smv.getMovableContainerCssLeftVal() }, 'slow', function () {
866                        smv.refreshScrollArrowsDisabledState();
867                    });
868                } else {
869                    smv.refreshScrollArrowsDisabledState();
870                }
871            });
872        };
873
874        p.startScrollLeft = function () {
875            var smv = this,
876                stc = smv.stc;
877
878            stc.$leftScrollArrow.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN, true);
879            smv.continueScrollLeft();
880        };
881
882        p.startScrollRight = function () {
883            var smv = this,
884                stc = smv.stc;
885
886            stc.$rightScrollArrow.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN, true);
887            smv.continueScrollRight(smv.getMinPos());
888        };
889
890        p.stopScrollLeft = function () {
891            var smv = this,
892                stc = smv.stc;
893
894            stc.$leftScrollArrow.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN, false);
895        };
896
897        p.stopScrollRight = function () {
898            var smv = this,
899                stc = smv.stc;
900
901            stc.$rightScrollArrow.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN, false);
902        };
903
904    }(ScrollMovement.prototype));
905
906
907
908    /* **********************************************************************
909     * ScrollingTabsControl - Class that each directive will instantiate
910     * **********************************************************************/
911    function ScrollingTabsControl($tabsContainer, $accTabInstance) {
912        var stc = this;
913
914        stc.$tabsContainer = $tabsContainer;
915        if ($accTabInstance)
916            stc.$accTabsContainer = $accTabInstance;
917        stc.movableContainerLeftPos = 0;
918        stc.scrollArrowsVisible = false;
919        stc.scrollToTabEdge = false;
920        stc.isTabMode = true;
921        stc.disableScrollArrowsOnFullyScrolled = false;
922
923        stc.scrollMovement = new ScrollMovement(stc);
924        stc.eventHandlers = new EventHandlers(stc);
925        stc.elementsHandler = new ElementsHandler(stc);
926    }
927
928    // prototype methods
929    (function (p) {
930        p.initTabs = function (options, $scroller, readyCallback, attachTabContentToDomCallback, $accTabs) {
931            var stc = this,
932                elementsHandler = stc.elementsHandler;
933
934            if (options.scrollToTabEdge) {
935                stc.scrollToTabEdge = true;
936            }
937
938            if (options.disableScrollArrowsOnFullyScrolled) {
939                stc.disableScrollArrowsOnFullyScrolled = true;
940            }
941            stc.$accTabsContainer = $accTabs;
942            setTimeout(initTabsAfterTimeout, 100);
943
944            function initTabsAfterTimeout() {
945                var actionsTaken;
946
947                // if we're just wrapping non-data-driven tabs, the user might
948                // have the .nav-tabs hidden to prevent the clunky flash of
949                // multi-line tabs on page refresh, so we need to make sure
950                // they're visible before trying to wrap them
951                $scroller.find('.TA-nav-tabs').show();
952
953                elementsHandler.initElements(options);
954                actionsTaken = elementsHandler.refreshAllElementSizes();
955
956                $scroller.css('visibility', 'visible');
957
958                if (attachTabContentToDomCallback) {
959                    attachTabContentToDomCallback();
960                }
961
962                if (readyCallback) {
963                    readyCallback();
964                }
965            }
966        };
967
968        p.scrollToActiveTab = function (options) {
969            var stc = this,
970                smv = stc.scrollMovement;
971
972            smv.scrollToActiveTab(options);
973        };
974    }(ScrollingTabsControl.prototype));
975
976
977
978
979    ////////////////////////////////////////////
980    //
981    // plugin-specific stuff
982    //
983    ////////////////////////////////////////////
984    var tabElements = (function () {
985
986        return {
987            getElTabPaneForLi: getElTabPaneForLi,
988            getNewElNavTabs: getNewElNavTabs,
989            getNewElScrollerElementWrappingNavTabsInstance: getNewElScrollerElementWrappingNavTabsInstance,
990            getNewElTabAnchor: getNewElTabAnchor,
991            getNewElTabContent: getNewElTabContent,
992            getNewElTabAccDiv: getNewElTabAccDiv,
993            getNewElTabLi: getNewElTabLi,
994            getNewElTabPane: getNewElTabPane
995        };
996
997        ///////////////////
998
999        // ---- retrieve existing elements from the DOM ----------
1000        function getElTabPaneForLi($li) {
1001            return $($li.find('a').attr('href'));
1002        }
1003
1004
1005        // ---- create new elements ----------
1006        function getNewElNavTabs() {
1007            return $('<ul class="TA-nav TA-nav-tabs"></ul>');
1008        }
1009
1010        function getNewElScrollerElementWrappingNavTabsInstance($navTabsInstance) {
1011            var $tabsContainer = $('<div class="scrtabs-tab-container"></div>'),
1012                $leftArrow = $('<div class="scrtabs-tab-scroll-arrow scrtabs-js-tab-scroll-arrow-left"><em class="fa fa-arrow-left"></em></div>'),
1013                $rightArrow = $('<div class="scrtabs-tab-scroll-arrow scrtabs-js-tab-scroll-arrow-right"><em class="fa fa-arrow-right"></em></div>'),
1014                $fixedContainer = $('<div class="scrtabs-tabs-fixed-container"></div>'),
1015                $movableContainer = $('<div class="scrtabs-tabs-movable-container" role="tablist"></div>');
1016
1017            //$leftArrow = $('<div class="scrtabs-tab-scroll-arrow scrtabs-js-tab-scroll-arrow-left"><span class="glyphicon glyphicon-chevron-left"></span></div>'),
1018            //$rightArrow = $('<div class="scrtabs-tab-scroll-arrow scrtabs-js-tab-scroll-arrow-right"><span class="glyphicon glyphicon-chevron-right"></span></div>'),
1019
1020            return $tabsContainer
1021                .append($leftArrow,
1022                    $fixedContainer.append($movableContainer.append($navTabsInstance)),
1023                    $rightArrow);
1024        }
1025
1026        function getNewElTabAnchor(tab, propNames, forAcc) {
1027            return $('<a role="tab" data-toggle="tab"></a>')
1028                .attr({ 'href': '#' + tab[propNames.paneId], 'aria-controls': tab[propNames.paneId], 'aria-selected': 'false' })
1029                .html(tab[propNames.title] + (forAcc ? "<em class=\"fa fa-plus\"></em>" : ""));
1030        }
1031
1032        function getNewElTabContent() {
1033            return $('<div class="TA-tab-content"></div>');
1034        }
1035
1036        function getNewElTabLi(tab, propNames, forceActiveTab) {
1037            var $li = $('<li role="presentation" class=""></li>'),
1038                $a = getNewElTabAnchor(tab, propNames).appendTo($li);
1039            if (tab[propNames.disabled]) {
1040                $li.addClass('disabled');
1041                $a.attr('data-toggle', '');
1042            } else if (forceActiveTab && tab[propNames.active]) {
1043                $li.addClass('active');
1044            }
1045
1046            return $li;
1047        }
1048        function getNewElTabAccDiv(tab, propNames, forceActiveTab) {
1049            var $div = $('<div role="presentation" class="accordionTabHead"></div>'),
1050                $a = getNewElTabAnchor(tab, propNames, true).appendTo($div);
1051            if (tab[propNames.disabled]) {
1052                $div.addClass('disabled');
1053                $a.attr('data-toggle', '');
1054            } else if (forceActiveTab && tab[propNames.active]) {
1055                $div.addClass('active');
1056            }
1057
1058            return $div;
1059        }
1060
1061        function getNewElTabPane(tab, propNames, forceActiveTab) {
1062            var $pane = $('<div role="tabpanel" class="tab-pane"></div>')
1063                .attr({ 'id': tab[propNames.paneId], 'aria-hidden': 'true', 'style': 'display:none' })
1064                .html(tab[propNames.content]);
1065
1066            if (forceActiveTab && tab[propNames.active]) {
1067                $pane.addClass('active').attr('aria-hidden', 'false');
1068                $pane.show();
1069                $pane.off("keydown", handleKeyDDownOnPanel);
1070                $pane.on("keydown", handleKeyDDownOnPanel);
1071            }
1072
1073            return $pane;
1074        }
1075        var handleKeyDDownOnPanel = function (e) {
1076            var $this = $(this);
1077            if ($this.is('.disabled, :disabled')) return;
1078            if (e.ctrlKey && (e.keyCode == 37 || e.keyCode == 38)) {
1079                e.preventDefault();
1080                e.stopPropagation();
1081                var arialabel = $this.attr("aria-labelledby");
1082                var contentCtrl = $this.parent().parent();
1083                if ($this.parent().hasClass('TA-accordionMode')) {
1084                    contentCtrl = $this.parent();
1085                }
1086                contentCtrl.find("#" + arialabel).focus();
1087            }
1088        };
1089
1090    }()); // tabElements
1091
1092    var tabUtils = (function () {
1093
1094        return {
1095            didTabOrderChange: didTabOrderChange,
1096            getIndexOfClosestEnabledTab: getIndexOfClosestEnabledTab,
1097            getTabIndexByPaneId: getTabIndexByPaneId,
1098            storeDataOnLiEl: storeDataOnLiEl
1099        };
1100
vendor: 3,083 bytes, lines 1101-1187
1101        ///////////////////
1102
1103        function didTabOrderChange($currTabLis, updatedTabs, propNames) {
1104            var isTabOrderChanged = false;
1105
1106            $currTabLis.each(function (currDomIdx) {
1107                var newIdx = getTabIndexByPaneId(updatedTabs, propNames.paneId, $(this).data('tab')[propNames.paneId]);
1108
1109                if ((newIdx > -1) && (newIdx !== currDomIdx)) { // tab moved
1110                    isTabOrderChanged = true;
1111                    return false; // exit .each() loop
1112                }
1113            });
1114
1115            return isTabOrderChanged;
1116        }
1117
1118        function getIndexOfClosestEnabledTab($currTabLis, startIndex) {
1119            var lastIndex = $currTabLis.length - 1,
1120                closestIdx = -1,
1121                incrementFromStartIndex = 0,
1122                testIdx = 0;
1123
1124            // expand out from the current tab looking for an enabled tab;
1125            // we prefer the tab after us over the tab before
1126            while ((closestIdx === -1) && (testIdx >= 0)) {
1127
1128                if ((((testIdx = startIndex + (++incrementFromStartIndex)) <= lastIndex) &&
1129                    !$currTabLis.eq(testIdx).hasClass('disabled')) ||
1130                    (((testIdx = startIndex - incrementFromStartIndex) >= 0) &&
1131                        !$currTabLis.eq(testIdx).hasClass('disabled'))) {
1132
1133                    closestIdx = testIdx;
1134
1135                }
1136            }
1137
1138            return closestIdx;
1139        }
1140
1141        function getTabIndexByPaneId(tabs, paneIdPropName, paneId) {
1142            var idx = -1;
1143
1144            tabs.some(function (tab, i) {
1145                if (tab[paneIdPropName] === paneId) {
1146                    idx = i;
1147                    return true; // exit loop
1148                }
1149            });
1150
1151            return idx;
1152        }
1153
1154        function storeDataOnLiEl($li, tabs, index) {
1155            $li.data({
1156                tab: $.extend({}, tabs[index]), // store a clone so we can check for changes
1157                index: index
1158            });
1159        }
1160
1161    }()); // tabUtils
1162
1163
1164    function buildNavTabsAndTabContentForTargetElementInstance($targetElInstance, settings, readyCallback) {
1165        var tabs = settings.tabs,
1166            propNames = {
1167                paneId: settings.propPaneId,
1168                title: settings.propTitle,
1169                active: settings.propActive,
1170                disabled: settings.propDisabled,
1171                content: settings.propContent,
1172                tabMode: settings.tabMode
1173            },
1174            ignoreTabPanes = settings.ignoreTabPanes,
1175            hasTabContent = tabs.length && tabs[0][propNames.content] !== undefined,
1176            $navTabs = tabElements.getNewElNavTabs(),
1177            $tabContent = tabElements.getNewElTabContent(),
1178            $scroller,
1179            $accordionTabs,
1180            attachTabContentToDomCallback = ignoreTabPanes ? null : function () {
1181                $scroller.after($tabContent);
1182            };
1183
1184        if (!tabs.length) {
1185            return;
1186        }
1187
1188        tabs.forEach(function (tab, index) {
1189
1190            var tabH = tabElements.getNewElTabLi(tab, propNames, true); // true -> forceActiveTab
1191            tabH.appendTo($navTabs);
1192
1193            var $accordionTabH = tabElements.getNewElTabAccDiv(tab, propNames, true);
1194            $tabContent.append($accordionTabH);
1195            // build the tab panes if we weren't told to ignore them and there's
1196            // tab content data available
1197            if (!ignoreTabPanes && hasTabContent) {
1198                tabElements
1199                    .getNewElTabPane(tab, propNames, true) // true -> forceActiveTab
1200                    .appendTo($tabContent);
1201            }
1202        });
1203        $accordionTabs = $tabContent.find("div.accordionTabHead");
1204        $scroller = wrapNavTabsInstanceInScroller($navTabs,
1205            settings,
1206            readyCallback,
1207            attachTabContentToDomCallback, $accordionTabs);
1208
1209
1210        $scroller.appendTo($targetElInstance);
1211        //$accordionTabs.appendTo($targetElInstance);
1212
1213        $targetElInstance.data({
1214            scrtabs: {
1215                tabs: tabs,
1216                propNames: propNames,
1217                ignoreTabPanes: ignoreTabPanes,
1218                hasTabContent: hasTabContent,
1219                scroller: $scroller
1220            }
1221        });
1222
1223        // once the nav-tabs are wrapped in the scroller, attach each tab's
1224        // data to it for reference later; we need to wait till they're
1225        // wrapped in the scroller because we wrap a *clone* of the nav-tabs
1226        // we built above, not the original nav-tabs
1227        $scroller.find('.TA-nav-tabs > li').each(function (index) {
1228            tabUtils.storeDataOnLiEl($(this), tabs, index);
1229        });
1230
1231        if (!settings.tabMode) {
1232            $scroller.hide();
1233            $accordionTabs.show();
1234        }
1235        else {
1236            $scroller.show();
1237            $accordionTabs.hide();
1238        }
1239    }
1240
1241    function checkForTabAdded(refreshData) {
1242        var updatedTabsArray = refreshData.updatedTabsArray,
1243            propNames = refreshData.propNames,
1244            ignoreTabPanes = refreshData.ignoreTabPanes,
1245            options = refreshData.options,
1246            $currTabLis = refreshData.$currTabLis,
1247            $navTabs = refreshData.$navTabs,
1248            $currTabContentPanesContainer = ignoreTabPanes ? null : refreshData.$currTabContentPanesContainer,
1249            $currTabContentPanes = ignoreTabPanes ? null : refreshData.$currTabContentPanes,
1250            isInitTabsRequired = false;
1251
1252        // make sure each tab in the updated tabs array has a corresponding DOM element
1253        updatedTabsArray.forEach(function (tab, idx) {
1254            var $li = $currTabLis.find('a[href="#' + tab[propNames.paneId] + '"]'),
1255                isTabIdxPastCurrTabs = (idx >= $currTabLis.length),
1256                $pane;
1257
1258            if (!$li.length) { // new tab
1259                isInitTabsRequired = true;
1260
1261                // add the tab, add its pane (if necessary), and refresh the scroller
1262                $li = tabElements.getNewElTabLi(tab, propNames, options.forceActiveTab);
1263                tabUtils.storeDataOnLiEl($li, updatedTabsArray, idx);
1264
1265                if (isTabIdxPastCurrTabs) { // append to end of current tabs
1266                    $li.appendTo($navTabs);
1267                } else {                        // insert in middle of current tabs
1268                    $li.insertBefore($currTabLis.eq(idx));
1269                }
1270
1271                if (!ignoreTabPanes && tab[propNames.content] !== undefined) {
1272                    $pane = tabElements.getNewElTabPane(tab, propNames, options.forceActiveTab);
1273                    if (isTabIdxPastCurrTabs) { // append to end of current tabs
1274                        $pane.appendTo($currTabContentPanesContainer);
1275                    } else {                        // insert in middle of current tabs
1276                        $pane.insertBefore($currTabContentPanes.eq(idx));
1277                    }
1278                }
1279
1280            }
1281
1282        });
1283
1284        return isInitTabsRequired;
1285    }
1286
1287    function checkForTabPropertiesUpdated(refreshData) {
1288        var tabLiData = refreshData.tabLi,
1289            ignoreTabPanes = refreshData.ignoreTabPanes,
1290            $li = tabLiData.$li,
1291            $contentPane = tabLiData.$contentPane,
1292            origTabData = tabLiData.origTabData,
1293            newTabData = tabLiData.newTabData,
1294            propNames = refreshData.propNames,
1295            isInitTabsRequired = false;
1296
1297        // update tab title if necessary
1298        if (origTabData[propNames.title] !== newTabData[propNames.title]) {
1299            $li.find('a[role="tab"]')
1300                .html(origTabData[propNames.title] = newTabData[propNames.title]);
1301
1302            isInitTabsRequired = true;
1303        }
1304
1305        // update tab active state if necessary
1306        if (refreshData.options.forceActiveTab) {
1307            // set the active tab based on the tabs array regardless of the current
1308            // DOM state, which could have been changed by the user clicking a tab
1309            // without those changes being reflected back to the tab data
1310            $li[newTabData[propNames.active] ? 'addClass' : 'removeClass']('active');
1311
1312            $contentPane[newTabData[propNames.active] ? 'addClass' : 'removeClass']('active');
1313
1314            origTabData[propNames.active] = newTabData[propNames.active];
1315
1316            isInitTabsRequired = true;
1317        }
1318
1319        // update tab content pane if necessary
1320        if (!ignoreTabPanes && origTabData[propNames.content] !== newTabData[propNames.content]) {
1321            $contentPane.html(origTabData[propNames.content] = newTabData[propNames.content]);
vendor: 8,300 bytes, lines 1322-1540
1322            isInitTabsRequired = true;
1323        }
1324
1325        return isInitTabsRequired;
1326    }
1327
1328    function checkForTabRemoved(refreshData) {
1329        var tabLiData = refreshData.tabLi,
1330            ignoreTabPanes = refreshData.ignoreTabPanes,
1331            $li = tabLiData.$li,
1332            idxToMakeActive;
1333
1334        if (tabLiData.newIdx !== -1) { // tab was not removed--it has a valid index
1335            return false;
1336        }
1337
1338        // if this was the active tab, make the closest enabled tab active
1339        if ($li.hasClass('active')) {
1340
1341            idxToMakeActive = tabUtils.getIndexOfClosestEnabledTab(refreshData.$currTabLis, tabLiData.currDomIdx);
1342            if (idxToMakeActive > -1) {
1343                refreshData.$currTabLis
1344                    .eq(idxToMakeActive)
1345                    .addClass('active');
1346
1347                if (!ignoreTabPanes) {
1348                    refreshData.$currTabContentPanes
1349                        .eq(idxToMakeActive)
1350                        .addClass('active');
1351                }
1352            }
1353        }
1354
1355        $li.remove();
1356
1357        if (!ignoreTabPanes) {
1358            tabLiData.$contentPane.remove();
1359        }
1360
1361        return true;
1362    }
1363
1364    function checkForTabsOrderChanged(refreshData) {
1365        var $currTabLis = refreshData.$currTabLis,
1366            updatedTabsArray = refreshData.updatedTabsArray,
1367            propNames = refreshData.propNames,
1368            ignoreTabPanes = refreshData.ignoreTabPanes,
1369            newTabsCollection = [],
1370            newTabPanesCollection = ignoreTabPanes ? null : [];
1371
1372        if (!tabUtils.didTabOrderChange($currTabLis, updatedTabsArray, propNames)) {
1373            return false;
1374        }
1375
1376        // the tab order changed...
1377        updatedTabsArray.forEach(function (t, i) {
1378            var paneId = t[propNames.paneId];
1379
1380            newTabsCollection.push(
1381                $currTabLis
1382                    .find('a[role="tab"][href="#' + paneId + '"]')
1383                    .parent('li')
1384            );
1385
1386            if (!ignoreTabPanes) {
1387                newTabPanesCollection.push($('#' + paneId));
1388            }
1389        });
1390
1391        refreshData.$navTabs.append(newTabsCollection);
1392
1393        if (!ignoreTabPanes) {
1394            refreshData.$currTabContentPanesContainer.append(newTabPanesCollection);
1395        }
1396
1397        return true;
1398    }
1399
1400    function checkForTabsRemovedOrUpdated(refreshData) {
1401        var $currTabLis = refreshData.$currTabLis,
1402            updatedTabsArray = refreshData.updatedTabsArray,
1403            propNames = refreshData.propNames,
1404            isInitTabsRequired = false;
1405
1406
1407        $currTabLis.each(function (currDomIdx) {
1408            var $li = $(this),
1409                origTabData = $li.data('tab'),
1410                newIdx = tabUtils.getTabIndexByPaneId(updatedTabsArray, propNames.paneId, origTabData[propNames.paneId]),
1411                newTabData = (newIdx > -1) ? updatedTabsArray[newIdx] : null;
1412
1413            refreshData.tabLi = {
1414                $li: $li,
1415                currDomIdx: currDomIdx,
1416                newIdx: newIdx,
1417                $contentPane: tabElements.getElTabPaneForLi($li),
1418                origTabData: origTabData,
1419                newTabData: newTabData
1420            };
1421
1422            if (checkForTabRemoved(refreshData)) {
1423                isInitTabsRequired = true;
1424                return; // continue to next $li in .each() since we removed this tab
1425            }
1426
1427            if (checkForTabPropertiesUpdated(refreshData)) {
1428                isInitTabsRequired = true;
1429            }
1430        });
1431
1432        return isInitTabsRequired;
1433    }
1434
1435    function destroyPlugin() {
1436        var $targetElInstance = $(this),
1437            scrtabsData = $targetElInstance.data('scrtabs'),
1438            $tabsContainer;
1439
1440        if (!scrtabsData) {
1441            return;
1442        }
1443
1444        scrtabsData.scroller
1445            .off(CONSTANTS.EVENTS.DROPDOWN_MENU_SHOW)
1446            .off(CONSTANTS.EVENTS.DROPDOWN_MENU_HIDE);
1447
1448        // if there were any dropdown menus opened, remove the css we added to
1449        // them so they would display correctly
1450        scrtabsData.scroller
1451            .find('[data-' + CONSTANTS.DATA_KEY_DDMENU_MODIFIED + ']')
1452            .css({
1453                display: '',
1454                left: '',
1455                top: ''
1456            })
1457            .off(CONSTANTS.EVENTS.CLICK)
1458            .removeAttr('data-' + CONSTANTS.DATA_KEY_DDMENU_MODIFIED);
1459
1460        if (scrtabsData.isWrapperOnly) { // we just wrapped nav-tabs markup, so restore it
1461            // $targetElInstance is the ul.nav-tabs
1462            $tabsContainer = $targetElInstance.parents('.scrtabs-tab-container');
1463
1464            if ($tabsContainer.length) {
1465                $tabsContainer.replaceWith($targetElInstance);
1466            }
1467
1468        } else { // we generated the tabs from data so destroy everything we created
1469            if (scrtabsData.scroller && scrtabsData.scroller.initTabs) {
1470                scrtabsData.scroller.initTabs = null;
1471            }
1472
1473            // $targetElInstance is the container for the ul.nav-tabs we generated
1474            $targetElInstance
1475                .find('.scrtabs-tab-container')
1476                .add('.tab-content')
1477                .remove();
1478        }
1479
1480        $targetElInstance.removeData('scrtabs');
1481
1482        $(window).off(CONSTANTS.EVENTS.WINDOW_RESIZE);
1483        $('body').off(CONSTANTS.EVENTS.FORCE_REFRESH);
1484    }
1485
1486    function listenForDropdownMenuTabs($scroller) {
1487        var $ddMenu;
1488
1489        $scroller
1490            .off(CONSTANTS.EVENTS.CLICK.replace(".scrtabs", ""), handleDropdownShow)
1491            .off(CONSTANTS.EVENTS.KEYDOWN.replace(".scrtabs", ""), 'a[role="tab"]', handleKeyDownOnDropdownMenuItem);
1492
1493        // for dropdown menus to show, we need to move them out of the
1494        // scroller and append them to the body
1495        $scroller
1496            .on(CONSTANTS.EVENTS.CLICK.replace(".scrtabs", ""), handleDropdownShow)
1497            .on(CONSTANTS.EVENTS.KEYDOWN.replace(".scrtabs", ""), 'a[role="tab"]', handleKeyDownOnDropdownMenuItem);
1498
1499        $scroller.parent().find('div[role="tabpanel"]').each(function (a) {
1500            $(this).off(CONSTANTS.EVENTS.KEYDOWN.replace(".scrtabs", ""), handleKeyDownOnTabPanel);
1501            $(this).on(CONSTANTS.EVENTS.KEYDOWN.replace(".scrtabs", ""), handleKeyDownOnTabPanel);
1502        });
1503
1504        $scroller.off(CONSTANTS.EVENTS.CLICK.replace(".scrtabs", ""), 'a[role="tab"]', handleClickOnDropdownMenuItem)
1505            .off(CONSTANTS.EVENTS.KEYDOWN.replace(".scrtabs", ""), 'a[role="tab"]', handleKeyDownOnDropdownMenuItem);
1506        $scroller.on(CONSTANTS.EVENTS.CLICK.replace(".scrtabs", ""), 'a[role="tab"]', handleClickOnDropdownMenuItem)
1507            .on(CONSTANTS.EVENTS.KEYDOWN.replace(".scrtabs", ""), 'a[role="tab"]', handleKeyDownOnDropdownMenuItem);
1508
1509        function handleDropdownHide(e) {
1510            // move the dropdown menu back into its tab
1511            $(e.target).append($ddMenu.off(CONSTANTS.EVENTS.CLICK));
1512        }
1513
1514        function handleDropdownShow(e) {
1515            var $ddParentTabLi = $(e.target),
1516                ddLiOffset = $ddParentTabLi.offset(),
1517                $currActiveTab = $scroller.find('li[role="presentation"].active'),
1518                ddMenuRightX,
1519                tabsContainerMaxX,
1520                ddMenuTargetLeft;
1521
1522
1523
1524            $ddMenu = $ddParentTabLi
1525                .find('.dropdown-menu')
1526                .attr('data-' + CONSTANTS.DATA_KEY_DDMENU_MODIFIED, true);
1527
1528
1529
1530            // if the dropdown's parent tab li isn't already active,
1531            // we need to deactivate any active menu item in the dropdown
1532            if ($currActiveTab[0] !== $ddParentTabLi[0]) {
1533                $ddMenu.find('li.active').removeClass('active');
1534            }
1535
1536            // we need to do our own click handling because the built-in
1537            // bootstrap handlers won't work since we moved the dropdown
1538            // menu outside the tabs container
1539            $ddMenu.on(CONSTANTS.EVENTS.CLICK, 'a[role="tab"]', handleClickOnDropdownMenuItem);
1540            //$ddMenu.on(CONSTANTS.EVENTS.KEYDOWN, 'a[role="tab"]', handleKeyDownOnDropdownMenuItem);
1541            //$ddMenu.on("keydown", 'a[role="tab"]', handleKeyDownOnDropdownMenuItem);
1542
1543
1544
1545
1546            $('body').append($ddMenu);
1547
1548            // make sure the menu doesn't go off the right side of the page
1549            ddMenuRightX = $ddMenu.width() + ddLiOffset.left;
1550            tabsContainerMaxX = $scroller.width() - (CONSTANTS.SCROLL_ARROW_WIDTH + 1);
1551            ddMenuTargetLeft = ddLiOffset.left;
1552
1553            if (ddMenuRightX > tabsContainerMaxX) {
1554                ddMenuTargetLeft -= (ddMenuRightX - tabsContainerMaxX);
1555            }
1556            //$scroller.scrollToActiveTab();
1557            $ddMenu.css({
1558                'display': 'block',
1559                'top': ddLiOffset.top + $ddParentTabLi.outerHeight() - 2,
1560                'left': ddMenuTargetLeft
1561            });
1562
1563
1564
1565            //$scroller.scrollToActiveTab();
1566        }
1567        function handleClickOnDropdownMenuItem(e) {
1568            e.preventDefault();
1569            e.stopPropagation();
1570            var $selectedMenuItemAnc = $(this),
1571                $selectedMenuItemLi = $selectedMenuItemAnc.parent('li'),
1572                $selectedMenuItemDropdownMenu = $selectedMenuItemLi.parent('.dropdown-menu'),
1573                targetPaneId = $selectedMenuItemAnc.attr('href');
1574
1575            if ($selectedMenuItemLi.hasClass('active')) {
1576                $scroller.scrollToActiveTab();
1577                return;
1578            }
1579
1580            $(targetPaneId).addClass('active');
1581            $(targetPaneId).parent().find('div[role="tabpanel"].active').attr("aria-hidden", "true").removeClass("active").hide();
1582            $(targetPaneId).attr("aria-hidden", "false").addClass("active").show();
1583            $(targetPaneId).off(CONSTANTS.EVENTS.KEYDOWN.replace(".scrtabs", ""), handleKeyDownOnTabPanel);
1584            $(targetPaneId).on(CONSTANTS.EVENTS.KEYDOWN.replace(".scrtabs", ""), handleKeyDownOnTabPanel);
1585
1586            $selectedMenuItemLi.parent().find('li.active a[role="tab"]').attr({ "aria-selected": "false" });
1587            $selectedMenuItemLi.parent().find('li.active').removeClass("active");
1588            $selectedMenuItemLi.addClass("active");
1589            $selectedMenuItemAnc.attr({ "aria-selected": "true" });
1590
1591            $scroller.scrollToActiveTab();
1592            $selectedMenuItemAnc.focus();
1593            return false;
1594
1595        }
1596        function handleKeyDownOnDropdownMenuItem(e) {
1597            if (!/(37|38|39|40|36|35|13)/.test(e.which)) return;
1598            var $this = $(this);
1599            if ($this.is('.disabled, :disabled')) return;
1600
1601            e.preventDefault();
1602            e.stopPropagation();
1603            var $ddParentTabLi = $(e.target),
1604                ddLiOffset = $ddParentTabLi.offset(),
1605                $currActiveTab = $scroller.find('li[role="presentation"].active'),
1606                ddMenuRightX,
1607                tabsContainerMaxX,
1608                ddMenuTargetLeft;
1609
1610            $ddMenu = $ddParentTabLi
1611                .find('.dropdown-menu')
1612                .attr('data-' + CONSTANTS.DATA_KEY_DDMENU_MODIFIED, true);
1613
1614            var currInd = $this.parent().index();
1615            var accTabs = $this.parent().parent().find("li");
1616            switch (e.which) {
1617                case 37: case 38:
1618                    var $obj = accTabs.eq(currInd - 1);
1619                    if (!($obj && $obj.find('a[role="tab"]').length <= 0) && currInd - 1 <= 0) {
1620                        //var index = (currInd - 1 <= 0) ? accTabs.length - 1 : 0;
1621                        $obj = accTabs.eq(0);
1622                    }
1623                    $obj.find('a[role="tab"]').click();
1624                    $obj.find('a[role="tab"]').focus();
1625                    break;
1626                case 39: case 40:
1627                    var $obj = accTabs.eq(currInd + 1);
1628                    if (!($obj && $obj.find('a[role="tab"]').length <= 0) && currInd + 1 > accTabs.length - 1) {
1629                        //var index = currInd + 1 > accTabs.length - 1 ? 0 : accTabs.length - 1;
1630                        $obj = accTabs.eq(accTabs.length - 1);
1631                    }
1632                    $obj.find('a[role="tab"]').click();
1633                    $obj.find('a[role="tab"]').focus();
1634                    break;
1635                case 36:
1636                    // activate first tab
1637                    accTabs.eq(0).find('a[role="tab"]:first').click();
1638                    accTabs.eq(0).find('a[role="tab"]:first').focus();
1639                    break;
1640                case 35:
1641                    // activate last tab
1642                    accTabs.eq(accTabs.length - 1).find('a[role="tab"]:last').click();
1643                    accTabs.eq(accTabs.length - 1).find('a[role="tab"]:last').focus();
1644                    break;
1645                case 13:
1646                    $(this).click();
1647                    break;
1648            }
1649        };
1650        function UpdateScrollTab($scroller) {
1651            var $currActiveTab = $scroller.find('li[role="presentation"].active');
1652            var $leftScrollArrow = $scroller.find('.scrtabs-js-tab-scroll-arrow-left');
1653            var $rightScrollArrow = $scroller.find('.scrtabs-js-tab-scroll-arrow-right');
1654            var liCount = $scroller.find('li[role="presentation"]').length - 1;
1655            $scroller.scrollToActiveTab();
1656            //if ($currActiveTab.index() <= 0 || $scroller.find('li[role="presentation"]:first').css("display") == "block"){
1657            //    //$leftScrollArrow.addClass("scrtabs-disable");
1658            //}
1659            //else{
1660            //    //$leftScrollArrow.removeClass("scrtabs-disable");
1661            //}
1662            //if ($currActiveTab.index() >= liCount || $scroller.find('li[role="presentation"]:last').css("display") == "block") {
1663            //    //$rightScrollArrow.addClass("scrtabs-disable");
1664            //} else {
1665            //    //$rightScrollArrow.removeClass("scrtabs-disable");
1666            //}
1667
1668        };
1669        function handleKeyDownOnTabPanel(e) {
1670            var $this = $(this);
1671            if ($this.is('.disabled, :disabled')) return;
1672            if (e.ctrlKey && (e.keyCode == 37 || e.keyCode == 38)) {
1673                e.preventDefault();
1674                e.stopPropagation();
1675                var arialabel = $this.attr("aria-labelledby");
1676                var contentCtrl = $this.parent().parent();
1677                if ($this.parent().hasClass('TA-accordionMode')) {
1678                    contentCtrl = $this.parent();
1679                }
1680                contentCtrl.find("#" + arialabel).focus();
1681            }
1682        };
1683    }
1684
1685
1686    function listenForAccordionTabs($accTabs) {
1687        var $ddMenu;
1688        $accTabs.off(CONSTANTS.EVENTS.CLICK.replace(".scrtabs", ""), handleDropdownShow)
1689            .off(CONSTANTS.EVENTS.KEYDOWN.replace(".scrtabs", ""), 'a[role="tab"]', handleKeyDownOnAccTabs);
1690
1691        // for dropdown menus to show, we need to move them out of the
1692        // scroller and append them to the body
1693        $accTabs.on(CONSTANTS.EVENTS.CLICK.replace(".scrtabs", ""), handleDropdownShow)
1694            .on(CONSTANTS.EVENTS.KEYDOWN.replace(".scrtabs", ""), 'a[role="tab"]', handleKeyDownOnAccTabs);
1695
1696        $accTabs.parent().find('div[role="tabpanel"]').each(function (a) {
1697            $(this).off(CONSTANTS.EVENTS.KEYDOWN.replace(".scrtabs", ""), handleKeyDownOnTabPanel);
1698            $(this).on(CONSTANTS.EVENTS.KEYDOWN.replace(".scrtabs", ""), handleKeyDownOnTabPanel);
1699        });
1700
1701        $accTabs.off(CONSTANTS.EVENTS.CLICK.replace(".scrtabs", ""), 'a[role="tab"]', handleClickOnAccTabs)
1702            .off(CONSTANTS.EVENTS.KEYDOWN.replace(".scrtabs", ""), 'a[role="tab"]', handleKeyDownOnAccTabs);
1703        $accTabs.on(CONSTANTS.EVENTS.CLICK.replace(".scrtabs", ""), 'a[role="tab"]', handleClickOnAccTabs)
1704            .on(CONSTANTS.EVENTS.KEYDOWN.replace(".scrtabs", ""), 'a[role="tab"]', handleKeyDownOnAccTabs);
1705
1706        function handleDropdownHide(e) {
1707            // move the dropdown menu back into its tab
1708            $(e.target).append($ddMenu.off(CONSTANTS.EVENTS.CLICK));
1709        }
1710
1711        function handleDropdownShow(e) {
1712            var $ddParentTabLi = $(e.target),
1713                ddLiOffset = $ddParentTabLi.offset(),
1714                $currActiveTab = $accTabs.find('div[role="presentation"].active'),
1715                ddMenuRightX,
1716                tabsContainerMaxX,
1717                ddMenuTargetLeft;
1718
1719            $ddMenu = $ddParentTabLi
1720                .find('.dropdown-menu')
1721                .attr('data-' + CONSTANTS.DATA_KEY_DDMENU_MODIFIED, true);
1722
1723            // if the dropdown's parent tab li isn't already active,
1724            // we need to deactivate any active menu item in the dropdown
1725            if ($currActiveTab[0] !== $ddParentTabLi[0]) {
1726                $ddMenu.find('div.active').removeClass('active');
1727            }
1728
1729            // we need to do our own click handling because the built-in
1730            // bootstrap handlers won't work since we moved the dropdown
1731            // menu outside the tabs container
1732            $ddMenu.on(CONSTANTS.EVENTS.CLICK, 'a[role="tab"]', handleClickOnAccTabs);
1733
1734            $('body').append($ddMenu);
1735
1736        }
1737        function handleClickOnAccTabs(e) {
1738            e.preventDefault();
1739            e.stopPropagation();
1740            var $selectedMenuItemAnc = $(this),
1741                $selectedMenuItemLi = $selectedMenuItemAnc.parent('div.accordionTabHead'),
1742                $selectedMenuItemDropdownMenu = $selectedMenuItemLi.parent('.dropdown-menu'),
1743                targetPaneId = $selectedMenuItemAnc.attr('href');
1744            var isActiveItem = $selectedMenuItemLi.hasClass('active');
1745            //collapse all accordions
1746            $(targetPaneId).parent().find('div[role="tabpanel"]').attr("aria-hidden", "true").removeClass("active").hide();
1747            $selectedMenuItemLi.parent().find('.active a[role="tab"]').attr({ "aria-selected": "false" });
1748            $selectedMenuItemLi.parent().find('.active').removeClass("active");
1749            //activate selected item
1750            if (!isActiveItem) {
1751                $(targetPaneId).addClass('active');
1752                $(targetPaneId).attr("aria-hidden", "false").addClass("active").show();
1753                $(targetPaneId).off(CONSTANTS.EVENTS.KEYDOWN.replace(".scrtabs", ""), handleKeyDownOnTabPanel);
1754                $(targetPaneId).on(CONSTANTS.EVENTS.KEYDOWN.replace(".scrtabs", ""), handleKeyDownOnTabPanel);
1755                $selectedMenuItemLi.addClass("active");
1756                $selectedMenuItemAnc.attr({ "aria-selected": "true" });
1757                $selectedMenuItemAnc.focus();
1758            }
1759            return false;
1760        }
1761
1762        function handleKeyDownOnAccTabs(e) {
1763            if (!/(37|38|39|40|36|35|13)/.test(e.which)) return;
1764            var $this = $(this);
1765            if ($this.is('.disabled, :disabled')) return;
1766
1767            e.preventDefault();
1768            e.stopPropagation();
1769            var $ddParentTabLi = $(e.target);
1770
1771
1772            $ddMenu = $ddParentTabLi
1773                .find('.dropdown-menu')
1774                .attr('data-' + CONSTANTS.DATA_KEY_DDMENU_MODIFIED, true);
1775            var currInd = $this.parent().index();
1776            var accTabs = $this.parent().parent().find(".accordionTabHead");
1777
1778            switch (e.which) {
1779                case 37: case 38:
1780                    var $obj = accTabs.eq(currInd / 2 - 1);
1781                    if (!($obj && $obj.find('a[role="tab"]').length != 0)) {
1782                        var index = (currInd / 2 - 1) <= 0 ? accTabs.length - 1 : 0;
1783                        $obj = accTabs.eq(index);
1784                    }
1785                    $obj.find('a[role="tab"]').click();
1786                    $obj.find('a[role="tab"]').focus();
1787
1788                    break;
1789                case 39: case 40:
1790                    var $obj = accTabs.eq(currInd / 2 + 1);
1791                    if (!($obj && $obj.find('a[role="tab"]').length != 0)) {
1792                        var index = ((currInd / 2 + 1) > accTabs.length - 1) ? 0 : accTabs.length - 1;
1793                        $obj = accTabs.eq(index);
1794                    }
1795                    $obj.find('a[role="tab"]').click();
1796                    $obj.find('a[role="tab"]').focus();
1797                    break;
1798                case 36:
1799                    // activate first tab
1800                    accTabs.eq(0).find('a[role="tab"]').click();
1801                    accTabs.eq(0).find('a[role="tab"]').focus();
1802                    break;
1803                case 35:
1804                    // activate last tab
1805                    accTabs.eq(accTabs.length - 1).find('a[role="tab"]').click();
1806                    accTabs.eq(accTabs.length - 1).find('a[role="tab"]').focus();
1807                    break;
1808                case 13:
1809                    $(this).click();
1810                    $(this).focus();
1811                    break;
1812            }
1813            return false;
1814        };
1815        function handleKeyDownOnTabPanel(e) {
1816            var $this = $(this);
1817            if ($this.is('.disabled, :disabled')) return;
1818            if (e.ctrlKey && (e.keyCode == 37 || e.keyCode == 38)) {
1819                e.preventDefault();
1820                e.stopPropagation();
1821                var arialabel = $this.attr("aria-labelledby");
1822                var contentCtrl = $this.parent().parent();
1823                if ($this.parent().hasClass('TA-accordionMode')) {
1824                    contentCtrl = $this.parent();
1825                }
1826                contentCtrl.find("#" + arialabel).focus();
1827            }
1828        };
1829    }
1830
1831
1832    function refreshDataDrivenTabs($container, options) {
1833        var instanceData = $container.data().scrtabs,
1834            scroller = instanceData.scroller,
1835            $navTabs = $container.find('.scrtabs-tab-container .TA-nav-tabs'),
1836            $currTabContentPanesContainer = $container.find('.TA-tab-content'),
1837            isInitTabsRequired = false,
1838            refreshData = {
1839                options: options,
1840                updatedTabsArray: instanceData.tabs,
1841                propNames: instanceData.propNames,
1842                ignoreTabPanes: instanceData.ignoreTabPanes,
1843                $navTabs: $navTabs,
1844                $currTabLis: $navTabs.find('> li'),
1845                $currTabContentPanesContainer: $currTabContentPanesContainer,
1846                $currTabContentPanes: $currTabContentPanesContainer.find('.tab-pane')
1847            };
1848
1849        // to preserve the tab positions if we're just adding or removing
1850        // a tab, don't completely rebuild the tab structure, but check
1851        // for differences between the new tabs array and the old
1852        if (checkForTabAdded(refreshData)) {
1853            isInitTabsRequired = true;
1854        }
1855
1856        if (checkForTabsOrderChanged(refreshData)) {
1857            isInitTabsRequired = true;
1858        }
1859
1860        if (checkForTabsRemovedOrUpdated(refreshData)) {
1861            isInitTabsRequired = true;
1862        }
1863
1864        if (isInitTabsRequired) {
1865            scroller.initTabs();
1866        }
1867
1868        return isInitTabsRequired;
1869    }
1870
1871    function refreshTargetElementInstance($container, options) {
1872        if (!$container.data('scrtabs')) { // target element doesn't have plugin on it
1873            return;
1874        }
1875
1876        // force a refresh if the tabs are static html or they're data-driven
1877        // but the data didn't change so we didn't call initTabs()
1878        if ($container.data('scrtabs').isWrapperOnly || !refreshDataDrivenTabs($container, options)) {
1879            $('body').trigger(CONSTANTS.EVENTS.FORCE_REFRESH);
1880        }
1881    }
1882
1883    function scrollToActiveTab() {
1884        var $targetElInstance = $(this),
1885            scrtabsData = $targetElInstance.data('scrtabs');
1886
1887        if (!scrtabsData) {
1888            return;
1889        }
1890
1891        scrtabsData.scroller.scrollToActiveTab();
1892    }
1893
1894    function wrapNavTabsInstanceInScroller($navTabsInstance, settings, readyCallback, attachTabContentToDomCallback, $accTabInstance) {
1895        var $scroller = tabElements.getNewElScrollerElementWrappingNavTabsInstance($navTabsInstance.clone(true)), // use clone because we replaceWith later            
1896            scrollingTabsControl = new ScrollingTabsControl($scroller, $accTabInstance),
1897            navTabsInstanceData = $navTabsInstance.data('scrtabs');
1898        var $accTabs;
1899        if ($accTabInstance)
1900            $accTabs = $accTabInstance;
1901
1902        if (!navTabsInstanceData) {
1903            $navTabsInstance.data('scrtabs', {
1904                scroller: $scroller,
1905            });
1906        } else {
1907            navTabsInstanceData.scroller = $scroller;
1908        }
1909
1910        $navTabsInstance.replaceWith($scroller.css('visibility', 'hidden'));
1911
1912        $scroller.initTabs = function () {
1913            scrollingTabsControl.initTabs(settings,
1914                $scroller,
1915                readyCallback,
1916                attachTabContentToDomCallback, $accTabs);
1917        };
1918
1919        $scroller.scrollToActiveTab = function () {
1920            scrollingTabsControl.scrollToActiveTab(settings);
1921        };
1922
1923        $scroller.initTabs();
1924
1925        listenForDropdownMenuTabs($scroller);
1926        //Accordion
1927        if ($accTabInstance)
1928            listenForAccordionTabs($accTabs);
1929        return $scroller;
1930    }
1931
1932
1933
1934    ////////////////////////
1935
1936    var methods = {
1937        destroy: function () {
1938            var $targetEls = this;
1939
1940            return $targetEls.each(destroyPlugin);
1941        },
1942
1943        init: function (options) {
1944            var $targetEls = this,
1945                targetElsLastIndex = $targetEls.length - 1,
1946                settings = $.extend({}, $.fn.scrollingTabs.defaults, options || {});
1947
1948            // ---- tabs NOT data-driven -------------------------Accordion events are not set in this
1949            if (!settings.tabs) {
1950
1951                // just wrap the selected .nav-tabs element(s) in the scroller
1952                return $targetEls.each(function (index) {
1953                    var dataObj = {
1954                        isWrapperOnly: true
1955                    },
1956                        $targetEl = $(this).data({ scrtabs: dataObj }),
1957                        readyCallback = (index < targetElsLastIndex) ? null : function () {
1958                            $targetEls.trigger(CONSTANTS.EVENTS.TABS_READY);
1959                        };
1960
1961                    wrapNavTabsInstanceInScroller($targetEl, settings, readyCallback);
1962                });
1963
1964            }
1965
1966            // ---- tabs data-driven -------------------------
1967            return $targetEls.each(function (index) {
1968                var $targetEl = $(this),
1969                    readyCallback = (index < targetElsLastIndex) ? null : function () {
1970                        $targetEls.trigger(CONSTANTS.EVENTS.TABS_READY);
1971                    };
1972                if (settings.tabs != null)
1973                    buildNavTabsAndTabContentForTargetElementInstance($targetEl, settings, readyCallback);
1974            });
1975        },
1976
1977        refresh: function (options) {
1978            var $targetEls = this,
1979                settings = $.extend({}, $.fn.scrollingTabs.defaults, options || {});
1980
1981            return $targetEls.each(function () {
1982                refreshTargetElementInstance($(this), settings);
1983            });
1984        },
1985
1986        scrollToActiveTab: function () {
1987            return this.each(scrollToActiveTab);
1988        }
1989    };
1990
1991
1992    $.fn.scrollingTabs = function (methodOrOptions) {
1993
1994        if (methods[methodOrOptions]) {
1995            return methods[methodOrOptions].apply(this, Array.prototype.slice.call(arguments, 1));
1996        } else if (!methodOrOptions || (typeof methodOrOptions === 'object')) {
1997            return methods.init.apply(this, arguments);
1998        } else {
1999            $.error('Method ' + methodOrOptions + ' does not exist on $.scrollingTabs.');
2000        }
2001    };
2002
2003    $.fn.scrollingTabs.defaults = {
2004        tabs: null,
2005        propPaneId: 'paneId',
2006        propTitle: 'title',
2007        propActive: 'active',
2008        propDisabled: 'disabled',
2009        propContent: 'content',
2010        tabMode: true,
2011        ignoreTabPanes: false,
2012        scrollToTabEdge: false,
2013        disableScrollArrowsOnFullyScrolled: false,
2014        forceActiveTab: false
2015    };
2016
2017
2018
2019}(jQuery, window));

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.