UNPKG

7.49 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2013-present, Facebook, Inc.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 */
8
9'use strict';
10
11var DOMProperty = require('./DOMProperty');
12var ReactDOMComponentTree = require('./ReactDOMComponentTree');
13var ReactInstrumentation = require('./ReactInstrumentation');
14
15var quoteAttributeValueForBrowser = require('./quoteAttributeValueForBrowser');
16var warning = require('fbjs/lib/warning');
17
18var VALID_ATTRIBUTE_NAME_REGEX = new RegExp('^[' + DOMProperty.ATTRIBUTE_NAME_START_CHAR + '][' + DOMProperty.ATTRIBUTE_NAME_CHAR + ']*$');
19var illegalAttributeNameCache = {};
20var validatedAttributeNameCache = {};
21
22function isAttributeNameSafe(attributeName) {
23 if (validatedAttributeNameCache.hasOwnProperty(attributeName)) {
24 return true;
25 }
26 if (illegalAttributeNameCache.hasOwnProperty(attributeName)) {
27 return false;
28 }
29 if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) {
30 validatedAttributeNameCache[attributeName] = true;
31 return true;
32 }
33 illegalAttributeNameCache[attributeName] = true;
34 process.env.NODE_ENV !== 'production' ? warning(false, 'Invalid attribute name: `%s`', attributeName) : void 0;
35 return false;
36}
37
38function shouldIgnoreValue(propertyInfo, value) {
39 return value == null || propertyInfo.hasBooleanValue && !value || propertyInfo.hasNumericValue && isNaN(value) || propertyInfo.hasPositiveNumericValue && value < 1 || propertyInfo.hasOverloadedBooleanValue && value === false;
40}
41
42/**
43 * Operations for dealing with DOM properties.
44 */
45var DOMPropertyOperations = {
46 /**
47 * Creates markup for the ID property.
48 *
49 * @param {string} id Unescaped ID.
50 * @return {string} Markup string.
51 */
52 createMarkupForID: function (id) {
53 return DOMProperty.ID_ATTRIBUTE_NAME + '=' + quoteAttributeValueForBrowser(id);
54 },
55
56 setAttributeForID: function (node, id) {
57 node.setAttribute(DOMProperty.ID_ATTRIBUTE_NAME, id);
58 },
59
60 createMarkupForRoot: function () {
61 return DOMProperty.ROOT_ATTRIBUTE_NAME + '=""';
62 },
63
64 setAttributeForRoot: function (node) {
65 node.setAttribute(DOMProperty.ROOT_ATTRIBUTE_NAME, '');
66 },
67
68 /**
69 * Creates markup for a property.
70 *
71 * @param {string} name
72 * @param {*} value
73 * @return {?string} Markup string, or null if the property was invalid.
74 */
75 createMarkupForProperty: function (name, value) {
76 var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ? DOMProperty.properties[name] : null;
77 if (propertyInfo) {
78 if (shouldIgnoreValue(propertyInfo, value)) {
79 return '';
80 }
81 var attributeName = propertyInfo.attributeName;
82 if (propertyInfo.hasBooleanValue || propertyInfo.hasOverloadedBooleanValue && value === true) {
83 return attributeName + '=""';
84 }
85 return attributeName + '=' + quoteAttributeValueForBrowser(value);
86 } else if (DOMProperty.isCustomAttribute(name)) {
87 if (value == null) {
88 return '';
89 }
90 return name + '=' + quoteAttributeValueForBrowser(value);
91 }
92 return null;
93 },
94
95 /**
96 * Creates markup for a custom property.
97 *
98 * @param {string} name
99 * @param {*} value
100 * @return {string} Markup string, or empty string if the property was invalid.
101 */
102 createMarkupForCustomAttribute: function (name, value) {
103 if (!isAttributeNameSafe(name) || value == null) {
104 return '';
105 }
106 return name + '=' + quoteAttributeValueForBrowser(value);
107 },
108
109 /**
110 * Sets the value for a property on a node.
111 *
112 * @param {DOMElement} node
113 * @param {string} name
114 * @param {*} value
115 */
116 setValueForProperty: function (node, name, value) {
117 var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ? DOMProperty.properties[name] : null;
118 if (propertyInfo) {
119 var mutationMethod = propertyInfo.mutationMethod;
120 if (mutationMethod) {
121 mutationMethod(node, value);
122 } else if (shouldIgnoreValue(propertyInfo, value)) {
123 this.deleteValueForProperty(node, name);
124 return;
125 } else if (propertyInfo.mustUseProperty) {
126 // Contrary to `setAttribute`, object properties are properly
127 // `toString`ed by IE8/9.
128 node[propertyInfo.propertyName] = value;
129 } else {
130 var attributeName = propertyInfo.attributeName;
131 var namespace = propertyInfo.attributeNamespace;
132 // `setAttribute` with objects becomes only `[object]` in IE8/9,
133 // ('' + value) makes it output the correct toString()-value.
134 if (namespace) {
135 node.setAttributeNS(namespace, attributeName, '' + value);
136 } else if (propertyInfo.hasBooleanValue || propertyInfo.hasOverloadedBooleanValue && value === true) {
137 node.setAttribute(attributeName, '');
138 } else {
139 node.setAttribute(attributeName, '' + value);
140 }
141 }
142 } else if (DOMProperty.isCustomAttribute(name)) {
143 DOMPropertyOperations.setValueForAttribute(node, name, value);
144 return;
145 }
146
147 if (process.env.NODE_ENV !== 'production') {
148 var payload = {};
149 payload[name] = value;
150 ReactInstrumentation.debugTool.onHostOperation({
151 instanceID: ReactDOMComponentTree.getInstanceFromNode(node)._debugID,
152 type: 'update attribute',
153 payload: payload
154 });
155 }
156 },
157
158 setValueForAttribute: function (node, name, value) {
159 if (!isAttributeNameSafe(name)) {
160 return;
161 }
162 if (value == null) {
163 node.removeAttribute(name);
164 } else {
165 node.setAttribute(name, '' + value);
166 }
167
168 if (process.env.NODE_ENV !== 'production') {
169 var payload = {};
170 payload[name] = value;
171 ReactInstrumentation.debugTool.onHostOperation({
172 instanceID: ReactDOMComponentTree.getInstanceFromNode(node)._debugID,
173 type: 'update attribute',
174 payload: payload
175 });
176 }
177 },
178
179 /**
180 * Deletes an attributes from a node.
181 *
182 * @param {DOMElement} node
183 * @param {string} name
184 */
185 deleteValueForAttribute: function (node, name) {
186 node.removeAttribute(name);
187 if (process.env.NODE_ENV !== 'production') {
188 ReactInstrumentation.debugTool.onHostOperation({
189 instanceID: ReactDOMComponentTree.getInstanceFromNode(node)._debugID,
190 type: 'remove attribute',
191 payload: name
192 });
193 }
194 },
195
196 /**
197 * Deletes the value for a property on a node.
198 *
199 * @param {DOMElement} node
200 * @param {string} name
201 */
202 deleteValueForProperty: function (node, name) {
203 var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ? DOMProperty.properties[name] : null;
204 if (propertyInfo) {
205 var mutationMethod = propertyInfo.mutationMethod;
206 if (mutationMethod) {
207 mutationMethod(node, undefined);
208 } else if (propertyInfo.mustUseProperty) {
209 var propName = propertyInfo.propertyName;
210 if (propertyInfo.hasBooleanValue) {
211 node[propName] = false;
212 } else {
213 node[propName] = '';
214 }
215 } else {
216 node.removeAttribute(propertyInfo.attributeName);
217 }
218 } else if (DOMProperty.isCustomAttribute(name)) {
219 node.removeAttribute(name);
220 }
221
222 if (process.env.NODE_ENV !== 'production') {
223 ReactInstrumentation.debugTool.onHostOperation({
224 instanceID: ReactDOMComponentTree.getInstanceFromNode(node)._debugID,
225 type: 'remove attribute',
226 payload: name
227 });
228 }
229 }
230};
231
232module.exports = DOMPropertyOperations;
\No newline at end of file