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)
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