Skip to content

Commit 6d3a128

Browse files
committed
feat(pfFilter): add 'complex-select' feature to pfFilterFields component
1 parent ca84a9a commit 6d3a128

File tree

6 files changed

+171
-26
lines changed

6 files changed

+171
-26
lines changed

src/filters/examples/filter.js

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@
1414
* <li>.id - (String) Optional unique Id for the filter field, useful for comparisons
1515
* <li>.title - (String) The title to display for the filter field
1616
* <li>.placeholder - (String) Text to display when no filter value has been entered
17-
* <li>.filterType - (String) The filter input field type (any html input type, or 'select' for a single select box)
18-
* <li>.filterValues - (Array) List of valid select values used when filterType is 'select'
17+
* <li>.filterMultiselect - (Boolean) In `complex-select`, allow selection of multiple values per category. Optional, default is `false`
18+
* <li>.filterType - (String) The filter input field type (any html input type, or 'select' for a single select box or 'complex-select' for a category select box)
19+
* <li>.filterValues - (Array) List of valid select values used when filterType is 'select' or 'complex-select' (in where these values serve as case insensitve keys for .filterCategories objects)
20+
* <li>.filterCategories - (Array of (Objects)) For 'complex-select' only, array of objects whoes keys (case insensitive) match the .filterValues, these objects include each of the filter fields above (sans .placeholder)
21+
* <li>.filterCategoriesPlaceholder - (String) Text to display in `complex-select` category value select when no filter value has been entered, Optional
22+
* <li>.filterDelimiter - (String) Delimiter separating 'complex-select' category and value. Optional, default is a space, ' '
1923
* </ul>
2024
* <li>.appliedFilters - (Array) List of the currently applied filters
2125
* <li>.resultsCount - (int) The number of results returned after the current applied filters have been applied
@@ -45,6 +49,9 @@
4549
<div class="col-md-2">
4650
<span>{{item.birthMonth}}</span>
4751
</div>
52+
<div class="col-md-4">
53+
<span>{{item.car}}</span>
54+
</div>
4855
</div>
4956
</div>
5057
</div>
@@ -67,27 +74,33 @@
6774
{
6875
name: "Fred Flintstone",
6976
address: "20 Dinosaur Way, Bedrock, Washingstone",
70-
birthMonth: 'February'
77+
birthMonth: 'February',
78+
car: 'Toyota-Echo'
7179
},
7280
{
7381
name: "John Smith",
7482
address: "415 East Main Street, Norfolk, Virginia",
75-
birthMonth: 'October'
83+
birthMonth: 'October',
84+
car: 'Subaru-Outback'
85+
7686
},
7787
{
7888
name: "Frank Livingston",
7989
address: "234 Elm Street, Pittsburgh, Pennsylvania",
80-
birthMonth: 'March'
90+
birthMonth: 'March',
91+
car: 'Toyota-Prius'
8192
},
8293
{
8394
name: "Judy Green",
8495
address: "2 Apple Boulevard, Cincinatti, Ohio",
85-
birthMonth: 'December'
96+
birthMonth: 'December',
97+
car: 'Subaru-Impreza'
8698
},
8799
{
88100
name: "Pat Thomas",
89101
address: "50 Second Street, New York, New York",
90-
birthMonth: 'February'
102+
birthMonth: 'February',
103+
car: 'Subaru-Outback'
91104
}
92105
];
93106
$scope.items = $scope.allItems;
@@ -102,6 +115,8 @@
102115
match = item.address.match(re) !== null;
103116
} else if (filter.id === 'birthMonth') {
104117
match = item.birthMonth === filter.value;
118+
} else if (filter.id === 'car') {
119+
match = item.car === filter.value;
105120
}
106121
return match;
107122
};
@@ -160,6 +175,24 @@
160175
placeholder: 'Filter by Birth Month',
161176
filterType: 'select',
162177
filterValues: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
178+
},
179+
{
180+
id: 'car',
181+
title: 'Car',
182+
placeholder: 'Filter by Car Make',
183+
filterType: 'complex-select',
184+
filterValues: ['Subaru', 'Toyota'],
185+
filterDelimiter: '-',
186+
filterCategoriesPlaceholder: 'Filter by Car Model',
187+
filterCategories: {subaru: {
188+
id: 'subaru',
189+
title: 'Subaru',
190+
filterValues: ['Outback', 'Crosstrek', 'Impreza']},
191+
toyota: {
192+
id: 'toyota',
193+
title: 'Toyota',
194+
filterValues: ['Prius', 'Corolla', 'Echo']}
195+
}
163196
}
164197
],
165198
resultsCount: $scope.items.length,

src/filters/filters.less

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22
a {
33
cursor: pointer;
44
}
5+
.input-group {
6+
.input-group-btn {
7+
.dropdown-menu>.selected>a {
8+
background-color: @color-pf-blue !important;
9+
border-color: #0076b7 !important;
10+
color: @color-pf-white !important;
11+
}
12+
}
13+
}
14+
.category-select{
15+
display: flex;
16+
}
17+
.category-select-value{
18+
border-left-width: 0 !important;
19+
}
520
}
621
.filter-pf.filter-fields {
722
.form-group {
@@ -19,15 +34,6 @@
1934
font-weight: 400;
2035
}
2136
}
22-
.input-group {
23-
.input-group-btn {
24-
.dropdown-menu>.selected>a {
25-
background-color: @color-pf-blue !important;
26-
border-color: #0076b7 !important;
27-
color: @color-pf-white !important;
28-
}
29-
}
30-
}
3137

3238
pf-filter-panel {
3339
.dropdown > .dropdown-menu, .input-group-btn > .dropdown-menu {

src/filters/simple-filter/filter-component.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,17 @@ angular.module('patternfly.filters').component('pfFilter', {
1818
};
1919

2020
function filterExists (filter) {
21-
var foundFilter = _.find(ctrl.config.appliedFilters, {title: filter.title, value: filter.value});
22-
return foundFilter !== undefined;
21+
return angular.isDefined(_.find(ctrl.config.appliedFilters, {title: filter.title, value: filter.value}));
22+
}
23+
24+
function findDuplicateCategory (field, value) {
25+
var duplicateValue;
26+
27+
function searchAppliedFilters (item) {
28+
return _.includes(item.value, _.split(value, field.filterDelimiter, 1)) ? duplicateValue = item : null;
29+
}
30+
31+
return _.some(ctrl.config.appliedFilters, searchAppliedFilters) ? duplicateValue : null;
2332
}
2433

2534
function enforceSingleSelect (filter) {
@@ -33,12 +42,17 @@ angular.module('patternfly.filters').component('pfFilter', {
3342
type: field.filterType,
3443
value: value
3544
};
45+
3646
if (!filterExists(newFilter)) {
3747

3848
if (newFilter.type === 'select') {
3949
enforceSingleSelect(newFilter);
4050
}
4151

52+
if (field.filterType === 'complex-select' && !field.filterMultiselect && findDuplicateCategory(field, value)) {
53+
_.remove(ctrl.config.appliedFilters, findDuplicateCategory(field, value));
54+
}
55+
4256
ctrl.config.appliedFilters.push(newFilter);
4357

4458
if (ctrl.config.onFilterChange) {

src/filters/simple-filter/filter-fields-component.js

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@
1414
* <li>.id - (String) Optional unique Id for the filter field, useful for comparisons
1515
* <li>.title - (String) The title to display for the filter field
1616
* <li>.placeholder - (String) Text to display when no filter value has been entered
17-
* <li>.filterType - (String) The filter input field type (any html input type, or 'select' for a select box)
18-
* <li>.filterValues - (Array) List of valid select values used when filterType is 'select'
17+
* <li>.filterMultiselect - (Boolean) In `complex-select`, allow selection of multiple values per category. Optional, default is `false`
18+
* <li>.filterType - (String) The filter input field type (any html input type, or 'select' for a single select box or 'complex-select' for a category select box)
19+
* <li>.filterValues - (Array) List of valid select values used when filterType is 'select' or 'complex-select' (in where these values serve as case insensitve keys for .filterCategories objects)
20+
* <li>.filterCategories - (Array of (Objects)) For 'complex-select' only, array of objects whoes keys (case insensitive) match the .filterValues, these objects include each of the filter fields above (sans .placeholder)
21+
* <li>.filterCategoriesPlaceholder - (String) Text to display in `complex-select` category value select when no filter value has been entered, Optional
22+
* <li>.filterDelimiter - (String) Delimiter separating 'complex-select' category and value. Optional, default is a space, ' '
1923
* </ul>
2024
* <li>.appliedFilters - (Array) List of the currently applied filters
2125
* </ul>
@@ -54,13 +58,29 @@ angular.module('patternfly.filters').component('pfFilterFields', {
5458

5559
function selectField (item) {
5660
ctrl.currentField = item;
61+
ctrl.currentField.filterDelimiter = ctrl.currentField.filterDelimiter || ' ';
5762
ctrl.currentValue = null;
5863
}
5964

60-
function selectValue (filterValue) {
61-
if (angular.isDefined(filterValue)) {
62-
ctrl.addFilterFn(ctrl.currentField, filterValue);
63-
ctrl.currentValue = null;
65+
function selectValue (filterValue, valueType) {
66+
if (angular.isDefined (filterValue)) {
67+
if (ctrl.currentField.filterType === 'complex-select') {
68+
switch (valueType) {
69+
case 'filter-category':
70+
ctrl.filterCategory = filterValue;
71+
ctrl.filterValue = null;
72+
break;
73+
case 'filter-value':
74+
ctrl.filterValue = filterValue;
75+
break;
76+
}
77+
if (ctrl.filterCategory && ctrl.filterValue) {
78+
ctrl.addFilterFn(ctrl.currentField, ctrl.filterCategory + ctrl.currentField.filterDelimiter + ctrl.filterValue);
79+
}
80+
} else {
81+
ctrl.addFilterFn(ctrl.currentField, filterValue);
82+
ctrl.currentValue = null;
83+
}
6484
}
6585
}
6686

src/filters/simple-filter/filter-fields.html

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
</li>
1515
</ul>
1616
</div>
17-
<div ng-if="$ctrl.currentField.filterType !== 'select'">
17+
<div ng-if="$ctrl.currentField.filterType !== 'select' && $ctrl.currentField.filterType !== 'complex-select'">
1818
<input class="form-control" type="{{$ctrl.currentField.filterType}}" ng-model="$ctrl.currentValue"
1919
placeholder="{{$ctrl.currentField.placeholder}}"
2020
ng-keypress="$ctrl.onValueKeyPress($event)"/>
@@ -39,5 +39,45 @@
3939
</ul>
4040
</div>
4141
</div>
42+
<div ng-if="$ctrl.currentField.filterType === 'complex-select'" class="category-select">
43+
44+
<div class="btn-group bootstrap-select form-control filter-select" uib-dropdown >
45+
<button type="button" uib-dropdown-toggle class="btn btn-default dropdown-toggle">
46+
<span class="filter-option pull-left">{{$ctrl.filterCategory || $ctrl.currentField.placeholder}}</span>
47+
<span class="caret"></span>
48+
</button>
49+
<ul uib-dropdown-menu class="dropdown-menu-right" role="menu">
50+
<li ng-if="$ctrl.currentField.placeholder">
51+
<a role="menuitem" tabindex="-1" ng-click="$ctrl.selectValue()">
52+
{{$ctrl.currentField.placeholder}}
53+
</a>
54+
</li>
55+
<li ng-repeat="filterCategory in $ctrl.currentField.filterValues" ng-class="{'selected': filterCategory === $ctrl.filterCategory}">
56+
<a role="menuitem" tabindex="-1" ng-click="$ctrl.selectValue(filterCategory, 'filter-category')">
57+
{{filterCategory}}
58+
</a>
59+
</li>
60+
</ul>
61+
</div>
62+
63+
<div class="btn-group bootstrap-select form-control filter-select " uib-dropdown >
64+
<button type="button" uib-dropdown-toggle class="btn btn-default dropdown-toggle category-select-value">
65+
<span class="filter-option pull-left">{{$ctrl.filterValue || $ctrl.currentField.filterCategoriesPlaceholder}}</span>
66+
<span class="caret"></span>
67+
</button>
68+
<ul uib-dropdown-menu class="dropdown-menu-right" role="menu">
69+
<li ng-if="$ctrl.currentField.placeholder">
70+
<a role="menuitem" tabindex="-1" ng-click="$ctrl.selectValue()">
71+
{{$ctrl.currentField.filterCategoriesPlaceholder}}
72+
</a>
73+
</li>
74+
<li ng-repeat="filterValue in $ctrl.currentField.filterCategories[$ctrl.filterCategory.toLowerCase()].filterValues" ng-class="{'selected': filterValue === $ctrl.filterValue}">
75+
<a role="menuitem" tabindex="-1" ng-click="$ctrl.selectValue(filterValue, 'filter-value')">
76+
{{filterValue}}
77+
</a>
78+
</li>
79+
</ul>
80+
</div>
81+
</div>
4282
</div>
4383
</div>

test/filters/filter.spec.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,24 @@ describe('Directive: pfFilter', function () {
4141
placeholder: 'Filter by Birth Month',
4242
filterType: 'select',
4343
filterValues: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
44+
},
45+
{
46+
id: 'car',
47+
title: 'Car',
48+
placeholder: 'Filter by Car Make',
49+
filterType: 'complex-select',
50+
filterValues: ['Subaru', 'Toyota'],
51+
filterDelimiter: '-',
52+
filterCategoriesPlaceholder: 'Filter by Car Model',
53+
filterCategories: {subaru: {
54+
id: 'subaru',
55+
title: 'Subaru',
56+
filterValues: ['Outback', 'Crosstrek', 'Impreza']},
57+
toyota: {
58+
id: 'toyota',
59+
title: 'Toyota',
60+
filterValues: ['Prius', 'Corolla', 'Echo']}
61+
}
4462
}
4563
],
4664
resultsCount: 5,
@@ -54,7 +72,7 @@ describe('Directive: pfFilter', function () {
5472

5573
it('should have correct number of filter fields', function () {
5674
var fields = element.find('.filter-field');
57-
expect(fields.length).toBe(3);
75+
expect(fields.length).toBe(4);
5876
});
5977

6078
it('should have correct number of results', function () {
@@ -104,6 +122,20 @@ describe('Directive: pfFilter', function () {
104122
expect(items.length).toBe($scope.filterConfig.fields[2].filterValues.length + 1); // +1 for the null value
105123
});
106124

125+
it ('should add a dropdown complex-select when a select type is chosen', function() {
126+
var filterSelect = element.find('.filter-select');
127+
var fields = element.find('.filter-field');
128+
129+
expect(filterSelect.length).toBe(0);
130+
eventFire(fields[3], 'click');
131+
$scope.$digest();
132+
filterSelect = element.find('.filter-select');
133+
expect(filterSelect.length).toBe(2);
134+
135+
var items = filterSelect.find('li');
136+
expect(items.length).toBe($scope.filterConfig.fields[3].filterValues.length + 2); // +2 for the null category and value
137+
});
138+
107139
it ('should clear a filter when the close button is clicked', function () {
108140
var closeButtons;
109141

0 commit comments

Comments
 (0)