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