Skip to content

Commit 5d2c76f

Browse files
committed
fix event propagation handling in immediate inline component handlers
1 parent ab12c7b commit 5d2c76f

File tree

3 files changed

+72
-23
lines changed

3 files changed

+72
-23
lines changed

src/instance/api/events.js

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,21 +85,36 @@ export default function (Vue) {
8585
/**
8686
* Trigger an event on self.
8787
*
88-
* @param {String} event
88+
* @param {String|Object} event
8989
* @return {Boolean} shouldPropagate
9090
*/
9191

9292
Vue.prototype.$emit = function (event) {
93+
var isSource = typeof event === 'string'
94+
event = isSource
95+
? event
96+
: event.name
9397
var cbs = this._events[event]
94-
var shouldPropagate = !cbs
98+
var shouldPropagate = isSource || !cbs
9599
if (cbs) {
96100
cbs = cbs.length > 1
97101
? toArray(cbs)
98102
: cbs
103+
// this is a somewhat hacky solution to the question raised
104+
// in #2102: for an inline component listener like <comp @test="doThis">,
105+
// the propagation handling is somewhat broken. Therefore we
106+
// need to treat these inline callbacks differently.
107+
var hasParentCbs = isSource && cbs.some(function (cb) {
108+
return cb._fromParent
109+
})
110+
if (hasParentCbs) {
111+
shouldPropagate = false
112+
}
99113
var args = toArray(arguments, 1)
100114
for (var i = 0, l = cbs.length; i < l; i++) {
101-
var res = cbs[i].apply(this, args)
102-
if (res === true) {
115+
var cb = cbs[i]
116+
var res = cb.apply(this, args)
117+
if (res === true && (!hasParentCbs || cb._fromParent)) {
103118
shouldPropagate = true
104119
}
105120
}
@@ -110,20 +125,30 @@ export default function (Vue) {
110125
/**
111126
* Recursively broadcast an event to all children instances.
112127
*
113-
* @param {String} event
128+
* @param {String|Object} event
114129
* @param {...*} additional arguments
115130
*/
116131

117132
Vue.prototype.$broadcast = function (event) {
133+
var isSource = typeof event === 'string'
134+
event = isSource
135+
? event
136+
: event.name
118137
// if no child has registered for this event,
119138
// then there's no need to broadcast.
120139
if (!this._eventsCount[event]) return
121140
var children = this.$children
141+
var args = toArray(arguments)
142+
if (isSource) {
143+
// use object event to indicate non-source emit
144+
// on children
145+
args[0] = { name: event, source: this }
146+
}
122147
for (var i = 0, l = children.length; i < l; i++) {
123148
var child = children[i]
124-
var shouldPropagate = child.$emit.apply(child, arguments)
149+
var shouldPropagate = child.$emit.apply(child, args)
125150
if (shouldPropagate) {
126-
child.$broadcast.apply(child, arguments)
151+
child.$broadcast.apply(child, args)
127152
}
128153
}
129154
return this
@@ -136,11 +161,16 @@ export default function (Vue) {
136161
* @param {...*} additional arguments
137162
*/
138163

139-
Vue.prototype.$dispatch = function () {
140-
this.$emit.apply(this, arguments)
164+
Vue.prototype.$dispatch = function (event) {
165+
var shouldPropagate = this.$emit.apply(this, arguments)
166+
if (!shouldPropagate) return
141167
var parent = this.$parent
168+
var args = toArray(arguments)
169+
// use object event to indicate non-source emit
170+
// on parents
171+
args[0] = { name: event, source: this }
142172
while (parent) {
143-
var shouldPropagate = parent.$emit.apply(parent, arguments)
173+
shouldPropagate = parent.$emit.apply(parent, args)
144174
parent = shouldPropagate
145175
? parent.$parent
146176
: null

src/instance/internal/events.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export default function (Vue) {
3838
if (eventRE.test(name)) {
3939
name = name.replace(eventRE, '')
4040
handler = (vm._scope || vm._context).$eval(attrs[i].value, true)
41+
handler._fromParent = true
4142
vm.$on(name.replace(eventRE), handler)
4243
}
4344
}

test/unit/specs/api/events_spec.js

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ describe('Events API', function () {
165165
},
166166
components: {
167167
child1: {
168-
template: '<child2 @test="onTest()" v-ref:child></child2>',
168+
template: '<child2 @test="onTest" v-ref:child></child2>',
169169
methods: {
170170
onTest: function () {
171171
spy()
@@ -182,9 +182,13 @@ describe('Events API', function () {
182182
},
183183
components: {
184184
child3: {
185-
template: '<child4 v-ref:child></child4>',
186-
// `v-on` on component will be treat as its inner handler
187-
// so propagation cancelling is ignored on `<child4 @test="handler">`
185+
template: '<child4 @test="onTest" v-ref:child></child4>',
186+
methods: {
187+
onTest: function () {
188+
spy()
189+
return true
190+
}
191+
},
188192
components: {
189193
child4: {}
190194
}
@@ -201,18 +205,32 @@ describe('Events API', function () {
201205
.$refs.child // child3
202206
.$refs.child // child4
203207
.$dispatch('test')
204-
expect(spy.calls.count()).toBe(2)
208+
expect(spy.calls.count()).toBe(3)
205209
})
206210

207-
it('$dispatch cancel', function () {
208-
var child = new Vue({ parent: vm })
209-
var child2 = new Vue({ parent: child })
210-
child.$on('test', function () {
211-
return false
211+
it('$dispatch forward on immediate inline component handler', function () {
212+
var shouldPropagate = true
213+
var parent = new Vue({
214+
el: document.createElement('div'),
215+
template: '<child @test="onTest" v-ref:child></child>',
216+
events: {
217+
test: spy
218+
},
219+
methods: {
220+
onTest: function () {
221+
spy()
222+
return shouldPropagate
223+
}
224+
},
225+
components: {
226+
child: {}
227+
}
212228
})
213-
vm.$on('test', spy)
214-
child2.$dispatch('test')
215-
expect(spy).not.toHaveBeenCalled()
229+
parent.$refs.child.$dispatch('test')
230+
expect(spy.calls.count()).toBe(2)
231+
shouldPropagate = false
232+
parent.$refs.child.$dispatch('test')
233+
expect(spy.calls.count()).toBe(3)
216234
})
217235

218236
})

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy