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
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 committed Feb 19, 2019
1 parent 7cde443 commit 6aa13d1
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 14 deletions.
16 changes: 13 additions & 3 deletions src/components/checkbox/checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,19 +169,29 @@ 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".
// It's important that MouseEvent is used here versus Event in order to match the native
// behavior and properly trigger ng-submit.
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
143 changes: 133 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,137 @@ 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);
});
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 6aa13d1

Please sign in to comment.