Skip to content

[NEP-12629] Ability to use "OR" between filters (+ filter groups) #108

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "react-filterbar",
"homepage": "https://github.com/jobready/react-filterbar",
"version": "1.7.1",
"version": "3.0",
"authors": [
"Jacob Bass <[email protected]>"
],
859 changes: 636 additions & 223 deletions dist/react-filterbar.js

Large diffs are not rendered by default.

29 changes: 15 additions & 14 deletions dist/react-filterbar.min.js

Large diffs are not rendered by default.

58 changes: 58 additions & 0 deletions docs/react-components-structure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
### React Components Structure

FilterBar
-> FilterList
-> FilterListOption
-> ApplyFiltersButton
-> ClearFiltersButton
-> SaveFiltersButton
-> SavedSearchesList
-> ConfigurationButton
-> ExportResultsButton
-> BatchActionsList
-> FilterDisplay
-> FilterGroup
-> FilterInput
-> FilterButton ("ADD" button)
-> FilterListOption
-> FilterButton ("ADD FILTER" or "OR" button)
-> FilterListOption



### Filters Structure
Class _FilterBarStore_ has the following varables:
```(javascript)
activeFilters = [] // as array of active groups with filters
filters = {} // as initial hash with available filters
```

#### Examples:
##### Case 1
Filter1

```(javascript)
activeFilters = [
[Filter1]
]

```
##### Case 2
Filter1 and Filter2

```(javascript)
activeFilters = [
[Filter1, Filter2]
]

```
##### Case 3
(Filter1 and Filter 2) or Filter2

```(javascript)
activeFilters = [
[Filter1, Filter2],
[Filter3]
]

```
861 changes: 637 additions & 224 deletions example/public/js/react-filterbar.js

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions example/saved_search.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
New Format: '[[{"field":"title","type":"text","label":"Title","filterUid":"title","uid":"title","value":"Book
10"}]]'
Old Format: '[{"uid":"author","type":"select","field":"author","value":"Author 35"},{"uid":"title","type":"text","field":"title","value":"Book
10"}]'
Old Fromat 2: '[{"uid":"author_multi_test","type":"multi_select","field":"author","value":["Author
10"]},{"uid":"author_multi_test","type":"multi_select","field":"author","value":["Author
20"]},{"uid":"author_multi_test","type":"multi_select","field":"author","value":["Author
30"]},{"uid":"author_multi_test","type":"multi_select","field":"author","value":["Author
40"]}]'
14 changes: 12 additions & 2 deletions example/server.rb
Original file line number Diff line number Diff line change
@@ -81,7 +81,17 @@ def loop_over_queries(queries, haystack)
end

def search(needle, haystack)
field, type, value = needle.values_at(*%w(field type value))
field, type, value = nil

if needle.is_a? Array
# Case 1: Filters with new format
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that these are sign-posted.

field, type, value = needle.first.values_at(*%w(field type value))
else
# Case 2: Filters with old format
field, type, value = needle.values_at(*%w(field type value))
end

value ||= ""
case type.to_sym
when :date
haystack.select do |hay|
@@ -103,7 +113,7 @@ def search(needle, haystack)
end
when :text
haystack.select do |hay|
hay.send(field).to_s.include?(value)
value && hay.send(field).to_s.include?(value)
end
else
[]
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-filterbar",
"version": "2.0",
"version": "3.0",
"description": "",
"main": "dist/react-filterbar.js",
"engines": {
74 changes: 38 additions & 36 deletions src/actors/FilterBarActor.js
Original file line number Diff line number Diff line change
@@ -23,10 +23,6 @@ export class FilterBarActor {
this.filterBarStore.enableFilter(filterUid, value);
}

disableFilter(filterUid) {
this.filterBarStore.disableFilter(filterUid);
}

disableAllFilters() {
this.filterBarStore.disableAllFilters();
this.filterBarStore.disableAllQuickFilters();
@@ -37,8 +33,12 @@ export class FilterBarActor {
this.applyFilters();
}

updateFilter(filterUid, propKey, propValue) {
this.filterBarStore.updateFilter(filterUid, propKey, propValue);
updateFilter(groupKey, inputKey, value) {
this.filterBarStore.updateFilter(groupKey, inputKey, value);
}

clearActiveFilter(groupKey, inputKey) {
this.filterBarStore.clearActiveFilter(groupKey, inputKey)
}

applyFilters() {
@@ -59,14 +59,17 @@ export class FilterBarActor {
}

applyQuickFilter(filterName, value, quickFilterName, blockName) {
let filter = this.filterBarStore.getFilter(filterName)
var filter = this.filterBarStore.getFilter(filterName)

if (filter.type === 'multi_select') {
value = value.split(",").map(function (string) {
return string.trim();
});
}

this.filterBarStore.enableQuickFilter(quickFilterName, blockName);
this.enableFilter(filterName, value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you still reference enableFilter() in this file?

this.filterBarStore.setActiveFilters([]);
this.filterBarStore.addGroupFilter(filterName, undefined, value);
this.applyFilters();
}

@@ -103,51 +106,50 @@ export class FilterBarActor {
loadSavedSearch(searchId) {
this.disableAllFilters();

var savedSearch = this.filterBarStore.getSavedSearch(searchId);
var filters = JSON.parse(savedSearch.configuration);
const savedSearch = this.filterBarStore.getSavedSearch(searchId);
const filters = this.parseSavedSearch(savedSearch);

if (this.verifySavedFilters(filters)) {
if (filters instanceof Array) {
filters.forEach((filter) =>
this.enableFilter(filter.uid, filter.value)
);
} else {
for (var filter in filters) {
this.enableFilter(filter, filters[filter]);
}
}

this.filterBarStore.setActiveFilters(filters);
this.applyFilters();
this.filterBarStore.emitChange();
} else {
this.deleteSavedSearch(searchId, 'One of the filters in this saved search cannot be applied anymore. Remove saved search?');
}
}

verifySavedFilters(filters) {
var filtersArr;
parseSavedSearch(savedSearch) {
const savedSearchFilters = JSON.parse(savedSearch.configuration)
var filters = savedSearchFilters;

if (filters instanceof Array) {
filtersArr = filters;
} else {
filtersArr = Object.keys(filters)
if (!Array.isArray(savedSearchFilters)) {
filters = Object.keys(savedSearchFilters)
.map(function (name) {
return { uid: name }
});
}

return new FilterVerificator(this.filterBarStore.getFilters(), filtersArr).verify();
if (!Array.isArray(filters[0])) {
filters = [filters]
}

const ctrl = this;

filters.map(function(groups) {
groups.map(function(filter) {
filter.label = ctrl.filterBarStore.getFilter(filter.uid).label;
});
});

return filters;
}

verifySavedFilters(parsedFilters) {
return new FilterVerificator(this.filterBarStore.getFilters(), parsedFilters).verify();
}

saveFilters(name) {
var filters = [];
for (var [filterUid, filter] of this.filterBarStore.enabledFilters()) {
filters.push({
uid: filterUid,
type: filter.type,
field: filter.field,
value: filter.value,
});
}
const filters = this.filterBarStore.getActiveFilters();

var savedSearchPacket = {
saved_search: {
62 changes: 51 additions & 11 deletions src/app.js
Original file line number Diff line number Diff line change
@@ -48,17 +48,15 @@ function setupConfiguration(configuration) {
configuration.tableConfiguration.page = Number(url.query(true).page);

if (url.query(true).q !== "") {
for (var filter of JSON.parse(url.query(true).q)) {
var configFilter = configuration.filterBarConfiguration.filters[filter.uid];

if (configFilter) {
configFilter.enabled = true;
configFilter.value = filter.value;

if (filter.operator) {
configFilter.operator = filter.operator;
}
}
const activeFilters = JSON.parse(url.query(true).q);
configuration.filterBarConfiguration.activeFilters = [];

if (Array.isArray(activeFilters[0])) {
// Case 1: Filters with new format
configuration.filterBarConfiguration.activeFilters = parseQueryVersion2(activeFilters, configuration);
} else {
// Case 2: Filters with old format
configuration.filterBarConfiguration.activeFilters = parseQueryVersion1(activeFilters, configuration);
}
}

@@ -73,6 +71,48 @@ function setupConfiguration(configuration) {
return configuration;
}

function parseQueryVersion1(activeFilters, configuration) {
const _groupFilters = [];

for (const filter of activeFilters) {
const configFilter = parseAndGetFilter(filter, configuration)
_groupFilters.push(configFilter);
}

return [_groupFilters];
}

function parseQueryVersion2(activeFilters, configuration) {
const results = [];

for (const groupFilters of activeFilters) {
const _groupFilters = []
groupFilters.map(function(filter) {
const configFilter = parseAndGetFilter(filter, configuration)
_groupFilters.push(configFilter);
});
results.push(_groupFilters);
}

return results;
}

function parseAndGetFilter(filter, configuration) {
const configFilter = configuration.filterBarConfiguration.filters[filter.uid];

if (configFilter) {
configFilter.filterUid = filter.uid;
configFilter.uid = filter.uid;
configFilter.value = filter.value;

if (filter.operator) {
configFilter.operator = filter.operator;
}
}

return configFilter;
}

document.addEventListener("DOMContentLoaded", function(){
var configuration = {},
filterableTableNode = document.getElementsByClassName("react-filterable-table")[0];
7 changes: 3 additions & 4 deletions src/components/FilterBar/FilterBar.react.js
Original file line number Diff line number Diff line change
@@ -14,14 +14,12 @@ export class FilterBar extends React.Component {
}

render() {
console.log("react-filterbar 3.2.1");

return (
<div>
<div>
<div className="btn-group margin-bottom-sm">
<FilterList
disabledFilters={this.context.filterBarStore.getDisabled()}
/>
Comment on lines -21 to -23
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the purpose of this previously?

Copy link
Author

@footnoise footnoise Jan 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah @SyntheticDave sorry, potentially I should put that architectural changes in PR description, because it's a huge changes. Definitely will do it before moving PR into Release stage.

So, before we had registered filters in a hash and it was represented in FilterBarStore like that:

{
  "some_filter_key1": {
    "uid" : "some_filter_key1",
    "value" : "",
    "name" : "Some Filter Key 1",
    ...
  }, {
    "some_filter_key2" : {
    "uid" : "some_filter_key2",
    "value" : "",
    "name" : "Some Filter Key 2",
    ...
  }
}

and if a User selected some filters, for example with the key some_filter_key1 we needed mark it somehow and don't allow user to select that filter again (it should be removed from dropdown) and to make it we should call method this.context.filterBarStore.getDisabled() and pass it as disabledFilters property into component.

The new logic works in completely different way, we still have original registered hash of filters (as I described above) but data and values we have in the same FilterBarStore but in activeFilters array. In this case I use groupKey and inputKey properties to manage values data. It's a double array, which has groupKey as ID of the first array and inputKey as ID for the second array.

So, that means we should hide filters from dropdown and we don't need to set property disabledFilters anymore.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation. I definitely like the change, but you're right that it's a big one.


<ApplyFiltersButton
filterBarActor={this.context.filterBarActor}
/>
@@ -57,6 +55,7 @@ export class FilterBar extends React.Component {
<FilterDisplay
filterBarActor={this.context.filterBarActor}
filterBarStore={this.context.filterBarStore}
filters={ this.context.filterBarStore.getActiveFilters() }
/>
</div>
</div>
Loading