Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
[#591](https://github.com/plotly/dash-table/issues/591)
- Fixed row and column selection when multiple tables are present

[#600](https://github.com/plotly/dash-table/issues/600)
- Fixed reconciliation when validation default value is `0` (number)
- Apply reconciliation value when deleting cells, if possible

## [4.3.0] - 2019-09-17
### Added
[#566](https://github.com/plotly/dash-table/pull/566)
Expand Down
25 changes: 17 additions & 8 deletions src/dash-table/components/ControlledTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import TableTooltip from './fragments/TableTooltip';
import queryLexicon from 'dash-table/syntax-tree/lexicon/query';

import dataLoading from 'dash-table/derived/table/data_loading';
import reconcile from 'dash-table/type/reconcile';

const DEFAULT_STYLE = {
width: '100%'
Expand Down Expand Up @@ -410,7 +411,7 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
// else we are navigating with arrow keys and extending selection
// with shift.

let {minRow, minCol, maxRow, maxCol} = selectionBounds(selected_cells);
let { minRow, minCol, maxRow, maxCol } = selectionBounds(selected_cells);
const selectingDown =
e.keyCode === KEY_CODES.ARROW_DOWN || e.keyCode === KEY_CODES.ENTER;
const selectingUp = e.keyCode === KEY_CODES.ARROW_UP;
Expand Down Expand Up @@ -459,7 +460,7 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
}

const finalSelected = makeSelection(
{minRow, maxRow, minCol, maxCol},
{ minRow, maxRow, minCol, maxCol },
visibleColumns, viewport
);

Expand Down Expand Up @@ -503,10 +504,18 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
);

realCells.forEach(cell => {
if (visibleColumns[cell[1]].editable) {
const column = visibleColumns[cell[1]];

if (column.editable) {
/**
* If the cell can reconcile `null`, use this reconciliation value,
* otherwise use the default `''`.
*/
const result = reconcile(null, column);

newData = R.set(
R.lensPath([cell[0], visibleColumns[cell[1]].id]),
'',
R.lensPath([cell[0], column.id]),
result.success ? result.value : '',
newData
);
}
Expand All @@ -522,7 +531,7 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>

const e = event;

const {row, column} = currentCell;
const { row, column } = currentCell;
let nextCoords;

switch (e.keyCode) {
Expand Down Expand Up @@ -787,7 +796,7 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
scrollbarWidth
);

/* Tooltip */
/* Tooltip */
let tableTooltip = derivedTooltips(
currentTooltip,
tooltip_data,
Expand Down Expand Up @@ -844,7 +853,7 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
ref={`r${rowIndex}c${columnIndex}`}
className={`cell cell-${rowIndex}-${columnIndex} ${c}`}
>
{g ? React.cloneElement(g, { style: s.cell }) : g}
{g ? React.cloneElement(g, { style: s.cell }) : g}
</div>))}
</div>))}
</div>
Expand Down
20 changes: 14 additions & 6 deletions src/dash-table/type/reconcile.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as R from 'ramda';

import {
ChangeAction,
ChangeFailure,
Expand All @@ -9,6 +11,7 @@ import reconcileAny from './any';
import { coerce as coerceNumber, validate as validateNumber } from './number';
import { coerce as coerceText, validate as validateText } from './text';
import { coerce as coerceDate, validate as validateDate } from './date';
import { OptionalPluck } from 'core/type';

export interface IReconciliation {
action?: ChangeAction;
Expand All @@ -17,7 +20,11 @@ export interface IReconciliation {
value?: any;
}

function getCoercer(c: IColumnType): (value: any, c?: any) => IReconciliation {
type Options = OptionalPluck<IColumnType, 'on_change'> &
OptionalPluck<IColumnType, 'type'> &
OptionalPluck<IColumnType, 'validation'>;

function getCoercer(c: Options): (value: any, c?: any) => IReconciliation {
switch (c.type) {
case ColumnType.Numeric:
return coerceNumber;
Expand All @@ -31,7 +38,7 @@ function getCoercer(c: IColumnType): (value: any, c?: any) => IReconciliation {
}
}

function getValidator(c: IColumnType): (value: any, c?: any) => IReconciliation {
function getValidator(c: Options): (value: any, c?: any) => IReconciliation {
switch (c.type) {
case ColumnType.Numeric:
return validateNumber;
Expand All @@ -45,7 +52,7 @@ function getValidator(c: IColumnType): (value: any, c?: any) => IReconciliation
}
}

function doAction(value: any, c: IColumnType) {
function doAction(value: any, c: Options) {
const action = (c && c.on_change && c.on_change.action) || ChangeAction.Coerce;

switch (action) {
Expand All @@ -58,13 +65,14 @@ function doAction(value: any, c: IColumnType) {
}
}

function doFailureRecovery(result: IReconciliation, c: IColumnType) {
function doFailureRecovery(result: IReconciliation, c: Options) {
// If c/v unsuccessful, process failure
const failure = (c && c.on_change && c.on_change.failure) || ChangeFailure.Reject;
result.failure = failure;

if (failure === ChangeFailure.Default) {
const defaultValue = (c && c.validation && c.validation.default) || null;
const validationDefault = c && c.validation && c.validation.default;
const defaultValue = R.isNil(validationDefault) ? null : validationDefault;
result.success = true;
result.value = defaultValue;
} else if (failure === ChangeFailure.Accept) {
Expand All @@ -74,7 +82,7 @@ function doFailureRecovery(result: IReconciliation, c: IColumnType) {
return result;
}

export default (value: any, c: IColumnType) => {
export default (value: any, c: Options) => {
let res: IReconciliation = doAction(value, c);

if (res.success) {
Expand Down
3 changes: 2 additions & 1 deletion tests/cypress/tests/unit/numberCoercion_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { isNully } from 'dash-table/type/null';
import { coerce } from 'dash-table/type/number';

const DEFAULT_COERCE_SUCCESS = [
{ input: 42, output: 42, name: 'from number' },
{ input: 0, output: 0, name: 'from number (0)' },
{ input: 42, output: 42, name: 'from number (42)' },
{ input: '42', output: 42, name: 'from number string' },
{ input: '-42', output: -42, name: 'from negative number string' },
{ input: '4.242', output: 4.242, name: 'from float string' },
Expand Down
3 changes: 2 additions & 1 deletion tests/cypress/tests/unit/numberValidation_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { isNully } from 'dash-table/type/null';
import { validate } from 'dash-table/type/number';

const DEFAULT_VALIDATE_SUCCESS = [
{ input: 42, output: 42, name: 'from number' }
{ input: 0, output: 0, name: 'from number (0)' },
{ input: 42, output: 42, name: 'from number (42)' }
];

const ALLOW_NULL_VALIDATE_SUCCESS = [
Expand Down
22 changes: 22 additions & 0 deletions tests/cypress/tests/unit/reconcile_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import reconcile from 'dash-table/type/reconcile';
import { ColumnType, ChangeAction, ChangeFailure } from 'dash-table/components/Table/props';

describe('reconcile', () => {
describe('coerce/validate', () => {
it('applies default ', () => {
const res = reconcile(null, {
type: ColumnType.Numeric,
on_change: {
action: ChangeAction.Coerce,
failure: ChangeFailure.Default
},
validation: {
default: 0
}
});

expect(res.success).to.equal(true);
expect(res.value).to.equal(0);
});
});
});