1<!-- 2@license 3Copyright (c) 2015 The Polymer Project Authors. All rights reserved. 4This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7Code distributed by Google as part of the polymer project is also 8subject 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` 19to `<input>`. 20 21### Two-way binding 22 23By 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 28for 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 34You 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 40It 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 42is 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 registered: function() { 112 // Feature detect whether we need to patch dispatchEvent (i.e. on FF and IE). 113 if (!this._canDispatchEventOnDisabled()) { 114 this._origDispatchEvent = this.dispatchEvent; 115 this.dispatchEvent = this._dispatchEventFirefoxIE; 116 } 117 }, 118 119 created: function() { 120 Polymer.IronA11yAnnouncer.requestAvailability(); 121 }, 122 123 _canDispatchEventOnDisabled: function() { 124 var input = document.createElement('input'); 125 var canDispatch = false; 126 input.disabled = true; 127 128 input.addEventListener('feature-check-dispatch-event', function() { 129 canDispatch = true; 130 }); 131 132 try { 133 input.dispatchEvent(new Event('feature-check-dispatch-event')); 134 } catch(e) {} 135 136 return canDispatch; 137 }, 138 139 /** 140 * @this {Node} 141 * @param {!Event} event 142 * @return {boolean} 143 */ 144 _dispatchEventFirefoxIE: function(event) { 145 // Due to Firefox bug, events fired on disabled form controls can throw 146 // errors; furthermore, neither IE nor Firefox will actually dispatch 147 // events from disabled form controls; as such, we toggle disable around 148 // the dispatch to allow notifying properties to notify 149 // See issue #47 for details 150 var disabled = this.disabled; 151 this.disabled = false; 152 var defaultPrevented = this._origDispatchEvent(event); 153 this.disabled = disabled; 154 return defaultPrevented 155 }, 156 157 get _patternRegExp() { 158 var pattern; 159 if (this.allowedPattern) { 160 pattern = new RegExp(this.allowedPattern); 161 } else { 162 switch (this.type) { 163 case 'number': 164 pattern = /[0-9.,e-]/; 165 break; 166 } 167 } 168 return pattern; 169 }, 170 171 ready: function() { 172 this.bindValue = this.value; 173 }, 174 175 /** 176 * @suppress {checkTypes} 177 */ 178 _bindValueChanged: function() { 179 if (this.value !== this.bindValue) { 180 this.value = !(this.bindValue || this.bindValue === 0 || this.bindValue === false) ? '' : this.bindValue; 181 } 182 // manually notify because we don't want to notify until after setting value 183 this.fire('bind-value-changed', {value: this.bindValue}); 184 }, 185 186 _allowedPatternChanged: function() { 187 // Force to prevent invalid input when an `allowed-pattern` is set 188 this.preventInvalidInput = this.allowedPattern ? true : false; 189 }, 190 191 _onInput: function() { 192 // Need to validate each of the characters pasted if they haven't 193 // been validated inside `_onKeypress` already. 194 if (this.preventInvalidInput && !this._patternAlreadyChecked) { 195 var valid = this._checkPatternValidity(); 196 if (!valid) { 197 this._announceInvalidCharacter('Invalid string of characters not entered.'); 198 this.value = this._previousValidInput; 199 } 200 } 201 202 this.bindValue = this.value; 203 this._previousValidInput = this.value; 204 this._patternAlreadyChecked = false; 205 }, 206 207 _isPrintable: function(event) { 208 // What a control/printable character is varies wildly based on the browser. 209 // - most control characters (arrows, backspace) do not send a `keypress` event 210 // in Chrome, but the *do* on Firefox 211 // - in Firefox, when they do send a `keypress` event, control chars have 212 // a charCode = 0, keyCode = xx (for ex. 40 for down arrow) 213 // - printable characters always send a keypress event. 214 // - in Firefox, printable chars always have a keyCode = 0. In Chrome, the keyCode 215 // always matches the charCode. 216 // None of this makes any sense. 217 218 // For these keys, ASCII code == browser keycode. 219 var anyNonPrintable = 220 (event.keyCode == 8) || // backspace 221 (event.keyCode == 9) || // tab 222 (event.keyCode == 13) || // enter 223 (event.keyCode == 27); // escape 224 225 // For these keys, make sure it's a browser keycode and not an ASCII code. 226 var mozNonPrintable = 227 (event.keyCode == 19) || // pause 228 (event.keyCode == 20) || // caps lock 229 (event.keyCode == 45) || // insert 230 (event.keyCode == 46) || // delete 231 (event.keyCode == 144) || // num lock 232 (event.keyCode == 145) || // scroll lock 233 (event.keyCode > 32 && event.keyCode < 41) || // page up/down, end, home, arrows 234 (event.keyCode > 111 && event.keyCode < 124); // fn keys 235 236 return !anyNonPrintable && !(event.charCode == 0 && mozNonPrintable); 237 }, 238 239 _onKeypress: function(event) { 240 if (!this.preventInvalidInput && this.type !== 'number') { 241 return; 242 } 243 var regexp = this._patternRegExp; 244 if (!regexp) { 245 return; 246 } 247 248 // Handle special keys and backspace 249 if (event.metaKey || event.ctrlKey || event.altKey) 250 return; 251 252 // Check the pattern either here or in `_onInput`, but not in both. 253 this._patternAlreadyChecked = true; 254 255 var thisChar = String.fromCharCode(event.charCode); 256 if (this._isPrintable(event) && !regexp.test(thisChar)) { 257 event.preventDefault(); 258 this._announceInvalidCharacter('Invalid character ' + thisChar + ' not entered.'); 259 } 260 }, 261 262 _checkPatternValidity: function() { 263 var regexp = this._patternRegExp; 264 if (!regexp) { 265 return true; 266 } 267 for (var i = 0; i < this.value.length; i++) { 268 if (!regexp.test(this.value[i])) { 269 return false; 270 } 271 } 272 return true; 273 }, 274 275 /** 276 * Returns true if `value` is valid. The validator provided in `validator` will be used first, 277 * then any constraints. 278 * @return {boolean} True if the value is valid. 279 */ 280 validate: function() { 281 // First, check what the browser thinks. Some inputs (like type=number) 282 // behave weirdly and will set the value to "" if something invalid is 283 // entered, but will set the validity correctly. 284 var valid = this.checkValidity(); 285 286 // Only do extra checking if the browser thought this was valid. 287 if (valid) { 288 // Empty, required input is invalid 289 if (this.required && this.value === '') { 290 valid = false; 291 } else if (this.hasValidator()) { 292 valid = Polymer.IronValidatableBehavior.validate.call(this, this.value); 293 } 294 } 295 296 this.invalid = !valid; 297 this.fire('iron-input-validate'); 298 return valid; 299 }, 300 301 _announceInvalidCharacter: function(message) { 302 this.fire('iron-announce', { text: message }); 303 } 304 }); 305 306 /* 307 The `iron-input-validate` event is fired whenever `validate()` is called. 308 @event iron-input-validate 309 */ 310 311</script> 312