Path: blob/trunk/third_party/closure/goog/dom/forms.js
4152 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Utilities for manipulating a form and elements.8*9* @suppress {strictMissingProperties}10*/1112goog.provide('goog.dom.forms');1314goog.require('goog.dom.InputType');15goog.require('goog.dom.TagName');16goog.require('goog.dom.safe');17goog.require('goog.structs.Map');18goog.require('goog.window');192021/**22* Submits form data via a new window. This hides references to the parent23* window and should be used when submitting forms to untrusted 3rd party urls.24* By default, this uses the action and method of the specified form25* element. It is possible to override the default action and method if an26* optional submit element with formaction and/or formmethod attributes is27* provided.28* @param {!HTMLFormElement} form The form.29* @param {!HTMLElement=} opt_submitElement The `<button>` or `<input>` element30* used to submit the form. The element should have a submit type.31* @return {boolean} true If the form was submitted succesfully.32* @throws {!Error} If opt_submitElement is not a valid form submit element.33*/34goog.dom.forms.submitFormInNewWindow = function(form, opt_submitElement) {35'use strict';36var formData = goog.dom.forms.getFormDataMap(form);37var action = form.action;38var method = form.method;3940if (opt_submitElement) {41if (goog.dom.InputType.SUBMIT != opt_submitElement.type.toLowerCase()) {42throw new Error('opt_submitElement does not have a valid type.');43}444546var submitValue =47/** @type {?string} */ (goog.dom.forms.getValue(opt_submitElement));48if (submitValue != null) {49goog.dom.forms.addFormDataToMap_(50formData, opt_submitElement.name, submitValue);51}5253if (opt_submitElement.getAttribute('formaction')) {54action = opt_submitElement.getAttribute('formaction');55}5657if (opt_submitElement.getAttribute('formmethod')) {58method = opt_submitElement.getAttribute('formmethod');59}60}6162return goog.dom.forms.submitFormDataInNewWindow(action, method, formData);63};6465/**66* Submits form data via a new window. This hides references to the parent67* window and should be used when submitting forms to untrusted 3rd party urls.68* @param {string} actionUri uri to submit form content to.69* @param {string} method HTTP method used to submit the form.70* @param {!goog.structs.Map<string, !Array<string>>} formData A map of the form71* data as field name to arrays of values.72* @return {boolean} true If the form was submitted succesfully.73*/74goog.dom.forms.submitFormDataInNewWindow = function(75actionUri, method, formData) {76'use strict';77var newWin = goog.window.openBlank('', {noreferrer: true});7879// This could be null if a new window could not be opened. e.g. if it was80// stopped by a popup blocker.81if (!newWin) {82return false;83}8485var newDocument = newWin.document;8687var newForm =88/** @type {!HTMLFormElement} */ (newDocument.createElement('form'));89newForm.method = method;90goog.dom.safe.setFormElementAction(newForm, actionUri);9192// After this point, do not directly reference the form object's functions as93// field names can shadow the form's properties.9495formData.forEach(function(fieldValues, fieldName) {96'use strict';97for (var i = 0; i < fieldValues.length; i++) {98var fieldValue = fieldValues[i];99var newInput = newDocument.createElement('input');100newInput.name = fieldName;101newInput.value = fieldValue;102newInput.type = 'hidden';103HTMLFormElement.prototype.appendChild.call(newForm, newInput);104}105});106107HTMLFormElement.prototype.submit.call(newForm);108return true;109};110111112/**113* Returns form data as a map of name to value arrays. This doesn't114* support file inputs.115* @param {HTMLFormElement} form The form.116* @return {!goog.structs.Map<string, !Array<string>>} A map of the form data117* as field name to arrays of values.118*/119goog.dom.forms.getFormDataMap = function(form) {120'use strict';121var map = new goog.structs.Map();122goog.dom.forms.getFormDataHelper_(123form, map, goog.dom.forms.addFormDataToMap_);124return map;125};126127128/**129* Returns the form data as an application/x-www-url-encoded string. This130* doesn't support file inputs.131* @param {HTMLFormElement} form The form.132* @return {string} An application/x-www-url-encoded string.133*/134goog.dom.forms.getFormDataString = function(form) {135'use strict';136var sb = [];137goog.dom.forms.getFormDataHelper_(138form, sb, goog.dom.forms.addFormDataToStringBuffer_);139return sb.join('&');140};141142143/**144* Returns the form data as a map or an application/x-www-url-encoded145* string. This doesn't support file inputs.146* @param {HTMLFormElement} form The form.147* @param {Object} result The object form data is being put in.148* @param {Function} fnAppend Function that takes `result`, an element149* name, and an element value, and adds the name/value pair to the result150* object.151* @private152*/153goog.dom.forms.getFormDataHelper_ = function(form, result, fnAppend) {154'use strict';155var els = form.elements;156for (var el, i = 0; el = els.item(i); i++) {157if ( // Make sure we don't include elements that are not part of the form.158// Some browsers include non-form elements. Check for 'form' property.159// See http://code.google.com/p/closure-library/issues/detail?id=227160// and161// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#the-input-element162(el.form != form) || el.disabled ||163// HTMLFieldSetElement has a form property but no value.164el.tagName == goog.dom.TagName.FIELDSET) {165continue;166}167168var name = el.name;169switch (el.type.toLowerCase()) {170case goog.dom.InputType.FILE:171// file inputs are not supported172case goog.dom.InputType.SUBMIT:173case goog.dom.InputType.RESET:174case goog.dom.InputType.BUTTON:175// don't submit these176break;177case goog.dom.InputType.SELECT_MULTIPLE:178var values = goog.dom.forms.getValue(el);179if (values != null) {180for (var value, j = 0; value = values[j]; j++) {181fnAppend(result, name, value);182}183}184break;185default:186var value = goog.dom.forms.getValue(el);187if (value != null) {188fnAppend(result, name, value);189}190}191}192193// input[type=image] are not included in the elements collection194var inputs = form.getElementsByTagName(String(goog.dom.TagName.INPUT));195for (var input, i = 0; input = inputs[i]; i++) {196if (input.form == form &&197input.type.toLowerCase() == goog.dom.InputType.IMAGE) {198name = input.name;199fnAppend(result, name, input.value);200fnAppend(result, name + '.x', '0');201fnAppend(result, name + '.y', '0');202}203}204};205206207/**208* Adds the name/value pair to the map.209* @param {!goog.structs.Map<string, !Array<string>>} map The map to add to.210* @param {string} name The name.211* @param {string} value The value.212* @private213*/214goog.dom.forms.addFormDataToMap_ = function(map, name, value) {215'use strict';216var array = map.get(name);217if (!array) {218array = [];219map.set(name, array);220}221array.push(value);222};223224225/**226* Adds a name/value pair to an string buffer array in the form 'name=value'.227* @param {Array<string>} sb The string buffer array for storing data.228* @param {string} name The name.229* @param {string} value The value.230* @private231*/232goog.dom.forms.addFormDataToStringBuffer_ = function(sb, name, value) {233'use strict';234sb.push(encodeURIComponent(name) + '=' + encodeURIComponent(value));235};236237238/**239* Whether the form has a file input.240* @param {HTMLFormElement} form The form.241* @return {boolean} Whether the form has a file input.242*/243goog.dom.forms.hasFileInput = function(form) {244'use strict';245var els = form.elements;246for (var el, i = 0; el = els[i]; i++) {247if (!el.disabled && el.type &&248el.type.toLowerCase() == goog.dom.InputType.FILE) {249return true;250}251}252return false;253};254255256/**257* Enables or disables either all elements in a form or a single form element.258* @param {Element} el The element, either a form or an element within a form.259* @param {boolean} disabled Whether the element should be disabled.260*/261goog.dom.forms.setDisabled = function(el, disabled) {262'use strict';263// disable all elements in a form264if (el.tagName == goog.dom.TagName.FORM) {265var els = /** @type {!HTMLFormElement} */ (el).elements;266for (var i = 0; el = els.item(i); i++) {267goog.dom.forms.setDisabled(el, disabled);268}269} else {270// makes sure to blur buttons, multi-selects, and any elements which271// maintain keyboard/accessibility focus when disabled272if (disabled == true) {273el.blur();274}275el.disabled = disabled;276}277};278279280/**281* Focuses, and optionally selects the content of, a form element.282* @param {Element} el The form element.283*/284goog.dom.forms.focusAndSelect = function(el) {285'use strict';286el.focus();287if (el.select) {288el.select();289}290};291292293/**294* Whether a form element has a value.295* @param {Element} el The element.296* @return {boolean} Whether the form has a value.297*/298goog.dom.forms.hasValue = function(el) {299'use strict';300var value = goog.dom.forms.getValue(el);301return !!value;302};303304305/**306* Whether a named form field has a value.307* @param {HTMLFormElement} form The form element.308* @param {string} name Name of an input to the form.309* @return {boolean} Whether the form has a value.310*/311goog.dom.forms.hasValueByName = function(form, name) {312'use strict';313var value = goog.dom.forms.getValueByName(form, name);314return !!value;315};316317318/**319* Gets the current value of any element with a type.320* @param {null|!Element|!RadioNodeList<?>} input The element.321* @return {string|Array<string>|null} The current value of the element322* (or null).323*/324goog.dom.forms.getValue = function(input) {325'use strict';326// Elements with a type may need more specialized logic.327var type = /** {{type: (string|undefined)}} */ (input).type;328329if (typeof type === 'string') {330var el = /** @type {!Element} */ (input);331332switch (type.toLowerCase()) {333case goog.dom.InputType.CHECKBOX:334case goog.dom.InputType.RADIO:335return goog.dom.forms.getInputChecked_(el);336case goog.dom.InputType.SELECT_ONE:337return goog.dom.forms.getSelectSingle_(el);338case goog.dom.InputType.SELECT_MULTIPLE:339return goog.dom.forms.getSelectMultiple_(el);340default:341// Not every element with a value has a type (e.g. meter and progress).342}343}344345// Coerce `undefined` to `null`.346return input.value != null ? input.value : null;347};348349350/**351* Returns the value of the named form field. In the case of radio buttons,352* returns the value of the checked button with the given name.353*354* @param {HTMLFormElement} form The form element.355* @param {string} name Name of an input to the form.356*357* @return {Array<string>|string|null} The value of the form element, or358* null if the form element does not exist or has no value.359*/360goog.dom.forms.getValueByName = function(form, name) {361'use strict';362var els = form.elements[name];363364if (!els) {365return null;366} else if (els.type) {367return goog.dom.forms.getValue(/** @type {!Element} */ (els));368} else {369for (var i = 0; i < els.length; i++) {370var val = goog.dom.forms.getValue(els[i]);371if (val) {372return val;373}374}375return null;376}377};378379380/**381* Gets the current value of a checkable input element.382* @param {Element} el The element.383* @return {?string} The value of the form element (or null).384* @private385*/386goog.dom.forms.getInputChecked_ = function(el) {387'use strict';388return el.checked ? /** @type {?} */ (el).value : null;389};390391392/**393* Gets the current value of a select-one element.394* @param {Element} el The element.395* @return {?string} The value of the form element (or null).396* @private397*/398goog.dom.forms.getSelectSingle_ = function(el) {399'use strict';400var selectedIndex = /** @type {!HTMLSelectElement} */ (el).selectedIndex;401return selectedIndex >= 0 ?402/** @type {!HTMLSelectElement} */ (el).options[selectedIndex].value :403null;404};405406407/**408* Gets the current value of a select-multiple element.409* @param {Element} el The element.410* @return {Array<string>?} The value of the form element (or null).411* @private412*/413goog.dom.forms.getSelectMultiple_ = function(el) {414'use strict';415var values = [];416for (var option, i = 0;417option = /** @type {!HTMLSelectElement} */ (el).options[i]; i++) {418if (option.selected) {419values.push(option.value);420}421}422return values.length ? values : null;423};424425426/**427* Sets the current value of any element with a type.428* @param {Element} el The element.429* @param {*=} opt_value The value to give to the element, which will be coerced430* by the browser in the default case using toString. This value should be431* an array for setting the value of select multiple elements.432*/433goog.dom.forms.setValue = function(el, opt_value) {434'use strict';435// Elements with a type may need more specialized logic.436var type = /** @type {!HTMLInputElement} */ (el).type;437switch (typeof type === 'string' && type.toLowerCase()) {438case goog.dom.InputType.CHECKBOX:439case goog.dom.InputType.RADIO:440goog.dom.forms.setInputChecked_(441el,442/** @type {string} */ (opt_value));443return;444case goog.dom.InputType.SELECT_ONE:445goog.dom.forms.setSelectSingle_(446el,447/** @type {string} */ (opt_value));448return;449case goog.dom.InputType.SELECT_MULTIPLE:450goog.dom.forms.setSelectMultiple_(451el,452/** @type {!Array<string>} */ (opt_value));453return;454default:455// Not every element with a value has a type (e.g. meter and progress).456el.value = opt_value != null ? opt_value : '';457}458};459460461/**462* Sets a checkable input element's checked property.463* #TODO(user): This seems potentially unintuitive since it doesn't set464* the value property but my hunch is that the primary use case is to check a465* checkbox, not to reset its value property.466* @param {Element} el The element.467* @param {string|boolean=} opt_value The value, sets the element checked if468* val is set.469* @private470*/471goog.dom.forms.setInputChecked_ = function(el, opt_value) {472'use strict';473el.checked = opt_value;474};475476477/**478* Sets the value of a select-one element.479* @param {Element} el The element.480* @param {string=} opt_value The value of the selected option element.481* @private482*/483goog.dom.forms.setSelectSingle_ = function(el, opt_value) {484'use strict';485// unset any prior selections486el.selectedIndex = -1;487if (typeof opt_value === 'string') {488for (var option, i = 0;489option = /** @type {!HTMLSelectElement} */ (el).options[i]; i++) {490if (option.value == opt_value) {491option.selected = true;492break;493}494}495}496};497498499/**500* Sets the value of a select-multiple element.501* @param {Element} el The element.502* @param {Array<string>|string=} opt_value The value of the selected option503* element(s).504* @private505*/506goog.dom.forms.setSelectMultiple_ = function(el, opt_value) {507'use strict';508// reset string opt_values as an array509if (typeof opt_value === 'string') {510opt_value = [opt_value];511}512for (var option, i = 0;513option = /** @type {!HTMLSelectElement} */ (el).options[i]; i++) {514// we have to reset the other options to false for select-multiple515option.selected = false;516if (opt_value) {517for (var value, j = 0; value = opt_value[j]; j++) {518if (option.value == value) {519option.selected = true;520}521}522}523}524};525526527