1 <!-- 2 @license 3 Copyright (c) 2015 The Polymer Project Authors. All rights reserved. 4 This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 Code distributed by Google as part of the polymer project is also 8 subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 --> 10 11 <link rel="import" href="../polymer/polymer.html"> 12 <link rel="import" href="../iron-a11y-announcer/iron-a11y-announcer.html"> 13 <link rel="import" href="../iron-validatable-behavior/iron-validatable-behavior.html"> 14 15 <script> 16 17 /* 18 `<iron-input>` adds two-way binding and custom validators using `Polymer.IronValidatorBehavior` 19 to `<input>`. 20 21 ### Two-way binding 22 23 By default you can only get notified of changes to an `input`'s `value` due to user input: 24 25 <input value="{{myValue::input}}"> 26 27 `iron-input` adds the `bind-value` property that mirrors the `value` property, and can be used 28 for two-way data binding. `bind-value` will notify if it is changed either by user input or by script. 29 30 <input is="iron-input" bind-value="{{myValue}}"> 31 32 ### Custom validators 33 34 You can use custom validators that implement `Polymer.IronValidatorBehavior` with `<iron-input>`. 35 36 <input is="iron-input" validator="my-custom-validator"> 37 38 ### Stopping invalid input 39 40 It may be desirable to only allow users to enter certain characters. You can use the 41 `prevent-invalid-input` and `allowed-pattern` attributes together to accomplish this. This feature 42 is separate from validation, and `allowed-pattern` does not affect how the input is validated. 43 44 <!-- only allow characters that match [0-9] --> 45 <input is="iron-input" prevent-invalid-input allowed-pattern="[0-9]"> 46 47 @hero hero.svg 48 @demo demo/index.html 49 */ 50 51 Polymer({ 52 53 is: 'iron-input', 54 55 extends: 'input', 56 57 behaviors: [ 58 Polymer.IronValidatableBehavior 59 ], 60 61 properties: { 62 63 /** 64 * Use this property instead of `value` for two-way data binding. 65 */ 66 bindValue: { 67 observer: '_bindValueChanged', 68 type: String 69 }, 70 71 /** 72 * Set to true to prevent the user from entering invalid input. If `allowedPattern` is set, 73 * any character typed by the user will be matched against that pattern, and rejected if it's not a match. 74 * Pasted input will have each character checked individually; if any character 75 * doesn't match `allowedPattern`, the entire pasted string will be rejected. 76 * If `allowedPattern` is not set, it will use the `type` attribute (only supported for `type=number`). 77 */ 78 preventInvalidInput: { 79 type: Boolean 80 }, 81 82 /** 83 * Regular expression that list the characters allowed as input. 84 * This pattern represents the allowed characters for the field; as the user inputs text, 85 * each individual character will be checked against the pattern (rather than checking 86 * the entire value as a whole). The recommended format should be a list of allowed characters; 87 * for example, `[a-zA-Z0-9.+-!;:]` 88 */ 89 allowedPattern: { 90 type: String, 91 observer: "_allowedPatternChanged" 92 }, 93 94 _previousValidInput: { 95 type: String, 96 value: '' 97 }, 98 99 _patternAlreadyChecked: { 100 type: Boolean, 101 value: false 102 } 103 104 }, 105 106 listeners: { 107 'input': '_onInput', 108 'keypress': '_onKeypress' 109 }, 110 111 /** @suppress {checkTypes} */ 112 registered: function() { 113 // Feature detect whether we need to patch dispatchEvent (i.e. on FF and IE). 114 if (!this._canDispatchEventOnDisabled()) { 115 this._origDispatchEvent = this.dispatchEvent; 116 this.dispatchEvent = this._dispatchEventFirefoxIE; 117 } 118 }, 119 120 created: function() { 121 Polymer.IronA11yAnnouncer.requestAvailability(); 122 }, 123 124 _canDispatchEventOnDisabled: function() { 125 var input = document.createElement('input'); 126 var canDispatch = false; 127 input.disabled = true; 128 129 input.addEventListener('feature-check-dispatch-event', function() { 130 canDispatch = true; 131 }); 132 133 try { 134 input.dispatchEvent(new Event('feature-check-dispatch-event')); 135 } catch(e) {} 136 137 return canDispatch; 138 }, 139 140 _dispatchEventFirefoxIE: function() { 141 // Due to Firefox bug, events fired on disabled form controls can throw 142 // errors; furthermore, neither IE nor Firefox will actually dispatch 143 // events from disabled form controls; as such, we toggle disable around 144 // the dispatch to allow notifying properties to notify 145 // See issue #47 for details 146 var disabled = this.disabled; 147 this.disabled = false; 148 this._origDispatchEvent.apply(this, arguments); 149 this.disabled = disabled; 150 }, 151 152 get _patternRegExp() { 153 var pattern; 154 if (this.allowedPattern) { 155 pattern = new RegExp(this.allowedPattern); 156 } else { 157 switch (this.type) { 158 case 'number': 159 pattern = /[0-9.,e-]/; 160 break; 161 } 162 } 163 return pattern; 164 }, 165 166 ready: function() { 167 this.bindValue = this.value; 168 }, 169 170 /** 171 * @suppress {checkTypes} 172 */ 173 _bindValueChanged: function() { 174 if (this.value !== this.bindValue) { 175 this.value = !(this.bindValue || this.bindValue === 0 || this.bindValue === false) ? '' : this.bindValue; 176 } 177 // manually notify because we don't want to notify until after setting value 178 this.fire('bind-value-changed', {value: this.bindValue}); 179 }, 180 181 _allowedPatternChanged: function() { 182 // Force to prevent invalid input when an `allowed-pattern` is set 183 this.preventInvalidInput = this.allowedPattern ? true : false; 184 }, 185 186 _onInput: function() { 187 // Need to validate each of the characters pasted if they haven't 188 // been validated inside `_onKeypress` already. 189 if (this.preventInvalidInput && !this._patternAlreadyChecked) { 190 var valid = this._checkPatternValidity(); 191 if (!valid) { 192 this._announceInvalidCharacter('Invalid string of characters not entered.'); 193 this.value = this._previousValidInput; 194 } 195 } 196 197 this.bindValue = this.value; 198 this._previousValidInput = this.value; 199 this._patternAlreadyChecked = false; 200 }, 201 202 _isPrintable: function(event) { 203 // What a control/printable character is varies wildly based on the browser. 204 // - most control characters (arrows, backspace) do not send a `keypress` event 205 // in Chrome, but the *do* on Firefox 206 // - in Firefox, when they do send a `keypress` event, control chars have 207 // a charCode = 0, keyCode = xx (for ex. 40 for down arrow) 208 // - printable characters always send a keypress event. 209 // - in Firefox, printable chars always have a keyCode = 0. In Chrome, the keyCode 210 // always matches the charCode. 211 // None of this makes any sense. 212 213 // For these keys, ASCII code == browser keycode. 214 var anyNonPrintable = 215 (event.keyCode == 8) || // backspace 216 (event.keyCode == 9) || // tab 217 (event.keyCode == 13) || // enter 218 (event.keyCode == 27); // escape 219 220 // For these keys, make sure it's a browser keycode and not an ASCII code. 221 var mozNonPrintable = 222 (event.keyCode == 19) || // pause 223 (event.keyCode == 20) || // caps lock 224 (event.keyCode == 45) || // insert 225 (event.keyCode == 46) || // delete 226 (event.keyCode == 144) || // num lock 227 (event.keyCode == 145) || // scroll lock 228 (event.keyCode > 32 && event.keyCode < 41) || // page up/down, end, home, arrows 229 (event.keyCode > 111 && event.keyCode < 124); // fn keys 230 231 return !anyNonPrintable && !(event.charCode == 0 && mozNonPrintable); 232 }, 233 234 _onKeypress: function(event) { 235 if (!this.preventInvalidInput && this.type !== 'number') { 236 return; 237 } 238 var regexp = this._patternRegExp; 239 if (!regexp) { 240 return; 241 } 242 243 // Handle special keys and backspace 244 if (event.metaKey || event.ctrlKey || event.altKey) 245 return; 246 247 // Check the pattern either here or in `_onInput`, but not in both. 248 this._patternAlreadyChecked = true; 249 250 var thisChar = String.fromCharCode(event.charCode); 251 if (this._isPrintable(event) && !regexp.test(thisChar)) { 252 event.preventDefault(); 253 this._announceInvalidCharacter('Invalid character ' + thisChar + ' not entered.'); 254 } 255 }, 256 257 _checkPatternValidity: function() { 258 var regexp = this._patternRegExp; 259 if (!regexp) { 260 return true; 261 } 262 for (var i = 0; i < this.value.length; i++) { 263 if (!regexp.test(this.value[i])) { 264 return false; 265 } 266 } 267 return true; 268 }, 269 270 /** 271 * Returns true if `value` is valid. The validator provided in `validator` will be used first, 272 * then any constraints. 273 * @return {boolean} True if the value is valid. 274 */ 275 validate: function() { 276 // First, check what the browser thinks. Some inputs (like type=number) 277 // behave weirdly and will set the value to "" if something invalid is 278 // entered, but will set the validity correctly. 279 var valid = this.checkValidity(); 280 281 // Only do extra checking if the browser thought this was valid. 282 if (valid) { 283 // Empty, required input is invalid 284 if (this.required && this.value === '') { 285 valid = false; 286 } else if (this.hasValidator()) { 287 valid = Polymer.IronValidatableBehavior.validate.call(this, this.value); 288 } 289 } 290 291 this.invalid = !valid; 292 this.fire('iron-input-validate'); 293 return valid; 294 }, 295 296 _announceInvalidCharacter: function(message) { 297 this.fire('iron-announce', { text: message }); 298 } 299 }); 300 301 /* 302 The `iron-input-validate` event is fired whenever `validate()` is called. 303 @event iron-input-validate 304 */ 305 306 </script> 307