eventHandler.js 7.78 KB
Newer Older
Johann-S's avatar
Johann-S committed
1
2
import Util from '../util'

Johann-S's avatar
Johann-S committed
3
4
/**
 * --------------------------------------------------------------------------
Johann-S's avatar
Johann-S committed
5
 * Bootstrap (v4.0.0-beta): dom/eventHandler.js
Johann-S's avatar
Johann-S committed
6
7
8
9
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * --------------------------------------------------------------------------
 */

10
11
12
13
14
15
16
17
18
19
20
21
22
23
// defaultPrevented is broken in IE.
// https://connect.microsoft.com/IE/feedback/details/790389/event-defaultprevented-returns-false-after-preventdefault-was-called
const workingDefaultPrevented = (() => {
  const e = document.createEvent('CustomEvent')
  e.initEvent('Bootstrap', true, true)
  e.preventDefault()
  return e.defaultPrevented
})()

// CustomEvent polyfill for IE (see: https://mzl.la/2v76Zvn)
if (typeof window.CustomEvent !== 'function') {
  window.CustomEvent = (event, params) => {
    params = params || {
      bubbles: false,
24
25
      cancelable: false,
      detail: null
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
58
59
60
61
62
    }
    const evt = document.createEvent('CustomEvent')
    evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail)
    if (!workingDefaultPrevented) {
      const origPreventDefault = Event.prototype.preventDefault
      evt.preventDefault = () => {
        if (!evt.cancelable) {
          return
        }

        origPreventDefault.call(evt)
        Object.defineProperty(evt, 'defaultPrevented', {
          get() {
            return true
          },
          configurable: true
        })
      }
    }
    return evt
  }

  window.CustomEvent.prototype = window.Event.prototype
}

// Event constructor shim
if (!window.Event || typeof window.Event !== 'function') {
  const origEvent = window.Event
  window.Event = (inType, params) => {
    params = params || {}
    const e = document.createEvent('Event')
    e.initEvent(inType, Boolean(params.bubbles), Boolean(params.cancelable))
    return e
  }
  window.Event.prototype = origEvent.prototype
}

Johann-S's avatar
Johann-S committed
63
64
const namespaceRegex = /[^.]*(?=\..*)\.|.*/
const stripNameRegex = /\..*/
Johann-S's avatar
Johann-S committed
65
const keyEventRegex  = /^key/
Johann-S's avatar
Johann-S committed
66
67
68
69
70
71
72
73
74
75
76
77
78
79

// Events storage
const eventRegistry = {}
let uidEvent = 1

function getUidEvent(element, uid) {
  return element.uidEvent = uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++
}

function getEvent(element) {
  const uid = getUidEvent(element)
  return eventRegistry[uid] = eventRegistry[uid] || {}
}

80
81
82
83
84
85
86
87
const nativeEvents = [
  'click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu',
  'mousewheel', 'DOMMouseScroll',
  'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend',
  'keydown', 'keypress', 'keyup',
  'orientationchange',
  'touchstart', 'touchmove', 'touchend', 'touchcancel',
  'gesturestart', 'gesturechange', 'gestureend',
88
  'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout',
89
90
91
  'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange',
  'error', 'abort', 'scroll'
]
Johann-S's avatar
Johann-S committed
92

Johann-S's avatar
Johann-S committed
93
94
95
96
97
98
99
100
101
102
103
104
105
const customEvents = {
  mouseenter: 'mouseover',
  mouseleave: 'mouseout'
}

function fixEvent(event) {
  // Add which for key events
  if (event.which === null && keyEventRegex.test(event.type)) {
    event.which = event.charCode !== null ? event.charCode : event.keyCode
  }
  return event
}

Johann-S's avatar
Johann-S committed
106
107
function bootstrapHandler(element, fn) {
  return function (event) {
Johann-S's avatar
Johann-S committed
108
    event = fixEvent(event)
Johann-S's avatar
Johann-S committed
109
110
111
112
    return fn.apply(element, [event])
  }
}

113
114
function bootstrapDelegationHandler(selector, fn) {
  return function (event) {
Johann-S's avatar
Johann-S committed
115
    event = fixEvent(event)
116
117
118
119
120
121
122
123
    const domElements = document.querySelectorAll(selector)
    for (let target = event.target; target && target !== this; target = target.parentNode) {
      for (let i = domElements.length; i--;) {
        if (domElements[i] === target) {
          return fn.apply(target, [event])
        }
      }
    }
124
125
    // To please ESLint
    return null
126
127
128
  }
}

Johann-S's avatar
Johann-S committed
129
const EventHandler = {
130
  on(element, originalTypeEvent, handler, delegationFn) {
Johann-S's avatar
Johann-S committed
131
132
133
134
135
    if (typeof originalTypeEvent !== 'string' ||
        (typeof element === 'undefined' || element === null)) {
      return
    }

Johann-S's avatar
Johann-S committed
136
137
    const delegation      = typeof handler === 'string'
    const originalHandler = delegation ? delegationFn : handler
Johann-S's avatar
Johann-S committed
138

Johann-S's avatar
Johann-S committed
139
140
    // allow to get the native events from namespaced events ('click.bs.button' --> 'click')
    let typeEvent = originalTypeEvent.replace(stripNameRegex, '')
Johann-S's avatar
Johann-S committed
141
142
143
144
145
146

    const custom = customEvents[typeEvent]
    if (custom) {
      typeEvent = custom
    }

Johann-S's avatar
Johann-S committed
147
148
149
150
151
152
    const isNative = nativeEvents.indexOf(typeEvent) > -1
    if (!isNative) {
      typeEvent = originalTypeEvent
    }
    const events    = getEvent(element)
    const handlers  = events[typeEvent] || (events[typeEvent] = {})
Johann-S's avatar
Johann-S committed
153
    const uid       = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, ''))
Johann-S's avatar
Johann-S committed
154
    if (handlers[uid]) {
Johann-S's avatar
Johann-S committed
155
156
      return
    }
Johann-S's avatar
Johann-S committed
157

158
    const fn = !delegation ? bootstrapHandler(element, handler) : bootstrapDelegationHandler(handler, delegationFn)
Johann-S's avatar
Johann-S committed
159
    fn.isDelegation = delegation
Johann-S's avatar
Johann-S committed
160
    handlers[uid] = fn
Johann-S's avatar
Johann-S committed
161
    originalHandler.uidEvent = uid
Johann-S's avatar
Johann-S committed
162
    fn.originalHandler = originalHandler
163
    element.addEventListener(typeEvent, fn, delegation)
Johann-S's avatar
Johann-S committed
164
165
166
  },

  one(element, event, handler) {
Johann-S's avatar
Johann-S committed
167
    function complete(e) {
168
      EventHandler.off(element, event, complete)
Johann-S's avatar
Johann-S committed
169
      handler.apply(element, [e])
Johann-S's avatar
Johann-S committed
170
    }
Johann-S's avatar
Johann-S committed
171
    EventHandler.on(element, event, complete)
Johann-S's avatar
Johann-S committed
172
173
  },

Johann-S's avatar
Johann-S committed
174
175
176
177
  off(element, originalTypeEvent, handler) {
    if (typeof originalTypeEvent !== 'string' ||
       (typeof element === 'undefined' || element === null)) {
      return
Johann-S's avatar
Johann-S committed
178
179
    }

Johann-S's avatar
Johann-S committed
180
181
182
183
184
185
186
187
188
189
    const events      = getEvent(element)
    let typeEvent     = originalTypeEvent.replace(stripNameRegex, '')
    const inNamespace = typeEvent !== originalTypeEvent
    const custom      = customEvents[typeEvent]
    if (custom) {
      typeEvent = custom
    }
    const isNative = nativeEvents.indexOf(typeEvent) > -1
    if (!isNative) {
      typeEvent = originalTypeEvent
Johann-S's avatar
Johann-S committed
190
    }
191

Johann-S's avatar
Johann-S committed
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
    if (typeof handler === 'undefined') {
      for (const elementEvent in events) {
        if (!Object.prototype.hasOwnProperty.call(events, elementEvent)) {
          continue
        }

        const storeElementEvent = events[elementEvent]
        for (const keyHandlers in storeElementEvent) {
          if (!Object.prototype.hasOwnProperty.call(storeElementEvent, keyHandlers)) {
            continue
          }
          // delete all the namespaced listeners
          if (inNamespace && keyHandlers.indexOf(originalTypeEvent) > -1) {
            const handlerFn = events[elementEvent][keyHandlers]
            EventHandler.off(element, elementEvent, handlerFn.originalHandler)
          }
        }
      }
    } else {
      if (!events || !events[typeEvent]) {
        return
      }

      const uidEvent = handler.uidEvent
      const fn = events[typeEvent][uidEvent]
      element.removeEventListener(typeEvent, fn, fn.delegation)
      delete events[typeEvent][uidEvent]
    }
Johann-S's avatar
Johann-S committed
220
  },
Johann-S's avatar
Johann-S committed
221

222
  trigger(element, event, args) {
Johann-S's avatar
Johann-S committed
223
    if (typeof event !== 'string' ||
224
        (typeof element === 'undefined' || element === null)) {
Johann-S's avatar
Johann-S committed
225
226
      return null
    }
Johann-S's avatar
Johann-S committed
227
228
229
230
231

    const typeEvent   = event.replace(stripNameRegex, '')
    const isNative    = nativeEvents.indexOf(typeEvent) > -1
    let evt           = null

Johann-S's avatar
Johann-S committed
232
    if (isNative) {
Johann-S's avatar
Johann-S committed
233
234
      evt = document.createEvent('HTMLEvents')
      evt.initEvent(typeEvent, true, true)
Johann-S's avatar
Johann-S committed
235
    } else {
Johann-S's avatar
Johann-S committed
236
      evt = new CustomEvent(event, {
Johann-S's avatar
Johann-S committed
237
        bubbles: true,
Johann-S's avatar
Johann-S committed
238
        cancelable: true
Johann-S's avatar
Johann-S committed
239
240
      })
    }
Johann-S's avatar
Johann-S committed
241
242
243
244
245
246
247

    // merge custom informations in our event
    if (typeof args !== 'undefined') {
      evt = Util.extend(evt, args)
    }
    element.dispatchEvent(evt)
    return evt
Johann-S's avatar
Johann-S committed
248
249
250
  }
}

251
252
253
254
255
256
257
258
259
260
261
262
263
264
// focusin and focusout polyfill
if (typeof window.onfocusin === 'undefined') {
  (() => {
    function listenerFocus(event) {
      EventHandler.trigger(event.target, 'focusin')
    }
    function listenerBlur(event) {
      EventHandler.trigger(event.target, 'focusout')
    }
    EventHandler.on(document, 'focus', 'input', listenerFocus)
    EventHandler.on(document, 'blur', 'input', listenerBlur)
  })()
}

Johann-S's avatar
Johann-S committed
265
export default EventHandler