Skip to content
This repository was archived by the owner on Oct 2, 2019. It is now read-only.

Commit a14e81d

Browse files
committed
Merge pull request #1208 from angular-ui/feat-object-as-source
feat(choices): use object as source iterating over its properties
2 parents 15920e4 + 87e732e commit a14e81d

6 files changed

+352
-21
lines changed

Diff for: examples/demo-object-as-source.html

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<!DOCTYPE html>
2+
<html lang="en" ng-app="demo">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>AngularJS ui-select</title>
6+
7+
<!--
8+
IE8 support, see AngularJS Internet Explorer Compatibility http://docs.angularjs.org/guide/ie
9+
For Firefox 3.6, you will also need to include jQuery and ECMAScript 5 shim
10+
-->
11+
<!--[if lt IE 9]>
12+
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.js"></script>
13+
<script src="http://cdnjs.cloudflare.com/ajax/libs/es5-shim/2.2.0/es5-shim.js"></script>
14+
<script>
15+
document.createElement('ui-select');
16+
document.createElement('ui-select-match');
17+
document.createElement('ui-select-choices');
18+
</script>
19+
<![endif]-->
20+
21+
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.js"></script>
22+
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular-sanitize.js"></script>
23+
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.css">
24+
25+
<!-- ui-select files -->
26+
<script src="../dist/select.js"></script>
27+
<link rel="stylesheet" href="../dist/select.css">
28+
29+
<script src="demo.js"></script>
30+
31+
<!-- Select2 theme -->
32+
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/select2/3.4.5/select2.css">
33+
34+
<!--
35+
Selectize theme
36+
Less versions are available at https://github.com/brianreavis/selectize.js/tree/master/dist/less
37+
-->
38+
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.8.5/css/selectize.default.css">
39+
<!-- <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.8.5/css/selectize.bootstrap2.css"> -->
40+
<!-- <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.8.5/css/selectize.bootstrap3.css"> -->
41+
42+
<style>
43+
body {
44+
padding: 15px;
45+
}
46+
47+
.select2 > .select2-choice.ui-select-match {
48+
/* Because of the inclusion of Bootstrap */
49+
height: 29px;
50+
}
51+
52+
.selectize-control > .selectize-dropdown {
53+
top: 36px;
54+
}
55+
</style>
56+
</head>
57+
58+
<body ng-controller="DemoCtrl">
59+
60+
<button class="btn btn-default btn-xs" ng-click="enable()">Enable ui-select</button>
61+
<button class="btn btn-default btn-xs" ng-click="disable()">Disable ui-select</button>
62+
<button class="btn btn-default btn-xs" ng-click="clear()">Clear ng-model</button>
63+
64+
<h1>(key, value) format</h1>
65+
66+
<h2>Using value for binding</h2>
67+
68+
<p>Selected: {{person.selectedValue}}</p>
69+
<ui-select ng-model="person.selectedValue" theme="select2" ng-disabled="disabled" style="min-width: 300px;" title="Choose a person">
70+
<ui-select-match placeholder="Select a person in the list or search his name/age...">{{$select.selected.value.name}}</ui-select-match>
71+
<ui-select-choices repeat="person.value as (key, person) in peopleObj | filter: {'value':$select.search}">
72+
<div ng-bind-html="person.value.name | highlight: $select.search"></div>
73+
<small>
74+
email: {{person.value.email}}
75+
age: <span ng-bind-html="''+person.value.age | highlight: $select.search"></span>
76+
</small>
77+
</ui-select-choices>
78+
</ui-select>
79+
80+
<h2>Using single property for binding</h2>
81+
<p>Selected: {{person.selectedSingle}}</p>
82+
<ui-select ng-model="person.selectedSingle" theme="select2" ng-disabled="disabled" style="min-width: 300px;" title="Choose a person">
83+
<ui-select-match placeholder="Select a person in the list or search his name/age...">{{$select.selected.value.name}}</ui-select-match>
84+
<ui-select-choices repeat="person.value.name as (key, person) in peopleObj | filter: {'value':$select.search}">
85+
<div ng-bind-html="person.value.name | highlight: $select.search"></div>
86+
<small>
87+
email: {{person.value.email}}
88+
age: <span ng-bind-html="''+person.value.age | highlight: $select.search"></span>
89+
</small>
90+
</ui-select-choices>
91+
</ui-select>
92+
93+
<h2>Using key for binding</h2>
94+
<p>Selected: {{person.selectedSingleKey}}</p>
95+
<ui-select ng-model="person.selectedSingleKey" theme="select2" ng-disabled="disabled" style="min-width: 300px;" title="Choose a person">
96+
<ui-select-match placeholder="Select a person in the list or search his name/age...">{{$select.selected.value.name}}</ui-select-match>
97+
<ui-select-choices repeat="person.key as (key, person) in peopleObj | filter: {'value':$select.search}">
98+
<div ng-bind-html="person.value.name | highlight: $select.search"></div>
99+
<small>
100+
email: {{person.value.email}}
101+
age: <span ng-bind-html="''+person.value.age | highlight: $select.search"></span>
102+
</small>
103+
</ui-select-choices>
104+
</ui-select>
105+
106+
</body>
107+
</html>

Diff for: examples/demo.js

+21-3
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ app.controller('DemoCtrl', function($scope, $http, $timeout, $interval) {
4646

4747
$scope.setInputFocus = function (){
4848
$scope.$broadcast('UiSelectDemo1');
49-
}
49+
};
5050

5151
$scope.enable = function() {
5252
$scope.disabled = false;
@@ -58,11 +58,11 @@ app.controller('DemoCtrl', function($scope, $http, $timeout, $interval) {
5858

5959
$scope.enableSearch = function() {
6060
$scope.searchEnabled = true;
61-
}
61+
};
6262

6363
$scope.disableSearch = function() {
6464
$scope.searchEnabled = false;
65-
}
65+
};
6666

6767
$scope.clear = function() {
6868
$scope.person.selected = undefined;
@@ -130,7 +130,25 @@ app.controller('DemoCtrl', function($scope, $http, $timeout, $interval) {
130130
return item;
131131
};
132132

133+
$scope.peopleObj = {
134+
'1' : { name: 'Adam', email: '[email protected]', age: 12, country: 'United States' },
135+
'2' : { name: 'Amalie', email: '[email protected]', age: 12, country: 'Argentina' },
136+
'3' : { name: 'Estefanía', email: '[email protected]', age: 21, country: 'Argentina' },
137+
'4' : { name: 'Adrian', email: '[email protected]', age: 21, country: 'Ecuador' },
138+
'5' : { name: 'Wladimir', email: '[email protected]', age: 30, country: 'Ecuador' },
139+
'6' : { name: 'Samantha', email: '[email protected]', age: 30, country: 'United States' },
140+
'7' : { name: 'Nicole', email: '[email protected]', age: 43, country: 'Colombia' },
141+
'8' : { name: 'Natasha', email: '[email protected]', age: 54, country: 'Ecuador' },
142+
'9' : { name: 'Michael', email: '[email protected]', age: 15, country: 'Colombia' },
143+
'10' : { name: 'Nicolás', email: '[email protected]', age: 43, country: 'Colombia' }
144+
};
145+
133146
$scope.person = {};
147+
148+
$scope.person.selectedValue = $scope.peopleObj[3];
149+
$scope.person.selectedSingle = 'Samantha';
150+
$scope.person.selectedSingleKey = '5';
151+
134152
$scope.people = [
135153
{ name: 'Adam', email: '[email protected]', age: 12, country: 'United States' },
136154
{ name: 'Amalie', email: '[email protected]', age: 12, country: 'Argentina' },

Diff for: src/uiSelectChoicesDirective.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ uis.directive('uiSelectChoices',
3939
throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-row but got '{0}'.", choices.length);
4040
}
4141

42-
choices.attr('ng-repeat', RepeatParser.getNgRepeatExpression($select.parserResult.itemName, '$select.items', $select.parserResult.trackByExp, groupByExp))
42+
choices.attr('ng-repeat', $select.parserResult.repeatExpression(groupByExp))
4343
.attr('ng-if', '$select.open') //Prevent unnecessary watches when dropdown is closed
4444
.attr('ng-mouseenter', '$select.setActiveItem('+$select.parserResult.itemName +')')
4545
.attr('ng-click', '$select.select(' + $select.parserResult.itemName + ',false,$event)');

Diff for: src/uiSelectController.js

+28-3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
* put as much logic in the controller (instead of the link functions) as possible so it can be easily tested.
66
*/
77
uis.controller('uiSelectCtrl',
8-
['$scope', '$element', '$timeout', '$filter', 'uisRepeatParser', 'uiSelectMinErr', 'uiSelectConfig',
9-
function($scope, $element, $timeout, $filter, RepeatParser, uiSelectMinErr, uiSelectConfig) {
8+
['$scope', '$element', '$timeout', '$filter', 'uisRepeatParser', 'uiSelectMinErr', 'uiSelectConfig', '$parse',
9+
function($scope, $element, $timeout, $filter, RepeatParser, uiSelectMinErr, uiSelectConfig, $parse) {
1010

1111
var ctrl = this;
1212

@@ -92,6 +92,9 @@ uis.controller('uiSelectCtrl',
9292
$timeout(function() {
9393
ctrl.search = initSearchValue || ctrl.search;
9494
ctrl.searchInput[0].focus();
95+
if(!ctrl.tagging.isActivated && ctrl.items.length > 1) {
96+
_ensureHighlightVisible();
97+
}
9598
});
9699
}
97100
};
@@ -141,6 +144,28 @@ uis.controller('uiSelectCtrl',
141144
ctrl.isGrouped = !!groupByExp;
142145
ctrl.itemProperty = ctrl.parserResult.itemName;
143146

147+
//If collection is an Object, convert it to Array
148+
149+
var originalSource = ctrl.parserResult.source;
150+
151+
//When an object is used as source, we better create an array and use it as 'source'
152+
var createArrayFromObject = function(){
153+
$scope.$uisSource = Object.keys(originalSource($scope)).map(function(v){
154+
var result = {};
155+
result[ctrl.parserResult.keyName] = v;
156+
result.value = $scope.peopleObj[v];
157+
return result;
158+
});
159+
};
160+
161+
if (ctrl.parserResult.keyName){ // Check for (key,value) syntax
162+
createArrayFromObject();
163+
ctrl.parserResult.source = $parse('$uisSource' + ctrl.parserResult.filters);
164+
$scope.$watch(originalSource, function(newVal, oldVal){
165+
if (newVal !== oldVal) createArrayFromObject();
166+
}, true);
167+
}
168+
144169
ctrl.refreshItems = function (data){
145170
$scope.calculateDropdownPos();
146171
data = data || ctrl.parserResult.source($scope);
@@ -165,7 +190,7 @@ uis.controller('uiSelectCtrl',
165190
ctrl.items = [];
166191
} else {
167192
if (!angular.isArray(items)) {
168-
throw uiSelectMinErr('items', "Expected an array but got '{0}'.", items);
193+
throw uiSelectMinErr('items', "Expected an array but got '{0}'.", items);
169194
} else {
170195
//Remove already selected items (ex: while searching)
171196
//TODO Should add a test

Diff for: src/uisRepeatParserService.js

+17-12
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,30 @@ uis.service('uisRepeatParser', ['uiSelectMinErr','$parse', function(uiSelectMinE
2020
*/
2121
self.parse = function(expression) {
2222

23-
var match = expression.match(/^\s*(?:([\s\S]+?)\s+as\s+)?([\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
23+
24+
//0000000000000000000000000000000000011111111100000000000000022222222222222003333333333333333333333000044444444444444444400000000000000005555500000666666666666600000000000000000000007777777770000000
25+
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*$/);
2426

2527
if (!match) {
2628
throw uiSelectMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
2729
expression);
2830
}
2931

3032
return {
31-
itemName: match[2], // (lhs) Left-hand side,
32-
source: $parse(match[3]),
33-
trackByExp: match[4],
34-
modelMapper: $parse(match[1] || match[2])
33+
itemName: match[4] || match[2], // (lhs) Left-hand side,
34+
keyName: match[3], //for (key, value) syntax
35+
source: $parse(!match[3] ? match[5] + (match[6] || ''): match[5]), //concat source with filters if its an array
36+
sourceName: match[5],
37+
filters: match[6],
38+
trackByExp: match[7],
39+
modelMapper: $parse(match[1] || match[4] || match[2]),
40+
repeatExpression: function (grouped) {
41+
var expression = this.itemName + ' in ' + (grouped ? '$group.items' : '$select.items');
42+
if (this.trackByExp) {
43+
expression += ' track by ' + this.trackByExp;
44+
}
45+
return expression;
46+
}
3547
};
3648

3749
};
@@ -40,11 +52,4 @@ uis.service('uisRepeatParser', ['uiSelectMinErr','$parse', function(uiSelectMinE
4052
return '$group in $select.groups';
4153
};
4254

43-
self.getNgRepeatExpression = function(itemName, source, trackByExp, grouped) {
44-
var expression = itemName + ' in ' + (grouped ? '$group.items' : source);
45-
if (trackByExp) {
46-
expression += ' track by ' + trackByExp;
47-
}
48-
return expression;
49-
};
5055
}]);

0 commit comments

Comments
 (0)