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

Commit 4006f53

Browse files
Frank3Kmmalerba
authored andcommitted
feat(checkbox/switch): add support for animating ng-messages (#9473)
Add support for animating ng-messages on md-checkbox and ng-switch, in line with input, md-select and md-datepicker. Fixes #9430.
1 parent 455c679 commit 4006f53

File tree

5 files changed

+116
-10
lines changed

5 files changed

+116
-10
lines changed

src/components/checkbox/checkbox.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ function MdCheckboxDirective(inputDirective, $mdAria, $mdConstant, $mdTheming, $
6060
return {
6161
restrict: 'E',
6262
transclude: true,
63-
require: '?ngModel',
63+
require: ['^?mdInputContainer', '?ngModel', '?^form'],
6464
priority: $mdConstant.BEFORE_NG_ARIA,
6565
template:
6666
'<div class="md-container" md-ink-ripple md-ink-ripple-checkbox>' +
@@ -92,9 +92,22 @@ function MdCheckboxDirective(inputDirective, $mdAria, $mdConstant, $mdTheming, $
9292
post: postLink
9393
};
9494

95-
function postLink(scope, element, attr, ngModelCtrl) {
95+
function postLink(scope, element, attr, ctrls) {
9696
var isIndeterminate;
97-
ngModelCtrl = ngModelCtrl || $mdUtil.fakeNgModel();
97+
var containerCtrl = ctrls[0];
98+
var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
99+
var formCtrl = ctrls[2];
100+
101+
if (containerCtrl) {
102+
var isErrorGetter = containerCtrl.isErrorGetter || function() {
103+
return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (formCtrl && formCtrl.$submitted));
104+
};
105+
106+
containerCtrl.input = element;
107+
108+
scope.$watch(isErrorGetter, containerCtrl.setInvalid);
109+
}
110+
98111
$mdTheming(element);
99112

100113
// Redirect focus events to the root element, because IE11 is always focusing the container element instead

src/components/input/demoErrors/index.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,27 @@
6969
</div>
7070
</md-input-container>
7171

72+
<md-input-container class="md-block">
73+
<md-checkbox name="tos" ng-model="project.tos" required>
74+
I accept the terms of service.
75+
</md-checkbox>
76+
<div ng-messages="projectForm.tos.$error" multiple md-auto-hide="false">
77+
<div ng-message="required">
78+
You must accept the terms of service before you can proceed.
79+
</div>
80+
</div>
81+
</md-input-container>
82+
83+
<md-input-container class="md-block">
84+
<md-switch class="md-primary" name="special" ng-model="project.special" required>
85+
Enable special options.
86+
</md-switch>
87+
<div ng-messages="projectForm.special.$error" multiple>
88+
<div ng-message="required">
89+
You must enable all special options before you can proceed.
90+
</div>
91+
</div>
92+
</md-input-container>
7293
<div>
7394
<md-button type="submit">Submit</md-button>
7495
</div>

src/components/input/demoErrors/script.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ angular.module('inputErrorsApp', ['ngMaterial', 'ngMessages'])
33
.controller('AppCtrl', function($scope) {
44
$scope.project = {
55
description: 'Nuclear Missile Defense System',
6-
rate: 500
6+
rate: 500,
7+
special: true
78
};
89
});

src/components/input/input-animations.spec.js

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ describe('md-input-container animations', function() {
44
cssTransitionsDisabled = false, lastAnimateCall;
55

66
// Load our modules
7-
beforeEach(module('ngAnimate', 'ngMessages', 'material.components.input'));
7+
beforeEach(module('ngAnimate', 'ngMessages', 'material.components.input', 'material.components.checkbox'));
88

99
// Run pre-test setup
1010
beforeEach(decorateAnimateCss);
@@ -14,7 +14,7 @@ describe('md-input-container animations', function() {
1414
// Run after-test teardown
1515
afterEach(teardown);
1616

17-
it('set the proper styles when showing messages', function() {
17+
it('set the proper styles when showing messages on an input', function() {
1818
compile(
1919
'<form name="testForm">' +
2020
' <md-input-container>' +
@@ -185,6 +185,75 @@ describe('md-input-container animations', function() {
185185
});
186186
});
187187

188+
it('set the proper styles when showing messages on an md-checkbox', function() {
189+
compile(
190+
'<form name="testForm">' +
191+
' <md-input-container>' +
192+
' <md-checkbox name="cb" ng-model="foo" required>Test</md-checkbox>' +
193+
' <div class="errors" ng-messages="testForm.cb.$error">' +
194+
' <div ng-message="required">required</div>' +
195+
' </div>' +
196+
' </md-input-container>' +
197+
'</form>'
198+
);
199+
200+
var container = el.find('md-input-container'),
201+
checkbox = el.find('md-checkbox'),
202+
doneSpy = jasmine.createSpy('done');
203+
204+
// Mimic the real validations/animations that fire
205+
206+
/*
207+
* 1. Uncheck the checkbox but don't blur (so it's not invalid yet)
208+
*
209+
* Expect nothing to happen ($animateCss called with no options)
210+
*/
211+
212+
setFoo(true);
213+
checkbox.triggerHandler('click');
214+
messageAnimation.enter(getError(), doneSpy);
215+
flush();
216+
217+
expectError(getError(), 'required');
218+
expect(doneSpy).toHaveBeenCalled();
219+
expect(container).not.toHaveClass('md-input-invalid');
220+
expect(lastAnimateCall).toEqual({element: getError(), options: {}});
221+
222+
/*
223+
* 2. Blur the checkbox, which adds the md-input-invalid class
224+
*
225+
* Expect to animate in the required message
226+
*/
227+
228+
doneSpy.calls.reset();
229+
checkbox.triggerHandler('blur');
230+
invalidAnimation.addClass(container, 'md-input-invalid', doneSpy);
231+
flush();
232+
233+
expectError(getError(), 'required');
234+
expect(doneSpy).toHaveBeenCalled();
235+
expect(container).toHaveClass('md-input-invalid');
236+
expect(lastAnimateCall.element).toEqual(getError());
237+
expect(lastAnimateCall.options.event).toEqual('enter');
238+
expect(lastAnimateCall.options.to).toEqual({"opacity": 1, "margin-top": "0"});
239+
240+
/*
241+
* 3. Clear the field
242+
*
243+
* Expect to animate away required message
244+
*/
245+
246+
doneSpy.calls.reset();
247+
messageAnimation.leave(getError(), doneSpy);
248+
flush();
249+
250+
expect(doneSpy).toHaveBeenCalled();
251+
expect(lastAnimateCall.element).toEqual(getError());
252+
expect(lastAnimateCall.options.event).toEqual('leave');
253+
expect(parseInt(lastAnimateCall.options.to["margin-top"])).toBeLessThan(0);
254+
255+
});
256+
188257
/*
189258
* Test Helper Functions
190259
*/

src/components/switch/switch.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ function MdSwitch(mdCheckboxDirective, $mdUtil, $mdConstant, $parse, $$rAF, $mdG
6262
'</div>'+
6363
'</div>' +
6464
'<div ng-transclude class="md-label"></div>',
65-
require: '?ngModel',
65+
require: ['^?mdInputContainer', '?ngModel', '?^form'],
6666
compile: mdSwitchCompile
6767
};
6868

@@ -71,8 +71,10 @@ function MdSwitch(mdCheckboxDirective, $mdUtil, $mdConstant, $parse, $$rAF, $mdG
7171
// No transition on initial load.
7272
element.addClass('md-dragging');
7373

74-
return function (scope, element, attr, ngModel) {
75-
ngModel = ngModel || $mdUtil.fakeNgModel();
74+
return function (scope, element, attr, ctrls) {
75+
var containerCtrl = ctrls[0];
76+
var ngModel = ctrls[1] || $mdUtil.fakeNgModel();
77+
var formCtrl = ctrls[2];
7678

7779
var disabledGetter = null;
7880
if (attr.disabled != null) {
@@ -90,7 +92,7 @@ function MdSwitch(mdCheckboxDirective, $mdUtil, $mdConstant, $parse, $$rAF, $mdG
9092
element.removeClass('md-dragging');
9193
});
9294

93-
checkboxLink(scope, element, attr, ngModel);
95+
checkboxLink(scope, element, attr, ctrls);
9496

9597
if (disabledGetter) {
9698
scope.$watch(disabledGetter, function(isDisabled) {

0 commit comments

Comments
 (0)