collapse.js 10.2 KB
Newer Older
fat's avatar
fat committed
1
2
/**
 * --------------------------------------------------------------------------
XhmikosR's avatar
XhmikosR committed
3
 * Bootstrap (v4.3.1): collapse.js
fat's avatar
fat committed
4
5
6
7
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * --------------------------------------------------------------------------
 */

8
9
10
import $ from 'jquery'
import Util from './util'

Johann-S's avatar
Johann-S committed
11
12
13
14
15
/**
 * ------------------------------------------------------------------------
 * Constants
 * ------------------------------------------------------------------------
 */
fat's avatar
fat committed
16

Johann-S's avatar
Johann-S committed
17
const NAME                = 'collapse'
XhmikosR's avatar
XhmikosR committed
18
const VERSION             = '4.3.1'
Johann-S's avatar
Johann-S committed
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
const DATA_KEY            = 'bs.collapse'
const EVENT_KEY           = `.${DATA_KEY}`
const DATA_API_KEY        = '.data-api'
const JQUERY_NO_CONFLICT  = $.fn[NAME]

const Default = {
  toggle : true,
  parent : ''
}

const DefaultType = {
  toggle : 'boolean',
  parent : '(string|element)'
}

const Event = {
  SHOW           : `show${EVENT_KEY}`,
  SHOWN          : `shown${EVENT_KEY}`,
  HIDE           : `hide${EVENT_KEY}`,
  HIDDEN         : `hidden${EVENT_KEY}`,
  CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`
}

const ClassName = {
  SHOW       : 'show',
  COLLAPSE   : 'collapse',
  COLLAPSING : 'collapsing',
  COLLAPSED  : 'collapsed'
}

const Dimension = {
  WIDTH  : 'width',
  HEIGHT : 'height'
}

const Selector = {
  ACTIVES     : '.show, .collapsing',
  DATA_TOGGLE : '[data-toggle="collapse"]'
}
fat's avatar
fat committed
58

Johann-S's avatar
Johann-S committed
59
60
61
62
63
/**
 * ------------------------------------------------------------------------
 * Class Definition
 * ------------------------------------------------------------------------
 */
fat's avatar
fat committed
64

Johann-S's avatar
Johann-S committed
65
66
67
68
69
class Collapse {
  constructor(element, config) {
    this._isTransitioning = false
    this._element         = element
    this._config          = this._getConfig(config)
70
    this._triggerArray    = [].slice.call(document.querySelectorAll(
Johann-S's avatar
Johann-S committed
71
72
73
      `[data-toggle="collapse"][href="#${element.id}"],` +
      `[data-toggle="collapse"][data-target="#${element.id}"]`
    ))
74

Johann-S's avatar
Johann-S committed
75
76
77
78
79
80
81
82
83
84
    const toggleList = [].slice.call(document.querySelectorAll(Selector.DATA_TOGGLE))
    for (let i = 0, len = toggleList.length; i < len; i++) {
      const elem = toggleList[i]
      const selector = Util.getSelectorFromElement(elem)
      const filterElement = [].slice.call(document.querySelectorAll(selector))
        .filter((foundElem) => foundElem === element)

      if (selector !== null && filterElement.length > 0) {
        this._selector = selector
        this._triggerArray.push(elem)
85
      }
Johann-S's avatar
Johann-S committed
86
    }
87

Johann-S's avatar
Johann-S committed
88
    this._parent = this._config.parent ? this._getParent() : null
fat's avatar
fat committed
89

Johann-S's avatar
Johann-S committed
90
91
92
    if (!this._config.parent) {
      this._addAriaAndCollapsedClass(this._element, this._triggerArray)
    }
fat's avatar
fat committed
93

Johann-S's avatar
Johann-S committed
94
95
    if (this._config.toggle) {
      this.toggle()
96
    }
Johann-S's avatar
Johann-S committed
97
  }
98

Johann-S's avatar
Johann-S committed
99
  // Getters
fat's avatar
fat committed
100

Johann-S's avatar
Johann-S committed
101
102
103
  static get VERSION() {
    return VERSION
  }
fat's avatar
fat committed
104

Johann-S's avatar
Johann-S committed
105
106
107
  static get Default() {
    return Default
  }
108

Johann-S's avatar
Johann-S committed
109
  // Public
fat's avatar
fat committed
110

Johann-S's avatar
Johann-S committed
111
112
113
114
115
  toggle() {
    if ($(this._element).hasClass(ClassName.SHOW)) {
      this.hide()
    } else {
      this.show()
fat's avatar
fat committed
116
    }
Johann-S's avatar
Johann-S committed
117
  }
fat's avatar
fat committed
118

Johann-S's avatar
Johann-S committed
119
120
121
122
123
  show() {
    if (this._isTransitioning ||
      $(this._element).hasClass(ClassName.SHOW)) {
      return
    }
fat's avatar
fat committed
124

Johann-S's avatar
Johann-S committed
125
126
    let actives
    let activesData
127

Johann-S's avatar
Johann-S committed
128
129
130
131
132
133
    if (this._parent) {
      actives = [].slice.call(this._parent.querySelectorAll(Selector.ACTIVES))
        .filter((elem) => {
          if (typeof this._config.parent === 'string') {
            return elem.getAttribute('data-parent') === this._config.parent
          }
134

Johann-S's avatar
Johann-S committed
135
136
          return elem.classList.contains(ClassName.COLLAPSE)
        })
fat's avatar
fat committed
137

Johann-S's avatar
Johann-S committed
138
139
      if (actives.length === 0) {
        actives = null
fat's avatar
fat committed
140
      }
Johann-S's avatar
Johann-S committed
141
    }
fat's avatar
fat committed
142

Johann-S's avatar
Johann-S committed
143
144
145
    if (actives) {
      activesData = $(actives).not(this._selector).data(DATA_KEY)
      if (activesData && activesData._isTransitioning) {
fat's avatar
fat committed
146
147
        return
      }
Johann-S's avatar
Johann-S committed
148
    }
fat's avatar
fat committed
149

Johann-S's avatar
Johann-S committed
150
151
152
153
154
155
156
157
158
159
    const startEvent = $.Event(Event.SHOW)
    $(this._element).trigger(startEvent)
    if (startEvent.isDefaultPrevented()) {
      return
    }

    if (actives) {
      Collapse._jQueryInterface.call($(actives).not(this._selector), 'hide')
      if (!activesData) {
        $(actives).data(DATA_KEY, null)
fat's avatar
fat committed
160
      }
Johann-S's avatar
Johann-S committed
161
    }
fat's avatar
fat committed
162

Johann-S's avatar
Johann-S committed
163
    const dimension = this._getDimension()
fat's avatar
fat committed
164

Johann-S's avatar
Johann-S committed
165
166
167
    $(this._element)
      .removeClass(ClassName.COLLAPSE)
      .addClass(ClassName.COLLAPSING)
fat's avatar
fat committed
168

Johann-S's avatar
Johann-S committed
169
    this._element.style[dimension] = 0
fat's avatar
fat committed
170

Johann-S's avatar
Johann-S committed
171
172
173
174
175
176
177
    if (this._triggerArray.length) {
      $(this._triggerArray)
        .removeClass(ClassName.COLLAPSED)
        .attr('aria-expanded', true)
    }

    this.setTransitioning(true)
fat's avatar
fat committed
178

Johann-S's avatar
Johann-S committed
179
180
181
182
183
    const complete = () => {
      $(this._element)
        .removeClass(ClassName.COLLAPSING)
        .addClass(ClassName.COLLAPSE)
        .addClass(ClassName.SHOW)
fat's avatar
fat committed
184

Johann-S's avatar
Johann-S committed
185
      this._element.style[dimension] = ''
fat's avatar
fat committed
186

Johann-S's avatar
Johann-S committed
187
      this.setTransitioning(false)
fat's avatar
fat committed
188

Johann-S's avatar
Johann-S committed
189
190
      $(this._element).trigger(Event.SHOWN)
    }
fat's avatar
fat committed
191

Johann-S's avatar
Johann-S committed
192
193
194
    const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)
    const scrollSize = `scroll${capitalizedDimension}`
    const transitionDuration = Util.getTransitionDurationFromElement(this._element)
fat's avatar
fat committed
195

Johann-S's avatar
Johann-S committed
196
197
    $(this._element)
      .one(Util.TRANSITION_END, complete)
fat's avatar
fat committed
198

Johann-S's avatar
Johann-S committed
199
    Util.emulateTransitionEnd(transitionDuration)
Johann-S's avatar
Johann-S committed
200
201
    this._element.style[dimension] = `${this._element[scrollSize]}px`
  }
fat's avatar
fat committed
202

Johann-S's avatar
Johann-S committed
203
204
205
206
  hide() {
    if (this._isTransitioning ||
      !$(this._element).hasClass(ClassName.SHOW)) {
      return
fat's avatar
fat committed
207
208
    }

Johann-S's avatar
Johann-S committed
209
210
211
212
213
    const startEvent = $.Event(Event.HIDE)
    $(this._element).trigger(startEvent)
    if (startEvent.isDefaultPrevented()) {
      return
    }
fat's avatar
fat committed
214

Johann-S's avatar
Johann-S committed
215
    const dimension = this._getDimension()
fat's avatar
fat committed
216

Johann-S's avatar
Johann-S committed
217
    this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`
fat's avatar
fat committed
218

Johann-S's avatar
Johann-S committed
219
    Util.reflow(this._element)
fat's avatar
fat committed
220

Johann-S's avatar
Johann-S committed
221
222
223
224
    $(this._element)
      .addClass(ClassName.COLLAPSING)
      .removeClass(ClassName.COLLAPSE)
      .removeClass(ClassName.SHOW)
fat's avatar
fat committed
225

Johann-S's avatar
Johann-S committed
226
227
228
229
230
    const triggerArrayLength = this._triggerArray.length
    if (triggerArrayLength > 0) {
      for (let i = 0; i < triggerArrayLength; i++) {
        const trigger = this._triggerArray[i]
        const selector = Util.getSelectorFromElement(trigger)
231

Johann-S's avatar
Johann-S committed
232
233
234
235
236
        if (selector !== null) {
          const $elem = $([].slice.call(document.querySelectorAll(selector)))
          if (!$elem.hasClass(ClassName.SHOW)) {
            $(trigger).addClass(ClassName.COLLAPSED)
              .attr('aria-expanded', false)
237
238
          }
        }
fat's avatar
fat committed
239
      }
Johann-S's avatar
Johann-S committed
240
    }
fat's avatar
fat committed
241

Johann-S's avatar
Johann-S committed
242
    this.setTransitioning(true)
243

Johann-S's avatar
Johann-S committed
244
245
    const complete = () => {
      this.setTransitioning(false)
fat's avatar
fat committed
246
      $(this._element)
Johann-S's avatar
Johann-S committed
247
248
249
        .removeClass(ClassName.COLLAPSING)
        .addClass(ClassName.COLLAPSE)
        .trigger(Event.HIDDEN)
fat's avatar
fat committed
250
251
    }

Johann-S's avatar
Johann-S committed
252
253
    this._element.style[dimension] = ''
    const transitionDuration = Util.getTransitionDurationFromElement(this._element)
fat's avatar
fat committed
254

Johann-S's avatar
Johann-S committed
255
256
    $(this._element)
      .one(Util.TRANSITION_END, complete)
Johann-S's avatar
Johann-S committed
257
258

    Util.emulateTransitionEnd(transitionDuration)
Johann-S's avatar
Johann-S committed
259
  }
fat's avatar
fat committed
260

Johann-S's avatar
Johann-S committed
261
262
263
  setTransitioning(isTransitioning) {
    this._isTransitioning = isTransitioning
  }
fat's avatar
fat committed
264

Johann-S's avatar
Johann-S committed
265
266
  dispose() {
    $.removeData(this._element, DATA_KEY)
fat's avatar
fat committed
267

Johann-S's avatar
Johann-S committed
268
269
270
271
272
273
274
275
    this._config          = null
    this._parent          = null
    this._element         = null
    this._triggerArray    = null
    this._isTransitioning = null
  }

  // Private
fat's avatar
fat committed
276

Johann-S's avatar
Johann-S committed
277
278
279
280
  _getConfig(config) {
    config = {
      ...Default,
      ...config
fat's avatar
fat committed
281
    }
Johann-S's avatar
Johann-S committed
282
283
284
285
    config.toggle = Boolean(config.toggle) // Coerce string values
    Util.typeCheckConfig(NAME, config, DefaultType)
    return config
  }
fat's avatar
fat committed
286

Johann-S's avatar
Johann-S committed
287
288
289
290
  _getDimension() {
    const hasWidth = $(this._element).hasClass(Dimension.WIDTH)
    return hasWidth ? Dimension.WIDTH : Dimension.HEIGHT
  }
291

Johann-S's avatar
Johann-S committed
292
293
  _getParent() {
    let parent
294

Johann-S's avatar
Johann-S committed
295
296
297
298
299
300
    if (Util.isElement(this._config.parent)) {
      parent = this._config.parent

      // It's a jQuery object
      if (typeof this._config.parent.jquery !== 'undefined') {
        parent = this._config.parent[0]
301
      }
Johann-S's avatar
Johann-S committed
302
303
304
    } else {
      parent = document.querySelector(this._config.parent)
    }
305

Johann-S's avatar
Johann-S committed
306
307
    const selector =
      `[data-toggle="collapse"][data-parent="${this._config.parent}"]`
fat's avatar
fat committed
308

Johann-S's avatar
Johann-S committed
309
310
311
312
313
314
315
    const children = [].slice.call(parent.querySelectorAll(selector))
    $(children).each((i, element) => {
      this._addAriaAndCollapsedClass(
        Collapse._getTargetFromElement(element),
        [element]
      )
    })
fat's avatar
fat committed
316

Johann-S's avatar
Johann-S committed
317
318
    return parent
  }
fat's avatar
fat committed
319

Johann-S's avatar
Johann-S committed
320
321
  _addAriaAndCollapsedClass(element, triggerArray) {
    const isOpen = $(element).hasClass(ClassName.SHOW)
fat's avatar
fat committed
322

Johann-S's avatar
Johann-S committed
323
324
325
326
    if (triggerArray.length) {
      $(triggerArray)
        .toggleClass(ClassName.COLLAPSED, !isOpen)
        .attr('aria-expanded', isOpen)
fat's avatar
fat committed
327
    }
Johann-S's avatar
Johann-S committed
328
  }
fat's avatar
fat committed
329

Johann-S's avatar
Johann-S committed
330
  // Static
fat's avatar
fat committed
331

Johann-S's avatar
Johann-S committed
332
333
334
335
  static _getTargetFromElement(element) {
    const selector = Util.getSelectorFromElement(element)
    return selector ? document.querySelector(selector) : null
  }
fat's avatar
fat committed
336

Johann-S's avatar
Johann-S committed
337
338
339
340
341
342
343
344
345
  static _jQueryInterface(config) {
    return this.each(function () {
      const $this   = $(this)
      let data      = $this.data(DATA_KEY)
      const _config = {
        ...Default,
        ...$this.data(),
        ...typeof config === 'object' && config ? config : {}
      }
fat's avatar
fat committed
346

Johann-S's avatar
Johann-S committed
347
348
349
      if (!data && _config.toggle && /show|hide/.test(config)) {
        _config.toggle = false
      }
fat's avatar
fat committed
350

Johann-S's avatar
Johann-S committed
351
352
353
354
      if (!data) {
        data = new Collapse(this, _config)
        $this.data(DATA_KEY, data)
      }
fat's avatar
fat committed
355

Johann-S's avatar
Johann-S committed
356
357
358
      if (typeof config === 'string') {
        if (typeof data[config] === 'undefined') {
          throw new TypeError(`No method named "${config}"`)
fat's avatar
fat committed
359
        }
Johann-S's avatar
Johann-S committed
360
361
362
        data[config]()
      }
    })
fat's avatar
fat committed
363
  }
Johann-S's avatar
Johann-S committed
364
}
fat's avatar
fat committed
365

Johann-S's avatar
Johann-S committed
366
367
368
369
370
/**
 * ------------------------------------------------------------------------
 * Data Api implementation
 * ------------------------------------------------------------------------
 */
fat's avatar
fat committed
371

Johann-S's avatar
Johann-S committed
372
373
374
375
376
$(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {
  // preventDefault only for <a> elements (which change the URL) not inside the collapsible element
  if (event.currentTarget.tagName === 'A') {
    event.preventDefault()
  }
fat's avatar
fat committed
377

Johann-S's avatar
Johann-S committed
378
379
380
  const $trigger = $(this)
  const selector = Util.getSelectorFromElement(this)
  const selectors = [].slice.call(document.querySelectorAll(selector))
381

Johann-S's avatar
Johann-S committed
382
383
384
385
386
  $(selectors).each(function () {
    const $target = $(this)
    const data    = $target.data(DATA_KEY)
    const config  = data ? 'toggle' : $trigger.data()
    Collapse._jQueryInterface.call($target, config)
fat's avatar
fat committed
387
  })
Johann-S's avatar
Johann-S committed
388
})
fat's avatar
fat committed
389

Johann-S's avatar
Johann-S committed
390
391
392
393
394
/**
 * ------------------------------------------------------------------------
 * jQuery
 * ------------------------------------------------------------------------
 */
fat's avatar
fat committed
395

Johann-S's avatar
Johann-S committed
396
397
398
399
400
401
$.fn[NAME] = Collapse._jQueryInterface
$.fn[NAME].Constructor = Collapse
$.fn[NAME].noConflict = () => {
  $.fn[NAME] = JQUERY_NO_CONFLICT
  return Collapse._jQueryInterface
}
fat's avatar
fat committed
402
403

export default Collapse