Added support for namespace attributes
Federico Rampazzo committed
May 5, 2015 at 16:08 UTC
a88b655ccd6f6519fb25fb8e81161f80274b9b02
6 files changed
+162
-3
src/browser/__tests__/ReactDOMSVG-test.js
new
+90
@@ -0,0 +1,90 @@
1
+/**
2
+ * Copyright 2013-2015, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @emails react-core
10
+ */
11
+
12
+/*jslint evil: true */
13
+
14
+'use strict';
15
+
16
+var React = require('React');
17
+var ReactTestUtils = require('ReactTestUtils');
18
+
19
+var SVGDOMNamespaces = {
20
+ xlink: 'http://www.w3.org/1999/xlink',
21
+ xml: 'http://www.w3.org/XML/1998/namespace',
22
+ xmlns: 'http://www.w3.org/2000/xmlns/'
23
+};
24
+
25
+describe('ReactDOMSVG', function() {
26
+ it("allows a SVG element", function() {
27
+ var element = React.createElement('svg', {xmlnsXlink: 'http://www.w3.org/1999/xlink'});
28
+ var instance = ReactTestUtils.renderIntoDocument(element);
29
+ var svg = React.findDOMNode(instance);
30
+ expect(svg.tagName).toBe('svg');
31
+ var xlink = svg.getAttributeNS(SVGDOMNamespaces.xmlns, 'xlink');
32
+ expect(xlink).toBe('http://www.w3.org/1999/xlink');
33
+ });
34
+
35
+ it("allows a SVG element with an image node", function() {
36
+ var instance = ReactTestUtils.renderIntoDocument(
37
+ <svg xmlnsXlink='http://www.w3.org/1999/xlink'>
38
+ <image xlinkHref="http://i.imgur.com/w7GCRPb.png" />
39
+ </svg>
40
+ );
41
+ var svg = React.findDOMNode(instance);
42
+ var image = svg.childNodes[0];
43
+ expect(image.tagName).toBe('image');
44
+ var href = image.getAttributeNS(SVGDOMNamespaces.xlink, 'href');
45
+ expect(href).toBe('http://i.imgur.com/w7GCRPb.png');
46
+ });
47
+
48
+ it("allows a SVG element with a link", function() {
49
+ var instance = ReactTestUtils.renderIntoDocument(
50
+ <svg xmlnsXlink='http://www.w3.org/1999/xlink'>
51
+ <g>
52
+ <a
53
+ xlinkHref="http://facebook.github.io/react/"
54
+ xlinkActuate="onRequest"
55
+ xlinkShow="new"
56
+ xlinkArcrole="http://example.com/iri-arcrole-reference.svg"
57
+ xlinkRole="http://example.com/iri-role-reference.svg"
58
+ xlinkTitle="React"
59
+ xlinkType="simple"
60
+ >
61
+ <text xmlLang="en-US" xmlSpace="preserve" x="0" y="15" fill="black">React</text>
62
+ </a>
63
+ </g>
64
+ </svg>
65
+ );
66
+ var svg = React.findDOMNode(instance);
67
+ var link = svg.childNodes[0].childNodes[0];
68
+ expect(link.tagName).toBe('a');
69
+ var href = link.getAttributeNS(SVGDOMNamespaces.xlink, 'href');
70
+ expect(href).toBe('http://facebook.github.io/react/');
71
+ var actuate = link.getAttributeNS(SVGDOMNamespaces.xlink, 'actuate');
72
+ expect(actuate).toBe('onRequest');
73
+ var show = link.getAttributeNS(SVGDOMNamespaces.xlink, 'show');
74
+ expect(show).toBe('new');
75
+ var arcrole = link.getAttributeNS(SVGDOMNamespaces.xlink, 'arcrole');
76
+ expect(arcrole).toBe('http://example.com/iri-arcrole-reference.svg');
77
+ var role = link.getAttributeNS(SVGDOMNamespaces.xlink, 'role');
78
+ expect(role).toBe('http://example.com/iri-role-reference.svg');
79
+ var title = link.getAttributeNS(SVGDOMNamespaces.xlink, 'title');
80
+ expect(title).toBe('React');
81
+ var type = link.getAttributeNS(SVGDOMNamespaces.xlink, 'type');
82
+ expect(type).toBe('simple');
83
+ var text = link.childNodes[0];
84
+ var lang = text.getAttributeNS(SVGDOMNamespaces.xml, 'lang');
85
+ expect(lang).toBe('en-US');
86
+ var space = text.getAttributeNS(SVGDOMNamespaces.xml, 'space');
87
+ expect(space).toBe('preserve');
88
+ });
89
+
90
+});
\ No newline at end of file
src/browser/ui/dom/DOMProperty.js
+12
@@ -60,6 +60,7 @@ var DOMPropertyInjection = {
60
*/
61
injectDOMPropertyConfig: function(domPropertyConfig) {
62
var Properties = domPropertyConfig.Properties || {};
63
+ var PropertyNamespaces = domPropertyConfig.PropertyNamespaces || {};
64
var DOMAttributeNames = domPropertyConfig.DOMAttributeNames || {};
65
var DOMPropertyNames = domPropertyConfig.DOMPropertyNames || {};
66
var DOMMutationMethods = domPropertyConfig.DOMMutationMethods || {};
@@ -93,6 +94,11 @@ var DOMPropertyInjection = {
94
DOMProperty.getAttributeName[propName] = lowerCased;
95
}
96
97
+ if (PropertyNamespaces.hasOwnProperty(propName)) {
98
+ DOMProperty.getAttributeNamespace[propName] =
99
+ PropertyNamespaces[propName];
100
+ }
101
+
102
DOMProperty.getPropertyName[propName] =
103
DOMPropertyNames.hasOwnProperty(propName) ?
104
DOMPropertyNames[propName] :
@@ -182,6 +188,12 @@ var DOMProperty = {
188
*/
189
getAttributeName: {},
190
191
+ /**
192
+ * Mapping from normalized names to namespaces.
193
+ * @type {Object}
194
+ */
195
+ getAttributeNamespace: {},
196
+
197
/**
198
* Mapping from normalized names to properties on DOM node instances.
199
* (This includes properties that mutate due to external factors.)
src/browser/ui/dom/DOMPropertyOperations.js
+7
-1
@@ -126,9 +126,15 @@ var DOMPropertyOperations = {
126
} else if (shouldIgnoreValue(name, value)) {
127
this.deleteValueForProperty(node, name);
128
} else if (DOMProperty.mustUseAttribute[name]) {
129
+ var attributeName = DOMProperty.getAttributeName[name];
130
+ var namespace = DOMProperty.getAttributeNamespace[name];
131
// `setAttribute` with objects becomes only `[object]` in IE8/9,
132
// ('' + value) makes it output the correct toString()-value.
131
- node.setAttribute(DOMProperty.getAttributeName[name], '' + value);
133
+ if (namespace) {
134
+ node.setAttributeNS(namespace, attributeName, '' + value);
135
+ } else {
136
+ node.setAttribute(attributeName, '' + value);
137
+ }
138
} else {
139
var propName = DOMProperty.getPropertyName[name];
140
// Must explicitly cast values for HAS_SIDE_EFFECTS-properties to the
src/browser/ui/dom/HTMLDOMPropertyConfig.js
+4
-1
@@ -39,7 +39,6 @@ if (ExecutionEnvironment.canUseDOM) {
39
);
40
}
41
42
-
42
var HTMLDOMPropertyConfig = {
43
isCustomAttribute: RegExp.prototype.test.bind(
44
/^(data|aria)-[a-z_][a-z\d_.\-]*$/
@@ -160,6 +159,7 @@ var HTMLDOMPropertyConfig = {
159
value: MUST_USE_PROPERTY | HAS_SIDE_EFFECTS,
160
width: MUST_USE_ATTRIBUTE,
161
wmode: MUST_USE_ATTRIBUTE,
162
+ xmlns: MUST_USE_ATTRIBUTE,
163
164
/**
165
* Non-standard Properties
@@ -183,6 +183,9 @@ var HTMLDOMPropertyConfig = {
183
// IE-only attribute that controls focus behavior
184
unselectable: MUST_USE_ATTRIBUTE
185
},
186
+ PropertyNamespaces: {
187
+ xmlns: 'http://www.w3.org/2000/xmlns/'
188
+ },
189
DOMAttributeNames: {
190
acceptCharset: 'accept-charset',
191
className: 'class',
src/browser/ui/dom/SVGDOMPropertyConfig.js
+42
-1
@@ -17,6 +17,12 @@ var DOMProperty = require('DOMProperty');
17
18
var MUST_USE_ATTRIBUTE = DOMProperty.injection.MUST_USE_ATTRIBUTE;
19
20
+var SVGDOMNamespaces = {
21
+ xlink: 'http://www.w3.org/1999/xlink',
22
+ xml: 'http://www.w3.org/XML/1998/namespace',
23
+ xmlns: 'http://www.w3.org/2000/xmlns/'
24
+};
25
+
26
var SVGDOMPropertyConfig = {
27
Properties: {
28
clipPath: MUST_USE_ATTRIBUTE,
@@ -60,10 +66,34 @@ var SVGDOMPropertyConfig = {
66
x1: MUST_USE_ATTRIBUTE,
67
x2: MUST_USE_ATTRIBUTE,
68
x: MUST_USE_ATTRIBUTE,
69
+ xlinkActuate: MUST_USE_ATTRIBUTE,
70
+ xlinkArcrole: MUST_USE_ATTRIBUTE,
71
+ xlinkHref: MUST_USE_ATTRIBUTE,
72
+ xlinkRole: MUST_USE_ATTRIBUTE,
73
+ xlinkShow: MUST_USE_ATTRIBUTE,
74
+ xlinkTitle: MUST_USE_ATTRIBUTE,
75
+ xlinkType: MUST_USE_ATTRIBUTE,
76
+ xmlBase: MUST_USE_ATTRIBUTE,
77
+ xmlLang: MUST_USE_ATTRIBUTE,
78
+ xmlSpace: MUST_USE_ATTRIBUTE,
79
+ xmlnsXlink: MUST_USE_ATTRIBUTE,
80
y1: MUST_USE_ATTRIBUTE,
81
y2: MUST_USE_ATTRIBUTE,
82
y: MUST_USE_ATTRIBUTE
83
},
84
+ PropertyNamespaces: {
85
+ xlinkActuate: SVGDOMNamespaces.xlink,
86
+ xlinkArcrole: SVGDOMNamespaces.xlink,
87
+ xlinkHref: SVGDOMNamespaces.xlink,
88
+ xlinkRole: SVGDOMNamespaces.xlink,
89
+ xlinkShow: SVGDOMNamespaces.xlink,
90
+ xlinkTitle: SVGDOMNamespaces.xlink,
91
+ xlinkType: SVGDOMNamespaces.xlink,
92
+ xmlBase: SVGDOMNamespaces.xml,
93
+ xmlLang: SVGDOMNamespaces.xml,
94
+ xmlSpace: SVGDOMNamespaces.xml,
95
+ xmlnsXlink: SVGDOMNamespaces.xmlns
96
+ },
97
DOMAttributeNames: {
98
clipPath: 'clip-path',
99
fillOpacity: 'fill-opacity',
@@ -85,7 +115,18 @@ var SVGDOMPropertyConfig = {
115
strokeOpacity: 'stroke-opacity',
116
strokeWidth: 'stroke-width',
117
textAnchor: 'text-anchor',
88
- viewBox: 'viewBox'
118
+ viewBox: 'viewBox',
119
+ xlinkActuate: 'xlink:actuate',
120
+ xlinkArcrole: 'xlink:arcrole',
121
+ xlinkHref: 'xlink:href',
122
+ xlinkRole: 'xlink:role',
123
+ xlinkShow: 'xlink:show',
124
+ xlinkTitle: 'xlink:title',
125
+ xlinkType: 'xlink:type',
126
+ xmlBase: 'xml:base',
127
+ xmlLang: 'xml:lang',
128
+ xmlSpace: 'xml:space',
129
+ xmlnsXlink: 'xmlns:xlink'
130
}
131
};
132
src/browser/ui/dom/__tests__/DOMPropertyOperations-test.js
+7
@@ -181,9 +181,11 @@ describe('DOMPropertyOperations', function() {
181
182
describe('setValueForProperty', function() {
183
var stubNode;
184
+ var stubSVGNode;
185
186
beforeEach(function() {
187
stubNode = document.createElement('div');
188
+ stubSVGNode = document.createElement('svg');
189
});
190
191
it('should set values as properties by default', function() {
@@ -196,6 +198,11 @@ describe('DOMPropertyOperations', function() {
198
expect(stubNode.getAttribute('role')).toBe('#');
199
expect(stubNode.role).toBeUndefined();
200
});
201
+
202
+ it('should set values as namespace attributes if necessary', function() {
203
+ DOMPropertyOperations.setValueForProperty(stubSVGNode, 'xmlnsXlink', 'http://www.w3.org/1999/xlink');
204
+ expect(stubSVGNode.getAttribute('xmlns:xlink')).toBe('http://www.w3.org/1999/xlink');
205
+ });
206
207
it('should convert attribute values to string first', function() {
208
// Browsers default to this behavior, but some test environments do not.