From 4be24e32a2cab41e757315760c1e3379d37369f1 Mon Sep 17 00:00:00 2001 From: Steve Purcell Date: Wed, 9 Oct 2013 10:42:54 +0100 Subject: [PATCH 1/3] fix(ngList): use custom separators for re-joining list items The separator string used to split the view value into a list for the model value is now used to join the list items back together again for the view value. BREAKING CHANGE: The `ngList` directive no longer supports splitting the view value via a regular expression. We need to be able to re-join list items back together and doing this when you can split with regular expressions can lead to inconsistent behaviour and would be much more complex to support. If your application relies upon ngList splitting with a regular expression then you should either try to convert the separator to a simple string or you can implement your own version of this directive for you application. Closes #4008 Closes #2561 Closes #4344 --- src/ng/directive/input.js | 10 ++++------ test/ng/directive/inputSpec.js | 17 +++++++++-------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index bf3111e8ffcd..c74e523cc7cb 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -2353,8 +2353,7 @@ var minlengthDirective = function() { * can be a fixed string (by default a comma) or a regular expression. * * @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. + * @param {string=} ngList optional delimiter that should be used to split the value. * * @example @@ -2403,8 +2402,7 @@ 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 || ','; + var separator = attr.ngList || ', '; var parse = function(viewValue) { // If the viewValue is invalid (say required but empty) it will be `undefined` @@ -2413,7 +2411,7 @@ var ngListDirective = function() { var list = []; if (viewValue) { - forEach(viewValue.split(separator), function(value) { + forEach(viewValue.split(trim(separator)), function(value) { if (value) list.push(trim(value)); }); } @@ -2424,7 +2422,7 @@ var ngListDirective = function() { ctrl.$parsers.push(parse); ctrl.$formatters.push(function(value) { if (isArray(value)) { - return value.join(', '); + return value.join(separator); } return undefined; diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index 5c3784f50507..f08674d15ad2 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 '); @@ -2675,6 +2675,11 @@ describe('input', function() { it('should allow custom separator', function() { compileInput(''); + scope.$apply(function() { + scope.list = ['x', 'y', 'z']; + }); + expect(inputElm.val()).toBe('x:y:z'); + changeInputValueTo('a,a'); expect(scope.list).toEqual(['a,a']); @@ -2682,15 +2687,11 @@ describe('input', function() { expect(scope.list).toEqual(['a', 'b']); }); + it('should ignore separator whitespace when splitting', function() { + compileInput(''); - it('should allow regexp as a separator', function() { - compileInput(''); - - changeInputValueTo('a,b'); + changeInputValueTo('a|b'); expect(scope.list).toEqual(['a', 'b']); - - changeInputValueTo('a,b: c'); - expect(scope.list).toEqual(['a', 'b', 'c']); }); }); From 3355a17bf2d0a073d815d2d3f09c97cb285f101c Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 16 Jul 2014 14:13:06 +0100 Subject: [PATCH 2/3] feat(ngList): use ngTrim to manage whitespace handling when splitting With the removal of regular expression support `ngList` no longer supported splitting on newlines (and other pure whitespace splitters). This change allows the application developer to specify whether whitespace should be respected or trimmed by using the `ngTrim` attribute. This also makes `ngList` consistent with the standard use of `ngTrim` in input directives in general. Related To: #4344 --- src/ng/directive/input.js | 122 ++++++++++++++++++++------------- test/ng/directive/inputSpec.js | 68 ++++++++++++++---- 2 files changed, 128 insertions(+), 62 deletions(-) diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index c74e523cc7cb..6ed1bb4c5e70 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -2349,60 +2349,84 @@ 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=" | "`. + * + * 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 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 }}
+ *
+ *
* * @element input * @param {string=} ngList optional delimiter that should be used to split the value. - * - * @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'); }); - -
*/ var ngListDirective = function() { return { require: 'ngModel', link: function(scope, element, attr, ctrl) { - var separator = 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` @@ -2411,8 +2435,8 @@ var ngListDirective = function() { var list = []; if (viewValue) { - forEach(viewValue.split(trim(separator)), function(value) { - if (value) list.push(trim(value)); + forEach(viewValue.split(separator), function(value) { + if (value) list.push(trimValues ? trim(value) : value); }); } @@ -2422,7 +2446,7 @@ var ngListDirective = function() { ctrl.$parsers.push(parse); ctrl.$formatters.push(function(value) { if (isArray(value)) { - return value.join(separator); + return value.join(ngList); } return undefined; diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index f08674d15ad2..bf6bd70d2ecc 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -2671,27 +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']); - scope.$apply(function() { - scope.list = ['x', 'y', 'z']; + changeInputValueTo('a:b'); + expect(scope.list).toEqual(['a', 'b']); }); - expect(inputElm.val()).toBe('x:y:z'); - changeInputValueTo('a,a'); - expect(scope.list).toEqual(['a,a']); - 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'); + }); }); - it('should ignore separator whitespace when splitting', function() { - compileInput(''); + describe('(with ngTrim undefined or true)', function() { - changeInputValueTo('a|b'); - expect(scope.list).toEqual(['a', 'b']); + it('should ignore separator whitespace when splitting', function() { + compileInput(''); + + changeInputValueTo('a|b'); + expect(scope.list).toEqual(['a', 'b']); + }); + + it('should trim whitespace from each list item', function() { + compileInput(''); + + 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(' *
{{ 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