collapse.js 9.6 KB
Newer Older
fat's avatar
fat committed
1
2
3
4
5
import Util from './util'


/**
 * --------------------------------------------------------------------------
Mark Otto's avatar
Mark Otto committed
6
 * Bootstrap (v4.0.0-alpha.6): collapse.js
fat's avatar
fat committed
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * --------------------------------------------------------------------------
 */

const Collapse = (($) => {


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

  const NAME                = 'collapse'
Mark Otto's avatar
Mark Otto committed
21
  const VERSION             = '4.0.0-alpha.6'
fat's avatar
fat committed
22
  const DATA_KEY            = 'bs.collapse'
fat's avatar
fat committed
23
24
  const EVENT_KEY           = `.${DATA_KEY}`
  const DATA_API_KEY        = '.data-api'
fat's avatar
fat committed
25
26
27
  const JQUERY_NO_CONFLICT  = $.fn[NAME]
  const TRANSITION_DURATION = 600

28
  const Default = {
fat's avatar
fat committed
29
    toggle : true,
30
    parent : ''
fat's avatar
fat committed
31
32
  }

fat's avatar
fat committed
33
34
  const DefaultType = {
    toggle : 'boolean',
35
    parent : 'string'
fat's avatar
fat committed
36
37
  }

fat's avatar
fat committed
38
  const Event = {
fat's avatar
fat committed
39
40
41
42
43
    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}`
fat's avatar
fat committed
44
45
46
  }

  const ClassName = {
Starsam80's avatar
Starsam80 committed
47
    SHOW       : 'show',
fat's avatar
fat committed
48
49
50
51
52
53
54
55
56
57
58
    COLLAPSE   : 'collapse',
    COLLAPSING : 'collapsing',
    COLLAPSED  : 'collapsed'
  }

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

  const Selector = {
Starsam80's avatar
Starsam80 committed
59
    ACTIVES     : '.card > .show, .card > .collapsing',
Johann-S's avatar
Johann-S committed
60
61
    DATA_TOGGLE : '[data-toggle="collapse"]',
    DATA_CHILDREN : 'data-children'
fat's avatar
fat committed
62
63
64
65
66
67
68
69
70
71
72
73
74
75
  }


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

  class Collapse {

    constructor(element, config) {
      this._isTransitioning = false
      this._element         = element
fat's avatar
fat committed
76
      this._config          = this._getConfig(config)
fat's avatar
fat committed
77
78
79
80
81
82
83
84
85
86
      this._triggerArray    = $.makeArray($(
        `[data-toggle="collapse"][href="#${element.id}"],` +
        `[data-toggle="collapse"][data-target="#${element.id}"]`
      ))
      this._parent = this._config.parent ? this._getParent() : null

      if (!this._config.parent) {
        this._addAriaAndCollapsedClass(this._element, this._triggerArray)
      }

Johann-S's avatar
Johann-S committed
87
88
89
90
      this._selectorActives = Selector.ACTIVES
      if (this._parent) {
        const childrenSelector = this._parent.hasAttribute(Selector.DATA_CHILDREN) ? this._parent.getAttribute(Selector.DATA_CHILDREN) : null
        if (childrenSelector !== null) {
Johann-S's avatar
Johann-S committed
91
          this._selectorActives = `${childrenSelector} > .show, ${childrenSelector} > .collapsing`
Johann-S's avatar
Johann-S committed
92
93
94
        }
      }

fat's avatar
fat committed
95
96
97
      if (this._config.toggle) {
        this.toggle()
      }
98
99
100
101
    }


    // getters
fat's avatar
fat committed
102

103
104
    static get VERSION() {
      return VERSION
fat's avatar
fat committed
105
106
    }

107
108
109
110
111
    static get Default() {
      return Default
    }


fat's avatar
fat committed
112
113
114
    // public

    toggle() {
Starsam80's avatar
Starsam80 committed
115
      if ($(this._element).hasClass(ClassName.SHOW)) {
fat's avatar
fat committed
116
117
118
119
120
121
122
        this.hide()
      } else {
        this.show()
      }
    }

    show() {
123
124
      if (this._isTransitioning ||
        $(this._element).hasClass(ClassName.SHOW)) {
fat's avatar
fat committed
125
126
127
128
        return
      }

      let actives
fat's avatar
fat committed
129
      let activesData
fat's avatar
fat committed
130
131

      if (this._parent) {
Johann-S's avatar
Johann-S committed
132
        actives = $.makeArray($(this._parent).find(this._selectorActives))
fat's avatar
fat committed
133
134
135
136
137
138
139
140
141
142
143
144
        if (!actives.length) {
          actives = null
        }
      }

      if (actives) {
        activesData = $(actives).data(DATA_KEY)
        if (activesData && activesData._isTransitioning) {
          return
        }
      }

145
      const startEvent = $.Event(Event.SHOW)
fat's avatar
fat committed
146
147
148
149
150
151
152
153
154
155
156
157
      $(this._element).trigger(startEvent)
      if (startEvent.isDefaultPrevented()) {
        return
      }

      if (actives) {
        Collapse._jQueryInterface.call($(actives), 'hide')
        if (!activesData) {
          $(actives).data(DATA_KEY, null)
        }
      }

158
      const dimension = this._getDimension()
fat's avatar
fat committed
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174

      $(this._element)
        .removeClass(ClassName.COLLAPSE)
        .addClass(ClassName.COLLAPSING)

      this._element.style[dimension] = 0
      this._element.setAttribute('aria-expanded', true)

      if (this._triggerArray.length) {
        $(this._triggerArray)
          .removeClass(ClassName.COLLAPSED)
          .attr('aria-expanded', true)
      }

      this.setTransitioning(true)

175
      const complete = () => {
fat's avatar
fat committed
176
177
178
        $(this._element)
          .removeClass(ClassName.COLLAPSING)
          .addClass(ClassName.COLLAPSE)
Starsam80's avatar
Starsam80 committed
179
          .addClass(ClassName.SHOW)
fat's avatar
fat committed
180
181
182
183
184
185
186
187
188
189
190
191
192

        this._element.style[dimension] = ''

        this.setTransitioning(false)

        $(this._element).trigger(Event.SHOWN)
      }

      if (!Util.supportsTransitionEnd()) {
        complete()
        return
      }

193
194
      const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)
      const scrollSize           = `scroll${capitalizedDimension}`
fat's avatar
fat committed
195
196
197
198
199

      $(this._element)
        .one(Util.TRANSITION_END, complete)
        .emulateTransitionEnd(TRANSITION_DURATION)

Jacob Thornton's avatar
Jacob Thornton committed
200
      this._element.style[dimension] = `${this._element[scrollSize]}px`
fat's avatar
fat committed
201
202
203
    }

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

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

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

217
      this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`
fat's avatar
fat committed
218
219
220
221
222
223

      Util.reflow(this._element)

      $(this._element)
        .addClass(ClassName.COLLAPSING)
        .removeClass(ClassName.COLLAPSE)
Starsam80's avatar
Starsam80 committed
224
        .removeClass(ClassName.SHOW)
fat's avatar
fat committed
225
226
227
228
229
230
231
232
233
234
235

      this._element.setAttribute('aria-expanded', false)

      if (this._triggerArray.length) {
        $(this._triggerArray)
          .addClass(ClassName.COLLAPSED)
          .attr('aria-expanded', false)
      }

      this.setTransitioning(true)

236
      const complete = () => {
fat's avatar
fat committed
237
238
239
240
241
242
243
        this.setTransitioning(false)
        $(this._element)
          .removeClass(ClassName.COLLAPSING)
          .addClass(ClassName.COLLAPSE)
          .trigger(Event.HIDDEN)
      }

244
      this._element.style[dimension] = ''
fat's avatar
fat committed
245
246

      if (!Util.supportsTransitionEnd()) {
Jacob Thornton's avatar
Jacob Thornton committed
247
248
        complete()
        return
fat's avatar
fat committed
249
250
251
252
253
254
255
256
257
258
259
      }

      $(this._element)
        .one(Util.TRANSITION_END, complete)
        .emulateTransitionEnd(TRANSITION_DURATION)
    }

    setTransitioning(isTransitioning) {
      this._isTransitioning = isTransitioning
    }

fat's avatar
fat committed
260
261
262
263
264
265
266
267
268
269
    dispose() {
      $.removeData(this._element, DATA_KEY)

      this._config          = null
      this._parent          = null
      this._element         = null
      this._triggerArray    = null
      this._isTransitioning = null
    }

fat's avatar
fat committed
270
271
272

    // private

fat's avatar
fat committed
273
274
    _getConfig(config) {
      config = $.extend({}, Default, config)
Jacob Thornton's avatar
Jacob Thornton committed
275
      config.toggle = Boolean(config.toggle) // coerce string values
fat's avatar
fat committed
276
277
278
279
      Util.typeCheckConfig(NAME, config, DefaultType)
      return config
    }

fat's avatar
fat committed
280
    _getDimension() {
281
      const hasWidth = $(this._element).hasClass(Dimension.WIDTH)
fat's avatar
fat committed
282
283
284
285
      return hasWidth ? Dimension.WIDTH : Dimension.HEIGHT
    }

    _getParent() {
286
287
      const parent   = $(this._config.parent)[0]
      const selector =
fat's avatar
fat committed
288
289
290
291
292
293
294
295
296
297
298
299
300
301
        `[data-toggle="collapse"][data-parent="${this._config.parent}"]`

      $(parent).find(selector).each((i, element) => {
        this._addAriaAndCollapsedClass(
          Collapse._getTargetFromElement(element),
          [element]
        )
      })

      return parent
    }

    _addAriaAndCollapsedClass(element, triggerArray) {
      if (element) {
Starsam80's avatar
Starsam80 committed
302
        const isOpen = $(element).hasClass(ClassName.SHOW)
fat's avatar
fat committed
303
304
305
306
307
308
309
310
311
312
313
314
315
316
        element.setAttribute('aria-expanded', isOpen)

        if (triggerArray.length) {
          $(triggerArray)
            .toggleClass(ClassName.COLLAPSED, !isOpen)
            .attr('aria-expanded', isOpen)
        }
      }
    }


    // static

    static _getTargetFromElement(element) {
317
      const selector = Util.getSelectorFromElement(element)
fat's avatar
fat committed
318
319
320
321
322
      return selector ? $(selector)[0] : null
    }

    static _jQueryInterface(config) {
      return this.each(function () {
323
324
325
        const $this   = $(this)
        let data      = $this.data(DATA_KEY)
        const _config = $.extend(
fat's avatar
fat committed
326
          {},
327
          Default,
fat's avatar
fat committed
328
329
330
331
332
333
334
335
336
337
338
339
340
341
          $this.data(),
          typeof config === 'object' && config
        )

        if (!data && _config.toggle && /show|hide/.test(config)) {
          _config.toggle = false
        }

        if (!data) {
          data = new Collapse(this, _config)
          $this.data(DATA_KEY, data)
        }

        if (typeof config === 'string') {
342
343
344
          if (data[config] === undefined) {
            throw new Error(`No method named "${config}"`)
          }
fat's avatar
fat committed
345
346
347
348
349
350
351
352
353
354
355
356
357
358
          data[config]()
        }
      })
    }

  }


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

fat's avatar
fat committed
359
  $(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {
360
361
362
    if (/input|textarea/i.test(event.target.tagName)) {
      event.preventDefault()
    }
fat's avatar
fat committed
363

364
365
366
    const target = Collapse._getTargetFromElement(this)
    const data   = $(target).data(DATA_KEY)
    const config = data ? 'toggle' : $(this).data()
fat's avatar
fat committed
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389

    Collapse._jQueryInterface.call($(target), config)
  })


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

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

  return Collapse

})(jQuery)

export default Collapse