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

Commit

Permalink
feat(input): add <md-input-container> parent for inputs/textareas, de…
Browse files Browse the repository at this point in the history
…precate md-text-float

Closes #993. Closes #372. Closes #476. Closes #523. Closes #543. Closes #547.
Closes #553. Closes #562. Closes #575. Closes #605. Closes #606. Closes #612.
Closes #654. Closes #687. Closes #744. Closes #839. Closes #847. Closes #931.
Closes #993.

md-textfloat has been deprecated due to flaws
(explanation in [#547](#547)).

Now, to create an input you use the native `<input>` and `<textarea>`
elements, with a `<md-input-container>` parent around each
`<input>` or `<textarea>`.

Change your code from this:

```html
<md-textfloat label="First Name" ng-model="firstName"></md-textfloat>
```

To this:

```html
<md-input-container>
  <label>First Name</label>
  <input ng-model="firstName">
</md-input-container>
````
  • Loading branch information
ajoslin committed Jan 8, 2015
1 parent 03c7592 commit 60fcd6f
Show file tree
Hide file tree
Showing 15 changed files with 439 additions and 188 deletions.
58 changes: 58 additions & 0 deletions src/components/input/demoBasicUsage/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<div ng-app="inputBasicDemo" ng-controller="DemoCtrl" layout="column">

<md-content md-theme="docs-dark" class="md-padding" layout="row" layout-sm="column">
<md-input-container>
<label>Title</label>
<input ng-model="user.title">
</md-input-container>
<md-input-container>
<label>Email</label>
<input ng-model="user.email" type="email">
</md-input-container>
</md-content>

<md-content class="md-padding">

<md-input-container flex>
<label>Company (Disabled)</label>
<input ng-model="user.company" disabled>
</md-input-container>

<div layout layout-padding layout-sm="column">
<md-input-container flex>
<label>First Name</label>
<input ng-model="user.firstName">
</md-input-container>
<md-input-container flex>
<label>Last Name</label>
<input ng-model="user.lastName">
</md-input-container>
</div>

<md-input-container flex>
<label>Address</label>
<input ng-model="user.address">
</md-input-container>

<div layout layout-padding layout-sm="column">
<md-input-container flex>
<label>City</label>
<input ng-model="user.city">
</md-input-container>
<md-input-container flex>
<label>State</label>
<input ng-model="user.state">
</md-input-container>
<md-input-container flex>
<label>Postal Code</label>
<input ng-model="user.postalCode">
</md-input-container>
</div>

<md-input-container flex>
<label>Country</label>
<input ng-model="user.country">
</md-input-container>

</md-content>
</div>
16 changes: 16 additions & 0 deletions src/components/input/demoBasicUsage/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
angular.module('inputBasicDemo', ['ngMaterial'])

.controller('DemoCtrl', function($scope) {
$scope.user = {
title: 'Developer',
email: 'ipsum@lorem.com',
firstName: '',
lastName: '' ,
company: 'Google' ,
address: '1600 Amphitheatre Pkwy' ,
city: 'Mountain View' ,
state: 'CA' ,
country: 'USA' ,
postalCode : '94043'
};
});
42 changes: 42 additions & 0 deletions src/components/input/input-theme.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
md-input-container.md-THEME_NAME-theme {
.md-input {
@include input-placeholder-color('{{foreground-3}}');
color: '{{foreground-1}}';
border-color: '{{foreground-4}}';
text-shadow: '{{foreground-shadow}}';
}

label {
text-shadow: '{{foreground-shadow}}';
color: '{{foreground-3}}';
}

&.md-input-focused {
.md-input {
border-color: '{{primary-500}}';
}
label {
color: '{{primary-500}}';
}
&.md-accent {
.md-input {
border-color: '{{accent-500}}';
}
label {
color: '{{accent-500}}';
}
}
}

&.md-input-has-value:not(.md-input-focused) {
label {
color: '{{foreground-2}}';
}
}

.md-input[disabled] {
border-bottom-color: transparent;
color: '{{foreground-3}}';
background-image: linear-gradient(to right, '{{foreground-4}}' 0%, '{{foreground-4}}' 33%, transparent 0%);
}
}
154 changes: 154 additions & 0 deletions src/components/input/input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
(function() {

/**
* @ngdoc module
* @name material.components.input
*/

angular.module('material.components.input', [
'material.core'
])
.directive('mdInputContainer', mdInputContainerDirective)
.directive('label', labelDirective)
.directive('input', inputDirective)
.directive('textarea', inputDirective);

/**
* @ngdoc directive
* @name mdInputContainer
* @module material.components.input
*
* @restrict E
*
* @description
* `<md-input-container>` is the parent of any input or textarea element.
*
* Input and textarea elements will not behave properly unless the md-input-container
* parent is provided.
*
* @usage
* <hljs lang="html">
*
* <md-input-container>
* <label>Username</label>
* <input type="text" ng-model="user.name">
* </md-input-container>
*
* <md-input-container>
* <label>Description</label>
* <textarea ng-model="user.description"></textarea>
* </md-input-container>
*
* </hljs>
*/
function mdInputContainerDirective($mdTheming) {
return {
restrict: 'E',
link: postLink,
controller: ContainerCtrl
};

function postLink(scope, element, attr) {
$mdTheming(element);
}
function ContainerCtrl($scope, $element, $mdUtil) {
var self = this;

self.setFocused = function(isFocused) {
$element.toggleClass('md-input-focused', !!isFocused);
};
self.setHasValue = function(hasValue) {
$element.toggleClass('md-input-has-value', !!hasValue);
};

$scope.$watch(function() {
return self.label && self.input;
}, function(hasLabelAndInput) {
if (hasLabelAndInput && !self.label.attr('for')) {
self.label.attr('for', self.input.attr('id'));
}
});
}
}

function labelDirective() {
return {
restrict: 'E',
require: '^?mdInputContainer',
link: function(scope, element, attr, containerCtrl) {
if (!containerCtrl) return;

containerCtrl.label = element;
scope.$on('$destroy', function() {
containerCtrl.label = null;
});
}
};
}

function inputDirective($mdUtil) {
return {
restrict: 'E',
require: ['^?mdInputContainer', '?ngModel'],
compile: compile,

This comment has been minimized.

Copy link
@gkalpak

gkalpak Jan 8, 2015

Member

Is there any benefit in using a compile function ?

};

function compile(element) {
element.addClass('md-input');
return postLink;
}
function postLink(scope, element, attr, ctrls) {

var containerCtrl = ctrls[0];
var ngModelCtrl = ctrls[1];

if ( !containerCtrl ) return;

if (containerCtrl.input) {
throw new Error("<md-input-container> can only have *one* <input> or <textarea> child element!");
}
if (!element.attr('id')) {
element.attr('id', 'input_' + $mdUtil.nextUid());
}

containerCtrl.input = element;

// When the input value changes, check if it "has" a value, and
// set the appropriate class on the input group
if (ngModelCtrl) {
ngModelCtrl.$formatters.push(checkHasValue);
ngModelCtrl.$parsers.push(checkHasValue);
} else {
element.on('input', function() {
containerCtrl.setHasValue( (""+element.val()).length > 0 );
});
containerCtrl.setHasValue( (""+element.val()).length > 0 );
}
function checkHasValue(value) {
containerCtrl.setHasValue(!ngModelCtrl.$isEmpty(value));
return value;
}

element
.on('focus', function(e) {
containerCtrl.setFocused(true);
})
.on('blur', function(e) {
containerCtrl.setFocused(false);
});

scope.$on('$destroy', function() {
containerCtrl.setFocused(false);
containerCtrl.setHasValue(false);
containerCtrl.input = null;
});

function isNotEmpty(value) {

This comment has been minimized.

Copy link
@gkalpak

gkalpak Jan 9, 2015

Member

Seems like this function is not used.

value = angular.isUndefined(value) ? element.val() : value;
return (angular.isDefined(value) && (value!==null) &&
(value.toString().trim() !== ""));
}
}
}

})();
88 changes: 88 additions & 0 deletions src/components/input/input.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
$input-container-padding: 2px !default;

$input-label-default-offset: 24px !default;
$input-label-default-scale: 1.0 !default;
$input-label-float-offset: 4px !default;
$input-label-float-scale: 0.75 !default;

$input-border-width-default: 1px !default;
$input-border-width-focused: 2px !default;
$input-line-height: 26px !default;
$input-padding-top: 2px !default;

md-input-container {
display: flex;
position: relative;
flex-direction: column;
padding: $input-container-padding;

textarea,
input[type="text"],
input[type="password"],
input[type="datetime"],
input[type="datetime-local"],
input[type="date"],
input[type="month"],
input[type="time"],
input[type="week"],
input[type="number"],
input[type="email"],
input[type="url"],
input[type="search"],
input[type="tel"],
input[type="color"] {
/* remove default appearance from all input/textarea */
-moz-appearance: none;
-webkit-appearance: none;
}

label {
order: 1;
pointer-events: none;
-webkit-font-smoothing: antialiased;
z-index: 1;
transform: translate3d(0, $input-label-default-offset, 0) scale($input-label-default-scale);
transform-origin: left top;
transition: all $swift-ease-out-timing-function 0.2s;
}

/*
* The .md-input class is added to the input/textarea
*/
.md-input {
flex: 1;
order: 2;
display: block;

background: none;
padding-top: $input-padding-top;
padding-bottom: $input-border-width-focused - $input-border-width-default;
border-width: 0 0 $input-border-width-default 0;
line-height: $input-line-height;

&:focus {
outline: none;
}
}

&.md-input-focused,
&.md-input-has-value {
label {
transform: translate3d(0,$input-label-float-offset,0) scale($input-label-float-scale);
}
}
&.md-input-focused {
.md-input {
padding-bottom: 0px; // Increase border width by 1px, decrease padding by 1
border-width: 0 0 $input-border-width-focused 0;
}
}

.md-input[disabled] {
background-position: 0 bottom;
// This background-size is coordinated with a linear-gradient set in input-theme.scss
// to create a dotted line under the input.
background-size: 3px 1px;
background-repeat: repeat-x;
}
}
Loading

11 comments on commit 60fcd6f

@gustavohenke
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow. This commit solves so much issues that I just can't wait to see it landing in a final release 💯

@marcysutton
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏

@epelc
Copy link
Contributor

@epelc epelc commented on 60fcd6f Jan 8, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ajoslin Great job! This should make forms a lot nicer again.

How will this affect the plans for handling errors/non floating labels will we just be adding classes to the md-input-container now? I think that'd be a lot nicer than having separate elements.

@ajoslin
Copy link
Contributor Author

@ajoslin ajoslin commented on 60fcd6f Jan 8, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Errors will be handled with ng-messages, the normal angular way. We'll just add some css for the ng-message elements.

@oste
Copy link
Contributor

@oste oste commented on 60fcd6f Jan 8, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Awesome.

@petebacondarwin
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@SebiH
Copy link

@SebiH SebiH commented on 60fcd6f Jan 8, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works great so far, thanks! 👍

@yarl
Copy link
Contributor

@yarl yarl commented on 60fcd6f Jan 8, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, but not on IE10:

Screen

To fix this, inside md-input-container .md-input I've changed -ms-flex-preferred-size to 26px and now looks fine. Probably it needs to be checked using IE11.

@ajoslin
Copy link
Contributor Author

@ajoslin ajoslin commented on 60fcd6f Jan 8, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @yarl.

@gkalpak
Copy link
Member

@gkalpak gkalpak commented on 60fcd6f Jan 9, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ajoslin: This looks great !

I have left a couple of inline comments (mostly cleanup stuff).
It would be nice to have better test-coverage (considering this is probably going to be one of the most heavily utilized components). E.g. tests for non-text inputs etc.

@gligoran
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Placeholder mixin won't work as expected in Chrome.

@mixin input-placeholder-color($color) {
  &::-webkit-input-placeholder,
  &::-moz-placeholder, /* Firefox 19+ */
  &:-moz-placeholder, /* Firefox 18- */
  &:-ms-input-placeholder {
    color: $color;
  }
}

This is because Chrome (at this point in time) fails to parse its part. These have to be separated out like so:

@mixin input-placeholder-color($color) {
  &::-webkit-input-placeholder { color: $color; }
  &::-moz-placeholder { color: $color; } /* Firefox 19+ */
  &:-moz-placeholder { color: $color; } /* Firefox 18- */
  &:-ms-input-placeholder { color: $color; }
}

BTW, found this out the hard way.

Please sign in to comment.