UNPKG

8.09 kBJavaScriptView Raw
1/**
2 * @license
3 * Copyright 2015 Google Inc. All Rights Reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18(function() {
19 'use strict';
20
21 /**
22 * Class constructor for Textfield MDL component.
23 * Implements MDL component design pattern defined at:
24 * https://github.com/jasonmayes/mdl-component-design-pattern
25 *
26 * @constructor
27 * @param {HTMLElement} element The element that will be upgraded.
28 */
29 var MaterialTextfield = function MaterialTextfield(element) {
30 this.element_ = element;
31 this.maxRows = this.Constant_.NO_MAX_ROWS;
32 // Initialize instance.
33 this.init();
34 };
35 window['MaterialTextfield'] = MaterialTextfield;
36
37 /**
38 * Store constants in one place so they can be updated easily.
39 *
40 * @enum {string | number}
41 * @private
42 */
43 MaterialTextfield.prototype.Constant_ = {
44 NO_MAX_ROWS: -1,
45 MAX_ROWS_ATTRIBUTE: 'maxrows'
46 };
47
48 /**
49 * Store strings for class names defined by this component that are used in
50 * JavaScript. This allows us to simply change it in one place should we
51 * decide to modify at a later date.
52 *
53 * @enum {string}
54 * @private
55 */
56 MaterialTextfield.prototype.CssClasses_ = {
57 LABEL: 'mdl-textfield__label',
58 INPUT: 'mdl-textfield__input',
59 IS_DIRTY: 'is-dirty',
60 IS_FOCUSED: 'is-focused',
61 IS_DISABLED: 'is-disabled',
62 IS_INVALID: 'is-invalid',
63 IS_UPGRADED: 'is-upgraded'
64 };
65
66 /**
67 * Handle input being entered.
68 *
69 * @param {Event} event The event that fired.
70 * @private
71 */
72 MaterialTextfield.prototype.onKeyDown_ = function(event) {
73 var currentRowCount = event.target.value.split('\n').length;
74 if (event.keyCode === 13) {
75 if (currentRowCount >= this.maxRows) {
76 event.preventDefault();
77 }
78 }
79 };
80
81 /**
82 * Handle focus.
83 *
84 * @param {Event} event The event that fired.
85 * @private
86 */
87 MaterialTextfield.prototype.onFocus_ = function(event) {
88 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
89 };
90
91 /**
92 * Handle lost focus.
93 *
94 * @param {Event} event The event that fired.
95 * @private
96 */
97 MaterialTextfield.prototype.onBlur_ = function(event) {
98 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
99 };
100
101 /**
102 * Handle reset event from out side.
103 *
104 * @param {Event} event The event that fired.
105 * @private
106 */
107 MaterialTextfield.prototype.onReset_ = function(event) {
108 this.updateClasses_();
109 };
110
111 /**
112 * Handle class updates.
113 *
114 * @private
115 */
116 MaterialTextfield.prototype.updateClasses_ = function() {
117 this.checkDisabled();
118 this.checkValidity();
119 this.checkDirty();
120 this.checkFocus();
121 };
122
123 // Public methods.
124
125 /**
126 * Check the disabled state and update field accordingly.
127 *
128 * @public
129 */
130 MaterialTextfield.prototype.checkDisabled = function() {
131 if (this.input_.disabled) {
132 this.element_.classList.add(this.CssClasses_.IS_DISABLED);
133 } else {
134 this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
135 }
136 };
137 MaterialTextfield.prototype['checkDisabled'] =
138 MaterialTextfield.prototype.checkDisabled;
139
140 /**
141 * Check the focus state and update field accordingly.
142 *
143 * @public
144 */
145 MaterialTextfield.prototype.checkFocus = function() {
146 if (Boolean(this.element_.querySelector(':focus'))) {
147 this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
148 } else {
149 this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
150 }
151 };
152 MaterialTextfield.prototype['checkFocus'] =
153 MaterialTextfield.prototype.checkFocus;
154
155 /**
156 * Check the validity state and update field accordingly.
157 *
158 * @public
159 */
160 MaterialTextfield.prototype.checkValidity = function() {
161 if (this.input_.validity) {
162 if (this.input_.validity.valid) {
163 this.element_.classList.remove(this.CssClasses_.IS_INVALID);
164 } else {
165 this.element_.classList.add(this.CssClasses_.IS_INVALID);
166 }
167 }
168 };
169 MaterialTextfield.prototype['checkValidity'] =
170 MaterialTextfield.prototype.checkValidity;
171
172 /**
173 * Check the dirty state and update field accordingly.
174 *
175 * @public
176 */
177 MaterialTextfield.prototype.checkDirty = function() {
178 if (this.input_.value && this.input_.value.length > 0) {
179 this.element_.classList.add(this.CssClasses_.IS_DIRTY);
180 } else {
181 this.element_.classList.remove(this.CssClasses_.IS_DIRTY);
182 }
183 };
184 MaterialTextfield.prototype['checkDirty'] =
185 MaterialTextfield.prototype.checkDirty;
186
187 /**
188 * Disable text field.
189 *
190 * @public
191 */
192 MaterialTextfield.prototype.disable = function() {
193 this.input_.disabled = true;
194 this.updateClasses_();
195 };
196 MaterialTextfield.prototype['disable'] = MaterialTextfield.prototype.disable;
197
198 /**
199 * Enable text field.
200 *
201 * @public
202 */
203 MaterialTextfield.prototype.enable = function() {
204 this.input_.disabled = false;
205 this.updateClasses_();
206 };
207 MaterialTextfield.prototype['enable'] = MaterialTextfield.prototype.enable;
208
209 /**
210 * Update text field value.
211 *
212 * @param {string} value The value to which to set the control (optional).
213 * @public
214 */
215 MaterialTextfield.prototype.change = function(value) {
216
217 this.input_.value = value || '';
218 this.updateClasses_();
219 };
220 MaterialTextfield.prototype['change'] = MaterialTextfield.prototype.change;
221
222 /**
223 * Initialize element.
224 */
225 MaterialTextfield.prototype.init = function() {
226
227 if (this.element_) {
228 this.label_ = this.element_.querySelector('.' + this.CssClasses_.LABEL);
229 this.input_ = this.element_.querySelector('.' + this.CssClasses_.INPUT);
230
231 if (this.input_) {
232 if (this.input_.hasAttribute(
233 /** @type {string} */ (this.Constant_.MAX_ROWS_ATTRIBUTE))) {
234 this.maxRows = parseInt(this.input_.getAttribute(
235 /** @type {string} */ (this.Constant_.MAX_ROWS_ATTRIBUTE)), 10);
236 if (isNaN(this.maxRows)) {
237 this.maxRows = this.Constant_.NO_MAX_ROWS;
238 }
239 }
240
241 this.boundUpdateClassesHandler = this.updateClasses_.bind(this);
242 this.boundFocusHandler = this.onFocus_.bind(this);
243 this.boundBlurHandler = this.onBlur_.bind(this);
244 this.boundResetHandler = this.onReset_.bind(this);
245 this.input_.addEventListener('input', this.boundUpdateClassesHandler);
246 this.input_.addEventListener('focus', this.boundFocusHandler);
247 this.input_.addEventListener('blur', this.boundBlurHandler);
248 this.input_.addEventListener('reset', this.boundResetHandler);
249
250 if (this.maxRows !== this.Constant_.NO_MAX_ROWS) {
251 // TODO: This should handle pasting multi line text.
252 // Currently doesn't.
253 this.boundKeyDownHandler = this.onKeyDown_.bind(this);
254 this.input_.addEventListener('keydown', this.boundKeyDownHandler);
255 }
256 var invalid = this.element_.classList
257 .contains(this.CssClasses_.IS_INVALID);
258 this.updateClasses_();
259 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
260 if (invalid) {
261 this.element_.classList.add(this.CssClasses_.IS_INVALID);
262 }
263 if (this.input_.hasAttribute('autofocus')) {
264 this.element_.focus();
265 this.checkFocus();
266 }
267 }
268 }
269 };
270
271 // The component registers itself. It can assume componentHandler is available
272 // in the global scope.
273 componentHandler.register({
274 constructor: MaterialTextfield,
275 classAsString: 'MaterialTextfield',
276 cssClass: 'mdl-js-textfield',
277 widget: true
278 });
279})();