tab.js 7 KB
Newer Older
1
import $ from 'jquery'
fat's avatar
tab es6  
fat committed
2
3
4
5
6
import Util from './util'


/**
 * --------------------------------------------------------------------------
Mark Otto's avatar
Mark Otto committed
7
 * Bootstrap (v4.0.0-beta): tab.js
fat's avatar
tab es6  
fat committed
8
9
10
11
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * --------------------------------------------------------------------------
 */

12
const Tab = (() => {
fat's avatar
tab es6  
fat committed
13
14
15
16
17
18
19
20
21


  /**
   * ------------------------------------------------------------------------
   * Constants
   * ------------------------------------------------------------------------
   */

  const NAME                = 'tab'
Mark Otto's avatar
Mark Otto committed
22
  const VERSION             = '4.0.0-beta'
fat's avatar
tab es6  
fat committed
23
  const DATA_KEY            = 'bs.tab'
fat's avatar
fat committed
24
25
  const EVENT_KEY           = `.${DATA_KEY}`
  const DATA_API_KEY        = '.data-api'
fat's avatar
tab es6  
fat committed
26
27
28
29
  const JQUERY_NO_CONFLICT  = $.fn[NAME]
  const TRANSITION_DURATION = 150

  const Event = {
fat's avatar
fat committed
30
31
32
33
34
    HIDE           : `hide${EVENT_KEY}`,
    HIDDEN         : `hidden${EVENT_KEY}`,
    SHOW           : `show${EVENT_KEY}`,
    SHOWN          : `shown${EVENT_KEY}`,
    CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`
fat's avatar
tab es6  
fat committed
35
36
37
38
39
  }

  const ClassName = {
    DROPDOWN_MENU : 'dropdown-menu',
    ACTIVE        : 'active',
40
    DISABLED      : 'disabled',
fat's avatar
tab es6  
fat committed
41
    FADE          : 'fade',
Starsam80's avatar
Starsam80 committed
42
    SHOW          : 'show'
fat's avatar
tab es6  
fat committed
43
44
45
  }

  const Selector = {
46
    DROPDOWN              : '.dropdown',
47
    NAV_LIST_GROUP        : '.nav, .list-group',
48
    ACTIVE                : '.active',
49
    ACTIVE_UL             : '> li > .active',
50
    DATA_TOGGLE           : '[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',
51
52
    DROPDOWN_TOGGLE       : '.dropdown-toggle',
    DROPDOWN_ACTIVE_CHILD : '> .dropdown-menu .active'
fat's avatar
tab es6  
fat committed
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
  }


  /**
   * ------------------------------------------------------------------------
   * Class Definition
   * ------------------------------------------------------------------------
   */

  class Tab {

    constructor(element) {
      this._element = element
    }


    // getters

    static get VERSION() {
      return VERSION
    }


    // public

    show() {
      if (this._element.parentNode &&
80
          this._element.parentNode.nodeType === Node.ELEMENT_NODE &&
81
82
          $(this._element).hasClass(ClassName.ACTIVE) ||
          $(this._element).hasClass(ClassName.DISABLED)) {
fat's avatar
tab es6  
fat committed
83
84
85
86
87
        return
      }

      let target
      let previous
88
      const listElement = $(this._element).closest(Selector.NAV_LIST_GROUP)[0]
89
      const selector    = Util.getSelectorFromElement(this._element)
fat's avatar
tab es6  
fat committed
90

91
      if (listElement) {
92
93
        const itemSelector = listElement.nodeName === 'UL' ? Selector.ACTIVE_UL : Selector.ACTIVE
        previous = $.makeArray($(listElement).find(itemSelector))
fat's avatar
tab es6  
fat committed
94
95
96
        previous = previous[previous.length - 1]
      }

97
      const hideEvent = $.Event(Event.HIDE, {
fat's avatar
tab es6  
fat committed
98
99
100
        relatedTarget: this._element
      })

101
      const showEvent = $.Event(Event.SHOW, {
fat's avatar
tab es6  
fat committed
102
103
104
105
106
107
108
109
110
111
        relatedTarget: previous
      })

      if (previous) {
        $(previous).trigger(hideEvent)
      }

      $(this._element).trigger(showEvent)

      if (showEvent.isDefaultPrevented() ||
112
         hideEvent.isDefaultPrevented()) {
fat's avatar
tab es6  
fat committed
113
114
115
116
117
118
119
120
        return
      }

      if (selector) {
        target = $(selector)[0]
      }

      this._activate(
121
        this._element,
122
        listElement
fat's avatar
tab es6  
fat committed
123
124
      )

125
126
      const complete = () => {
        const hiddenEvent = $.Event(Event.HIDDEN, {
fat's avatar
tab es6  
fat committed
127
128
129
          relatedTarget: this._element
        })

130
        const shownEvent = $.Event(Event.SHOWN, {
fat's avatar
tab es6  
fat committed
131
132
133
134
135
136
137
138
139
140
141
142
143
144
          relatedTarget: previous
        })

        $(previous).trigger(hiddenEvent)
        $(this._element).trigger(shownEvent)
      }

      if (target) {
        this._activate(target, target.parentNode, complete)
      } else {
        complete()
      }
    }

fat's avatar
fat committed
145
    dispose() {
146
      $.removeData(this._element, DATA_KEY)
fat's avatar
fat committed
147
148
149
      this._element = null
    }

fat's avatar
tab es6  
fat committed
150
151
152
153

    // private

    _activate(element, container, callback) {
154
155
156
157
158
159
160
      let activeElements
      if (container.nodeName === 'UL') {
        activeElements = $(container).find(Selector.ACTIVE_UL)
      } else {
        activeElements = $(container).children(Selector.ACTIVE)
      }

161
      const active          = activeElements[0]
162
      const isTransitioning = callback
fat's avatar
tab es6  
fat committed
163
        && Util.supportsTransitionEnd()
164
        && (active && $(active).hasClass(ClassName.FADE))
fat's avatar
tab es6  
fat committed
165

166
      const complete = () => this._transitionComplete(
167
168
169
170
171
        element,
        active,
        isTransitioning,
        callback
      )
fat's avatar
tab es6  
fat committed
172
173
174
175
176
177
178
179
180
181
182

      if (active && isTransitioning) {
        $(active)
          .one(Util.TRANSITION_END, complete)
          .emulateTransitionEnd(TRANSITION_DURATION)

      } else {
        complete()
      }

      if (active) {
Starsam80's avatar
Starsam80 committed
183
        $(active).removeClass(ClassName.SHOW)
fat's avatar
tab es6  
fat committed
184
185
186
187
188
189
190
      }
    }

    _transitionComplete(element, active, isTransitioning, callback) {
      if (active) {
        $(active).removeClass(ClassName.ACTIVE)

191
        const dropdownChild = $(active.parentNode).find(
fat's avatar
tab es6  
fat committed
192
193
          Selector.DROPDOWN_ACTIVE_CHILD
        )[0]
194

fat's avatar
tab es6  
fat committed
195
196
197
198
        if (dropdownChild) {
          $(dropdownChild).removeClass(ClassName.ACTIVE)
        }

199
200
201
        if (active.getAttribute('role') === 'tab') {
          active.setAttribute('aria-selected', false)
        }
fat's avatar
tab es6  
fat committed
202
203
204
      }

      $(element).addClass(ClassName.ACTIVE)
205
206
207
      if (element.getAttribute('role') === 'tab') {
        element.setAttribute('aria-selected', true)
      }
fat's avatar
tab es6  
fat committed
208
209
210

      if (isTransitioning) {
        Util.reflow(element)
Starsam80's avatar
Starsam80 committed
211
        $(element).addClass(ClassName.SHOW)
fat's avatar
tab es6  
fat committed
212
213
214
215
216
      } else {
        $(element).removeClass(ClassName.FADE)
      }

      if (element.parentNode &&
217
          $(element.parentNode).hasClass(ClassName.DROPDOWN_MENU)) {
fat's avatar
tab es6  
fat committed
218

219
        const dropdownElement = $(element).closest(Selector.DROPDOWN)[0]
fat's avatar
tab es6  
fat committed
220
        if (dropdownElement) {
221
          $(dropdownElement).find(Selector.DROPDOWN_TOGGLE).addClass(ClassName.ACTIVE)
fat's avatar
tab es6  
fat committed
222
223
        }

224
        element.setAttribute('aria-expanded', true)
fat's avatar
tab es6  
fat committed
225
226
227
228
229
230
231
232
233
234
235
236
      }

      if (callback) {
        callback()
      }
    }


    // static

    static _jQueryInterface(config) {
      return this.each(function () {
237
238
        const $this = $(this)
        let data    = $this.data(DATA_KEY)
fat's avatar
tab es6  
fat committed
239
240

        if (!data) {
241
          data = new Tab(this)
fat's avatar
tab es6  
fat committed
242
243
244
245
          $this.data(DATA_KEY, data)
        }

        if (typeof config === 'string') {
XhmikosR's avatar
XhmikosR committed
246
          if (typeof data[config] === 'undefined') {
247
248
            throw new Error(`No method named "${config}"`)
          }
fat's avatar
tab es6  
fat committed
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
          data[config]()
        }
      })
    }

  }


  /**
   * ------------------------------------------------------------------------
   * Data Api implementation
   * ------------------------------------------------------------------------
   */

  $(document)
fat's avatar
fat committed
264
    .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {
XhmikosR's avatar
XhmikosR committed
265
266
267
      event.preventDefault()
      Tab._jQueryInterface.call($(this), 'show')
    })
fat's avatar
tab es6  
fat committed
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284


  /**
   * ------------------------------------------------------------------------
   * jQuery
   * ------------------------------------------------------------------------
   */

  $.fn[NAME]             = Tab._jQueryInterface
  $.fn[NAME].Constructor = Tab
  $.fn[NAME].noConflict  = function () {
    $.fn[NAME] = JQUERY_NO_CONFLICT
    return Tab._jQueryInterface
  }

  return Tab

Johann-S's avatar
Johann-S committed
285
})($)
fat's avatar
tab es6  
fat committed
286
287

export default Tab