<!– @license Copyright © 2015 The Polymer Project Authors. All rights reserved. This code may only be used under the BSD style license found at polymer.github.io/LICENSE.txt The complete set of authors may be found at polymer.github.io/AUTHORS.txt The complete set of contributors may be found at polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as part of the polymer project is also subject to an additional IP rights grant found at polymer.github.io/PATENTS.txt –>

<link rel=“import” href=“../polymer/polymer.html”> <link rel=“import” href=“../iron-a11y-announcer/iron-a11y-announcer.html”> <link rel=“import” href=“../iron-validatable-behavior/iron-validatable-behavior.html”>

<script>

/* `<iron-input>` adds two-way binding and custom validators using `Polymer.IronValidatorBehavior` to `<input>`.

### Two-way binding

By default you can only get notified of changes to an `input`'s `value` due to user input:

<input value="{{myValue::input}}">

`iron-input` adds the `bind-value` property that mirrors the `value` property, and can be used for two-way data binding. `bind-value` will notify if it is changed either by user input or by script.

<input is="iron-input" bind-value="{{myValue}}">

### Custom validators

You can use custom validators that implement `Polymer.IronValidatorBehavior` with `<iron-input>`.

<input is="iron-input" validator="my-custom-validator">

### Stopping invalid input

It may be desirable to only allow users to enter certain characters. You can use the `prevent-invalid-input` and `allowed-pattern` attributes together to accomplish this. This feature is separate from validation, and `allowed-pattern` does not affect how the input is validated.

<!-- only allow characters that match [0-9] -->
<input is="iron-input" prevent-invalid-input allowed-pattern="[0-9]">

@hero hero.svg @demo demo/index.html */

Polymer({

  is: 'iron-input',

  extends: 'input',

  behaviors: [
    Polymer.IronValidatableBehavior
  ],

  properties: {

    /**
     * Use this property instead of `value` for two-way data binding.
     */
    bindValue: {
      observer: '_bindValueChanged',
      type: String
    },

    /**
     * Set to true to prevent the user from entering invalid input. If `allowedPattern` is set,
     * any character typed by the user will be matched against that pattern, and rejected if it's not a match.
     * Pasted input will have each character checked individually; if any character
     * doesn't match `allowedPattern`, the entire pasted string will be rejected.
     * If `allowedPattern` is not set, it will use the `type` attribute (only supported for `type=number`).
     */
    preventInvalidInput: {
      type: Boolean
    },

    /**
     * Regular expression that list the characters allowed as input.
     * This pattern represents the allowed characters for the field; as the user inputs text,
     * each individual character will be checked against the pattern (rather than checking
     * the entire value as a whole). The recommended format should be a list of allowed characters;
     * for example, `[a-zA-Z0-9.+-!;:]`
     */
    allowedPattern: {
      type: String,
      observer: "_allowedPatternChanged"
    },

    _previousValidInput: {
      type: String,
      value: ''
    },

    _patternAlreadyChecked: {
      type: Boolean,
      value: false
    }

  },

  listeners: {
    'input': '_onInput',
    'keypress': '_onKeypress'
  },

  /** @suppress {checkTypes} */
  registered: function() {
    // Feature detect whether we need to patch dispatchEvent (i.e. on FF and IE).
    if (!this._canDispatchEventOnDisabled()) {
      this._origDispatchEvent = this.dispatchEvent;
      this.dispatchEvent = this._dispatchEventFirefoxIE;
    }
  },

  created: function() {
    Polymer.IronA11yAnnouncer.requestAvailability();
  },

  _canDispatchEventOnDisabled: function() {
    var input = document.createElement('input');
    var canDispatch = false;
    input.disabled = true;

    input.addEventListener('feature-check-dispatch-event', function() {
      canDispatch = true;
    });

    try {
      input.dispatchEvent(new Event('feature-check-dispatch-event'));
    } catch(e) {}

    return canDispatch;
  },

  _dispatchEventFirefoxIE: function() {
    // Due to Firefox bug, events fired on disabled form controls can throw
    // errors; furthermore, neither IE nor Firefox will actually dispatch
    // events from disabled form controls; as such, we toggle disable around
    // the dispatch to allow notifying properties to notify
    // See issue #47 for details
    var disabled = this.disabled;
    this.disabled = false;
    this._origDispatchEvent.apply(this, arguments);
    this.disabled = disabled;
  },

  get _patternRegExp() {
    var pattern;
    if (this.allowedPattern) {
      pattern = new RegExp(this.allowedPattern);
    } else {
      switch (this.type) {
        case 'number':
          pattern = /[0-9.,e-]/;
          break;
      }
    }
    return pattern;
  },

  ready: function() {
    this.bindValue = this.value;
  },

  /**
   * @suppress {checkTypes}
   */
  _bindValueChanged: function() {
    if (this.value !== this.bindValue) {
      this.value = !(this.bindValue || this.bindValue === 0 || this.bindValue === false) ? '' : this.bindValue;
    }
    // manually notify because we don't want to notify until after setting value
    this.fire('bind-value-changed', {value: this.bindValue});
  },

  _allowedPatternChanged: function() {
    // Force to prevent invalid input when an `allowed-pattern` is set
    this.preventInvalidInput = this.allowedPattern ? true : false;
  },

  _onInput: function() {
    // Need to validate each of the characters pasted if they haven't
    // been validated inside `_onKeypress` already.
    if (this.preventInvalidInput && !this._patternAlreadyChecked) {
      var valid = this._checkPatternValidity();
      if (!valid) {
        this._announceInvalidCharacter('Invalid string of characters not entered.');
        this.value = this._previousValidInput;
      }
    }

    this.bindValue = this.value;
    this._previousValidInput = this.value;
    this._patternAlreadyChecked = false;
  },

  _isPrintable: function(event) {
    // What a control/printable character is varies wildly based on the browser.
    // - most control characters (arrows, backspace) do not send a `keypress` event
    //   in Chrome, but the *do* on Firefox
    // - in Firefox, when they do send a `keypress` event, control chars have
    //   a charCode = 0, keyCode = xx (for ex. 40 for down arrow)
    // - printable characters always send a keypress event.
    // - in Firefox, printable chars always have a keyCode = 0. In Chrome, the keyCode
    //   always matches the charCode.
    // None of this makes any sense.

    // For these keys, ASCII code == browser keycode.
    var anyNonPrintable =
      (event.keyCode == 8)   ||  // backspace
      (event.keyCode == 9)   ||  // tab
      (event.keyCode == 13)  ||  // enter
      (event.keyCode == 27);     // escape

    // For these keys, make sure it's a browser keycode and not an ASCII code.
    var mozNonPrintable =
      (event.keyCode == 19)  ||  // pause
      (event.keyCode == 20)  ||  // caps lock
      (event.keyCode == 45)  ||  // insert
      (event.keyCode == 46)  ||  // delete
      (event.keyCode == 144) ||  // num lock
      (event.keyCode == 145) ||  // scroll lock
      (event.keyCode > 32 && event.keyCode < 41)   || // page up/down, end, home, arrows
      (event.keyCode > 111 && event.keyCode < 124); // fn keys

    return !anyNonPrintable && !(event.charCode == 0 && mozNonPrintable);
  },

  _onKeypress: function(event) {
    if (!this.preventInvalidInput && this.type !== 'number') {
      return;
    }
    var regexp = this._patternRegExp;
    if (!regexp) {
      return;
    }

    // Handle special keys and backspace
    if (event.metaKey || event.ctrlKey || event.altKey)
      return;

    // Check the pattern either here or in `_onInput`, but not in both.
    this._patternAlreadyChecked = true;

    var thisChar = String.fromCharCode(event.charCode);
    if (this._isPrintable(event) && !regexp.test(thisChar)) {
      event.preventDefault();
      this._announceInvalidCharacter('Invalid character ' + thisChar + ' not entered.');
    }
  },

  _checkPatternValidity: function() {
    var regexp = this._patternRegExp;
    if (!regexp) {
      return true;
    }
    for (var i = 0; i < this.value.length; i++) {
      if (!regexp.test(this.value[i])) {
        return false;
      }
    }
    return true;
  },

  /**
   * Returns true if `value` is valid. The validator provided in `validator` will be used first,
   * then any constraints.
   * @return {boolean} True if the value is valid.
   */
  validate: function() {
    // First, check what the browser thinks. Some inputs (like type=number)
    // behave weirdly and will set the value to "" if something invalid is
    // entered, but will set the validity correctly.
    var valid =  this.checkValidity();

    // Only do extra checking if the browser thought this was valid.
    if (valid) {
      // Empty, required input is invalid
      if (this.required && this.value === '') {
        valid = false;
      } else if (this.hasValidator()) {
        valid = Polymer.IronValidatableBehavior.validate.call(this, this.value);
      }
    }

    this.invalid = !valid;
    this.fire('iron-input-validate');
    return valid;
  },

  _announceInvalidCharacter: function(message) {
    this.fire('iron-announce', { text: message });
  }
});

/*
The `iron-input-validate` event is fired whenever `validate()` is called.
@event iron-input-validate
*/

</script>