Skip to content

Commit abd53e8

Browse files
committed
fix content select logic (fix #936)
1 parent 9fbed43 commit abd53e8

File tree

2 files changed

+77
-20
lines changed

2 files changed

+77
-20
lines changed

src/compiler/content.js

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,40 +18,51 @@ module.exports = {
1818
var raw = contentOwner.$options._content
1919
var content
2020
if (!raw) {
21-
// fallback content
22-
// extract as a fragment
23-
content = _.extractContent(this.el, true)
24-
this.compile(content, vm)
21+
this.fallback()
2522
return
2623
}
2724
var parent = contentOwner.$parent
2825
var selector = this.el.getAttribute('select')
2926
if (!selector) {
30-
// default content
31-
// Importent: clone the rawContent before extracting
32-
// content because the <content> may be inside a v-if
33-
// and need to be compiled more than once.
34-
content = _.extractContent(raw.cloneNode(true), true)
35-
this.compile(content, parent, vm)
27+
// Default content
28+
var self = this
29+
var compileDefaultContent = function () {
30+
self.compile(
31+
extractFragment(raw.childNodes, raw, true),
32+
contentOwner.$parent,
33+
vm
34+
)
35+
}
36+
if (!contentOwner._isCompiled) {
37+
// defer until the end of instance compilation,
38+
// because the default outlet must wait until all
39+
// other possible outlets with selectors have picked
40+
// out their contents.
41+
contentOwner.$once('hook:compiled', compileDefaultContent)
42+
} else {
43+
compileDefaultContent()
44+
}
3645
} else {
3746
// select content
3847
selector = vm.$interpolate(selector)
3948
var nodes = raw.querySelectorAll(selector)
4049
if (nodes.length) {
41-
content = document.createDocumentFragment()
42-
for (var i = 0, l = nodes.length; i < l; i++) {
43-
// only allow top-level select
44-
if (nodes[i].parentNode === raw) {
45-
content.appendChild(nodes[i].cloneNode(true))
46-
}
47-
}
50+
content = extractFragment(nodes, raw)
4851
if (content.hasChildNodes()) {
4952
this.compile(content, parent, vm)
53+
} else {
54+
this.fallback()
5055
}
56+
} else {
57+
this.fallback()
5158
}
5259
}
5360
},
5461

62+
fallback: function () {
63+
this.compile(_.extractContent(this.el, true), this.vm)
64+
},
65+
5566
compile: function (content, owner, host) {
5667
if (content && owner) {
5768
this.unlink = owner.$compile(content, host)
@@ -68,5 +79,33 @@ module.exports = {
6879
this.unlink()
6980
}
7081
}
82+
}
83+
84+
/**
85+
* Extract qualified content nodes from a node list.
86+
*
87+
* @param {NodeList} nodes
88+
* @param {Element} parent
89+
* @param {Boolean} main
90+
* @return {DocumentFragment}
91+
*/
7192

93+
function extractFragment (nodes, parent, main) {
94+
var frag = document.createDocumentFragment()
95+
for (var i = 0, l = nodes.length; i < l; i++) {
96+
var node = nodes[i]
97+
// if this is the main outlet, we want to skip all
98+
// previously selected nodes;
99+
// otherwise, we want to mark the node as selected.
100+
// clone the node so the original raw content remains
101+
// intact. this ensures proper re-compilation in cases
102+
// where the outlet is inside a conditional block
103+
if (main && !node.selected) {
104+
frag.appendChild(node.cloneNode(true))
105+
} else if (!main && node.parentNode === parent) {
106+
node.selected = true
107+
frag.appendChild(node.cloneNode(true))
108+
}
109+
}
110+
return frag
72111
}

test/unit/specs/compiler/content_spec.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ describe('Content Transclusion', function () {
4747

4848
it('fallback content with multiple select', function () {
4949
el.innerHTML = '<p class="b">select b</p>'
50-
options.template = '<content select=".a"><p>fallback a</p></content><content select=".b">fallback b</content>'
50+
options.template =
51+
'<content select=".a"><p>fallback a</p></content>' +
52+
'<content select=".b">fallback b</content>'
5153
mount()
5254
expect(el.childNodes.length).toBe(2)
5355
expect(el.firstChild.textContent).toBe('fallback a')
@@ -61,6 +63,16 @@ describe('Content Transclusion', function () {
6163
expect(el.innerHTML).toBe('<p class="t">1</p><p class="t">2</p>')
6264
})
6365

66+
it('default content should only render parts not selected', function () {
67+
el.innerHTML = '<div>hi</div><p class="a">1</p><p class="b">2</p>'
68+
options.template =
69+
'<content select=".a"></content>' +
70+
'<content></content>' +
71+
'<content select=".b"></content>'
72+
mount()
73+
expect(el.innerHTML).toBe('<p class="a">1</p><div>hi</div><p class="b">2</p>')
74+
})
75+
6476
it('content transclusion with replace', function () {
6577
el.innerHTML = '<p>hi</p>'
6678
options.template = '<div><div><content></content></div></div>'
@@ -92,8 +104,14 @@ describe('Content Transclusion', function () {
92104
})
93105

94106
it('select should only match children', function () {
95-
el.innerHTML = '<p class="b">select b</p><span><p class="b">nested b</p></span><span><p class="c">nested c</p></span>'
96-
options.template = '<content select=".a"><p>fallback a</p></content><content select=".b">fallback b</content><content select=".c">fallback c</content>'
107+
el.innerHTML =
108+
'<p class="b">select b</p>' +
109+
'<span><p class="b">nested b</p></span>' +
110+
'<span><p class="c">nested c</p></span>'
111+
options.template =
112+
'<content select=".a"><p>fallback a</p></content>' +
113+
'<content select=".b">fallback b</content>' +
114+
'<content select=".c">fallback c</content>'
97115
mount()
98116
expect(el.childNodes.length).toBe(3)
99117
expect(el.firstChild.textContent).toBe('fallback a')

0 commit comments

Comments
 (0)