Here is a clientside callback that I use:
It builds a secondary table with the changes, using the Key (unique identifier column, identifier). Shows the column that was changed and the oldvalue and the newvalue:
app.clientside_callback(
"""function addToHistory(ts, data, data_previous, diff_store_data, keys) {
if (ts) {
let difference = data.filter(x => !data_previous.includes(x))[0]
let old = data_previous.filter(x => !data.includes(x))[0]
newKeys = Object.keys(difference)
keys = keys.toLowerCase().split('|')
info = ''
oldValue = ''
newValue = ''
column = ''
for (x=0; x<keys.length; x++) {
for (y=0; y<newKeys.length; y++) {
if (newKeys[y].toLowerCase() == keys[x]) {
newData = String(difference[newKeys[y]])
if (newData.includes('[')) {
newData = newData.split(']')[0].split('[')[1]
}
info += newData + '|'
}
if (difference[newKeys[y]] != old[newKeys[y]]) {
column = newKeys[y]
oldValue = old[newKeys[y]]
newValue = difference[newKeys[y]]
}
}
}
info = info.substring(0,info.length-1)
diff_store_data.unshift({"Key":info, "Column":column, "OldValue":oldValue, "NewValue":newValue})
return diff_store_data
}}""",
Output("history", "data"),
[Input("information", "data_timestamp")],
[
State("information", "data"),
State("information", "data_previous"),
State("history", "data"),
State('keys', 'value'),
], prevent_initial_call=True
)
Then on âsubmitâ, you roll all the changes up to their key and submit the changes to the backend.
Also, I have another clientside callback which uses the key to undo a change upon deletion of the record:
app.clientside_callback(
"""function reloadHistory(ts, data, data_previous, history_data, keys) {
if (ts && data.length > 0) {
let difference = data_previous.filter(x => !history_data.includes(x))[0]
newKeys = Object.keys(data[0])
oldKeys = keys.split('|')
for (x=0; x<data.length; x++) {
test = false
for (y=0; y<oldKeys.length; y++) {
for (z=0; z<newKeys.length; z++) {
if (newKeys[z].toLowerCase() == oldKeys[y]) {
newData = String(data[x][newKeys[z]])
try {
if (newData.includes('[')) {
newData = newData.split(']')[0].split('[')[1]
} } catch {}
if (String(newData) == difference['Key'].split('|')[y]) {
test = true
} else {
test = false
break
}
}
}
if (!test) {
break
}
}
if (test) {
data[x][difference['Column']] = difference['OldValue']
break
}
}
return data
}}""",
Output("information", "data"),
Input("history", "data_timestamp"),
[State('information', 'data'),
State('history', 'data_previous'),
State('history', 'data'),
State('keys', 'value')], prevent_initial_call=True
)
Please note, I have my keys delimited by â|â for ease, you could substitute with a list or just one for something where there is only one identifier.
I allow for multiple keys in the event that I dont want to use a record uid, but instead of combination of two columns.