UNPKG

api-console-assets

Version:

This repo only exists to publish api console components to npm

514 lines (438 loc) 17.4 kB
<!-- @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') || 'GET'; 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] === undefined) { 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>