Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/autocomplete/js/autocompleteDirective.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ angular
* When the dropdown doesn't fit into the viewport, the dropdown will shrink
* as much as possible.
* @param {string=} md-dropdown-position Overrides the default dropdown position. Options: `top`, `bottom`.
* @param {string=} ng-trim If set to false, the search text will be not trimmed automatically.
* @param {boolean=} ng-trim If set to false, the search text will be not trimmed automatically.
* Defaults to true.
* @param {string=} ng-pattern Adds the pattern validator to the ngModel of the search text.
* See the [ngPattern Directive](https://docs.angularjs.org/api/ng/directive/ngPattern)
Expand Down
58 changes: 44 additions & 14 deletions src/components/input/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ function labelDirective() {
* The purpose of **`md-maxlength`** is exactly to show the max length counter text. If you don't
* want the counter text and only need "plain" validation, you can use the "simple" `ng-maxlength`
* or maxlength attributes.<br/><br/>
* **Note:** Only valid for text/string inputs (not numeric).
*
* @param {boolean=} ng-trim If set to false, the input text will be not trimmed automatically.
* Defaults to true.
* @param {string=} aria-label Aria-label is required when no label is present. A warning message
* will be logged in the console if not present.
* @param {string=} placeholder An alternative approach to using aria-label when the label is not
Expand Down Expand Up @@ -639,7 +639,8 @@ function mdMaxlengthDirective($animate, $mdUtil) {
var ngModelCtrl = ctrls[0];
var containerCtrl = ctrls[1];
var charCountEl, errorsSpacer;

var ngTrim = angular.isDefined(attr.ngTrim) ? $mdUtil.parseAttributeBoolean(attr.ngTrim) : true;
var isPasswordInput = attr.type === 'password';

ngModelCtrl.$validators['md-maxlength'] = function(modelValue, viewValue) {
if (!angular.isNumber(maxlength) || maxlength < 0) {
Expand All @@ -650,7 +651,24 @@ function mdMaxlengthDirective($animate, $mdUtil) {
// Using the $validators for triggering the update works very well.
renderCharCount();

return ( modelValue || element.val() || viewValue || '' ).length <= maxlength;
var elementVal = element.val() || viewValue;
if (elementVal === undefined || elementVal === null) {
elementVal = '';
}
elementVal = ngTrim && !isPasswordInput && angular.isString(elementVal) ? elementVal.trim() : elementVal;
// Force the value into a string since it may be a number,
// which does not have a length property.
return String(elementVal).length <= maxlength;
};

/**
* Override the default NgModelController $isEmpty check to take ng-trim, password inputs,
* etc. into account.
* @param value {*} the input's value
* @returns {boolean} true if the input's value should be considered empty, false otherwise
*/
ngModelCtrl.$isEmpty = function(value) {
return calculateInputValueLength(value) === 0;
};

// Wait until the next tick to ensure that the input has setup the errors spacer where we will
Expand All @@ -662,10 +680,10 @@ function mdMaxlengthDirective($animate, $mdUtil) {
// Append our character counter inside the errors spacer
errorsSpacer.append(charCountEl);

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

attr.$observe('ngTrim', function (value) {
ngTrim = angular.isDefined(value) ? $mdUtil.parseAttributeBoolean(value) : true;
});
scope.$watch(attr.mdMaxlength, function(value) {
maxlength = value;
if (angular.isNumber(value) && value > 0) {
Expand All @@ -679,16 +697,28 @@ function mdMaxlengthDirective($animate, $mdUtil) {
});
});

function renderCharCount(value) {
// If we have not been initialized or appended to the body yet; do not render
if (!charCountEl || !charCountEl.parent) {
return value;
/**
* Calculate the input value's length after coercing it to a string
* and trimming it if appropriate.
* @param value {*} the input's value
* @returns {number} calculated length of the input's value
*/
function calculateInputValueLength(value) {
value = ngTrim && !isPasswordInput && angular.isString(value) ? value.trim() : value;
if (value === undefined || value === null) {
value = '';
}
return String(value).length;
}

function renderCharCount() {
// If we have not been initialized or appended to the body yet; do not render.
if (!charCountEl || !charCountEl.parent()) {
return;
}
// Force the value into a string since it may be a number,
// which does not have a length property.
charCountEl.text(String(element.val() || value || '').length + ' / ' + maxlength);
return value;
charCountEl.text(calculateInputValueLength(element.val()) + ' / ' + maxlength);
}
}
}
Expand Down
74 changes: 74 additions & 0 deletions src/components/input/input.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,80 @@ describe('md-input-container directive', function() {
expect(pageScope.form.foo.$error['md-maxlength']).toBeFalsy();
expect(getCharCounter(el).length).toBe(0);
});

it('should not accept spaces for required inputs by default', function() {
var el = $compile(
'<form name="form">' +
' <md-input-container>' +
' <input md-maxlength="max" ng-model="foo" name="foo" required>' +
' </md-input-container>' +
'</form>')(pageScope);
var input = el.find('input');

pageScope.$apply('foo = ""');
pageScope.$apply('max = 1');

// Flush any pending $mdUtil.nextTick calls
$timeout.flush();

expect(input.hasClass('ng-invalid')).toBe(true);
expect(input.hasClass('ng-invalid-required')).toBe(true);
expect(pageScope.form.foo.$error['md-maxlength']).toBeFalsy();

pageScope.$apply('foo = " "');
expect(input.hasClass('ng-invalid')).toBe(true);
expect(input.hasClass('ng-invalid-required')).toBe(true);
expect(pageScope.form.foo.$error['required']).toBeTruthy();
expect(pageScope.form.foo.$error['md-maxlength']).toBeFalsy();
});

it('should not trim spaces for required password inputs', function() {
var el = $compile(
'<form name="form">' +
' <md-input-container>' +
' <input md-maxlength="max" ng-model="foo" name="foo" type="password" required>' +
' </md-input-container>' +
'</form>')(pageScope);
var input = el.find('input');

pageScope.$apply('foo = ""');
pageScope.$apply('max = 1');

// Flush any pending $mdUtil.nextTick calls
$timeout.flush();

expect(input.hasClass('ng-invalid')).toBe(true);
expect(input.hasClass('ng-invalid-required')).toBe(true);
expect(pageScope.form.foo.$error['md-maxlength']).toBeFalsy();

pageScope.$apply('foo = " "');
expect(input.hasClass('ng-invalid')).toBe(true);
expect(input.hasClass('ng-invalid-required')).toBe(false);
expect(pageScope.form.foo.$error['required']).toBeFalsy();
expect(pageScope.form.foo.$error['md-maxlength']).toBeTruthy();
});

it('should respect ng-trim="false"', function() {
var el = $compile(
'<form name="form">' +
' <md-input-container>' +
' <input md-maxlength="max" ng-model="foo" name="foo" ng-trim="false" required>' +
' </md-input-container>' +
'</form>')(pageScope);

pageScope.$apply('foo = ""');
pageScope.$apply('max = 1');

// Flush any pending $mdUtil.nextTick calls
$timeout.flush();

expect(pageScope.form.foo.$error['required']).toBeTruthy();
expect(pageScope.form.foo.$error['md-maxlength']).toBeFalsy();

pageScope.$apply('foo = " "');
expect(pageScope.form.foo.$error['required']).toBeFalsy();
expect(pageScope.form.foo.$error['md-maxlength']).toBeTruthy();
});
});

it('should not add the md-input-has-placeholder class when the placeholder is transformed into a label', inject(function($rootScope, $compile) {
Expand Down