diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index bf3111e8ffcd..abe7402c6e37 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -2349,62 +2349,92 @@ var minlengthDirective = function() { * @name ngList * * @description - * Text input that converts between a delimited string and an array of strings. The delimiter - * can be a fixed string (by default a comma) or a regular expression. + * Text input that converts between a delimited string and an array of strings. The default + * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom + * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`. * - * @element input - * @param {string=} ngList optional delimiter that should be used to split the value. If - * specified in form `/something/` then the value will be converted into a regular expression. + * The behaviour of the directive is affected by the use of the `ngTrim` attribute. + * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each + * list item is respected. This implies that the user of the directive is responsible for + * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a + * tab or newline character. + * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected + * when joining the list items back together) and whitespace around each list item is stripped + * before it is added to the model. * - * @example - - - -
- List: - - Required! -
- names = {{names}}
- myForm.namesInput.$valid = {{myForm.namesInput.$valid}}
- myForm.namesInput.$error = {{myForm.namesInput.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
-
-
- - var listInput = element(by.model('names')); - var names = element(by.binding('{{names}}')); - var valid = element(by.binding('myForm.namesInput.$valid')); - var error = element(by.css('span.error')); - - it('should initialize to model', function() { - expect(names.getText()).toContain('["igor","misko","vojta"]'); - expect(valid.getText()).toContain('true'); - expect(error.getCssValue('display')).toBe('none'); - }); - - it('should be invalid if empty', function() { - listInput.clear(); - listInput.sendKeys(''); - - expect(names.getText()).toContain(''); - expect(valid.getText()).toContain('false'); - expect(error.getCssValue('display')).not.toBe('none'); }); - -
+ * ### Example with Validation + * + * + * + * angular.module('listExample', []) + * .controller('ExampleController', ['$scope', function($scope) { + * $scope.names = ['morpheus', 'neo', 'trinity']; + * }]); + * + * + *
+ * List: + * + * Required! + *
+ * names = {{names}}
+ * myForm.namesInput.$valid = {{myForm.namesInput.$valid}}
+ * myForm.namesInput.$error = {{myForm.namesInput.$error}}
+ * myForm.$valid = {{myForm.$valid}}
+ * myForm.$error.required = {{!!myForm.$error.required}}
+ *
+ *
+ * + * var listInput = element(by.model('names')); + * var names = element(by.binding('{{names}}')); + * var valid = element(by.binding('myForm.namesInput.$valid')); + * var error = element(by.css('span.error')); + * + * it('should initialize to model', function() { + * expect(names.getText()).toContain('["morpheus","neo","trinity"]'); + * expect(valid.getText()).toContain('true'); + * expect(error.getCssValue('display')).toBe('none'); + * }); + * + * it('should be invalid if empty', function() { + * listInput.clear(); + * listInput.sendKeys(''); + * + * expect(names.getText()).toContain(''); + * expect(valid.getText()).toContain('false'); + * expect(error.getCssValue('display')).not.toBe('none'); + * }); + * + *
+ * + * ### Example - splitting on whitespace + * + * + * + *
{{ list | json }}
+ *
+ * + * it("should split the text by newlines", function() { + * var listInput = element(by.model('list')); + * var output = element(by.binding('{{ list | json }}')); + * listInput.sendKeys('abc\ndef\nghi'); + * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); + * }); + * + *
+ * + * @element input + * @param {string=} ngList optional delimiter that should be used to split the value. */ var ngListDirective = function() { return { require: 'ngModel', link: function(scope, element, attr, ctrl) { - var match = /\/(.*)\//.exec(attr.ngList), - separator = match && new RegExp(match[1]) || attr.ngList || ','; + // We want to control whitespace trimming so we use this convoluted approach + // to access the ngList attribute, which doesn't pre-trim the attribute + var ngList = element.attr(attr.$attr.ngList) || ', '; + var trimValues = attr.ngTrim !== 'false'; + var separator = trimValues ? trim(ngList) : ngList; var parse = function(viewValue) { // If the viewValue is invalid (say required but empty) it will be `undefined` @@ -2414,7 +2444,7 @@ var ngListDirective = function() { if (viewValue) { forEach(viewValue.split(separator), function(value) { - if (value) list.push(trim(value)); + if (value) list.push(trimValues ? trim(value) : value); }); } @@ -2424,7 +2454,7 @@ var ngListDirective = function() { ctrl.$parsers.push(parse); ctrl.$formatters.push(function(value) { if (isArray(value)) { - return value.join(', '); + return value.join(ngList); } return undefined; diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index 5c3784f50507..bf6bd70d2ecc 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -2633,7 +2633,7 @@ describe('input', function() { it("should not clobber text if model changes due to itself", function() { // When the user types 'a,b' the 'a,' stage parses to ['a'] but if the // $parseModel function runs it will change to 'a', in essence preventing - // the user from ever typying ','. + // the user from ever typing ','. compileInput(''); changeInputValueTo('a '); @@ -2671,26 +2671,69 @@ describe('input', function() { expect(inputElm).toBeValid(); }); + describe('with a custom separator', function() { + it('should split on the custom separator', function() { + compileInput(''); - it('should allow custom separator', function() { - compileInput(''); + changeInputValueTo('a,a'); + expect(scope.list).toEqual(['a,a']); - changeInputValueTo('a,a'); - expect(scope.list).toEqual(['a,a']); + changeInputValueTo('a:b'); + expect(scope.list).toEqual(['a', 'b']); + }); - changeInputValueTo('a:b'); - expect(scope.list).toEqual(['a', 'b']); + + it("should join the list back together with the custom separator", function() { + compileInput(''); + + scope.$apply(function() { + scope.list = ['x', 'y', 'z']; + }); + expect(inputElm.val()).toBe('x : y : z'); + }); }); + describe('(with ngTrim undefined or true)', function() { - it('should allow regexp as a separator', function() { - compileInput(''); + it('should ignore separator whitespace when splitting', function() { + compileInput(''); - changeInputValueTo('a,b'); - expect(scope.list).toEqual(['a', 'b']); + changeInputValueTo('a|b'); + expect(scope.list).toEqual(['a', 'b']); + }); + + it('should trim whitespace from each list item', function() { + compileInput(''); - changeInputValueTo('a,b: c'); - expect(scope.list).toEqual(['a', 'b', 'c']); + changeInputValueTo('a | b'); + expect(scope.list).toEqual(['a', 'b']); + }); + }); + + describe('(with ngTrim set to false)', function() { + + it('should use separator whitespace when splitting', function() { + compileInput(''); + + changeInputValueTo('a|b'); + expect(scope.list).toEqual(['a|b']); + + changeInputValueTo('a | b'); + expect(scope.list).toEqual(['a','b']); + + }); + + it("should not trim whitespace from each list item", function() { + compileInput(''); + changeInputValueTo('a | b'); + expect(scope.list).toEqual(['a ',' b']); + }); + + it("should support splitting on newlines", function() { + compileInput('