UNPKG

3.66 kBJavaScriptView Raw
1/* @flow */
2
3import {
4 tip,
5 toArray,
6 hyphenate,
7 formatComponentName,
8 invokeWithErrorHandling
9} from '../util/index'
10import { updateListeners } from '../vdom/helpers/index'
11
12export function initEvents (vm: Component) {
13 vm._events = Object.create(null)
14 vm._hasHookEvent = false
15 // init parent attached events
16 const listeners = vm.$options._parentListeners
17 if (listeners) {
18 updateComponentListeners(vm, listeners)
19 }
20}
21
22let target: any
23
24function add (event, fn) {
25 target.$on(event, fn)
26}
27
28function remove (event, fn) {
29 target.$off(event, fn)
30}
31
32function createOnceHandler (event, fn) {
33 const _target = target
34 return function onceHandler () {
35 const res = fn.apply(null, arguments)
36 if (res !== null) {
37 _target.$off(event, onceHandler)
38 }
39 }
40}
41
42export function updateComponentListeners (
43 vm: Component,
44 listeners: Object,
45 oldListeners: ?Object
46) {
47 target = vm
48 updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
49 target = undefined
50}
51
52export function eventsMixin (Vue: Class<Component>) {
53 const hookRE = /^hook:/
54 Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
55 const vm: Component = this
56 if (Array.isArray(event)) {
57 for (let i = 0, l = event.length; i < l; i++) {
58 vm.$on(event[i], fn)
59 }
60 } else {
61 (vm._events[event] || (vm._events[event] = [])).push(fn)
62 // optimize hook:event cost by using a boolean flag marked at registration
63 // instead of a hash lookup
64 if (hookRE.test(event)) {
65 vm._hasHookEvent = true
66 }
67 }
68 return vm
69 }
70
71 Vue.prototype.$once = function (event: string, fn: Function): Component {
72 const vm: Component = this
73 function on () {
74 vm.$off(event, on)
75 fn.apply(vm, arguments)
76 }
77 on.fn = fn
78 vm.$on(event, on)
79 return vm
80 }
81
82 Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
83 const vm: Component = this
84 // all
85 if (!arguments.length) {
86 vm._events = Object.create(null)
87 return vm
88 }
89 // array of events
90 if (Array.isArray(event)) {
91 for (let i = 0, l = event.length; i < l; i++) {
92 vm.$off(event[i], fn)
93 }
94 return vm
95 }
96 // specific event
97 const cbs = vm._events[event]
98 if (!cbs) {
99 return vm
100 }
101 if (!fn) {
102 vm._events[event] = null
103 return vm
104 }
105 // specific handler
106 let cb
107 let i = cbs.length
108 while (i--) {
109 cb = cbs[i]
110 if (cb === fn || cb.fn === fn) {
111 cbs.splice(i, 1)
112 break
113 }
114 }
115 return vm
116 }
117
118 Vue.prototype.$emit = function (event: string): Component {
119 const vm: Component = this
120 if (process.env.NODE_ENV !== 'production') {
121 const lowerCaseEvent = event.toLowerCase()
122 if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
123 tip(
124 `Event "${lowerCaseEvent}" is emitted in component ` +
125 `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
126 `Note that HTML attributes are case-insensitive and you cannot use ` +
127 `v-on to listen to camelCase events when using in-DOM templates. ` +
128 `You should probably use "${hyphenate(event)}" instead of "${event}".`
129 )
130 }
131 }
132 let cbs = vm._events[event]
133 if (cbs) {
134 cbs = cbs.length > 1 ? toArray(cbs) : cbs
135 const args = toArray(arguments, 1)
136 const info = `event handler for "${event}"`
137 for (let i = 0, l = cbs.length; i < l; i++) {
138 invokeWithErrorHandling(cbs[i], vm, args, vm, info)
139 }
140 }
141 return vm
142 }
143}