Skip to content

Commit

Permalink
[O2B-532] Create a reusable filtering system
Browse files Browse the repository at this point in the history
  • Loading branch information
martinboulais committed Apr 25, 2023
1 parent 934f189 commit 06b9a15
Show file tree
Hide file tree
Showing 14 changed files with 1,014 additions and 35 deletions.
61 changes: 61 additions & 0 deletions lib/public/components/Filters/common/FilterModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* @license
* Copyright CERN and copyright holders of ALICE O2. This software is
* distributed under the terms of the GNU General Public License v3 (GPL
* Version 3), copied verbatim in the file "COPYING".
*
* See http://alice-o2.web.cern.ch/license for full licensing information.
*
* In applying this license CERN does not waive the privileges and immunities
* granted to it by virtue of its status as an Intergovernmental Organization
* or submit itself to any jurisdiction.
*/
import { Observable } from '/js/src/index.js';

/**
* Model storing the state of a given filter
*/
export class FilterModel extends Observable {
/**
* Constructor
*/
constructor() {
super();

this._visualChange$ = new Observable();
}

/**
* Reset the filter to its initial state
* @return {void}
*/
reset() {
}

/**
* States if the filter has been filled with a valid value
*
* @return {boolean} true if the filter is filled
*/
get isEmpty() {
return true;
}

/**
* Returns the normalized value of the filter, that can be used as URL parameter
*
* @return {string|number|object|array|null} the normalized value
*/
get normalized() {
return null;
}

/**
* Returns the observable notified any time there is a visual change which has no impact on the actual filter value
*
* @return {Observable} the observable
*/
get visualChange$() {
return this._visualChange$;
}
}
146 changes: 146 additions & 0 deletions lib/public/components/Filters/common/FilteringModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* @license
* Copyright CERN and copyright holders of ALICE O2. This software is
* distributed under the terms of the GNU General Public License v3 (GPL
* Version 3), copied verbatim in the file "COPYING".
*
* See http://alice-o2.web.cern.ch/license for full licensing information.
*
* In applying this license CERN does not waive the privileges and immunities
* granted to it by virtue of its status as an Intergovernmental Organization
* or submit itself to any jurisdiction.
*/

import { Observable } from '/js/src/index.js';
import { FilterModel } from './FilterModel.js';
import { ToggleableModel } from '../../common/toggle/TogglableModel.js';

/**
* Model representing a filtering system, including filter inputs visibility, filters values and so on
*/
export class FilteringModel extends Observable {
/**
* Constructor
*
* @param {FilterModel} filters the filters list model
*/
constructor(filters) {
super();

this._visualChange$ = new Observable();

this._toggleModel = new ToggleableModel();
this._toggleModel.bubbleTo(this._visualChange$);

/**
* @type {Map<string, {filter: FilterModel, humanName: (string|undefined)}>}
* @private
*/
this._filtersMeta = new Map();
for (const propertyKey in filters) {
this._addFilter(propertyKey, filters[propertyKey]);
}

this._filtersStore = filters;
}

/**
* Reset the filters
*
* @return {void}
*/
reset() {
this._filtersMeta.forEach(({ filter }) => filter.reset());
}

/**
* Returns the normalized value of all the filters, without null values
*
* @return {Object} the normalized values
*/
get normalized() {
const ret = {};
for (const [filterKey, { filter }] of this._filtersMeta) {
if (!filter.isEmpty) {
ret[filterKey] = filter.normalized;
}
}
return ret;
}

/**
* States if there is currently at least one filter active
*
* @return {boolean} true if at least one filter is active
*/
isAnyFilterActive() {
for (const [, { filter }] of this._filtersMeta) {
if (!filter.isEmpty) {
return true;
}
}
return false;
}

/**
* Returns the list of human-readable names of currently active filters
*
* @return {string} the active filters names
*/
get activeFiltersNames() {
const ret = [];
for (const [, { filter, humanName }] of this._filtersMeta) {
if (!filter.isEmpty) {
ret.push(humanName);
}
}
return ret.join(', ');
}

/**
* Returns the observable notified any time there is a visual change which has no impact on the actual filtering
*
* @return {Observable} the filters visibility observable
*/
get visualChange$() {
return this._visualChange$;
}

/**
* Returns the object storing all the filters models
*
* @return {Object} the filters store
*/
get filters() {
return this._filtersStore;
}

/**
* The visibility state of the filters popup
*
* @return {ToggleableModel} the toggle model
*/
get toggleModel() {
return this._toggleModel;
}

/**
* Add a filter to the list of registered filters, and bubble filters events (global and visual) to this model
*
* @param {string} filterKey the key of the filter, used to normalize filtering request
* @param {FilterModel} filter the filter model
* @return {void}
* @private
*/
_addFilter(filterKey, filter) {
this._filtersMeta.set(
filterKey,
{
filter,
humanName: `${filterKey[0].toUpperCase()}${filterKey.slice(1).replaceAll(/([A-Z])/g, ' $1').toLowerCase()}`,
},
);
filter.bubbleTo(this);
filter.visualChange$.bubbleTo(this._visualChange$);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* @license
* Copyright CERN and copyright holders of ALICE O2. This software is
* distributed under the terms of the GNU General Public License v3 (GPL
* Version 3), copied verbatim in the file "COPYING".
*
* See http://alice-o2.web.cern.ch/license for full licensing information.
*
* In applying this license CERN does not waive the privileges and immunities
* granted to it by virtue of its status as an Intergovernmental Organization
* or submit itself to any jurisdiction.
*/
import { arrayHasSameContent } from '../../../../utilities/arrayHasSameContent.js';
import { FilterModel } from '../FilterModel.js';

/**
* Model for a coma separated values filter
*
* This filter input is a comma separated list of values and its value is an array of values
*/
export class CommaSeparatedValuesFilterModel extends FilterModel {
/**
* Constructor
*/
constructor() {
super();

this._values = null;
this._raw = '';
}

// eslint-disable-next-line valid-jsdoc
/**
* @inheritDoc
* @override
*/
reset() {
this._values = null;
this._raw = '';
}

// eslint-disable-next-line valid-jsdoc
/**
* @inheritDoc
* @override
*/
get isEmpty() {
const { values } = this;
return !values || values.length === 0;
}

// eslint-disable-next-line valid-jsdoc
/**
* @inheritDoc
* @override
*/
get normalized() {
return this.values;
}

/**
* Define the current value of the filter
*
* @param {string} raw the raw value of the filter
* @param {array} values the list of parsed values of the filter
*
* @return {void}
*/
update(raw, values) {
const previousValues = [...this._values || []];

this._values = values;
this._raw = raw;

if (arrayHasSameContent(values || [], previousValues)) {
// Only raw value changed
this.visualChange$.notify();
} else {
this.notify();
}
}

/**
* Returns the raw value of the filter (the user input)
*
* @return {string} the raw value
*/
get raw() {
return this._raw;
}

/**
* Return the parsed values of the filter
*
* @return {array} the parsed values
*/
get values() {
if (!Array.isArray(this._values) || this._values.length === 0) {
return null;
}
return this._values;
}
}
Loading

0 comments on commit 06b9a15

Please sign in to comment.