Skip to content

Commit

Permalink
Merge pull request #261 from emilhe/cellValueChanged
Browse files Browse the repository at this point in the history
Propagate muti-cell paste event to Dash
  • Loading branch information
BSd3v authored Jan 31, 2024
2 parents 5ba7d1b + 1f5da06 commit d3f7726
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 55 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to `dash-ag-grid` will be documented in this file.
This project adheres to [Semantic Versioning](https://semver.org/).
Links "DE#nnn" prior to version 2.0 point to the Dash Enterprise closed-source Dash AG Grid repo

## UNRELEASED

### Changed

- [#261](https://github.com/plotly/dash-ag-grid/pull/261) The `cellValueChanged` property has changed been changed from a (single) event object to a _list_ of event objects. For multi-cell edits, the list will contain an element per change. In other cases, the list will contain a single element. Fixes [#262](https://github.com/plotly/dash-ag-grid/issues/262)

## [2.4.0] - 2023-10-17

### Added
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"build:js": "webpack --mode production",
"build:backends": "dash-generate-components ./src/lib/components dash_ag_grid -p package-info.json --r-prefix '' --jl-prefix ''",
"build": "run-s prepublishOnly build:js build:backends",
"postbuild": "es-check es2015 dash_ag_grid/*.js",
"postbuild": "es-check es2017 dash_ag_grid/*.js",
"private::format.eslint": "eslint --quiet --fix src",
"private::format.prettier": "prettier --write src --ignore-path=.prettierignore",
"format": "run-s private::format.*",
Expand Down
74 changes: 38 additions & 36 deletions src/lib/components/AgGrid.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -682,42 +682,44 @@ DashAgGrid.propTypes = {
/**
* Value has changed after editing.
*/
cellValueChanged: PropTypes.shape({
/**
* rowIndex, typically a row number
*/
rowIndex: PropTypes.number,

/**
* Row Id from the grid, this could be a number automatically, or set via getRowId
*/
rowId: PropTypes.any,

/**
* data, data object from the row
*/
data: PropTypes.object,

/**
* old value of the cell
*/
oldValue: PropTypes.any,

/**
* new value of the cell
*/
newValue: PropTypes.any,

/**
* column where the cell was changed
*/
colId: PropTypes.any,

/**
* Timestamp of when the event was fired
*/
timestamp: PropTypes.any,
}),
cellValueChanged: PropTypes.arrayOf(
PropTypes.shape({
/**
* rowIndex, typically a row number
*/
rowIndex: PropTypes.number,

/**
* Row Id from the grid, this could be a number automatically, or set via getRowId
*/
rowId: PropTypes.any,

/**
* data, data object from the row
*/
data: PropTypes.object,

/**
* old value of the cell
*/
oldValue: PropTypes.any,

/**
* new value of the cell
*/
newValue: PropTypes.any,

/**
* column where the cell was changed
*/
colId: PropTypes.any,

/**
* Timestamp of when the event was fired
*/
timestamp: PropTypes.any,
})
),

/**
* Other ag-grid options
Expand Down
47 changes: 37 additions & 10 deletions src/lib/fragments/AgGrid.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ const RESIZE_DEBOUNCE_MS = 200;
// Rate-limit for updating columnState when interacting with the grid
const COL_RESIZE_DEBOUNCE_MS = 500;

// Time between syncing cell value changes with Dash
const CELL_VALUE_CHANGED_DEBOUNCE_MS = 1;

const xssMessage = (context) => {
console.error(
context,
Expand Down Expand Up @@ -119,6 +122,7 @@ export default class DashAgGrid extends Component {
this.onCellClicked = this.onCellClicked.bind(this);
this.onCellDoubleClicked = this.onCellDoubleClicked.bind(this);
this.onCellValueChanged = this.onCellValueChanged.bind(this);
this.afterCellValueChanged = this.afterCellValueChanged.bind(this);
this.onRowDataUpdated = this.onRowDataUpdated.bind(this);
this.onFilterChanged = this.onFilterChanged.bind(this);
this.onSortChanged = this.onSortChanged.bind(this);
Expand Down Expand Up @@ -181,6 +185,7 @@ export default class DashAgGrid extends Component {

this.selectionEventFired = false;
this.reference = React.createRef();
this.pendingChanges = null;
}

onPaginationChanged() {
Expand Down Expand Up @@ -912,20 +917,38 @@ export default class DashAgGrid extends Component {
node,
}) {
const timestamp = Date.now();
// Collect new change.
const newChange = {
rowIndex,
rowId: node.id,
data,
oldValue,
value,
colId,
timestamp,
};
// Append it to current change session.
if (!this.pendingCellValueChanges) {
this.pendingCellValueChanges = [newChange];
} else {
this.pendingCellValueChanges.push(newChange);
}
}

afterCellValueChanged() {
// Guard against multiple invocations of the same change session.
if (!this.pendingCellValueChanges) {
return;
}
// Send update(s) for current change session to Dash.
const virtualRowData = this.virtualRowData();
this.props.setProps({
cellValueChanged: {
rowIndex,
rowId: node.id,
data,
oldValue,
value,
colId,
timestamp,
},
cellValueChanged: this.pendingCellValueChanges,
virtualRowData,
});
this.syncRowData();
// Mark current change session as ended.
this.pendingCellValueChanges = null;
}

onDisplayedColumnsChanged() {
Expand Down Expand Up @@ -1325,7 +1348,11 @@ export default class DashAgGrid extends Component {
onSelectionChanged={this.onSelectionChanged}
onCellClicked={this.onCellClicked}
onCellDoubleClicked={this.onCellDoubleClicked}
onCellValueChanged={this.onCellValueChanged}
onCellValueChanged={debounce(
this.afterCellValueChanged,
CELL_VALUE_CHANGED_DEBOUNCE_MS,
this.onCellValueChanged
)}
onFilterChanged={this.onFilterChanged}
onSortChanged={this.onSortChanged}
onRowDragEnd={this.onSortChanged}
Expand Down
5 changes: 4 additions & 1 deletion src/lib/utils/debounce.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
export default function debounce(fn, wait) {
export default function debounce(fn, wait, onDebounce) {
let lastTimestamp = 0;
let handle;

return (...args) => {
const now = Date.now();
const delay = Math.min(now - lastTimestamp, wait);

if (onDebounce) {
onDebounce(...args);
}
if (handle) {
clearTimeout(handle);
}
Expand Down
62 changes: 55 additions & 7 deletions tests/test_cell_value_changed.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_cv001_cell_value_changed(dash_duo):
dag.AgGrid(
id="history",
columnDefs=[{"field": "Key", "checkboxSelection": True}]
+ [{"field": i} for i in ["Column", "OldValue", "NewValue"]],
+ [{"field": i} for i in ["Column", "OldValue", "NewValue"]],
rowData=[],
),
],
Expand All @@ -43,12 +43,16 @@ def test_cv001_cell_value_changed(dash_duo):
)

app.clientside_callback(
"""function addToHistory(data) {
if (data) {
reloadData = {...data.data}
reloadData[data.colId] = data.oldValue
newData = [{Key: data.rowId, Column: data.colId, OldValue: data.oldValue, NewValue: data.value,
reloadData}]
"""function addToHistory(changes) {
if (changes) {
newData = []
for (let i = 0; i < changes.length; i++) {
data = changes[i];
reloadData = {...data.data};
reloadData[data.colId] = data.oldValue;
newData.push({Key: data.rowId, Column: data.colId, OldValue: data.oldValue,
NewValue: data.value, reloadData});
}
return {'add': newData}
}
return window.dash_clientside.no_update
Expand Down Expand Up @@ -100,3 +104,47 @@ def test_cv001_cell_value_changed(dash_duo):
grid.get_cell(1, 2).click()

hist.wait_for_rendered_rows(1)


def test_cv001_cell_value_changed_multi(dash_duo):
app = Dash(__name__)
app.layout = html.Div(
[
dag.AgGrid(
columnDefs=columnDefs,
rowData=df.to_dict("records"),
columnSize="sizeToFit",
defaultColDef={"editable": True},
id="grid",
getRowId="params.data.nation",
dashGridOptions={'editType':'fullRow'}
),
html.Div(id="log")
]
)

app.clientside_callback(
"""function countEvents(changes) {
console.log("FIRE");
return changes? changes.length : 0;
}""",
Output("log", "children"),
Input("grid", "cellValueChanged"),
prevent_initial_call=True,
)

dash_duo.start_server(app)

grid = utils.Grid(dash_duo, "grid")
grid.wait_for_cell_text(0, 0, "South Korea")

# Test single event.
grid.get_cell(0, 1).send_keys("50")
grid.get_cell(1, 2).click()
dash_duo.wait_for_text_to_equal('#log', "1")

# Test multi event.
grid.get_cell(0, 1).send_keys("20")
grid.get_cell_editing_input(0, 2).send_keys("20")
grid.get_cell(1, 2).click()
dash_duo.wait_for_text_to_equal('#log', "2")
5 changes: 5 additions & 0 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ def get_cell(self, row, col):
f'#{self.id} .ag-row[row-index="{row}"] .ag-cell[aria-colindex="{col + 1}"]'
)

def get_cell_editing_input(self, row, col):
return self.dash_duo.find_element(
f'#{self.id} .ag-row[row-index="{row}"] .ag-cell[aria-colindex="{col + 1}"] .ag-cell-editor input'
)

def get_row(self, row):
return self.dash_duo.find_element(f'#{self.id} .ag-row[row-index="{row}"]')

Expand Down

0 comments on commit d3f7726

Please sign in to comment.