Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exposing input change event and form.submit action #5707

Merged
merged 6 commits into from
Oct 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 22 additions & 0 deletions build-system/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,28 @@ app.use('/form/echo-json/post', function(req, res) {
});
});

app.use('/form/json/poll1', function(req, res) {
assertCors(req, res, ['POST']);
var form = new formidable.IncomingForm();
form.parse(req, function(err, fields) {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({
result: [{
answer: 'Penguins',
percentage: new Array(77),
}, {
answer: 'Ostriches',
percentage: new Array(8),
}, {
answer: 'Kiwis',
percentage: new Array(14),
}, {
answer: 'Wekas',
percentage: new Array(1),
},]
}));
});
});

app.use('/form/search-html/get', function(req, res) {
res.setHeader('Content-Type', 'text/html');
Expand Down
1 change: 1 addition & 0 deletions build-system/tasks/presubmit-checks.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ var forbiddenTerms = {
whitelist: [
'src/service/action-impl.js',
'extensions/amp-access/0.1/amp-access.js',
'extensions/amp-form/0.1/amp-form.js',
],
},
'installActivityService': {
Expand Down
100 changes: 100 additions & 0 deletions examples/forms.amp.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,51 @@
align-items: center;
color: white;
}


#poll1 li {
position: relative;
padding: 1em;
list-style: none;
margin: 0;
display: block;
height: 20px;
}

#poll1.amp-form-submit-success fieldset {
display: none;
}

.percentage-container {
z-index: 0;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.one-pc {
width: 1%;
height: 100%;
display: inline-block;
}
.Wekas .one-pc {
background: #dc4e41;
}
.Penguins .one-pc {
background: #3f51b5;
}
.Ostriches .one-pc {
background: #4CAF50;
}
.Kiwis .one-pc {
background: #00ffff;
}
.poll1-results .title {
z-index: 1;
color: black;
position: absolute;
}
</style>
</head>
<body>
Expand Down Expand Up @@ -372,5 +417,60 @@ <h4>Show As You Go Messages On Submit</h4>
</fieldset>
</form>

<h4 id="header1">on=change and form.submit examples</h4>
<button on="tap:header1.submit">Wrong Action - should throw an error</button>
<form method="post"
id="poll1"
action-xhr="/form/json/poll1"
target="_blank"
custom-validation-reporting="as-you-go">
<fieldset>
<p>What is your favorite flightless bird?</p>
<ul>
<li>
<label>
<input name="question1" value="Penguins" type="radio" on="change:poll1.submit">
Penguins
</label>
</li>
<li>
<label>
<input name="question1" value="Ostriches" type="radio" on="change:poll1.submit">
Ostriches
</label>
</li>
<li>
<label>
<input name="question1" value="Kiwis" type="radio" on="change:poll1.submit">
Kiwis
</label>
</li>
<li>
<label>
<input name="question1" value="Wekas" type="radio" on="change:poll1.submit">
Wekas
</label>
</li>
</ul>
</fieldset>

<div submit-success>
<template type="amp-mustache">
Thanks for answering the poll! Here are the results!
<ul class="poll1-results">
{{#result}}
<li>
<div class="percentage-container {{answer}}">
{{#percentage}}<span class="one-pc"></span>{{/percentage}}
</div>
<span class="title">{{answer}}</span>
</li>
{{/result}}
</ul>
</template>
</div>
</form>


</body>
</html>
39 changes: 28 additions & 11 deletions extensions/amp-form/0.1/amp-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,6 @@ export class AmpForm {
this.form_.classList.add('-amp-form');

const submitButtons = this.form_.querySelectorAll('input[type=submit]');
user().assert(submitButtons && submitButtons.length > 0,
'form requires at least one <input type=submit>: %s', this.form_);

/** @const @private {!Array<!Element>} */
this.submitButtons_ = toArray(submitButtons);

Expand All @@ -138,11 +135,23 @@ export class AmpForm {
/** @const @private {!./form-validators.FormValidator} */
this.validator_ = getFormValidator(this.form_);

this.installSubmitHandler_();
this.actions_.installActionHandler(
this.form_, this.actionHandler_.bind(this));
this.installEventHandlers_();
}

/**
* @param {!../../../src/service/action-impl.ActionInvocation} invocation
* @private
*/
actionHandler_(invocation) {
if (invocation.method == 'submit') {
this.handleSubmit_();
}
}

/** @private */
installSubmitHandler_() {
installEventHandlers_() {
this.form_.addEventListener('submit', e => this.handleSubmit_(e), true);
this.form_.addEventListener('blur', e => {
onInputInteraction_(e);
Expand All @@ -163,20 +172,24 @@ export class AmpForm {
* invalid. stopImmediatePropagation allows us to make sure we don't trigger it
*
*
* @param {!Event} e
* @param {?Event=} opt_event
* @private
*/
handleSubmit_(e) {
handleSubmit_(opt_event) {
if (this.state_ == FormState_.SUBMITTING) {
e.stopImmediatePropagation();
if (opt_event) {
opt_event.stopImmediatePropagation();
}
return;
}

// Validity checking should always occur, novalidate only circumvent
// reporting and blocking submission on non-valid forms.
const isValid = checkUserValidityOnSubmission(this.form_);
if (this.shouldValidate_ && !isValid) {
e.stopImmediatePropagation();
if (opt_event) {
opt_event.stopImmediatePropagation();
}
// TODO(#3776): Use .mutate method when it supports passing state.
this.vsync_.run({
measure: undefined,
Expand All @@ -188,7 +201,9 @@ export class AmpForm {
}

if (this.xhrAction_) {
e.preventDefault();
if (opt_event) {
opt_event.preventDefault();
}
this.cleanupRenderedTemplate_();
this.setState_(FormState_.SUBMITTING);
const isHeadOrGet = this.method_ == 'GET' || this.method_ == 'HEAD';
Expand All @@ -212,7 +227,9 @@ export class AmpForm {
rethrowAsync('Form submission failed:', error);
});
} else if (this.method_ == 'POST') {
e.preventDefault();
if (opt_event) {
opt_event.preventDefault();
}
user().assert(false,
'Only XHR based (via action-xhr attribute) submissions are support ' +
'for POST requests. %s',
Expand Down
43 changes: 22 additions & 21 deletions extensions/amp-form/0.1/test/test-amp-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {installDocService,} from
'../../../../src/service/ampdoc-impl';
import {installActionServiceForDoc,} from
'../../../../src/service/action-impl';
import {actionServiceForDoc} from '../../../../src/action';

describe('amp-form', () => {

Expand Down Expand Up @@ -89,14 +90,6 @@ describe('amp-form', () => {
sandbox.restore();
});

it('should assert form has at least 1 submit button', () => {
let form = getForm(document, false, false);
expect(() => new AmpForm(form)).to.throw(
/form requires at least one <input type=submit>/);
form = getForm(document, true, false);
expect(() => new AmpForm(form)).to.not.throw;
});

it('should assert valid action-xhr when provided', () => {
const form = getForm();
form.setAttribute('action-xhr', 'http://example.com');
Expand Down Expand Up @@ -141,7 +134,7 @@ describe('amp-form', () => {
target: form,
preventDefault: sandbox.spy(),
};
sandbox.spy(ampForm.xhr_, 'fetchJson');
sandbox.stub(ampForm.xhr_, 'fetchJson').returns(Promise.resolve());
sandbox.spy(form, 'checkValidity');
ampForm.handleSubmit_(event);
expect(event.stopImmediatePropagation.called).to.be.true;
Expand All @@ -158,7 +151,7 @@ describe('amp-form', () => {
target: form,
preventDefault: sandbox.spy(),
};
sandbox.spy(ampForm.xhr_, 'fetchJson');
sandbox.stub(ampForm.xhr_, 'fetchJson').returns(Promise.resolve());
sandbox.spy(form, 'checkValidity');
expect(() => ampForm.handleSubmit_(event)).to.throw(
/Only XHR based \(via action-xhr attribute\) submissions are support/);
Expand All @@ -175,6 +168,7 @@ describe('amp-form', () => {
emailInput.setAttribute('required', '');
form.appendChild(emailInput);
const ampForm = new AmpForm(form);
sandbox.stub(ampForm.xhr_, 'fetchJson').returns(Promise.resolve());
const event = {
stopImmediatePropagation: sandbox.spy(),
target: form,
Expand Down Expand Up @@ -212,7 +206,7 @@ describe('amp-form', () => {
emailInput.setAttribute('required', '');
form.appendChild(emailInput);
sandbox.spy(form, 'checkValidity');
sandbox.spy(ampForm.xhr_, 'fetchJson');
sandbox.stub(ampForm.xhr_, 'fetchJson').returns(Promise.resolve());

const event = {
stopImmediatePropagation: sandbox.spy(),
Expand Down Expand Up @@ -345,7 +339,7 @@ describe('amp-form', () => {
sandbox.stub(ampForm.xhr_, 'fetchJson').returns(new Promise(resolve => {
fetchJsonResolver = resolve;
}));
sandbox.spy(ampForm.actions_, 'trigger');
sandbox.stub(ampForm.actions_, 'trigger');
const form = ampForm.form_;
const event = {
stopImmediatePropagation: sandbox.spy(),
Expand Down Expand Up @@ -378,7 +372,7 @@ describe('amp-form', () => {
.returns(new Promise((unusedResolve, reject) => {
fetchJsonRejecter = reject;
}));
sandbox.spy(ampForm.actions_, 'trigger');
sandbox.stub(ampForm.actions_, 'trigger');
const form = ampForm.form_;
const event = {
stopImmediatePropagation: sandbox.spy(),
Expand Down Expand Up @@ -473,22 +467,16 @@ describe('amp-form', () => {
const newRender = document.createElement('div');
newRender.innerText = 'New Success: What What';

let fetchJsonResolver;
sandbox.stub(ampForm.xhr_, 'fetchJson')
.returns(new Promise(resolve => {
fetchJsonResolver = resolve;
}));
.returns(Promise.resolve({'message': 'What What'}));
sandbox.stub(ampForm.templates_, 'findAndRenderTemplate')
.returns(new Promise(resolve => {
resolve(newRender);
}));
.returns(Promise.resolve(newRender));
const event = {
stopImmediatePropagation: sandbox.spy(),
target: form,
preventDefault: sandbox.spy(),
};
ampForm.handleSubmit_(event);
fetchJsonResolver({'message': 'What What'});
return timer.promise(5).then(() => {
expect(ampForm.templates_.findAndRenderTemplate.called).to.be.true;
expect(ampForm.templates_.findAndRenderTemplate.calledWith(
Expand Down Expand Up @@ -826,4 +814,17 @@ describe('amp-form', () => {
});
});

it('should install action handler and handle submit action', () => {
const form = getForm();
const actions = actionServiceForDoc(form.ownerDocument);
sandbox.stub(actions, 'installActionHandler');
const ampForm = new AmpForm(form);
sandbox.stub(ampForm.xhr_, 'fetchJson').returns(Promise.resolve());
expect(actions.installActionHandler).to.be.calledWith(form);
sandbox.spy(ampForm, 'handleSubmit_');
ampForm.actionHandler_({method: 'anything'});
expect(ampForm.handleSubmit_).to.have.not.been.called;
ampForm.actionHandler_({method: 'submit'});
expect(ampForm.handleSubmit_).to.have.been.called;
});
});