Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 89 additions & 53 deletions js/src/dom/eventHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ import Util from '../util'

const EventHandler = (() => {


/**
* ------------------------------------------------------------------------
* Polyfills
* ------------------------------------------------------------------------
*/


// defaultPrevented is broken in IE.
// https://connect.microsoft.com/IE/feedback/details/790389/event-defaultprevented-returns-false-after-preventdefault-was-called
const workingDefaultPrevented = (() => {
Expand Down Expand Up @@ -128,51 +126,125 @@ const EventHandler = (() => {
* ------------------------------------------------------------------------
*/


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

function getEvent(element) {
const uid = getUidEvent(element)
element.uidEvent = uid

return eventRegistry[uid] = eventRegistry[uid] || {}
}

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

event.delegateTarget = element
}

function bootstrapHandler(element, fn) {
return function (event) {
event = fixEvent(event)
return function handler(event) {
fixEvent(event, element)
if (handler.oneOff) {
EventHandler.off(element, event.type, fn)
}

return fn.apply(element, [event])
}
}

function bootstrapDelegationHandler(element, selector, fn) {
return function (event) {
event = fixEvent(event)
return function handler(event) {
const domElements = element.querySelectorAll(selector)
for (let target = event.target; target && target !== this; target = target.parentNode) {
for (let i = domElements.length; i--;) {
if (domElements[i] === target) {
fixEvent(event, target)
if (handler.oneOff) {
EventHandler.off(element, event.type, fn)
}

return fn.apply(target, [event])
}
}
}

// To please ESLint
return null
}
}

function findHandler(events, handler) {
for (const uid in events) {
if (!Object.prototype.hasOwnProperty.call(events, uid)) {
continue
}

if (events[uid].originalHandler === handler) {
return events[uid]
}
}

return null
}

function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) {
if (typeof originalTypeEvent !== 'string' || (typeof element === 'undefined' || element === null)) {
return
}

if (!handler) {
handler = delegationFn
delegationFn = null
}

const delegation = typeof handler === 'string'
const originalHandler = delegation ? delegationFn : handler

// allow to get the native events from namespaced events ('click.bs.button' --> 'click')
let typeEvent = originalTypeEvent.replace(stripNameRegex, '')

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

const isNative = nativeEvents.indexOf(typeEvent) > -1
if (!isNative) {
typeEvent = originalTypeEvent
}

const events = getEvent(element)
const handlers = events[typeEvent] || (events[typeEvent] = {})
const previousFn = findHandler(handlers, originalHandler)

if (previousFn) {
previousFn.oneOff = previousFn.oneOff && oneOff
return
}

const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, ''))
const fn = !delegation ? bootstrapHandler(element, handler) : bootstrapDelegationHandler(element, handler, delegationFn)

fn.isDelegation = delegation
fn.originalHandler = originalHandler
fn.oneOff = oneOff
handlers[uid] = fn

element.addEventListener(typeEvent, fn, delegation)
}

function removeHandler(element, events, typeEvent, handler) {
const uidEvent = handler.uidEvent
const fn = events[typeEvent][uidEvent]
element.removeEventListener(typeEvent, fn, fn.delegation)
const fn = findHandler(events[typeEvent], handler)
if (fn === null) {
return
}

element.removeEventListener(typeEvent, fn, fn.isDelegation)
delete events[typeEvent][uidEvent]
}

Expand All @@ -190,48 +262,12 @@ const EventHandler = (() => {
}

return {
on(element, originalTypeEvent, handler, delegationFn) {
if (typeof originalTypeEvent !== 'string'
|| (typeof element === 'undefined' || element === null)) {
return
}

const delegation = typeof handler === 'string'
const originalHandler = delegation ? delegationFn : handler

// allow to get the native events from namespaced events ('click.bs.button' --> 'click')
let typeEvent = originalTypeEvent.replace(stripNameRegex, '')

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

const isNative = nativeEvents.indexOf(typeEvent) > -1
if (!isNative) {
typeEvent = originalTypeEvent
}
const events = getEvent(element)
const handlers = events[typeEvent] || (events[typeEvent] = {})
const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, ''))
if (handlers[uid]) {
return
}

const fn = !delegation ? bootstrapHandler(element, handler) : bootstrapDelegationHandler(element, handler, delegationFn)
fn.isDelegation = delegation
handlers[uid] = fn
originalHandler.uidEvent = uid
fn.originalHandler = originalHandler
element.addEventListener(typeEvent, fn, delegation)
on(element, event, handler, delegationFn) {
addHandler(element, event, handler, delegationFn, false)
},

one(element, event, handler) {
function complete(e) {
EventHandler.off(element, event, complete)
handler.apply(element, [e])
}
EventHandler.on(element, event, complete)
one(element, event, handler, delegationFn) {
addHandler(element, event, handler, delegationFn, true)
},

off(element, originalTypeEvent, handler) {
Expand Down
58 changes: 30 additions & 28 deletions js/src/popover.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import $ from 'jquery'
import Data from './dom/data'
import SelectorEngine from './dom/selectorEngine'
import Tooltip from './tooltip'

import Util from './util'

/**
* --------------------------------------------------------------------------
Expand All @@ -22,11 +23,10 @@ const Popover = (() => {
const VERSION = '4.0.0-beta'
const DATA_KEY = 'bs.popover'
const EVENT_KEY = `.${DATA_KEY}`
const JQUERY_NO_CONFLICT = $.fn[NAME]
const CLASS_PREFIX = 'bs-popover'
const BSCLS_PREFIX_REGEX = new RegExp(`(^|\\s)${CLASS_PREFIX}\\S+`, 'g')

const Default = $.extend({}, Tooltip.Default, {
const Default = Util.extend({}, Tooltip.Default, {
placement : 'right',
trigger : 'click',
content : '',
Expand All @@ -36,7 +36,7 @@ const Popover = (() => {
+ '<div class="popover-body"></div></div>'
})

const DefaultType = $.extend({}, Tooltip.DefaultType, {
const DefaultType = Util.extend({}, Tooltip.DefaultType, {
content : '(string|element|function)'
})

Expand Down Expand Up @@ -111,22 +111,18 @@ const Popover = (() => {
}

addAttachmentClass(attachment) {
$(this.getTipElement()).addClass(`${CLASS_PREFIX}-${attachment}`)
}

getTipElement() {
this.tip = this.tip || $(this.config.template)[0]
return this.tip
this.getTipElement().classList.add(`${CLASS_PREFIX}-${attachment}`)
}

setContent() {
const $tip = $(this.getTipElement())
const tip = this.getTipElement()

// we use append for html objects to maintain js events
this.setElementContent($tip.find(Selector.TITLE), this.getTitle())
this.setElementContent($tip.find(Selector.CONTENT), this._getContent())
this.setElementContent(SelectorEngine.findOne(Selector.TITLE, tip), this.getTitle())
this.setElementContent(SelectorEngine.findOne(Selector.CONTENT, tip), this._getContent())

$tip.removeClass(`${ClassName.FADE} ${ClassName.SHOW}`)
tip.classList.remove(ClassName.FADE)
tip.classList.remove(ClassName.SHOW)
}

// private
Expand All @@ -139,28 +135,29 @@ const Popover = (() => {
}

_cleanTipClass() {
const $tip = $(this.getTipElement())
const tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX)
const tip = this.getTipElement()
const tabClass = tip.getAttribute('class').match(BSCLS_PREFIX_REGEX)
if (tabClass !== null && tabClass.length > 0) {
$tip.removeClass(tabClass.join(''))
tabClass.map((token) => token.trim()).forEach((tClass) => {
tip.classList.remove(tClass)
})
}
}


// static

static _jQueryInterface(config) {
return this.each(function () {
let data = $(this).data(DATA_KEY)
const _config = typeof config === 'object' ? config : null
let data = Data.getData(this, DATA_KEY)
const _config = typeof config === 'object' && config

if (!data && /destroy|hide/.test(config)) {
return
}

if (!data) {
data = new Popover(this, _config)
$(this).data(DATA_KEY, data)
Data.setData(this, DATA_KEY, data)
}

if (typeof config === 'string') {
Expand All @@ -180,15 +177,20 @@ const Popover = (() => {
* ------------------------------------------------------------------------
*/

$.fn[NAME] = Popover._jQueryInterface
$.fn[NAME].Constructor = Popover
$.fn[NAME].noConflict = function () {
$.fn[NAME] = JQUERY_NO_CONFLICT
return Popover._jQueryInterface
const $ = Util.jQuery

if (typeof $ !== 'undefined') {
const JQUERY_NO_CONFLICT = $.fn[NAME]
$.fn[NAME] = Popover._jQueryInterface
$.fn[NAME].Constructor = Popover
$.fn[NAME].noConflict = function () {
$.fn[NAME] = JQUERY_NO_CONFLICT
return Popover._jQueryInterface
}
}

return Popover

})(jQuery)
})()

export default Popover
Loading