gristlabs_grist-core/app/client/models/ColumnFilter.ts
Cyprien P 6793377579 (core) Fix values ordering in column filter menu
Summary:
Column filter menu use to mess up the ordering of the items for
numeric and dates values, and also for ref/reflist columns when the
visible column is a numeric a date column.

Solution was to:
 - use the actual value of the visible column for comparison.
 - use native comparison.
 - tweak the native comparison to make blanks appears before valid value. Indeed, it came up several time that it's convenient to have invalid values show up first in the filter panel, it makes for a convenient way to detect them.

Test Plan: Adds new nbrowser test

Reviewers: alexmojaki

Reviewed By: alexmojaki

Differential Revision: https://phab.getgrist.com/D3441
2022-05-24 16:30:28 +02:00

102 lines
3.0 KiB
TypeScript

import {ColumnFilterFunc, makeFilterFunc} from "app/common/ColumnFilterFunc";
import {CellValue} from 'app/common/DocActions';
import {FilterSpec, FilterState, makeFilterState} from "app/common/FilterState";
import {nativeCompare} from 'app/common/gutil';
import {Computed, Disposable, Observable} from 'grainjs';
/**
* ColumnFilter implements a custom filter on a column, i.e. a filter that's diverged from what's
* on the server. It has methods to modify the filter state, and exposes a public filterFunc
* observable which gets triggered whenever the filter state changes.
*
* It does NOT listen to changes in the initial JSON, since it's only used when the filter has
* been customized.
*/
export class ColumnFilter extends Disposable {
public readonly filterFunc = Observable.create<ColumnFilterFunc>(this, () => true);
// Computed that returns true if filter is an inclusion filter, false otherwise.
public readonly isInclusionFilter: Computed<boolean> = Computed.create(this, this.filterFunc, () => this._include);
// Computed that returns the current filter state.
public readonly state: Computed<FilterState> = Computed.create(this, this.filterFunc, () => this._getState());
private _include: boolean;
private _values: Set<CellValue>;
constructor(private _initialFilterJson: string, private _columnType: string = '',
public visibleColumnType: string = '') {
super();
this.setState(_initialFilterJson);
}
public get columnType() {
return this._columnType;
}
public setState(filterJson: string|FilterSpec) {
const state = makeFilterState(filterJson);
this._include = state.include;
this._values = state.values;
this._updateState();
}
public includes(val: CellValue): boolean {
return this._values.has(val) === this._include;
}
public add(val: CellValue) {
this.addMany([val]);
}
public addMany(values: CellValue[]) {
for (const val of values) {
this._include ? this._values.add(val) : this._values.delete(val);
}
this._updateState();
}
public delete(val: CellValue) {
this.deleteMany([val]);
}
public deleteMany(values: CellValue[]) {
for (const val of values) {
this._include ? this._values.delete(val) : this._values.add(val);
}
this._updateState();
}
public clear() {
this._values.clear();
this._include = true;
this._updateState();
}
public selectAll() {
this._values.clear();
this._include = false;
this._updateState();
}
// For saving the filter value back.
public makeFilterJson(): string {
const values = Array.from(this._values).sort(nativeCompare);
return JSON.stringify(this._include ? {included: values} : {excluded: values});
}
public hasChanged(): boolean {
return this.makeFilterJson() !== this._initialFilterJson;
}
private _updateState(): void {
this.filterFunc.set(makeFilterFunc(this._getState(), this._columnType));
}
private _getState(): FilterState {
return {include: this._include, values: this._values};
}
}
export const allInclusive = '{"excluded":[]}';