8000 Use more robust string -> fragment conversion · hwclass/vue@c941fd6 · GitHub
[go: up one dir, main page]

Skip to content

Commit c941fd6

Browse files
committed
Use more robust string -> fragment conversion
Using the wrapper technique from jQuery so that utils.toFragment() can deal with table elements and SVG elements (with proper namespace)
1 parent 6be817e commit c941fd6

File tree

7 files changed

+117
-46
lines changed

7 files changed

+117
-46
lines changed

component.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"src/emitter.js",
1212
"src/config.js",
1313
"src/utils.js",
14+
"src/fragment.js",
1415
"src/compiler.js",
1516
"src/viewmodel.js",
1617
"src/binding.js",

src/compiler.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,8 @@ CompilerProto.setupElement = function (options) {
222222
}
223223
// replace option: use the first node in
224224
// the template directly
225-
if (options.replace && template.childNodes.length === 1) {
226-
replacer = template.childNodes[0].cloneNode(true)
225+
if (options.replace && template.firstChild === template.lastChild) {
226+
replacer = template.firstChild.cloneNode(true)
227227
if (el.parentNode) {
228228
el.parentNode.insertBefore(replacer, el)
229229
el.parentNode.removeChild(el)

src/directives/html.js

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
var guard = require('../utils').guard,
1+
var utils = require('../utils'),
22
slice = [].slice
33

44
/**
@@ -11,14 +11,13 @@ module.exports = {
1111
// {{{ inline unescaped html }}}
1212
if (this.el.nodeType === 8) {
1313
// hold nodes
14-
this.holder = document.createElement('div')
1514
this.nodes = []
1615
}
1716
},
1817

1918
update: function (value) {
20-
value = guard(value)
21-
if (this.holder) {
19+
value = utils.guard(value)
20+
if (this.nodes) {
2221
this.swap(value)
2322
} else {
2423
this.el.innerHTML = value
@@ -27,16 +26,16 @@ module.exports = {
2726

2827
swap: function (value) {
2928
var parent = this.el.parentNode,
30-
holder = this.holder,
31-
nodes = this.nodes,
32-
i = nodes.length, l
29+
nodes = this.nodes,
30+
i = nodes.length
31+
// remove old nodes
3332
while (i--) {
3433
parent.removeChild(nodes[i])
3534
}
36-
holder.innerHTML = value
37-
nodes = this.nodes = slice.call(holder.childNodes)
38-
for (i = 0, l = nodes.length; i < l; i++) {
39-
parent.insertBefore(nodes[i], this.el)
40-
}
35+
// convert new value to a fragment
36+
var frag = utils.toFragment(value)
37+
// save a reference to these nodes so we can remove later
38+
this.nodes = slice.call(frag.childNodes)
39+
parent.insertBefore(frag, this.el)
4140
}
4241
}

src/fragment.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// string -> DOM conversion
2+
// wrappers originally from jQuery, scooped from component/domify
3+
var map = {
4+
legend : [1, '<fieldset>', '</fieldset>'],
5+
tr : [2, '<table><tbody>', '</tbody></table>'],
6+
col : [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
7+
_default : [0, '', '']
8+
}
9+
10+
map.td =
11+
map.th = [3, '<table><tbody><tr>', '</tr></tbody></table>']
12+
13+
map.option =
14+
map.optgroup = [1, '<select multiple="multiple">', '</select>']
15+
16+
map.thead =
17+
map.tbody =
18+
map.colgroup =
19+
map.caption =
20+
map.tfoot = [1, '<table>', '</table>']
21+
22+
map.text =
23+
map.circle =
24+
map.ellipse =
25+
map.line =
26+
map.path =
27+
map.polygon =
28+
map.polyline =
29+
map.rect = [1, '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">','</svg>']
30+
31+
var TAG_RE = /<([\w:]+)/
32+
33+
module.exports = function (template) {
34+
35+
if (typeof template !== 'string') {
36+
return template
37+
}
38+
39+
// template by ID
40+
if (template.charAt(0) === '#') {
41+
var templateNode = document.getElementById(template.slice(1))
42+
if (!templateNode) return
43+
// if its a template tag and the browser supports it,
44+
// its content is already a document fragment!
45+
if (templateNode.tagName === 'TEMPLATE' && templateNode.content) {
46+
return templateNode.content
47+
}
48+
template = templateNode.innerHTML
49+
}
50+
51+
var frag = document.createDocumentFragment(),
52+
m = TAG_RE.exec(template)
53+
// text only
54+
if (!m) {
55+
frag.appendChild(document.createTextNode(template))
56+
return frag
57+
}
58+
59+
var tag = m[1],
60+
wrap = map[tag] || map._default,
61+
depth = wrap[0],
62+
prefix = wrap[1],
63+
suffix = wrap[2],
64+
node = document.createElement('div')
65+
66+
node.innerHTML = prefix + template.trim() + suffix
67+
while (depth--) node = node.lastChild
68+
69+
// one element
70+
if (node.firstChild === node.lastChild) {
71+
frag.appendChild(node.firstChild)
72+
return frag
73+
}
74+
75+
// multiple nodes, return a fragment
76+
var child
77+
/* jshint boss: true */
78+
while (child = node.firstChild) {
79+
if (node.nodeType === 1) {
80+
frag.appendChild(child)
81+
}
82+
}
83+
return frag
84+
}

src/utils.js

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@ var config = require('./config'),
44
console = win.console,
55
timeout = win.setTimeout,
66
def = Object.defineProperty,
7-
THIS_RE = /[^\w]this[^\w]/,
87
OBJECT = 'object',
8+
THIS_RE = /[^\w]this[^\w]/,
99
hasClassList = 'classList' in document.documentElement,
1010
ViewModel // late def
1111

1212
var utils = module.exports = {
1313

14+
/**
15+
* Convert a string template to a dom fragment
16+
*/
17+
toFragment: require('./fragment'),
18+
1419
/**
1520
* get a value from an object keypath
1621
*/
@@ -164,36 +169,6 @@ var utils = module.exports = {
164169
return res
165170
},
166171

167-
/**
168-
* Convert a string template to a dom fragment
169-
*/
170-
toFragment: function (template) {
171-
if (typeof template !== 'string') {
172-
return template
173-
}
174-
if (template.charAt(0) === '#') {
175-
var templateNode = document.getElementById(template.slice(1))
176-
if (!templateNode) return
177-
// if its a template tag and the browser supports it,
178-
// its content is already a document fragment!
179-
if (templateNode.tagName === 'TEMPLATE' && templateNode.content) {
180-
return templateNode.content
181-
}
182-
template = templateNode.innerHTML
183-
}
184-
var node = document.createElement('div'),
185-
frag = document.createDocumentFragment(),
186-
child
187-
node.innerHTML = template.trim()
188-
/* jshint boss: true */
189-
while (child = node.firstChild) {
190-
if (node.nodeType === 1) {
191-
frag.appendChild(child)
192-
}
193-
}
194-
return frag
195-
},
196-
197172
/**
198173
* Convert the object to a ViewModel constructor
199174
* if it is not already one

test/unit/specs/directives.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ describe('Directives', function () {
120120
dir.el = comment
121121

122122
dir.bind()
123-
assert.ok(dir.holder)
124123
assert.ok(dir.nodes)
125124

126125
var pre = 'what!',

test/unit/specs/utils.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,19 @@ describe('Utils', function () {
223223
assert.equal(frag.querySelector('p').textContent, 'ha')
224224
})
225225

226+
it('should work with table elements', function () {
227+
var frag = utils.toFragment('<td></td>')
228+
assert.ok(frag instanceof window.DocumentFragment)
229+
assert.equal(frag.firstChild.tagName, 'TD')
230+
})
231+
232+
it('should work with SVG elements', function () {
233+
var frag = utils.toFragment('<polygon></polygon>')
234+
assert.ok(frag instanceof window.DocumentFragment)
235+
assert.equal(frag.firstChild.tagName.toLowerCase(), 'polygon')
236+
assert.equal(frag.firstChild.namespaceURI, 'http://www.w3.org/2000/svg')
237+
})
238+
226239
})
227240

228241
describe('toConstructor', function () {

0 commit comments

Comments
 (0)
0