You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1962 lines
71 KiB

  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 <michaeljjacobson1@gmail.com>
  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 support
  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. ;(function ($, window) {
  287. 'use strict';
  288. /* jshint unused:false */
  289. /* exported CONSTANTS */
  290. var CONSTANTS = {
  291. CONTINUOUS_SCROLLING_TIMEOUT_INTERVAL: 50, // timeout interval for repeatedly moving the tabs container
  292. // by one increment while the mouse is held down--decrease to
  293. // make mousedown continous scrolling faster
  294. SCROLL_OFFSET_FRACTION: 6, // each click moves the container this fraction of the fixed container--decrease
  295. // to make the tabs scroll farther per click
  296. DATA_KEY_DDMENU_MODIFIED: 'scrtabsddmenumodified',
  297. DATA_KEY_IS_MOUSEDOWN: 'scrtabsismousedown',
  298. CSS_CLASSES: {
  299. BOOTSTRAP4: 'scrtabs-bootstrap4',
  300. RTL: 'scrtabs-rtl',
  301. SCROLL_ARROW_CLICK_TARGET: 'scrtabs-click-target',
  302. SCROLL_ARROW_DISABLE: 'scrtabs-disable',
  303. SCROLL_ARROW_WITH_CLICK_TARGET: 'scrtabs-with-click-target'
  304. },
  305. SLIDE_DIRECTION: {
  306. LEFT: 1,
  307. RIGHT: 2
  308. },
  309. EVENTS: {
  310. CLICK: 'click.scrtabs',
  311. DROPDOWN_MENU_HIDE: 'hide.bs.dropdown.scrtabs',
  312. DROPDOWN_MENU_SHOW: 'show.bs.dropdown.scrtabs',
  313. FORCE_REFRESH: 'forcerefresh.scrtabs',
  314. MOUSEDOWN: 'mousedown.scrtabs',
  315. MOUSEUP: 'mouseup.scrtabs',
  316. TABS_READY: 'ready.scrtabs',
  317. TOUCH_END: 'touchend.scrtabs',
  318. TOUCH_MOVE: 'touchmove.scrtabs',
  319. TOUCH_START: 'touchstart.scrtabs',
  320. WINDOW_RESIZE: 'resize.scrtabs'
  321. }
  322. };
  323. // smartresize from Paul Irish (debounced window resize)
  324. (function (sr) {
  325. var debounce = function (func, threshold, execAsap) {
  326. var timeout;
  327. return function debounced() {
  328. var obj = this, args = arguments;
  329. function delayed() {
  330. if (!execAsap) {
  331. func.apply(obj, args);
  332. }
  333. timeout = null;
  334. }
  335. if (timeout) {
  336. clearTimeout(timeout);
  337. } else if (execAsap) {
  338. func.apply(obj, args);
  339. }
  340. timeout = setTimeout(delayed, threshold || 100);
  341. };
  342. };
  343. $.fn[sr] = function (fn, customEventName) {
  344. var eventName = customEventName || CONSTANTS.EVENTS.WINDOW_RESIZE;
  345. return fn ? this.bind(eventName, debounce(fn)) : this.trigger(sr);
  346. };
  347. })('smartresizeScrtabs');
  348. /* ***********************************************************************************
  349. * ElementsHandler - Class that each instance of ScrollingTabsControl will instantiate
  350. * **********************************************************************************/
  351. function ElementsHandler(scrollingTabsControl) {
  352. var ehd = this;
  353. ehd.stc = scrollingTabsControl;
  354. }
  355. // ElementsHandler prototype methods
  356. (function (p) {
  357. p.initElements = function (options) {
  358. var ehd = this;
  359. ehd.setElementReferences(options);
  360. ehd.setEventListeners(options);
  361. };
  362. p.listenForTouchEvents = function () {
  363. var ehd = this,
  364. stc = ehd.stc,
  365. smv = stc.scrollMovement,
  366. ev = CONSTANTS.EVENTS;
  367. var touching = false;
  368. var touchStartX;
  369. var startingContainerLeftPos;
  370. var newLeftPos;
  371. stc.$movableContainer
  372. .on(ev.TOUCH_START, function (e) {
  373. touching = true;
  374. startingContainerLeftPos = stc.movableContainerLeftPos;
  375. touchStartX = e.originalEvent.changedTouches[0].pageX;
  376. })
  377. .on(ev.TOUCH_END, function () {
  378. touching = false;
  379. })
  380. .on(ev.TOUCH_MOVE, function (e) {
  381. if (!touching) {
  382. return;
  383. }
  384. var touchPageX = e.originalEvent.changedTouches[0].pageX;
  385. var diff = touchPageX - touchStartX;
  386. if (stc.rtl) {
  387. diff = -diff;
  388. }
  389. var minPos;
  390. newLeftPos = startingContainerLeftPos + diff;
  391. if (newLeftPos > 0) {
  392. newLeftPos = 0;
  393. } else {
  394. minPos = smv.getMinPos();
  395. if (newLeftPos < minPos) {
  396. newLeftPos = minPos;
  397. }
  398. }
  399. stc.movableContainerLeftPos = newLeftPos;
  400. var leftOrRight = stc.rtl ? 'right' : 'left';
  401. stc.$movableContainer.css(leftOrRight, smv.getMovableContainerCssLeftVal());
  402. smv.refreshScrollArrowsDisabledState();
  403. });
  404. };
  405. p.refreshAllElementSizes = function () {
  406. var ehd = this,
  407. stc = ehd.stc,
  408. smv = stc.scrollMovement,
  409. scrollArrowsWereVisible = stc.scrollArrowsVisible,
  410. actionsTaken = {
  411. didScrollToActiveTab: false
  412. },
  413. isPerformingSlideAnim = false,
  414. minPos;
  415. ehd.setElementWidths();
  416. ehd.setScrollArrowVisibility();
  417. // this could have been a window resize or the removal of a
  418. // dynamic tab, so make sure the movable container is positioned
  419. // correctly because, if it is far to the left and we increased the
  420. // window width, it's possible that the tabs will be too far left,
  421. // beyond the min pos.
  422. if (stc.scrollArrowsVisible) {
  423. // make sure container not too far left
  424. minPos = smv.getMinPos();
  425. isPerformingSlideAnim = smv.scrollToActiveTab({
  426. isOnWindowResize: true
  427. });
  428. if (!isPerformingSlideAnim) {
  429. smv.refreshScrollArrowsDisabledState();
  430. if (stc.rtl) {
  431. if (stc.movableContainerRightPos < minPos) {
  432. smv.incrementMovableContainerLeft(minPos);
  433. }
  434. } else {
  435. if (stc.movableContainerLeftPos < minPos) {
  436. smv.incrementMovableContainerRight(minPos);
  437. }
  438. }
  439. }
  440. actionsTaken.didScrollToActiveTab = true;
  441. } else if (scrollArrowsWereVisible) {
  442. // scroll arrows went away after resize, so position movable container at 0
  443. stc.movableContainerLeftPos = 0;
  444. smv.slideMovableContainerToLeftPos();
  445. }
  446. return actionsTaken;
  447. };
  448. p.setElementReferences = function (settings) {
  449. var ehd = this,
  450. stc = ehd.stc,
  451. $tabsContainer = stc.$tabsContainer,
  452. $leftArrow,
  453. $rightArrow,
  454. $leftArrowClickTarget,
  455. $rightArrowClickTarget;
  456. stc.isNavPills = false;
  457. if (stc.rtl) {
  458. $tabsContainer.addClass(CONSTANTS.CSS_CLASSES.RTL);
  459. }
  460. if (stc.usingBootstrap4) {
  461. $tabsContainer.addClass(CONSTANTS.CSS_CLASSES.BOOTSTRAP4);
  462. }
  463. stc.$fixedContainer = $tabsContainer.find('.scrtabs-tabs-fixed-container');
  464. $leftArrow = stc.$fixedContainer.prev();
  465. $rightArrow = stc.$fixedContainer.next();
  466. // if we have custom arrow content, we might have a click target defined
  467. if (settings.leftArrowContent) {
  468. $leftArrowClickTarget = $leftArrow.find('.' + CONSTANTS.CSS_CLASSES.SCROLL_ARROW_CLICK_TARGET);
  469. }
  470. if (settings.rightArrowContent) {
  471. $rightArrowClickTarget = $rightArrow.find('.' + CONSTANTS.CSS_CLASSES.SCROLL_ARROW_CLICK_TARGET);
  472. }
  473. if ($leftArrowClickTarget && $leftArrowClickTarget.length) {
  474. $leftArrow.addClass(CONSTANTS.CSS_CLASSES.SCROLL_ARROW_WITH_CLICK_TARGET);
  475. } else {
  476. $leftArrowClickTarget = $leftArrow;
  477. }
  478. if ($rightArrowClickTarget && $rightArrowClickTarget.length) {
  479. $rightArrow.addClass(CONSTANTS.CSS_CLASSES.SCROLL_ARROW_WITH_CLICK_TARGET);
  480. } else {
  481. $rightArrowClickTarget = $rightArrow;
  482. }
  483. stc.$movableContainer = $tabsContainer.find('.scrtabs-tabs-movable-container');
  484. stc.$tabsUl = $tabsContainer.find('.nav-tabs');
  485. // check for pills
  486. if (!stc.$tabsUl.length) {
  487. stc.$tabsUl = $tabsContainer.find('.nav-pills');
  488. if (stc.$tabsUl.length) {
  489. stc.isNavPills = true;
  490. }
  491. }
  492. stc.$tabsLiCollection = stc.$tabsUl.find('> li');
  493. stc.$slideLeftArrow = stc.reverseScroll ? $leftArrow : $rightArrow;
  494. stc.$slideLeftArrowClickTarget = stc.reverseScroll ? $leftArrowClickTarget : $rightArrowClickTarget;
  495. stc.$slideRightArrow = stc.reverseScroll ? $rightArrow : $leftArrow;
  496. stc.$slideRightArrowClickTarget = stc.reverseScroll ? $rightArrowClickTarget : $leftArrowClickTarget;
  497. stc.$scrollArrows = stc.$slideLeftArrow.add(stc.$slideRightArrow);
  498. stc.$win = $(window);
  499. };
  500. p.setElementWidths = function () {
  501. var ehd = this,
  502. stc = ehd.stc;
  503. stc.winWidth = stc.$win.width();
  504. stc.scrollArrowsCombinedWidth = stc.$slideLeftArrow.outerWidth() + stc.$slideRightArrow.outerWidth();
  505. ehd.setFixedContainerWidth();
  506. ehd.setMovableContainerWidth();
  507. };
  508. p.setEventListeners = function (settings) {
  509. var ehd = this,
  510. stc = ehd.stc,
  511. evh = stc.eventHandlers,
  512. ev = CONSTANTS.EVENTS,
  513. resizeEventName = ev.WINDOW_RESIZE + stc.instanceId;
  514. if (settings.enableSwiping) {
  515. ehd.listenForTouchEvents();
  516. }
  517. stc.$slideLeftArrowClickTarget
  518. .off('.scrtabs')
  519. .on(ev.MOUSEDOWN, function (e) { evh.handleMousedownOnSlideMovContainerLeftArrow.call(evh, e); })
  520. .on(ev.MOUSEUP, function (e) { evh.handleMouseupOnSlideMovContainerLeftArrow.call(evh, e); })
  521. .on(ev.CLICK, function (e) { evh.handleClickOnSlideMovContainerLeftArrow.call(evh, e); });
  522. stc.$slideRightArrowClickTarget
  523. .off('.scrtabs')
  524. .on(ev.MOUSEDOWN, function (e) { evh.handleMousedownOnSlideMovContainerRightArrow.call(evh, e); })
  525. .on(ev.MOUSEUP, function (e) { evh.handleMouseupOnSlideMovContainerRightArrow.call(evh, e); })
  526. .on(ev.CLICK, function (e) { evh.handleClickOnSlideMovContainerRightArrow.call(evh, e); });
  527. if (stc.tabClickHandler) {
  528. stc.$tabsLiCollection
  529. .find('a[data-toggle="tab"]')
  530. .off(ev.CLICK)
  531. .on(ev.CLICK, stc.tabClickHandler);
  532. }
  533. stc.$win
  534. .off(resizeEventName)
  535. .smartresizeScrtabs(function (e) { evh.handleWindowResize.call(evh, e); }, resizeEventName);
  536. $('body').on(CONSTANTS.EVENTS.FORCE_REFRESH, stc.elementsHandler.refreshAllElementSizes.bind(stc.elementsHandler));
  537. };
  538. p.setFixedContainerWidth = function () {
  539. var ehd = this,
  540. stc = ehd.stc,
  541. tabsContainerRect = stc.$tabsContainer.get(0).getBoundingClientRect();
  542. /**
  543. * @author poletaew
  544. * It solves problem with rounding by jQuery.outerWidth
  545. * If we have real width 100.5 px, jQuery.outerWidth returns us 101 px and we get layout's fail
  546. */
  547. stc.fixedContainerWidth = tabsContainerRect.width || (tabsContainerRect.right - tabsContainerRect.left);
  548. stc.fixedContainerWidth = stc.fixedContainerWidth * stc.widthMultiplier;
  549. stc.$fixedContainer.width(stc.fixedContainerWidth);
  550. };
  551. p.setFixedContainerWidthForHiddenScrollArrows = function () {
  552. var ehd = this,
  553. stc = ehd.stc;
  554. stc.$fixedContainer.width(stc.fixedContainerWidth);
  555. };
  556. p.setFixedContainerWidthForVisibleScrollArrows = function () {
  557. var ehd = this,
  558. stc = ehd.stc;
  559. stc.$fixedContainer.width(stc.fixedContainerWidth - stc.scrollArrowsCombinedWidth);
  560. };
  561. p.setMovableContainerWidth = function () {
  562. var ehd = this,
  563. stc = ehd.stc,
  564. $tabLi = stc.$tabsUl.find('> li');
  565. stc.movableContainerWidth = 0;
  566. if ($tabLi.length) {
  567. $tabLi.each(function () {
  568. var $li = $(this),
  569. totalMargin = 0;
  570. if (stc.isNavPills) { // pills have a margin-left, tabs have no margin
  571. totalMargin = parseInt($li.css('margin-left'), 10) + parseInt($li.css('margin-right'), 10);
  572. }
  573. stc.movableContainerWidth += ($li.outerWidth() + totalMargin);
  574. });
  575. stc.movableContainerWidth += 1;
  576. // if the tabs don't span the width of the page, force the
  577. // movable container width to full page width so the bottom
  578. // border spans the page width instead of just spanning the
  579. // width of the tabs
  580. if (stc.movableContainerWidth < stc.fixedContainerWidth) {
  581. stc.movableContainerWidth = stc.fixedContainerWidth;
  582. }
  583. }
  584. stc.$movableContainer.width(stc.movableContainerWidth);
  585. };
  586. p.setScrollArrowVisibility = function () {
  587. var ehd = this,
  588. stc = ehd.stc,
  589. shouldBeVisible = stc.movableContainerWidth > stc.fixedContainerWidth;
  590. if (shouldBeVisible && !stc.scrollArrowsVisible) {
  591. stc.$scrollArrows.show();
  592. stc.scrollArrowsVisible = true;
  593. } else if (!shouldBeVisible && stc.scrollArrowsVisible) {
  594. stc.$scrollArrows.hide();
  595. stc.scrollArrowsVisible = false;
  596. }
  597. if (stc.scrollArrowsVisible) {
  598. ehd.setFixedContainerWidthForVisibleScrollArrows();
  599. } else {
  600. ehd.setFixedContainerWidthForHiddenScrollArrows();
  601. }
  602. };
  603. }(ElementsHandler.prototype));
  604. /* ***********************************************************************************
  605. * EventHandlers - Class that each instance of ScrollingTabsControl will instantiate
  606. * **********************************************************************************/
  607. function EventHandlers(scrollingTabsControl) {
  608. var evh = this;
  609. evh.stc = scrollingTabsControl;
  610. }
  611. // prototype methods
  612. (function (p){
  613. p.handleClickOnSlideMovContainerLeftArrow = function () {
  614. var evh = this,
  615. stc = evh.stc;
  616. stc.scrollMovement.incrementMovableContainerLeft();
  617. };
  618. p.handleClickOnSlideMovContainerRightArrow = function () {
  619. var evh = this,
  620. stc = evh.stc;
  621. stc.scrollMovement.incrementMovableContainerRight();
  622. };
  623. p.handleMousedownOnSlideMovContainerLeftArrow = function () {
  624. var evh = this,
  625. stc = evh.stc;
  626. stc.$slideLeftArrowClickTarget.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN, true);
  627. stc.scrollMovement.continueSlideMovableContainerLeft();
  628. };
  629. p.handleMousedownOnSlideMovContainerRightArrow = function () {
  630. var evh = this,
  631. stc = evh.stc;
  632. stc.$slideRightArrowClickTarget.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN, true);
  633. stc.scrollMovement.continueSlideMovableContainerRight();
  634. };
  635. p.handleMouseupOnSlideMovContainerLeftArrow = function () {
  636. var evh = this,
  637. stc = evh.stc;
  638. stc.$slideLeftArrowClickTarget.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN, false);
  639. };
  640. p.handleMouseupOnSlideMovContainerRightArrow = function () {
  641. var evh = this,
  642. stc = evh.stc;
  643. stc.$slideRightArrowClickTarget.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN, false);
  644. };
  645. p.handleWindowResize = function () {
  646. var evh = this,
  647. stc = evh.stc,
  648. newWinWidth = stc.$win.width();
  649. if (newWinWidth === stc.winWidth) {
  650. return false;
  651. }
  652. stc.winWidth = newWinWidth;
  653. stc.elementsHandler.refreshAllElementSizes();
  654. };
  655. }(EventHandlers.prototype));
  656. /* ***********************************************************************************
  657. * ScrollMovement - Class that each instance of ScrollingTabsControl will instantiate
  658. * **********************************************************************************/
  659. function ScrollMovement(scrollingTabsControl) {
  660. var smv = this;
  661. smv.stc = scrollingTabsControl;
  662. }
  663. // prototype methods
  664. (function (p) {
  665. p.continueSlideMovableContainerLeft = function () {
  666. var smv = this,
  667. stc = smv.stc;
  668. setTimeout(function() {
  669. if (stc.movableContainerLeftPos <= smv.getMinPos() ||
  670. !stc.$slideLeftArrowClickTarget.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN)) {
  671. return;
  672. }
  673. if (!smv.incrementMovableContainerLeft()) { // haven't reached max left
  674. smv.continueSlideMovableContainerLeft();
  675. }
  676. }, CONSTANTS.CONTINUOUS_SCROLLING_TIMEOUT_INTERVAL);
  677. };
  678. p.continueSlideMovableContainerRight = function () {
  679. var smv = this,
  680. stc = smv.stc;
  681. setTimeout(function() {
  682. if (stc.movableContainerLeftPos >= 0 ||
  683. !stc.$slideRightArrowClickTarget.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN)) {
  684. return;
  685. }
  686. if (!smv.incrementMovableContainerRight()) { // haven't reached max right
  687. smv.continueSlideMovableContainerRight();
  688. }
  689. }, CONSTANTS.CONTINUOUS_SCROLLING_TIMEOUT_INTERVAL);
  690. };
  691. p.decrementMovableContainerLeftPos = function (minPos) {
  692. var smv = this,
  693. stc = smv.stc;
  694. stc.movableContainerLeftPos -= (stc.fixedContainerWidth / CONSTANTS.SCROLL_OFFSET_FRACTION);
  695. if (stc.movableContainerLeftPos < minPos) {
  696. stc.movableContainerLeftPos = minPos;
  697. } else if (stc.scrollToTabEdge) {
  698. smv.setMovableContainerLeftPosToTabEdge(CONSTANTS.SLIDE_DIRECTION.LEFT);
  699. if (stc.movableContainerLeftPos < minPos) {
  700. stc.movableContainerLeftPos = minPos;
  701. }
  702. }
  703. };
  704. p.disableSlideLeftArrow = function () {
  705. var smv = this,
  706. stc = smv.stc;
  707. if (!stc.disableScrollArrowsOnFullyScrolled || !stc.scrollArrowsVisible) {
  708. return;
  709. }
  710. stc.$slideLeftArrow.addClass(CONSTANTS.CSS_CLASSES.SCROLL_ARROW_DISABLE);
  711. };
  712. p.disableSlideRightArrow = function () {
  713. var smv = this,
  714. stc = smv.stc;
  715. if (!stc.disableScrollArrowsOnFullyScrolled || !stc.scrollArrowsVisible) {
  716. return;
  717. }
  718. stc.$slideRightArrow.addClass(CONSTANTS.CSS_CLASSES.SCROLL_ARROW_DISABLE);
  719. };
  720. p.enableSlideLeftArrow = function () {
  721. var smv = this,
  722. stc = smv.stc;
  723. if (!stc.disableScrollArrowsOnFullyScrolled || !stc.scrollArrowsVisible) {
  724. return;
  725. }
  726. stc.$slideLeftArrow.removeClass(CONSTANTS.CSS_CLASSES.SCROLL_ARROW_DISABLE);
  727. };
  728. p.enableSlideRightArrow = function () {
  729. var smv = this,
  730. stc = smv.stc;
  731. if (!stc.disableScrollArrowsOnFullyScrolled || !stc.scrollArrowsVisible) {
  732. return;
  733. }
  734. stc.$slideRightArrow.removeClass(CONSTANTS.CSS_CLASSES.SCROLL_ARROW_DISABLE);
  735. };
  736. p.getMinPos = function () {
  737. var smv = this,
  738. stc = smv.stc;
  739. return stc.scrollArrowsVisible ? (stc.fixedContainerWidth - stc.movableContainerWidth - stc.scrollArrowsCombinedWidth) : 0;
  740. };
  741. p.getMovableContainerCssLeftVal = function () {
  742. var smv = this,
  743. stc = smv.stc;
  744. return (stc.movableContainerLeftPos === 0) ? '0' : stc.movableContainerLeftPos + 'px';
  745. };
  746. p.incrementMovableContainerLeft = function () {
  747. var smv = this,
  748. stc = smv.stc,
  749. minPos = smv.getMinPos();
  750. smv.decrementMovableContainerLeftPos(minPos);
  751. smv.slideMovableContainerToLeftPos();
  752. smv.enableSlideRightArrow();
  753. // return true if we're fully left, false otherwise
  754. return (stc.movableContainerLeftPos === minPos);
  755. };
  756. p.incrementMovableContainerRight = function (minPos) {
  757. var smv = this,
  758. stc = smv.stc;
  759. // if minPos passed in, the movable container was beyond the minPos
  760. if (minPos) {
  761. stc.movableContainerLeftPos = minPos;
  762. } else {
  763. stc.movableContainerLeftPos += (stc.fixedContainerWidth / CONSTANTS.SCROLL_OFFSET_FRACTION);
  764. if (stc.movableContainerLeftPos > 0) {
  765. stc.movableContainerLeftPos = 0;
  766. } else if (stc.scrollToTabEdge) {
  767. smv.setMovableContainerLeftPosToTabEdge(CONSTANTS.SLIDE_DIRECTION.RIGHT);
  768. }
  769. }
  770. smv.slideMovableContainerToLeftPos();
  771. smv.enableSlideLeftArrow();
  772. // return true if we're fully right, false otherwise
  773. // left pos of 0 is the movable container's max position (farthest right)
  774. return (stc.movableContainerLeftPos === 0);
  775. };
  776. p.refreshScrollArrowsDisabledState = function() {
  777. var smv = this,
  778. stc = smv.stc;
  779. if (!stc.disableScrollArrowsOnFullyScrolled || !stc.scrollArrowsVisible) {
  780. return;
  781. }
  782. if (stc.movableContainerLeftPos >= 0) { // movable container fully right
  783. smv.disableSlideRightArrow();
  784. smv.enableSlideLeftArrow();
  785. return;
  786. }
  787. if (stc.movableContainerLeftPos <= smv.getMinPos()) { // fully left
  788. smv.disableSlideLeftArrow();
  789. smv.enableSlideRightArrow();
  790. return;
  791. }
  792. smv.enableSlideLeftArrow();
  793. smv.enableSlideRightArrow();
  794. };
  795. p.scrollToActiveTab = function () {
  796. var smv = this,
  797. stc = smv.stc,
  798. $activeTab,
  799. $activeTabAnchor,
  800. activeTabLeftPos,
  801. activeTabRightPos,
  802. rightArrowLeftPos,
  803. activeTabWidth,
  804. leftPosOffset,
  805. offsetToMiddle,
  806. leftScrollArrowWidth,
  807. rightScrollArrowWidth;
  808. if (!stc.scrollArrowsVisible) {
  809. return;
  810. }
  811. if (stc.usingBootstrap4) {
  812. $activeTabAnchor = stc.$tabsUl.find('li > .nav-link.active');
  813. if ($activeTabAnchor.length) {
  814. $activeTab = $activeTabAnchor.parent();
  815. }
  816. } else {
  817. $activeTab = stc.$tabsUl.find('li.active');
  818. }
  819. if (!$activeTab || !$activeTab.length) {
  820. return;
  821. }
  822. rightScrollArrowWidth = stc.$slideRightArrow.outerWidth();
  823. activeTabWidth = $activeTab.outerWidth();
  824. /**
  825. * @author poletaew
  826. * We need relative offset (depends on $fixedContainer), don't absolute
  827. */
  828. activeTabLeftPos = $activeTab.offset().left - stc.$fixedContainer.offset().left;
  829. activeTabRightPos = activeTabLeftPos + activeTabWidth;
  830. rightArrowLeftPos = stc.fixedContainerWidth - rightScrollArrowWidth;
  831. if (stc.rtl) {
  832. leftScrollArrowWidth = stc.$slideLeftArrow.outerWidth();
  833. if (activeTabLeftPos < 0) { // active tab off left side
  834. stc.movableContainerLeftPos += activeTabLeftPos;
  835. smv.slideMovableContainerToLeftPos();
  836. return true;
  837. } else { // active tab off right side
  838. if (activeTabRightPos > rightArrowLeftPos) {
  839. stc.movableContainerLeftPos += (activeTabRightPos - rightArrowLeftPos) + (2 * rightScrollArrowWidth);
  840. smv.slideMovableContainerToLeftPos();
  841. return true;
  842. }
  843. }
  844. } else {
  845. if (activeTabRightPos > rightArrowLeftPos) { // active tab off right side
  846. leftPosOffset = activeTabRightPos - rightArrowLeftPos + rightScrollArrowWidth;
  847. offsetToMiddle = stc.fixedContainerWidth / 2;
  848. leftPosOffset += offsetToMiddle - (activeTabWidth / 2);
  849. stc.movableContainerLeftPos -= leftPosOffset;
  850. smv.slideMovableContainerToLeftPos();
  851. return true;
  852. } else {
  853. leftScrollArrowWidth = stc.$slideLeftArrow.outerWidth();
  854. if (activeTabLeftPos < 0) { // active tab off left side
  855. offsetToMiddle = stc.fixedContainerWidth / 2;
  856. stc.movableContainerLeftPos += (-activeTabLeftPos) + offsetToMiddle - (activeTabWidth / 2);
  857. smv.slideMovableContainerToLeftPos();
  858. return true;
  859. }
  860. }
  861. }
  862. return false;
  863. };
  864. p.setMovableContainerLeftPosToTabEdge = function (slideDirection) {
  865. var smv = this,
  866. stc = smv.stc,
  867. offscreenWidth = -stc.movableContainerLeftPos,
  868. totalTabWidth = 0;
  869. // make sure LeftPos is set so that a tab edge will be against the
  870. // left scroll arrow so we won't have a partial, cut-off tab
  871. stc.$tabsLiCollection.each(function () {
  872. var tabWidth = $(this).width();
  873. totalTabWidth += tabWidth;
  874. if (totalTabWidth > offscreenWidth) {
  875. stc.movableContainerLeftPos = (slideDirection === CONSTANTS.SLIDE_DIRECTION.RIGHT) ? -(totalTabWidth - tabWidth) : -totalTabWidth;
  876. return false; // exit .each() loop
  877. }
  878. });
  879. };
  880. p.slideMovableContainerToLeftPos = function () {
  881. var smv = this,
  882. stc = smv.stc,
  883. minPos = smv.getMinPos(),
  884. leftOrRightVal;
  885. if (stc.movableContainerLeftPos > 0) {
  886. stc.movableContainerLeftPos = 0;
  887. } else if (stc.movableContainerLeftPos < minPos) {
  888. stc.movableContainerLeftPos = minPos;
  889. }
  890. stc.movableContainerLeftPos = stc.movableContainerLeftPos / 1;
  891. leftOrRightVal = smv.getMovableContainerCssLeftVal();
  892. smv.performingSlideAnim = true;
  893. var targetPos = stc.rtl ? { right: leftOrRightVal } : { left: leftOrRightVal };
  894. stc.$movableContainer.stop().animate(targetPos, 'slow', function __slideAnimComplete() {
  895. var newMinPos = smv.getMinPos();
  896. smv.performingSlideAnim = false;
  897. // if we slid past the min pos--which can happen if you resize the window
  898. // quickly--move back into position
  899. if (stc.movableContainerLeftPos < newMinPos) {
  900. smv.decrementMovableContainerLeftPos(newMinPos);
  901. targetPos = stc.rtl ? { right: smv.getMovableContainerCssLeftVal() } : { left: smv.getMovableContainerCssLeftVal() };
  902. stc.$movableContainer.stop().animate(targetPos, 'fast', function() {
  903. smv.refreshScrollArrowsDisabledState();
  904. });
  905. } else {
  906. smv.refreshScrollArrowsDisabledState();
  907. }
  908. });
  909. };
  910. }(ScrollMovement.prototype));
  911. /* **********************************************************************
  912. * ScrollingTabsControl - Class that each directive will instantiate
  913. * **********************************************************************/
  914. function ScrollingTabsControl($tabsContainer) {
  915. var stc = this;
  916. stc.$tabsContainer = $tabsContainer;
  917. stc.instanceId = $.fn.scrollingTabs.nextInstanceId++;
  918. stc.movableContainerLeftPos = 0;
  919. stc.scrollArrowsVisible = false;
  920. stc.scrollToTabEdge = false;
  921. stc.disableScrollArrowsOnFullyScrolled = false;
  922. stc.reverseScroll = false;
  923. stc.widthMultiplier = 1;
  924. stc.scrollMovement = new ScrollMovement(stc);
  925. stc.eventHandlers = new EventHandlers(stc);
  926. stc.elementsHandler = new ElementsHandler(stc);
  927. }
  928. // prototype methods
  929. (function (p) {
  930. p.initTabs = function (options, $scroller, readyCallback, attachTabContentToDomCallback) {
  931. var stc = this,
  932. elementsHandler = stc.elementsHandler,
  933. num;
  934. if (options.enableRtlSupport && $('html').attr('dir') === 'rtl') {
  935. stc.rtl = true;
  936. }
  937. if (options.scrollToTabEdge) {
  938. stc.scrollToTabEdge = true;
  939. }
  940. if (options.disableScrollArrowsOnFullyScrolled) {
  941. stc.disableScrollArrowsOnFullyScrolled = true;
  942. }
  943. if (options.reverseScroll) {
  944. stc.reverseScroll = true;
  945. }
  946. if (options.widthMultiplier !== 1) {
  947. num = Number(options.widthMultiplier); // handle string value
  948. if (!isNaN(num)) {
  949. stc.widthMultiplier = num;
  950. }
  951. }
  952. if (options.bootstrapVersion.toString().charAt(0) === '4') {
  953. stc.usingBootstrap4 = true;
  954. }
  955. setTimeout(initTabsAfterTimeout, 100);
  956. function initTabsAfterTimeout() {
  957. var actionsTaken;
  958. // if we're just wrapping non-data-driven tabs, the user might
  959. // have the .nav-tabs hidden to prevent the clunky flash of
  960. // multi-line tabs on page refresh, so we need to make sure
  961. // they're visible before trying to wrap them
  962. $scroller.find('.nav-tabs').show();
  963. elementsHandler.initElements(options);
  964. actionsTaken = elementsHandler.refreshAllElementSizes();
  965. $scroller.css('visibility', 'visible');
  966. if (attachTabContentToDomCallback) {
  967. attachTabContentToDomCallback();
  968. }
  969. if (readyCallback) {
  970. readyCallback();
  971. }
  972. }
  973. };
  974. p.scrollToActiveTab = function(options) {
  975. var stc = this,
  976. smv = stc.scrollMovement;
  977. smv.scrollToActiveTab(options);
  978. };
  979. }(ScrollingTabsControl.prototype));
  980. /* exported buildNavTabsAndTabContentForTargetElementInstance */
  981. var tabElements = (function () {
  982. return {
  983. getElTabPaneForLi: getElTabPaneForLi,
  984. getNewElNavTabs: getNewElNavTabs,
  985. getNewElScrollerElementWrappingNavTabsInstance: getNewElScrollerElementWrappingNavTabsInstance,
  986. getNewElTabAnchor: getNewElTabAnchor,
  987. getNewElTabContent: getNewElTabContent,
  988. getNewElTabLi: getNewElTabLi,
  989. getNewElTabPane: getNewElTabPane
  990. };
  991. ///////////////////
  992. // ---- retrieve existing elements from the DOM ----------
  993. function getElTabPaneForLi($li) {
  994. return $($li.find('a').attr('href'));
  995. }
  996. // ---- create new elements ----------
  997. function getNewElNavTabs() {
  998. return $('<ul class="nav nav-tabs" role="tablist"></ul>');
  999. }
  1000. function getNewElScrollerElementWrappingNavTabsInstance($navTabsInstance, settings) {
  1001. var $tabsContainer = $('<div class="scrtabs-tab-container"></div>'),
  1002. leftArrowContent = settings.leftArrowContent || '<div class="scrtabs-tab-scroll-arrow scrtabs-tab-scroll-arrow-left"><span class="' + settings.cssClassLeftArrow + '"></span></div>',
  1003. $leftArrow = $(leftArrowContent),
  1004. rightArrowContent = settings.rightArrowContent || '<div class="scrtabs-tab-scroll-arrow scrtabs-tab-scroll-arrow-right"><span class="' + settings.cssClassRightArrow + '"></span></div>',
  1005. $rightArrow = $(rightArrowContent),
  1006. $fixedContainer = $('<div class="scrtabs-tabs-fixed-container"></div>'),
  1007. $movableContainer = $('<div class="scrtabs-tabs-movable-container"></div>');
  1008. if (settings.disableScrollArrowsOnFullyScrolled) {
  1009. $leftArrow.add($rightArrow).addClass(CONSTANTS.CSS_CLASSES.SCROLL_ARROW_DISABLE);
  1010. }
  1011. return $tabsContainer
  1012. .append($leftArrow,
  1013. $fixedContainer.append($movableContainer.append($navTabsInstance)),
  1014. $rightArrow);
  1015. }
  1016. function getNewElTabAnchor(tab, propNames) {
  1017. return $('<a role="tab" data-toggle="tab"></a>')
  1018. .attr('href', '#' + tab[propNames.paneId])
  1019. .html(tab[propNames.title]);
  1020. }
  1021. function getNewElTabContent() {
  1022. return $('<div class="tab-content"></div>');
  1023. }
  1024. function getNewElTabLi(tab, propNames, options) {
  1025. var liContent = options.tabLiContent || '<li role="presentation" class=""></li>',
  1026. $li = $(liContent),
  1027. $a = getNewElTabAnchor(tab, propNames).appendTo($li);
  1028. if (tab[propNames.disabled]) {
  1029. $li.addClass('disabled');
  1030. $a.attr('data-toggle', '');
  1031. } else if (options.forceActiveTab && tab[propNames.active]) {
  1032. $li.addClass('active');
  1033. }
  1034. if (options.tabPostProcessor) {
  1035. options.tabPostProcessor($li, $a);
  1036. }
  1037. return $li;
  1038. }
  1039. function getNewElTabPane(tab, propNames, options) {
  1040. var $pane = $('<div role="tabpanel" class="tab-pane"></div>')
  1041. .attr('id', tab[propNames.paneId])
  1042. .html(tab[propNames.content]);
  1043. if (options.forceActiveTab && tab[propNames.active]) {
  1044. $pane.addClass('active');
  1045. }
  1046. return $pane;
  1047. }
  1048. }()); // tabElements
  1049. var tabUtils = (function () {
  1050. return {
  1051. didTabOrderChange: didTabOrderChange,
  1052. getIndexOfClosestEnabledTab: getIndexOfClosestEnabledTab,
  1053. getTabIndexByPaneId: getTabIndexByPaneId,
  1054. storeDataOnLiEl: storeDataOnLiEl
  1055. };
  1056. ///////////////////
  1057. function didTabOrderChange($currTabLis, updatedTabs, propNames) {
  1058. var isTabOrderChanged = false;
  1059. $currTabLis.each(function (currDomIdx) {
  1060. var newIdx = getTabIndexByPaneId(updatedTabs, propNames.paneId, $(this).data('tab')[propNames.paneId]);
  1061. if ((newIdx > -1) && (newIdx !== currDomIdx)) { // tab moved
  1062. isTabOrderChanged = true;
  1063. return false; // exit .each() loop
  1064. }
  1065. });
  1066. return isTabOrderChanged;
  1067. }
  1068. function getIndexOfClosestEnabledTab($currTabLis, startIndex) {
  1069. var lastIndex = $currTabLis.length - 1,
  1070. closestIdx = -1,
  1071. incrementFromStartIndex = 0,
  1072. testIdx = 0;
  1073. // expand out from the current tab looking for an enabled tab;
  1074. // we prefer the tab after us over the tab before
  1075. while ((closestIdx === -1) && (testIdx >= 0)) {
  1076. if ( (((testIdx = startIndex + (++incrementFromStartIndex)) <= lastIndex) &&
  1077. !$currTabLis.eq(testIdx).hasClass('disabled')) ||
  1078. (((testIdx = startIndex - incrementFromStartIndex) >= 0) &&
  1079. !$currTabLis.eq(testIdx).hasClass('disabled')) ) {
  1080. closestIdx = testIdx;
  1081. }
  1082. }
  1083. return closestIdx;
  1084. }
  1085. function getTabIndexByPaneId(tabs, paneIdPropName, paneId) {
  1086. var idx = -1;
  1087. tabs.some(function (tab, i) {
  1088. if (tab[paneIdPropName] === paneId) {
  1089. idx = i;
  1090. return true; // exit loop
  1091. }
  1092. });
  1093. return idx;
  1094. }
  1095. function storeDataOnLiEl($li, tabs, index) {
  1096. $li.data({
  1097. tab: $.extend({}, tabs[index]), // store a clone so we can check for changes
  1098. index: index
  1099. });
  1100. }
  1101. }()); // tabUtils
  1102. function buildNavTabsAndTabContentForTargetElementInstance($targetElInstance, settings, readyCallback) {
  1103. var tabs = settings.tabs,
  1104. propNames = {
  1105. paneId: settings.propPaneId,
  1106. title: settings.propTitle,
  1107. active: settings.propActive,
  1108. disabled: settings.propDisabled,
  1109. content: settings.propContent
  1110. },
  1111. ignoreTabPanes = settings.ignoreTabPanes,
  1112. hasTabContent = tabs.length && tabs[0][propNames.content] !== undefined,
  1113. $navTabs = tabElements.getNewElNavTabs(),
  1114. $tabContent = tabElements.getNewElTabContent(),
  1115. $scroller,
  1116. attachTabContentToDomCallback = ignoreTabPanes ? null : function() {
  1117. $scroller.after($tabContent);
  1118. };
  1119. if (!tabs.length) {
  1120. return;
  1121. }
  1122. tabs.forEach(function(tab, index) {
  1123. var options = {
  1124. forceActiveTab: true,
  1125. tabLiContent: settings.tabsLiContent && settings.tabsLiContent[index],
  1126. tabPostProcessor: settings.tabsPostProcessors && settings.tabsPostProcessors[index]
  1127. };
  1128. tabElements
  1129. .getNewElTabLi(tab, propNames, options)
  1130. .appendTo($navTabs);
  1131. // build the tab panes if we weren't told to ignore them and there's
  1132. // tab content data available
  1133. if (!ignoreTabPanes && hasTabContent) {
  1134. tabElements
  1135. .getNewElTabPane(tab, propNames, options)
  1136. .appendTo($tabContent);
  1137. }
  1138. });
  1139. $scroller = wrapNavTabsInstanceInScroller($navTabs,
  1140. settings,
  1141. readyCallback,
  1142. attachTabContentToDomCallback);
  1143. $scroller.appendTo($targetElInstance);
  1144. $targetElInstance.data({
  1145. scrtabs: {
  1146. tabs: tabs,
  1147. propNames: propNames,
  1148. ignoreTabPanes: ignoreTabPanes,
  1149. hasTabContent: hasTabContent,
  1150. tabsLiContent: settings.tabsLiContent,
  1151. tabsPostProcessors: settings.tabsPostProcessors,
  1152. scroller: $scroller
  1153. }
  1154. });
  1155. // once the nav-tabs are wrapped in the scroller, attach each tab's
  1156. // data to it for reference later; we need to wait till they're
  1157. // wrapped in the scroller because we wrap a *clone* of the nav-tabs
  1158. // we built above, not the original nav-tabs
  1159. $scroller.find('.nav-tabs > li').each(function (index) {
  1160. tabUtils.storeDataOnLiEl($(this), tabs, index);
  1161. });
  1162. return $targetElInstance;
  1163. }
  1164. function wrapNavTabsInstanceInScroller($navTabsInstance, settings, readyCallback, attachTabContentToDomCallback) {
  1165. var $scroller = tabElements.getNewElScrollerElementWrappingNavTabsInstance($navTabsInstance.clone(true), settings), // use clone because we replaceWith later
  1166. scrollingTabsControl = new ScrollingTabsControl($scroller),
  1167. navTabsInstanceData = $navTabsInstance.data('scrtabs');
  1168. if (!navTabsInstanceData) {
  1169. $navTabsInstance.data('scrtabs', {
  1170. scroller: $scroller
  1171. });
  1172. } else {
  1173. navTabsInstanceData.scroller = $scroller;
  1174. }
  1175. $navTabsInstance.replaceWith($scroller.css('visibility', 'hidden'));
  1176. if (settings.tabClickHandler && (typeof settings.tabClickHandler === 'function')) {
  1177. $scroller.hasTabClickHandler = true;
  1178. scrollingTabsControl.tabClickHandler = settings.tabClickHandler;
  1179. }
  1180. $scroller.initTabs = function () {
  1181. scrollingTabsControl.initTabs(settings,
  1182. $scroller,
  1183. readyCallback,
  1184. attachTabContentToDomCallback);
  1185. };
  1186. $scroller.scrollToActiveTab = function() {
  1187. scrollingTabsControl.scrollToActiveTab(settings);
  1188. };
  1189. $scroller.initTabs();
  1190. listenForDropdownMenuTabs($scroller, scrollingTabsControl);
  1191. return $scroller;
  1192. }
  1193. /* exported listenForDropdownMenuTabs,
  1194. refreshTargetElementInstance,
  1195. scrollToActiveTab */
  1196. function checkForTabAdded(refreshData) {
  1197. var updatedTabsArray = refreshData.updatedTabsArray,
  1198. updatedTabsLiContent = refreshData.updatedTabsLiContent || [],
  1199. updatedTabsPostProcessors = refreshData.updatedTabsPostProcessors || [],
  1200. propNames = refreshData.propNames,
  1201. ignoreTabPanes = refreshData.ignoreTabPanes,
  1202. options = refreshData.options,
  1203. $currTabLis = refreshData.$currTabLis,
  1204. $navTabs = refreshData.$navTabs,
  1205. $currTabContentPanesContainer = ignoreTabPanes ? null : refreshData.$currTabContentPanesContainer,
  1206. $currTabContentPanes = ignoreTabPanes ? null : refreshData.$currTabContentPanes,
  1207. isInitTabsRequired = false;
  1208. // make sure each tab in the updated tabs array has a corresponding DOM element
  1209. updatedTabsArray.forEach(function (tab, idx) {
  1210. var $li = $currTabLis.find('a[href="#' + tab[propNames.paneId] + '"]'),
  1211. isTabIdxPastCurrTabs = (idx >= $currTabLis.length),
  1212. $pane;
  1213. if (!$li.length) { // new tab
  1214. isInitTabsRequired = true;
  1215. // add the tab, add its pane (if necessary), and refresh the scroller
  1216. options.tabLiContent = updatedTabsLiContent[idx];
  1217. options.tabPostProcessor = updatedTabsPostProcessors[idx];
  1218. $li = tabElements.getNewElTabLi(tab, propNames, options);
  1219. tabUtils.storeDataOnLiEl($li, updatedTabsArray, idx);
  1220. if (isTabIdxPastCurrTabs) { // append to end of current tabs
  1221. $li.appendTo($navTabs);
  1222. } else { // insert in middle of current tabs
  1223. $li.insertBefore($currTabLis.eq(idx));
  1224. }
  1225. if (!ignoreTabPanes && tab[propNames.content] !== undefined) {
  1226. $pane = tabElements.getNewElTabPane(tab, propNames, options);
  1227. if (isTabIdxPastCurrTabs) { // append to end of current tabs
  1228. $pane.appendTo($currTabContentPanesContainer);
  1229. } else { // insert in middle of current tabs
  1230. $pane.insertBefore($currTabContentPanes.eq(idx));
  1231. }
  1232. }
  1233. }
  1234. });
  1235. return isInitTabsRequired;
  1236. }
  1237. function checkForTabPropertiesUpdated(refreshData) {
  1238. var tabLiData = refreshData.tabLi,
  1239. ignoreTabPanes = refreshData.ignoreTabPanes,
  1240. $li = tabLiData.$li,
  1241. $contentPane = tabLiData.$contentPane,
  1242. origTabData = tabLiData.origTabData,
  1243. newTabData = tabLiData.newTabData,
  1244. propNames = refreshData.propNames,
  1245. isInitTabsRequired = false;
  1246. // update tab title if necessary
  1247. if (origTabData[propNames.title] !== newTabData[propNames.title]) {
  1248. $li.find('a[role="tab"]')
  1249. .html(origTabData[propNames.title] = newTabData[propNames.title]);
  1250. isInitTabsRequired = true;
  1251. }
  1252. // update tab disabled state if necessary
  1253. if (origTabData[propNames.disabled] !== newTabData[propNames.disabled]) {
  1254. if (newTabData[propNames.disabled]) { // enabled -> disabled
  1255. $li.addClass('disabled');
  1256. $li.find('a[role="tab"]').attr('data-toggle', '');
  1257. } else { // disabled -> enabled
  1258. $li.removeClass('disabled');
  1259. $li.find('a[role="tab"]').attr('data-toggle', 'tab');
  1260. }
  1261. origTabData[propNames.disabled] = newTabData[propNames.disabled];
  1262. isInitTabsRequired = true;
  1263. }
  1264. // update tab active state if necessary
  1265. if (refreshData.options.forceActiveTab) {
  1266. // set the active tab based on the tabs array regardless of the current
  1267. // DOM state, which could have been changed by the user clicking a tab
  1268. // without those changes being reflected back to the tab data
  1269. $li[newTabData[propNames.active] ? 'addClass' : 'removeClass']('active');
  1270. $contentPane[newTabData[propNames.active] ? 'addClass' : 'removeClass']('active');
  1271. origTabData[propNames.active] = newTabData[propNames.active];
  1272. isInitTabsRequired = true;
  1273. }
  1274. // update tab content pane if necessary
  1275. if (!ignoreTabPanes && origTabData[propNames.content] !== newTabData[propNames.content]) {
  1276. $contentPane.html(origTabData[propNames.content] = newTabData[propNames.content]);
  1277. isInitTabsRequired = true;
  1278. }
  1279. return isInitTabsRequired;
  1280. }
  1281. function checkForTabRemoved(refreshData) {
  1282. var tabLiData = refreshData.tabLi,
  1283. ignoreTabPanes = refreshData.ignoreTabPanes,
  1284. $li = tabLiData.$li,
  1285. idxToMakeActive;
  1286. if (tabLiData.newIdx !== -1) { // tab was not removed--it has a valid index
  1287. return false;
  1288. }
  1289. // if this was the active tab, make the closest enabled tab active
  1290. if ($li.hasClass('active')) {
  1291. idxToMakeActive = tabUtils.getIndexOfClosestEnabledTab(refreshData.$currTabLis, tabLiData.currDomIdx);
  1292. if (idxToMakeActive > -1) {
  1293. refreshData.$currTabLis
  1294. .eq(idxToMakeActive)
  1295. .addClass('active');
  1296. if (!ignoreTabPanes) {
  1297. refreshData.$currTabContentPanes
  1298. .eq(idxToMakeActive)
  1299. .addClass('active');
  1300. }
  1301. }
  1302. }
  1303. $li.remove();
  1304. if (!ignoreTabPanes) {
  1305. tabLiData.$contentPane.remove();
  1306. }
  1307. return true;
  1308. }
  1309. function checkForTabsOrderChanged(refreshData) {
  1310. var $currTabLis = refreshData.$currTabLis,
  1311. updatedTabsArray = refreshData.updatedTabsArray,
  1312. propNames = refreshData.propNames,
  1313. ignoreTabPanes = refreshData.ignoreTabPanes,
  1314. newTabsCollection = [],
  1315. newTabPanesCollection = ignoreTabPanes ? null : [];
  1316. if (!tabUtils.didTabOrderChange($currTabLis, updatedTabsArray, propNames)) {
  1317. return false;
  1318. }
  1319. // the tab order changed...
  1320. updatedTabsArray.forEach(function (t) {
  1321. var paneId = t[propNames.paneId];
  1322. newTabsCollection.push(
  1323. $currTabLis
  1324. .find('a[role="tab"][href="#' + paneId + '"]')
  1325. .parent('li')
  1326. );
  1327. if (!ignoreTabPanes) {
  1328. newTabPanesCollection.push($('#' + paneId));
  1329. }
  1330. });
  1331. refreshData.$navTabs.append(newTabsCollection);
  1332. if (!ignoreTabPanes) {
  1333. refreshData.$currTabContentPanesContainer.append(newTabPanesCollection);
  1334. }
  1335. return true;
  1336. }
  1337. function checkForTabsRemovedOrUpdated(refreshData) {
  1338. var $currTabLis = refreshData.$currTabLis,
  1339. updatedTabsArray = refreshData.updatedTabsArray,
  1340. propNames = refreshData.propNames,
  1341. isInitTabsRequired = false;
  1342. $currTabLis.each(function (currDomIdx) {
  1343. var $li = $(this),
  1344. origTabData = $li.data('tab'),
  1345. newIdx = tabUtils.getTabIndexByPaneId(updatedTabsArray, propNames.paneId, origTabData[propNames.paneId]),
  1346. newTabData = (newIdx > -1) ? updatedTabsArray[newIdx] : null;
  1347. refreshData.tabLi = {
  1348. $li: $li,
  1349. currDomIdx: currDomIdx,
  1350. newIdx: newIdx,
  1351. $contentPane: tabElements.getElTabPaneForLi($li),
  1352. origTabData: origTabData,
  1353. newTabData: newTabData
  1354. };
  1355. if (checkForTabRemoved(refreshData)) {
  1356. isInitTabsRequired = true;
  1357. return; // continue to next $li in .each() since we removed this tab
  1358. }
  1359. if (checkForTabPropertiesUpdated(refreshData)) {
  1360. isInitTabsRequired = true;
  1361. }
  1362. });
  1363. return isInitTabsRequired;
  1364. }
  1365. function listenForDropdownMenuTabs($scroller, stc) {
  1366. var $ddMenu;
  1367. // for dropdown menus to show, we need to move them out of the
  1368. // scroller and append them to the body
  1369. $scroller
  1370. .on(CONSTANTS.EVENTS.DROPDOWN_MENU_SHOW, handleDropdownShow)
  1371. .on(CONSTANTS.EVENTS.DROPDOWN_MENU_HIDE, handleDropdownHide);
  1372. function handleDropdownHide(e) {
  1373. // move the dropdown menu back into its tab
  1374. $(e.target).append($ddMenu.off(CONSTANTS.EVENTS.CLICK));
  1375. }
  1376. function handleDropdownShow(e) {
  1377. var $ddParentTabLi = $(e.target),
  1378. ddLiOffset = $ddParentTabLi.offset(),
  1379. $currActiveTab = $scroller.find('li[role="presentation"].active'),
  1380. ddMenuRightX,
  1381. tabsContainerMaxX,
  1382. ddMenuTargetLeft;
  1383. $ddMenu = $ddParentTabLi
  1384. .find('.dropdown-menu')
  1385. .attr('data-' + CONSTANTS.DATA_KEY_DDMENU_MODIFIED, true);
  1386. // if the dropdown's parent tab li isn't already active,
  1387. // we need to deactivate any active menu item in the dropdown
  1388. if ($currActiveTab[0] !== $ddParentTabLi[0]) {
  1389. $ddMenu.find('li.active').removeClass('active');
  1390. }
  1391. // we need to do our own click handling because the built-in
  1392. // bootstrap handlers won't work since we moved the dropdown
  1393. // menu outside the tabs container
  1394. $ddMenu.on(CONSTANTS.EVENTS.CLICK, 'a[role="tab"]', handleClickOnDropdownMenuItem);
  1395. $('body').append($ddMenu);
  1396. // make sure the menu doesn't go off the right side of the page
  1397. ddMenuRightX = $ddMenu.width() + ddLiOffset.left;
  1398. tabsContainerMaxX = $scroller.width() - (stc.$slideRightArrow.outerWidth() + 1);
  1399. ddMenuTargetLeft = ddLiOffset.left;
  1400. if (ddMenuRightX > tabsContainerMaxX) {
  1401. ddMenuTargetLeft -= (ddMenuRightX - tabsContainerMaxX);
  1402. }
  1403. $ddMenu.css({
  1404. 'display': 'block',
  1405. 'top': ddLiOffset.top + $ddParentTabLi.outerHeight() - 2,
  1406. 'left': ddMenuTargetLeft
  1407. });
  1408. function handleClickOnDropdownMenuItem() {
  1409. /* jshint validthis: true */
  1410. var $selectedMenuItemAnc = $(this),
  1411. $selectedMenuItemLi = $selectedMenuItemAnc.parent('li'),
  1412. $selectedMenuItemDropdownMenu = $selectedMenuItemLi.parent('.dropdown-menu'),
  1413. targetPaneId = $selectedMenuItemAnc.attr('href');
  1414. if ($selectedMenuItemLi.hasClass('active')) {
  1415. return;
  1416. }
  1417. // once we select a menu item from the dropdown, deactivate
  1418. // the current tab (unless it's our parent tab), deactivate
  1419. // any active dropdown menu item, make our parent tab active
  1420. // (if it's not already), and activate the selected menu item
  1421. $scroller
  1422. .find('li.active')
  1423. .not($ddParentTabLi)
  1424. .add($selectedMenuItemDropdownMenu.find('li.active'))
  1425. .removeClass('active');
  1426. $ddParentTabLi
  1427. .add($selectedMenuItemLi)
  1428. .addClass('active');
  1429. // manually deactivate current active pane and activate our pane
  1430. $('.tab-content .tab-pane.active').removeClass('active');
  1431. $(targetPaneId).addClass('active');
  1432. }
  1433. }
  1434. }
  1435. function refreshDataDrivenTabs($container, options) {
  1436. var instanceData = $container.data().scrtabs,
  1437. scroller = instanceData.scroller,
  1438. $navTabs = $container.find('.scrtabs-tab-container .nav-tabs'),
  1439. $currTabContentPanesContainer = $container.find('.tab-content'),
  1440. isInitTabsRequired = false,
  1441. refreshData = {
  1442. options: options,
  1443. updatedTabsArray: instanceData.tabs,
  1444. updatedTabsLiContent: instanceData.tabsLiContent,
  1445. updatedTabsPostProcessors: instanceData.tabsPostProcessors,
  1446. propNames: instanceData.propNames,
  1447. ignoreTabPanes: instanceData.ignoreTabPanes,
  1448. $navTabs: $navTabs,
  1449. $currTabLis: $navTabs.find('> li'),
  1450. $currTabContentPanesContainer: $currTabContentPanesContainer,
  1451. $currTabContentPanes: $currTabContentPanesContainer.find('.tab-pane')
  1452. };
  1453. // to preserve the tab positions if we're just adding or removing
  1454. // a tab, don't completely rebuild the tab structure, but check
  1455. // for differences between the new tabs array and the old
  1456. if (checkForTabAdded(refreshData)) {
  1457. isInitTabsRequired = true;
  1458. }
  1459. if (checkForTabsOrderChanged(refreshData)) {
  1460. isInitTabsRequired = true;
  1461. }
  1462. if (checkForTabsRemovedOrUpdated(refreshData)) {
  1463. isInitTabsRequired = true;
  1464. }
  1465. if (isInitTabsRequired) {
  1466. scroller.initTabs();
  1467. }
  1468. return isInitTabsRequired;
  1469. }
  1470. function refreshTargetElementInstance($container, options) {
  1471. if (!$container.data('scrtabs')) { // target element doesn't have plugin on it
  1472. return;
  1473. }
  1474. // force a refresh if the tabs are static html or they're data-driven
  1475. // but the data didn't change so we didn't call initTabs()
  1476. if ($container.data('scrtabs').isWrapperOnly || !refreshDataDrivenTabs($container, options)) {
  1477. $('body').trigger(CONSTANTS.EVENTS.FORCE_REFRESH);
  1478. }
  1479. }
  1480. function scrollToActiveTab() {
  1481. /* jshint validthis: true */
  1482. var $targetElInstance = $(this),
  1483. scrtabsData = $targetElInstance.data('scrtabs');
  1484. if (!scrtabsData) {
  1485. return;
  1486. }
  1487. scrtabsData.scroller.scrollToActiveTab();
  1488. }
  1489. var methods = {
  1490. destroy: function() {
  1491. var $targetEls = this;
  1492. return $targetEls.each(destroyPlugin);
  1493. },
  1494. init: function(options) {
  1495. var $targetEls = this,
  1496. targetElsLastIndex = $targetEls.length - 1,
  1497. settings = $.extend({}, $.fn.scrollingTabs.defaults, options || {});
  1498. // ---- tabs NOT data-driven -------------------------
  1499. if (!settings.tabs) {
  1500. // just wrap the selected .nav-tabs element(s) in the scroller
  1501. return $targetEls.each(function(index) {
  1502. var dataObj = {
  1503. isWrapperOnly: true
  1504. },
  1505. $targetEl = $(this).data({ scrtabs: dataObj }),
  1506. readyCallback = (index < targetElsLastIndex) ? null : function() {
  1507. $targetEls.trigger(CONSTANTS.EVENTS.TABS_READY);
  1508. };
  1509. wrapNavTabsInstanceInScroller($targetEl, settings, readyCallback);
  1510. });
  1511. }
  1512. // ---- tabs data-driven -------------------------
  1513. return $targetEls.each(function (index) {
  1514. var $targetEl = $(this),
  1515. readyCallback = (index < targetElsLastIndex) ? null : function() {
  1516. $targetEls.trigger(CONSTANTS.EVENTS.TABS_READY);
  1517. };
  1518. buildNavTabsAndTabContentForTargetElementInstance($targetEl, settings, readyCallback);
  1519. });
  1520. },
  1521. refresh: function(options) {
  1522. var $targetEls = this,
  1523. settings = $.extend({}, $.fn.scrollingTabs.defaults, options || {});
  1524. return $targetEls.each(function () {
  1525. refreshTargetElementInstance($(this), settings);
  1526. });
  1527. },
  1528. scrollToActiveTab: function() {
  1529. return this.each(scrollToActiveTab);
  1530. }
  1531. };
  1532. function destroyPlugin() {
  1533. /* jshint validthis: true */
  1534. var $targetElInstance = $(this),
  1535. scrtabsData = $targetElInstance.data('scrtabs'),
  1536. $tabsContainer;
  1537. if (!scrtabsData) {
  1538. return;
  1539. }
  1540. if (scrtabsData.enableSwipingElement === 'self') {
  1541. $targetElInstance.removeClass(CONSTANTS.CSS_CLASSES.ALLOW_SCROLLBAR);
  1542. } else if (scrtabsData.enableSwipingElement === 'parent') {
  1543. $targetElInstance.closest('.scrtabs-tab-container').parent().removeClass(CONSTANTS.CSS_CLASSES.ALLOW_SCROLLBAR);
  1544. }
  1545. scrtabsData.scroller
  1546. .off(CONSTANTS.EVENTS.DROPDOWN_MENU_SHOW)
  1547. .off(CONSTANTS.EVENTS.DROPDOWN_MENU_HIDE);
  1548. // if there were any dropdown menus opened, remove the css we added to
  1549. // them so they would display correctly
  1550. scrtabsData.scroller
  1551. .find('[data-' + CONSTANTS.DATA_KEY_DDMENU_MODIFIED + ']')
  1552. .css({
  1553. display: '',
  1554. left: '',
  1555. top: ''
  1556. })
  1557. .off(CONSTANTS.EVENTS.CLICK)
  1558. .removeAttr('data-' + CONSTANTS.DATA_KEY_DDMENU_MODIFIED);
  1559. if (scrtabsData.scroller.hasTabClickHandler) {
  1560. $targetElInstance
  1561. .find('a[data-toggle="tab"]')
  1562. .off('.scrtabs');
  1563. }
  1564. if (scrtabsData.isWrapperOnly) { // we just wrapped nav-tabs markup, so restore it
  1565. // $targetElInstance is the ul.nav-tabs
  1566. $tabsContainer = $targetElInstance.parents('.scrtabs-tab-container');
  1567. if ($tabsContainer.length) {
  1568. $tabsContainer.replaceWith($targetElInstance);
  1569. }
  1570. } else { // we generated the tabs from data so destroy everything we created
  1571. if (scrtabsData.scroller && scrtabsData.scroller.initTabs) {
  1572. scrtabsData.scroller.initTabs = null;
  1573. }
  1574. // $targetElInstance is the container for the ul.nav-tabs we generated
  1575. $targetElInstance
  1576. .find('.scrtabs-tab-container')
  1577. .add('.tab-content')
  1578. .remove();
  1579. }
  1580. $targetElInstance.removeData('scrtabs');
  1581. while(--$.fn.scrollingTabs.nextInstanceId >= 0) {
  1582. $(window).off(CONSTANTS.EVENTS.WINDOW_RESIZE + $.fn.scrollingTabs.nextInstanceId);
  1583. }
  1584. $('body').off(CONSTANTS.EVENTS.FORCE_REFRESH);
  1585. }
  1586. $.fn.scrollingTabs = function(methodOrOptions) {
  1587. if (methods[methodOrOptions]) {
  1588. return methods[methodOrOptions].apply(this, Array.prototype.slice.call(arguments, 1));
  1589. } else if (!methodOrOptions || (typeof methodOrOptions === 'object')) {
  1590. return methods.init.apply(this, arguments);
  1591. } else {
  1592. $.error('Method ' + methodOrOptions + ' does not exist on $.scrollingTabs.');
  1593. }
  1594. };
  1595. $.fn.scrollingTabs.nextInstanceId = 0;
  1596. $.fn.scrollingTabs.defaults = {
  1597. tabs: null,
  1598. propPaneId: 'paneId',
  1599. propTitle: 'title',
  1600. propActive: 'active',
  1601. propDisabled: 'disabled',
  1602. propContent: 'content',
  1603. ignoreTabPanes: false,
  1604. scrollToTabEdge: false,
  1605. disableScrollArrowsOnFullyScrolled: false,
  1606. forceActiveTab: false,
  1607. reverseScroll: false,
  1608. widthMultiplier: 1,
  1609. tabClickHandler: null,
  1610. cssClassLeftArrow: 'fa fa-chevron-left',
  1611. cssClassRightArrow: 'fa fa-chevron-right',
  1612. leftArrowContent: '',
  1613. rightArrowContent: '',
  1614. tabsLiContent: null,
  1615. tabsPostProcessors: null,
  1616. enableSwiping: false,
  1617. enableRtlSupport: false,
  1618. bootstrapVersion: 4
  1619. };
  1620. }(jQuery, window));