/* @flow */
|
|
import {
|
tip,
|
toArray,
|
hyphenate,
|
formatComponentName,
|
invokeWithErrorHandling
|
} from '../util/index'
|
import { updateListeners } from '../vdom/helpers/index'
|
|
export function initEvents (vm: Component) {
|
vm._events = Object.create(null)
|
vm._hasHookEvent = false
|
// init parent attached events
|
const listeners = vm.$options._parentListeners
|
if (listeners) {
|
updateComponentListeners(vm, listeners)
|
}
|
}
|
|
let target: any
|
|
function add (event, fn) {
|
target.$on(event, fn)
|
}
|
|
function remove (event, fn) {
|
target.$off(event, fn)
|
}
|
|
function createOnceHandler (event, fn) {
|
const _target = target
|
return function onceHandler () {
|
const res = fn.apply(null, arguments)
|
if (res !== null) {
|
_target.$off(event, onceHandler)
|
}
|
}
|
}
|
|
export function updateComponentListeners (
|
vm: Component,
|
listeners: Object,
|
oldListeners: ?Object
|
) {
|
target = vm
|
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
|
target = undefined
|
}
|
|
export function eventsMixin (Vue: Class<Component>) {
|
const hookRE = /^hook:/
|
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
|
const vm: Component = this
|
if (Array.isArray(event)) {
|
for (let i = 0, l = event.length; i < l; i++) {
|
vm.$on(event[i], fn)
|
}
|
} else {
|
(vm._events[event] || (vm._events[event] = [])).push(fn)
|
// optimize hook:event cost by using a boolean flag marked at registration
|
// instead of a hash lookup
|
if (hookRE.test(event)) {
|
vm._hasHookEvent = true
|
}
|
}
|
return vm
|
}
|
|
Vue.prototype.$once = function (event: string, fn: Function): Component {
|
const vm: Component = this
|
function on () {
|
vm.$off(event, on)
|
fn.apply(vm, arguments)
|
}
|
on.fn = fn
|
vm.$on(event, on)
|
return vm
|
}
|
|
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
|
const vm: Component = this
|
// all
|
if (!arguments.length) {
|
vm._events = Object.create(null)
|
return vm
|
}
|
// array of events
|
if (Array.isArray(event)) {
|
for (let i = 0, l = event.length; i < l; i++) {
|
vm.$off(event[i], fn)
|
}
|
return vm
|
}
|
// specific event
|
const cbs = vm._events[event]
|
if (!cbs) {
|
return vm
|
}
|
if (!fn) {
|
vm._events[event] = null
|
return vm
|
}
|
// specific handler
|
let cb
|
let i = cbs.length
|
while (i--) {
|
cb = cbs[i]
|
if (cb === fn || cb.fn === fn) {
|
cbs.splice(i, 1)
|
break
|
}
|
}
|
return vm
|
}
|
|
Vue.prototype.$emit = function (event: string): Component {
|
const vm: Component = this
|
if (process.env.NODE_ENV !== 'production') {
|
const lowerCaseEvent = event.toLowerCase()
|
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
|
tip(
|
`Event "${lowerCaseEvent}" is emitted in component ` +
|
`${formatComponentName(vm)} but the handler is registered for "${event}". ` +
|
`Note that HTML attributes are case-insensitive and you cannot use ` +
|
`v-on to listen to camelCase events when using in-DOM templates. ` +
|
`You should probably use "${hyphenate(event)}" instead of "${event}".`
|
)
|
}
|
}
|
let cbs = vm._events[event]
|
if (cbs) {
|
cbs = cbs.length > 1 ? toArray(cbs) : cbs
|
const args = toArray(arguments, 1)
|
const info = `event handler for "${event}"`
|
for (let i = 0, l = cbs.length; i < l; i++) {
|
invokeWithErrorHandling(cbs[i], vm, args, vm, info)
|
}
|
}
|
return vm
|
}
|
}
|