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

Commit 4d8ea26

Browse files
committed
Merge pull request #49 from just-boris/master
Add choices grouping using 'group by' expression
2 parents a9aa10c + 59d1d18 commit 4d8ea26

12 files changed

+257
-37
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="'group'" 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: 8 additions & 8 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 },
62-
{ name: 'Amalie', email: '[email protected]', age: 12 },
63-
{ name: 'Wladimir', email: 'wladimir@email.com', age: 30 },
64-
{ name: 'Samantha', email: 'samantha@email.com', age: 31 },
65-
{ name: 'Estefanía', email: 'estefanía@email.com', age: 16 },
66-
{ name: 'Natasha', email: 'natasha@email.com', age: 54 },
67-
{ name: 'Nicole', email: '[email protected]', age: 43 },
68-
{ name: 'Adrian', email: 'adrian@email.com', age: 21 }
61+
{ name: 'Adam', email: '[email protected]', group: 'Foo', age: 12 },
62+
{ name: 'Amalie', email: '[email protected]', group: 'Foo', age: 12 },
63+
{ name: 'Estefanía', email: 'estefanía@email.com', group: 'Foo', age: 21 },
64+
{ name: 'Adrian', email: 'adrian@email.com', group: 'Foo', age: 21 },
65+
{ name: 'Wladimir', email: 'wladimir@email.com', group: 'Foo', age: 30 },
66+
{ name: 'Samantha', email: 'samantha@email.com', group: 'bar', age: 30 },
67+
{ name: 'Nicole', email: '[email protected]', group: 'bar', age: 43 },
68+
{ name: 'Natasha', email: 'natasha@email.com', group: 'Baz', 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="'group'" 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="'group'" 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: 57 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,33 @@
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+
ctrl.items = [];
174+
angular.forEach(Object.keys(ctrl.groups).sort(), function(group) {
175+
ctrl.items = ctrl.items.concat(ctrl.groups[group]);
176+
});
177+
}
178+
179+
function setPlainItems(items) {
180+
ctrl.items = items;
181+
}
182+
183+
var repeat = RepeatParser.parse(repeatAttr),
184+
setItemsFn = groupByExp ? updateGroups : setPlainItems;
185+
186+
ctrl.itemProperty = repeat.lhs;
158187

159188
// See https://github.com/angular/angular.js/blob/v1.2.15/src/ng/directive/ngRepeat.js#L259
160189
$scope.$watchCollection(repeat.rhs, function(items) {
@@ -169,7 +198,7 @@
169198
throw uiSelectMinErr('items', "Expected an array but got '{0}'.", items);
170199
} else {
171200
// Regular case
172-
ctrl.items = items;
201+
setItemsFn(items);
173202
}
174203
}
175204

@@ -198,6 +227,14 @@
198227
}
199228
};
200229

230+
ctrl.setActiveItem = function(item) {
231+
ctrl.activeIndex = ctrl.items.indexOf(item);
232+
};
233+
234+
ctrl.isActive = function(itemScope) {
235+
return ctrl.items.indexOf(itemScope[ctrl.itemProperty]) === ctrl.activeIndex;
236+
};
237+
201238
// When the user clicks on an item inside the dropdown
202239
ctrl.select = function(item) {
203240
ctrl.selected = item;
@@ -493,28 +530,34 @@
493530

494531
compile: function(tElement, tAttrs) {
495532
var repeat = RepeatParser.parse(tAttrs.repeat);
533+
var groupByExp = tAttrs.groupBy;
496534
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);
535+
536+
if(groupByExp) {
537+
var groups = element.querySelectorAll('.ui-select-choices-group');
538+
groups.attr('ng-repeat', RepeatParser.getGroupNgRepeatExpression());
539+
}
540+
541+
var choices = element.querySelectorAll('.ui-select-choices-row');
542+
if (choices.length !== 1) {
543+
throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-row but got '{0}'.", choices.length);
501544
}
502545

503-
rows.attr('ng-repeat', RepeatParser.getNgRepeatExpression(repeat.lhs, '$select.items', repeat.trackByExp))
504-
.attr('ng-mouseenter', '$select.activeIndex = $index')
546+
choices.attr('ng-repeat', RepeatParser.getNgRepeatExpression(repeat.lhs, '$select.items', repeat.trackByExp, groupByExp))
547+
.attr('ng-mouseenter', '$select.setActiveItem('+repeat.lhs+')')
505548
.attr('ng-click', '$select.select(' + repeat.lhs + ')');
506549

507550

508551
transcludeFn(function(clone) {
509552
var rowsInner = element.querySelectorAll('.ui-select-choices-row-inner');
510553
if (rowsInner.length !== 1)
511554
throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-row-inner but got '{0}'.", rowsInner.length);
512-
555+
513556
rowsInner.append(clone);
514557
$compile(element)(scope);
515558
});
516559

517-
$select.parseRepeatAttr(attrs.repeat);
560+
$select.parseRepeatAttr(attrs.repeat, groupByExp);
518561

519562
scope.$watch('$select.search', function() {
520563
$select.activeIndex = 0;
@@ -565,4 +608,4 @@
565608
return query && matchItem ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<span class="ui-select-highlight">$&</span>') : matchItem;
566609
};
567610
});
568-
}());
611+
}());

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)