diff --git a/package.json b/package.json
index 78cd771b6..dda889ca1 100644
--- a/package.json
+++ b/package.json
@@ -29,5 +29,9 @@
"karma-ng-html2js-preprocessor": "^0.1.0",
"karma-phantomjs-launcher": "~0.1.4"
},
+ "scripts": {
+ "postinstall": "bower install",
+ "test": "gulp test"
+ },
"license": "MIT"
}
diff --git a/src/select.js b/src/select.js
index fb31b71a2..233c3e4a6 100644
--- a/src/select.js
+++ b/src/select.js
@@ -43,57 +43,40 @@
* Original discussion about parsing "repeat" attribute instead of fully relying on ng-repeat:
* https://github.com/angular-ui/ui-select/commit/5dd63ad#commitcomment-5504697
*/
- .service('RepeatParser', ['uiSelectMinErr', function(uiSelectMinErr) {
+ .service('RepeatParser', ['uiSelectMinErr','$parse', function(uiSelectMinErr, $parse) {
var self = this;
/**
* Example:
* expression = "address in addresses | filter: {street: $select.search} track by $index"
- * lhs = "address",
- * rhs = "addresses | filter: {street: $select.search}",
+ * itemName = "address",
+ * source = "addresses | filter: {street: $select.search}",
* trackByExp = "$index",
- * valueIdentifier = "address",
- * keyIdentifier = undefined
*/
self.parse = function(expression) {
- if (!expression) {
- throw uiSelectMinErr('repeat', "Expected 'repeat' expression.");
- }
- var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
+ var match = expression.match(/^\s*(?:([\s\S]+?)\s+as\s+)?([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
if (!match) {
throw uiSelectMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
- expression);
- }
-
- var lhs = match[1]; // Left-hand side
- var rhs = match[2]; // Right-hand side
- var trackByExp = match[3];
-
- match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
- if (!match) {
- throw uiSelectMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.",
- lhs);
+ expression);
}
- // Unused for now
- // var valueIdentifier = match[3] || match[1];
- // var keyIdentifier = match[2];
-
return {
- lhs: lhs,
- rhs: rhs,
- trackByExp: trackByExp
+ itemName: match[2], // (lhs) Left-hand side,
+ source: match[3], // (rhs) Right-hand side,
+ trackByExp: match[4],
+ modelMapper: $parse(match[1] || match[2])
};
+
};
self.getGroupNgRepeatExpression = function() {
return '($group, $items) in $select.groups';
};
- self.getNgRepeatExpression = function(lhs, rhs, trackByExp, grouped) {
- var expression = lhs + ' in ' + (grouped ? '$items' : rhs);
+ self.getNgRepeatExpression = function(itemName, source, trackByExp, grouped) {
+ var expression = itemName + ' in ' + (grouped ? '$items' : source);
if (trackByExp) {
expression += ' track by ' + trackByExp;
}
@@ -180,14 +163,15 @@
ctrl.items = items;
}
- var repeat = RepeatParser.parse(repeatAttr),
- setItemsFn = groupByExp ? updateGroups : setPlainItems;
+ var setItemsFn = groupByExp ? updateGroups : setPlainItems;
+
+ ctrl.parserResult = RepeatParser.parse(repeatAttr);
ctrl.isGrouped = !!groupByExp;
- ctrl.itemProperty = repeat.lhs;
+ ctrl.itemProperty = ctrl.parserResult.itemName;
// See https://github.com/angular/angular.js/blob/v1.2.15/src/ng/directive/ngRepeat.js#L259
- $scope.$watchCollection(repeat.rhs, function(items) {
+ $scope.$watchCollection(ctrl.parserResult.source, function(items) {
if (items === undefined || items === null) {
// If the user specifies undefined or null => reset the collection
@@ -204,6 +188,7 @@
}
});
+
};
var _refreshDelayPromise;
@@ -355,6 +340,32 @@
var $select = ctrls[0];
var ngModel = ctrls[1];
+ //From view --> model
+ ngModel.$parsers.unshift(function (inputValue) {
+ var locals = {};
+ locals[$select.parserResult.itemName] = inputValue;
+ var result = $select.parserResult.modelMapper(scope, locals);
+ return result;
+ });
+
+ //From model --> view
+ ngModel.$formatters.unshift(function (inputValue) {
+ var match = $select.parserResult.source.match(/^\s*([\S]+).*$/);
+ var data = scope[match[1]];
+ if (data){
+ for (var i = data.length - 1; i >= 0; i--) {
+ var locals = {};
+ locals[$select.parserResult.itemName] = data[i];
+ var result = $select.parserResult.modelMapper(scope, locals);
+ if (result == inputValue){
+ return data[i];
+ }
+ }
+ }
+ return inputValue;
+ });
+
+
//Idea from: https://github.com/ivaynberg/select2/blob/79b5bf6db918d7560bdd959109b7bcfb47edaf43/select2.js#L1954
var focusser = angular.element("");
$compile(focusser)(scope);
@@ -533,9 +544,15 @@
},
compile: function(tElement, tAttrs) {
- var repeat = RepeatParser.parse(tAttrs.repeat);
- var groupByExp = tAttrs.groupBy;
+
+ if (!tAttrs.repeat) throw uiSelectMinErr('repeat', "Expected 'repeat' expression.");
+
return function link(scope, element, attrs, $select, transcludeFn) {
+
+ // var repeat = RepeatParser.parse(attrs.repeat);
+ var groupByExp = attrs.groupBy;
+
+ $select.parseRepeatAttr(attrs.repeat, groupByExp); //Result ready at $select.parserResult
if(groupByExp) {
var groups = element.querySelectorAll('.ui-select-choices-group');
@@ -548,10 +565,9 @@
throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-row but got '{0}'.", choices.length);
}
- choices.attr('ng-repeat', RepeatParser.getNgRepeatExpression(repeat.lhs, '$select.items', repeat.trackByExp, groupByExp))
- .attr('ng-mouseenter', '$select.setActiveItem('+repeat.lhs+')')
- .attr('ng-click', '$select.select(' + repeat.lhs + ')');
-
+ choices.attr('ng-repeat', RepeatParser.getNgRepeatExpression($select.parserResult.itemName, '$select.items', $select.parserResult.trackByExp, groupByExp))
+ .attr('ng-mouseenter', '$select.setActiveItem('+$select.parserResult.itemName +')')
+ .attr('ng-click', '$select.select(' + $select.parserResult.itemName + ')');
transcludeFn(function(clone) {
var rowsInner = element.querySelectorAll('.ui-select-choices-row-inner');
@@ -562,8 +578,6 @@
$compile(element)(scope);
});
- $select.parseRepeatAttr(attrs.repeat, groupByExp);
-
scope.$watch('$select.search', function() {
$select.activeIndex = 0;
$select.refresh(attrs.refresh);
diff --git a/test/select.spec.js b/test/select.spec.js
index a9709ab0c..79486df60 100644
--- a/test/select.spec.js
+++ b/test/select.spec.js
@@ -8,7 +8,7 @@ describe('ui-select tests', function() {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
$compile = _$compile_;
-
+ scope.selection = {}
scope.getGroupLabel = function(person) {
return person.age % 2 ? 'even' : 'odd';
};
@@ -42,7 +42,7 @@ describe('ui-select tests', function() {
}
return compileTemplate(
- ' \
+ ' \
{{$select.selected.name}} \
\
\
@@ -102,7 +102,7 @@ describe('ui-select tests', function() {
});
it('should correctly render initial state', function() {
- scope.selection = scope.people[0];
+ scope.selection.selected = scope.people[0];
var el = createUiSelect();
@@ -178,7 +178,7 @@ describe('ui-select tests', function() {
scope.items = ['false'];
var el = compileTemplate(
- ' \
+ ' \
{{$select.selected}} \
\
\
@@ -199,7 +199,7 @@ describe('ui-select tests', function() {
}
function createUiSelect() {
return compileTemplate(
- ' \
+ ' \
{{$select.selected.name}} \
\
\
@@ -249,7 +249,7 @@ describe('ui-select tests', function() {
describe('choices group by function', function() {
function createUiSelect() {
return compileTemplate(
- ' \
+ ' \
{{$select.selected.name}} \
\
\
@@ -268,7 +268,7 @@ describe('ui-select tests', function() {
it('should throw when no ui-select-choices found', function() {
expect(function() {
compileTemplate(
- ' \
+ ' \
\
'
);
@@ -278,7 +278,7 @@ describe('ui-select tests', function() {
it('should throw when no repeat attribute is provided to ui-select-choices', function() {
expect(function() {
compileTemplate(
- ' \
+ ' \
\
'
);
@@ -288,9 +288,96 @@ describe('ui-select tests', function() {
it('should throw when no ui-select-match found', function() {
expect(function() {
compileTemplate(
- ' \
+ ' \
\
'
);
}).toThrow(new Error('[ui.select:transcluded] Expected 1 .ui-select-match but got \'0\'.'));
- });});
+ });
+
+ it('should format the model correctly using alias', function() {
+ var el = compileTemplate(
+ ' \
+ {{$select.selected.name}} \
+ \
+ \
+ \
+ \
+ '
+ );
+ clickItem(el, 'Samantha');
+ expect(scope.selection.selected).toBe(scope.people[5]);
+ });
+
+ it('should parse the model correctly using alias', function() {
+ var el = compileTemplate(
+ ' \
+ {{$select.selected.name}} \
+ \
+ \
+ \
+ \
+ '
+ );
+ scope.selection.selected = scope.people[5];
+ scope.$digest();
+ expect(getMatchLabel(el)).toEqual('Samantha');
+ });
+
+ it('should format the model correctly using property of alias', function() {
+ var el = compileTemplate(
+ ' \
+ {{$select.selected.name}} \
+ \
+ \
+ \
+ \
+ '
+ );
+ clickItem(el, 'Samantha');
+ expect(scope.selection.selected).toBe('Samantha');
+ });
+
+ it('should parse the model correctly using property of alias', function() {
+ var el = compileTemplate(
+ ' \
+ {{$select.selected.name}} \
+ \
+ \
+ \
+ \
+ '
+ );
+ scope.selection.selected = 'Samantha';
+ scope.$digest();
+ expect(getMatchLabel(el)).toEqual('Samantha');
+ });
+
+ it('should parse the model correctly using property of alias but passed whole object', function() {
+ var el = compileTemplate(
+ ' \
+ {{$select.selected.name}} \
+ \
+ \
+ \
+ \
+ '
+ );
+ scope.selection.selected = scope.people[5];
+ scope.$digest();
+ expect(getMatchLabel(el)).toEqual('Samantha');
+ });
+
+ it('should format the model correctly without alias', function() {
+ var el = createUiSelect();
+ clickItem(el, 'Samantha');
+ expect(scope.selection.selected).toBe(scope.people[5]);
+ });
+
+ it('should parse the model correctly without alias', function() {
+ var el = createUiSelect();
+ scope.selection.selected = scope.people[5];
+ scope.$digest();
+ expect(getMatchLabel(el)).toEqual('Samantha');
+ });
+});