2  * Angular Material Design
 
   3  * https://github.com/angular/material
 
   7 goog.provide('ngmaterial.components.input');
 
   8 goog.require('ngmaterial.core');
 
  11  * @name material.components.input
 
  13 mdInputContainerDirective['$inject'] = ["$mdTheming", "$parse"];
 
  14 inputTextareaDirective['$inject'] = ["$mdUtil", "$window", "$mdAria", "$timeout", "$mdGesture"];
 
  15 mdMaxlengthDirective['$inject'] = ["$animate", "$mdUtil"];
 
  16 placeholderDirective['$inject'] = ["$compile"];
 
  17 ngMessageDirective['$inject'] = ["$mdUtil"];
 
  18 mdSelectOnFocusDirective['$inject'] = ["$timeout"];
 
  19 mdInputInvalidMessagesAnimation['$inject'] = ["$$AnimateRunner", "$animateCss", "$mdUtil", "$log"];
 
  20 ngMessagesAnimation['$inject'] = ["$$AnimateRunner", "$animateCss", "$mdUtil", "$log"];
 
  21 ngMessageAnimation['$inject'] = ["$$AnimateRunner", "$animateCss", "$mdUtil", "$log"];
 
  22 var inputModule = angular.module('material.components.input', [
 
  25   .directive('mdInputContainer', mdInputContainerDirective)
 
  26   .directive('label', labelDirective)
 
  27   .directive('input', inputTextareaDirective)
 
  28   .directive('textarea', inputTextareaDirective)
 
  29   .directive('mdMaxlength', mdMaxlengthDirective)
 
  30   .directive('placeholder', placeholderDirective)
 
  31   .directive('ngMessages', ngMessagesDirective)
 
  32   .directive('ngMessage', ngMessageDirective)
 
  33   .directive('ngMessageExp', ngMessageDirective)
 
  34   .directive('mdSelectOnFocus', mdSelectOnFocusDirective)
 
  36   .animation('.md-input-invalid', mdInputInvalidMessagesAnimation)
 
  37   .animation('.md-input-messages-animation', ngMessagesAnimation)
 
  38   .animation('.md-input-message-animation', ngMessageAnimation);
 
  40 // If we are running inside of tests; expose some extra services so that we can test them
 
  41 if (window._mdMocksIncluded) {
 
  42   inputModule.service('$$mdInput', function() {
 
  44       // special accessor to internals... useful for testing
 
  46         show        : showInputMessages,
 
  47         hide        : hideInputMessages,
 
  48         getElement  : getMessagesElement
 
  53   // Register a service for each animation so that we can easily inject them into unit tests
 
  54   .service('mdInputInvalidAnimation', mdInputInvalidMessagesAnimation)
 
  55   .service('mdInputMessagesAnimation', ngMessagesAnimation)
 
  56   .service('mdInputMessageAnimation', ngMessageAnimation);
 
  61  * @name mdInputContainer
 
  62  * @module material.components.input
 
  67  * `<md-input-container>` is the parent of any input or textarea element.
 
  69  * Input and textarea elements will not behave properly unless the md-input-container
 
  72  * A single `<md-input-container>` should contain only one `<input>` element, otherwise it will throw an error.
 
  74  * <b>Exception:</b> Hidden inputs (`<input type="hidden" />`) are ignored and will not throw an error, so
 
  75  * you may combine these with other inputs.
 
  77  * <b>Note:</b> When using `ngMessages` with your input element, make sure the message and container elements
 
  78  * are *block* elements, otherwise animations applied to the messages will not look as intended. Either use a `div` and
 
  79  * apply the `ng-message` and `ng-messages` classes respectively, or use the `md-block` class on your element.
 
  81  * @param md-is-error {expression=} When the given expression evaluates to true, the input container
 
  82  *   will go into error state. Defaults to erroring if the input has been touched and is invalid.
 
  83  * @param md-no-float {boolean=} When present, `placeholder` attributes on the input will not be converted to floating
 
  89  * <md-input-container>
 
  90  *   <label>Username</label>
 
  91  *   <input type="text" ng-model="user.name">
 
  92  * </md-input-container>
 
  94  * <md-input-container>
 
  95  *   <label>Description</label>
 
  96  *   <textarea ng-model="user.description"></textarea>
 
  97  * </md-input-container>
 
 101  * <h3>When disabling floating labels</h3>
 
 104  * <md-input-container md-no-float>
 
 105  *   <input type="text" placeholder="Non-Floating Label">
 
 106  * </md-input-container>
 
 110 function mdInputContainerDirective($mdTheming, $parse) {
 
 112   ContainerCtrl['$inject'] = ["$scope", "$element", "$attrs", "$animate"];
 
 113   var INPUT_TAGS = ['INPUT', 'TEXTAREA', 'SELECT', 'MD-SELECT'];
 
 115   var LEFT_SELECTORS = INPUT_TAGS.reduce(function(selectors, isel) {
 
 116     return selectors.concat(['md-icon ~ ' + isel, '.md-icon ~ ' + isel]);
 
 119   var RIGHT_SELECTORS = INPUT_TAGS.reduce(function(selectors, isel) {
 
 120     return selectors.concat([isel + ' ~ md-icon', isel + ' ~ .md-icon']);
 
 126     controller: ContainerCtrl
 
 129   function compile(tElement) {
 
 130     // Check for both a left & right icon
 
 131     var leftIcon = tElement[0].querySelector(LEFT_SELECTORS);
 
 132     var rightIcon = tElement[0].querySelector(RIGHT_SELECTORS);
 
 134     if (leftIcon) { tElement.addClass('md-icon-left'); }
 
 135     if (rightIcon) { tElement.addClass('md-icon-right'); }
 
 137     return function postLink(scope, element) {
 
 142   function ContainerCtrl($scope, $element, $attrs, $animate) {
 
 145     self.isErrorGetter = $attrs.mdIsError && $parse($attrs.mdIsError);
 
 147     self.delegateClick = function() {
 
 150     self.element = $element;
 
 151     self.setFocused = function(isFocused) {
 
 152       $element.toggleClass('md-input-focused', !!isFocused);
 
 154     self.setHasValue = function(hasValue) {
 
 155       $element.toggleClass('md-input-has-value', !!hasValue);
 
 157     self.setHasPlaceholder = function(hasPlaceholder) {
 
 158       $element.toggleClass('md-input-has-placeholder', !!hasPlaceholder);
 
 160     self.setInvalid = function(isInvalid) {
 
 162         $animate.addClass($element, 'md-input-invalid');
 
 164         $animate.removeClass($element, 'md-input-invalid');
 
 167     $scope.$watch(function() {
 
 168       return self.label && self.input;
 
 169     }, function(hasLabelAndInput) {
 
 170       if (hasLabelAndInput && !self.label.attr('for')) {
 
 171         self.label.attr('for', self.input.attr('id'));
 
 177 function labelDirective() {
 
 180     require: '^?mdInputContainer',
 
 181     link: function(scope, element, attr, containerCtrl) {
 
 182       if (!containerCtrl || attr.mdNoFloat || element.hasClass('md-container-ignore')) return;
 
 184       containerCtrl.label = element;
 
 185       scope.$on('$destroy', function() {
 
 186         containerCtrl.label = null;
 
 196  * @module material.components.input
 
 199  * You can use any `<input>` or `<textarea>` element as a child of an `<md-input-container>`. This
 
 200  * allows you to build complex forms for data entry.
 
 202  * When the input is required and uses a floating label, then the label will automatically contain
 
 203  * an asterisk (`*`).<br/>
 
 204  * This behavior can be disabled by using the `md-no-asterisk` attribute.
 
 206  * @param {number=} md-maxlength The maximum number of characters allowed in this input. If this is
 
 207  *   specified, a character counter will be shown underneath the input.<br/><br/>
 
 208  *   The purpose of **`md-maxlength`** is exactly to show the max length counter text. If you don't
 
 209  *   want the counter text and only need "plain" validation, you can use the "simple" `ng-maxlength`
 
 210  *   or maxlength attributes.<br/><br/>
 
 211  *   **Note:** Only valid for text/string inputs (not numeric).
 
 213  * @param {string=} aria-label Aria-label is required when no label is present.  A warning message
 
 214  *   will be logged in the console if not present.
 
 215  * @param {string=} placeholder An alternative approach to using aria-label when the label is not
 
 216  *   PRESENT. The placeholder text is copied to the aria-label attribute.
 
 217  * @param md-no-autogrow {boolean=} When present, textareas will not grow automatically.
 
 218  * @param md-no-asterisk {boolean=} When present, an asterisk will not be appended to the inputs floating label
 
 219  * @param md-no-resize {boolean=} Disables the textarea resize handle.
 
 220  * @param {number=} max-rows The maximum amount of rows for a textarea.
 
 221  * @param md-detect-hidden {boolean=} When present, textareas will be sized properly when they are
 
 222  *   revealed after being hidden. This is off by default for performance reasons because it
 
 223  *   guarantees a reflow every digest cycle.
 
 227  * <md-input-container>
 
 228  *   <label>Color</label>
 
 229  *   <input type="text" ng-model="color" required md-maxlength="10">
 
 230  * </md-input-container>
 
 233  * <h3>With Errors</h3>
 
 235  * `md-input-container` also supports errors using the standard `ng-messages` directives and
 
 236  * animates the messages when they become visible using from the `ngEnter`/`ngLeave` events or
 
 237  * the `ngShow`/`ngHide` events.
 
 239  * By default, the messages will be hidden until the input is in an error state. This is based off
 
 240  * of the `md-is-error` expression of the `md-input-container`. This gives the user a chance to
 
 241  * fill out the form before the errors become visible.
 
 244  * <form name="colorForm">
 
 245  *   <md-input-container>
 
 246  *     <label>Favorite Color</label>
 
 247  *     <input name="favoriteColor" ng-model="favoriteColor" required>
 
 248  *     <div ng-messages="colorForm.favoriteColor.$error">
 
 249  *       <div ng-message="required">This is required!</div>
 
 251  *   </md-input-container>
 
 255  * We automatically disable this auto-hiding functionality if you provide any of the following
 
 256  * visibility directives on the `ng-messages` container:
 
 259  *  - `ng-show`/`ng-hide`
 
 260  *  - `ng-switch-when`/`ng-switch-default`
 
 262  * You can also disable this functionality manually by adding the `md-auto-hide="false"` expression
 
 263  * to the `ng-messages` container. This may be helpful if you always want to see the error messages
 
 264  * or if you are building your own visibilty directive.
 
 266  * _<b>Note:</b> The `md-auto-hide` attribute is a static string that is  only checked upon
 
 267  * initialization of the `ng-messages` directive to see if it equals the string `false`._
 
 270  * <form name="userForm">
 
 271  *   <md-input-container>
 
 272  *     <label>Last Name</label>
 
 273  *     <input name="lastName" ng-model="lastName" required md-maxlength="10" minlength="4">
 
 274  *     <div ng-messages="userForm.lastName.$error" ng-show="userForm.lastName.$dirty">
 
 275  *       <div ng-message="required">This is required!</div>
 
 276  *       <div ng-message="md-maxlength">That's too long!</div>
 
 277  *       <div ng-message="minlength">That's too short!</div>
 
 279  *   </md-input-container>
 
 280  *   <md-input-container>
 
 281  *     <label>Biography</label>
 
 282  *     <textarea name="bio" ng-model="biography" required md-maxlength="150"></textarea>
 
 283  *     <div ng-messages="userForm.bio.$error" ng-show="userForm.bio.$dirty">
 
 284  *       <div ng-message="required">This is required!</div>
 
 285  *       <div ng-message="md-maxlength">That's too long!</div>
 
 287  *   </md-input-container>
 
 288  *   <md-input-container>
 
 289  *     <input aria-label='title' ng-model='title'>
 
 290  *   </md-input-container>
 
 291  *   <md-input-container>
 
 292  *     <input placeholder='title' ng-model='title'>
 
 293  *   </md-input-container>
 
 299  * - Requires [ngMessages](https://docs.angularjs.org/api/ngMessages).
 
 300  * - Behaves like the [AngularJS input directive](https://docs.angularjs.org/api/ng/directive/input).
 
 302  * The `md-input` and `md-input-container` directives use very specific positioning to achieve the
 
 303  * error animation effects. Therefore, it is *not* advised to use the Layout system inside of the
 
 304  * `<md-input-container>` tags. Instead, use relative or absolute positioning.
 
 307  * <h3>Textarea directive</h3>
 
 308  * The `textarea` element within a `md-input-container` has the following specific behavior:
 
 309  * - By default the `textarea` grows as the user types. This can be disabled via the `md-no-autogrow`
 
 311  * - If a `textarea` has the `rows` attribute, it will treat the `rows` as the minimum height and will
 
 312  * continue growing as the user types. For example a textarea with `rows="3"` will be 3 lines of text
 
 313  * high initially. If no rows are specified, the directive defaults to 1.
 
 314  * - The textarea's height gets set on initialization, as well as while the user is typing. In certain situations
 
 315  * (e.g. while animating) the directive might have been initialized, before the element got it's final height. In
 
 316  * those cases, you can trigger a resize manually by broadcasting a `md-resize-textarea` event on the scope.
 
 317  * - If you wan't a `textarea` to stop growing at a certain point, you can specify the `max-rows` attribute.
 
 318  * - The textarea's bottom border acts as a handle which users can drag, in order to resize the element vertically.
 
 319  * Once the user has resized a `textarea`, the autogrowing functionality becomes disabled. If you don't want a
 
 320  * `textarea` to be resizeable by the user, you can add the `md-no-resize` attribute.
 
 323 function inputTextareaDirective($mdUtil, $window, $mdAria, $timeout, $mdGesture) {
 
 326     require: ['^?mdInputContainer', '?ngModel', '?^form'],
 
 330   function postLink(scope, element, attr, ctrls) {
 
 332     var containerCtrl = ctrls[0];
 
 333     var hasNgModel = !!ctrls[1];
 
 334     var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
 
 335     var parentForm = ctrls[2];
 
 336     var isReadonly = angular.isDefined(attr.readonly);
 
 337     var mdNoAsterisk = $mdUtil.parseAttributeBoolean(attr.mdNoAsterisk);
 
 338     var tagName = element[0].tagName.toLowerCase();
 
 341     if (!containerCtrl) return;
 
 342     if (attr.type === 'hidden') {
 
 343       element.attr('aria-hidden', 'true');
 
 345     } else if (containerCtrl.input) {
 
 346       if (containerCtrl.input[0].contains(element[0])) {
 
 349         throw new Error("<md-input-container> can only have *one* <input>, <textarea> or <md-select> child element!");
 
 352     containerCtrl.input = element;
 
 354     setupAttributeWatchers();
 
 356     // Add an error spacer div after our input to provide space for the char counter and any ng-messages
 
 357     var errorsSpacer = angular.element('<div class="md-errors-spacer">');
 
 358     element.after(errorsSpacer);
 
 360     if (!containerCtrl.label) {
 
 361       $mdAria.expect(element, 'aria-label', attr.placeholder);
 
 364     element.addClass('md-input');
 
 365     if (!element.attr('id')) {
 
 366       element.attr('id', 'input_' + $mdUtil.nextUid());
 
 369     // This works around a Webkit issue where number inputs, placed in a flexbox, that have
 
 370     // a `min` and `max` will collapse to about 1/3 of their proper width. Please check #7349
 
 371     // for more info. Also note that we don't override the `step` if the user has specified it,
 
 372     // in order to prevent some unexpected behaviour.
 
 373     if (tagName === 'input' && attr.type === 'number' && attr.min && attr.max && !attr.step) {
 
 374       element.attr('step', 'any');
 
 375     } else if (tagName === 'textarea') {
 
 379     // If the input doesn't have an ngModel, it may have a static value. For that case,
 
 380     // we have to do one initial check to determine if the container should be in the
 
 381     // "has a value" state.
 
 386     var isErrorGetter = containerCtrl.isErrorGetter || function() {
 
 387       return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (parentForm && parentForm.$submitted));
 
 390     scope.$watch(isErrorGetter, containerCtrl.setInvalid);
 
 392     // When the developer uses the ngValue directive for the input, we have to observe the attribute, because
 
 393     // Angular's ngValue directive is just setting the `value` attribute.
 
 395       attr.$observe('value', inputCheckValue);
 
 398     ngModelCtrl.$parsers.push(ngModelPipelineCheckValue);
 
 399     ngModelCtrl.$formatters.push(ngModelPipelineCheckValue);
 
 401     element.on('input', inputCheckValue);
 
 405         .on('focus', function(ev) {
 
 406           $mdUtil.nextTick(function() {
 
 407             containerCtrl.setFocused(true);
 
 410         .on('blur', function(ev) {
 
 411           $mdUtil.nextTick(function() {
 
 412             containerCtrl.setFocused(false);
 
 418     scope.$on('$destroy', function() {
 
 419       containerCtrl.setFocused(false);
 
 420       containerCtrl.setHasValue(false);
 
 421       containerCtrl.input = null;
 
 424     /** Gets run through ngModel's pipeline and set the `has-value` class on the container. */
 
 425     function ngModelPipelineCheckValue(arg) {
 
 426       containerCtrl.setHasValue(!ngModelCtrl.$isEmpty(arg));
 
 430     function setupAttributeWatchers() {
 
 431       if (containerCtrl.label) {
 
 432         attr.$observe('required', function (value) {
 
 433           // We don't need to parse the required value, it's always a boolean because of angular's
 
 434           // required directive.
 
 435           containerCtrl.label.toggleClass('md-required', value && !mdNoAsterisk);
 
 440     function inputCheckValue() {
 
 441       // An input's value counts if its length > 0,
 
 442       // or if the input's validity state says it has bad input (eg string in a number input)
 
 443       containerCtrl.setHasValue(element.val().length > 0 || (element[0].validity || {}).badInput);
 
 446     function setupTextarea() {
 
 447       var isAutogrowing = !attr.hasOwnProperty('mdNoAutogrow');
 
 449       attachResizeHandle();
 
 451       if (!isAutogrowing) return;
 
 453       // Can't check if height was or not explicity set,
 
 454       // so rows attribute will take precedence if present
 
 455       var minRows = attr.hasOwnProperty('rows') ? parseInt(attr.rows) : NaN;
 
 456       var maxRows = attr.hasOwnProperty('maxRows') ? parseInt(attr.maxRows) : NaN;
 
 457       var scopeResizeListener = scope.$on('md-resize-textarea', growTextarea);
 
 458       var lineHeight = null;
 
 459       var node = element[0];
 
 461       // This timeout is necessary, because the browser needs a little bit
 
 462       // of time to calculate the `clientHeight` and `scrollHeight`.
 
 463       $timeout(function() {
 
 464         $mdUtil.nextTick(growTextarea);
 
 467       // We could leverage ngModel's $parsers here, however it
 
 468       // isn't reliable, because Angular trims the input by default,
 
 469       // which means that growTextarea won't fire when newlines and
 
 471       element.on('input', growTextarea);
 
 473       // We should still use the $formatters, because they fire when
 
 474       // the value was changed from outside the textarea.
 
 476         ngModelCtrl.$formatters.push(formattersListener);
 
 480         element.attr('rows', 1);
 
 483       angular.element($window).on('resize', growTextarea);
 
 484       scope.$on('$destroy', disableAutogrow);
 
 486       function growTextarea() {
 
 487         // temporarily disables element's flex so its height 'runs free'
 
 490           .css('height', 'auto')
 
 491           .addClass('md-no-flex');
 
 493         var height = getHeight();
 
 496           // offsetHeight includes padding which can throw off our value
 
 497           var originalPadding = element[0].style.padding || '';
 
 498           lineHeight = element.css('padding', 0).prop('offsetHeight');
 
 499           element[0].style.padding = originalPadding;
 
 502         if (minRows && lineHeight) {
 
 503           height = Math.max(height, lineHeight * minRows);
 
 506         if (maxRows && lineHeight) {
 
 507           var maxHeight = lineHeight * maxRows;
 
 509           if (maxHeight < height) {
 
 510             element.attr('md-no-autogrow', '');
 
 513             element.removeAttr('md-no-autogrow');
 
 518           element.attr('rows', Math.round(height / lineHeight));
 
 522           .css('height', height + 'px')
 
 523           .removeClass('md-no-flex');
 
 526       function getHeight() {
 
 527         var offsetHeight = node.offsetHeight;
 
 528         var line = node.scrollHeight - offsetHeight;
 
 529         return offsetHeight + Math.max(line, 0);
 
 532       function formattersListener(value) {
 
 533         $mdUtil.nextTick(growTextarea);
 
 537       function disableAutogrow() {
 
 538         if (!isAutogrowing) return;
 
 540         isAutogrowing = false;
 
 541         angular.element($window).off('resize', growTextarea);
 
 542         scopeResizeListener && scopeResizeListener();
 
 544           .attr('md-no-autogrow', '')
 
 545           .off('input', growTextarea);
 
 548           var listenerIndex = ngModelCtrl.$formatters.indexOf(formattersListener);
 
 550           if (listenerIndex > -1) {
 
 551             ngModelCtrl.$formatters.splice(listenerIndex, 1);
 
 556       function attachResizeHandle() {
 
 557         if (attr.hasOwnProperty('mdNoResize')) return;
 
 559         var handle = angular.element('<div class="md-resize-handle"></div>');
 
 560         var isDragging = false;
 
 561         var dragStart = null;
 
 563         var container = containerCtrl.element;
 
 564         var dragGestureHandler = $mdGesture.register(handle, 'drag', { horizontal: false });
 
 567         element.wrap('<div class="md-resize-wrapper">').after(handle);
 
 568         handle.on('mousedown', onMouseDown);
 
 571           .on('$md.dragstart', onDragStart)
 
 572           .on('$md.drag', onDrag)
 
 573           .on('$md.dragend', onDragEnd);
 
 575         scope.$on('$destroy', function() {
 
 577             .off('mousedown', onMouseDown)
 
 581             .off('$md.dragstart', onDragStart)
 
 582             .off('$md.drag', onDrag)
 
 583             .off('$md.dragend', onDragEnd);
 
 585           dragGestureHandler();
 
 588           dragGestureHandler = null;
 
 591         function onMouseDown(ev) {
 
 594           dragStart = ev.clientY;
 
 595           startHeight = parseFloat(element.css('height')) || element.prop('offsetHeight');
 
 598         function onDragStart(ev) {
 
 599           if (!isDragging) return;
 
 602           container.addClass('md-input-resized');
 
 605         function onDrag(ev) {
 
 606           if (!isDragging) return;
 
 608           element.css('height', (startHeight + ev.pointer.distanceY) + 'px');
 
 611         function onDragEnd(ev) {
 
 612           if (!isDragging) return;
 
 614           container.removeClass('md-input-resized');
 
 618       // Attach a watcher to detect when the textarea gets shown.
 
 619       if (attr.hasOwnProperty('mdDetectHidden')) {
 
 621         var handleHiddenChange = function() {
 
 622           var wasHidden = false;
 
 625             var isHidden = node.offsetHeight === 0;
 
 627             if (isHidden === false && wasHidden === true) {
 
 631             wasHidden = isHidden;
 
 635         // Check every digest cycle whether the visibility of the textarea has changed.
 
 636         // Queue up to run after the digest cycle is complete.
 
 637         scope.$watch(function() {
 
 638           $mdUtil.nextTick(handleHiddenChange, false);
 
 646 function mdMaxlengthDirective($animate, $mdUtil) {
 
 649     require: ['ngModel', '^mdInputContainer'],
 
 653   function postLink(scope, element, attr, ctrls) {
 
 655     var ngModelCtrl = ctrls[0];
 
 656     var containerCtrl = ctrls[1];
 
 657     var charCountEl, errorsSpacer;
 
 659     // Wait until the next tick to ensure that the input has setup the errors spacer where we will
 
 660     // append our counter
 
 661     $mdUtil.nextTick(function() {
 
 662       errorsSpacer = angular.element(containerCtrl.element[0].querySelector('.md-errors-spacer'));
 
 663       charCountEl = angular.element('<div class="md-char-counter">');
 
 665       // Append our character counter inside the errors spacer
 
 666       errorsSpacer.append(charCountEl);
 
 668       // Stop model from trimming. This makes it so whitespace
 
 669       // over the maxlength still counts as invalid.
 
 670       attr.$set('ngTrim', 'false');
 
 672       scope.$watch(attr.mdMaxlength, function(value) {
 
 674         if (angular.isNumber(value) && value > 0) {
 
 675           if (!charCountEl.parent().length) {
 
 676             $animate.enter(charCountEl, errorsSpacer);
 
 680           $animate.leave(charCountEl);
 
 684       ngModelCtrl.$validators['md-maxlength'] = function(modelValue, viewValue) {
 
 685         if (!angular.isNumber(maxlength) || maxlength < 0) {
 
 689         // We always update the char count, when the modelValue has changed.
 
 690         // Using the $validators for triggering the update works very well.
 
 693         return ( modelValue || element.val() || viewValue || '' ).length <= maxlength;
 
 697     function renderCharCount(value) {
 
 698       // If we have not been appended to the body yet; do not render
 
 699       if (!charCountEl.parent) {
 
 703       // Force the value into a string since it may be a number,
 
 704       // which does not have a length property.
 
 705       charCountEl.text(String(element.val() || value || '').length + ' / ' + maxlength);
 
 711 function placeholderDirective($compile) {
 
 714     require: '^^?mdInputContainer',
 
 717       // Note that we need to do this in the pre-link, as opposed to the post link, if we want to
 
 718       // support data bindings in the placeholder. This is necessary, because we have a case where
 
 719       // we transfer the placeholder value to the `<label>` and we remove it from the original `<input>`.
 
 720       // If we did this in the post-link, Angular would have set up the observers already and would be
 
 721       // re-adding the attribute, even though we removed it from the element.
 
 726   function preLink(scope, element, attr, inputContainer) {
 
 727     // If there is no input container, just return
 
 728     if (!inputContainer) return;
 
 730     var label = inputContainer.element.find('label');
 
 731     var noFloat = inputContainer.element.attr('md-no-float');
 
 733     // If we have a label, or they specify the md-no-float attribute, just return
 
 734     if ((label && label.length) || noFloat === '' || scope.$eval(noFloat)) {
 
 735       // Add a placeholder class so we can target it in the CSS
 
 736       inputContainer.setHasPlaceholder(true);
 
 740     // md-select handles placeholders on it's own
 
 741     if (element[0].nodeName != 'MD-SELECT') {
 
 742       // Move the placeholder expression to the label
 
 743       var newLabel = angular.element('<label ng-click="delegateClick()" tabindex="-1">' + attr.placeholder + '</label>');
 
 745       // Note that we unset it via `attr`, in order to get Angular
 
 746       // to remove any observers that it might have set up. Otherwise
 
 747       // the attribute will be added on the next digest.
 
 748       attr.$set('placeholder', null);
 
 750       // We need to compile the label manually in case it has any bindings.
 
 751       // A gotcha here is that we first add the element to the DOM and we compile
 
 752       // it later. This is necessary, because if we compile the element beforehand,
 
 753       // it won't be able to find the `mdInputContainer` controller.
 
 754       inputContainer.element
 
 755         .addClass('md-icon-float')
 
 758       $compile(newLabel)(scope);
 
 765  * @name mdSelectOnFocus
 
 766  * @module material.components.input
 
 771  * The `md-select-on-focus` directive allows you to automatically select the element's input text on focus.
 
 774  * - The use of `md-select-on-focus` is restricted to `<input>` and `<textarea>` elements.
 
 777  * <h3>Using with an Input</h3>
 
 780  * <md-input-container>
 
 781  *   <label>Auto Select</label>
 
 782  *   <input type="text" md-select-on-focus>
 
 783  * </md-input-container>
 
 786  * <h3>Using with a Textarea</h3>
 
 789  * <md-input-container>
 
 790  *   <label>Auto Select</label>
 
 791  *   <textarea md-select-on-focus>This text will be selected on focus.</textarea>
 
 792  * </md-input-container>
 
 796 function mdSelectOnFocusDirective($timeout) {
 
 803   function postLink(scope, element, attr) {
 
 804     if (element[0].nodeName !== 'INPUT' && element[0].nodeName !== "TEXTAREA") return;
 
 806     var preventMouseUp = false;
 
 809       .on('focus', onFocus)
 
 810       .on('mouseup', onMouseUp);
 
 812     scope.$on('$destroy', function() {
 
 814         .off('focus', onFocus)
 
 815         .off('mouseup', onMouseUp);
 
 819       preventMouseUp = true;
 
 821       $timeout(function() {
 
 822         // Use HTMLInputElement#select to fix firefox select issues.
 
 823         // The debounce is here for Edge's sake, otherwise the selection doesn't work.
 
 826         // This should be reset from inside the `focus`, because the event might
 
 827         // have originated from something different than a click, e.g. a keyboard event.
 
 828         preventMouseUp = false;
 
 832     // Prevents the default action of the first `mouseup` after a focus.
 
 833     // This is necessary, because browsers fire a `mouseup` right after the element
 
 834     // has been focused. In some browsers (Firefox in particular) this can clear the
 
 835     // selection. There are examples of the problem in issue #7487.
 
 836     function onMouseUp(event) {
 
 837       if (preventMouseUp) {
 
 838         event.preventDefault();
 
 844 var visibilityDirectives = ['ngIf', 'ngShow', 'ngHide', 'ngSwitchWhen', 'ngSwitchDefault'];
 
 845 function ngMessagesDirective() {
 
 850     // This is optional because we don't want target *all* ngMessage instances, just those inside of
 
 852     require: '^^?mdInputContainer'
 
 855   function postLink(scope, element, attrs, inputContainer) {
 
 856     // If we are not a child of an input container, don't do anything
 
 857     if (!inputContainer) return;
 
 859     // Add our animation class
 
 860     element.toggleClass('md-input-messages-animation', true);
 
 862     // Add our md-auto-hide class to automatically hide/show messages when container is invalid
 
 863     element.toggleClass('md-auto-hide', true);
 
 865     // If we see some known visibility directives, remove the md-auto-hide class
 
 866     if (attrs.mdAutoHide == 'false' || hasVisibiltyDirective(attrs)) {
 
 867       element.toggleClass('md-auto-hide', false);
 
 871   function hasVisibiltyDirective(attrs) {
 
 872     return visibilityDirectives.some(function(attr) {
 
 878 function ngMessageDirective($mdUtil) {
 
 885   function compile(tElement) {
 
 886     if (!isInsideInputContainer(tElement)) {
 
 888       // When the current element is inside of a document fragment, then we need to check for an input-container
 
 889       // in the postLink, because the element will be later added to the DOM and is currently just in a temporary
 
 890       // fragment, which causes the input-container check to fail.
 
 891       if (isInsideFragment()) {
 
 892         return function (scope, element) {
 
 893           if (isInsideInputContainer(element)) {
 
 894             // Inside of the postLink function, a ngMessage directive will be a comment element, because it's
 
 895             // currently hidden. To access the shown element, we need to use the element from the compile function.
 
 896             initMessageElement(tElement);
 
 901       initMessageElement(tElement);
 
 904     function isInsideFragment() {
 
 905       var nextNode = tElement[0];
 
 906       while (nextNode = nextNode.parentNode) {
 
 907         if (nextNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
 
 914     function isInsideInputContainer(element) {
 
 915       return !!$mdUtil.getClosest(element, "md-input-container");
 
 918     function initMessageElement(element) {
 
 919       // Add our animation class
 
 920       element.toggleClass('md-input-message-animation', true);
 
 925 var $$AnimateRunner, $animateCss, $mdUtil, $log;
 
 927 function mdInputInvalidMessagesAnimation($$AnimateRunner, $animateCss, $mdUtil, $log) {
 
 928   saveSharedServices($$AnimateRunner, $animateCss, $mdUtil, $log);
 
 931     addClass: function(element, className, done) {
 
 932       showInputMessages(element, done);
 
 935     // NOTE: We do not need the removeClass method, because the message ng-leave animation will fire
 
 939 function ngMessagesAnimation($$AnimateRunner, $animateCss, $mdUtil, $log) {
 
 940   saveSharedServices($$AnimateRunner, $animateCss, $mdUtil, $log);
 
 943     enter: function(element, done) {
 
 944       showInputMessages(element, done);
 
 947     leave: function(element, done) {
 
 948       hideInputMessages(element, done);
 
 951     addClass: function(element, className, done) {
 
 952       if (className == "ng-hide") {
 
 953         hideInputMessages(element, done);
 
 959     removeClass: function(element, className, done) {
 
 960       if (className == "ng-hide") {
 
 961         showInputMessages(element, done);
 
 969 function ngMessageAnimation($$AnimateRunner, $animateCss, $mdUtil, $log) {
 
 970   saveSharedServices($$AnimateRunner, $animateCss, $mdUtil, $log);
 
 973     enter: function(element, done) {
 
 974       var animator = showMessage(element);
 
 976       animator.start().done(done);
 
 979     leave: function(element, done) {
 
 980       var animator = hideMessage(element);
 
 982       animator.start().done(done);
 
 987 function showInputMessages(element, done) {
 
 988   var animators = [], animator;
 
 989   var messages = getMessagesElement(element);
 
 990   var children = messages.children();
 
 992   if (messages.length == 0 || children.length == 0) {
 
 993     $log.warn('mdInput messages show animation called on invalid messages element: ', element);
 
 998   angular.forEach(children, function(child) {
 
 999     animator = showMessage(angular.element(child));
 
1001     animators.push(animator.start());
 
1004   $$AnimateRunner.all(animators, done);
 
1007 function hideInputMessages(element, done) {
 
1008   var animators = [], animator;
 
1009   var messages = getMessagesElement(element);
 
1010   var children = messages.children();
 
1012   if (messages.length == 0 || children.length == 0) {
 
1013     $log.warn('mdInput messages hide animation called on invalid messages element: ', element);
 
1018   angular.forEach(children, function(child) {
 
1019     animator = hideMessage(angular.element(child));
 
1021     animators.push(animator.start());
 
1024   $$AnimateRunner.all(animators, done);
 
1027 function showMessage(element) {
 
1028   var height = parseInt(window.getComputedStyle(element[0]).height);
 
1029   var topMargin = parseInt(window.getComputedStyle(element[0]).marginTop);
 
1031   var messages = getMessagesElement(element);
 
1032   var container = getInputElement(element);
 
1034   // Check to see if the message is already visible so we can skip
 
1035   var alreadyVisible = (topMargin > -height);
 
1037   // If we have the md-auto-hide class, the md-input-invalid animation will fire, so we can skip
 
1038   if (alreadyVisible || (messages.hasClass('md-auto-hide') && !container.hasClass('md-input-invalid'))) {
 
1039     return $animateCss(element, {});
 
1042   return $animateCss(element, {
 
1045     from: {"opacity": 0, "margin-top": -height + "px"},
 
1046     to: {"opacity": 1, "margin-top": "0"},
 
1051 function hideMessage(element) {
 
1052   var height = element[0].offsetHeight;
 
1053   var styles = window.getComputedStyle(element[0]);
 
1055   // If we are already hidden, just return an empty animation
 
1056   if (parseInt(styles.opacity) === 0) {
 
1057     return $animateCss(element, {});
 
1060   // Otherwise, animate
 
1061   return $animateCss(element, {
 
1064     from: {"opacity": 1, "margin-top": 0},
 
1065     to: {"opacity": 0, "margin-top": -height + "px"},
 
1070 function getInputElement(element) {
 
1071   var inputContainer = element.controller('mdInputContainer');
 
1073   return inputContainer.element;
 
1076 function getMessagesElement(element) {
 
1077   // If we ARE the messages element, just return ourself
 
1078   if (element.hasClass('md-input-messages-animation')) {
 
1082   // If we are a ng-message element, we need to traverse up the DOM tree
 
1083   if (element.hasClass('md-input-message-animation')) {
 
1084     return angular.element($mdUtil.getClosest(element, function(node) {
 
1085       return node.classList.contains('md-input-messages-animation');
 
1089   // Otherwise, we can traverse down
 
1090   return angular.element(element[0].querySelector('.md-input-messages-animation'));
 
1093 function saveSharedServices(_$$AnimateRunner_, _$animateCss_, _$mdUtil_, _$log_) {
 
1094   $$AnimateRunner = _$$AnimateRunner_;
 
1095   $animateCss = _$animateCss_;
 
1096   $mdUtil = _$mdUtil_;
 
1100 ngmaterial.components.input = angular.module("material.components.input");