api-console-assets
Version:
This repo only exists to publish api console components to npm
514 lines (438 loc) • 17.4 kB
HTML
<!--
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>
Copyright (c)