diff --git a/examples/demo-object-as-source.html b/examples/demo-object-as-source.html
new file mode 100644
index 000000000..a4095903f
--- /dev/null
+++ b/examples/demo-object-as-source.html
@@ -0,0 +1,107 @@
+
+
+
+
+ AngularJS ui-select
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (key, value) format
+
+ Using value for binding
+
+ Selected: {{person.selectedValue}}
+
+ {{$select.selected.value.name}}
+
+
+
+ email: {{person.value.email}}
+ age:
+
+
+
+
+ Using single property for binding
+ Selected: {{person.selectedSingle}}
+
+ {{$select.selected.value.name}}
+
+
+
+ email: {{person.value.email}}
+ age:
+
+
+
+
+ Using key for binding
+ Selected: {{person.selectedSingleKey}}
+
+ {{$select.selected.value.name}}
+
+
+
+ email: {{person.value.email}}
+ age:
+
+
+
+
+
+
diff --git a/examples/demo.js b/examples/demo.js
index 759917531..d53204914 100644
--- a/examples/demo.js
+++ b/examples/demo.js
@@ -46,7 +46,7 @@ app.controller('DemoCtrl', function($scope, $http, $timeout, $interval) {
$scope.setInputFocus = function (){
$scope.$broadcast('UiSelectDemo1');
- }
+ };
$scope.enable = function() {
$scope.disabled = false;
@@ -58,11 +58,11 @@ app.controller('DemoCtrl', function($scope, $http, $timeout, $interval) {
$scope.enableSearch = function() {
$scope.searchEnabled = true;
- }
+ };
$scope.disableSearch = function() {
$scope.searchEnabled = false;
- }
+ };
$scope.clear = function() {
$scope.person.selected = undefined;
@@ -130,7 +130,25 @@ app.controller('DemoCtrl', function($scope, $http, $timeout, $interval) {
return item;
};
+ $scope.peopleObj = {
+ '1' : { name: 'Adam', email: 'adam@email.com', age: 12, country: 'United States' },
+ '2' : { name: 'Amalie', email: 'amalie@email.com', age: 12, country: 'Argentina' },
+ '3' : { name: 'Estefanía', email: 'estefania@email.com', age: 21, country: 'Argentina' },
+ '4' : { name: 'Adrian', email: 'adrian@email.com', age: 21, country: 'Ecuador' },
+ '5' : { name: 'Wladimir', email: 'wladimir@email.com', age: 30, country: 'Ecuador' },
+ '6' : { name: 'Samantha', email: 'samantha@email.com', age: 30, country: 'United States' },
+ '7' : { name: 'Nicole', email: 'nicole@email.com', age: 43, country: 'Colombia' },
+ '8' : { name: 'Natasha', email: 'natasha@email.com', age: 54, country: 'Ecuador' },
+ '9' : { name: 'Michael', email: 'michael@email.com', age: 15, country: 'Colombia' },
+ '10' : { name: 'Nicolás', email: 'nicolas@email.com', age: 43, country: 'Colombia' }
+ };
+
$scope.person = {};
+
+ $scope.person.selectedValue = $scope.peopleObj[3];
+ $scope.person.selectedSingle = 'Samantha';
+ $scope.person.selectedSingleKey = '5';
+
$scope.people = [
{ name: 'Adam', email: 'adam@email.com', age: 12, country: 'United States' },
{ name: 'Amalie', email: 'amalie@email.com', age: 12, country: 'Argentina' },
diff --git a/src/uiSelectChoicesDirective.js b/src/uiSelectChoicesDirective.js
index 69e29b6cc..0b4f8a562 100644
--- a/src/uiSelectChoicesDirective.js
+++ b/src/uiSelectChoicesDirective.js
@@ -39,7 +39,7 @@ uis.directive('uiSelectChoices',
throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-row but got '{0}'.", choices.length);
}
- choices.attr('ng-repeat', RepeatParser.getNgRepeatExpression($select.parserResult.itemName, '$select.items', $select.parserResult.trackByExp, groupByExp))
+ choices.attr('ng-repeat', $select.parserResult.repeatExpression(groupByExp))
.attr('ng-if', '$select.open') //Prevent unnecessary watches when dropdown is closed
.attr('ng-mouseenter', '$select.setActiveItem('+$select.parserResult.itemName +')')
.attr('ng-click', '$select.select(' + $select.parserResult.itemName + ',false,$event)');
diff --git a/src/uiSelectController.js b/src/uiSelectController.js
index 486c245a2..a1b786168 100644
--- a/src/uiSelectController.js
+++ b/src/uiSelectController.js
@@ -5,8 +5,8 @@
* put as much logic in the controller (instead of the link functions) as possible so it can be easily tested.
*/
uis.controller('uiSelectCtrl',
- ['$scope', '$element', '$timeout', '$filter', 'uisRepeatParser', 'uiSelectMinErr', 'uiSelectConfig',
- function($scope, $element, $timeout, $filter, RepeatParser, uiSelectMinErr, uiSelectConfig) {
+ ['$scope', '$element', '$timeout', '$filter', 'uisRepeatParser', 'uiSelectMinErr', 'uiSelectConfig', '$parse',
+ function($scope, $element, $timeout, $filter, RepeatParser, uiSelectMinErr, uiSelectConfig, $parse) {
var ctrl = this;
@@ -92,6 +92,9 @@ uis.controller('uiSelectCtrl',
$timeout(function() {
ctrl.search = initSearchValue || ctrl.search;
ctrl.searchInput[0].focus();
+ if(!ctrl.tagging.isActivated && ctrl.items.length > 1) {
+ _ensureHighlightVisible();
+ }
});
}
};
@@ -141,6 +144,28 @@ uis.controller('uiSelectCtrl',
ctrl.isGrouped = !!groupByExp;
ctrl.itemProperty = ctrl.parserResult.itemName;
+ //If collection is an Object, convert it to Array
+
+ var originalSource = ctrl.parserResult.source;
+
+ //When an object is used as source, we better create an array and use it as 'source'
+ var createArrayFromObject = function(){
+ $scope.$uisSource = Object.keys(originalSource($scope)).map(function(v){
+ var result = {};
+ result[ctrl.parserResult.keyName] = v;
+ result.value = $scope.peopleObj[v];
+ return result;
+ });
+ };
+
+ if (ctrl.parserResult.keyName){ // Check for (key,value) syntax
+ createArrayFromObject();
+ ctrl.parserResult.source = $parse('$uisSource' + ctrl.parserResult.filters);
+ $scope.$watch(originalSource, function(newVal, oldVal){
+ if (newVal !== oldVal) createArrayFromObject();
+ }, true);
+ }
+
ctrl.refreshItems = function (data){
data = data || ctrl.parserResult.source($scope);
var selectedItems = ctrl.selected;
@@ -164,7 +189,7 @@ uis.controller('uiSelectCtrl',
ctrl.items = [];
} else {
if (!angular.isArray(items)) {
- throw uiSelectMinErr('items', "Expected an array but got '{0}'.", items);
+ throw uiSelectMinErr('items', "Expected an array but got '{0}'.", items);
} else {
//Remove already selected items (ex: while searching)
//TODO Should add a test
diff --git a/src/uisRepeatParserService.js b/src/uisRepeatParserService.js
index 7ed9955f1..8d343a1e0 100644
--- a/src/uisRepeatParserService.js
+++ b/src/uisRepeatParserService.js
@@ -20,7 +20,9 @@ uis.service('uisRepeatParser', ['uiSelectMinErr','$parse', function(uiSelectMinE
*/
self.parse = function(expression) {
- var match = expression.match(/^\s*(?:([\s\S]+?)\s+as\s+)?([\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
+
+ //0000000000000000000000000000000000011111111100000000000000022222222222222003333333333333333333333000044444444444444444400000000000000005555500000666666666666600000000000000000000007777777770000000
+ var match = expression.match(/^\s*(?:([\s\S]+?)\s+as\s+)?(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\w]+)\s*(|\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}'.",
@@ -28,10 +30,20 @@ uis.service('uisRepeatParser', ['uiSelectMinErr','$parse', function(uiSelectMinE
}
return {
- itemName: match[2], // (lhs) Left-hand side,
- source: $parse(match[3]),
- trackByExp: match[4],
- modelMapper: $parse(match[1] || match[2])
+ itemName: match[4] || match[2], // (lhs) Left-hand side,
+ keyName: match[3], //for (key, value) syntax
+ source: $parse(!match[3] ? match[5] + (match[6] || ''): match[5]), //concat source with filters if its an array
+ sourceName: match[5],
+ filters: match[6],
+ trackByExp: match[7],
+ modelMapper: $parse(match[1] || match[4] || match[2]),
+ repeatExpression: function (grouped) {
+ var expression = this.itemName + ' in ' + (grouped ? '$group.items' : '$select.items');
+ if (this.trackByExp) {
+ expression += ' track by ' + this.trackByExp;
+ }
+ return expression;
+ }
};
};
@@ -40,11 +52,4 @@ uis.service('uisRepeatParser', ['uiSelectMinErr','$parse', function(uiSelectMinE
return '$group in $select.groups';
};
- self.getNgRepeatExpression = function(itemName, source, trackByExp, grouped) {
- var expression = itemName + ' in ' + (grouped ? '$group.items' : source);
- if (trackByExp) {
- expression += ' track by ' + trackByExp;
- }
- return expression;
- };
}]);
diff --git a/test/select.spec.js b/test/select.spec.js
index 80f953c46..aaeedfacc 100644
--- a/test/select.spec.js
+++ b/test/select.spec.js
@@ -1,7 +1,7 @@
'use strict';
describe('ui-select tests', function() {
- var scope, $rootScope, $compile, $timeout, $injector;
+ var scope, $rootScope, $compile, $timeout, $injector, uisRepeatParser;
var Key = {
Enter: 13,
@@ -48,12 +48,13 @@ describe('ui-select tests', function() {
});
});
- beforeEach(inject(function(_$rootScope_, _$compile_, _$timeout_, _$injector_) {
+ beforeEach(inject(function(_$rootScope_, _$compile_, _$timeout_, _$injector_, _uisRepeatParser_) {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
$compile = _$compile_;
$timeout = _$timeout_;
$injector = _$injector_;
+ uisRepeatParser = _uisRepeatParser_;
scope.selection = {};
scope.getGroupLabel = function(person) {
@@ -77,6 +78,19 @@ describe('ui-select tests', function() {
{ name: 'Nicole', email: 'nicole@email.com', group: 'bar', age: 43 },
{ name: 'Natasha', email: 'natasha@email.com', group: 'Baz', age: 54 }
];
+
+ scope.peopleObj = {
+ '1' : { name: 'Adam', email: 'adam@email.com', age: 12, country: 'United States' },
+ '2' : { name: 'Amalie', email: 'amalie@email.com', age: 12, country: 'Argentina' },
+ '3' : { name: 'Estefanía', email: 'estefania@email.com', age: 21, country: 'Argentina' },
+ '4' : { name: 'Adrian', email: 'adrian@email.com', age: 21, country: 'Ecuador' },
+ '5' : { name: 'Wladimir', email: 'wladimir@email.com', age: 30, country: 'Ecuador' },
+ '6' : { name: 'Samantha', email: 'samantha@email.com', age: 30, country: 'United States' },
+ '7' : { name: 'Nicole', email: 'nicole@email.com', age: 43, country: 'Colombia' },
+ '8' : { name: 'Natasha', email: 'natasha@email.com', age: 54, country: 'Ecuador' },
+ '9' : { name: 'Michael', email: 'michael@email.com', age: 15, country: 'Colombia' },
+ '10' : { name: 'Nicolás', email: 'nicolas@email.com', age: 43, country: 'Colombia' }
+ };
scope.someObject = {};
scope.someObject.people = [
@@ -190,6 +204,86 @@ describe('ui-select tests', function() {
// Tests
+ //uisRepeatParser
+
+ it('should parse simple repeat syntax', function() {
+
+ var locals = {};
+ locals.people = [{name: 'Wladimir'}, {name: 'Samantha'}];
+ locals.person = locals.people[0];
+
+ var parserResult = uisRepeatParser.parse('person in people');
+ expect(parserResult.itemName).toBe('person');
+ expect(parserResult.modelMapper(locals)).toBe(locals.person);
+ expect(parserResult.source(locals)).toBe(locals.people);
+
+ var ngExp = parserResult.repeatExpression(false);
+ expect(ngExp).toBe('person in $select.items');
+
+ var ngExpGrouped = parserResult.repeatExpression(true);
+ expect(ngExpGrouped).toBe('person in $group.items');
+
+ });
+
+ it('should parse simple repeat syntax', function() {
+
+ var locals = {};
+ locals.people = [{name: 'Wladimir'}, {name: 'Samantha'}];
+ locals.person = locals.people[0];
+
+ var parserResult = uisRepeatParser.parse('person.name as person in people');
+ expect(parserResult.itemName).toBe('person');
+ expect(parserResult.modelMapper(locals)).toBe(locals.person.name);
+ expect(parserResult.source(locals)).toBe(locals.people);
+
+ });
+
+ it('should parse simple property binding repeat syntax', function() {
+
+ var locals = {};
+ locals.people = [{name: 'Wladimir'}, {name: 'Samantha'}];
+ locals.person = locals.people[0];
+
+ var parserResult = uisRepeatParser.parse('person.name as person in people');
+ expect(parserResult.itemName).toBe('person');
+ expect(parserResult.modelMapper(locals)).toBe(locals.person.name);
+ expect(parserResult.source(locals)).toBe(locals.people);
+
+ });
+
+ it('should parse (key, value) repeat syntax', function() {
+
+ var locals = {};
+ locals.people = { 'WC' : {name: 'Wladimir'}, 'SH' : {name: 'Samantha'}};
+ locals.person = locals.people[0];
+
+ var parserResult = uisRepeatParser.parse('(key,person) in people');
+ expect(parserResult.itemName).toBe('person');
+ expect(parserResult.keyName).toBe('key');
+ expect(parserResult.modelMapper(locals)).toBe(locals.person);
+ expect(parserResult.source(locals)).toBe(locals.people);
+
+ var ngExp = parserResult.repeatExpression(false);
+ expect(ngExp).toBe('person in $select.items');
+
+ var ngExpGrouped = parserResult.repeatExpression(true);
+ expect(ngExpGrouped).toBe('person in $group.items');
+
+ });
+
+ it('should parse simple property binding with (key, value) repeat syntax', function() {
+
+ var locals = {};
+ locals.people = { 'WC' : {name: 'Wladimir'}, 'SH' : {name: 'Samantha'}};
+ locals.person = locals.people['WC'];
+
+ var parserResult = uisRepeatParser.parse('person.name as (key, person) in people');
+ expect(parserResult.itemName).toBe('person');
+ expect(parserResult.keyName).toBe('key');
+ expect(parserResult.modelMapper(locals)).toBe(locals.person.name);
+ expect(parserResult.source(locals)).toBe(locals.people);
+
+ });
it('should compile child directives', function() {
var el = createUiSelect();
@@ -469,6 +563,88 @@ describe('ui-select tests', function() {
el2.remove();
});
+ it('should bind model correctly (with object as source)', function() {
+ var el = compileTemplate(
+ ' \
+ {{$select.selected.value.name}} \
+ \
+ \
+ \
+ \
+ '
+ );
+ // scope.selection.selected = 'Samantha';
+
+ clickItem(el, 'Samantha');
+ scope.$digest();
+ expect(getMatchLabel(el)).toEqual('Samantha');
+ expect(scope.selection.selected).toBe(scope.peopleObj[6]);
+
+ });
+
+ it('should bind model correctly (with object as source) using a single property', function() {
+ var el = compileTemplate(
+ ' \
+ {{$select.selected.value.name}} \
+ \
+ \
+ \
+ \
+ '
+ );
+ // scope.selection.selected = 'Samantha';
+
+ clickItem(el, 'Samantha');
+ scope.$digest();
+ expect(getMatchLabel(el)).toEqual('Samantha');
+ expect(scope.selection.selected).toBe('Samantha');
+
+ });
+
+ it('should update choices when original source changes (with object as source)', function() {
+ var el = compileTemplate(
+ ' \
+ {{$select.selected.value.name}} \
+ \
+ \
+ \
+ \
+ '
+ );
+
+ scope.$digest();
+
+ openDropdown(el);
+ var choicesEls = $(el).find('.ui-select-choices-row');
+ expect(choicesEls.length).toEqual(10);
+
+ scope.peopleObj['11'] = { name: 'Camila', email: 'camila@email.com', age: 1, country: 'Ecuador' };
+ scope.$digest();
+
+ choicesEls = $(el).find('.ui-select-choices-row');
+ expect(choicesEls.length).toEqual(11);
+
+ });
+
+ it('should bind model correctly (with object as source) using the key of collection', function() {
+ var el = compileTemplate(
+ ' \
+ {{$select.selected.value.name}} \
+ \
+ \
+ \
+ \
+ '
+ );
+ // scope.selection.selected = 'Samantha';
+
+ clickItem(el, 'Samantha');
+ scope.$digest();
+ expect(getMatchLabel(el)).toEqual('Samantha');
+ expect(scope.selection.selected).toBe('6');
+
+ });
+
describe('disabled options', function() {
function createUiSelect(attrs) {
var attrsDisabled = '';