• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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    /** @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