Skip to content
This repository has been archived by the owner on Aug 29, 2023. It is now read-only.

Commit

Permalink
fix(mdInput): Support multiple ng-messages simultaneously.
Browse files Browse the repository at this point in the history
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 fix number input widths in Firefox.

Also update errors demo messages to be more dynamic and show
multiple errors.

Fixes #2648. Fixes #1957.
  • Loading branch information
topherfangio committed Sep 15, 2015
1 parent afc0963 commit 9c64578
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 22 deletions.
26 changes: 21 additions & 5 deletions src/components/input/demoErrors/index.html
Expand Up @@ -27,11 +27,27 @@ <h1 class="md-toolbar-tools">

<md-input-container>
<label>Hourly Rate (USD)</label>
<input required type="number" step="any" name="rate" ng-model="project.rate" min="800" max="4999">
<div ng-messages="projectForm.rate.$error">
<div ng-message="required">You've got to charge something! You can't just <b>give away</b> a Missile Defense System.</div>
<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>
<div ng-message="max">$5,000 an hour? That's a little ridiculous. I doubt event Bill Clinton could afford that.</div>
<input required type="number" step="any" name="rate" ng-model="project.rate" min="800"
max="4999" ng-pattern="/1234/">
<div ng-messages="projectForm.rate.$error" multiple>
<div ng-message="required">
You've got to charge something! You can't just <b>give away</b> a Missile Defense
System.
</div>

<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>

<div ng-message="pattern">
You should charge exactly $1,234.
</div>

<div ng-message="max">
{{projectForm.rate.$viewValue | currency:"$":0}} an hour? That's a little ridiculous. I
doubt event Bill Clinton could afford that.
</div>
</div>
</md-input-container>
</form>
Expand Down
36 changes: 32 additions & 4 deletions src/components/input/input.js
Expand Up @@ -11,7 +11,8 @@ angular.module('material.components.input', [
.directive('input', inputTextareaDirective)
.directive('textarea', inputTextareaDirective)
.directive('mdMaxlength', mdMaxlengthDirective)
.directive('placeholder', placeholderDirective);
.directive('placeholder', placeholderDirective)
.directive('ngMessages', ngMessagesDirective);

/**
* @ngdoc directive
Expand Down Expand Up @@ -69,6 +70,9 @@ function mdInputContainerDirective($mdTheming, $parse) {
self.setHasValue = function(hasValue) {
$element.toggleClass('md-input-has-value', !!hasValue);
};
self.setHasMessages = function(hasMessages) {
$element.toggleClass('md-input-has-messages', !!hasMessages);
};
self.setInvalid = function(isInvalid) {
$element.toggleClass('md-input-invalid', !!isInvalid);
};
Expand Down Expand Up @@ -341,11 +345,12 @@ function mdMaxlengthDirective($animate) {
var ngModelCtrl = ctrls[0];
var containerCtrl = ctrls[1];
var charCountEl = angular.element('<div class="md-char-counter">');
var input = angular.element(containerCtrl.element[0].querySelector('[md-maxlength]'));

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

ngModelCtrl.$formatters.push(renderCharCount);
ngModelCtrl.$viewChangeListeners.push(renderCharCount);
Expand All @@ -357,8 +362,7 @@ function mdMaxlengthDirective($animate) {
maxlength = value;
if (angular.isNumber(value) && value > 0) {
if (!charCountEl.parent().length) {
$animate.enter(charCountEl, containerCtrl.element,
angular.element(containerCtrl.element[0].lastElementChild));
$animate.enter(charCountEl, containerCtrl.element, input);
}
renderCharCount();
} else {
Expand Down Expand Up @@ -408,3 +412,27 @@ function placeholderDirective($log) {

}
}

function ngMessagesDirective() {
return {
restrict: 'EA',
link: postLink,

// This is optional because we don't want target *all* ngMessage instances, just those inside of
// mdInputContainer.
require: '^^?mdInputContainer'
};

function postLink(scope, element, attr, inputContainer) {
// If we are not a child of an input container, don't do anything
if (!inputContainer) return;

// Tell our parent input container we have messages so we can set the proper classes
inputContainer.setHasMessages(true);

// When destroyed, inform our input container
scope.$on('$destroy', function() {
inputContainer.setHasMessages(false);
});
}
}
54 changes: 41 additions & 13 deletions src/components/input/input.scss
Expand Up @@ -28,6 +28,12 @@ md-input-container {
padding: $input-container-padding;
padding-bottom: $input-container-padding + $input-error-height;

// When we have ng-messages, remove the input error height from our bottom padding, since the
// ng-messages wrapper has a min-height of 1 error (so we don't adjust height as often; see below)
&.md-input-has-messages {
padding-bottom: $input-container-padding;
}

> md-icon {
position: absolute;
top: 5px;
Expand Down Expand Up @@ -143,6 +149,9 @@ md-input-container {
-ms-flex-preferred-size: $input-line-height; //IE fix
border-radius: 0;

// Fix number inputs in Firefox to be full-width
width: auto;

&:focus {
outline: none;
}
Expand All @@ -156,45 +165,64 @@ md-input-container {
}
}

.md-char-counter {
position: absolute;
right: 0;
order: 3;
}

ng-messages, data-ng-messages, x-ng-messages,
[ng-messages], [data-ng-messages], [x-ng-messages] {
order: 3;
position: relative;
order: 4;
min-height: $input-error-height;
}
ng-message, data-ng-message, x-ng-message,
[ng-message], [data-ng-message], [x-ng-message],
.md-char-counter {
$input-error-line-height: $input-error-font-size + 2px;
//-webkit-font-smoothing: antialiased;
position: absolute;
font-size: $input-error-font-size;
line-height: $input-error-height;
line-height: $input-error-line-height;
overflow: hidden;

// Add some top padding which is equal to half the difference between the expected height
// and the actual height
$error-padding-top: ($input-error-height - $input-error-line-height) / 2;
padding-top: $error-padding-top;

&:not(.md-char-counter) {
padding-right: rem(3);
padding-right: rem(5);
}

&.ng-enter {
transition: $swift-ease-out;
transition-delay: 0.2s;
transition: $swift-ease-in;

// Delay the enter transition so it happens after the leave
transition-delay: $swift-ease-in-duration / 1.5;

// Since we're delaying the transition, we speed up the duration a little bit to compensate
transition-duration: $swift-ease-in-duration / 1.5;
}
&.ng-leave {
transition: $swift-ease-in;
transition: $swift-ease-out;

// Speed up the duration (see enter comment above)
transition-duration: $swift-ease-out-duration / 1.5;
}
&.ng-enter,
&.ng-leave.ng-leave-active {
// Move the error upwards off the screen and fade it out
margin-top: -$input-error-line-height - $error-padding-top;
opacity: 0;
transform: translate3d(0, -20%, 0);
}
&.ng-leave,
&.ng-enter.ng-enter-active {
// Move the error down into position and fade it in
margin-top: 0;
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
.md-char-counter {
bottom: $input-container-padding;
right: $input-container-padding;
}

&.md-input-focused,
&.md-input-has-value {
Expand Down

0 comments on commit 9c64578

Please sign in to comment.