forms.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Utilities for manipulating a form and elements.
  16. *
  17. * @author arv@google.com (Erik Arvidsson)
  18. */
  19. goog.provide('goog.dom.forms');
  20. goog.require('goog.dom.InputType');
  21. goog.require('goog.dom.TagName');
  22. goog.require('goog.structs.Map');
  23. goog.require('goog.window');
  24. /**
  25. * Submits form data via a new window. This hides references to the parent
  26. * window and should be used when submitting forms to untrusted 3rd party urls.
  27. * By default, this uses the action and method of the specified form
  28. * element. It is possible to override the default action and method if an
  29. * optional submit element with formaction and/or formmethod attributes is
  30. * provided.
  31. * @param {!HTMLFormElement} form The form.
  32. * @param {!HTMLElement=} opt_submitElement The `<button>` or `<input>` element
  33. * used to submit the form. The element should have a submit type.
  34. * @return {boolean} true If the form was submitted succesfully.
  35. * @throws {!Error} If opt_submitElement is not a valid form submit element.
  36. */
  37. goog.dom.forms.submitFormInNewWindow = function(form, opt_submitElement) {
  38. var formData = goog.dom.forms.getFormDataMap(form);
  39. var action = form.action;
  40. var method = form.method;
  41. if (opt_submitElement) {
  42. if (goog.dom.InputType.SUBMIT != opt_submitElement.type.toLowerCase()) {
  43. throw Error('opt_submitElement does not have a valid type.');
  44. }
  45. var submitValue =
  46. /** @type {?string} */ (goog.dom.forms.getValue(opt_submitElement));
  47. if (submitValue != null) {
  48. goog.dom.forms.addFormDataToMap_(
  49. formData, opt_submitElement.name, submitValue);
  50. }
  51. if (opt_submitElement.getAttribute('formaction')) {
  52. action = opt_submitElement.getAttribute('formaction');
  53. }
  54. if (opt_submitElement.getAttribute('formmethod')) {
  55. method = opt_submitElement.getAttribute('formmethod');
  56. }
  57. }
  58. return goog.dom.forms.submitFormDataInNewWindow(action, method, formData);
  59. };
  60. /**
  61. * Submits form data via a new window. This hides references to the parent
  62. * window and should be used when submitting forms to untrusted 3rd party urls.
  63. * @param {string} actionUri uri to submit form content to.
  64. * @param {string} method HTTP method used to submit the form.
  65. * @param {!goog.structs.Map<string, !Array<string>>} formData A map of the form
  66. * data as field name to arrays of values.
  67. * @return {boolean} true If the form was submitted succesfully.
  68. */
  69. goog.dom.forms.submitFormDataInNewWindow = function(
  70. actionUri, method, formData) {
  71. var newWin = goog.window.openBlank('', {noreferrer: true});
  72. // This could be null if a new window could not be opened. e.g. if it was
  73. // stopped by a popup blocker.
  74. if (!newWin) {
  75. return false;
  76. }
  77. var newDocument = newWin.document;
  78. var newForm =
  79. /** @type {!HTMLFormElement} */ (newDocument.createElement('form'));
  80. newForm.method = method;
  81. newForm.action = actionUri;
  82. // After this point, do not directly reference the form object's functions as
  83. // field names can shadow the form's properties.
  84. formData.forEach(function(fieldValues, fieldName) {
  85. for (var i = 0; i < fieldValues.length; i++) {
  86. var fieldValue = fieldValues[i];
  87. var newInput = newDocument.createElement('input');
  88. newInput.name = fieldName;
  89. newInput.value = fieldValue;
  90. newInput.type = 'hidden';
  91. HTMLFormElement.prototype.appendChild.call(newForm, newInput);
  92. }
  93. });
  94. HTMLFormElement.prototype.submit.call(newForm);
  95. return true;
  96. };
  97. /**
  98. * Returns form data as a map of name to value arrays. This doesn't
  99. * support file inputs.
  100. * @param {HTMLFormElement} form The form.
  101. * @return {!goog.structs.Map<string, !Array<string>>} A map of the form data
  102. * as field name to arrays of values.
  103. */
  104. goog.dom.forms.getFormDataMap = function(form) {
  105. var map = new goog.structs.Map();
  106. goog.dom.forms.getFormDataHelper_(
  107. form, map, goog.dom.forms.addFormDataToMap_);
  108. return map;
  109. };
  110. /**
  111. * Returns the form data as an application/x-www-url-encoded string. This
  112. * doesn't support file inputs.
  113. * @param {HTMLFormElement} form The form.
  114. * @return {string} An application/x-www-url-encoded string.
  115. */
  116. goog.dom.forms.getFormDataString = function(form) {
  117. var sb = [];
  118. goog.dom.forms.getFormDataHelper_(
  119. form, sb, goog.dom.forms.addFormDataToStringBuffer_);
  120. return sb.join('&');
  121. };
  122. /**
  123. * Returns the form data as a map or an application/x-www-url-encoded
  124. * string. This doesn't support file inputs.
  125. * @param {HTMLFormElement} form The form.
  126. * @param {Object} result The object form data is being put in.
  127. * @param {Function} fnAppend Function that takes {@code result}, an element
  128. * name, and an element value, and adds the name/value pair to the result
  129. * object.
  130. * @private
  131. */
  132. goog.dom.forms.getFormDataHelper_ = function(form, result, fnAppend) {
  133. var els = form.elements;
  134. for (var el, i = 0; el = els[i]; i++) {
  135. if ( // Make sure we don't include elements that are not part of the form.
  136. // Some browsers include non-form elements. Check for 'form' property.
  137. // See http://code.google.com/p/closure-library/issues/detail?id=227
  138. // and
  139. // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#the-input-element
  140. (el.form != form) || el.disabled ||
  141. // HTMLFieldSetElement has a form property but no value.
  142. el.tagName == goog.dom.TagName.FIELDSET) {
  143. continue;
  144. }
  145. var name = el.name;
  146. switch (el.type.toLowerCase()) {
  147. case goog.dom.InputType.FILE:
  148. // file inputs are not supported
  149. case goog.dom.InputType.SUBMIT:
  150. case goog.dom.InputType.RESET:
  151. case goog.dom.InputType.BUTTON:
  152. // don't submit these
  153. break;
  154. case goog.dom.InputType.SELECT_MULTIPLE:
  155. var values = goog.dom.forms.getValue(el);
  156. if (values != null) {
  157. for (var value, j = 0; value = values[j]; j++) {
  158. fnAppend(result, name, value);
  159. }
  160. }
  161. break;
  162. default:
  163. var value = goog.dom.forms.getValue(el);
  164. if (value != null) {
  165. fnAppend(result, name, value);
  166. }
  167. }
  168. }
  169. // input[type=image] are not included in the elements collection
  170. var inputs = form.getElementsByTagName(String(goog.dom.TagName.INPUT));
  171. for (var input, i = 0; input = inputs[i]; i++) {
  172. if (input.form == form &&
  173. input.type.toLowerCase() == goog.dom.InputType.IMAGE) {
  174. name = input.name;
  175. fnAppend(result, name, input.value);
  176. fnAppend(result, name + '.x', '0');
  177. fnAppend(result, name + '.y', '0');
  178. }
  179. }
  180. };
  181. /**
  182. * Adds the name/value pair to the map.
  183. * @param {!goog.structs.Map<string, !Array<string>>} map The map to add to.
  184. * @param {string} name The name.
  185. * @param {string} value The value.
  186. * @private
  187. */
  188. goog.dom.forms.addFormDataToMap_ = function(map, name, value) {
  189. var array = map.get(name);
  190. if (!array) {
  191. array = [];
  192. map.set(name, array);
  193. }
  194. array.push(value);
  195. };
  196. /**
  197. * Adds a name/value pair to an string buffer array in the form 'name=value'.
  198. * @param {Array<string>} sb The string buffer array for storing data.
  199. * @param {string} name The name.
  200. * @param {string} value The value.
  201. * @private
  202. */
  203. goog.dom.forms.addFormDataToStringBuffer_ = function(sb, name, value) {
  204. sb.push(encodeURIComponent(name) + '=' + encodeURIComponent(value));
  205. };
  206. /**
  207. * Whether the form has a file input.
  208. * @param {HTMLFormElement} form The form.
  209. * @return {boolean} Whether the form has a file input.
  210. */
  211. goog.dom.forms.hasFileInput = function(form) {
  212. var els = form.elements;
  213. for (var el, i = 0; el = els[i]; i++) {
  214. if (!el.disabled && el.type &&
  215. el.type.toLowerCase() == goog.dom.InputType.FILE) {
  216. return true;
  217. }
  218. }
  219. return false;
  220. };
  221. /**
  222. * Enables or disables either all elements in a form or a single form element.
  223. * @param {Element} el The element, either a form or an element within a form.
  224. * @param {boolean} disabled Whether the element should be disabled.
  225. */
  226. goog.dom.forms.setDisabled = function(el, disabled) {
  227. // disable all elements in a form
  228. if (el.tagName == goog.dom.TagName.FORM) {
  229. var els = /** @type {!HTMLFormElement} */ (el).elements;
  230. for (var i = 0; el = els[i]; i++) {
  231. goog.dom.forms.setDisabled(el, disabled);
  232. }
  233. } else {
  234. // makes sure to blur buttons, multi-selects, and any elements which
  235. // maintain keyboard/accessibility focus when disabled
  236. if (disabled == true) {
  237. el.blur();
  238. }
  239. el.disabled = disabled;
  240. }
  241. };
  242. /**
  243. * Focuses, and optionally selects the content of, a form element.
  244. * @param {Element} el The form element.
  245. */
  246. goog.dom.forms.focusAndSelect = function(el) {
  247. el.focus();
  248. if (el.select) {
  249. el.select();
  250. }
  251. };
  252. /**
  253. * Whether a form element has a value.
  254. * @param {Element} el The element.
  255. * @return {boolean} Whether the form has a value.
  256. */
  257. goog.dom.forms.hasValue = function(el) {
  258. var value = goog.dom.forms.getValue(el);
  259. return !!value;
  260. };
  261. /**
  262. * Whether a named form field has a value.
  263. * @param {HTMLFormElement} form The form element.
  264. * @param {string} name Name of an input to the form.
  265. * @return {boolean} Whether the form has a value.
  266. */
  267. goog.dom.forms.hasValueByName = function(form, name) {
  268. var value = goog.dom.forms.getValueByName(form, name);
  269. return !!value;
  270. };
  271. /**
  272. * Gets the current value of any element with a type.
  273. * @param {Element} el The element.
  274. * @return {string|Array<string>|null} The current value of the element
  275. * (or null).
  276. */
  277. goog.dom.forms.getValue = function(el) {
  278. var type = /** @type {!HTMLInputElement} */ (el).type;
  279. if (!goog.isDef(type)) {
  280. return null;
  281. }
  282. switch (type.toLowerCase()) {
  283. case goog.dom.InputType.CHECKBOX:
  284. case goog.dom.InputType.RADIO:
  285. return goog.dom.forms.getInputChecked_(el);
  286. case goog.dom.InputType.SELECT_ONE:
  287. return goog.dom.forms.getSelectSingle_(el);
  288. case goog.dom.InputType.SELECT_MULTIPLE:
  289. return goog.dom.forms.getSelectMultiple_(el);
  290. default:
  291. return goog.isDef(el.value) ? el.value : null;
  292. }
  293. };
  294. /**
  295. * Returns the value of the named form field. In the case of radio buttons,
  296. * returns the value of the checked button with the given name.
  297. *
  298. * @param {HTMLFormElement} form The form element.
  299. * @param {string} name Name of an input to the form.
  300. *
  301. * @return {Array<string>|string|null} The value of the form element, or
  302. * null if the form element does not exist or has no value.
  303. */
  304. goog.dom.forms.getValueByName = function(form, name) {
  305. var els = form.elements[name];
  306. if (els) {
  307. if (els.type) {
  308. return goog.dom.forms.getValue(els);
  309. } else {
  310. for (var i = 0; i < els.length; i++) {
  311. var val = goog.dom.forms.getValue(els[i]);
  312. if (val) {
  313. return val;
  314. }
  315. }
  316. }
  317. }
  318. return null;
  319. };
  320. /**
  321. * Gets the current value of a checkable input element.
  322. * @param {Element} el The element.
  323. * @return {?string} The value of the form element (or null).
  324. * @private
  325. */
  326. goog.dom.forms.getInputChecked_ = function(el) {
  327. return el.checked ? /** @type {?} */ (el).value : null;
  328. };
  329. /**
  330. * Gets the current value of a select-one element.
  331. * @param {Element} el The element.
  332. * @return {?string} The value of the form element (or null).
  333. * @private
  334. */
  335. goog.dom.forms.getSelectSingle_ = function(el) {
  336. var selectedIndex = /** @type {!HTMLSelectElement} */ (el).selectedIndex;
  337. return selectedIndex >= 0 ?
  338. /** @type {!HTMLSelectElement} */ (el).options[selectedIndex].value :
  339. null;
  340. };
  341. /**
  342. * Gets the current value of a select-multiple element.
  343. * @param {Element} el The element.
  344. * @return {Array<string>?} The value of the form element (or null).
  345. * @private
  346. */
  347. goog.dom.forms.getSelectMultiple_ = function(el) {
  348. var values = [];
  349. for (var option, i = 0;
  350. option = /** @type {!HTMLSelectElement} */ (el).options[i]; i++) {
  351. if (option.selected) {
  352. values.push(option.value);
  353. }
  354. }
  355. return values.length ? values : null;
  356. };
  357. /**
  358. * Sets the current value of any element with a type.
  359. * @param {Element} el The element.
  360. * @param {*=} opt_value The value to give to the element, which will be coerced
  361. * by the browser in the default case using toString. This value should be
  362. * an array for setting the value of select multiple elements.
  363. */
  364. goog.dom.forms.setValue = function(el, opt_value) {
  365. var type = /** @type {!HTMLInputElement} */ (el).type;
  366. if (goog.isDef(type)) {
  367. switch (type.toLowerCase()) {
  368. case goog.dom.InputType.CHECKBOX:
  369. case goog.dom.InputType.RADIO:
  370. goog.dom.forms.setInputChecked_(
  371. el,
  372. /** @type {string} */ (opt_value));
  373. break;
  374. case goog.dom.InputType.SELECT_ONE:
  375. goog.dom.forms.setSelectSingle_(
  376. el,
  377. /** @type {string} */ (opt_value));
  378. break;
  379. case goog.dom.InputType.SELECT_MULTIPLE:
  380. goog.dom.forms.setSelectMultiple_(
  381. el,
  382. /** @type {Array<string>} */ (opt_value));
  383. break;
  384. default:
  385. el.value = goog.isDefAndNotNull(opt_value) ? opt_value : '';
  386. }
  387. }
  388. };
  389. /**
  390. * Sets a checkable input element's checked property.
  391. * #TODO(user): This seems potentially unintuitive since it doesn't set
  392. * the value property but my hunch is that the primary use case is to check a
  393. * checkbox, not to reset its value property.
  394. * @param {Element} el The element.
  395. * @param {string|boolean=} opt_value The value, sets the element checked if
  396. * val is set.
  397. * @private
  398. */
  399. goog.dom.forms.setInputChecked_ = function(el, opt_value) {
  400. el.checked = opt_value;
  401. };
  402. /**
  403. * Sets the value of a select-one element.
  404. * @param {Element} el The element.
  405. * @param {string=} opt_value The value of the selected option element.
  406. * @private
  407. */
  408. goog.dom.forms.setSelectSingle_ = function(el, opt_value) {
  409. // unset any prior selections
  410. el.selectedIndex = -1;
  411. if (goog.isString(opt_value)) {
  412. for (var option, i = 0;
  413. option = /** @type {!HTMLSelectElement} */ (el).options[i]; i++) {
  414. if (option.value == opt_value) {
  415. option.selected = true;
  416. break;
  417. }
  418. }
  419. }
  420. };
  421. /**
  422. * Sets the value of a select-multiple element.
  423. * @param {Element} el The element.
  424. * @param {Array<string>|string=} opt_value The value of the selected option
  425. * element(s).
  426. * @private
  427. */
  428. goog.dom.forms.setSelectMultiple_ = function(el, opt_value) {
  429. // reset string opt_values as an array
  430. if (goog.isString(opt_value)) {
  431. opt_value = [opt_value];
  432. }
  433. for (var option, i = 0;
  434. option = /** @type {!HTMLSelectElement} */ (el).options[i]; i++) {
  435. // we have to reset the other options to false for select-multiple
  436. option.selected = false;
  437. if (opt_value) {
  438. for (var value, j = 0; value = opt_value[j]; j++) {
  439. if (option.value == value) {
  440. option.selected = true;
  441. }
  442. }
  443. }
  444. }
  445. };