add experimental svg support

- supports nested svg elements
- supports svg attributes
- supports xlink
This commit is contained in:
Leo Horie 2014-03-26 23:05:14 -04:00
parent 7e2136ba51
commit 48025dd16a
8 changed files with 87 additions and 18 deletions

View file

@ -32,7 +32,7 @@ new function(window) {
}
return cell
}
function build(parent, data, cached) {
function build(parent, data, cached, namespace) {
if (data === null || data === undefined) return
var cachedType = type.call(cached), dataType = type.call(data)
@ -45,7 +45,7 @@ new function(window) {
if (dataType == "[object Array]") {
var nodes = [], intact = cached.length === data.length
for (var i = 0; i < data.length; i++) {
var item = build(parent, data[i], cached[i])
var item = build(parent, data[i], cached[i], namespace)
if (item === undefined) continue
if (!item.nodes.intact) intact = false
cached[i] = item
@ -63,15 +63,16 @@ new function(window) {
if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join()) clear(cached.nodes)
var node, isNew = cached.nodes.length === 0
if (data.tag === "svg") namespace = "http://www.w3.org/2000/svg"
if (isNew) {
node = window.document.createElement(data.tag)
cached = {tag: data.tag, attrs: setAttributes(node, data.attrs, {}), children: build(node, data.children, cached.children), nodes: [node]}
node = namespace === undefined ? window.document.createElement(data.tag) : window.document.createElementNS(namespace, data.tag)
cached = {tag: data.tag, attrs: setAttributes(node, data.attrs, {}, namespace), children: build(node, data.children, cached.children, namespace), nodes: [node]}
parent.appendChild(node)
}
else {
node = cached.nodes[0]
setAttributes(node, data.attrs, cached.attrs)
cached.children = build(node, data.children, cached.children)
setAttributes(node, data.attrs, cached.attrs, namespace)
cached.children = build(node, data.children, cached.children, namespace)
cached.nodes.intact = true
}
if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew)
@ -116,7 +117,7 @@ new function(window) {
return cached
}
function setAttributes(node, dataAttrs, cachedAttrs) {
function setAttributes(node, dataAttrs, cachedAttrs, namespace) {
for (var attrName in dataAttrs) {
var dataAttr = dataAttrs[attrName]
if (!(attrName in cachedAttrs) || (cachedAttrs[attrName] !== dataAttr)) {
@ -124,7 +125,8 @@ new function(window) {
if (attrName == "config") continue
if (attrName.indexOf("on") == 0 && typeof dataAttr == "function") dataAttr = autoredraw(dataAttr, node)
if (attrName == "style") for (var rule in dataAttr) node.style[rule] = dataAttr[rule]
else if (attrName in node) node[attrName] = dataAttr
else if (attrName in node && namespace === undefined) node[attrName] = dataAttr
else if (namespace !== undefined && attrName === "href") node.setAttributeNS("http://www.w3.org/1999/xlink", "href", dataAttr)
else node.setAttribute(attrName, dataAttr)
}
}
@ -453,11 +455,17 @@ mock.window = new function() {
setAttribute: function(name, value) {
this[name] = value.toString()
},
setAttributeNS: function(namespace, name, value) {
this[name] = value.toString()
},
getAttribute: function(name, value) {
return this[name]
},
}
}
window.document.createElementNS = function(namespace, tag) {
return window.document.createElement(tag)
}
window.document.createTextNode = function(text) {
return {nodeValue: text.toString()}
}
@ -537,6 +545,8 @@ function testMithril(mock) {
test(function() {return m("div", m("div")).attrs.tag === "div"}) //yes, this is expected behavior: see method signature
test(function() {return m("div", [undefined]).tag === "div"})
test(function() {return m("div", [{foo: "bar"}])}) //as long as it doesn't throw errors, it's fine
test(function() {return m("svg", [m("g")])})
test(function() {return m("svg", [m("a[href='http://google.com']")])})
//m.module
test(function() {
@ -621,6 +631,18 @@ function testMithril(mock) {
m.render(root, m("div", [undefined]))
return root.childNodes[0].childNodes.length === 0
})
test(function() {
var root = mock.document.createElement("div")
m.render(root, m("svg", [m("g")]))
console.log(root.childNodes[0].childNodes[0])
return root.childNodes[0].childNodes[0].nodeName === "G"
})
test(function() {
var root = mock.document.createElement("div")
m.render(root, m("svg", [m("a[href='http://google.com']")]))
console.log(root.childNodes[0].childNodes[0])
return root.childNodes[0].childNodes[0].nodeName === "A"
})
//m.redraw
test(function() {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -32,7 +32,7 @@ new function(window) {
}
return cell
}
function build(parent, data, cached) {
function build(parent, data, cached, namespace) {
if (data === null || data === undefined) return
var cachedType = type.call(cached), dataType = type.call(data)
@ -45,7 +45,7 @@ new function(window) {
if (dataType == "[object Array]") {
var nodes = [], intact = cached.length === data.length
for (var i = 0; i < data.length; i++) {
var item = build(parent, data[i], cached[i])
var item = build(parent, data[i], cached[i], namespace)
if (item === undefined) continue
if (!item.nodes.intact) intact = false
cached[i] = item
@ -63,15 +63,16 @@ new function(window) {
if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join()) clear(cached.nodes)
var node, isNew = cached.nodes.length === 0
if (data.tag === "svg") namespace = "http://www.w3.org/2000/svg"
if (isNew) {
node = window.document.createElement(data.tag)
cached = {tag: data.tag, attrs: setAttributes(node, data.attrs, {}), children: build(node, data.children, cached.children), nodes: [node]}
node = namespace === undefined ? window.document.createElement(data.tag) : window.document.createElementNS(namespace, data.tag)
cached = {tag: data.tag, attrs: setAttributes(node, data.attrs, {}, namespace), children: build(node, data.children, cached.children, namespace), nodes: [node]}
parent.appendChild(node)
}
else {
node = cached.nodes[0]
setAttributes(node, data.attrs, cached.attrs)
cached.children = build(node, data.children, cached.children)
setAttributes(node, data.attrs, cached.attrs, namespace)
cached.children = build(node, data.children, cached.children, namespace)
cached.nodes.intact = true
}
if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew)
@ -116,7 +117,7 @@ new function(window) {
return cached
}
function setAttributes(node, dataAttrs, cachedAttrs) {
function setAttributes(node, dataAttrs, cachedAttrs, namespace) {
for (var attrName in dataAttrs) {
var dataAttr = dataAttrs[attrName]
if (!(attrName in cachedAttrs) || (cachedAttrs[attrName] !== dataAttr)) {
@ -124,7 +125,8 @@ new function(window) {
if (attrName == "config") continue
if (attrName.indexOf("on") == 0 && typeof dataAttr == "function") dataAttr = autoredraw(dataAttr, node)
if (attrName == "style") for (var rule in dataAttr) node.style[rule] = dataAttr[rule]
else if (attrName in node) node[attrName] = dataAttr
else if (attrName in node && namespace === undefined) node[attrName] = dataAttr
else if (namespace !== undefined && attrName === "href") node.setAttributeNS("http://www.w3.org/1999/xlink", "href", dataAttr)
else node.setAttribute(attrName, dataAttr)
}
}

View file

@ -21,6 +21,8 @@ function testMithril(mock) {
test(function() {return m("div", m("div")).attrs.tag === "div"}) //yes, this is expected behavior: see method signature
test(function() {return m("div", [undefined]).tag === "div"})
test(function() {return m("div", [{foo: "bar"}])}) //as long as it doesn't throw errors, it's fine
test(function() {return m("svg", [m("g")])})
test(function() {return m("svg", [m("a[href='http://google.com']")])})
//m.module
test(function() {
@ -105,6 +107,18 @@ function testMithril(mock) {
m.render(root, m("div", [undefined]))
return root.childNodes[0].childNodes.length === 0
})
test(function() {
var root = mock.document.createElement("div")
m.render(root, m("svg", [m("g")]))
console.log(root.childNodes[0].childNodes[0])
return root.childNodes[0].childNodes[0].nodeName === "G"
})
test(function() {
var root = mock.document.createElement("div")
m.render(root, m("svg", [m("a[href='http://google.com']")]))
console.log(root.childNodes[0].childNodes[0])
return root.childNodes[0].childNodes[0].nodeName === "A"
})
//m.redraw
test(function() {

View file

@ -13,11 +13,17 @@ mock.window = new function() {
setAttribute: function(name, value) {
this[name] = value.toString()
},
setAttributeNS: function(namespace, name, value) {
this[name] = value.toString()
},
getAttribute: function(name, value) {
return this[name]
},
}
}
window.document.createElementNS = function(namespace, tag) {
return window.document.createElement(tag)
}
window.document.createTextNode = function(text) {
return {nodeValue: text.toString()}
}

25
tests/svg.html Normal file
View file

@ -0,0 +1,25 @@
<!doctype html>
<html>
<head></head>
<body>
<p>Since it's not possible to test SVG functionality from a NodeJS environment, this page can be used to test it in a browser.</p>
<p>There should be an HTML link labeled "HTML link", a SVG link labeled "SVG link", and a tilted blue square. The links should open in a new tab. All three items should display title tooltips.</p>
<div id="test"></div>
<script src="../mithril.js"></script>
<script>
var svg = [
m("p", [
m("a[href='http://google.com'][target='_blank'][title='HTML link']", "HTML link")
]),
m("svg[width=180][height=200]", [
m("rect[x=50][y=50][height=100][width=100][transform='translate(30) rotate(45 50 50)'][title='Square']", {style: {stroke: "#000", fill: "#0086b2"}}),
m("a[href='http://google.com'][title='SVG link'][target=_new]", {style: {textDecoration: "underline"}}, [
m("text[x=0][y=20]", "SVG Link")
])
])
]
m.render(document.getElementById("test"), svg)
</script>
</body>
</html>