Skip to content

Commit 154c845

Browse files
committed
call attach/detach for dynamicly created components inside if block
1 parent 7b84594 commit 154c845

File tree

6 files changed

+178
-66
lines changed

6 files changed

+178
-66
lines changed

src/compiler/compile.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ function compileNodeList (nodeList, options) {
341341
*/
342342

343343
function makeChildLinkFn (linkFns) {
344-
return function childLinkFn (vm, nodes) {
344+
return function childLinkFn (vm, nodes, host) {
345345
var node, nodeLinkFn, childrenLinkFn
346346
for (var i = 0, n = 0, l = linkFns.length; i < l; n++) {
347347
node = nodes[n]
@@ -350,10 +350,10 @@ function makeChildLinkFn (linkFns) {
350350
// cache childNodes before linking parent, fix #657
351351
var childNodes = _.toArray(node.childNodes)
352352
if (nodeLinkFn) {
353-
nodeLinkFn(vm, node)
353+
nodeLinkFn(vm, node, host)
354354
}
355355
if (childrenLinkFn) {
356-
childrenLinkFn(vm, childNodes)
356+
childrenLinkFn(vm, childNodes, host)
357357
}
358358
}
359359
}

src/compiler/transclude.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
var _ = require('../util')
2+
var config = require('../config')
23
var templateParser = require('../parsers/template')
4+
var transcludedFlagAttr = '__vue__transcluded'
35

46
/**
57
* Process an element or a DocumentFragment based on a
@@ -14,6 +16,27 @@ var templateParser = require('../parsers/template')
1416
*/
1517

1618
module.exports = function transclude (el, options) {
19+
if (options && options._asComponent) {
20+
// Mark content nodes and attrs so that the compiler
21+
// knows they should be compiled in parent scope.
22+
options._transcludedAttrs = extractAttrs(el.attributes)
23+
var i = el.childNodes.length
24+
while (i--) {
25+
var node = el.childNodes[i]
26+
if (node.nodeType === 1) {
27+
node.setAttribute(transcludedFlagAttr, '')
28+
} else if (node.nodeType === 3 && node.data.trim()) {
29+
// wrap transcluded textNodes in spans, because
30+
// raw textNodes can't be persisted through clones
31+
// by attaching attributes.
32+
var wrapper = document.createElement('span')
33+
wrapper.textContent = node.data
34+
wrapper.setAttribute('__vue__wrap', '')
35+
wrapper.setAttribute(transcludedFlagAttr, '')
36+
el.replaceChild(wrapper, node)
37+
}
38+
}
39+
}
1740
// for template tags, what we want is its content as
1841
// a documentFragment (for block instances)
1942
if (el.tagName === 'TEMPLATE') {
@@ -153,4 +176,24 @@ function insertContentAt (outlet, contents) {
153176
parent.insertBefore(contents[i], outlet)
154177
}
155178
parent.removeChild(outlet)
179+
}
180+
181+
/**
182+
* Helper to extract a component container's attribute names
183+
* into a map, and filtering out `v-with` in the process.
184+
* The resulting map will be used in compiler/compile to
185+
* determine whether an attribute is transcluded.
186+
*
187+
* @param {NameNodeMap} attrs
188+
*/
189+
190+
function extractAttrs (attrs) {
191+
var res = {}
192+
var vwith = config.prefix + 'with'
193+
var i = attrs.length
194+
while (i--) {
195+
var name = attrs[i].name
196+
if (name !== vwith) res[name] = true
197+
}
198+
return res
156199
}

src/directives/if.js

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -50,46 +50,81 @@ module.exports = {
5050
// NOTE: this function is shared in v-partial
5151
compile: function (frag) {
5252
var vm = this.vm
53-
var originalChildLength = vm._children.length
54-
var originalParentChildLength = vm.$parent &&
55-
vm.$parent._children.length
5653
// the linker is not guaranteed to be present because
5754
// this function might get called by v-partial
5855
this.unlink = this.linker
5956
? this.linker(vm, frag)
6057
: vm.$compile(frag)
6158
transition.blockAppend(frag, this.end, vm)
62-
this.children = vm._children.slice(originalChildLength)
63-
if (vm.$parent) {
64-
this.children = this.children.concat(
65-
vm.$parent._children.slice(originalParentChildLength)
66-
)
67-
}
68-
if (this.children.length && _.inDoc(vm.$el)) {
69-
this.children.forEach(function (child) {
70-
child._callHook('attached')
71-
})
59+
// call attached for all the child components created
60+
// during the compilation
61+
if (_.inDoc(vm.$el)) {
62+
var children = this.getContainedComponents()
63+
if (children) children.forEach(callAttach)
7264
}
7365
},
7466

7567
// NOTE: this function is shared in v-partial
7668
teardown: function () {
7769
if (!this.unlink) return
78-
transition.blockRemove(this.start, this.end, this.vm)
79-
if (this.children && _.inDoc(this.vm.$el)) {
80-
this.children.forEach(function (child) {
81-
if (!child._isDestroyed) {
82-
child._callHook('detached')
83-
}
84-
})
70+
// collect children beforehand
71+
var children
72+
if (_.inDoc(this.vm.$el)) {
73+
children = this.getContainedComponents()
8574
}
75+
transition.blockRemove(this.start, this.end, this.vm)
76+
if (children) children.forEach(callDetach)
8677
this.unlink()
8778
this.unlink = null
8879
},
8980

81+
// NOTE: this function is shared in v-partial
82+
getContainedComponents: function () {
83+
var vm = this.vm
84+
var start = this.start.nextSibling
85+
var end = this.end
86+
var selfCompoents =
87+
vm._children.length &&
88+
vm._children.filter(contains)
89+
var transComponents =
90+
vm._transCpnts &&
91+
vm._transCpnts.filter(contains)
92+
93+
function contains (c) {
94+
var cur = start
95+
var next
96+
while (next !== end) {
97+
next = cur.nextSibling
98+
if (cur.contains(c.$el)) {
99+
return true
100+
}
101+
cur = next
102+
}
103+
return false
104+
}
105+
106+
return selfCompoents
107+
? transComponents
108+
? selfCompoents.concat(transComponents)
109+
: selfCompoents
110+
: transComponents
111+
},
112+
90113
// NOTE: this function is shared in v-partial
91114
unbind: function () {
92115
if (this.unlink) this.unlink()
93116
}
94117

118+
}
119+
120+
function callAttach (child) {
121+
if (!child._isAttached) {
122+
child._callHook('attached')
123+
}
124+
}
125+
126+
function callDetach (child) {
127+
if (child._isAttached) {
128+
child._callHook('detached')
129+
}
95130
}

src/directives/partial.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module.exports = {
99
// same logic reuse from v-if
1010
compile: vIf.compile,
1111
teardown: vIf.teardown,
12+
getContainedComponents: vIf.getContainedComponents,
1213
unbind: vIf.unbind,
1314

1415
bind: function () {

src/instance/compile.js

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
var _ = require('../util')
2-
var config = require('../config')
32
var Directive = require('../directive')
43
var compile = require('../compiler/compile')
54
var transclude = require('../compiler/transclude')
6-
var transcludedFlagAttr = '__vue__transcluded'
75

86
/**
97
* Transclude, compile and link element.
@@ -21,30 +19,10 @@ var transcludedFlagAttr = '__vue__transcluded'
2119
exports._compile = function (el) {
2220
var options = this.$options
2321
if (options._linkFn) {
22+
// pre-transcluded with linker, just use it
2423
this._initElement(el)
2524
options._linkFn(this, el)
2625
} else {
27-
if (options._asComponent) {
28-
// Mark content nodes and attrs so that the compiler
29-
// knows they should be compiled in parent scope.
30-
options._transcludedAttrs = extractAttrs(el.attributes)
31-
var i = el.childNodes.length
32-
while (i--) {
33-
var node = el.childNodes[i]
34-
if (node.nodeType === 1) {
35-
node.setAttribute(transcludedFlagAttr, '')
36-
} else if (node.nodeType === 3 && node.data.trim()) {
37-
// wrap transcluded textNodes in spans, because
38-
// raw textNodes can't be persisted through clones
39-
// by attaching attributes.
40-
var wrapper = document.createElement('span')
41-
wrapper.textContent = node.data
42-
wrapper.setAttribute('__vue__wrap', '')
43-
wrapper.setAttribute(transcludedFlagAttr, '')
44-
el.replaceChild(wrapper, node)
45-
}
46-
}
47-
}
4826
// transclude and init element
4927
// transclude can potentially replace original
5028
// so we need to keep reference
@@ -186,24 +164,4 @@ exports._cleanup = function () {
186164
this._callHook('destroyed')
187165
// turn off all instance listeners.
188166
this.$off()
189-
}
190-
191-
/**
192-
* Helper to extract a component container's attribute names
193-
* into a map, and filtering out `v-with` in the process.
194-
* The resulting map will be used in compiler/compile to
195-
* determine whether an attribute is transcluded.
196-
*
197-
* @param {NameNodeMap} attrs
198-
*/
199-
200-
function extractAttrs (attrs) {
201-
var res = {}
202-
var vwith = config.prefix + 'with'
203-
var i = attrs.length
204-
while (i--) {
205-
var name = attrs[i].name
206-
if (name !== vwith) res[name] = true
207-
}
208-
return res
209167
}

test/unit/specs/directives/if_spec.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,84 @@ if (_.inBrowser) {
235235
vm.show = false
236236
_.nextTick(function () {
237237
expect(detachSpy).toHaveBeenCalled()
238+
document.body.removeChild(el)
238239
done()
239240
})
240241
})
241242

243+
it('call attach/detach for dynamicly created components inside if block', function (done) {
244+
document.body.appendChild(el)
245+
var attachSpy = jasmine.createSpy('attached')
246+
var detachSpy = jasmine.createSpy('detached')
247+
var vm = new Vue({
248+
el: el,
249+
data: {
250+
show: true,
251+
list: [{a:0}]
252+
},
253+
template:
254+
'<div v-component="outer">' +
255+
'<div>' + // an extra layer to test components deep inside the tree
256+
'<div v-repeat="list" v-component="transcluded"></div>' +
257+
'</div>' +
258+
'</div>',
259+
components: {
260+
outer: {
261+
template:
262+
'<div v-if="$parent.show">' +
263+
'<content></content>' +
264+
'</div>' +
265+
// this is to test that compnents that are not in the if block
266+
// should not fire attach/detach when v-if toggles
267+
'<div v-component="transcluded"></div>'
268+
},
269+
transcluded: {
270+
template: '{{a}}',
271+
attached: attachSpy,
272+
detached: detachSpy
273+
}
274+
}
275+
})
276+
assertMarkup()
277+
expect(attachSpy.calls.count()).toBe(2)
278+
vm.show = false
279+
_.nextTick(function () {
280+
assertMarkup()
281+
expect(detachSpy.calls.count()).toBe(1)
282+
vm.list.push({a:1})
283+
vm.show = true
284+
_.nextTick(function () {
285+
assertMarkup()
286+
expect(attachSpy.calls.count()).toBe(2 + 2)
287+
vm.list.push({a:2})
288+
vm.show = false
289+
_.nextTick(function () {
290+
assertMarkup()
291+
expect(attachSpy.calls.count()).toBe(2 + 2 + 1)
292+
expect(detachSpy.calls.count()).toBe(1 + 3)
293+
document.body.removeChild(el)
294+
done()
295+
})
296+
})
297+
})
298+
299+
function assertMarkup () {
300+
var showBlock = vm.show
301+
? '<div><div>' +
302+
vm.list.map(function (o) {
303+
return '<div>' + o.a + '</div>'
304+
}).join('') + '<!--v-repeat-->' +
305+
'</div></div>'
306+
: ''
307+
var markup = '<div>' +
308+
'<!--v-if-start-->' +
309+
showBlock +
310+
'<!--v-if-end-->' +
311+
'<div></div><!--v-component-->' +
312+
'</div><!--v-component-->'
313+
expect(el.innerHTML).toBe(markup)
314+
}
315+
})
316+
242317
})
243318
}

0 commit comments

Comments
 (0)