socketioMEANseed/www/bower_components/angular-material/modules/js/input/input.js
www/bower_components/angular-material/modules/js/input/input.jsRaw
1/*!
2 * Angular Material Design
3 * https://github.com/angular/material
4 * @license MIT
5 * v1.1.3
6 */
7(function( window, angular, undefined ){
8"use strict";
9
10/**
11 * @ngdoc module
12 * @name material.components.input
13 */
14mdInputContainerDirective['$inject'] = ["$mdTheming", "$parse"];
15inputTextareaDirective['$inject'] = ["$mdUtil", "$window", "$mdAria", "$timeout", "$mdGesture"];
16mdMaxlengthDirective['$inject'] = ["$animate", "$mdUtil"];
17placeholderDirective['$inject'] = ["$compile"];
18ngMessageDirective['$inject'] = ["$mdUtil"];
19mdSelectOnFocusDirective['$inject'] = ["$timeout"];
20mdInputInvalidMessagesAnimation['$inject'] = ["$$AnimateRunner", "$animateCss", "$mdUtil", "$log"];
21ngMessagesAnimation['$inject'] = ["$$AnimateRunner", "$animateCss", "$mdUtil", "$log"];
22ngMessageAnimation['$inject'] = ["$$AnimateRunner", "$animateCss", "$mdUtil", "$log"];
23var inputModule = angular.module('material.components.input', [
24 'material.core'
25 ])
26 .directive('mdInputContainer', mdInputContainerDirective)
27 .directive('label', labelDirective)
28 .directive('input', inputTextareaDirective)
29 .directive('textarea', inputTextareaDirective)
30 .directive('mdMaxlength', mdMaxlengthDirective)
31 .directive('placeholder', placeholderDirective)
32 .directive('ngMessages', ngMessagesDirective)
33 .directive('ngMessage', ngMessageDirective)
34 .directive('ngMessageExp', ngMessageDirective)
35 .directive('mdSelectOnFocus', mdSelectOnFocusDirective)
36
37 .animation('.md-input-invalid', mdInputInvalidMessagesAnimation)
38 .animation('.md-input-messages-animation', ngMessagesAnimation)
39 .animation('.md-input-message-animation', ngMessageAnimation);
40
41// If we are running inside of tests; expose some extra services so that we can test them
42if (window._mdMocksIncluded) {
43 inputModule.service('$$mdInput', function() {
44 return {
45 // special accessor to internals... useful for testing
46 messages: {
47 show : showInputMessages,
48 hide : hideInputMessages,
49 getElement : getMessagesElement
50 }
51 }
52 })
53
54 // Register a service for each animation so that we can easily inject them into unit tests
55 .service('mdInputInvalidAnimation', mdInputInvalidMessagesAnimation)
56 .service('mdInputMessagesAnimation', ngMessagesAnimation)
57 .service('mdInputMessageAnimation', ngMessageAnimation);
58}
59
60/**
61 * @ngdoc directive
62 * @name mdInputContainer
63 * @module material.components.input
64 *
65 * @restrict E
66 *
67 * @description
68 * `<md-input-container>` is the parent of any input or textarea element.
69 *
70 * Input and textarea elements will not behave properly unless the md-input-container
71 * parent is provided.
72 *
73 * A single `<md-input-container>` should contain only one `<input>` element, otherwise it will throw an error.
74 *
75 * <b>Exception:</b> Hidden inputs (`<input type="hidden" />`) are ignored and will not throw an error, so
76 * you may combine these with other inputs.
77 *
78 * <b>Note:</b> When using `ngMessages` with your input element, make sure the message and container elements
79 * are *block* elements, otherwise animations applied to the messages will not look as intended. Either use a `div` and
80 * apply the `ng-message` and `ng-messages` classes respectively, or use the `md-block` class on your element.
81 *
82 * @param md-is-error {expression=} When the given expression evaluates to true, the input container
83 * will go into error state. Defaults to erroring if the input has been touched and is invalid.
84 * @param md-no-float {boolean=} When present, `placeholder` attributes on the input will not be converted to floating
85 * labels.
86 *
87 * @usage
88 * <hljs lang="html">
89 *
90 * <md-input-container>
91 * <label>Username</label>
92 * <input type="text" ng-model="user.name">
93 * </md-input-container>
94 *
95 * <md-input-container>
96 * <label>Description</label>
97 * <textarea ng-model="user.description"></textarea>
98 * </md-input-container>
99 *
100 * </hljs>
101 *
102 * <h3>When disabling floating labels</h3>
103 * <hljs lang="html">
104 *
105 * <md-input-container md-no-float>
106 * <input type="text" placeholder="Non-Floating Label">
107 * </md-input-container>
108 *
109 * </hljs>
110 */
111function mdInputContainerDirective($mdTheming, $parse) {
112
113 ContainerCtrl['$inject'] = ["$scope", "$element", "$attrs", "$animate"];
114 var INPUT_TAGS = ['INPUT', 'TEXTAREA', 'SELECT', 'MD-SELECT'];
115
116 var LEFT_SELECTORS = INPUT_TAGS.reduce(function(selectors, isel) {
117 return selectors.concat(['md-icon ~ ' + isel, '.md-icon ~ ' + isel]);
118 }, []).join(",");
119
120 var RIGHT_SELECTORS = INPUT_TAGS.reduce(function(selectors, isel) {
121 return selectors.concat([isel + ' ~ md-icon', isel + ' ~ .md-icon']);
122 }, []).join(",");
123
124 return {
125 restrict: 'E',
126 compile: compile,
127 controller: ContainerCtrl
128 };
129
130 function compile(tElement) {
131 // Check for both a left & right icon
132 var leftIcon = tElement[0].querySelector(LEFT_SELECTORS);
133 var rightIcon = tElement[0].querySelector(RIGHT_SELECTORS);
134
135 if (leftIcon) { tElement.addClass('md-icon-left'); }
136 if (rightIcon) { tElement.addClass('md-icon-right'); }
137
138 return function postLink(scope, element) {
139 $mdTheming(element);
140 };
141 }
142
143 function ContainerCtrl($scope, $element, $attrs, $animate) {
144 var self = this;
145
146 self.isErrorGetter = $attrs.mdIsError && $parse($attrs.mdIsError);
147
148 self.delegateClick = function() {
149 self.input.focus();
150 };
151 self.element = $element;
152 self.setFocused = function(isFocused) {
153 $element.toggleClass('md-input-focused', !!isFocused);
154 };
155 self.setHasValue = function(hasValue) {
156 $element.toggleClass('md-input-has-value', !!hasValue);
157 };
158 self.setHasPlaceholder = function(hasPlaceholder) {
159 $element.toggleClass('md-input-has-placeholder', !!hasPlaceholder);
160 };
161 self.setInvalid = function(isInvalid) {
162 if (isInvalid) {
163 $animate.addClass($element, 'md-input-invalid');
164 } else {
165 $animate.removeClass($element, 'md-input-invalid');
166 }
167 };
168 $scope.$watch(function() {
169 return self.label && self.input;
170 }, function(hasLabelAndInput) {
171 if (hasLabelAndInput && !self.label.attr('for')) {
172 self.label.attr('for', self.input.attr('id'));
173 }
174 });
175 }
176}
177
178function labelDirective() {
179 return {
180 restrict: 'E',
181 require: '^?mdInputContainer',
182 link: function(scope, element, attr, containerCtrl) {
183 if (!containerCtrl || attr.mdNoFloat || element.hasClass('md-container-ignore')) return;
184
185 containerCtrl.label = element;
186 scope.$on('$destroy', function() {
187 containerCtrl.label = null;
188 });
189 }
190 };
191}
192
193/**
194 * @ngdoc directive
195 * @name mdInput
196 * @restrict E
197 * @module material.components.input
198 *
199 * @description
200 * You can use any `<input>` or `<textarea>` element as a child of an `<md-input-container>`. This
201 * allows you to build complex forms for data entry.
202 *
203 * When the input is required and uses a floating label, then the label will automatically contain
204 * an asterisk (`*`).<br/>
205 * This behavior can be disabled by using the `md-no-asterisk` attribute.
206 *
207 * @param {number=} md-maxlength The maximum number of characters allowed in this input. If this is
208 * specified, a character counter will be shown underneath the input.<br/><br/>
209 * The purpose of **`md-maxlength`** is exactly to show the max length counter text. If you don't
210 * want the counter text and only need "plain" validation, you can use the "simple" `ng-maxlength`
211 * or maxlength attributes.<br/><br/>
212 * **Note:** Only valid for text/string inputs (not numeric).
213 *
214 * @param {string=} aria-label Aria-label is required when no label is present. A warning message
215 * will be logged in the console if not present.
216 * @param {string=} placeholder An alternative approach to using aria-label when the label is not
217 * PRESENT. The placeholder text is copied to the aria-label attribute.
218 * @param md-no-autogrow {boolean=} When present, textareas will not grow automatically.
219 * @param md-no-asterisk {boolean=} When present, an asterisk will not be appended to the inputs floating label
220 * @param md-no-resize {boolean=} Disables the textarea resize handle.
221 * @param {number=} max-rows The maximum amount of rows for a textarea.
222 * @param md-detect-hidden {boolean=} When present, textareas will be sized properly when they are
223 * revealed after being hidden. This is off by default for performance reasons because it
224 * guarantees a reflow every digest cycle.
225 *
226 * @usage
227 * <hljs lang="html">
228 * <md-input-container>
229 * <label>Color</label>
230 * <input type="text" ng-model="color" required md-maxlength="10">
231 * </md-input-container>
232 * </hljs>
233 *
234 * <h3>With Errors</h3>
235 *
236 * `md-input-container` also supports errors using the standard `ng-messages` directives and
237 * animates the messages when they become visible using from the `ngEnter`/`ngLeave` events or
238 * the `ngShow`/`ngHide` events.
239 *
240 * By default, the messages will be hidden until the input is in an error state. This is based off
241 * of the `md-is-error` expression of the `md-input-container`. This gives the user a chance to
242 * fill out the form before the errors become visible.
243 *
244 * <hljs lang="html">
245 * <form name="colorForm">
246 * <md-input-container>
247 * <label>Favorite Color</label>
248 * <input name="favoriteColor" ng-model="favoriteColor" required>
249 * <div ng-messages="colorForm.favoriteColor.$error">
250 * <div ng-message="required">This is required!</div>
251 * </div>
252 * </md-input-container>
253 * </form>
254 * </hljs>
255 *
256 * We automatically disable this auto-hiding functionality if you provide any of the following
257 * visibility directives on the `ng-messages` container:
258 *
259 * - `ng-if`
260 * - `ng-show`/`ng-hide`
261 * - `ng-switch-when`/`ng-switch-default`
262 *
263 * You can also disable this functionality manually by adding the `md-auto-hide="false"` expression
264 * to the `ng-messages` container. This may be helpful if you always want to see the error messages
265 * or if you are building your own visibilty directive.
266 *
267 * _<b>Note:</b> The `md-auto-hide` attribute is a static string that is only checked upon
268 * initialization of the `ng-messages` directive to see if it equals the string `false`._
269 *
270 * <hljs lang="html">
271 * <form name="userForm">
272 * <md-input-container>
273 * <label>Last Name</label>
274 * <input name="lastName" ng-model="lastName" required md-maxlength="10" minlength="4">
275 * <div ng-messages="userForm.lastName.$error" ng-show="userForm.lastName.$dirty">
276 * <div ng-message="required">This is required!</div>
277 * <div ng-message="md-maxlength">That's too long!</div>
278 * <div ng-message="minlength">That's too short!</div>
279 * </div>
280 * </md-input-container>
281 * <md-input-container>
282 * <label>Biography</label>
283 * <textarea name="bio" ng-model="biography" required md-maxlength="150"></textarea>
284 * <div ng-messages="userForm.bio.$error" ng-show="userForm.bio.$dirty">
285 * <div ng-message="required">This is required!</div>
286 * <div ng-message="md-maxlength">That's too long!</div>
287 * </div>
288 * </md-input-container>
289 * <md-input-container>
290 * <input aria-label='title' ng-model='title'>
291 * </md-input-container>
292 * <md-input-container>
293 * <input placeholder='title' ng-model='title'>
294 * </md-input-container>
295 * </form>
296 * </hljs>
297 *
298 * <h3>Notes</h3>
299 *
300 * - Requires [ngMessages](https://docs.angularjs.org/api/ngMessages).
301 * - Behaves like the [AngularJS input directive](https://docs.angularjs.org/api/ng/directive/input).
302 *
303 * The `md-input` and `md-input-container` directives use very specific positioning to achieve the
304 * error animation effects. Therefore, it is *not* advised to use the Layout system inside of the
305 * `<md-input-container>` tags. Instead, use relative or absolute positioning.
306 *
307 *
308 * <h3>Textarea directive</h3>
309 * The `textarea` element within a `md-input-container` has the following specific behavior:
310 * - By default the `textarea` grows as the user types. This can be disabled via the `md-no-autogrow`
311 * attribute.
312 * - If a `textarea` has the `rows` attribute, it will treat the `rows` as the minimum height and will
313 * continue growing as the user types. For example a textarea with `rows="3"` will be 3 lines of text
314 * high initially. If no rows are specified, the directive defaults to 1.
315 * - The textarea's height gets set on initialization, as well as while the user is typing. In certain situations
316 * (e.g. while animating) the directive might have been initialized, before the element got it's final height. In
317 * those cases, you can trigger a resize manually by broadcasting a `md-resize-textarea` event on the scope.
318 * - If you wan't a `textarea` to stop growing at a certain point, you can specify the `max-rows` attribute.
319 * - The textarea's bottom border acts as a handle which users can drag, in order to resize the element vertically.
320 * Once the user has resized a `textarea`, the autogrowing functionality becomes disabled. If you don't want a
321 * `textarea` to be resizeable by the user, you can add the `md-no-resize` attribute.
322 */
323
324function inputTextareaDirective($mdUtil, $window, $mdAria, $timeout, $mdGesture) {
325 return {
326 restrict: 'E',
327 require: ['^?mdInputContainer', '?ngModel', '?^form'],
328 link: postLink
329 };
330
331 function postLink(scope, element, attr, ctrls) {
332
333 var containerCtrl = ctrls[0];
334 var hasNgModel = !!ctrls[1];
335 var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
336 var parentForm = ctrls[2];
337 var isReadonly = angular.isDefined(attr.readonly);
338 var mdNoAsterisk = $mdUtil.parseAttributeBoolean(attr.mdNoAsterisk);
339 var tagName = element[0].tagName.toLowerCase();
340
341
342 if (!containerCtrl) return;
343 if (attr.type === 'hidden') {
344 element.attr('aria-hidden', 'true');
345 return;
346 } else if (containerCtrl.input) {
347 if (containerCtrl.input[0].contains(element[0])) {
348 return;
349 } else {
350 throw new Error("<md-input-container> can only have *one* <input>, <textarea> or <md-select> child element!");
351 }
352 }
353 containerCtrl.input = element;
354
355 setupAttributeWatchers();
356
357 // Add an error spacer div after our input to provide space for the char counter and any ng-messages
358 var errorsSpacer = angular.element('<div class="md-errors-spacer">');
359 element.after(errorsSpacer);
360
361 if (!containerCtrl.label) {
362 $mdAria.expect(element, 'aria-label', attr.placeholder);
363 }
364
365 element.addClass('md-input');
366 if (!element.attr('id')) {
367 element.attr('id', 'input_' + $mdUtil.nextUid());
368 }
369
370 // This works around a Webkit issue where number inputs, placed in a flexbox, that have
371 // a `min` and `max` will collapse to about 1/3 of their proper width. Please check #7349
372 // for more info. Also note that we don't override the `step` if the user has specified it,
373 // in order to prevent some unexpected behaviour.
374 if (tagName === 'input' && attr.type === 'number' && attr.min && attr.max && !attr.step) {
375 element.attr('step', 'any');
376 } else if (tagName === 'textarea') {
377 setupTextarea();
378 }
379
380 // If the input doesn't have an ngModel, it may have a static value. For that case,
381 // we have to do one initial check to determine if the container should be in the
382 // "has a value" state.
383 if (!hasNgModel) {
384 inputCheckValue();
385 }
386
387 var isErrorGetter = containerCtrl.isErrorGetter || function() {
388 return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (parentForm && parentForm.$submitted));
389 };
390
391 scope.$watch(isErrorGetter, containerCtrl.setInvalid);
392
393 // When the developer uses the ngValue directive for the input, we have to observe the attribute, because
394 // Angular's ngValue directive is just setting the `value` attribute.
395 if (attr.ngValue) {
396 attr.$observe('value', inputCheckValue);
397 }
398
399 ngModelCtrl.$parsers.push(ngModelPipelineCheckValue);
400 ngModelCtrl.$formatters.push(ngModelPipelineCheckValue);
401
402 element.on('input', inputCheckValue);
403
404 if (!isReadonly) {
405 element
406 .on('focus', function(ev) {
407 $mdUtil.nextTick(function() {
408 containerCtrl.setFocused(true);
409 });
410 })
411 .on('blur', function(ev) {
412 $mdUtil.nextTick(function() {
413 containerCtrl.setFocused(false);
414 inputCheckValue();
415 });
416 });
417 }
418
419 scope.$on('$destroy', function() {
420 containerCtrl.setFocused(false);
421 containerCtrl.setHasValue(false);
422 containerCtrl.input = null;
423 });
424
425 /** Gets run through ngModel's pipeline and set the `has-value` class on the container. */
426 function ngModelPipelineCheckValue(arg) {
427 containerCtrl.setHasValue(!ngModelCtrl.$isEmpty(arg));
428 return arg;
429 }
430
431 function setupAttributeWatchers() {
432 if (containerCtrl.label) {
433 attr.$observe('required', function (value) {
434 // We don't need to parse the required value, it's always a boolean because of angular's
435 // required directive.
436 containerCtrl.label.toggleClass('md-required', value && !mdNoAsterisk);
437 });
438 }
439 }
440
441 function inputCheckValue() {
442 // An input's value counts if its length > 0,
443 // or if the input's validity state says it has bad input (eg string in a number input)
444 containerCtrl.setHasValue(element.val().length > 0 || (element[0].validity || {}).badInput);
445 }
446
447 function setupTextarea() {
448 var isAutogrowing = !attr.hasOwnProperty('mdNoAutogrow');
449
450 attachResizeHandle();
451
452 if (!isAutogrowing) return;
453
454 // Can't check if height was or not explicity set,
455 // so rows attribute will take precedence if present
456 var minRows = attr.hasOwnProperty('rows') ? parseInt(attr.rows) : NaN;
457 var maxRows = attr.hasOwnProperty('maxRows') ? parseInt(attr.maxRows) : NaN;
458 var scopeResizeListener = scope.$on('md-resize-textarea', growTextarea);
459 var lineHeight = null;
460 var node = element[0];
461
462 // This timeout is necessary, because the browser needs a little bit
463 // of time to calculate the `clientHeight` and `scrollHeight`.
464 $timeout(function() {
465 $mdUtil.nextTick(growTextarea);
466 }, 10, false);
467
468 // We could leverage ngModel's $parsers here, however it
469 // isn't reliable, because Angular trims the input by default,
470 // which means that growTextarea won't fire when newlines and
471 // spaces are added.
472 element.on('input', growTextarea);
473
474 // We should still use the $formatters, because they fire when
475 // the value was changed from outside the textarea.
476 if (hasNgModel) {
477 ngModelCtrl.$formatters.push(formattersListener);
478 }
479
480 if (!minRows) {
481 element.attr('rows', 1);
482 }
483
484 angular.element($window).on('resize', growTextarea);
485 scope.$on('$destroy', disableAutogrow);
486
487 function growTextarea() {
488 // temporarily disables element's flex so its height 'runs free'
489 element
490 .attr('rows', 1)
491 .css('height', 'auto')
492 .addClass('md-no-flex');
493
494 var height = getHeight();
495
496 if (!lineHeight) {
497 // offsetHeight includes padding which can throw off our value
498 var originalPadding = element[0].style.padding || '';
499 lineHeight = element.css('padding', 0).prop('offsetHeight');
500 element[0].style.padding = originalPadding;
501 }
502
503 if (minRows && lineHeight) {
504 height = Math.max(height, lineHeight * minRows);
505 }
506
507 if (maxRows && lineHeight) {
508 var maxHeight = lineHeight * maxRows;
509
510 if (maxHeight < height) {
511 element.attr('md-no-autogrow', '');
512 height = maxHeight;
513 } else {
514 element.removeAttr('md-no-autogrow');
515 }
516 }
517
518 if (lineHeight) {
519 element.attr('rows', Math.round(height / lineHeight));
520 }
521
522 element
523 .css('height', height + 'px')
524 .removeClass('md-no-flex');
525 }
526
527 function getHeight() {
528 var offsetHeight = node.offsetHeight;
529 var line = node.scrollHeight - offsetHeight;
530 return offsetHeight + Math.max(line, 0);
531 }
532
533 function formattersListener(value) {
534 $mdUtil.nextTick(growTextarea);
535 return value;
536 }
537
538 function disableAutogrow() {
539 if (!isAutogrowing) return;
540
541 isAutogrowing = false;
542 angular.element($window).off('resize', growTextarea);
543 scopeResizeListener && scopeResizeListener();
544 element
545 .attr('md-no-autogrow', '')
546 .off('input', growTextarea);
547
548 if (hasNgModel) {
549 var listenerIndex = ngModelCtrl.$formatters.indexOf(formattersListener);
550
551 if (listenerIndex > -1) {
552 ngModelCtrl.$formatters.splice(listenerIndex, 1);
553 }
554 }
555 }
556
557 function attachResizeHandle() {
558 if (attr.hasOwnProperty('mdNoResize')) return;
559
560 var handle = angular.element('<div class="md-resize-handle"></div>');
561 var isDragging = false;
562 var dragStart = null;
563 var startHeight = 0;
564 var container = containerCtrl.element;
565 var dragGestureHandler = $mdGesture.register(handle, 'drag', { horizontal: false });
566
567
568 element.wrap('<div class="md-resize-wrapper">').after(handle);
569 handle.on('mousedown', onMouseDown);
570
571 container
572 .on('$md.dragstart', onDragStart)
573 .on('$md.drag', onDrag)
574 .on('$md.dragend', onDragEnd);
575
576 scope.$on('$destroy', function() {
577 handle
578 .off('mousedown', onMouseDown)
579 .remove();
580
581 container
582 .off('$md.dragstart', onDragStart)
583 .off('$md.drag', onDrag)
584 .off('$md.dragend', onDragEnd);
585
586 dragGestureHandler();
587 handle = null;
588 container = null;
589 dragGestureHandler = null;
590 });
591
592 function onMouseDown(ev) {
593 ev.preventDefault();
594 isDragging = true;
595 dragStart = ev.clientY;
596 startHeight = parseFloat(element.css('height')) || element.prop('offsetHeight');
597 }
598
599 function onDragStart(ev) {
600 if (!isDragging) return;
601 ev.preventDefault();
602 disableAutogrow();
603 container.addClass('md-input-resized');
604 }
605
606 function onDrag(ev) {
607 if (!isDragging) return;
608
609 element.css('height', (startHeight + ev.pointer.distanceY) + 'px');
610 }
611
612 function onDragEnd(ev) {
613 if (!isDragging) return;
614 isDragging = false;
615 container.removeClass('md-input-resized');
616 }
617 }
618
619 // Attach a watcher to detect when the textarea gets shown.
620 if (attr.hasOwnProperty('mdDetectHidden')) {
621
622 var handleHiddenChange = function() {
623 var wasHidden = false;
624
625 return function() {
626 var isHidden = node.offsetHeight === 0;
627
628 if (isHidden === false && wasHidden === true) {
629 growTextarea();
630 }
631
632 wasHidden = isHidden;
633 };
634 }();
635
636 // Check every digest cycle whether the visibility of the textarea has changed.
637 // Queue up to run after the digest cycle is complete.
638 scope.$watch(function() {
639 $mdUtil.nextTick(handleHiddenChange, false);
640 return true;
641 });
642 }
643 }
644 }
645}
646
647function mdMaxlengthDirective($animate, $mdUtil) {
648 return {
649 restrict: 'A',
650 require: ['ngModel', '^mdInputContainer'],
651 link: postLink
652 };
653
654 function postLink(scope, element, attr, ctrls) {
655 var maxlength;
656 var ngModelCtrl = ctrls[0];
657 var containerCtrl = ctrls[1];
658 var charCountEl, errorsSpacer;
659
660 // Wait until the next tick to ensure that the input has setup the errors spacer where we will
661 // append our counter
662 $mdUtil.nextTick(function() {
663 errorsSpacer = angular.element(containerCtrl.element[0].querySelector('.md-errors-spacer'));
664 charCountEl = angular.element('<div class="md-char-counter">');
665
666 // Append our character counter inside the errors spacer
667 errorsSpacer.append(charCountEl);
668
669 // Stop model from trimming. This makes it so whitespace
670 // over the maxlength still counts as invalid.
671 attr.$set('ngTrim', 'false');
672
673 scope.$watch(attr.mdMaxlength, function(value) {
674 maxlength = value;
675 if (angular.isNumber(value) && value > 0) {
676 if (!charCountEl.parent().length) {
677 $animate.enter(charCountEl, errorsSpacer);
678 }
679 renderCharCount();
680 } else {
681 $animate.leave(charCountEl);
682 }
683 });
684
685 ngModelCtrl.$validators['md-maxlength'] = function(modelValue, viewValue) {
686 if (!angular.isNumber(maxlength) || maxlength < 0) {
687 return true;
688 }
689
690 // We always update the char count, when the modelValue has changed.
691 // Using the $validators for triggering the update works very well.
692 renderCharCount();
693
694 return ( modelValue || element.val() || viewValue || '' ).length <= maxlength;
695 };
696 });
697
698 function renderCharCount(value) {
699 // If we have not been appended to the body yet; do not render
700 if (!charCountEl.parent) {
701 return value;
702 }
703
704 // Force the value into a string since it may be a number,
705 // which does not have a length property.
706 charCountEl.text(String(element.val() || value || '').length + ' / ' + maxlength);
707 return value;
708 }
709 }
710}
711
712function placeholderDirective($compile) {
713 return {
714 restrict: 'A',
715 require: '^^?mdInputContainer',
716 priority: 200,
717 link: {
718 // Note that we need to do this in the pre-link, as opposed to the post link, if we want to
719 // support data bindings in the placeholder. This is necessary, because we have a case where
720 // we transfer the placeholder value to the `<label>` and we remove it from the original `<input>`.
721 // If we did this in the post-link, Angular would have set up the observers already and would be
722 // re-adding the attribute, even though we removed it from the element.
723 pre: preLink
724 }
725 };
726
727 function preLink(scope, element, attr, inputContainer) {
728 // If there is no input container, just return
729 if (!inputContainer) return;
730
731 var label = inputContainer.element.find('label');
732 var noFloat = inputContainer.element.attr('md-no-float');
733
734 // If we have a label, or they specify the md-no-float attribute, just return
735 if ((label && label.length) || noFloat === '' || scope.$eval(noFloat)) {
736 // Add a placeholder class so we can target it in the CSS
737 inputContainer.setHasPlaceholder(true);
738 return;
739 }
740
741 // md-select handles placeholders on it's own
742 if (element[0].nodeName != 'MD-SELECT') {
743 // Move the placeholder expression to the label
744 var newLabel = angular.element('<label ng-click="delegateClick()" tabindex="-1">' + attr.placeholder + '</label>');
745
746 // Note that we unset it via `attr`, in order to get Angular
747 // to remove any observers that it might have set up. Otherwise
748 // the attribute will be added on the next digest.
749 attr.$set('placeholder', null);
750
751 // We need to compile the label manually in case it has any bindings.
752 // A gotcha here is that we first add the element to the DOM and we compile
753 // it later. This is necessary, because if we compile the element beforehand,
754 // it won't be able to find the `mdInputContainer` controller.
755 inputContainer.element
756 .addClass('md-icon-float')
757 .prepend(newLabel);
758
759 $compile(newLabel)(scope);
760 }
761 }
762}
763
764/**
765 * @ngdoc directive
766 * @name mdSelectOnFocus
767 * @module material.components.input
768 *
769 * @restrict A
770 *
771 * @description
772 * The `md-select-on-focus` directive allows you to automatically select the element's input text on focus.
773 *
774 * <h3>Notes</h3>
775 * - The use of `md-select-on-focus` is restricted to `<input>` and `<textarea>` elements.
776 *
777 * @usage
778 * <h3>Using with an Input</h3>
779 * <hljs lang="html">
780 *
781 * <md-input-container>
782 * <label>Auto Select</label>
783 * <input type="text" md-select-on-focus>
784 * </md-input-container>
785 * </hljs>
786 *
787 * <h3>Using with a Textarea</h3>
788 * <hljs lang="html">
789 *
790 * <md-input-container>
791 * <label>Auto Select</label>
792 * <textarea md-select-on-focus>This text will be selected on focus.</textarea>
793 * </md-input-container>
794 *
795 * </hljs>
796 */
797function mdSelectOnFocusDirective($timeout) {
798
799 return {
800 restrict: 'A',
801 link: postLink
802 };
803
804 function postLink(scope, element, attr) {
805 if (element[0].nodeName !== 'INPUT' && element[0].nodeName !== "TEXTAREA") return;
806
807 var preventMouseUp = false;
808
809 element
810 .on('focus', onFocus)
811 .on('mouseup', onMouseUp);
812
813 scope.$on('$destroy', function() {
814 element
815 .off('focus', onFocus)
816 .off('mouseup', onMouseUp);
817 });
818
819 function onFocus() {
820 preventMouseUp = true;
821
822 $timeout(function() {
823 // Use HTMLInputElement#select to fix firefox select issues.
824 // The debounce is here for Edge's sake, otherwise the selection doesn't work.
825 element[0].select();
826
827 // This should be reset from inside the `focus`, because the event might
828 // have originated from something different than a click, e.g. a keyboard event.
829 preventMouseUp = false;
830 }, 1, false);
831 }
832
833 // Prevents the default action of the first `mouseup` after a focus.
834 // This is necessary, because browsers fire a `mouseup` right after the element
835 // has been focused. In some browsers (Firefox in particular) this can clear the
836 // selection. There are examples of the problem in issue #7487.
837 function onMouseUp(event) {
838 if (preventMouseUp) {
839 event.preventDefault();
840 }
841 }
842 }
843}
844
845var visibilityDirectives = ['ngIf', 'ngShow', 'ngHide', 'ngSwitchWhen', 'ngSwitchDefault'];
846function ngMessagesDirective() {
847 return {
848 restrict: 'EA',
849 link: postLink,
850
851 // This is optional because we don't want target *all* ngMessage instances, just those inside of
852 // mdInputContainer.
853 require: '^^?mdInputContainer'
854 };
855
856 function postLink(scope, element, attrs, inputContainer) {
857 // If we are not a child of an input container, don't do anything
858 if (!inputContainer) return;
859
860 // Add our animation class
861 element.toggleClass('md-input-messages-animation', true);
862
863 // Add our md-auto-hide class to automatically hide/show messages when container is invalid
864 element.toggleClass('md-auto-hide', true);
865
866 // If we see some known visibility directives, remove the md-auto-hide class
867 if (attrs.mdAutoHide == 'false' || hasVisibiltyDirective(attrs)) {
868 element.toggleClass('md-auto-hide', false);
869 }
870 }
871
872 function hasVisibiltyDirective(attrs) {
873 return visibilityDirectives.some(function(attr) {
874 return attrs[attr];
875 });
876 }
877}
878
879function ngMessageDirective($mdUtil) {
880 return {
881 restrict: 'EA',
882 compile: compile,
883 priority: 100
884 };
885
886 function compile(tElement) {
887 if (!isInsideInputContainer(tElement)) {
888
889 // When the current element is inside of a document fragment, then we need to check for an input-container
890 // in the postLink, because the element will be later added to the DOM and is currently just in a temporary
891 // fragment, which causes the input-container check to fail.
892 if (isInsideFragment()) {
893 return function (scope, element) {
894 if (isInsideInputContainer(element)) {
895 // Inside of the postLink function, a ngMessage directive will be a comment element, because it's
896 // currently hidden. To access the shown element, we need to use the element from the compile function.
897 initMessageElement(tElement);
898 }
899 };
900 }
901 } else {
902 initMessageElement(tElement);
903 }
904
905 function isInsideFragment() {
906 var nextNode = tElement[0];
907 while (nextNode = nextNode.parentNode) {
908 if (nextNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
909 return true;
910 }
911 }
912 return false;
913 }
914
915 function isInsideInputContainer(element) {
916 return !!$mdUtil.getClosest(element, "md-input-container");
917 }
918
919 function initMessageElement(element) {
920 // Add our animation class
921 element.toggleClass('md-input-message-animation', true);
922 }
923 }
924}
925
926var $$AnimateRunner, $animateCss, $mdUtil, $log;
927
928function mdInputInvalidMessagesAnimation($$AnimateRunner, $animateCss, $mdUtil, $log) {
929 saveSharedServices($$AnimateRunner, $animateCss, $mdUtil, $log);
930
931 return {
932 addClass: function(element, className, done) {
933 showInputMessages(element, done);
934 }
935
936 // NOTE: We do not need the removeClass method, because the message ng-leave animation will fire
937 };
938}
939
940function ngMessagesAnimation($$AnimateRunner, $animateCss, $mdUtil, $log) {
941 saveSharedServices($$AnimateRunner, $animateCss, $mdUtil, $log);
942
943 return {
944 enter: function(element, done) {
945 showInputMessages(element, done);
946 },
947
948 leave: function(element, done) {
949 hideInputMessages(element, done);
950 },
951
952 addClass: function(element, className, done) {
953 if (className == "ng-hide") {
954 hideInputMessages(element, done);
955 } else {
956 done();
957 }
958 },
959
960 removeClass: function(element, className, done) {
961 if (className == "ng-hide") {
962 showInputMessages(element, done);
963 } else {
964 done();
965 }
966 }
967 };
968}
969
970function ngMessageAnimation($$AnimateRunner, $animateCss, $mdUtil, $log) {
971 saveSharedServices($$AnimateRunner, $animateCss, $mdUtil, $log);
972
973 return {
974 enter: function(element, done) {
975 var animator = showMessage(element);
976
977 animator.start().done(done);
978 },
979
980 leave: function(element, done) {
981 var animator = hideMessage(element);
982
983 animator.start().done(done);
984 }
985 };
986}
987
988function showInputMessages(element, done) {
989 var animators = [], animator;
990 var messages = getMessagesElement(element);
991 var children = messages.children();
992
993 if (messages.length == 0 || children.length == 0) {
994 $log.warn('mdInput messages show animation called on invalid messages element: ', element);
995 done();
996 return;
997 }
998
999 angular.forEach(children, function(child) {
1000 animator = showMessage(angular.element(child));
1001
1002 animators.push(animator.start());
1003 });
1004
1005 $$AnimateRunner.all(animators, done);
1006}
1007
1008function hideInputMessages(element, done) {
1009 var animators = [], animator;
1010 var messages = getMessagesElement(element);
1011 var children = messages.children();
1012
1013 if (messages.length == 0 || children.length == 0) {
1014 $log.warn('mdInput messages hide animation called on invalid messages element: ', element);
1015 done();
1016 return;
1017 }
1018
1019 angular.forEach(children, function(child) {
1020 animator = hideMessage(angular.element(child));
1021
1022 animators.push(animator.start());
1023 });
1024
1025 $$AnimateRunner.all(animators, done);
1026}
1027
1028function showMessage(element) {
1029 var height = parseInt(window.getComputedStyle(element[0]).height);
1030 var topMargin = parseInt(window.getComputedStyle(element[0]).marginTop);
1031
1032 var messages = getMessagesElement(element);
1033 var container = getInputElement(element);
1034
1035 // Check to see if the message is already visible so we can skip
1036 var alreadyVisible = (topMargin > -height);
1037
1038 // If we have the md-auto-hide class, the md-input-invalid animation will fire, so we can skip
1039 if (alreadyVisible || (messages.hasClass('md-auto-hide') && !container.hasClass('md-input-invalid'))) {
1040 return $animateCss(element, {});
1041 }
1042
1043 return $animateCss(element, {
1044 event: 'enter',
1045 structural: true,
1046 from: {"opacity": 0, "margin-top": -height + "px"},
1047 to: {"opacity": 1, "margin-top": "0"},
1048 duration: 0.3
1049 });
1050}
1051
1052function hideMessage(element) {
1053 var height = element[0].offsetHeight;
1054 var styles = window.getComputedStyle(element[0]);
1055
1056 // If we are already hidden, just return an empty animation
1057 if (parseInt(styles.opacity) === 0) {
1058 return $animateCss(element, {});
1059 }
1060
1061 // Otherwise, animate
1062 return $animateCss(element, {
1063 event: 'leave',
1064 structural: true,
1065 from: {"opacity": 1, "margin-top": 0},
1066 to: {"opacity": 0, "margin-top": -height + "px"},
1067 duration: 0.3
1068 });
1069}
1070
1071function getInputElement(element) {
1072 var inputContainer = element.controller('mdInputContainer');
1073
1074 return inputContainer.element;
1075}
1076
1077function getMessagesElement(element) {
1078 // If we ARE the messages element, just return ourself
1079 if (element.hasClass('md-input-messages-animation')) {
1080 return element;
1081 }
1082
1083 // If we are a ng-message element, we need to traverse up the DOM tree
1084 if (element.hasClass('md-input-message-animation')) {
1085 return angular.element($mdUtil.getClosest(element, function(node) {
1086 return node.classList.contains('md-input-messages-animation');
1087 }));
1088 }
1089
1090 // Otherwise, we can traverse down
1091 return angular.element(element[0].querySelector('.md-input-messages-animation'));
1092}
1093
1094function saveSharedServices(_$$AnimateRunner_, _$animateCss_, _$mdUtil_, _$log_) {
1095 $$AnimateRunner = _$$AnimateRunner_;
1096 $animateCss = _$animateCss_;
1097 $mdUtil = _$mdUtil_;
1098 $log = _$log_;
1099}
1100
1101})(window, window.angular);