Here is how it should be done I think:
import dash
from dash import dcc, html
from dash.dependencies import State, Input, Output
from dash import dash_table
from dataclasses import dataclass
import pandas as pd
import dash_ag_grid as dag
import pandas as pd
from dash import Dash, html, Output, Input, dcc
@dataclass()
class Token:
id: int
text: str
upos: str
xpos: str
start_char: int
end_char: int
feats: dict[str, str]
@dataclass()
class Audio:
word: str
start: float
end: float
app = dash.Dash(__name__)
def get_text_data():
TOKENS = [
Token(id=1, text='Pride', upos='PROPN', xpos='NNP', start_char=0, end_char=5, feats={'Number': 'Sing'}),
Token(id=2, text='and', upos='CCONJ', xpos='CC', start_char=6, end_char=9, feats=None),
Token(id=3, text='Prejudice', upos='PROPN', xpos='NNP', start_char=10, end_char=19,feats={'Number': 'Sing'}),
Token(id=4, text='is', upos='AUX', xpos='VBZ', start_char=20, end_char=22,feats={'Mood': 'Ind', 'Number': 'Sing', 'Person': '3', 'Tense': 'Pres', 'VerbForm': 'Fin'}),
Token(id=5, text='the', upos='DET', xpos='DT', start_char=23, end_char=26,feats={'Definite': 'Def', 'PronType': 'Art'}),
]
df = pd.DataFrame(TOKENS)
df["mergeUp"] = "↑"
df["mergeDown"] = "↓"
df["split"] = ""
df["card"] = "This a test card"
for col in ["id", "xpos", "upos", "start_char", "end_char"]:
df[col] = df[col].apply(lambda x: x if isinstance(x, list) else [x])
df = df[["id", "text", "upos", "xpos", "start_char", "end_char", "mergeUp", "mergeDown", "split", "card"]]
return df.to_dict("records")
def get_audio_data():
AUDIO_EN = [
Audio(word='Pride', start=2.062, end=2.382),
Audio(word='and', start=2.422, end=2.522),
Audio(word='Prejudice', start=2.582, end=3.103),
Audio(word='is', start=3.863, end=3.943),
Audio(word='the', start=3.983, end=4.103),
]
df = pd.DataFrame(AUDIO_EN)
# df['card'] = df.apply(lambda row: [row['word'], row['start'], row['end']], axis=1)
df['card'] = df.apply(lambda row: f"{row['word']}, {row['start']}, {row['end']}", axis=1)
# Drop the original columns if needed
df = df.drop(['word', 'start', 'end'], axis=1)
return df.to_dict("records")
app = Dash(__name__)
columnDefs = [
{"field": "id"},
{"field": "text"},
{"field": "upos"},
{"field": "xpos"},
{"field": "start_char"},
{"field": "end_char"},
{"field": "mergeUp"},
{"field": "mergeDown"},
{"field": "split"},
{"field": "card", "dndSource": True}
]
# {'name': 'DnD', 'id': 'dragDrop'},
app.layout = html.Div(
[
dag.AgGrid(
columnDefs=columnDefs,
rowData=get_text_data(),
columnSize="sizeToFit",
defaultColDef={"resizable": True, "sortable": True, "filter": True},
dashGridOptions={"rowDragManaged": True, "animateRows": True},
style={'width': '60%', "height": "90vh"},
id='token_table'
),
dag.AgGrid(
columnDefs=[{"field": "card", "dndSource": True}],
rowData=get_audio_data(),
columnSize="sizeToFit",
defaultColDef={"resizable": True, "sortable": True, "filter": True},
dashGridOptions={"rowDragManaged": True},
style={'width': '40%', "height": "90vh"},
id='cards_table'
),
# html.Button(id='drop1', style={'display': 'none'}, n_clicks=0),
# html.Button(id='drop2', style={'display': 'none'}, n_clicks=0),
dcc.Store(id='dropStore', storage_type='session')
],
style={"display": "flex", "height": "90vh"},
)
## enable drag-n-drop
app.clientside_callback(
"""
function (p) {
console.log("JavaScript code executed!");
var gridOrigin;
var gridDragOver = (event) => {
console.log("dragOver event triggered!");
const dragSupported = event.dataTransfer.types.length;
if (dragSupported) {
event.dataTransfer.dropEffect = 'copy';
event.preventDefault();
}
};
var gridDragStart = (origin, event) => {
console.log("dragStart event triggered!");
gridOrigin = origin;
// Get the dragged data (JSON string)
const jsonData = event.dataTransfer.getData('application/json');
console.log("Dragged data:", jsonData);
// Parse the JSON data to obtain the card value
const data = JSON.parse(jsonData);
console.log("Card value being dragged:", data.card);
}
var gridDrop = (target, event) => {
console.log("drop event triggered!");
event.preventDefault();
const jsonData = event.dataTransfer.getData('application/json');
const data = JSON.parse(jsonData);
// If data is missing or the drop target is the same as the origin, do nothing
if (!data || target == gridOrigin) {
console.log("Data missing or same target as origin. Exiting.");
return;
}
// Get the grid API for the target
const gridApi = dash_ag_grid.getApi(target);
// Get the overNode (row) based on drop coordinates
const overNode = gridApi.getRenderedNodes().find(node => {
const rowTop = node.rowTop;
const rowBottom = node.rowTop + node.rowHeight;
return rowTop <= event.clientY && event.clientY <= rowBottom;
});
console.log("overNode:", overNode);
// Log the existing data for the overNode
console.log("Existing data for overNode:", overNode.data);
// Update the specific row with the dragged card value
const updatedData = { ...overNode.data, card: data.card };
console.log("Updated data:", updatedData);
gridApi.applyTransactionAsync({'update': [updatedData]});
};
setTimeout(() => {
document.querySelector('#token_table').addEventListener('dragstart', (event) => gridDragStart('token_table', event))
document.querySelector('#cards_table').addEventListener('dragstart', (event) => gridDragStart('cards_table', event))
document.querySelector('#token_table').addEventListener('dragover', gridDragOver)
document.querySelector('#cards_table').addEventListener('dragover', gridDragOver)
document.querySelector('#cards_table').addEventListener('drop', (event) => gridDrop('cards_table', event))
document.querySelector('#token_table').addEventListener('drop', (event) => gridDrop('token_table', event))
}, 500)
return window.dash_clientside.no_update
}
""",
Output('cards_table', 'id'),
Input('cards_table', 'id')
)
if __name__ == "__main__":
app.run_server(debug=True, port=1234)
Also, to be able to update your data in the grid, you need to set row ids that are unique for the data.
Once you do that, I believe you should be good with the drag and drop.