02025128788cbf766ddae06307861cb0130aaf7c
[vnfsdk/refrepo.git] /
1 /*!
2  * Angular Material Design
3  * https://github.com/angular/material
4  * @license MIT
5  * v1.1.3
6  */
7 goog.provide('ngmaterial.components.input');
8 goog.require('ngmaterial.core');
9 /**
10  * @ngdoc module
11  * @name material.components.input
12  */
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', [
23     'material.core'
24   ])
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)
35
36   .animation('.md-input-invalid', mdInputInvalidMessagesAnimation)
37   .animation('.md-input-messages-animation', ngMessagesAnimation)
38   .animation('.md-input-message-animation', ngMessageAnimation);
39
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() {
43     return {
44       // special accessor to internals... useful for testing
45       messages: {
46         show        : showInputMessages,
47         hide        : hideInputMessages,
48         getElement  : getMessagesElement
49       }
50     }
51   })
52
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);
57 }
58
59 /**
60  * @ngdoc directive
61  * @name mdInputContainer
62  * @module material.components.input
63  *
64  * @restrict E
65  *
66  * @description
67  * `<md-input-container>` is the parent of any input or textarea element.
68  *
69  * Input and textarea elements will not behave properly unless the md-input-container
70  * parent is provided.
71  *
72  * A single `<md-input-container>` should contain only one `<input>` element, otherwise it will throw an error.
73  *
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.
76  *
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.
80  *
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
84  *   labels.
85  *
86  * @usage
87  * <hljs lang="html">
88  *
89  * <md-input-container>
90  *   <label>Username</label>
91  *   <input type="text" ng-model="user.name">
92  * </md-input-container>
93  *
94  * <md-input-container>
95  *   <label>Description</label>
96  *   <textarea ng-model="user.description"></textarea>
97  * </md-input-container>
98  *
99  * </hljs>
100  *
101  * <h3>When disabling floating labels</h3>
102  * <hljs lang="html">
103  *
104  * <md-input-container md-no-float>
105  *   <input type="text" placeholder="Non-Floating Label">
106  * </md-input-container>
107  *
108  * </hljs>
109  */
110 function mdInputContainerDirective($mdTheming, $parse) {
111
112   ContainerCtrl['$inject'] = ["$scope", "$element", "$attrs", "$animate"];
113   var INPUT_TAGS = ['INPUT', 'TEXTAREA', 'SELECT', 'MD-SELECT'];
114
115   var LEFT_SELECTORS = INPUT_TAGS.reduce(function(selectors, isel) {
116     return selectors.concat(['md-icon ~ ' + isel, '.md-icon ~ ' + isel]);
117   }, []).join(",");
118
119   var RIGHT_SELECTORS = INPUT_TAGS.reduce(function(selectors, isel) {
120     return selectors.concat([isel + ' ~ md-icon', isel + ' ~ .md-icon']);
121   }, []).join(",");
122
123   return {
124     restrict: 'E',
125     compile: compile,
126     controller: ContainerCtrl
127   };
128
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);
133
134     if (leftIcon) { tElement.addClass('md-icon-left'); }
135     if (rightIcon) { tElement.addClass('md-icon-right'); }
136
137     return function postLink(scope, element) {
138       $mdTheming(element);
139     };
140   }
141
142   function ContainerCtrl($scope, $element, $attrs, $animate) {
143     var self = this;
144
145     self.isErrorGetter = $attrs.mdIsError && $parse($attrs.mdIsError);
146
147     self.delegateClick = function() {
148       self.input.focus();
149     };
150     self.element = $element;
151     self.setFocused = function(isFocused) {
152       $element.toggleClass('md-input-focused', !!isFocused);
153     };
154     self.setHasValue = function(hasValue) {
155       $element.toggleClass('md-input-has-value', !!hasValue);
156     };
157     self.setHasPlaceholder = function(hasPlaceholder) {
158       $element.toggleClass('md-input-has-placeholder', !!hasPlaceholder);
159     };
160     self.setInvalid = function(isInvalid) {
161       if (isInvalid) {
162         $animate.addClass($element, 'md-input-invalid');
163       } else {
164         $animate.removeClass($element, 'md-input-invalid');
165       }
166     };
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'));
172       }
173     });
174   }
175 }
176
177 function labelDirective() {
178   return {
179     restrict: 'E',
180     require: '^?mdInputContainer',
181     link: function(scope, element, attr, containerCtrl) {
182       if (!containerCtrl || attr.mdNoFloat || element.hasClass('md-container-ignore')) return;
183
184       containerCtrl.label = element;
185       scope.$on('$destroy', function() {
186         containerCtrl.label = null;
187       });
188     }
189   };
190 }
191
192 /**
193  * @ngdoc directive
194  * @name mdInput
195  * @restrict E
196  * @module material.components.input
197  *
198  * @description
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.
201  *
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.
205  *
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).
212  *
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.
224  *
225  * @usage
226  * <hljs lang="html">
227  * <md-input-container>
228  *   <label>Color</label>
229  *   <input type="text" ng-model="color" required md-maxlength="10">
230  * </md-input-container>
231  * </hljs>
232  *
233  * <h3>With Errors</h3>
234  *
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.
238  *
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.
242  *
243  * <hljs lang="html">
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>
250  *     </div>
251  *   </md-input-container>
252  * </form>
253  * </hljs>
254  *
255  * We automatically disable this auto-hiding functionality if you provide any of the following
256  * visibility directives on the `ng-messages` container:
257  *
258  *  - `ng-if`
259  *  - `ng-show`/`ng-hide`
260  *  - `ng-switch-when`/`ng-switch-default`
261  *
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.
265  *
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`._
268  *
269  * <hljs lang="html">
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>
278  *     </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>
286  *     </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>
294  * </form>
295  * </hljs>
296  *
297  * <h3>Notes</h3>
298  *
299  * - Requires [ngMessages](https://docs.angularjs.org/api/ngMessages).
300  * - Behaves like the [AngularJS input directive](https://docs.angularjs.org/api/ng/directive/input).
301  *
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.
305  *
306  *
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`
310  * attribute.
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.
321  */
322
323 function inputTextareaDirective($mdUtil, $window, $mdAria, $timeout, $mdGesture) {
324   return {
325     restrict: 'E',
326     require: ['^?mdInputContainer', '?ngModel', '?^form'],
327     link: postLink
328   };
329
330   function postLink(scope, element, attr, ctrls) {
331
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();
339
340
341     if (!containerCtrl) return;
342     if (attr.type === 'hidden') {
343       element.attr('aria-hidden', 'true');
344       return;
345     } else if (containerCtrl.input) {
346       if (containerCtrl.input[0].contains(element[0])) {
347         return;
348       } else {
349         throw new Error("<md-input-container> can only have *one* <input>, <textarea> or <md-select> child element!");
350       }
351     }
352     containerCtrl.input = element;
353
354     setupAttributeWatchers();
355
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);
359
360     if (!containerCtrl.label) {
361       $mdAria.expect(element, 'aria-label', attr.placeholder);
362     }
363
364     element.addClass('md-input');
365     if (!element.attr('id')) {
366       element.attr('id', 'input_' + $mdUtil.nextUid());
367     }
368
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') {
376       setupTextarea();
377     }
378
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.
382     if (!hasNgModel) {
383       inputCheckValue();
384     }
385
386     var isErrorGetter = containerCtrl.isErrorGetter || function() {
387       return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (parentForm && parentForm.$submitted));
388     };
389
390     scope.$watch(isErrorGetter, containerCtrl.setInvalid);
391
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.
394     if (attr.ngValue) {
395       attr.$observe('value', inputCheckValue);
396     }
397
398     ngModelCtrl.$parsers.push(ngModelPipelineCheckValue);
399     ngModelCtrl.$formatters.push(ngModelPipelineCheckValue);
400
401     element.on('input', inputCheckValue);
402
403     if (!isReadonly) {
404       element
405         .on('focus', function(ev) {
406           $mdUtil.nextTick(function() {
407             containerCtrl.setFocused(true);
408           });
409         })
410         .on('blur', function(ev) {
411           $mdUtil.nextTick(function() {
412             containerCtrl.setFocused(false);
413             inputCheckValue();
414           });
415         });
416     }
417
418     scope.$on('$destroy', function() {
419       containerCtrl.setFocused(false);
420       containerCtrl.setHasValue(false);
421       containerCtrl.input = null;
422     });
423
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));
427       return arg;
428     }
429
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);
436         });
437       }
438     }
439
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);
444     }
445
446     function setupTextarea() {
447       var isAutogrowing = !attr.hasOwnProperty('mdNoAutogrow');
448
449       attachResizeHandle();
450
451       if (!isAutogrowing) return;
452
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];
460
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);
465       }, 10, false);
466
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
470       // spaces are added.
471       element.on('input', growTextarea);
472
473       // We should still use the $formatters, because they fire when
474       // the value was changed from outside the textarea.
475       if (hasNgModel) {
476         ngModelCtrl.$formatters.push(formattersListener);
477       }
478
479       if (!minRows) {
480         element.attr('rows', 1);
481       }
482
483       angular.element($window).on('resize', growTextarea);
484       scope.$on('$destroy', disableAutogrow);
485
486       function growTextarea() {
487         // temporarily disables element's flex so its height 'runs free'
488         element
489           .attr('rows', 1)
490           .css('height', 'auto')
491           .addClass('md-no-flex');
492
493         var height = getHeight();
494
495         if (!lineHeight) {
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;
500         }
501
502         if (minRows && lineHeight) {
503           height = Math.max(height, lineHeight * minRows);
504         }
505
506         if (maxRows && lineHeight) {
507           var maxHeight = lineHeight * maxRows;
508
509           if (maxHeight < height) {
510             element.attr('md-no-autogrow', '');
511             height = maxHeight;
512           } else {
513             element.removeAttr('md-no-autogrow');
514           }
515         }
516
517         if (lineHeight) {
518           element.attr('rows', Math.round(height / lineHeight));
519         }
520
521         element
522           .css('height', height + 'px')
523           .removeClass('md-no-flex');
524       }
525
526       function getHeight() {
527         var offsetHeight = node.offsetHeight;
528         var line = node.scrollHeight - offsetHeight;
529         return offsetHeight + Math.max(line, 0);
530       }
531
532       function formattersListener(value) {
533         $mdUtil.nextTick(growTextarea);
534         return value;
535       }
536
537       function disableAutogrow() {
538         if (!isAutogrowing) return;
539
540         isAutogrowing = false;
541         angular.element($window).off('resize', growTextarea);
542         scopeResizeListener && scopeResizeListener();
543         element
544           .attr('md-no-autogrow', '')
545           .off('input', growTextarea);
546
547         if (hasNgModel) {
548           var listenerIndex = ngModelCtrl.$formatters.indexOf(formattersListener);
549
550           if (listenerIndex > -1) {
551             ngModelCtrl.$formatters.splice(listenerIndex, 1);
552           }
553         }
554       }
555
556       function attachResizeHandle() {
557         if (attr.hasOwnProperty('mdNoResize')) return;
558
559         var handle = angular.element('<div class="md-resize-handle"></div>');
560         var isDragging = false;
561         var dragStart = null;
562         var startHeight = 0;
563         var container = containerCtrl.element;
564         var dragGestureHandler = $mdGesture.register(handle, 'drag', { horizontal: false });
565
566
567         element.wrap('<div class="md-resize-wrapper">').after(handle);
568         handle.on('mousedown', onMouseDown);
569
570         container
571           .on('$md.dragstart', onDragStart)
572           .on('$md.drag', onDrag)
573           .on('$md.dragend', onDragEnd);
574
575         scope.$on('$destroy', function() {
576           handle
577             .off('mousedown', onMouseDown)
578             .remove();
579
580           container
581             .off('$md.dragstart', onDragStart)
582             .off('$md.drag', onDrag)
583             .off('$md.dragend', onDragEnd);
584
585           dragGestureHandler();
586           handle = null;
587           container = null;
588           dragGestureHandler = null;
589         });
590
591         function onMouseDown(ev) {
592           ev.preventDefault();
593           isDragging = true;
594           dragStart = ev.clientY;
595           startHeight = parseFloat(element.css('height')) || element.prop('offsetHeight');
596         }
597
598         function onDragStart(ev) {
599           if (!isDragging) return;
600           ev.preventDefault();
601           disableAutogrow();
602           container.addClass('md-input-resized');
603         }
604
605         function onDrag(ev) {
606           if (!isDragging) return;
607
608           element.css('height', (startHeight + ev.pointer.distanceY) + 'px');
609         }
610
611         function onDragEnd(ev) {
612           if (!isDragging) return;
613           isDragging = false;
614           container.removeClass('md-input-resized');
615         }
616       }
617
618       // Attach a watcher to detect when the textarea gets shown.
619       if (attr.hasOwnProperty('mdDetectHidden')) {
620
621         var handleHiddenChange = function() {
622           var wasHidden = false;
623
624           return function() {
625             var isHidden = node.offsetHeight === 0;
626
627             if (isHidden === false && wasHidden === true) {
628               growTextarea();
629             }
630
631             wasHidden = isHidden;
632           };
633         }();
634
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);
639           return true;
640         });
641       }
642     }
643   }
644 }
645
646 function mdMaxlengthDirective($animate, $mdUtil) {
647   return {
648     restrict: 'A',
649     require: ['ngModel', '^mdInputContainer'],
650     link: postLink
651   };
652
653   function postLink(scope, element, attr, ctrls) {
654     var maxlength;
655     var ngModelCtrl = ctrls[0];
656     var containerCtrl = ctrls[1];
657     var charCountEl, errorsSpacer;
658
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">');
664
665       // Append our character counter inside the errors spacer
666       errorsSpacer.append(charCountEl);
667
668       // Stop model from trimming. This makes it so whitespace
669       // over the maxlength still counts as invalid.
670       attr.$set('ngTrim', 'false');
671
672       scope.$watch(attr.mdMaxlength, function(value) {
673         maxlength = value;
674         if (angular.isNumber(value) && value > 0) {
675           if (!charCountEl.parent().length) {
676             $animate.enter(charCountEl, errorsSpacer);
677           }
678           renderCharCount();
679         } else {
680           $animate.leave(charCountEl);
681         }
682       });
683
684       ngModelCtrl.$validators['md-maxlength'] = function(modelValue, viewValue) {
685         if (!angular.isNumber(maxlength) || maxlength < 0) {
686           return true;
687         }
688
689         // We always update the char count, when the modelValue has changed.
690         // Using the $validators for triggering the update works very well.
691         renderCharCount();
692
693         return ( modelValue || element.val() || viewValue || '' ).length <= maxlength;
694       };
695     });
696
697     function renderCharCount(value) {
698       // If we have not been appended to the body yet; do not render
699       if (!charCountEl.parent) {
700         return value;
701       }
702
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);
706       return value;
707     }
708   }
709 }
710
711 function placeholderDirective($compile) {
712   return {
713     restrict: 'A',
714     require: '^^?mdInputContainer',
715     priority: 200,
716     link: {
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.
722       pre: preLink
723     }
724   };
725
726   function preLink(scope, element, attr, inputContainer) {
727     // If there is no input container, just return
728     if (!inputContainer) return;
729
730     var label = inputContainer.element.find('label');
731     var noFloat = inputContainer.element.attr('md-no-float');
732
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);
737       return;
738     }
739
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>');
744
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);
749
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')
756         .prepend(newLabel);
757
758       $compile(newLabel)(scope);
759     }
760   }
761 }
762
763 /**
764  * @ngdoc directive
765  * @name mdSelectOnFocus
766  * @module material.components.input
767  *
768  * @restrict A
769  *
770  * @description
771  * The `md-select-on-focus` directive allows you to automatically select the element's input text on focus.
772  *
773  * <h3>Notes</h3>
774  * - The use of `md-select-on-focus` is restricted to `<input>` and `<textarea>` elements.
775  *
776  * @usage
777  * <h3>Using with an Input</h3>
778  * <hljs lang="html">
779  *
780  * <md-input-container>
781  *   <label>Auto Select</label>
782  *   <input type="text" md-select-on-focus>
783  * </md-input-container>
784  * </hljs>
785  *
786  * <h3>Using with a Textarea</h3>
787  * <hljs lang="html">
788  *
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>
793  *
794  * </hljs>
795  */
796 function mdSelectOnFocusDirective($timeout) {
797
798   return {
799     restrict: 'A',
800     link: postLink
801   };
802
803   function postLink(scope, element, attr) {
804     if (element[0].nodeName !== 'INPUT' && element[0].nodeName !== "TEXTAREA") return;
805
806     var preventMouseUp = false;
807
808     element
809       .on('focus', onFocus)
810       .on('mouseup', onMouseUp);
811
812     scope.$on('$destroy', function() {
813       element
814         .off('focus', onFocus)
815         .off('mouseup', onMouseUp);
816     });
817
818     function onFocus() {
819       preventMouseUp = true;
820
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.
824         element[0].select();
825
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;
829       }, 1, false);
830     }
831
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();
839       }
840     }
841   }
842 }
843
844 var visibilityDirectives = ['ngIf', 'ngShow', 'ngHide', 'ngSwitchWhen', 'ngSwitchDefault'];
845 function ngMessagesDirective() {
846   return {
847     restrict: 'EA',
848     link: postLink,
849
850     // This is optional because we don't want target *all* ngMessage instances, just those inside of
851     // mdInputContainer.
852     require: '^^?mdInputContainer'
853   };
854
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;
858
859     // Add our animation class
860     element.toggleClass('md-input-messages-animation', true);
861
862     // Add our md-auto-hide class to automatically hide/show messages when container is invalid
863     element.toggleClass('md-auto-hide', true);
864
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);
868     }
869   }
870
871   function hasVisibiltyDirective(attrs) {
872     return visibilityDirectives.some(function(attr) {
873       return attrs[attr];
874     });
875   }
876 }
877
878 function ngMessageDirective($mdUtil) {
879   return {
880     restrict: 'EA',
881     compile: compile,
882     priority: 100
883   };
884
885   function compile(tElement) {
886     if (!isInsideInputContainer(tElement)) {
887
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);
897           }
898         };
899       }
900     } else {
901       initMessageElement(tElement);
902     }
903
904     function isInsideFragment() {
905       var nextNode = tElement[0];
906       while (nextNode = nextNode.parentNode) {
907         if (nextNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
908           return true;
909         }
910       }
911       return false;
912     }
913
914     function isInsideInputContainer(element) {
915       return !!$mdUtil.getClosest(element, "md-input-container");
916     }
917
918     function initMessageElement(element) {
919       // Add our animation class
920       element.toggleClass('md-input-message-animation', true);
921     }
922   }
923 }
924
925 var $$AnimateRunner, $animateCss, $mdUtil, $log;
926
927 function mdInputInvalidMessagesAnimation($$AnimateRunner, $animateCss, $mdUtil, $log) {
928   saveSharedServices($$AnimateRunner, $animateCss, $mdUtil, $log);
929
930   return {
931     addClass: function(element, className, done) {
932       showInputMessages(element, done);
933     }
934
935     // NOTE: We do not need the removeClass method, because the message ng-leave animation will fire
936   };
937 }
938
939 function ngMessagesAnimation($$AnimateRunner, $animateCss, $mdUtil, $log) {
940   saveSharedServices($$AnimateRunner, $animateCss, $mdUtil, $log);
941
942   return {
943     enter: function(element, done) {
944       showInputMessages(element, done);
945     },
946
947     leave: function(element, done) {
948       hideInputMessages(element, done);
949     },
950
951     addClass: function(element, className, done) {
952       if (className == "ng-hide") {
953         hideInputMessages(element, done);
954       } else {
955         done();
956       }
957     },
958
959     removeClass: function(element, className, done) {
960       if (className == "ng-hide") {
961         showInputMessages(element, done);
962       } else {
963         done();
964       }
965     }
966   };
967 }
968
969 function ngMessageAnimation($$AnimateRunner, $animateCss, $mdUtil, $log) {
970   saveSharedServices($$AnimateRunner, $animateCss, $mdUtil, $log);
971
972   return {
973     enter: function(element, done) {
974       var animator = showMessage(element);
975
976       animator.start().done(done);
977     },
978
979     leave: function(element, done) {
980       var animator = hideMessage(element);
981
982       animator.start().done(done);
983     }
984   };
985 }
986
987 function showInputMessages(element, done) {
988   var animators = [], animator;
989   var messages = getMessagesElement(element);
990   var children = messages.children();
991
992   if (messages.length == 0 || children.length == 0) {
993     $log.warn('mdInput messages show animation called on invalid messages element: ', element);
994     done();
995     return;
996   }
997
998   angular.forEach(children, function(child) {
999     animator = showMessage(angular.element(child));
1000
1001     animators.push(animator.start());
1002   });
1003
1004   $$AnimateRunner.all(animators, done);
1005 }
1006
1007 function hideInputMessages(element, done) {
1008   var animators = [], animator;
1009   var messages = getMessagesElement(element);
1010   var children = messages.children();
1011
1012   if (messages.length == 0 || children.length == 0) {
1013     $log.warn('mdInput messages hide animation called on invalid messages element: ', element);
1014     done();
1015     return;
1016   }
1017
1018   angular.forEach(children, function(child) {
1019     animator = hideMessage(angular.element(child));
1020
1021     animators.push(animator.start());
1022   });
1023
1024   $$AnimateRunner.all(animators, done);
1025 }
1026
1027 function showMessage(element) {
1028   var height = parseInt(window.getComputedStyle(element[0]).height);
1029   var topMargin = parseInt(window.getComputedStyle(element[0]).marginTop);
1030
1031   var messages = getMessagesElement(element);
1032   var container = getInputElement(element);
1033
1034   // Check to see if the message is already visible so we can skip
1035   var alreadyVisible = (topMargin > -height);
1036
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, {});
1040   }
1041
1042   return $animateCss(element, {
1043     event: 'enter',
1044     structural: true,
1045     from: {"opacity": 0, "margin-top": -height + "px"},
1046     to: {"opacity": 1, "margin-top": "0"},
1047     duration: 0.3
1048   });
1049 }
1050
1051 function hideMessage(element) {
1052   var height = element[0].offsetHeight;
1053   var styles = window.getComputedStyle(element[0]);
1054
1055   // If we are already hidden, just return an empty animation
1056   if (parseInt(styles.opacity) === 0) {
1057     return $animateCss(element, {});
1058   }
1059
1060   // Otherwise, animate
1061   return $animateCss(element, {
1062     event: 'leave',
1063     structural: true,
1064     from: {"opacity": 1, "margin-top": 0},
1065     to: {"opacity": 0, "margin-top": -height + "px"},
1066     duration: 0.3
1067   });
1068 }
1069
1070 function getInputElement(element) {
1071   var inputContainer = element.controller('mdInputContainer');
1072
1073   return inputContainer.element;
1074 }
1075
1076 function getMessagesElement(element) {
1077   // If we ARE the messages element, just return ourself
1078   if (element.hasClass('md-input-messages-animation')) {
1079     return element;
1080   }
1081
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');
1086     }));
1087   }
1088
1089   // Otherwise, we can traverse down
1090   return angular.element(element[0].querySelector('.md-input-messages-animation'));
1091 }
1092
1093 function saveSharedServices(_$$AnimateRunner_, _$animateCss_, _$mdUtil_, _$log_) {
1094   $$AnimateRunner = _$$AnimateRunner_;
1095   $animateCss = _$animateCss_;
1096   $mdUtil = _$mdUtil_;
1097   $log = _$log_;
1098 }
1099
1100 ngmaterial.components.input = angular.module("material.components.input");