Rift.IO OSM R1 Initial Submission
[osm/UI.git] / skyquake / framework / widgets / text-input / rw.text-input.js
1
2 /*
3 *
4 * Copyright 2016 RIFT.IO Inc
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19
20 var React = require('react');
21 var ButtonEventListenerMixin = require('../mixins/ButtonEventListener.js');
22 var validator = require('validator');
23
24 /**
25 * A text input component.
26 * It's props values and a brief description below
27 *
28 * value: Holds the initial text content of the input.
29 * label: The text content of the label.
30 * requiredText: The text content of the "if required" message.
31 * errorText: The text content of the error message.
32 * placeholder: The initial placeholder text of the input
33 * ClassName: Css Classes applied to the element.
34 * size: The size of the element.
35 * minWidth: Minimum width of the element.
36 * maxWidth: Maximum width of the element.
37 * isRequired: A boolean indicating whether or not the input is required.
38 * isDisabled: A boolean indicating the disabled state of the element.
39 * isReadOnly: A boolean indicating whether or not the input is read only.
40 * pattern: A regex putting constraints on what the user input can be.
41 * maxLength: The hard limit on how many characters can be in the input.
42 **/
43 module.exports = React.createClass({
44 displayName: "TextInput",
45 mixins:[ButtonEventListenerMixin],
46 propTypes: {
47 value: React.PropTypes.string,
48 label: React.PropTypes.string,
49 requiredText: React.PropTypes.string,
50 errorText: React.PropTypes.string,
51 placeholder: React.PropTypes.string,
52 className: React.PropTypes.string,
53 size: React.PropTypes.string,
54 minWidth: React.PropTypes.number,
55 maxWidth: React.PropTypes.number,
56 isRequired: React.PropTypes.bool,
57 isDisabled: React.PropTypes.bool,
58 isReadOnly: React.PropTypes.bool,
59 pattern: React.PropTypes.string,
60 maxLength: React.PropTypes.number,
61 instructions: React.PropTypes.string
62 },
63
64
65 /**
66 * Sets the default input state.
67 * If there is no description for the variable, assume it's the same as it's props counterpart.
68 *
69 * value: The current text contents of the input.
70 * isActive: Boolean to indicate if input is active.
71 * isHovered: Boolean to indicate if the input is being hovered over.
72 * isFocused: Boolean to indicate if the input has been focused.
73 * isDisabled: Boolean to indicate if input has been disabled.
74 *
75 * @returns {{sizeOfButton: (*|string), isActive: boolean, isHovered: boolean, isFocused: boolean, isDisabled: (*|boolean),
76 * isReadOnly: (*|.textInput.isReadOnly|.exports.propTypes.isReadOnly|.exports.getInitialState.isReadOnly|boolean),
77 * isRequired: (*|.textInput.isRequired|.exports.propTypes.isRequired|.exports.getInitialState.isRequired|isRequired|null),
78 * isValid: null, isSuccess: null}}
79 */
80 getInitialState: function() {
81 return {
82 value: this.props.value || '',
83 label: this.props.label || "",
84 requiredText: this.props.requiredText || "Required",
85 instructionsText: this.props.instructions || "",
86 errorText: "",
87 size: this.props.size || '',
88 isActive: false,
89 isHovered: false,
90 isFocused: false,
91 isDisabled: this.props.isDisabled || false,
92 isReadOnly: this.props.isReadOnly || false,
93 isRequired: this.props.isRequired || null,
94 isValid: null, // Three way bool. Valid: true. Invalid: false. Not acted on: null.
95 isSuccess: null // Three way bool. Success: true. Error: false. Not acted on: null.
96 }
97 },
98
99 /**
100 * If any of the state variables have changed, the component should update.
101 * "nextProps" and "nextState" hold the state and property variables as they will be after the update.
102 * "this.props" and "this.state" hold the state and property variables as they were before the update.
103 * returns true if the state have changed. Otherwise returns false.
104 * @param nextProps
105 * @param nextState
106 * @returns {boolean}
107 */
108 shouldComponentUpdate: function(nextProps, nextState) {
109 var currentStateString = this.state.isReadOnly + this.state.isDisabled + this.state.isActive + this.state.isFocused +
110 this.state.isHovered + this.state.isValid + this.state.isSuccess + this.state.value;
111 var nextStateString = nextState.isReadOnly + nextState.isDisabled + nextState.isActive + nextState.isFocused +
112 nextState.isHovered + nextState.isValid + nextState.isSuccess + nextState.value;
113 if (currentStateString == nextStateString) {
114 return false;
115 }
116 return true;
117 },
118
119 /**
120 * Makes sure that when the user types new input, the contents of the input changes accordingly.
121 *
122 * @param event
123 */
124 handleChange: function(event) {
125 this.setState({value:event.target.value});
126 },
127
128 /**
129 * Makes sure that when the user types new input, the contents of the input changes accordingly.
130 *
131 * @param event
132 */
133 handleValidation: function(event) {
134 if (this.props.isRequired) {
135 if (validator.isNull(event.target.value) || event.target.value == '') {
136 this.setState({
137 errorText: this.props.errorText,
138 isValid: false
139 });
140 } else {
141 this.setState({
142 errorText: '',
143 isValid: true
144 });
145 }
146 }
147 if (this.props.pattern) {
148 if (!validator.matches(event.target.value, this.props.pattern)) {
149 this.setState({
150 errorText: this.props.errorText,
151 isValid: false
152 });
153 } else {
154 this.setState({
155 errorText: '',
156 isValid: true
157 });
158 }
159 }
160
161 },
162
163 /**
164 * Returns a string reflecting the current state of the input.
165 * If the component state "isDisabled" is true, returns a string "disabled".
166 * If the component state "isReadOnly" is true, returns a string "readonly".
167 * Otherwise returns a string containing a word for each state that has been set to true.
168 * (ie "active focused" if the states active and focused are true, but hovered is false).
169 * @returns {string}
170 */
171 setComponentState: function() {
172 var ret = "";
173 if (this.state.isDisabled) {
174 return "disabled";
175 }
176 if (this.state.isReadOnly) {
177 return "readonly";
178 }
179 if (this.state.isActive) {
180 ret += "active ";
181 }
182 if (this.state.isHovered) {
183 ret += "hovered ";
184 }
185 if (this.state.isFocused) {
186 ret += "focused ";
187 }
188 return ret;
189 },
190
191 /**
192 * Renders the Text Input component.
193 *
194 **/
195 render: function() {
196 var value = this.state.value;
197 var input = null;
198 var input_style = {};
199 var input_state = this.setComponentState();
200
201 // The following if statements translates the min/max width from a number into a string.
202 if (this.props.minWidth) {
203 input_style['min-width'] = String(this.props.minWidth) + 'px';
204 }
205 if (this.props.maxWidth) {
206 input_style['max-width'] = String(this.props.maxWidth) + 'px';
207 }
208
209 // The input element.
210 input = React.createElement("input", {
211 ref: "inputRef",
212 "data-state": input_state,
213 "data-validate": this.state.isValid,
214 value: value,
215 placeholder: this.props.placeholder,
216 pattern: this.props.pattern,
217 maxLength: this.props.maxLength,
218 required: this.state.isRequired,
219 disabled: this.state.isDisabled,
220 readOnly: this.state.isReadOnly,
221 onChange: this.handleChange,
222 onClick: this.onClick,
223 onMouseUp: this.onMouseUp,
224 onMouseDown: this.onMouseDown,
225 onMouseOver: this.onMouseOver,
226 onMouseEnter: this.onMouseEnter,
227 onMouseLeave: this.onMouseLeave,
228 onMouseOut: this.onMouseOut,
229 onTouchCancel: this.onTouchCancel,
230 onTouchEnd: this.onTouchEnd,
231 onTouchMove: this.onTouchMove,
232 onTouchStart: this.onTouchStart,
233 onKeyDown: this.onKeyDown,
234 onKeyPress: this.onKeyPress,
235 onKeyUp: this.handleValidation,
236 onFocus: this.onFocus,
237 onBlur: this.handleValidation,
238 className: (this.props.className || "rw-textinput"),
239 tabIndex: 0
240 },
241 null
242 );
243
244 // The "if required" element. It displays a label if the element is required.
245 if(this.props.isRequired == true){
246 var requiredEle = React.createElement("small", {className: "rw-form__required-label"}, this.state.requiredText);
247 }
248
249 //
250 if(this.state.isValid == false) {
251 var validations = React.createElement("svg", {className: "rw-form__icon"}, this.state.errorText);
252 }
253 // The label element associated with the input.
254 var label = React.createElement("label", {className: "rw-form__label"}, this.state.label, requiredEle, input, validations);
255
256 // The "error" element. It pops up as a message if there is an error with the input.
257 if(this.state.errorText != "") {
258 var error = React.createElement("p", {className: "rw-form__message-error"}, this.state.errorText);
259 }
260
261 //
262 if(this.state.instructionsText != ""){
263 var instructions = React.createElement("p", {className: "rw-form__instructions"}, this.state.instructionsText)
264 }
265
266
267
268 // The parent element for all.
269 var ret = React.createElement("div", {
270 "data-state": input_state,
271 required: this.state.isRequired,
272 disabled: this.state.isDisabled,
273 readOnly: this.state.isReadOnly,
274 "data-validate": this.state.isValid,
275 className: "rw-form"
276 }, label, error, instructions);
277
278 return ret;
279 }
280 });