diff --git a/src/components/checkbox/checkbox.js b/src/components/checkbox/checkbox.js index 2be6943d540..21f71141683 100644 --- a/src/components/checkbox/checkbox.js +++ b/src/components/checkbox/checkbox.js @@ -169,8 +169,13 @@ 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: @@ -178,10 +183,15 @@ function MdCheckboxDirective(inputDirective, $mdAria, $mdConstant, $mdTheming, $ 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; } diff --git a/src/components/checkbox/checkbox.spec.js b/src/components/checkbox/checkbox.spec.js index f010fe6d4dc..dfab773d048 100644 --- a/src/components/checkbox/checkbox.spec.js +++ b/src/components/checkbox/checkbox.spec.js @@ -2,7 +2,7 @@ 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')); @@ -10,9 +10,10 @@ describe('mdCheckbox', function() { $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) { @@ -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('
'); - 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('
' + + '
'); + + // 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( + '
' + + '
'); + + // 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( + '
' + + '
'); + + // 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( + '
' + + '
'); + + // 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( + '
' + + '
'); + + // 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( + '
' + + '' + + '
'); + + // 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; }; diff --git a/src/core/util/util.js b/src/core/util/util.js index 70a036d35e6..804ab41b0fc 100644 --- a/src/core/util/util.js +++ b/src/core/util/util.js @@ -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)) {