UNPKG

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