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

Commit 5fa3482

Browse files
committed
add choices grouping using 'group by' expression
1 parent a9aa10c commit 5fa3482

File tree

12 files changed

+248
-31
lines changed

12 files changed

+248
-31
lines changed

examples/bootstrap.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,21 @@
5757
</div>
5858
</div>
5959

60+
<div class="form-group">
61+
<label class="col-sm-3 control-label">Grouped</label>
62+
<div class="col-sm-6">
63+
64+
<ui-select ng-model="person.selected" theme="bootstrap">
65+
<ui-select-match placeholder="Select or search a person in the list...">{{$select.selected.name}}</ui-select-match>
66+
<ui-select-choices group-by="'age'" repeat="item in people | filter: $select.search">
67+
<span ng-bind-html="item.name | highlight: $select.search"></span>
68+
<small ng-bind-html="item.email | highlight: $select.search"></small>
69+
</ui-select-choices>
70+
</ui-select>
71+
72+
</div>
73+
</div>
74+
6075
<div class="form-group">
6176
<label class="col-sm-3 control-label">With a clear button</label>
6277
<div class="col-sm-6">

examples/demo.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@ app.controller('DemoCtrl', function($scope, $http) {
5858

5959
$scope.person = {};
6060
$scope.people = [
61-
{ name: 'Adam', email: '[email protected]', age: 10 },
61+
{ name: 'Adam', email: '[email protected]', age: 12 },
6262
{ name: 'Amalie', email: '[email protected]', age: 12 },
63+
{ name: 'Estefanía', email: 'estefaní[email protected]', age: 21 },
64+
{ name: 'Adrian', email: '[email protected]', age: 21 },
6365
{ name: 'Wladimir', email: '[email protected]', age: 30 },
64-
{ name: 'Samantha', email: '[email protected]', age: 31 },
65-
{ name: 'Estefanía', email: 'estefaní[email protected]', age: 16 },
66-
{ name: 'Natasha', email: '[email protected]', age: 54 },
66+
{ name: 'Samantha', email: '[email protected]', age: 30 },
6767
{ name: 'Nicole', email: '[email protected]', age: 43 },
68-
{ name: 'Adrian', email: 'adrian@email.com', age: 21 }
68+
{ name: 'Natasha', email: 'natasha@email.com', age: 54 }
6969
];
7070

7171
$scope.address = {};

examples/select2-bootstrap3.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,21 @@
6464
</div>
6565
</div>
6666

67+
<div class="form-group">
68+
<label class="col-sm-3 control-label">Grouped</label>
69+
<div class="col-sm-6">
70+
71+
<ui-select ng-model="person.selected" theme="select2" class="form-control">
72+
<ui-select-match placeholder="Select or search a person in the list...">{{$select.selected.name}}</ui-select-match>
73+
<ui-select-choices group-by="'age'" repeat="item in people | filter: $select.search">
74+
<span ng-bind-html="item.name | highlight: $select.search"></span>
75+
<small ng-bind-html="item.email | highlight: $select.search"></small>
76+
</ui-select-choices>
77+
</ui-select>
78+
79+
</div>
80+
</div>
81+
6782
<div class="form-group">
6883
<label class="col-sm-3 control-label">With a clear button</label>
6984
<div class="col-sm-6">

examples/selectize-bootstrap3.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,21 @@
7979
</div>
8080
</div>
8181

82+
<div class="form-group">
83+
<label class="col-sm-3 control-label">Grouped</label>
84+
<div class="col-sm-6">
85+
86+
<ui-select ng-model="person.selected" theme="selectize">
87+
<ui-select-match placeholder="Select or search a person in the list...">{{$select.selected.name}}</ui-select-match>
88+
<ui-select-choices group-by="'age'" repeat="item in people | filter: $select.search">
89+
<span ng-bind-html="item.name | highlight: $select.search"></span>
90+
<small ng-bind-html="item.email | highlight: $select.search"></small>
91+
</ui-select-choices>
92+
</ui-select>
93+
94+
</div>
95+
</div>
96+
8297
<div class="form-group">
8398
<label class="col-sm-3 control-label">With a clear button</label>
8499
<div class="col-sm-6">

karma.conf.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module.exports = function(config) {
1515
'bower_components/angular-mocks/angular-mocks.js',
1616

1717
'dist/select.js',
18+
'test/helpers.js',
1819
'test/**/*.spec.js'
1920
],
2021

src/bootstrap/choices.tpl.html

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
<ul class="ui-select-choices ui-select-choices-content dropdown-menu"
22
role="menu" aria-labelledby="dLabel"
33
ng-show="$select.items.length > 0">
4-
<li class="ui-select-choices-row" ng-class="{active: $select.activeIndex === $index}">
5-
<a class="ui-select-choices-row-inner" href="javascript:void(0)"></a>
4+
<li class="ui-select-choices-group">
5+
<div class="divider" ng-show="$index > 0"></div>
6+
<div class="ui-select-choices-group-label dropdown-header">{{$group}}</div>
7+
<div class="ui-select-choices-row" ng-class="{active: $select.isActive(this)}">
8+
<a href="javascript:void(0)" class="ui-select-choices-row-inner"></a>
9+
</div>
610
</li>
711
</ul>

src/select.css

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,29 @@
9595
overflow-x: hidden;
9696
}
9797

98+
.ui-select-bootstrap .ui-select-choices-row>a {
99+
display: block;
100+
padding: 3px 20px;
101+
clear: both;
102+
font-weight: 400;
103+
line-height: 1.42857143;
104+
color: #333;
105+
white-space: nowrap;
106+
}
107+
108+
.ui-select-bootstrap .ui-select-choices-row>a:hover, .ui-select-bootstrap .ui-select-choices-row>a:focus {
109+
text-decoration: none;
110+
color: #262626;
111+
background-color: #f5f5f5;
112+
}
113+
114+
.ui-select-bootstrap .ui-select-choices-row.active>a {
115+
color: #fff;
116+
text-decoration: none;
117+
outline: 0;
118+
background-color: #428bca;
119+
}
120+
98121
/* fix hide/show angular animation */
99122
.ui-select-match.ng-hide-add,
100123
.ui-select-search.ng-hide-add {

src/select.js

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,12 @@
8888
};
8989
};
9090

91-
self.getNgRepeatExpression = function(lhs, rhs, trackByExp) {
92-
var expression = lhs + ' in ' + rhs;
91+
self.getGroupNgRepeatExpression = function() {
92+
return '($group, $items) in $select.groups'
93+
};
94+
95+
self.getNgRepeatExpression = function(lhs, rhs, trackByExp, grouped) {
96+
var expression = lhs + ' in ' + (grouped ? '$items' : rhs);
9397
if (trackByExp) {
9498
expression += ' track by ' + trackByExp;
9599
}
@@ -153,8 +157,30 @@
153157
}
154158
};
155159

156-
ctrl.parseRepeatAttr = function(repeatAttr) {
157-
var repeat = RepeatParser.parse(repeatAttr);
160+
ctrl.parseRepeatAttr = function(repeatAttr, groupByExp) {
161+
function updateGroups(items) {
162+
ctrl.groups = {};
163+
angular.forEach(items, function(item) {
164+
var groupFn = $scope.$eval(groupByExp);
165+
var groupValue = angular.isFunction(groupFn) ? groupFn(item) : item[groupFn];
166+
if(!ctrl.groups[groupValue]) {
167+
ctrl.groups[groupValue] = [item];
168+
}
169+
else {
170+
ctrl.groups[groupValue].push(item);
171+
}
172+
});
173+
setPlainItems(items);
174+
}
175+
176+
function setPlainItems(items) {
177+
ctrl.items = items;
178+
}
179+
180+
var repeat = RepeatParser.parse(repeatAttr),
181+
setItemsFn = groupByExp ? updateGroups : setPlainItems;
182+
183+
ctrl.itemProperty = repeat.lhs;
158184

159185
// See https://github.com/angular/angular.js/blob/v1.2.15/src/ng/directive/ngRepeat.js#L259
160186
$scope.$watchCollection(repeat.rhs, function(items) {
@@ -169,7 +195,7 @@
169195
throw uiSelectMinErr('items', "Expected an array but got '{0}'.", items);
170196
} else {
171197
// Regular case
172-
ctrl.items = items;
198+
setItemsFn(items);
173199
}
174200
}
175201

@@ -198,6 +224,14 @@
198224
}
199225
};
200226

227+
ctrl.setActiveItem = function(item) {
228+
ctrl.activeIndex = ctrl.items.indexOf(item);
229+
};
230+
231+
ctrl.isActive = function(itemScope) {
232+
return ctrl.items.indexOf(itemScope[ctrl.itemProperty]) === ctrl.activeIndex;
233+
};
234+
201235
// When the user clicks on an item inside the dropdown
202236
ctrl.select = function(item) {
203237
ctrl.selected = item;
@@ -493,28 +527,34 @@
493527

494528
compile: function(tElement, tAttrs) {
495529
var repeat = RepeatParser.parse(tAttrs.repeat);
530+
var groupByExp = tAttrs.groupBy;
496531
return function link(scope, element, attrs, $select, transcludeFn) {
497-
498-
var rows = element.querySelectorAll('.ui-select-choices-row');
499-
if (rows.length !== 1) {
500-
throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-row but got '{0}'.", rows.length);
532+
533+
var groups = element.querySelectorAll('.ui-select-choices-group');
534+
if(groupByExp) {
535+
groups.attr('ng-repeat', RepeatParser.getGroupNgRepeatExpression())
536+
}
537+
538+
var choices = element.querySelectorAll('.ui-select-choices-row');
539+
if (choices.length !== 1) {
540+
throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-row but got '{0}'.", choices.length);
501541
}
502542

503-
rows.attr('ng-repeat', RepeatParser.getNgRepeatExpression(repeat.lhs, '$select.items', repeat.trackByExp))
504-
.attr('ng-mouseenter', '$select.activeIndex = $index')
543+
choices.attr('ng-repeat', RepeatParser.getNgRepeatExpression(repeat.lhs, '$select.items', repeat.trackByExp, groupByExp))
544+
.attr('ng-mouseenter', '$select.setActiveItem('+repeat.lhs+')')
505545
.attr('ng-click', '$select.select(' + repeat.lhs + ')');
506546

507547

508548
transcludeFn(function(clone) {
509549
var rowsInner = element.querySelectorAll('.ui-select-choices-row-inner');
510550
if (rowsInner.length !== 1)
511551
throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-row-inner but got '{0}'.", rowsInner.length);
512-
552+
513553
rowsInner.append(clone);
514554
$compile(element)(scope);
515555
});
516556

517-
$select.parseRepeatAttr(attrs.repeat);
557+
$select.parseRepeatAttr(attrs.repeat, groupByExp);
518558

519559
scope.$watch('$select.search', function() {
520560
$select.activeIndex = 0;
@@ -565,4 +605,4 @@
565605
return query && matchItem ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<span class="ui-select-highlight">$&</span>') : matchItem;
566606
};
567607
});
568-
}());
608+
}());

src/select2/choices.tpl.html

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
<ul class="ui-select-choices ui-select-choices-content select2-results">
2-
<li class="ui-select-choices-row" ng-class="{'select2-highlighted': $select.activeIndex === $index}">
3-
<div class="select2-result-label ui-select-choices-row-inner"></div>
2+
<li class="ui-select-choices-group">
3+
<div class="ui-select-choices-group-label select2-result-label">{{$group}}</div>
4+
<ul class="select2-result-sub">
5+
<li class="ui-select-choices-row" ng-class="{'select2-highlighted': $select.isActive(this)}">
6+
<div class="select2-result-label ui-select-choices-row-inner"></div>
7+
</li>
8+
</ul>
49
</li>
510
</ul>

src/selectize/choices.tpl.html

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
<div ng-show="$select.open" class="ui-select-choices selectize-dropdown single">
22
<div class="ui-select-choices-content selectize-dropdown-content">
3-
<div class="ui-select-choices-row"
4-
ng-class="{'active': $select.activeIndex === $index}">
5-
<div class="option ui-select-choices-row-inner" data-selectable></div>
3+
<div class="ui-select-choices-group optgroup">
4+
<div class="ui-select-choices-group-label optgroup-header">{{$group}}</div>
5+
<div class="ui-select-choices-row" ng-class="{active: $select.isActive(this)}">
6+
<div class="option ui-select-choices-row-inner" data-selectable></div>
7+
</div>
68
</div>
79
</div>
810
</div>

test/helpers.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
beforeEach(function() {
2+
jasmine.addMatchers({
3+
toHaveClass: function(util, customEqualityTesters) {
4+
return {
5+
compare: function(actual, cls) {
6+
var pass = actual.hasClass(cls);
7+
return {
8+
pass: pass,
9+
message: "Expected '" + actual + "'" + (pass ? ' not ' : ' ') + "to have class '" + cls + "'."
10+
}
11+
}
12+
}
13+
}
14+
});
15+
});

0 commit comments

Comments
 (0)