Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

Commit

Permalink
fix(checkbox): enter submits form when submit button is disabled (#11640
Browse files Browse the repository at this point in the history
)

match the behavior of the native input type checkbox

Fixes #11639
  • Loading branch information
Splaktar authored and jelbourn committed Feb 21, 2019
1 parent 7cde443 commit 251cfed
Show file tree
Hide file tree
Showing 3 changed files with 312 additions and 14 deletions.
18 changes: 15 additions & 3 deletions src/components/checkbox/checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,19 +169,31 @@ function MdCheckboxDirective(inputDirective, $mdAria, $mdConstant, $mdTheming, $
}
}

/**
* @param {KeyboardEvent} ev 'keypress' event to handle
*/
function keypressHandler(ev) {
var keyCode = ev.which || ev.keyCode;
var submit, form;

ev.preventDefault();
switch (keyCode) {
case $mdConstant.KEY_CODE.SPACE:
element.addClass('md-focused');
listener(ev);
break;
case $mdConstant.KEY_CODE.ENTER:
var form = $mdUtil.getClosest(ev.target, 'form');
// We have to use a native event here as the form directive does not use jqlite
// Match the behavior of the native <input type="checkbox">.
// When the enter key is pressed while focusing a native checkbox inside a form,
// the browser will trigger a `click` on the first non-disabled submit button/input
// in the form. Note that this is different from text inputs, which
// will directly submit the form without needing a submit button/input to be present.
form = $mdUtil.getClosest(ev.target, 'form');
if (form) {
form.dispatchEvent(new Event('submit'));
submit = form.querySelector('button[type="submit"]:enabled, input[type="submit"]:enabled');
if (submit) {
submit.click();
}
}
break;
}
Expand Down
305 changes: 295 additions & 10 deletions src/components/checkbox/checkbox.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@
describe('mdCheckbox', function() {
var CHECKED_CSS = 'md-checked';
var INDETERMINATE_CSS = 'md-indeterminate';
var $compile, $log, pageScope, $mdConstant;
var $compile, $log, pageScope, $mdConstant, $window;

beforeEach(module('ngAria', 'material.components.checkbox'));

beforeEach(inject(function($injector) {
$compile = $injector.get('$compile');
$log = $injector.get('$log');
$mdConstant = $injector.get('$mdConstant');
$window = $injector.get('$window');

var $rootScope = $injector.get('$rootScope');
pageScope = $rootScope.$new();
pageScope = $rootScope.$new(false);
}));

function compileAndLink(template, opt_scope) {
Expand Down Expand Up @@ -298,15 +299,299 @@ describe('mdCheckbox', function() {
expect(isChecked(checkbox)).toBe(false);
});

it('should submit a parent form when ENTER is pressed', function () {
var form = compileAndLink('<form><md-checkbox ng-checked="value"></md-checkbox></form>');
angular.element(form[0].getElementsByTagName('md-checkbox')[0]).triggerHandler({
type: 'keypress',
keyCode: $mdConstant.KEY_CODE.ENTER
it('should not submit a parent form when ENTER is pressed and there is no submit button/input',
function() {
var submitted = false;
pageScope.onSubmit = function() {
submitted = true;
};
var form = compileAndLink('<form name="form" ng-submit="onSubmit()">' +
'<md-checkbox ng-checked="value"></md-checkbox></form>');

// We need to add the form to the DOM in order for `submit` events to be properly fired.
var node = $window.document.body.appendChild(form[0]);
angular.element(form[0].getElementsByTagName('md-checkbox')[0]).triggerHandler({
type: 'keypress',
keyCode: $mdConstant.KEY_CODE.ENTER
});
pageScope.$apply();
expect(submitted).toBe(false);
expect(pageScope.form.$submitted).toBe(false);
$window.document.body.removeChild(node);
});

it('should click an enabled submit button when ENTER is pressed',
function() {
var submitted = false;
pageScope.onSubmit = function() {
submitted = true;
};
var form = compileAndLink(
'<form name="form"><md-checkbox ng-checked="value"></md-checkbox>' +
'<button type="submit" ng-click="onSubmit()">Submit</button></form>');

// We need to add the form to the DOM in order for `submit` events to be properly fired.
var node = $window.document.body.appendChild(form[0]);
angular.element(form[0].getElementsByTagName('md-checkbox')[0]).triggerHandler({
type: 'keypress',
keyCode: $mdConstant.KEY_CODE.ENTER
});
pageScope.$apply();
expect(submitted).toBe(true);
expect(pageScope.form.$submitted).toBe(true);
$window.document.body.removeChild(node);
});

it('should click an enabled submit input when ENTER is pressed',
function() {
var submitted = false;
pageScope.onSubmit = function() {
submitted = true;
};
var form = compileAndLink(
'<form name="form"><md-checkbox ng-checked="value"></md-checkbox>' +
'<input type="submit" ng-click="onSubmit()" value="Submit"></form>');

// We need to add the form to the DOM in order for `submit` events to be properly fired.
var node = $window.document.body.appendChild(form[0]);
angular.element(form[0].getElementsByTagName('md-checkbox')[0]).triggerHandler({
type: 'keypress',
keyCode: $mdConstant.KEY_CODE.ENTER
});
pageScope.$apply();
expect(submitted).toBe(true);
expect(pageScope.form.$submitted).toBe(true);
$window.document.body.removeChild(node);
});

it('should submit a parent form when ENTER is pressed and there is an enabled submit button',
function() {
var submitted = false;
pageScope.onSubmit = function() {
submitted = true;
};
var form = compileAndLink(
'<form name="form" ng-submit="onSubmit()"><md-checkbox ng-checked="value">' +
'</md-checkbox><button type="submit">Submit</button></form>');

// We need to add the form to the DOM in order for `submit` events to be properly fired.
var node = $window.document.body.appendChild(form[0]);
angular.element(form[0].getElementsByTagName('md-checkbox')[0]).triggerHandler({
type: 'keypress',
keyCode: $mdConstant.KEY_CODE.ENTER
});
pageScope.$apply();
expect(submitted).toBe(true);
expect(pageScope.form.$submitted).toBe(true);
$window.document.body.removeChild(node);
});

it('should submit a parent form when ENTER is pressed and there is an enabled submit input',
function() {
var submitted = false;
pageScope.onSubmit = function() {
submitted = true;
};
var form = compileAndLink(
'<form name="form" ng-submit="onSubmit()"><md-checkbox ng-checked="value">' +
'</md-checkbox><input type="submit" value="Submit"></form>');

// We need to add the form to the DOM in order for `submit` events to be properly fired.
var node = $window.document.body.appendChild(form[0]);
angular.element(form[0].getElementsByTagName('md-checkbox')[0]).triggerHandler({
type: 'keypress',
keyCode: $mdConstant.KEY_CODE.ENTER
});
pageScope.$apply();
expect(submitted).toBe(true);
expect(pageScope.form.$submitted).toBe(true);
$window.document.body.removeChild(node);
});

it('should not submit a parent form when ENTER is pressed and the submit components are disabled',
function() {
var submitted = false;
pageScope.onSubmit = function() {
submitted = true;
};
var form = compileAndLink(
'<form name="form"><md-checkbox ng-checked="value"></md-checkbox>' +
'<input type="submit" ng-click="onSubmit()" value="Submit" disabled>' +
'<button type="submit" ng-click="onSubmit()" disabled>Submit</button></form>');

// We need to add the form to the DOM in order for `submit` events to be properly fired.
var node = $window.document.body.appendChild(form[0]);
angular.element(form[0].getElementsByTagName('md-checkbox')[0]).triggerHandler({
type: 'keypress',
keyCode: $mdConstant.KEY_CODE.ENTER
});
pageScope.$apply();
expect(submitted).toBe(false);
expect(pageScope.form.$submitted).toBe(false);
$window.document.body.removeChild(node);
});

it('should click the first enabled submit input when there are multiple',
function() {
var submitted = false, submitted2 = false;
pageScope.onSubmit = function() {
submitted = true;
};
pageScope.onSubmit2 = function() {
submitted2 = true;
};
var form = compileAndLink(
'<form name="form"><md-checkbox ng-checked="value">' +
'</md-checkbox><input type="submit" value="Submit" ng-click="onSubmit()">' +
'<input type="submit" value="Second Submit" ng-click="onSubmit2()"></form>');

// We need to add the form to the DOM in order for `submit` events to be properly fired.
var node = $window.document.body.appendChild(form[0]);
angular.element(form[0].getElementsByTagName('md-checkbox')[0]).triggerHandler({
type: 'keypress',
keyCode: $mdConstant.KEY_CODE.ENTER
});
pageScope.$apply();
expect(submitted).toBe(true);
expect(submitted2).toBe(false);
expect(pageScope.form.$submitted).toBe(true);
$window.document.body.removeChild(node);
});

it('should click the first enabled submit button when there are multiple',
function() {
var submitted = false, submitted2 = false;
pageScope.onSubmit = function() {
submitted = true;
};
pageScope.onSubmit2 = function() {
submitted2 = true;
};
var form = compileAndLink(
'<form name="form"><md-checkbox ng-checked="value">' +
'</md-checkbox><button type="submit" ng-click="onSubmit()">Submit</button>' +
'<button type="submit" ng-click="onSubmit2()">Second Submit</button></form>');

// We need to add the form to the DOM in order for `submit` events to be properly fired.
var node = $window.document.body.appendChild(form[0]);
angular.element(form[0].getElementsByTagName('md-checkbox')[0]).triggerHandler({
type: 'keypress',
keyCode: $mdConstant.KEY_CODE.ENTER
});
pageScope.$apply();
expect(submitted).toBe(true);
expect(submitted2).toBe(false);
expect(pageScope.form.$submitted).toBe(true);
$window.document.body.removeChild(node);
});

it('should click the first submit button when there is a submit input after it',
function() {
var submitted = false, submitted2 = false;
pageScope.onSubmit = function() {
submitted = true;
};
pageScope.onSubmit2 = function() {
submitted2 = true;
};
var form = compileAndLink(
'<form name="form"><md-checkbox ng-checked="value">' +
'</md-checkbox><button type="submit" ng-click="onSubmit()">Submit</button>' +
'<input type="submit" value="Second Submit" ng-click="onSubmit2()"></form>');

// We need to add the form to the DOM in order for `submit` events to be properly fired.
var node = $window.document.body.appendChild(form[0]);
angular.element(form[0].getElementsByTagName('md-checkbox')[0]).triggerHandler({
type: 'keypress',
keyCode: $mdConstant.KEY_CODE.ENTER
});
pageScope.$apply();
expect(submitted).toBe(true);
expect(submitted2).toBe(false);
expect(pageScope.form.$submitted).toBe(true);
$window.document.body.removeChild(node);
});

it('should click the first submit input when there is a submit button after it',
function() {
var submitted = false, submitted2 = false;
pageScope.onSubmit = function() {
submitted = true;
};
pageScope.onSubmit2 = function() {
submitted2 = true;
};
var form = compileAndLink(
'<form name="form"><md-checkbox ng-checked="value">' +
'</md-checkbox><input type="submit" value="Submit" ng-click="onSubmit()">' +
'<button type="submit" ng-click="onSubmit2()">Second Submit</button></form>');

// We need to add the form to the DOM in order for `submit` events to be properly fired.
var node = $window.document.body.appendChild(form[0]);
angular.element(form[0].getElementsByTagName('md-checkbox')[0]).triggerHandler({
type: 'keypress',
keyCode: $mdConstant.KEY_CODE.ENTER
});
pageScope.$apply();
expect(submitted).toBe(true);
expect(submitted2).toBe(false);
expect(pageScope.form.$submitted).toBe(true);
$window.document.body.removeChild(node);
});

it('should click the submit button when the first submit input is disabled',
function() {
var submitted = false, submitted2 = false;
pageScope.onSubmit = function() {
submitted = true;
};
pageScope.onSubmit2 = function() {
submitted2 = true;
};
var form = compileAndLink(
'<form name="form"><md-checkbox ng-checked="value">' +
'</md-checkbox><input type="submit" value="Submit" ng-click="onSubmit()" disabled>' +
'<button type="submit" ng-click="onSubmit2()">Second Submit</button></form>');

// We need to add the form to the DOM in order for `submit` events to be properly fired.
var node = $window.document.body.appendChild(form[0]);
angular.element(form[0].getElementsByTagName('md-checkbox')[0]).triggerHandler({
type: 'keypress',
keyCode: $mdConstant.KEY_CODE.ENTER
});
pageScope.$apply();
expect(submitted).toBe(false);
expect(submitted2).toBe(true);
expect(pageScope.form.$submitted).toBe(true);
$window.document.body.removeChild(node);
});

it('should click the submit input when the first submit button is disabled',
function() {
var submitted = false, submitted2 = false;
pageScope.onSubmit = function() {
submitted = true;
};
pageScope.onSubmit2 = function() {
submitted2 = true;
};
var form = compileAndLink(
'<form name="form"><md-checkbox ng-checked="value">' +
'</md-checkbox><button type="submit" ng-click="onSubmit()" disabled>Submit</button>' +
'<input type="submit" value="Second Submit" ng-click="onSubmit2()"></form>');

// We need to add the form to the DOM in order for `submit` events to be properly fired.
var node = $window.document.body.appendChild(form[0]);
angular.element(form[0].getElementsByTagName('md-checkbox')[0]).triggerHandler({
type: 'keypress',
keyCode: $mdConstant.KEY_CODE.ENTER
});
pageScope.$apply();
expect(submitted).toBe(false);
expect(submitted2).toBe(true);
expect(pageScope.form.$submitted).toBe(true);
$window.document.body.removeChild(node);
});
pageScope.$apply();
expect(form[0].classList.contains('ng-submitted')).toBe(true);
});

it('should mark the checkbox as selected on load with ng-checked', function() {
pageScope.isChecked = function() { return true; };
Expand Down
3 changes: 2 additions & 1 deletion src/core/util/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,8 @@ function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $in
* @param {string|function} validateWith If a string is passed, it will be evaluated against
* each of the parent nodes' tag name. If a function is passed, the loop will call it with each
* of the parents and will use the return value to determine whether the node is a match.
* @param {boolean} onlyParent Only start checking from the parent element, not `el`.
* @param {boolean=} onlyParent Only start checking from the parent element, not `el`.
* @returns {Node|null} closest matching parent Node or null if not found
*/
getClosest: function getClosest(el, validateWith, onlyParent) {
if (angular.isString(validateWith)) {
Expand Down

0 comments on commit 251cfed

Please sign in to comment.