Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 8d18d20

Browse files
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
1 parent c6c9d26 commit 8d18d20

File tree

2 files changed

+128
-62
lines changed

2 files changed

+128
-62
lines changed

src/ng/directive/input.js

+73-49
Original file line numberDiff line numberDiff line change
@@ -2349,60 +2349,84 @@ var minlengthDirective = function() {
23492349
* @name ngList
23502350
*
23512351
* @description
2352-
* Text input that converts between a delimited string and an array of strings. The delimiter
2353-
* can be a fixed string (by default a comma) or a regular expression.
2352+
* Text input that converts between a delimited string and an array of strings. The default
2353+
* delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
2354+
* delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
2355+
*
2356+
* The behaviour of the directive is affected by the use of the `ngTrim` attribute.
2357+
* * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
2358+
* list item is respected. This implies that the user of the directive is responsible for
2359+
* dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
2360+
* tab or newline character.
2361+
* * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
2362+
* when joining the list items back together) and whitespace around each list item is stripped
2363+
* before it is added to the model.
2364+
*
2365+
* ### Example with Validation
2366+
*
2367+
* <example name="ngList-directive" module="listExample">
2368+
* <file name="app.js">
2369+
* angular.module('listExample', [])
2370+
* .controller('ExampleController', ['$scope', function($scope) {
2371+
* $scope.names = ['morpheus', 'neo', 'trinity'];
2372+
* }]);
2373+
* </file>
2374+
* <file name="index.html">
2375+
* <form name="myForm" ng-controller="ExampleController">
2376+
* List: <input name="namesInput" ng-model="names" ng-list required>
2377+
* <span class="error" ng-show="myForm.namesInput.$error.required">
2378+
* Required!</span>
2379+
* <br>
2380+
* <tt>names = {{names}}</tt><br/>
2381+
* <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
2382+
* <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
2383+
* <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
2384+
* <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
2385+
* </form>
2386+
* </file>
2387+
* <file name="protractor.js" type="protractor">
2388+
* var listInput = element(by.model('names'));
2389+
* var names = element(by.binding('{{names}}'));
2390+
* var valid = element(by.binding('myForm.namesInput.$valid'));
2391+
* var error = element(by.css('span.error'));
2392+
*
2393+
* it('should initialize to model', function() {
2394+
* expect(names.getText()).toContain('["morpheus","neo","trinity"]');
2395+
* expect(valid.getText()).toContain('true');
2396+
* expect(error.getCssValue('display')).toBe('none');
2397+
* });
2398+
*
2399+
* it('should be invalid if empty', function() {
2400+
* listInput.clear();
2401+
* listInput.sendKeys('');
2402+
*
2403+
* expect(names.getText()).toContain('');
2404+
* expect(valid.getText()).toContain('false');
2405+
* expect(error.getCssValue('display')).not.toBe('none');
2406+
* });
2407+
* </file>
2408+
* </example>
2409+
*
2410+
* ### Example - splitting on whitespace
2411+
* <example name="ngList-directive-newlines">
2412+
* <file name="index.html">
2413+
* <textarea ng-model="list" ng-list="&#10;" ng-trim="false"></textarea>
2414+
* <pre>{{ list | json }}</pre>
2415+
* </file>
2416+
* </example>
23542417
*
23552418
* @element input
23562419
* @param {string=} ngList optional delimiter that should be used to split the value.
2357-
*
2358-
* @example
2359-
<example name="ngList-directive" module="listExample">
2360-
<file name="index.html">
2361-
<script>
2362-
angular.module('listExample', [])
2363-
.controller('ExampleController', ['$scope', function($scope) {
2364-
$scope.names = ['igor', 'misko', 'vojta'];
2365-
}]);
2366-
</script>
2367-
<form name="myForm" ng-controller="ExampleController">
2368-
List: <input name="namesInput" ng-model="names" ng-list required>
2369-
<span class="error" ng-show="myForm.namesInput.$error.required">
2370-
Required!</span>
2371-
<br>
2372-
<tt>names = {{names}}</tt><br/>
2373-
<tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
2374-
<tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
2375-
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
2376-
<tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
2377-
</form>
2378-
</file>
2379-
<file name="protractor.js" type="protractor">
2380-
var listInput = element(by.model('names'));
2381-
var names = element(by.binding('{{names}}'));
2382-
var valid = element(by.binding('myForm.namesInput.$valid'));
2383-
var error = element(by.css('span.error'));
2384-
2385-
it('should initialize to model', function() {
2386-
expect(names.getText()).toContain('["igor","misko","vojta"]');
2387-
expect(valid.getText()).toContain('true');
2388-
expect(error.getCssValue('display')).toBe('none');
2389-
});
2390-
2391-
it('should be invalid if empty', function() {
2392-
listInput.clear();
2393-
listInput.sendKeys('');
2394-
2395-
expect(names.getText()).toContain('');
2396-
expect(valid.getText()).toContain('false');
2397-
expect(error.getCssValue('display')).not.toBe('none'); });
2398-
</file>
2399-
</example>
24002420
*/
24012421
var ngListDirective = function() {
24022422
return {
24032423
require: 'ngModel',
24042424
link: function(scope, element, attr, ctrl) {
2405-
var separator = attr.ngList || ', ';
2425+
// We want to control whitespace trimming so we use this convoluted approach
2426+
// to access the ngList attribute, which doesn't pre-trim the attribute
2427+
var ngList = element.attr(attr.$attr.ngList) || ', ';
2428+
var trimValues = attr.ngTrim !== 'false';
2429+
var separator = trimValues ? trim(ngList) : ngList;
24062430

24072431
var parse = function(viewValue) {
24082432
// If the viewValue is invalid (say required but empty) it will be `undefined`
@@ -2411,8 +2435,8 @@ var ngListDirective = function() {
24112435
var list = [];
24122436

24132437
if (viewValue) {
2414-
forEach(viewValue.split(trim(separator)), function(value) {
2415-
if (value) list.push(trim(value));
2438+
forEach(viewValue.split(separator), function(value) {
2439+
if (value) list.push(trimValues ? trim(value) : value);
24162440
});
24172441
}
24182442

@@ -2422,7 +2446,7 @@ var ngListDirective = function() {
24222446
ctrl.$parsers.push(parse);
24232447
ctrl.$formatters.push(function(value) {
24242448
if (isArray(value)) {
2425-
return value.join(separator);
2449+
return value.join(ngList);
24262450
}
24272451

24282452
return undefined;

test/ng/directive/inputSpec.js

+55-13
Original file line numberDiff line numberDiff line change
@@ -2671,27 +2671,69 @@ describe('input', function() {
26712671
expect(inputElm).toBeValid();
26722672
});
26732673

2674+
describe('with a custom separator', function() {
2675+
it('should split on the custom separator', function() {
2676+
compileInput('<input type="text" ng-model="list" ng-list=":" />');
26742677

2675-
it('should allow custom separator', function() {
2676-
compileInput('<input type="text" ng-model="list" ng-list=":" />');
2678+
changeInputValueTo('a,a');
2679+
expect(scope.list).toEqual(['a,a']);
26772680

2678-
scope.$apply(function() {
2679-
scope.list = ['x', 'y', 'z'];
2681+
changeInputValueTo('a:b');
2682+
expect(scope.list).toEqual(['a', 'b']);
26802683
});
2681-
expect(inputElm.val()).toBe('x:y:z');
26822684

2683-
changeInputValueTo('a,a');
2684-
expect(scope.list).toEqual(['a,a']);
26852685

2686-
changeInputValueTo('a:b');
2687-
expect(scope.list).toEqual(['a', 'b']);
2686+
it("should join the list back together with the custom separator", function() {
2687+
compileInput('<input type="text" ng-model="list" ng-list=" : " />');
2688+
2689+
scope.$apply(function() {
2690+
scope.list = ['x', 'y', 'z'];
2691+
});
2692+
expect(inputElm.val()).toBe('x : y : z');
2693+
});
26882694
});
26892695

2690-
it('should ignore separator whitespace when splitting', function() {
2691-
compileInput('<input type="text" ng-model="list" ng-list=" | " />');
2696+
describe('(with ngTrim undefined or true)', function() {
26922697

2693-
changeInputValueTo('a|b');
2694-
expect(scope.list).toEqual(['a', 'b']);
2698+
it('should ignore separator whitespace when splitting', function() {
2699+
compileInput('<input type="text" ng-model="list" ng-list=" | " />');
2700+
2701+
changeInputValueTo('a|b');
2702+
expect(scope.list).toEqual(['a', 'b']);
2703+
});
2704+
2705+
it('should trim whitespace from each list item', function() {
2706+
compileInput('<input type="text" ng-model="list" ng-list="|" />');
2707+
2708+
changeInputValueTo('a | b');
2709+
expect(scope.list).toEqual(['a', 'b']);
2710+
});
2711+
});
2712+
2713+
describe('(with ngTrim set to false)', function() {
2714+
2715+
it('should use separator whitespace when splitting', function() {
2716+
compileInput('<input type="text" ng-model="list" ng-trim="false" ng-list=" | " />');
2717+
2718+
changeInputValueTo('a|b');
2719+
expect(scope.list).toEqual(['a|b']);
2720+
2721+
changeInputValueTo('a | b');
2722+
expect(scope.list).toEqual(['a','b']);
2723+
2724+
});
2725+
2726+
it("should not trim whitespace from each list item", function() {
2727+
compileInput('<input type="text" ng-model="list" ng-trim="false" ng-list="|" />');
2728+
changeInputValueTo('a | b');
2729+
expect(scope.list).toEqual(['a ',' b']);
2730+
});
2731+
2732+
it("should support splitting on newlines", function() {
2733+
compileInput('<textarea type="text" ng-model="list" ng-trim="false" ng-list="&#10;"></textarea');
2734+
changeInputValueTo('a\nb');
2735+
expect(scope.list).toEqual(['a','b']);
2736+
});
26952737
});
26962738
});
26972739

0 commit comments

Comments
 (0)