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

Commit 3d0b418

Browse files
topherfangioThomasBurleson
authored andcommitted
fix(mdInput): Support multiple ng-messages simultaneously.
Previously, multiple `ng-message`s would render on top of each other. Fix by altering CSS position and altering transition to support multiple messages (i.e. potentially varying height). Also some other small fixes to inputs/errors: * Fix number input widths in Firefox * Update errors demo messages to be more dynamic and show multiple errors * Update SCSS to allow `ng-message-exp` and associated `data-` and `x-` attributes * Add demo using `ng-message-exp` to show new SCSS styles being applied Fixes #2648. Fixes #1957. Fixes #1793. Closes #4647. Closes #4472. Closes #4008. > Should also close PRs #4472 and #4008. Thanks to @bopm and @iksose for the initial PRs!
1 parent 416dc4c commit 3d0b418

File tree

4 files changed

+115
-26
lines changed

4 files changed

+115
-26
lines changed

src/components/input/demoErrors/index.html

+34-5
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,42 @@ <h1 class="md-toolbar-tools">
2525
</div>
2626
</md-input-container>
2727

28+
<md-input-container>
29+
<label>Client Email</label>
30+
<input required type="email" name="clientEmail" ng-model="project.clientEmail"
31+
minlength="10" maxlength="100" ng-pattern="/^.+@.+\..+$/" />
32+
33+
<div ng-messages="projectForm.clientEmail.$error" role="alert">
34+
<div ng-message-exp="['required', 'minlength', 'maxlength', 'pattern']">
35+
Your email must be between 10 and 100 characters long and look like an e-mail address.
36+
</div>
37+
</div>
38+
</md-input-container>
39+
2840
<md-input-container>
2941
<label>Hourly Rate (USD)</label>
30-
<input required type="number" step="any" name="rate" ng-model="project.rate" min="800" max="4999">
31-
<div ng-messages="projectForm.rate.$error">
32-
<div ng-message="required">You've got to charge something! You can't just <b>give away</b> a Missile Defense System.</div>
33-
<div ng-message="min">You should charge at least $800 an hour. This job is a big deal... if you mess up, everyone dies!</div>
34-
<div ng-message="max">$5,000 an hour? That's a little ridiculous. I doubt event Bill Clinton could afford that.</div>
42+
<input required type="number" step="any" name="rate" ng-model="project.rate" min="800"
43+
max="4999" ng-pattern="/^1234$/">
44+
45+
<div ng-messages="projectForm.rate.$error" multiple>
46+
<div ng-message="required">
47+
You've got to charge something! You can't just <b>give away</b> a Missile Defense
48+
System.
49+
</div>
50+
51+
<div ng-message="min">
52+
You should charge at least $800 an hour. This job is a big deal... if you mess up,
53+
everyone dies!
54+
</div>
55+
56+
<div ng-message="pattern">
57+
You should charge exactly $1,234.
58+
</div>
59+
60+
<div ng-message="max">
61+
{{projectForm.rate.$viewValue | currency:"$":0}} an hour? That's a little ridiculous. I
62+
doubt event Bill Clinton could afford that.
63+
</div>
3564
</div>
3665
</md-input-container>
3766
</form>

src/components/input/input-theme.scss

+6-4
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ md-input-container.md-THEME_NAME-theme {
1616
color: '{{foreground-3}}';
1717
}
1818

19-
ng-messages,
20-
[ng-message], [data-ng-message], [x-ng-message] {
21-
color: '{{warn-500}}'
19+
ng-messages, [ng-messages],
20+
ng-message, data-ng-message, x-ng-message,
21+
[ng-message], [data-ng-message], [x-ng-message],
22+
[ng-message-exp], [data-ng-message-exp], [x-ng-message-exp] {
23+
color: '{{warn-500}}';
2224
}
2325

24-
2526
&:not(.md-input-invalid) {
2627
&.md-input-has-value {
2728
label {
@@ -65,6 +66,7 @@ md-input-container.md-THEME_NAME-theme {
6566
}
6667
ng-message, data-ng-message, x-ng-message,
6768
[ng-message], [data-ng-message], [x-ng-message],
69+
[ng-message-exp], [data-ng-message-exp], [x-ng-message-exp],
6870
.md-char-counter {
6971
color: '{{warn-500}}';
7072
}

src/components/input/input.js

+32-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ angular.module('material.components.input', [
1111
.directive('input', inputTextareaDirective)
1212
.directive('textarea', inputTextareaDirective)
1313
.directive('mdMaxlength', mdMaxlengthDirective)
14-
.directive('placeholder', placeholderDirective);
14+
.directive('placeholder', placeholderDirective)
15+
.directive('ngMessages', ngMessagesDirective);
1516

1617
/**
1718
* @ngdoc directive
@@ -69,6 +70,9 @@ function mdInputContainerDirective($mdTheming, $parse) {
6970
self.setHasValue = function(hasValue) {
7071
$element.toggleClass('md-input-has-value', !!hasValue);
7172
};
73+
self.setHasMessages = function(hasMessages) {
74+
$element.toggleClass('md-input-has-messages', !!hasMessages);
75+
};
7276
self.setInvalid = function(isInvalid) {
7377
$element.toggleClass('md-input-invalid', !!isInvalid);
7478
};
@@ -341,11 +345,12 @@ function mdMaxlengthDirective($animate) {
341345
var ngModelCtrl = ctrls[0];
342346
var containerCtrl = ctrls[1];
343347
var charCountEl = angular.element('<div class="md-char-counter">');
348+
var input = angular.element(containerCtrl.element[0].querySelector('[md-maxlength]'));
344349

345350
// Stop model from trimming. This makes it so whitespace
346351
// over the maxlength still counts as invalid.
347352
attr.$set('ngTrim', 'false');
348-
containerCtrl.element.append(charCountEl);
353+
input.after(charCountEl);
349354

350355
ngModelCtrl.$formatters.push(renderCharCount);
351356
ngModelCtrl.$viewChangeListeners.push(renderCharCount);
@@ -357,8 +362,7 @@ function mdMaxlengthDirective($animate) {
357362
maxlength = value;
358363
if (angular.isNumber(value) && value > 0) {
359364
if (!charCountEl.parent().length) {
360-
$animate.enter(charCountEl, containerCtrl.element,
361-
angular.element(containerCtrl.element[0].lastElementChild));
365+
$animate.enter(charCountEl, containerCtrl.element, input);
362366
}
363367
renderCharCount();
364368
} else {
@@ -408,3 +412,27 @@ function placeholderDirective($log) {
408412

409413
}
410414
}
415+
416+
function ngMessagesDirective() {
417+
return {
418+
restrict: 'EA',
419+
link: postLink,
420+
421+
// This is optional because we don't want target *all* ngMessage instances, just those inside of
422+
// mdInputContainer.
423+
require: '^^?mdInputContainer'
424+
};
425+
426+
function postLink(scope, element, attr, inputContainer) {
427+
// If we are not a child of an input container, don't do anything
428+
if (!inputContainer) return;
429+
430+
// Tell our parent input container we have messages so we can set the proper classes
431+
inputContainer.setHasMessages(true);
432+
433+
// When destroyed, inform our input container
434+
scope.$on('$destroy', function() {
435+
inputContainer.setHasMessages(false);
436+
});
437+
}
438+
}

src/components/input/input.scss

+43-13
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ md-input-container {
2828
padding: $input-container-padding;
2929
padding-bottom: $input-container-padding + $input-error-height;
3030

31+
// When we have ng-messages, remove the input error height from our bottom padding, since the
32+
// ng-messages wrapper has a min-height of 1 error (so we don't adjust height as often; see below)
33+
&.md-input-has-messages {
34+
padding-bottom: $input-container-padding;
35+
}
36+
3137
> md-icon {
3238
position: absolute;
3339
top: 5px;
@@ -143,6 +149,9 @@ md-input-container {
143149
-ms-flex-preferred-size: $input-line-height; //IE fix
144150
border-radius: 0;
145151

152+
// Fix number inputs in Firefox to be full-width
153+
width: auto;
154+
146155
&:focus {
147156
outline: none;
148157
}
@@ -156,45 +165,66 @@ md-input-container {
156165
}
157166
}
158167

168+
.md-char-counter {
169+
position: absolute;
170+
right: 0;
171+
order: 3;
172+
}
173+
159174
ng-messages, data-ng-messages, x-ng-messages,
160175
[ng-messages], [data-ng-messages], [x-ng-messages] {
161-
order: 3;
162176
position: relative;
177+
order: 4;
178+
min-height: $input-error-height;
163179
}
180+
164181
ng-message, data-ng-message, x-ng-message,
165182
[ng-message], [data-ng-message], [x-ng-message],
183+
[ng-message-exp], [data-ng-message-exp], [x-ng-message-exp],
166184
.md-char-counter {
185+
$input-error-line-height: $input-error-font-size + 2px;
167186
//-webkit-font-smoothing: antialiased;
168-
position: absolute;
169187
font-size: $input-error-font-size;
170-
line-height: $input-error-height;
188+
line-height: $input-error-line-height;
189+
overflow: hidden;
190+
191+
// Add some top padding which is equal to half the difference between the expected height
192+
// and the actual height
193+
$error-padding-top: ($input-error-height - $input-error-line-height) / 2;
194+
padding-top: $error-padding-top;
171195

172196
&:not(.md-char-counter) {
173-
padding-right: rem(3);
197+
padding-right: rem(5);
174198
}
175199

176200
&.ng-enter {
177-
transition: $swift-ease-out;
178-
transition-delay: 0.2s;
201+
transition: $swift-ease-in;
202+
203+
// Delay the enter transition so it happens after the leave
204+
transition-delay: $swift-ease-in-duration / 1.5;
205+
206+
// Since we're delaying the transition, we speed up the duration a little bit to compensate
207+
transition-duration: $swift-ease-in-duration / 1.5;
179208
}
180209
&.ng-leave {
181-
transition: $swift-ease-in;
210+
transition: $swift-ease-out;
211+
212+
// Speed up the duration (see enter comment above)
213+
transition-duration: $swift-ease-out-duration / 1.5;
182214
}
183215
&.ng-enter,
184216
&.ng-leave.ng-leave-active {
217+
// Move the error upwards off the screen and fade it out
218+
margin-top: -$input-error-line-height - $error-padding-top;
185219
opacity: 0;
186-
transform: translate3d(0, -20%, 0);
187220
}
188221
&.ng-leave,
189222
&.ng-enter.ng-enter-active {
223+
// Move the error down into position and fade it in
224+
margin-top: 0;
190225
opacity: 1;
191-
transform: translate3d(0, 0, 0);
192226
}
193227
}
194-
.md-char-counter {
195-
bottom: $input-container-padding;
196-
right: $input-container-padding;
197-
}
198228

199229
&.md-input-focused,
200230
&.md-input-has-value {

0 commit comments

Comments
 (0)