Index: third_party/polymer/components/iron-form/iron-form.html |
diff --git a/third_party/polymer/components/iron-form/iron-form.html b/third_party/polymer/components/iron-form/iron-form.html |
new file mode 100644 |
index 0000000000000000000000000000000000000000..4e39f670fadb300e4c7cfe4619c7ae07ad730082 |
--- /dev/null |
+++ b/third_party/polymer/components/iron-form/iron-form.html |
@@ -0,0 +1,513 @@ |
+<!-- |
+@license |
+Copyright (c) 2015 The Polymer Project Authors. All rights reserved. |
+This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
+The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
+The complete set of contributors may be found at http://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 http://polymer.github.io/PATENTS.txt |
+--> |
+ |
+<link rel="import" href="../polymer/polymer.html"> |
+<link rel="import" href="../iron-ajax/iron-ajax.html"> |
+ |
+<script> |
+/* |
+`<iron-form>` is an HTML `<form>` element that can validate and submit any custom |
+elements that implement `Polymer.IronFormElementBehavior`, as well as any |
+native HTML elements. For more information on which attributes are |
+available on the native form element, see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form |
+ |
+It supports both `get` and `post` methods, and uses an `iron-ajax` element to |
+submit the form data to the action URL. |
+ |
+ Example: |
+ |
+ <form is="iron-form" id="form" method="post" action="/form/handler"> |
+ <paper-input name="name" label="name"></paper-input> |
+ <input name="address"> |
+ ... |
+ </form> |
+ |
+By default, a native `<button>` element will submit this form. However, if you |
+want to submit it from a custom element's click handler, you need to explicitly |
+call the form's `submit` method. |
+ |
+ Example: |
+ |
+ <paper-button raised onclick="submitForm()">Submit</paper-button> |
+ |
+ function submitForm() { |
+ document.getElementById('form').submit(); |
+ } |
+ |
+To customize the request sent to the server, you can listen to the `iron-form-presubmit` |
+event, and modify the form's[`iron-ajax`](https://elements.polymer-project.org/elements/iron-ajax) |
+object. However, If you want to not use `iron-ajax` at all, you can cancel the |
+event and do your own custom submission: |
+ |
+ Example of modifying the request, but still using the build-in form submission: |
+ |
+ form.addEventListener('iron-form-presubmit', function() { |
+ this.request.method = 'put'; |
+ this.request.params = someCustomParams; |
+ }); |
+ |
+ Example of bypassing the build-in form submission: |
+ |
+ form.addEventListener('iron-form-presubmit', function(event) { |
+ event.preventDefault(); |
+ var firebase = new Firebase(form.getAttribute('action')); |
+ firebase.set(form.serialize()); |
+ }); |
+ |
+@demo demo/index.html |
+*/ |
+ Polymer({ |
+ |
+ is: 'iron-form', |
+ |
+ extends: 'form', |
+ |
+ properties: { |
+ /** |
+ * By default, the form will display the browser's native validation |
+ * UI (i.e. popup bubbles and invalid styles on invalid fields). You can |
+ * manually disable this; however, if you do, note that you will have to |
+ * manually style invalid *native* HTML fields yourself, as you are |
+ * explicitly preventing the native form from doing so. |
+ */ |
+ disableNativeValidationUi: { |
+ type: Boolean, |
+ value: false |
+ }, |
+ |
+ /** |
+ * Set the withCredentials flag when sending data. |
+ */ |
+ withCredentials: { |
+ type: Boolean, |
+ value: false |
+ }, |
+ |
+ /** |
+ * Content type to use when sending data. If the `contentType` property |
+ * is set and a `Content-Type` header is specified in the `headers` |
+ * property, the `headers` property value will take precedence. |
+ * If Content-Type is set to a value listed below, then |
+ * the `body` (typically used with POST requests) will be encoded accordingly. |
+ * |
+ * * `content-type="application/json"` |
+ * * body is encoded like `{"foo":"bar baz","x":1}` |
+ * * `content-type="application/x-www-form-urlencoded"` |
+ * * body is encoded like `foo=bar+baz&x=1` |
+ */ |
+ contentType: { |
+ type: String, |
+ value: "application/x-www-form-urlencoded" |
+ }, |
+ |
+ /** |
+ * HTTP request headers to send. |
+ * |
+ * Note: setting a `Content-Type` header here will override the value |
+ * specified by the `contentType` property of this element. |
+ */ |
+ headers: { |
+ type: Object, |
+ value: function() { |
+ return {}; |
+ } |
+ }, |
+ |
+ /** |
+ * iron-ajax request object used to submit the form. |
+ */ |
+ request: { |
+ type: Object, |
+ } |
+ }, |
+ |
+ /** |
+ * Fired if the form cannot be submitted because it's invalid. |
+ * |
+ * @event iron-form-invalid |
+ */ |
+ |
+ /** |
+ * Fired before the form is submitted. |
+ * |
+ * @event iron-form-presubmit |
+ */ |
+ |
+ /** |
+ * Fired after the form is submitted. |
+ * |
+ * @event iron-form-submit |
+ */ |
+ |
+ /** |
+ * Fired after the form is reset. |
+ * |
+ * @event iron-form-reset |
+ */ |
+ |
+ /** |
+ * Fired after the form is submitted and a response is received. An |
+ * IronRequestElement is included as the event.detail object. |
+ * |
+ * @event iron-form-response |
+ */ |
+ |
+ /** |
+ * Fired after the form is submitted and an error is received. An |
+ * IronRequestElement is included as the event.detail object. |
+ * |
+ * @event iron-form-error |
+ */ |
+ listeners: { |
+ 'iron-form-element-register': '_registerElement', |
+ 'iron-form-element-unregister': '_unregisterElement', |
+ 'submit': '_onSubmit', |
+ 'reset': '_onReset' |
+ }, |
+ |
+ registered: function() { |
+ // Dear reader: I apologize for what you're about to experience. You see, |
+ // Safari does not respect `required` on input elements, so it never |
+ // has any browser validation bubbles to show. And we have to feature |
+ // detect that, since we rely on the form submission to do the right thing. |
+ // See http://caniuse.com/#search=required. |
+ |
+ // Create a fake form, with an invalid input. If it gets submitted, it's Safari. |
+ var form = document.createElement('form'); |
+ var input = document.createElement('input'); |
+ input.setAttribute('required', 'true'); |
+ form.appendChild(input); |
+ |
+ // If you call submit(), the form doesn't actually fire a submit event, |
+ // so you can't intercept it and cancel it. The event is only fired |
+ // from the magical button click submission. |
+ // See http://wayback.archive.org/web/20090323062817/http://blogs.vertigosoftware.com/snyholm/archive/2006/09/27/3788.aspx. |
+ var button = document.createElement('input'); |
+ button.setAttribute('type', 'submit'); |
+ form.appendChild(button); |
+ |
+ Polymer.clientSupportsFormValidationUI = true; |
+ form.addEventListener('submit', function(event) { |
+ // Oh good! We don't handle `required` correctly. |
+ Polymer.clientSupportsFormValidationUI = false; |
+ event.preventDefault(); |
+ }); |
+ button.click(); |
+ }, |
+ |
+ ready: function() { |
+ // Object that handles the ajax form submission request. |
+ this.request = document.createElement('iron-ajax'); |
+ this.request.addEventListener('response', this._handleFormResponse.bind(this)); |
+ this.request.addEventListener('error', this._handleFormError.bind(this)); |
+ |
+ // Holds all the custom elements registered with this form. |
+ this._customElements = []; |
+ // Holds the initial values of the custom elements registered with this form. |
+ this._customElementsInitialValues = []; |
+ }, |
+ |
+ /** |
+ * Submits the form. |
+ */ |
+ submit: function() { |
+ if (!this.noValidate && !this.validate()) { |
+ // In order to trigger the native browser invalid-form UI, we need |
+ // to do perform a fake form submit. |
+ if (Polymer.clientSupportsFormValidationUI && !this.disableNativeValidationUi) { |
+ this._doFakeSubmitForValidation(); |
+ } |
+ this.fire('iron-form-invalid'); |
+ return; |
+ } |
+ |
+ var json = this.serialize(); |
+ |
+ // Native forms can also index elements magically by their name (can't make |
+ // this up if I tried) so we need to get the correct attributes, not the |
+ // elements with those names. |
+ this.request.url = this.getAttribute('action'); |
+ this.request.method = this.getAttribute('method'); |
+ this.request.contentType = this.contentType; |
+ this.request.withCredentials = this.withCredentials; |
+ this.request.headers = this.headers; |
+ |
+ if (this.request.method.toUpperCase() === 'POST') { |
+ this.request.body = json; |
+ } else { |
+ this.request.params = json; |
+ } |
+ |
+ // Allow for a presubmit hook |
+ var event = this.fire('iron-form-presubmit', {}, {cancelable: true}); |
+ if(!event.defaultPrevented) { |
+ this.request.generateRequest(); |
+ this.fire('iron-form-submit', json); |
+ } |
+ }, |
+ |
+ /** |
+ * Handler that is called when the native form fires a `submit` event |
+ * |
+ * @param {Event} event A `submit` event. |
+ */ |
+ _onSubmit: function(event) { |
+ this.submit(); |
+ |
+ // Don't perform a page refresh. |
+ if (event) { |
+ event.preventDefault(); |
+ } |
+ |
+ return false; |
+ }, |
+ |
+ /** |
+ * Handler that is called when the native form fires a `reset` event |
+ * |
+ * @param {Event} event A `reset` event. |
+ */ |
+ _onReset: function(event) { |
+ this._resetCustomElements(); |
+ }, |
+ |
+ /** |
+ * Returns a json object containing name/value pairs for all the registered |
+ * custom components and native elements of the form. If there are elements |
+ * with duplicate names, then their values will get aggregated into an |
+ * array of values. |
+ * |
+ * @return {!Object} |
+ */ |
+ serialize: function() { |
+ var json = {}; |
+ |
+ function addSerializedElement(name, value) { |
+ // If the name doesn't exist, add it. Otherwise, serialize it to |
+ // an array, |
+ if (!json[name]) { |
+ json[name] = value; |
+ } else { |
+ if (!Array.isArray(json[name])) { |
+ json[name] = [json[name]]; |
+ } |
+ json[name].push(value); |
+ } |
+ } |
+ |
+ // Go through all of the registered custom components. |
+ for (var el, i = 0; el = this._customElements[i], i < this._customElements.length; i++) { |
+ // If this custom element is inside a custom element that has already |
+ // registered to this form, skip it. |
+ if (!this._isChildOfRegisteredParent(el, true) && this._useValue(el)) { |
+ addSerializedElement(el.name, el.value); |
+ } |
+ } |
+ |
+ // Also go through the form's native elements. |
+ for (var el, i = 0; el = this.elements[i], i < this.elements.length; i++) { |
+ // If this native element is inside a custom element that has already |
+ // registered to this form, skip it. |
+ if (this._isChildOfRegisteredParent(el, true) || !this._useValue(el)) { |
+ continue; |
+ } |
+ |
+ // A <select multiple> has an array of values. |
+ if (el.tagName.toLowerCase() === 'select' && el.multiple) { |
+ for (var o = 0; o < el.options.length; o++) { |
+ if (el.options[o].selected) { |
+ addSerializedElement(el.name, el.options[o].value); |
+ } |
+ } |
+ } else { |
+ addSerializedElement(el.name, el.value); |
+ } |
+ } |
+ |
+ return json; |
+ }, |
+ |
+ _handleFormResponse: function (event) { |
+ this.fire('iron-form-response', event.detail); |
+ }, |
+ |
+ _handleFormError: function (event) { |
+ this.fire('iron-form-error', event.detail); |
+ }, |
+ |
+ _registerElement: function(e) { |
+ // Get the actual element that fired the event |
+ var element = Polymer.dom(e).rootTarget; |
+ |
+ element._parentForm = this; |
+ this._customElements.push(element); |
+ |
+ // Save the original value of this input. |
+ this._customElementsInitialValues.push( |
+ this._usesCheckedInsteadOfValue(element) ? element.checked : element.value); |
+ }, |
+ |
+ _unregisterElement: function(e) { |
+ var target = e.detail.target; |
+ if (target) { |
+ var index = this._customElements.indexOf(target); |
+ if (index > -1) { |
+ this._customElements.splice(index, 1); |
+ this._customElementsInitialValues.splice(index, 1); |
+ } |
+ } |
+ }, |
+ |
+ /** |
+ * Validates all the required elements (custom and native) in the form. |
+ * @return {boolean} True if all the elements are valid. |
+ */ |
+ validate: function() { |
+ var valid = true; |
+ |
+ // Validate all the custom elements. |
+ var validatable; |
+ for (var el, i = 0; el = this._customElements[i], i < this._customElements.length; i++) { |
+ if (!this._isChildOfRegisteredParent(el, false) && !el.disabled) { |
+ validatable = /** @type {{validate: (function() : boolean)}} */ (el); |
+ // Some elements may not have correctly defined a validate method. |
+ if (validatable.validate) |
+ valid = !!validatable.validate() && valid; |
+ } |
+ } |
+ |
+ // Validate the form's native elements. |
+ for (var el, i = 0; el = this.elements[i], i < this.elements.length; i++) { |
+ // If this native element is inside a custom element that has already |
+ // registered to this form, skip it. |
+ if (this._isChildOfRegisteredParent(el, false)) { |
+ continue; |
+ } |
+ |
+ // Custom elements that extend a native element will also appear in |
+ // this list, but they've already been validated. |
+ if (!el.hasAttribute('is') && el.willValidate && el.checkValidity) { |
+ valid = el.checkValidity() && valid; |
+ } |
+ } |
+ |
+ return valid; |
+ }, |
+ |
+ /** |
+ * Returns whether the given element is a radio-button or a checkbox. |
+ * @return {boolean} True if the element has a `checked` property. |
+ */ |
+ _usesCheckedInsteadOfValue: function(el) { |
+ if (el.type == 'checkbox' || |
+ el.type == 'radio' || |
+ el.getAttribute('role') == 'checkbox' || |
+ el.getAttribute('role') == 'radio' || |
+ el['_hasIronCheckedElementBehavior']) { |
+ return true; |
+ } |
+ return false; |
+ }, |
+ |
+ _useValue: function(el) { |
+ // Skip disabled elements or elements that don't have a `name` attribute. |
+ if (el.disabled || !el.name) { |
+ return false; |
+ } |
+ |
+ // Checkboxes and radio buttons should only use their value if they're |
+ // checked. Custom paper-checkbox and paper-radio-button elements |
+ // don't have a type, but they have the correct role set. |
+ if (this._usesCheckedInsteadOfValue(el)) |
+ return el.checked; |
+ return true; |
+ }, |
+ |
+ _doFakeSubmitForValidation: function() { |
+ var fakeSubmit = document.createElement('input'); |
+ fakeSubmit.setAttribute('type', 'submit'); |
+ fakeSubmit.style.display = 'none'; |
+ this.appendChild(fakeSubmit); |
+ |
+ fakeSubmit.click(); |
+ |
+ this.removeChild(fakeSubmit); |
+ }, |
+ |
+ /** |
+ * Resets all non-disabled form custom elements to their initial values. |
+ */ |
+ _resetCustomElements: function() { |
+ // Reset all the registered custom components. We need to do this after |
+ // the native reset, since programmatically changing the `value` of some |
+ // native elements (iron-input in particular) does not notify its |
+ // parent `paper-input`, which will now display the wrong value. |
+ this.async(function() { |
+ for (var el, i = 0; el = this._customElements[i], i < this._customElements.length; i++) { |
+ if (el.disabled) |
+ continue; |
+ |
+ if (this._usesCheckedInsteadOfValue(el)) { |
+ el.checked = this._customElementsInitialValues[i]; |
+ } else { |
+ // The native input/textarea displays literal "undefined" when its |
+ // its value is set to undefined, so default to null instead. |
+ var value = this._customElementsInitialValues[i]; |
+ if (value === undefined) { |
+ value = null; |
+ } |
+ el.value = value; |
+ |
+ // In the shady DOM, the native form is all-seeing, and will |
+ // reset the nested inputs inside <paper-input> and <paper-textarea>. |
+ // In particular, it resets them to what it thinks the default value |
+ // is (i.e. "", before the bindings have ran), and since this is |
+ // a programmatic update, it also doesn't fire any events. |
+ // Which means we need to manually update the native element's value. |
+ if (el.inputElement) { |
+ el.inputElement.value = el.value; |
+ } else if (el.textarea) { |
+ el.textarea.value = el.value; |
+ } |
+ } |
+ el.invalid = false; |
+ } |
+ |
+ this.fire('iron-form-reset'); |
+ }, 1); |
+ }, |
+ |
+ /** |
+ * Returns true if `node` is in the shadow DOM of a different element, |
+ * that has also implemented IronFormElementBehavior and is registered |
+ * to this form. The second parameter specifies if the parent must have a |
+ * name to be considered. |
+ */ |
+ _isChildOfRegisteredParent: function(node, checkHasName) { |
+ var parent = node; |
+ |
+ // At some point going up the tree we'll find either this form or the document. |
+ while (parent && parent !== document && parent != this) { |
+ // Use logical parentnode, or native ShadowRoot host. |
+ parent = Polymer.dom(parent).parentNode || parent.host; |
+ |
+ // Check if the parent was registered and submittable. |
+ if (parent && |
+ (!checkHasName || parent.name) && |
+ parent._parentForm === this) { |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ }); |
+ |
+</script> |