Home | History | Annotate | Download | only in paper-input
      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-keys-behavior/iron-a11y-keys-behavior.html">
     13 <link rel="import" href="../iron-behaviors/iron-control-state.html">
     14 
     15 <script>
     16 
     17   // Generate unique, monotonically increasing IDs for labels (needed by
     18   // aria-labelledby) and add-ons.
     19   Polymer.PaperInputHelper = {};
     20   Polymer.PaperInputHelper.NextLabelID = 1;
     21   Polymer.PaperInputHelper.NextAddonID = 1;
     22 
     23   /**
     24    * Use `Polymer.PaperInputBehavior` to implement inputs with `<paper-input-container>`. This
     25    * behavior is implemented by `<paper-input>`. It exposes a number of properties from
     26    * `<paper-input-container>` and `<input is="iron-input">` and they should be bound in your
     27    * template.
     28    *
     29    * The input element can be accessed by the `inputElement` property if you need to access
     30    * properties or methods that are not exposed.
     31    * @polymerBehavior Polymer.PaperInputBehavior
     32    */
     33   Polymer.PaperInputBehaviorImpl = {
     34 
     35     properties: {
     36       /**
     37        * Fired when the input changes due to user interaction.
     38        *
     39        * @event change
     40        */
     41 
     42       /**
     43        * The label for this input. If you're using PaperInputBehavior to
     44        * implement your own paper-input-like element, bind this to
     45        * `<label>`'s content and `hidden` property, e.g.
     46        * `<label hidden$="[[!label]]">[[label]]</label>` in your `template`
     47        */
     48       label: {
     49         type: String
     50       },
     51 
     52       /**
     53        * The value for this input. If you're using PaperInputBehavior to
     54        * implement your own paper-input-like element, bind this to
     55        * the `<input is="iron-input">`'s `bindValue`
     56        * property, or the value property of your input that is `notify:true`.
     57        */
     58       value: {
     59         notify: true,
     60         type: String
     61       },
     62 
     63       /**
     64        * Set to true to disable this input. If you're using PaperInputBehavior to
     65        * implement your own paper-input-like element, bind this to
     66        * both the `<paper-input-container>`'s and the input's `disabled` property.
     67        */
     68       disabled: {
     69         type: Boolean,
     70         value: false
     71       },
     72 
     73       /**
     74        * Returns true if the value is invalid. If you're using PaperInputBehavior to
     75        * implement your own paper-input-like element, bind this to both the
     76        * `<paper-input-container>`'s and the input's `invalid` property.
     77        *
     78        * If `autoValidate` is true, the `invalid` attribute is managed automatically,
     79        * which can clobber attempts to manage it manually.
     80        */
     81       invalid: {
     82         type: Boolean,
     83         value: false,
     84         notify: true
     85       },
     86 
     87       /**
     88        * Set to true to prevent the user from entering invalid input. If you're
     89        * using PaperInputBehavior to  implement your own paper-input-like element,
     90        * bind this to `<input is="iron-input">`'s `preventInvalidInput` property.
     91        */
     92       preventInvalidInput: {
     93         type: Boolean
     94       },
     95 
     96       /**
     97        * Set this to specify the pattern allowed by `preventInvalidInput`. If
     98        * you're using PaperInputBehavior to implement your own paper-input-like
     99        * element, bind this to the `<input is="iron-input">`'s `allowedPattern`
    100        * property.
    101        */
    102       allowedPattern: {
    103         type: String
    104       },
    105 
    106       /**
    107        * The type of the input. The supported types are `text`, `number` and `password`.
    108        * If you're using PaperInputBehavior to implement your own paper-input-like element,
    109        * bind this to the `<input is="iron-input">`'s `type` property.
    110        */
    111       type: {
    112         type: String
    113       },
    114 
    115       /**
    116        * The datalist of the input (if any). This should match the id of an existing `<datalist>`.
    117        * If you're using PaperInputBehavior to implement your own paper-input-like
    118        * element, bind this to the `<input is="iron-input">`'s `list` property.
    119        */
    120       list: {
    121         type: String
    122       },
    123 
    124       /**
    125        * A pattern to validate the `input` with. If you're using PaperInputBehavior to
    126        * implement your own paper-input-like element, bind this to
    127        * the `<input is="iron-input">`'s `pattern` property.
    128        */
    129       pattern: {
    130         type: String
    131       },
    132 
    133       /**
    134        * Set to true to mark the input as required. If you're using PaperInputBehavior to
    135        * implement your own paper-input-like element, bind this to
    136        * the `<input is="iron-input">`'s `required` property.
    137        */
    138       required: {
    139         type: Boolean,
    140         value: false
    141       },
    142 
    143       /**
    144        * The error message to display when the input is invalid. If you're using
    145        * PaperInputBehavior to implement your own paper-input-like element,
    146        * bind this to the `<paper-input-error>`'s content, if using.
    147        */
    148       errorMessage: {
    149         type: String
    150       },
    151 
    152       /**
    153        * Set to true to show a character counter.
    154        */
    155       charCounter: {
    156         type: Boolean,
    157         value: false
    158       },
    159 
    160       /**
    161        * Set to true to disable the floating label. If you're using PaperInputBehavior to
    162        * implement your own paper-input-like element, bind this to
    163        * the `<paper-input-container>`'s `noLabelFloat` property.
    164        */
    165       noLabelFloat: {
    166         type: Boolean,
    167         value: false
    168       },
    169 
    170       /**
    171        * Set to true to always float the label. If you're using PaperInputBehavior to
    172        * implement your own paper-input-like element, bind this to
    173        * the `<paper-input-container>`'s `alwaysFloatLabel` property.
    174        */
    175       alwaysFloatLabel: {
    176         type: Boolean,
    177         value: false
    178       },
    179 
    180       /**
    181        * Set to true to auto-validate the input value. If you're using PaperInputBehavior to
    182        * implement your own paper-input-like element, bind this to
    183        * the `<paper-input-container>`'s `autoValidate` property.
    184        */
    185       autoValidate: {
    186         type: Boolean,
    187         value: false
    188       },
    189 
    190       /**
    191        * Name of the validator to use. If you're using PaperInputBehavior to
    192        * implement your own paper-input-like element, bind this to
    193        * the `<input is="iron-input">`'s `validator` property.
    194        */
    195       validator: {
    196         type: String
    197       },
    198 
    199       // HTMLInputElement attributes for binding if needed
    200 
    201       /**
    202        * If you're using PaperInputBehavior to implement your own paper-input-like
    203        * element, bind this to the `<input is="iron-input">`'s `autocomplete` property.
    204        */
    205       autocomplete: {
    206         type: String,
    207         value: 'off'
    208       },
    209 
    210       /**
    211        * If you're using PaperInputBehavior to implement your own paper-input-like
    212        * element, bind this to the `<input is="iron-input">`'s `autofocus` property.
    213        */
    214       autofocus: {
    215         type: Boolean
    216       },
    217 
    218       /**
    219        * If you're using PaperInputBehavior to implement your own paper-input-like
    220        * element, bind this to the `<input is="iron-input">`'s `inputmode` property.
    221        */
    222       inputmode: {
    223         type: String
    224       },
    225 
    226       /**
    227        * The minimum length of the input value.
    228        * If you're using PaperInputBehavior to implement your own paper-input-like
    229        * element, bind this to the `<input is="iron-input">`'s `minlength` property.
    230        */
    231       minlength: {
    232         type: Number
    233       },
    234 
    235       /**
    236        * The maximum length of the input value.
    237        * If you're using PaperInputBehavior to implement your own paper-input-like
    238        * element, bind this to the `<input is="iron-input">`'s `maxlength` property.
    239        */
    240       maxlength: {
    241         type: Number
    242       },
    243 
    244       /**
    245        * The minimum (numeric or date-time) input value.
    246        * If you're using PaperInputBehavior to implement your own paper-input-like
    247        * element, bind this to the `<input is="iron-input">`'s `min` property.
    248        */
    249       min: {
    250         type: String
    251       },
    252 
    253       /**
    254        * The maximum (numeric or date-time) input value.
    255        * Can be a String (e.g. `"2000-1-1"`) or a Number (e.g. `2`).
    256        * If you're using PaperInputBehavior to implement your own paper-input-like
    257        * element, bind this to the `<input is="iron-input">`'s `max` property.
    258        */
    259       max: {
    260         type: String
    261       },
    262 
    263       /**
    264        * Limits the numeric or date-time increments.
    265        * If you're using PaperInputBehavior to implement your own paper-input-like
    266        * element, bind this to the `<input is="iron-input">`'s `step` property.
    267        */
    268       step: {
    269         type: String
    270       },
    271 
    272       /**
    273        * If you're using PaperInputBehavior to implement your own paper-input-like
    274        * element, bind this to the `<input is="iron-input">`'s `name` property.
    275        */
    276       name: {
    277         type: String
    278       },
    279 
    280       /**
    281        * A placeholder string in addition to the label. If this is set, the label will always float.
    282        */
    283       placeholder: {
    284         type: String,
    285         // need to set a default so _computeAlwaysFloatLabel is run
    286         value: ''
    287       },
    288 
    289       /**
    290        * If you're using PaperInputBehavior to implement your own paper-input-like
    291        * element, bind this to the `<input is="iron-input">`'s `readonly` property.
    292        */
    293       readonly: {
    294         type: Boolean,
    295         value: false
    296       },
    297 
    298       /**
    299        * If you're using PaperInputBehavior to implement your own paper-input-like
    300        * element, bind this to the `<input is="iron-input">`'s `size` property.
    301        */
    302       size: {
    303         type: Number
    304       },
    305 
    306       // Nonstandard attributes for binding if needed
    307 
    308       /**
    309        * If you're using PaperInputBehavior to implement your own paper-input-like
    310        * element, bind this to the `<input is="iron-input">`'s `autocapitalize` property.
    311        */
    312       autocapitalize: {
    313         type: String,
    314         value: 'none'
    315       },
    316 
    317       /**
    318        * If you're using PaperInputBehavior to implement your own paper-input-like
    319        * element, bind this to the `<input is="iron-input">`'s `autocorrect` property.
    320        */
    321       autocorrect: {
    322         type: String,
    323         value: 'off'
    324       },
    325 
    326       /**
    327        * If you're using PaperInputBehavior to implement your own paper-input-like
    328        * element, bind this to the `<input is="iron-input">`'s `autosave` property,
    329        * used with type=search.
    330        */
    331       autosave: {
    332         type: String
    333       },
    334 
    335       /**
    336        * If you're using PaperInputBehavior to implement your own paper-input-like
    337        * element, bind this to the `<input is="iron-input">`'s `results` property,
    338        * used with type=search.
    339        */
    340       results: {
    341         type: Number
    342       },
    343 
    344       /**
    345        * If you're using PaperInputBehavior to implement your own paper-input-like
    346        * element, bind this to the `<input is="iron-input">`'s `accept` property,
    347        * used with type=file.
    348        */
    349       accept: {
    350         type: String
    351       },
    352 
    353       /**
    354        * If you're using PaperInputBehavior to implement your own paper-input-like
    355        * element, bind this to the`<input is="iron-input">`'s `multiple` property,
    356        * used with type=file.
    357        */
    358       multiple: {
    359         type: Boolean
    360       },
    361 
    362       _ariaDescribedBy: {
    363         type: String,
    364         value: ''
    365       },
    366 
    367       _ariaLabelledBy: {
    368         type: String,
    369         value: ''
    370       }
    371 
    372     },
    373 
    374     listeners: {
    375       'addon-attached': '_onAddonAttached',
    376     },
    377 
    378     keyBindings: {
    379       'shift+tab:keydown': '_onShiftTabDown'
    380     },
    381 
    382     hostAttributes: {
    383       tabindex: 0
    384     },
    385 
    386     /**
    387      * Returns a reference to the input element.
    388      */
    389     get inputElement() {
    390       return this.$.input;
    391     },
    392 
    393     /**
    394      * Returns a reference to the focusable element.
    395      */
    396     get _focusableElement() {
    397       return this.inputElement;
    398     },
    399 
    400     registered: function() {
    401       // These types have some default placeholder text; overlapping
    402       // the label on top of it looks terrible. Auto-float the label in this case.
    403       this._typesThatHaveText = ["date", "datetime", "datetime-local", "month",
    404           "time", "week", "file"];
    405     },
    406 
    407     attached: function() {
    408       this._updateAriaLabelledBy();
    409 
    410       if (this.inputElement &&
    411           this._typesThatHaveText.indexOf(this.inputElement.type) !== -1) {
    412         this.alwaysFloatLabel = true;
    413       }
    414     },
    415 
    416     _appendStringWithSpace: function(str, more) {
    417       if (str) {
    418         str = str + ' ' + more;
    419       } else {
    420         str = more;
    421       }
    422       return str;
    423     },
    424 
    425     _onAddonAttached: function(event) {
    426       var target = event.path ? event.path[0] : event.target;
    427       if (target.id) {
    428         this._ariaDescribedBy = this._appendStringWithSpace(this._ariaDescribedBy, target.id);
    429       } else {
    430         var id = 'paper-input-add-on-' + Polymer.PaperInputHelper.NextAddonID++;
    431         target.id = id;
    432         this._ariaDescribedBy = this._appendStringWithSpace(this._ariaDescribedBy, id);
    433       }
    434     },
    435 
    436     /**
    437      * Validates the input element and sets an error style if needed.
    438      *
    439      * @return {boolean}
    440      */
    441     validate: function() {
    442       return this.inputElement.validate();
    443     },
    444 
    445     /**
    446      * Forward focus to inputElement. Overriden from IronControlState.
    447      */
    448     _focusBlurHandler: function(event) {
    449       Polymer.IronControlState._focusBlurHandler.call(this, event);
    450 
    451       // Forward the focus to the nested input.
    452       if (this.focused && !this._shiftTabPressed)
    453         this._focusableElement.focus();
    454     },
    455 
    456     /**
    457      * Handler that is called when a shift+tab keypress is detected by the menu.
    458      *
    459      * @param {CustomEvent} event A key combination event.
    460      */
    461     _onShiftTabDown: function(event) {
    462       var oldTabIndex = this.getAttribute('tabindex');
    463       this._shiftTabPressed = true;
    464       this.setAttribute('tabindex', '-1');
    465       this.async(function() {
    466         this.setAttribute('tabindex', oldTabIndex);
    467         this._shiftTabPressed = false;
    468       }, 1);
    469     },
    470 
    471     /**
    472      * If `autoValidate` is true, then validates the element.
    473      */
    474     _handleAutoValidate: function() {
    475       if (this.autoValidate)
    476         this.validate();
    477     },
    478 
    479     /**
    480      * Restores the cursor to its original position after updating the value.
    481      * @param {string} newValue The value that should be saved.
    482      */
    483     updateValueAndPreserveCaret: function(newValue) {
    484       // Not all elements might have selection, and even if they have the
    485       // right properties, accessing them might throw an exception (like for
    486       // <input type=number>)
    487       try {
    488         var start = this.inputElement.selectionStart;
    489         this.value = newValue;
    490 
    491         // The cursor automatically jumps to the end after re-setting the value,
    492         // so restore it to its original position.
    493         this.inputElement.selectionStart = start;
    494         this.inputElement.selectionEnd = start;
    495       } catch (e) {
    496         // Just set the value and give up on the caret.
    497         this.value = newValue;
    498       }
    499     },
    500 
    501     _computeAlwaysFloatLabel: function(alwaysFloatLabel, placeholder) {
    502       return placeholder || alwaysFloatLabel;
    503     },
    504 
    505     _updateAriaLabelledBy: function() {
    506       var label = Polymer.dom(this.root).querySelector('label');
    507       if (!label) {
    508         this._ariaLabelledBy = '';
    509         return;
    510       }
    511       var labelledBy;
    512       if (label.id) {
    513         labelledBy = label.id;
    514       } else {
    515         labelledBy = 'paper-input-label-' + Polymer.PaperInputHelper.NextLabelID++;
    516         label.id = labelledBy;
    517       }
    518       this._ariaLabelledBy = labelledBy;
    519     },
    520 
    521     _onChange:function(event) {
    522       // In the Shadow DOM, the `change` event is not leaked into the
    523       // ancestor tree, so we must do this manually.
    524       // See https://w3c.github.io/webcomponents/spec/shadow/#events-that-are-not-leaked-into-ancestor-trees.
    525       if (this.shadowRoot) {
    526         this.fire(event.type, {sourceEvent: event}, {
    527           node: this,
    528           bubbles: event.bubbles,
    529           cancelable: event.cancelable
    530         });
    531       }
    532     }
    533   }
    534 
    535   /** @polymerBehavior */
    536   Polymer.PaperInputBehavior = [
    537     Polymer.IronControlState,
    538     Polymer.IronA11yKeysBehavior,
    539     Polymer.PaperInputBehaviorImpl
    540   ];
    541 </script>
    542