Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(997)

Side by Side Diff: third_party/polymer/components/iron-form/iron-form.html

Issue 2906483004: [pinpoint] Add iron-form and paper-checkbox to polymer components. (Closed)
Patch Set: Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 <!--
2 @license
3 Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
4 This code may only be used under the BSD style license found at http://polymer.g ithub.io/LICENSE.txt
5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6 The complete set of contributors may be found at http://polymer.github.io/CONTRI BUTORS.txt
7 Code distributed by Google as part of the polymer project is also
8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN TS.txt
9 -->
10
11 <link rel="import" href="../polymer/polymer.html">
12 <link rel="import" href="../iron-ajax/iron-ajax.html">
13
14 <script>
15 /*
16 `<iron-form>` is an HTML `<form>` element that can validate and submit any custo m
17 elements that implement `Polymer.IronFormElementBehavior`, as well as any
18 native HTML elements. For more information on which attributes are
19 available on the native form element, see https://developer.mozilla.org/en-US/do cs/Web/HTML/Element/form
20
21 It supports both `get` and `post` methods, and uses an `iron-ajax` element to
22 submit the form data to the action URL.
23
24 Example:
25
26 <form is="iron-form" id="form" method="post" action="/form/handler">
27 <paper-input name="name" label="name"></paper-input>
28 <input name="address">
29 ...
30 </form>
31
32 By default, a native `<button>` element will submit this form. However, if you
33 want to submit it from a custom element's click handler, you need to explicitly
34 call the form's `submit` method.
35
36 Example:
37
38 <paper-button raised onclick="submitForm()">Submit</paper-button>
39
40 function submitForm() {
41 document.getElementById('form').submit();
42 }
43
44 To customize the request sent to the server, you can listen to the `iron-form-pr esubmit`
45 event, and modify the form's[`iron-ajax`](https://elements.polymer-project.org/e lements/iron-ajax)
46 object. However, If you want to not use `iron-ajax` at all, you can cancel the
47 event and do your own custom submission:
48
49 Example of modifying the request, but still using the build-in form submission :
50
51 form.addEventListener('iron-form-presubmit', function() {
52 this.request.method = 'put';
53 this.request.params = someCustomParams;
54 });
55
56 Example of bypassing the build-in form submission:
57
58 form.addEventListener('iron-form-presubmit', function(event) {
59 event.preventDefault();
60 var firebase = new Firebase(form.getAttribute('action'));
61 firebase.set(form.serialize());
62 });
63
64 @demo demo/index.html
65 */
66 Polymer({
67
68 is: 'iron-form',
69
70 extends: 'form',
71
72 properties: {
73 /**
74 * By default, the form will display the browser's native validation
75 * UI (i.e. popup bubbles and invalid styles on invalid fields). You can
76 * manually disable this; however, if you do, note that you will have to
77 * manually style invalid *native* HTML fields yourself, as you are
78 * explicitly preventing the native form from doing so.
79 */
80 disableNativeValidationUi: {
81 type: Boolean,
82 value: false
83 },
84
85 /**
86 * Set the withCredentials flag when sending data.
87 */
88 withCredentials: {
89 type: Boolean,
90 value: false
91 },
92
93 /**
94 * Content type to use when sending data. If the `contentType` property
95 * is set and a `Content-Type` header is specified in the `headers`
96 * property, the `headers` property value will take precedence.
97 * If Content-Type is set to a value listed below, then
98 * the `body` (typically used with POST requests) will be encoded accordin gly.
99 *
100 * * `content-type="application/json"`
101 * * body is encoded like `{"foo":"bar baz","x":1}`
102 * * `content-type="application/x-www-form-urlencoded"`
103 * * body is encoded like `foo=bar+baz&x=1`
104 */
105 contentType: {
106 type: String,
107 value: "application/x-www-form-urlencoded"
108 },
109
110 /**
111 * HTTP request headers to send.
112 *
113 * Note: setting a `Content-Type` header here will override the value
114 * specified by the `contentType` property of this element.
115 */
116 headers: {
117 type: Object,
118 value: function() {
119 return {};
120 }
121 },
122
123 /**
124 * iron-ajax request object used to submit the form.
125 */
126 request: {
127 type: Object,
128 }
129 },
130
131 /**
132 * Fired if the form cannot be submitted because it's invalid.
133 *
134 * @event iron-form-invalid
135 */
136
137 /**
138 * Fired before the form is submitted.
139 *
140 * @event iron-form-presubmit
141 */
142
143 /**
144 * Fired after the form is submitted.
145 *
146 * @event iron-form-submit
147 */
148
149 /**
150 * Fired after the form is reset.
151 *
152 * @event iron-form-reset
153 */
154
155 /**
156 * Fired after the form is submitted and a response is received. An
157 * IronRequestElement is included as the event.detail object.
158 *
159 * @event iron-form-response
160 */
161
162 /**
163 * Fired after the form is submitted and an error is received. An
164 * IronRequestElement is included as the event.detail object.
165 *
166 * @event iron-form-error
167 */
168 listeners: {
169 'iron-form-element-register': '_registerElement',
170 'iron-form-element-unregister': '_unregisterElement',
171 'submit': '_onSubmit',
172 'reset': '_onReset'
173 },
174
175 registered: function() {
176 // Dear reader: I apologize for what you're about to experience. You see,
177 // Safari does not respect `required` on input elements, so it never
178 // has any browser validation bubbles to show. And we have to feature
179 // detect that, since we rely on the form submission to do the right thing .
180 // See http://caniuse.com/#search=required.
181
182 // Create a fake form, with an invalid input. If it gets submitted, it's S afari.
183 var form = document.createElement('form');
184 var input = document.createElement('input');
185 input.setAttribute('required', 'true');
186 form.appendChild(input);
187
188 // If you call submit(), the form doesn't actually fire a submit event,
189 // so you can't intercept it and cancel it. The event is only fired
190 // from the magical button click submission.
191 // See http://wayback.archive.org/web/20090323062817/http://blogs.vertigos oftware.com/snyholm/archive/2006/09/27/3788.aspx.
192 var button = document.createElement('input');
193 button.setAttribute('type', 'submit');
194 form.appendChild(button);
195
196 Polymer.clientSupportsFormValidationUI = true;
197 form.addEventListener('submit', function(event) {
198 // Oh good! We don't handle `required` correctly.
199 Polymer.clientSupportsFormValidationUI = false;
200 event.preventDefault();
201 });
202 button.click();
203 },
204
205 ready: function() {
206 // Object that handles the ajax form submission request.
207 this.request = document.createElement('iron-ajax');
208 this.request.addEventListener('response', this._handleFormResponse.bind(th is));
209 this.request.addEventListener('error', this._handleFormError.bind(this));
210
211 // Holds all the custom elements registered with this form.
212 this._customElements = [];
213 // Holds the initial values of the custom elements registered with this fo rm.
214 this._customElementsInitialValues = [];
215 },
216
217 /**
218 * Submits the form.
219 */
220 submit: function() {
221 if (!this.noValidate && !this.validate()) {
222 // In order to trigger the native browser invalid-form UI, we need
223 // to do perform a fake form submit.
224 if (Polymer.clientSupportsFormValidationUI && !this.disableNativeValidat ionUi) {
225 this._doFakeSubmitForValidation();
226 }
227 this.fire('iron-form-invalid');
228 return;
229 }
230
231 var json = this.serialize();
232
233 // Native forms can also index elements magically by their name (can't mak e
234 // this up if I tried) so we need to get the correct attributes, not the
235 // elements with those names.
236 this.request.url = this.getAttribute('action');
237 this.request.method = this.getAttribute('method');
238 this.request.contentType = this.contentType;
239 this.request.withCredentials = this.withCredentials;
240 this.request.headers = this.headers;
241
242 if (this.request.method.toUpperCase() === 'POST') {
243 this.request.body = json;
244 } else {
245 this.request.params = json;
246 }
247
248 // Allow for a presubmit hook
249 var event = this.fire('iron-form-presubmit', {}, {cancelable: true});
250 if(!event.defaultPrevented) {
251 this.request.generateRequest();
252 this.fire('iron-form-submit', json);
253 }
254 },
255
256 /**
257 * Handler that is called when the native form fires a `submit` event
258 *
259 * @param {Event} event A `submit` event.
260 */
261 _onSubmit: function(event) {
262 this.submit();
263
264 // Don't perform a page refresh.
265 if (event) {
266 event.preventDefault();
267 }
268
269 return false;
270 },
271
272 /**
273 * Handler that is called when the native form fires a `reset` event
274 *
275 * @param {Event} event A `reset` event.
276 */
277 _onReset: function(event) {
278 this._resetCustomElements();
279 },
280
281 /**
282 * Returns a json object containing name/value pairs for all the registered
283 * custom components and native elements of the form. If there are elements
284 * with duplicate names, then their values will get aggregated into an
285 * array of values.
286 *
287 * @return {!Object}
288 */
289 serialize: function() {
290 var json = {};
291
292 function addSerializedElement(name, value) {
293 // If the name doesn't exist, add it. Otherwise, serialize it to
294 // an array,
295 if (!json[name]) {
296 json[name] = value;
297 } else {
298 if (!Array.isArray(json[name])) {
299 json[name] = [json[name]];
300 }
301 json[name].push(value);
302 }
303 }
304
305 // Go through all of the registered custom components.
306 for (var el, i = 0; el = this._customElements[i], i < this._customElements .length; i++) {
307 // If this custom element is inside a custom element that has already
308 // registered to this form, skip it.
309 if (!this._isChildOfRegisteredParent(el, true) && this._useValue(el)) {
310 addSerializedElement(el.name, el.value);
311 }
312 }
313
314 // Also go through the form's native elements.
315 for (var el, i = 0; el = this.elements[i], i < this.elements.length; i++) {
316 // If this native element is inside a custom element that has already
317 // registered to this form, skip it.
318 if (this._isChildOfRegisteredParent(el, true) || !this._useValue(el)) {
319 continue;
320 }
321
322 // A <select multiple> has an array of values.
323 if (el.tagName.toLowerCase() === 'select' && el.multiple) {
324 for (var o = 0; o < el.options.length; o++) {
325 if (el.options[o].selected) {
326 addSerializedElement(el.name, el.options[o].value);
327 }
328 }
329 } else {
330 addSerializedElement(el.name, el.value);
331 }
332 }
333
334 return json;
335 },
336
337 _handleFormResponse: function (event) {
338 this.fire('iron-form-response', event.detail);
339 },
340
341 _handleFormError: function (event) {
342 this.fire('iron-form-error', event.detail);
343 },
344
345 _registerElement: function(e) {
346 // Get the actual element that fired the event
347 var element = Polymer.dom(e).rootTarget;
348
349 element._parentForm = this;
350 this._customElements.push(element);
351
352 // Save the original value of this input.
353 this._customElementsInitialValues.push(
354 this._usesCheckedInsteadOfValue(element) ? element.checked : element.v alue);
355 },
356
357 _unregisterElement: function(e) {
358 var target = e.detail.target;
359 if (target) {
360 var index = this._customElements.indexOf(target);
361 if (index > -1) {
362 this._customElements.splice(index, 1);
363 this._customElementsInitialValues.splice(index, 1);
364 }
365 }
366 },
367
368 /**
369 * Validates all the required elements (custom and native) in the form.
370 * @return {boolean} True if all the elements are valid.
371 */
372 validate: function() {
373 var valid = true;
374
375 // Validate all the custom elements.
376 var validatable;
377 for (var el, i = 0; el = this._customElements[i], i < this._customElements .length; i++) {
378 if (!this._isChildOfRegisteredParent(el, false) && !el.disabled) {
379 validatable = /** @type {{validate: (function() : boolean)}} */ (el);
380 // Some elements may not have correctly defined a validate method.
381 if (validatable.validate)
382 valid = !!validatable.validate() && valid;
383 }
384 }
385
386 // Validate the form's native elements.
387 for (var el, i = 0; el = this.elements[i], i < this.elements.length; i++) {
388 // If this native element is inside a custom element that has already
389 // registered to this form, skip it.
390 if (this._isChildOfRegisteredParent(el, false)) {
391 continue;
392 }
393
394 // Custom elements that extend a native element will also appear in
395 // this list, but they've already been validated.
396 if (!el.hasAttribute('is') && el.willValidate && el.checkValidity) {
397 valid = el.checkValidity() && valid;
398 }
399 }
400
401 return valid;
402 },
403
404 /**
405 * Returns whether the given element is a radio-button or a checkbox.
406 * @return {boolean} True if the element has a `checked` property.
407 */
408 _usesCheckedInsteadOfValue: function(el) {
409 if (el.type == 'checkbox' ||
410 el.type == 'radio' ||
411 el.getAttribute('role') == 'checkbox' ||
412 el.getAttribute('role') == 'radio' ||
413 el['_hasIronCheckedElementBehavior']) {
414 return true;
415 }
416 return false;
417 },
418
419 _useValue: function(el) {
420 // Skip disabled elements or elements that don't have a `name` attribute.
421 if (el.disabled || !el.name) {
422 return false;
423 }
424
425 // Checkboxes and radio buttons should only use their value if they're
426 // checked. Custom paper-checkbox and paper-radio-button elements
427 // don't have a type, but they have the correct role set.
428 if (this._usesCheckedInsteadOfValue(el))
429 return el.checked;
430 return true;
431 },
432
433 _doFakeSubmitForValidation: function() {
434 var fakeSubmit = document.createElement('input');
435 fakeSubmit.setAttribute('type', 'submit');
436 fakeSubmit.style.display = 'none';
437 this.appendChild(fakeSubmit);
438
439 fakeSubmit.click();
440
441 this.removeChild(fakeSubmit);
442 },
443
444 /**
445 * Resets all non-disabled form custom elements to their initial values.
446 */
447 _resetCustomElements: function() {
448 // Reset all the registered custom components. We need to do this after
449 // the native reset, since programmatically changing the `value` of some
450 // native elements (iron-input in particular) does not notify its
451 // parent `paper-input`, which will now display the wrong value.
452 this.async(function() {
453 for (var el, i = 0; el = this._customElements[i], i < this._customElemen ts.length; i++) {
454 if (el.disabled)
455 continue;
456
457 if (this._usesCheckedInsteadOfValue(el)) {
458 el.checked = this._customElementsInitialValues[i];
459 } else {
460 // The native input/textarea displays literal "undefined" when its
461 // its value is set to undefined, so default to null instead.
462 var value = this._customElementsInitialValues[i];
463 if (value === undefined) {
464 value = null;
465 }
466 el.value = value;
467
468 // In the shady DOM, the native form is all-seeing, and will
469 // reset the nested inputs inside <paper-input> and <paper-textarea> .
470 // In particular, it resets them to what it thinks the default value
471 // is (i.e. "", before the bindings have ran), and since this is
472 // a programmatic update, it also doesn't fire any events.
473 // Which means we need to manually update the native element's value .
474 if (el.inputElement) {
475 el.inputElement.value = el.value;
476 } else if (el.textarea) {
477 el.textarea.value = el.value;
478 }
479 }
480 el.invalid = false;
481 }
482
483 this.fire('iron-form-reset');
484 }, 1);
485 },
486
487 /**
488 * Returns true if `node` is in the shadow DOM of a different element,
489 * that has also implemented IronFormElementBehavior and is registered
490 * to this form. The second parameter specifies if the parent must have a
491 * name to be considered.
492 */
493 _isChildOfRegisteredParent: function(node, checkHasName) {
494 var parent = node;
495
496 // At some point going up the tree we'll find either this form or the docu ment.
497 while (parent && parent !== document && parent != this) {
498 // Use logical parentnode, or native ShadowRoot host.
499 parent = Polymer.dom(parent).parentNode || parent.host;
500
501 // Check if the parent was registered and submittable.
502 if (parent &&
503 (!checkHasName || parent.name) &&
504 parent._parentForm === this) {
505 return true;
506 }
507 }
508 return false;
509 }
510
511 });
512
513 </script>
OLDNEW
« no previous file with comments | « third_party/polymer/components/iron-form/index.html ('k') | third_party/polymer/components/iron-form/test/basic.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698