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.