| OLD | NEW | 
| (Empty) |  | 
 |    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.g
     ithub.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/CONTRI
     BUTORS.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/PATEN
     TS.txt | 
 |    9 --> | 
 |   10  | 
 |   11 <link rel="import" href="../polymer/polymer.html"> | 
 |   12 <link rel="import" href="../iron-ajax/iron-ajax.html"> | 
 |   13  | 
 |   14 <script> | 
 |   15 /* | 
 |   16 `<iron-form>` is an HTML `<form>` element that can validate and submit any custo
     m | 
 |   17 elements that implement `Polymer.IronFormElementBehavior`, as well as any | 
 |   18 native HTML elements. For more information on which attributes are | 
 |   19 available on the native form element, see https://developer.mozilla.org/en-US/do
     cs/Web/HTML/Element/form | 
 |   20  | 
 |   21 It supports both `get` and `post` methods, and uses an `iron-ajax` element to | 
 |   22 submit the form data to the action URL. | 
 |   23  | 
 |   24   Example: | 
 |   25  | 
 |   26     <form is="iron-form" id="form" method="post" action="/form/handler"> | 
 |   27       <paper-input name="name" label="name"></paper-input> | 
 |   28       <input name="address"> | 
 |   29       ... | 
 |   30     </form> | 
 |   31  | 
 |   32 By default, a native `<button>` element will submit this form. However, if you | 
 |   33 want to submit it from a custom element's click handler, you need to explicitly | 
 |   34 call the form's `submit` method. | 
 |   35  | 
 |   36   Example: | 
 |   37  | 
 |   38     <paper-button raised onclick="submitForm()">Submit</paper-button> | 
 |   39  | 
 |   40     function submitForm() { | 
 |   41       document.getElementById('form').submit(); | 
 |   42     } | 
 |   43  | 
 |   44 To customize the request sent to the server, you can listen to the `iron-form-pr
     esubmit` | 
 |   45 event, and modify the form's[`iron-ajax`](https://elements.polymer-project.org/e
     lements/iron-ajax) | 
 |   46 object. However, If you want to not use `iron-ajax` at all, you can cancel the | 
 |   47 event and do your own custom submission: | 
 |   48  | 
 |   49   Example of modifying the request, but still using the build-in form submission
     : | 
 |   50  | 
 |   51     form.addEventListener('iron-form-presubmit', function() { | 
 |   52       this.request.method = 'put'; | 
 |   53       this.request.params = someCustomParams; | 
 |   54     }); | 
 |   55  | 
 |   56   Example of bypassing the build-in form submission: | 
 |   57  | 
 |   58     form.addEventListener('iron-form-presubmit', function(event) { | 
 |   59       event.preventDefault(); | 
 |   60       var firebase = new Firebase(form.getAttribute('action')); | 
 |   61       firebase.set(form.serialize()); | 
 |   62     }); | 
 |   63  | 
 |   64 @demo demo/index.html | 
 |   65 */ | 
 |   66   Polymer({ | 
 |   67  | 
 |   68     is: 'iron-form', | 
 |   69  | 
 |   70     extends: 'form', | 
 |   71  | 
 |   72     properties: { | 
 |   73       /** | 
 |   74        * By default, the form will display the browser's native validation | 
 |   75        * UI (i.e. popup bubbles and invalid styles on invalid fields). You can | 
 |   76        * manually disable this; however, if you do, note that you will have to | 
 |   77        * manually style invalid *native* HTML fields yourself, as you are | 
 |   78        * explicitly preventing the native form from doing so. | 
 |   79        */ | 
 |   80       disableNativeValidationUi: { | 
 |   81         type: Boolean, | 
 |   82         value: false | 
 |   83       }, | 
 |   84  | 
 |   85       /** | 
 |   86       * Set the withCredentials flag when sending data. | 
 |   87       */ | 
 |   88       withCredentials: { | 
 |   89         type: Boolean, | 
 |   90         value: false | 
 |   91       }, | 
 |   92  | 
 |   93       /** | 
 |   94        * Content type to use when sending data. If the `contentType` property | 
 |   95        * is set and a `Content-Type` header is specified in the `headers` | 
 |   96        * property, the `headers` property value will take precedence. | 
 |   97        * If Content-Type is set to a value listed below, then | 
 |   98        * the `body` (typically used with POST requests) will be encoded accordin
     gly. | 
 |   99        * | 
 |  100        *    * `content-type="application/json"` | 
 |  101        *      * body is encoded like `{"foo":"bar baz","x":1}` | 
 |  102        *    * `content-type="application/x-www-form-urlencoded"` | 
 |  103        *      * body is encoded like `foo=bar+baz&x=1` | 
 |  104        */ | 
 |  105       contentType: { | 
 |  106         type: String, | 
 |  107         value: "application/x-www-form-urlencoded" | 
 |  108       }, | 
 |  109  | 
 |  110       /** | 
 |  111       * HTTP request headers to send. | 
 |  112       * | 
 |  113       * Note: setting a `Content-Type` header here will override the value | 
 |  114       * specified by the `contentType` property of this element. | 
 |  115       */ | 
 |  116       headers: { | 
 |  117         type: Object, | 
 |  118         value: function() { | 
 |  119           return {}; | 
 |  120         } | 
 |  121       }, | 
 |  122  | 
 |  123       /** | 
 |  124       * iron-ajax request object used to submit the form. | 
 |  125       */ | 
 |  126       request: { | 
 |  127         type: Object, | 
 |  128       } | 
 |  129     }, | 
 |  130  | 
 |  131     /** | 
 |  132      * Fired if the form cannot be submitted because it's invalid. | 
 |  133      * | 
 |  134      * @event iron-form-invalid | 
 |  135      */ | 
 |  136  | 
 |  137     /** | 
 |  138      * Fired before the form is submitted. | 
 |  139      * | 
 |  140      * @event iron-form-presubmit | 
 |  141      */ | 
 |  142  | 
 |  143     /** | 
 |  144      * Fired after the form is submitted. | 
 |  145      * | 
 |  146      * @event iron-form-submit | 
 |  147      */ | 
 |  148  | 
 |  149      /** | 
 |  150       * Fired after the form is reset. | 
 |  151       * | 
 |  152       * @event iron-form-reset | 
 |  153       */ | 
 |  154  | 
 |  155     /** | 
 |  156     * Fired after the form is submitted and a response is received. An | 
 |  157     * IronRequestElement is included as the event.detail object. | 
 |  158     * | 
 |  159     * @event iron-form-response | 
 |  160     */ | 
 |  161  | 
 |  162     /** | 
 |  163      * Fired after the form is submitted and an error is received. An | 
 |  164      * IronRequestElement is included as the event.detail object. | 
 |  165      * | 
 |  166      * @event iron-form-error | 
 |  167      */ | 
 |  168     listeners: { | 
 |  169       'iron-form-element-register': '_registerElement', | 
 |  170       'iron-form-element-unregister': '_unregisterElement', | 
 |  171       'submit': '_onSubmit', | 
 |  172       'reset': '_onReset' | 
 |  173     }, | 
 |  174  | 
 |  175     registered: function() { | 
 |  176       // Dear reader: I apologize for what you're about to experience. You see, | 
 |  177       // Safari does not respect `required` on input elements, so it never | 
 |  178       // has any browser validation bubbles to show. And we have to feature | 
 |  179       // detect that, since we rely on the form submission to do the right thing
     . | 
 |  180       // See http://caniuse.com/#search=required. | 
 |  181  | 
 |  182       // Create a fake form, with an invalid input. If it gets submitted, it's S
     afari. | 
 |  183       var form = document.createElement('form'); | 
 |  184       var input = document.createElement('input'); | 
 |  185       input.setAttribute('required', 'true'); | 
 |  186       form.appendChild(input); | 
 |  187  | 
 |  188       // If you call submit(), the form doesn't actually fire a submit event, | 
 |  189       // so you can't intercept it and cancel it. The event is only fired | 
 |  190       // from the magical button click submission. | 
 |  191       // See http://wayback.archive.org/web/20090323062817/http://blogs.vertigos
     oftware.com/snyholm/archive/2006/09/27/3788.aspx. | 
 |  192       var button = document.createElement('input'); | 
 |  193       button.setAttribute('type', 'submit'); | 
 |  194       form.appendChild(button); | 
 |  195  | 
 |  196       Polymer.clientSupportsFormValidationUI = true; | 
 |  197       form.addEventListener('submit', function(event) { | 
 |  198         // Oh good! We don't handle `required` correctly. | 
 |  199         Polymer.clientSupportsFormValidationUI = false; | 
 |  200         event.preventDefault(); | 
 |  201       }); | 
 |  202       button.click(); | 
 |  203     }, | 
 |  204  | 
 |  205     ready: function() { | 
 |  206       // Object that handles the ajax form submission request. | 
 |  207       this.request = document.createElement('iron-ajax'); | 
 |  208       this.request.addEventListener('response', this._handleFormResponse.bind(th
     is)); | 
 |  209       this.request.addEventListener('error', this._handleFormError.bind(this)); | 
 |  210  | 
 |  211       // Holds all the custom elements registered with this form. | 
 |  212       this._customElements = []; | 
 |  213       // Holds the initial values of the custom elements registered with this fo
     rm. | 
 |  214       this._customElementsInitialValues = []; | 
 |  215     }, | 
 |  216  | 
 |  217     /** | 
 |  218      * Submits the form. | 
 |  219      */ | 
 |  220     submit: function() { | 
 |  221       if (!this.noValidate && !this.validate()) { | 
 |  222         // In order to trigger the native browser invalid-form UI, we need | 
 |  223         // to do perform a fake form submit. | 
 |  224         if (Polymer.clientSupportsFormValidationUI && !this.disableNativeValidat
     ionUi) { | 
 |  225           this._doFakeSubmitForValidation(); | 
 |  226         } | 
 |  227         this.fire('iron-form-invalid'); | 
 |  228         return; | 
 |  229       } | 
 |  230  | 
 |  231       var json = this.serialize(); | 
 |  232  | 
 |  233       // Native forms can also index elements magically by their name (can't mak
     e | 
 |  234       // this up if I tried) so we need to get the correct attributes, not the | 
 |  235       // elements with those names. | 
 |  236       this.request.url = this.getAttribute('action'); | 
 |  237       this.request.method = this.getAttribute('method'); | 
 |  238       this.request.contentType = this.contentType; | 
 |  239       this.request.withCredentials = this.withCredentials; | 
 |  240       this.request.headers = this.headers; | 
 |  241  | 
 |  242       if (this.request.method.toUpperCase() === 'POST') { | 
 |  243         this.request.body = json; | 
 |  244       } else { | 
 |  245         this.request.params = json; | 
 |  246       } | 
 |  247  | 
 |  248       // Allow for a presubmit hook | 
 |  249       var event = this.fire('iron-form-presubmit', {}, {cancelable: true}); | 
 |  250       if(!event.defaultPrevented) { | 
 |  251         this.request.generateRequest(); | 
 |  252         this.fire('iron-form-submit', json); | 
 |  253       } | 
 |  254     }, | 
 |  255  | 
 |  256     /** | 
 |  257      * Handler that is called when the native form fires a `submit` event | 
 |  258      * | 
 |  259      * @param {Event} event A `submit` event. | 
 |  260      */ | 
 |  261     _onSubmit: function(event) { | 
 |  262       this.submit(); | 
 |  263  | 
 |  264       // Don't perform a page refresh. | 
 |  265       if (event) { | 
 |  266         event.preventDefault(); | 
 |  267       } | 
 |  268  | 
 |  269       return false; | 
 |  270     }, | 
 |  271  | 
 |  272     /** | 
 |  273      * Handler that is called when the native form fires a `reset` event | 
 |  274      * | 
 |  275      * @param {Event} event A `reset` event. | 
 |  276      */ | 
 |  277     _onReset: function(event) { | 
 |  278       this._resetCustomElements(); | 
 |  279     }, | 
 |  280  | 
 |  281     /** | 
 |  282      * Returns a json object containing name/value pairs for all the registered | 
 |  283      * custom components and native elements of the form. If there are elements | 
 |  284      * with duplicate names, then their values will get aggregated into an | 
 |  285      * array of values. | 
 |  286      * | 
 |  287      * @return {!Object} | 
 |  288      */ | 
 |  289     serialize: function() { | 
 |  290       var json = {}; | 
 |  291  | 
 |  292       function addSerializedElement(name, value) { | 
 |  293         // If the name doesn't exist, add it. Otherwise, serialize it to | 
 |  294         // an array, | 
 |  295         if (!json[name]) { | 
 |  296           json[name] = value; | 
 |  297         } else { | 
 |  298           if (!Array.isArray(json[name])) { | 
 |  299             json[name] = [json[name]]; | 
 |  300           } | 
 |  301           json[name].push(value); | 
 |  302         } | 
 |  303       } | 
 |  304  | 
 |  305       // Go through all of the registered custom components. | 
 |  306       for (var el, i = 0; el = this._customElements[i], i < this._customElements
     .length; i++) { | 
 |  307         // If this custom element is inside a custom element that has already | 
 |  308         // registered to this form, skip it. | 
 |  309         if (!this._isChildOfRegisteredParent(el, true) && this._useValue(el)) { | 
 |  310           addSerializedElement(el.name, el.value); | 
 |  311         } | 
 |  312       } | 
 |  313  | 
 |  314       // Also go through the form's native elements. | 
 |  315       for (var el, i = 0; el = this.elements[i], i < this.elements.length; i++) 
     { | 
 |  316         // If this native element is inside a custom element that has already | 
 |  317         // registered to this form, skip it. | 
 |  318         if (this._isChildOfRegisteredParent(el, true) || !this._useValue(el)) { | 
 |  319           continue; | 
 |  320         } | 
 |  321  | 
 |  322         // A <select multiple> has an array of values. | 
 |  323         if (el.tagName.toLowerCase() === 'select' && el.multiple) { | 
 |  324           for (var o = 0; o < el.options.length; o++) { | 
 |  325             if (el.options[o].selected) { | 
 |  326               addSerializedElement(el.name, el.options[o].value); | 
 |  327             } | 
 |  328           } | 
 |  329         } else { | 
 |  330           addSerializedElement(el.name, el.value); | 
 |  331         } | 
 |  332       } | 
 |  333  | 
 |  334       return json; | 
 |  335     }, | 
 |  336  | 
 |  337     _handleFormResponse: function (event) { | 
 |  338       this.fire('iron-form-response', event.detail); | 
 |  339     }, | 
 |  340  | 
 |  341     _handleFormError: function (event) { | 
 |  342       this.fire('iron-form-error', event.detail); | 
 |  343     }, | 
 |  344  | 
 |  345     _registerElement: function(e) { | 
 |  346       // Get the actual element that fired the event | 
 |  347       var element = Polymer.dom(e).rootTarget; | 
 |  348  | 
 |  349       element._parentForm = this; | 
 |  350       this._customElements.push(element); | 
 |  351  | 
 |  352       // Save the original value of this input. | 
 |  353       this._customElementsInitialValues.push( | 
 |  354           this._usesCheckedInsteadOfValue(element) ? element.checked : element.v
     alue); | 
 |  355     }, | 
 |  356  | 
 |  357     _unregisterElement: function(e) { | 
 |  358       var target = e.detail.target; | 
 |  359       if (target) { | 
 |  360         var index = this._customElements.indexOf(target); | 
 |  361         if (index > -1) { | 
 |  362           this._customElements.splice(index, 1); | 
 |  363           this._customElementsInitialValues.splice(index, 1); | 
 |  364         } | 
 |  365       } | 
 |  366     }, | 
 |  367  | 
 |  368     /** | 
 |  369      * Validates all the required elements (custom and native) in the form. | 
 |  370      * @return {boolean} True if all the elements are valid. | 
 |  371      */ | 
 |  372     validate: function() { | 
 |  373       var valid = true; | 
 |  374  | 
 |  375       // Validate all the custom elements. | 
 |  376       var validatable; | 
 |  377       for (var el, i = 0; el = this._customElements[i], i < this._customElements
     .length; i++) { | 
 |  378         if (!this._isChildOfRegisteredParent(el, false) && !el.disabled) { | 
 |  379           validatable = /** @type {{validate: (function() : boolean)}} */ (el); | 
 |  380           // Some elements may not have correctly defined a validate method. | 
 |  381           if (validatable.validate) | 
 |  382             valid = !!validatable.validate() && valid; | 
 |  383         } | 
 |  384       } | 
 |  385  | 
 |  386       // Validate the form's native elements. | 
 |  387       for (var el, i = 0; el = this.elements[i], i < this.elements.length; i++) 
     { | 
 |  388         // If this native element is inside a custom element that has already | 
 |  389         // registered to this form, skip it. | 
 |  390         if (this._isChildOfRegisteredParent(el, false)) { | 
 |  391           continue; | 
 |  392         } | 
 |  393  | 
 |  394         // Custom elements that extend a native element will also appear in | 
 |  395         // this list, but they've already been validated. | 
 |  396         if (!el.hasAttribute('is') && el.willValidate && el.checkValidity) { | 
 |  397           valid = el.checkValidity() && valid; | 
 |  398         } | 
 |  399       } | 
 |  400  | 
 |  401       return valid; | 
 |  402     }, | 
 |  403  | 
 |  404     /** | 
 |  405      * Returns whether the given element is a radio-button or a checkbox. | 
 |  406      * @return {boolean} True if the element has a `checked` property. | 
 |  407      */ | 
 |  408     _usesCheckedInsteadOfValue: function(el) { | 
 |  409       if (el.type == 'checkbox' || | 
 |  410           el.type == 'radio' || | 
 |  411           el.getAttribute('role') == 'checkbox' || | 
 |  412           el.getAttribute('role') == 'radio' || | 
 |  413           el['_hasIronCheckedElementBehavior']) { | 
 |  414         return true; | 
 |  415       } | 
 |  416       return false; | 
 |  417     }, | 
 |  418  | 
 |  419     _useValue: function(el) { | 
 |  420       // Skip disabled elements or elements that don't have a `name` attribute. | 
 |  421       if (el.disabled || !el.name) { | 
 |  422         return false; | 
 |  423       } | 
 |  424  | 
 |  425       // Checkboxes and radio buttons should only use their value if they're | 
 |  426       // checked. Custom paper-checkbox and paper-radio-button elements | 
 |  427       // don't have a type, but they have the correct role set. | 
 |  428       if (this._usesCheckedInsteadOfValue(el)) | 
 |  429         return el.checked; | 
 |  430       return true; | 
 |  431     }, | 
 |  432  | 
 |  433     _doFakeSubmitForValidation: function() { | 
 |  434       var fakeSubmit = document.createElement('input'); | 
 |  435       fakeSubmit.setAttribute('type', 'submit'); | 
 |  436       fakeSubmit.style.display = 'none'; | 
 |  437       this.appendChild(fakeSubmit); | 
 |  438  | 
 |  439       fakeSubmit.click(); | 
 |  440  | 
 |  441       this.removeChild(fakeSubmit); | 
 |  442     }, | 
 |  443  | 
 |  444     /** | 
 |  445      * Resets all non-disabled form custom elements to their initial values. | 
 |  446      */ | 
 |  447     _resetCustomElements: function() { | 
 |  448       // Reset all the registered custom components. We need to do this after | 
 |  449       // the native reset, since programmatically changing the `value` of some | 
 |  450       // native elements (iron-input in particular) does not notify its | 
 |  451       // parent `paper-input`, which will now display the wrong value. | 
 |  452       this.async(function() { | 
 |  453         for (var el, i = 0; el = this._customElements[i], i < this._customElemen
     ts.length; i++) { | 
 |  454           if (el.disabled) | 
 |  455             continue; | 
 |  456  | 
 |  457           if (this._usesCheckedInsteadOfValue(el)) { | 
 |  458             el.checked = this._customElementsInitialValues[i]; | 
 |  459           } else { | 
 |  460             // The native input/textarea displays literal "undefined" when its | 
 |  461             // its value is set to undefined, so default to null instead. | 
 |  462             var value = this._customElementsInitialValues[i]; | 
 |  463             if (value === undefined) { | 
 |  464               value = null; | 
 |  465             } | 
 |  466             el.value = value; | 
 |  467  | 
 |  468             // In the shady DOM, the native form is all-seeing, and will | 
 |  469             // reset the nested inputs inside <paper-input> and <paper-textarea>
     . | 
 |  470             // In particular, it resets them to what it thinks the default value | 
 |  471             // is (i.e. "", before the bindings have ran), and since this is | 
 |  472             // a programmatic update, it also doesn't fire any events. | 
 |  473             // Which means we need to manually update the native element's value
     . | 
 |  474             if (el.inputElement) { | 
 |  475               el.inputElement.value = el.value; | 
 |  476             } else if (el.textarea) { | 
 |  477               el.textarea.value = el.value; | 
 |  478             } | 
 |  479           } | 
 |  480           el.invalid = false; | 
 |  481         } | 
 |  482  | 
 |  483         this.fire('iron-form-reset'); | 
 |  484       }, 1); | 
 |  485     }, | 
 |  486  | 
 |  487     /** | 
 |  488      * Returns true if `node` is in the shadow DOM of a different element, | 
 |  489      * that has also implemented IronFormElementBehavior and is registered | 
 |  490      * to this form. The second parameter specifies if the parent must have a | 
 |  491      * name to be considered. | 
 |  492      */ | 
 |  493     _isChildOfRegisteredParent: function(node, checkHasName) { | 
 |  494       var parent = node; | 
 |  495  | 
 |  496       // At some point going up the tree we'll find either this form or the docu
     ment. | 
 |  497       while (parent && parent !== document && parent != this) { | 
 |  498         // Use logical parentnode, or native ShadowRoot host. | 
 |  499         parent = Polymer.dom(parent).parentNode || parent.host; | 
 |  500  | 
 |  501         // Check if the parent was registered and submittable. | 
 |  502         if (parent && | 
 |  503             (!checkHasName || parent.name) && | 
 |  504             parent._parentForm === this) { | 
 |  505           return true; | 
 |  506         } | 
 |  507       } | 
 |  508       return false; | 
 |  509     } | 
 |  510  | 
 |  511   }); | 
 |  512  | 
 |  513 </script> | 
| OLD | NEW |