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
-
-
-
-
-
-
- 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'];
+ * }]);
+ *
+ *
+ *
+ *
+ *
+ * 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('