• 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    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