Skip to content

Commit 7cdf406

Browse files
committed
pfFilterPanel
1 parent a3dc2c9 commit 7cdf406

15 files changed

+728
-5
lines changed
Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
/**
2+
* @ngdoc directive
3+
* @name patternfly.filters.component:pfFilterPanel
4+
* @restrict E
5+
*
6+
* @description
7+
* The Filter Panel is opened and closed by clicking on the Filter button. It can contain any HTML desired by
8+
* the application developer. As such, the application developer is repsonsible for constructing the <code>appliedFilters</code> array which is passed to
9+
* the filter results tags.
10+
* The application developer is responsible for filtering items based on the <code>appliedFilters</code> array; both when a
11+
* filter is changed on the panel, or when a filter tag is 'cleared' (by user clicking on 'x' in the filter results tag).
12+
* <br>
13+
*
14+
* @param {object} config configuration settings for the filters:<br/>
15+
* <ul style='list-style-type: none'>
16+
* <li>.appliedFilters - (Array) List of the currently applied filters. Used to render the filter results tags and returned
17+
* in the <code>onFilterChange</code> function where it can be used to filter a set of items.
18+
* <ul style='list-style-type: none'>
19+
* <li>.id - (String) Id for the filter, useful for comparisons
20+
* <li>.title - (String) The title to display for the filter results tag
21+
* <li>.values - (Array) The value(s) to display for the filter results tag. Ie. [title: [value1 x] [value2 x]]
22+
* </ul>
23+
* <li>.resultsCount - (int) The number of results returned after the current applied filters have been applied
24+
* <li>.totalCount - (int) The total number of items before any filters have been applied. The 'm' in the label: 'n' of 'm'
25+
* <li>.resultsLabel - (String) Optional label for the result units. Default is "Results". Ex: "'n' of 'm' Results"
26+
* <li>.onFilterChange - ( function(appliedFilters, changedFilterId, changedFilterValue) ) Function to call when the applied
27+
* filters list changes. Triggered by user clicking on 'x' or 'Clear All Filters' in the filter result tags.
28+
* <code>changedFilterId</code> and <code>changedFilterValue</code> are returned after user clicks on an 'x' in a tag to
29+
* denote which filter and filter value was cleared. <code>changedFilterId</code> and <code>changedFilterValue</code> are
30+
* not returned when 'Clear All Filters' link is clicked.
31+
* </ul>
32+
*
33+
* @example
34+
<example module="patternfly.filters">
35+
<file name="index.html">
36+
<div ng-controller="ViewCtrl" class="row example-container">
37+
<div class="col-md-12">
38+
<pf-filter-panel id="exampleFilter" config="filterConfig">
39+
<div class="filter-panel-container">
40+
<input type="text" ng-model="filterPanelModel[0].value"
41+
class="keyword-filter"
42+
placeholder="{{filterPanelModel[0].placeholder}}"
43+
ng-keypress="onKeywordKeyPress($event)"/>
44+
<div class="category" ng-repeat="filter in filterPanelModel" ng-if="!$first">{{filter.title}}
45+
<ul>
46+
<li ng-repeat="value in filter.values">
47+
<input type="checkbox" ng-model="value.selected" ng-change="filterChanged()"/>
48+
<span class="category-option-label">{{value.title}}</span>
49+
</li>
50+
</ul>
51+
</div>
52+
</div>
53+
</pf-filter-panel>
54+
</div>
55+
<hr class="col-md-12">
56+
<div class="col-md-12">
57+
<label class="events-label">Valid Items: </label>
58+
</div>
59+
<div class="col-md-12">
60+
<div class="col-md-12 cfme-row-column">
61+
<div class="row">
62+
<div class="col-md-2"><b>ID</b></div>
63+
<div class="col-md-2"><b>Keyword</b></div>
64+
<div class="col-md-4"><b>Category One</b></div>
65+
<div class="col-md-4"><b>Category Two</b></div>
66+
</div>
67+
</div>
68+
<div ng-repeat="item in items" class="col-md-12 cfme-row-column">
69+
<div class="row">
70+
<div class="col-md-2">
71+
<span>{{item.id}}</span>
72+
</div>
73+
<div class="col-md-2">
74+
<span>{{item.keyword}}</span>
75+
</div>
76+
<div class="col-md-4">
77+
<span>{{item.categoryOne}}</span>
78+
</div>
79+
<div class="col-md-4">
80+
<span>{{item.categoryTwo}}</span>
81+
</div>
82+
</div>
83+
</div>
84+
</div>
85+
</div>
86+
</file>
87+
88+
<file name="script.js">
89+
angular.module('patternfly.filters').controller('ViewCtrl', ['$scope',
90+
function ($scope) {
91+
$scope.filterPanelModel = [
92+
{
93+
id: 'keyword',
94+
title: 'Keyword',
95+
placeholder: 'Filter by Keyword',
96+
filterType: 'input',
97+
values: []
98+
},
99+
{
100+
id: 'category1',
101+
title: 'Category One',
102+
filterType: 'checkbox',
103+
values: [
104+
{
105+
id: 'val1',
106+
title: 'Value 1',
107+
value: 'Value 1',
108+
selected: false
109+
},
110+
{
111+
id: 'val2',
112+
title: 'Value 2',
113+
value: 'Value 2',
114+
selected: false
115+
},
116+
{
117+
id: 'val3',
118+
title: 'Value 3',
119+
value: 'Value 3',
120+
selected: false
121+
}
122+
]
123+
},
124+
{
125+
id: 'category2',
126+
title: 'Category Two',
127+
filterType: 'checkbox',
128+
values: [
129+
{
130+
id: 'val1',
131+
title: 'Value 1',
132+
value: 'Value 1',
133+
selected: false
134+
},
135+
{
136+
id: 'val2',
137+
title: 'Value 2',
138+
value: 'Value 2',
139+
selected: false
140+
},
141+
{
142+
id: 'val3',
143+
title: 'Value 3',
144+
value: 'Value 3',
145+
selected: false
146+
}
147+
]
148+
}
149+
];
150+
151+
$scope.filtersText = '';
152+
153+
$scope.allItems = [
154+
{
155+
id: "1",
156+
keyword: "Foo",
157+
categoryOne: "Value 1",
158+
categoryTwo: "Value 1"
159+
},
160+
{
161+
id: "2",
162+
keyword: "Bar",
163+
categoryOne: "Value 2",
164+
categoryTwo: "Value 1"
165+
},
166+
{
167+
id: "3",
168+
keyword: "Foo",
169+
categoryOne: "Value 3",
170+
categoryTwo: "Value 1"
171+
},
172+
{
173+
id: "4",
174+
keyword: "Bar",
175+
categoryOne: "Value 1",
176+
categoryTwo: "Value 2"
177+
},
178+
{
179+
id: "5",
180+
keyword: "Foo",
181+
categoryOne: "Value 2",
182+
categoryTwo: "Value 2"
183+
},
184+
{
185+
id: "6",
186+
keyword: "Bar",
187+
categoryOne: "Value 3",
188+
categoryTwo: "Value 2"
189+
},
190+
{
191+
id: "7",
192+
keyword: "Foo",
193+
categoryOne: "Value 1",
194+
categoryTwo: "Value 3"
195+
},
196+
{
197+
id: "8",
198+
keyword: "Bar",
199+
categoryOne: "Value 2",
200+
categoryTwo: "Value 3"
201+
},
202+
{
203+
id: "9",
204+
keyword: "Foo",
205+
categoryOne: "Value 3",
206+
categoryTwo: "Value 3"
207+
}
208+
];
209+
$scope.items = $scope.allItems;
210+
211+
// called when filter cleared by hitting 'x' in filter results tag, or 'Clear All Filters' link
212+
var onFilterChange = function (appliedFilters, changedFilterId, changedFilterValue) {
213+
if (angular.isDefined(changedFilterId) && angular.isDefined(changedFilterValue)) {
214+
updateFilterPanelModel(changedFilterId, changedFilterValue);
215+
} else {
216+
// the 'Clear All Filters' link was clicked
217+
resetFilterPanelModel();
218+
}
219+
// filter items
220+
filterItems(appliedFilters);
221+
};
222+
223+
$scope.filterConfig = {
224+
resultsLabel: "Items",
225+
resultsCount: $scope.items.length,
226+
totalCount: $scope.allItems.length,
227+
appliedFilters: [],
228+
onFilterChange: onFilterChange
229+
};
230+
231+
// called when filter is changed in the filter panel
232+
$scope.filterChanged = function() {
233+
applyFilters();
234+
};
235+
236+
$scope.onKeywordKeyPress = function(keyEvent) {
237+
if (keyEvent.which === 13 && $scope.filterPanelModel[0].value.length > 0) {
238+
// store new keywoard filter value in values array
239+
$scope.filterPanelModel[0].values.push($scope.filterPanelModel[0].value);
240+
// remove the keyword value to show placeholder text
241+
delete $scope.filterPanelModel[0].value;
242+
applyFilters();
243+
}
244+
};
245+
246+
var applyFilters = function () {
247+
var newAppliedFilters = [];
248+
_.forEach($scope.filterPanelModel, function(filter) {
249+
var filterValues = [];
250+
if (angular.isDefined(filter.values) && filter.values.length > 0) {
251+
if(filter.filterType === "checkbox") {
252+
// the values of the selected checkboxes are stored in a single new appliedFilter
253+
_.forEach(filter.values, function(value) {
254+
if(value.selected) {
255+
filterValues.push(value.value)
256+
}
257+
});
258+
if (filterValues.length > 0) {
259+
newAppliedFilters.push( createAppliedFilter (filter, filterValues) );
260+
}
261+
} else {
262+
// each keyword value gets a new appliedFilter
263+
_.forEach(filter.values, function(value) {
264+
filterValues = [value];
265+
newAppliedFilters.push( createAppliedFilter (filter, filterValues) );
266+
});
267+
}
268+
}
269+
});
270+
271+
// sets the filter result tags
272+
$scope.filterConfig.appliedFilters = newAppliedFilters;
273+
// filter items
274+
filterItems($scope.filterConfig.appliedFilters);
275+
};
276+
277+
var createAppliedFilter = function(filter, values) {
278+
return {
279+
id: filter.id,
280+
title: filter.title,
281+
values: values
282+
};
283+
};
284+
285+
var updateFilterPanelModel = function (changedFilterId, changedFilterValue) {
286+
var changedFilter = _.find($scope.filterPanelModel, function(f) { return f.id === changedFilterId});
287+
if(changedFilter.filterType === "checkbox") {
288+
// unselect the checkbox
289+
_.find(changedFilter.values, function(v) {return v.value == changedFilterValue}).selected = false;
290+
} else {
291+
// remove keyword from values array
292+
_.remove(changedFilter.values, function(v) {return v == changedFilterValue});
293+
}
294+
};
295+
296+
var resetFilterPanelModel = function () {
297+
_.forEach($scope.filterPanelModel, function(filter) {
298+
if (angular.isDefined(filter.values) && filter.values.length > 0) {
299+
if(filter.filterType === "checkbox") {
300+
// unselect all checkboxes
301+
filter.values.forEach(function (value) {
302+
value.selected = false;
303+
});
304+
} else {
305+
// clear all keyword filter values
306+
filter.values = [];
307+
}
308+
}
309+
});
310+
};
311+
312+
var filterItems = function (filters) {
313+
$scope.items = [];
314+
if (filters && filters.length > 0) {
315+
_.forEach($scope.allItems, function(item) {
316+
if (matchesFilters(item, filters)) {
317+
$scope.items.push(item);
318+
}
319+
});
320+
} else {
321+
$scope.items = $scope.allItems;
322+
}
323+
$scope.filterConfig.resultsCount = $scope.items.length;
324+
};
325+
326+
var matchesFilters = function (item, filters) {
327+
var matches = true;
328+
329+
_.forEach(filters, function(filter) {
330+
if (!matchesFilter(item, filter)) {
331+
matches = false;
332+
return false;
333+
}
334+
});
335+
return matches;
336+
};
337+
338+
var matchesFilter = function (item, filter) {
339+
var match = true;
340+
341+
if (filter.id === 'keyword') {
342+
var re = new RegExp(filter.values[0], 'i');
343+
match = item.keyword.match(re) !== null;
344+
} else if (filter.id === 'category1') {
345+
// values are OR'ed
346+
_.forEach(filter.values, function(value) {
347+
match = item.categoryOne === value;
348+
return !match;
349+
});
350+
} else if (filter.id === 'category2') {
351+
// values are OR'ed
352+
_.forEach(filter.values, function(value) {
353+
match = item.categoryTwo === value;
354+
return !match;
355+
});
356+
}
357+
return match;
358+
};
359+
}
360+
]);
361+
</file>
362+
</example>
363+
*/
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
angular.module('patternfly.filters').component('pfFilterPanel', {
2+
bindings: {
3+
config: '='
4+
},
5+
transclude: true,
6+
templateUrl: 'filters/filter-panel/filter-panel.html',
7+
controller: function () {
8+
'use strict';
9+
10+
var ctrl = this;
11+
}
12+
});

0 commit comments

Comments
 (0)