diff --git a/CHANGELOG.md b/CHANGELOG.md index 65241360d..094ac0799 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). - `style_header_conditional` ### Fixed +[#347](https://github.com/plotly/dash-core/issues/347) +- Fixed table behavior when `filtering_settings` is updated through a callback + [#322](https://github.com/plotly/dash-core/issues/322) - Added LICENSE file to Python distributable diff --git a/demo/App.js b/demo/App.js index 9de783ce6..ce51c534d 100644 --- a/demo/App.js +++ b/demo/App.js @@ -2,9 +2,10 @@ import * as R from 'ramda'; import React, {Component} from 'react'; import { DataTable } from 'dash-table'; +import Environment from 'core/environment'; import { memoizeOne } from 'core/memoizer'; import Logger from 'core/Logger'; -import AppMode from './AppMode'; +import AppState, { AppMode } from './AppMode'; import './style.less'; @@ -12,7 +13,7 @@ class App extends Component { constructor() { super(); - this.state = AppMode; + this.state = AppState; const setProps = memoizeOne(() => { return newProps => { @@ -28,11 +29,30 @@ class App extends Component { }); } + renderMode() { + const mode = Environment.searchParams.get('mode'); + + if (mode === AppMode.Filtering) { + return (); + } + } + render() { - return (); + return (
+ {this.renderMode()} + +
); } } diff --git a/demo/AppMode.ts b/demo/AppMode.ts index fc699df19..60d02ce61 100644 --- a/demo/AppMode.ts +++ b/demo/AppMode.ts @@ -16,6 +16,7 @@ import { TooltipSyntax } from 'dash-table/tooltips/props'; export enum AppMode { Date = 'date', Default = 'default', + Filtering = 'filtering', FixedTooltips = 'fixed,tooltips', FixedVirtualized = 'fixed,virtualized', ReadOnly = 'readonly', @@ -31,6 +32,11 @@ export const ReadWriteModes = [ AppMode.Virtualized ]; +export const BasicModes = [ + ...ReadWriteModes, + AppMode.ReadOnly +]; + function getBaseTableProps(mock: IDataMock) { return { id: 'table', @@ -209,6 +215,13 @@ function getDateState() { return state; } +function getFilteringState() { + const state = getDefaultState(); + state.tableProps.filtering = true; + + return state; +} + function getVirtualizedState() { const mock = generateMockData(5000); @@ -253,6 +266,8 @@ function getState() { switch (mode) { case AppMode.Date: return getDateState(); + case AppMode.Filtering: + return getFilteringState(); case AppMode.FixedTooltips: return getFixedTooltipsState(); case AppMode.FixedVirtualized: diff --git a/src/core/components/IsolatedInput/index.tsx b/src/core/components/IsolatedInput/index.tsx index ce239a6cc..d0d39f892 100644 --- a/src/core/components/IsolatedInput/index.tsx +++ b/src/core/components/IsolatedInput/index.tsx @@ -38,6 +38,16 @@ export default class IsolatedInput extends PureComponent { return this.props as PropsWithDefaults; } + componentWillReceiveProps(nextProps: IProps) { + const { value: nextValue } = nextProps; + + if (this.state.value !== nextValue) { + this.setState({ + value: nextValue + }); + } + } + constructor(props: PropsWithDefaults) { super(props); diff --git a/src/core/syntax-tree/index.ts b/src/core/syntax-tree/index.ts index aa69ff0b1..292d351d0 100644 --- a/src/core/syntax-tree/index.ts +++ b/src/core/syntax-tree/index.ts @@ -18,18 +18,16 @@ export default class SyntaxTree { } evaluate = (target: any) => { - if (!this.isValid || !this.tree) { + if (!this.isValid) { const msg = `unable to evaluate target: syntax tree is invalid for query=${this.query}`; Logger.error(msg); throw new Error(msg); } - const evaluate = this.tree.lexeme.evaluate; - - return evaluate ? - evaluate(target, this.tree) : - false; + return this.tree && this.tree.lexeme && this.tree.lexeme.evaluate ? + this.tree.lexeme.evaluate(target, this.tree) : + true; } filter = (targets: any[]) => { diff --git a/src/core/syntax-tree/syntaxer.ts b/src/core/syntax-tree/syntaxer.ts index 408baa525..8729ed1df 100644 --- a/src/core/syntax-tree/syntaxer.ts +++ b/src/core/syntax-tree/syntaxer.ts @@ -64,6 +64,10 @@ export default (lexerResult: ILexerResult): ISyntaxerResult => { return { valid: false, error: `lexer -- ${lexerResult.error}` }; } + if (lexerResult.lexemes.length === 0) { + return { valid: true }; + } + try { return { tree: parser(lexemes), valid: true }; } catch (error) { diff --git a/src/dash-table/components/Filter/Column.tsx b/src/dash-table/components/Filter/Column.tsx index f7c96b542..5f1aad2ce 100644 --- a/src/dash-table/components/Filter/Column.tsx +++ b/src/dash-table/components/Filter/Column.tsx @@ -14,11 +14,7 @@ interface IColumnFilterProps { value?: string; } -interface IColumnFilterState { - value?: string; -} - -export default class ColumnFilter extends PureComponent { +export default class ColumnFilter extends PureComponent { constructor(props: IColumnFilterProps) { super(props); @@ -27,16 +23,6 @@ export default class ColumnFilter extends PureComponent { const { setFilter } = this.props; diff --git a/src/dash-table/components/FilterFactory.tsx b/src/dash-table/components/FilterFactory.tsx index 5413c72a7..d87fe691a 100644 --- a/src/dash-table/components/FilterFactory.tsx +++ b/src/dash-table/components/FilterFactory.tsx @@ -130,8 +130,12 @@ export default class FilterFactory { } const { tree } = syntaxerResult; - const toCheck: (ISyntaxTree | undefined)[] = [tree]; + if (!tree) { + this.ops.clear(); + return; + } + const toCheck: (ISyntaxTree | undefined)[] = [tree]; while (toCheck.length) { const item = toCheck.pop(); if (!item) { diff --git a/tests/cypress/tests/standalone/filtering_test.ts b/tests/cypress/tests/standalone/filtering_test.ts index 851efcec2..7c8094004 100644 --- a/tests/cypress/tests/standalone/filtering_test.ts +++ b/tests/cypress/tests/standalone/filtering_test.ts @@ -5,31 +5,76 @@ import Key from 'cypress/Key'; import { AppMode } from 'demo/AppMode'; describe(`filter`, () => { - beforeEach(() => { - cy.visit(`http://localhost:8080?mode=${AppMode.ColumnsInSpace}`); - DashTable.toggleScroll(false); + describe(`special characters`, () => { + beforeEach(() => { + cy.visit(`http://localhost:8080?mode=${AppMode.ColumnsInSpace}`); + DashTable.toggleScroll(false); + }); + + it('can filter on special column id', () => { + DashTable.getFilterById('c cc').click(); + DOM.focused.type(`gt num(90)${Key.Enter}`); + + DashTable.getFilterById('d:dd').click(); + DOM.focused.type(`lt num(12500)${Key.Enter}`); + + DashTable.getFilterById('e-ee').click(); + DOM.focused.type(`is prime${Key.Enter}`); + + DashTable.getFilterById('f_ff').click(); + DOM.focused.type(`le num(106)${Key.Enter}`); + + DashTable.getFilterById('g.gg').click(); + DOM.focused.type(`gt num(1000)${Key.Enter}`); + + DashTable.getFilterById('b+bb').click(); + DOM.focused.type(`eq "Wet"${Key.Enter}`); + + DashTable.getCellById(0, 'rows').within(() => cy.get('.dash-cell-value').should('have.html', '101')); + DashTable.getCellById(1, 'rows').should('not.exist'); + }); }); - it('can filter on special column id', () => { - DashTable.getFilterById('c cc').click(); - DOM.focused.type(`gt num(90)${Key.Enter}`); + describe('reset', () => { + beforeEach(() => { + cy.visit(`http://localhost:8080?mode=${AppMode.Filtering}`); + DashTable.toggleScroll(false); + }); + + it('updates results and filter fields', () => { + let cell_0; + let cell_1; + + DashTable.getCellById(0, 'ccc') + .within(() => cy.get('.dash-cell-value') + .then($el => cell_0 = $el[0].innerHTML)); - DashTable.getFilterById('d:dd').click(); - DOM.focused.type(`lt num(12500)${Key.Enter}`); + DashTable.getCellById(1, 'ccc') + .within(() => cy.get('.dash-cell-value') + .then($el => cell_1 = $el[0].innerHTML)); - DashTable.getFilterById('e-ee').click(); - DOM.focused.type(`is prime${Key.Enter}`); + DashTable.getFilterById('ccc').click(); + DOM.focused.type(`gt num(100)`); + DashTable.getFilterById('ddd').click(); + DOM.focused.type('lt num(20000)'); + DashTable.getFilterById('eee').click(); + DOM.focused.type('is prime'); + DashTable.getFilterById('bbb').click(); + DOM.focused.type(`eq "Wet"`); + DashTable.getFilterById('ccc').click(); - DashTable.getFilterById('f_ff').click(); - DOM.focused.type(`le num(106)${Key.Enter}`); + DashTable.getCellById(0, 'ccc').within(() => cy.get('.dash-cell-value').should('have.html', '101')); + DashTable.getCellById(1, 'ccc').within(() => cy.get('.dash-cell-value').should('have.html', '109')); - DashTable.getFilterById('g.gg').click(); - DOM.focused.type(`gt num(1000)${Key.Enter}`); + cy.get('.clear-filters').click(); - DashTable.getFilterById('b+bb').click(); - DOM.focused.type(`eq "Wet"${Key.Enter}`); + DashTable.getCellById(0, 'ccc').within(() => cy.get('.dash-cell-value').should('have.html', cell_0)); + DashTable.getCellById(1, 'ccc').within(() => cy.get('.dash-cell-value').should('have.html', cell_1)); - DashTable.getCellById(0, 'rows').within(() => cy.get('.dash-cell-value').should('have.html', '101')); - DashTable.getCellById(1, 'rows').should('not.exist'); + DashTable.getFilterById('bbb').within(() => cy.get('input').should('have.value', '')); + DashTable.getFilterById('ccc').within(() => cy.get('input').should('have.value', '')); + DashTable.getFilterById('ddd').within(() => cy.get('input').should('have.value', '')); + DashTable.getFilterById('eee').within(() => cy.get('input').should('have.value', '')); + }); }); }); \ No newline at end of file diff --git a/tests/cypress/tests/standalone/navigation_test.ts b/tests/cypress/tests/standalone/navigation_test.ts index 6e3af5e06..4565da75c 100644 --- a/tests/cypress/tests/standalone/navigation_test.ts +++ b/tests/cypress/tests/standalone/navigation_test.ts @@ -2,9 +2,9 @@ import DashTable from 'cypress/DashTable'; import DOM from 'cypress/DOM'; import Key from 'cypress/Key'; -import { AppMode } from 'demo/AppMode'; +import { BasicModes } from 'demo/AppMode'; -Object.values(AppMode).forEach(mode => { +Object.values(BasicModes).forEach(mode => { describe(`navigate, mode=${mode}`, () => { beforeEach(() => { cy.visit(`http://localhost:8080?mode=${mode}`); diff --git a/tests/cypress/tests/standalone/select_row_test.ts b/tests/cypress/tests/standalone/select_row_test.ts index b6004431b..5a65ed26d 100644 --- a/tests/cypress/tests/standalone/select_row_test.ts +++ b/tests/cypress/tests/standalone/select_row_test.ts @@ -1,8 +1,8 @@ import DashTable from 'cypress/DashTable'; -import { AppMode } from 'demo/AppMode'; +import { BasicModes } from 'demo/AppMode'; -Object.values(AppMode).forEach(mode => { +Object.values(BasicModes).forEach(mode => { describe(`select row, mode=${mode}`, () => { beforeEach(() => { cy.visit(`http://localhost:8080?mode=${mode}`); diff --git a/tests/cypress/tests/standalone/selection_test.ts b/tests/cypress/tests/standalone/selection_test.ts index 0e5f79248..91deebd86 100644 --- a/tests/cypress/tests/standalone/selection_test.ts +++ b/tests/cypress/tests/standalone/selection_test.ts @@ -2,9 +2,9 @@ import DashTable from 'cypress/DashTable'; import DOM from 'cypress/DOM'; import Key from 'cypress/Key'; -import { AppMode } from 'demo/AppMode'; +import { BasicModes } from 'demo/AppMode'; -Object.values(AppMode).forEach(mode => { +Object.values(BasicModes).forEach(mode => { describe(`select, mode=${mode}`, () => { beforeEach(() => { cy.visit(`http://localhost:8080?mode=${mode}`);