Skip to content

Commit 5d2c76f

Browse files
committedJan 2, 2016
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

+40-10
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

+1
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

+31-13
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)
Please sign in to comment.