Skip to content

Commit

Permalink
Merge pull request #274 from lineupjs/sgratzl/stringfilter
Browse files Browse the repository at this point in the history
String column: Filter independently for missing values and text input
  • Loading branch information
thinkh committed Mar 25, 2020
2 parents 33c3fac + 57f86be commit d3eec73
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 82 deletions.
4 changes: 2 additions & 2 deletions src/model/LinkColumn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {IDataRow, IGroup, IValueColumnDesc, ITypeFactory} from './interfaces';
import {patternFunction} from './internal';
import ValueColumn, {dataLoaded} from './ValueColumn';
import {IEventListener, ISequence} from '../internal';
import {IStringDesc, EAlignment, IStringGroupCriteria, EStringGroupCriteriaType} from './StringColumn';
import {IStringDesc, EAlignment, IStringGroupCriteria, EStringGroupCriteriaType, IStringFilter} from './StringColumn';
import StringColumn from './StringColumn';

export interface ILinkDesc extends IStringDesc {
Expand Down Expand Up @@ -187,7 +187,7 @@ export default class LinkColumn extends ValueColumn<string | ILink> {
return this.currentFilter;
}

setFilter(filter: string | RegExp | null) {
setFilter(filter: IStringFilter) {
return StringColumn.prototype.setFilter.call(this, filter);
}

Expand Down
65 changes: 48 additions & 17 deletions src/model/StringColumn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export interface IStringDesc {

export declare type IStringColumnDesc = IStringDesc & IValueColumnDesc<string>;

export interface IStringFilter {
filter: string | RegExp | null;
filterMissing: boolean;
}

/**
* emitted when the filter property changes
* @asMemberOf StringColumn
Expand All @@ -64,8 +69,8 @@ export default class StringColumn extends ValueColumn<string> {
static readonly EVENT_GROUPING_CHANGED = 'groupingChanged';

//magic key for filtering missing ones
static readonly FILTER_MISSING = '__FILTER_MISSING';
private currentFilter: string | RegExp | null = null;
private static readonly FILTER_MISSING = '__FILTER_MISSING';
private currentFilter: IStringFilter | null = null;

readonly alignment: EAlignment;
readonly escape: boolean;
Expand Down Expand Up @@ -134,11 +139,36 @@ export default class StringColumn extends ValueColumn<string> {

restore(dump: any, factory: ITypeFactory) {
super.restore(dump, factory);
if (dump.filter && (<string>dump.filter).startsWith('REGEX:')) {
this.currentFilter = new RegExp(dump.filter.slice(6), 'gm');
if (dump.filter) {
const filter = dump.filter;
if (typeof filter === 'string') {
// compatibility case
if ((<string>filter).startsWith('REGEX:')) {
this.currentFilter = {
filter: new RegExp(filter.slice(6), 'gm'),
filterMissing: false
};
} else if (filter === StringColumn.FILTER_MISSING) {
this.currentFilter = {
filter: null,
filterMissing: true
};
} else {
this.currentFilter = {
filter,
filterMissing: false
};
}
} else {
this.currentFilter = {
filter: filter.filter && (<string>filter.filter).startsWith('REGEX:') ? new RegExp(filter.slice(6), 'gm') : filter.filter || '',
filterMissing: filter.filterMissing === true
};
}
} else {
this.currentFilter = dump.filter || null;
this.currentFilter = null;
}

// tslint:disable-next-line: early-exit
if (dump.groupCriteria) {
const {type, values} = <IStringGroupCriteria>dump.groupCriteria;
Expand All @@ -158,29 +188,30 @@ export default class StringColumn extends ValueColumn<string> {
return true;
}
const r = this.getLabel(row);
const filter = this.currentFilter;
const filter = this.currentFilter!;
const ff = filter.filter;

if (filter === StringColumn.FILTER_MISSING) { //filter empty
return r != null && r.trim() !== '';
if (r == null || r.trim() === '') {
return !filter.filterMissing;
}
if (typeof filter === 'string' && filter.length > 0) {
return r !== '' && r.toLowerCase().indexOf(filter.toLowerCase()) >= 0;
if (!ff) {
return true;
}
if (filter instanceof RegExp) {
return r !== '' && r.match(filter) != null; // You can not use RegExp.test(), because of https://stackoverflow.com/a/6891667
if (ff instanceof RegExp) {
return r !== '' && r.match(ff) != null; // You can not use RegExp.test(), because of https://stackoverflow.com/a/6891667
}
return true;
return r !== '' && r.toLowerCase().includes(ff.toLowerCase());
}

getFilter() {
return this.currentFilter;
}

setFilter(filter: string | RegExp | null) {
if (filter === '') {
filter = null;
setFilter(filter: IStringFilter | null) {
if (filter === this.currentFilter) {
return;
}
if (this.currentFilter === filter) {
if (this.currentFilter && filter && this.currentFilter.filterMissing === filter.filterMissing && this.currentFilter.filter === filter.filter) {
return;
}
this.fire([StringColumn.EVENT_FILTER_CHANGED, Column.EVENT_DIRTY_VALUES, Column.EVENT_DIRTY], this.currentFilter, this.currentFilter = filter);
Expand Down
52 changes: 19 additions & 33 deletions src/renderer/StringCellRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {StringColumn, Column, IDataRow, IOrderedGroup} from '../model';
import {StringColumn, Column, IDataRow, IOrderedGroup, IStringFilter} from '../model';
import {filterMissingMarkup, findFilterMissing} from '../ui/missing';
import {IRenderContext, ICellRendererFactory} from './interfaces';
import {renderMissingDOM} from './missing';
Expand Down Expand Up @@ -61,20 +61,15 @@ export default class StringCellRenderer implements ICellRendererFactory {
const isRegex = <HTMLInputElement>node.querySelector('input[type="checkbox"]');

const update = () => {
input.disabled = filterMissing.checked;
isRegex.disabled = filterMissing.checked;

if (filterMissing.checked) {
col.setFilter(StringColumn.FILTER_MISSING);
return;
}
const valid = input.value.trim();
filterMissing.disabled = valid.length > 0;
if (valid.length <= 0) {
col.setFilter(null);
col.setFilter({filter: null, filterMissing: filterMissing.checked});
return;
}
col.setFilter(isRegex.checked ? new RegExp(input.value) : input.value);
col.setFilter({
filter: isRegex.checked ? new RegExp(input.value) : input.value,
filterMissing: filterMissing.checked
});
};

filterMissing.onchange = update;
Expand All @@ -89,17 +84,11 @@ export default class StringCellRenderer implements ICellRendererFactory {

return (actCol: StringColumn) => {
col = actCol;
let bak = col.getFilter() || '';
const bakMissing = bak === StringColumn.FILTER_MISSING;
if (bakMissing) {
bak = '';
}
filterMissing.checked = bakMissing;
input.value = bak instanceof RegExp ? bak.source : bak;
const f = col.getFilter() || {filter: null, filterMissing: false};
const bak = f.filter;
filterMissing.checked = f.filterMissing;
input.value = bak instanceof RegExp ? bak.source : bak || '';
isRegex.checked = bak instanceof RegExp;
filterMissing.disabled = input.value.trim().length > 0;
input.disabled = filterMissing.checked;
isRegex.disabled = filterMissing.checked;
};
}

Expand All @@ -108,21 +97,18 @@ export default class StringCellRenderer implements ICellRendererFactory {
return {
template: `<div></div>`,
update: (node: HTMLElement) => {
const filter = col.getFilter() || '';
const filter = col.getFilter();
node.textContent = toString(filter);
}
};
}
let bak = col.getFilter() || '';
const bakMissing = bak === StringColumn.FILTER_MISSING;
if (bakMissing) {
bak = '';
}
const f = col.getFilter() || {filter: null, filterMissing: false};
const bak = f.filter || '';
let update: (col: StringColumn) => void;
return {
template: `<form><input type="text" placeholder="Filter ${col.desc.label}..." autofocus value="${(bak instanceof RegExp) ? bak.source : bak}">
<label class="${cssClass('checkbox')}"><input type="checkbox" ${(bak instanceof RegExp) ? 'checked="checked"' : ''}><span>Use regular expressions</span></label>
${filterMissingMarkup(bakMissing)}</form>`,
${filterMissingMarkup(f.filterMissing)}</form>`,
update: (node: HTMLElement) => {
if (!update) {
update = StringCellRenderer.interactiveSummary(col, node);
Expand All @@ -133,12 +119,12 @@ export default class StringCellRenderer implements ICellRendererFactory {
}
}

function toString(filter: null | string | RegExp) {
if (filter == null || filter === '' || filter === StringColumn.FILTER_MISSING) {
function toString(filter: IStringFilter | null) {
if (filter == null || !filter.filter) {
return '';
}
if (filter instanceof RegExp) {
return filter.source;
if (filter.filter instanceof RegExp) {
return filter.filter.source;
}
return String(filter);
return filter.filter;
}
41 changes: 11 additions & 30 deletions src/ui/dialogs/StringFilterDialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,58 +14,39 @@ export default class StringFilterDialog extends ADialog {
});
}

private updateFilter(filter: string | RegExp | null) {
updateFilterState(this.attachment, this.column, filter != null && filter !== '');
this.column.setFilter(filter);
private updateFilter(filter: string | RegExp | null, filterMissing: boolean) {
updateFilterState(this.attachment, this.column, filterMissing || (filter != null && filter !== ''));
this.column.setFilter({filter, filterMissing});
}

reset() {
this.findInput('input[type="text"]').value = '';
this.forEach('input[type=checkbox]', (n: HTMLInputElement) => n.checked = false);
this.updateFilter(null);
this.updateFilter(null, false);
}

submit() {
const filterMissing = findFilterMissing(this.node).checked;
if (filterMissing) {
this.updateFilter(StringColumn.FILTER_MISSING);
return true;
}
const input = this.findInput('input[type="text"]').value;
const isRegex = this.findInput('input[type="checkbox"]').checked;
this.updateFilter(isRegex ? new RegExp(input, 'gm') : input);
this.updateFilter(isRegex ? new RegExp(input, 'gm') : input, filterMissing);
return true;
}

protected build(node: HTMLElement) {
let bak = this.column.getFilter() || '';
const bakMissing = bak === StringColumn.FILTER_MISSING;
if (bakMissing) {
bak = '';
}
node.insertAdjacentHTML('beforeend', `<input type="text" placeholder="Filter ${this.column.desc.label}..." autofocus value="${(bak instanceof RegExp) ? bak.source : bak}" style="width: 100%">
<label class="${cssClass('checkbox')}"><input type="checkbox" ${(bak instanceof RegExp) ? 'checked="checked"' : ''}><span>Use regular expressions</span></label>
${filterMissingMarkup(bakMissing)}`);
const bak = this.column.getFilter() || {filter: '', filterMissing: false};
node.insertAdjacentHTML('beforeend', `<input type="text" placeholder="Filter ${this.column.desc.label}..." autofocus value="${(bak.filter instanceof RegExp) ? bak.filter.source : bak.filter || ''}" style="width: 100%">
<label class="${cssClass('checkbox')}"><input type="checkbox" ${(bak.filter instanceof RegExp) ? 'checked="checked"' : ''}><span>Use regular expressions</span></label>
${filterMissingMarkup(bak.filterMissing)}`);

const filterMissing = findFilterMissing(node);
const input = <HTMLInputElement>node.querySelector('input[type="text"]');
const isRegex = <HTMLInputElement>node.querySelector('input[type="checkbox"]');

const update = () => {
input.disabled = filterMissing.checked;
isRegex.disabled = filterMissing.checked;

if (filterMissing.checked) {
this.updateFilter(StringColumn.FILTER_MISSING);
return;
}
const valid = input.value.trim();
filterMissing.disabled = valid.length > 0;
if (valid.length <= 0) {
this.updateFilter(null);
return;
}
this.updateFilter(isRegex.checked ? new RegExp(input.value, 'gm') : input.value);
const f = valid.length > 0 ? (isRegex.checked ? new RegExp(valid, 'gm') : valid) : null;
this.updateFilter(f, filterMissing.checked);
};

filterMissing.onchange = update;
Expand Down

0 comments on commit d3eec73

Please sign in to comment.