diff options
Diffstat (limited to 'catapult/third_party/polymer/components/iron-form/iron-form.html')
-rw-r--r-- | catapult/third_party/polymer/components/iron-form/iron-form.html | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/catapult/third_party/polymer/components/iron-form/iron-form.html b/catapult/third_party/polymer/components/iron-form/iron-form.html new file mode 100644 index 00000000..258ca997 --- /dev/null +++ b/catapult/third_party/polymer/components/iron-form/iron-form.html @@ -0,0 +1,462 @@ +<!-- +@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"> + +<!-- +`<iron-form>` is a wrapper around the HTML `<form>` element, that can +validate and submit both custom and native HTML elements. Note that this +is a breaking change from iron-form 1.0, which was a type extension. + +It has two modes: if `allow-redirect` is true, then after the form submission you +will be redirected to the server response. Otherwise, if it is false, it will +use an `iron-ajax` element to submit the form contents to the server. + + Example: + + <iron-form> + <form method="get" action="/form/handler"> + <input type="text" name="name" value="Batman"> + <input type="checkbox" name="donuts" checked> I like donuts<br> + <paper-checkbox name="cheese" value="yes" checked></paper-checkbox> + </form> + </iron-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 `iron-form`'s `submit` method. + + Example: + + <paper-button raised onclick="submitForm()">Submit</paper-button> + + function submitForm() { + document.getElementById('iron-form').submit(); + } + +If you are not using the `allow-redirect` mode, then you also have the option of +customizing the request sent to the server. To do so, 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['extraParam'] = 'someValue'; + }); + + 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.serializeForm()); + }); + +Note that if you're dynamically creating this element, it's mandatory that you +first create the contained `<form>` element and all its children, and only then +attach it to the `<iron-form>`: + + var wrapper = document.createElement('iron-form'); + var form = document.createElement('form'); + var input = document.createElement('input'); + form.appendChild(input); + document.body.appendChild(wrapper); + wrapper.appendChild(form); + +@element iron-form +@hero hero.svg +@demo demo/index.html +--> + +<dom-module id="iron-form"> + <template> + <style> + :host { + display: block; + } + </style> + + <!-- This form is used to collect the elements that should be submitted --> + <slot></slot> + + <!-- This form is used for submission --> + <form id="helper" action$="[[action]]" method$="[[method]]" enctype$="[[enctype]]"></form> + </template> + + <script> + Polymer({ + is: 'iron-form', + + properties: { + /* + * Set this to true if you don't want the form to be submitted through an + * ajax request, and you want the page to redirect to the action URL + * after the form has been submitted. + */ + allowRedirect: { + type: Boolean, + value: false + }, + /** + * HTTP request headers to send. See PolymerElements/iron-ajax for + * more details. Only works when `allowRedirect` is false. + */ + headers: { + type: Object, + value: function() { return {}; } + }, + /** + * Set the `withCredentials` flag on the request. See PolymerElements/iron-ajax for + * more details. Only works when `allowRedirect` is false. + */ + withCredentials: { + type: Boolean, + value: false + }, + }, + /** + * Fired if the form cannot be submitted because it's invalid. + * + * @event iron-form-invalid + */ + + /** + * Fired after the form is submitted. + * + * @event iron-form-submit + */ + + /** + * Fired before the form is submitted. + * + * @event iron-form-presubmit + */ + + /** + * 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 + * error message is included in event.detail.error and an + * IronRequestElement is included in event.detail.request. + * + * @event iron-form-error + */ + + attached: function() { + this._nodeObserver = Polymer.dom(this).observeNodes( + function(mutations) { + for (var i = 0; i < mutations.addedNodes.length; i++) { + if (mutations.addedNodes[i].tagName === 'FORM' && !this._alreadyCalledInit) { + this._alreadyCalledInit = true; + this._form = mutations.addedNodes[i]; + this._init(); + } + } + }.bind(this)); + }, + + detached: function() { + if (this._nodeObserver) { + Polymer.dom(this).unobserveNodes(this._nodeObserver); + this._nodeObserver = null; + } + }, + + _init: function() { + this._form.addEventListener('submit', this.submit.bind(this)); + this._form.addEventListener('reset', this.reset.bind(this)); + + // Save the initial values. + this._defaults = this._defaults || new WeakMap(); + var nodes = this._getSubmittableElements(); + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i]; + if (!this._defaults.has(node)) { + this._defaults.set(node, { + checked: node.checked, + value: node.value, + }); + } + } + }, + + /** + * Validates all the required elements (custom and native) in the form. + * @return {boolean} True if all the elements are valid. + */ + validate: function() { + if (this._form.getAttribute('novalidate') === '') + return true; + + // Start by making the form check the native elements it knows about. + var valid = this._form.checkValidity(); + var elements = this._getValidatableElements(); + + // Go through all the elements, and validate the custom ones. + for (var el, i = 0; el = elements[i], i < elements.length; i++) { + // This is weird to appease the compiler. We assume the custom element + // has a validate() method, otherwise we can't check it. + var validatable = /** @type {{validate: (function() : boolean)}} */ (el); + if (validatable.validate) { + valid = !!validatable.validate() && valid; + } + } + return valid; + }, + + /** + * Submits the form. + */ + submit: function(event) { + // We are not using this form for submission, so always cancel its event. + if (event) { + event.preventDefault(); + } + + // If you've called this before distribution happened, bail out. + if (!this._form) { + return; + } + + if (!this.validate()) { + this.fire('iron-form-invalid'); + return; + } + + // Remove any existing children in the submission form (from a previous submit). + this.$.helper.textContent = ''; + + var json = this.serializeForm(); + + // If we want a redirect, submit the form natively. + if (this.allowRedirect) { + // If we're submitting the form natively, then create a hidden element for + // each of the values. + for (var element in json) { + this.$.helper.appendChild(this._createHiddenElement(element, json[element])); + } + + // Copy the original form attributes. + this.$.helper.action = this._form.getAttribute('action'); + this.$.helper.method = this._form.getAttribute('method') || 'GET'; + this.$.helper.contentType = this._form.getAttribute('enctype') || 'application/x-www-form-urlencoded'; + + this.$.helper.submit(); + this.fire('iron-form-submit'); + } else { + this._makeAjaxRequest(json); + } + }, + + /** + * Resets the form to the default values. + */ + reset: function(event) { + // We are not using this form for submission, so always cancel its event. + if (event) + event.preventDefault(); + + // If you've called this before distribution happened, bail out. + if (!this._form) { + return; + } + + // Load the initial values. + var nodes = this._getSubmittableElements(); + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i]; + if (this._defaults.has(node)) { + var defaults = this._defaults.get(node); + node.value = defaults.value; + node.checked = defaults.checked; + } + } + }, + + /** + * Serializes the form as will be used in submission. Note that `serialize` + * is a Polymer reserved keyword, so calling `someIronForm`.serialize()` + * will give you unexpected results. + * @return {Object} An object containing name-value pairs for elements that + * would be submitted. + */ + serializeForm: function() { + // Only elements that have a `name` and are not disabled are submittable. + var elements = this._getSubmittableElements(); + var json = {}; + for (var i = 0; i < elements.length; i++) { + var values = this._serializeElementValues(elements[i]); + for (var v = 0; v < values.length; v++) { + this._addSerializedElement(json, elements[i].name, values[v]); + } + } + return json; + }, + + _handleFormResponse: function (event) { + this.fire('iron-form-response', event.detail); + }, + + _handleFormError: function (event) { + this.fire('iron-form-error', event.detail); + }, + + _makeAjaxRequest: function(json) { + // Initialize the iron-ajax element if we haven't already. + if (!this.request) { + this.request = document.createElement('iron-ajax'); + this.request.addEventListener('response', this._handleFormResponse.bind(this)); + this.request.addEventListener('error', this._handleFormError.bind(this)); + } + + // 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._form.getAttribute('action'); + this.request.method = this._form.getAttribute('method') || 'GET'; + this.request.contentType = this._form.getAttribute('enctype') || 'application/x-www-form-urlencoded'; + this.request.withCredentials = this.withCredentials; + this.request.headers = this.headers; + + if (this._form.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); + } + }, + + _getValidatableElements: function() { + return this._findElements(this._form, true); + }, + + _getSubmittableElements: function() { + return this._findElements(this._form, false); + }, + + _findElements: function(parent, ignoreName) { + var nodes = Polymer.dom(parent).querySelectorAll('*'); + var submittable = []; + + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i]; + // An element is submittable if it is not disabled, and if it has a + // 'name' attribute. + if(!node.disabled && (ignoreName || node.name)) { + submittable.push(node); + } + else { + // This element has a root which could contain more submittable elements. + if(node.root) { + Array.prototype.push.apply(submittable, this._findElements(node.root, ignoreName)); + } + } + } + return submittable; + }, + + _serializeElementValues: function(element) { + // We will assume that every custom element that needs to be serialized + // has a `value` property, and it contains the correct value. + // The only weird one is an element that implements IronCheckedElementBehaviour, + // in which case like the native checkbox/radio button, it's only used + // when checked. + // For native elements, from https://www.w3.org/TR/html5/forms.html#the-form-element. + // Native submittable elements: button, input, keygen, object, select, textarea; + // 1. We will skip `keygen and `object` for this iteration, and deal with + // them if they're actually required. + // 2. <button> and <textarea> have a `value` property, so they behave like + // the custom elements. + // 3. <select> can have multiple options selected, in which case its + // `value` is incorrect, and we must use the values of each of its + // `selectedOptions` + // 4. <input> can have a whole bunch of behaviours, so it's handled separately. + // 5. Buttons are hard. The button that was clicked to submit the form + // is the one who's name/value gets sent to the server. + var tag = element.tagName.toLowerCase(); + if (tag === 'button' || (tag === 'input' && (element.type === 'submit' || element.type === 'reset'))) { + return []; + } + + if (tag === 'select') { + return this._serializeSelectValues(element); + } else if (tag === 'input') { + return this._serializeInputValues(element); + } else { + if (element['_hasIronCheckedElementBehavior'] && !element.checked) + return []; + return [element.value]; + } + }, + + _serializeSelectValues: function(element) { + var values = []; + + // A <select multiple> has an array of options, some of which can be selected. + for (var i = 0; i < element.options.length; i++) { + if (element.options[i].selected) { + values.push(element.options[i].value) + } + } + return values; + }, + + _serializeInputValues: function(element) { + // Most of the inputs use their 'value' attribute, with the exception + // of radio buttons, checkboxes and file. + var type = element.type.toLowerCase(); + + // Don't do anything for unchecked checkboxes/radio buttons. + // Don't do anything for file, since that requires a different request. + if (((type === 'checkbox' || type === 'radio') && !element.checked) || + type === 'file') { + return []; + } + return [element.value]; + }, + + _createHiddenElement: function(name, value) { + var input = document.createElement("input"); + input.setAttribute("type", "hidden"); + input.setAttribute("name", name); + input.setAttribute("value", value); + return input; + }, + + _addSerializedElement: function(json, name, value) { + // If the name doesn't exist, add it. Otherwise, serialize it to + // an array, + if (json[name] === undefined) { + json[name] = value; + } else { + if (!Array.isArray(json[name])) { + json[name] = [json[name]]; + } + json[name].push(value); + } + } + }); + </script> +</dom-module> |