Row Dragging

Row dragging is used to rearrange rows by dragging the row with the mouse. To enable row dragging, set the column
property rowDrag=True on one (typically the first) column.

columnDefs = [
    # make all rows draggable
    { 'field': 'athlete', 'rowDrag': True },
]

There are two ways in which row dragging works in the grid, managed and unmanaged:

Managed Dragging

In managed dragging, the grid is responsible for rearranging the rows as the rows are dragged. To enable the managed
dragging set the Grid Option:

dashGridOptions = {"rowDragManaged": True}

Dragging Animation

To enable animation of the rows while dragging, set the Grid Option:

dashGridOptions = {'animateRows': True}

Suppress Move When Dragging

By default, the managed row dragging moves the rows while you are dragging them. This effect might not be desirable due
to your application design. To prevent this default behavior, set the Grid Option:

dashGridOptions = {"suppressMoveWhenRowDragging": True}

Multi-Row Dragging

It is possible to drag multiple rows at the same time, set the Grid Options:

dashGridOptions = {"rowDragMultiRow": True, "rowSelection": "multiple"}

The example below shows simple managed dragging. In this example:

When multi-row dragging is enabled:

import dash_ag_grid as dag
from dash import Dash, html, dcc, Input, Output, callback
import pandas as pd

app = Dash(__name__)

df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)

columnDefs = [
    {'field': 'athlete', 'rowDrag': True},
    {'field': 'country'},
    {'field': 'age'},
    {'field': 'year'},
    {'field': 'sport'},
    {'field': 'total'},
]

app.layout = html.Div(
    [
        dcc.Checklist(
            id='chk-row-dragging-options',
            options={
                'animate': 'Animate rows',
                'suppress_move': 'Suppress move when dragging',
                'multi': 'Multi-row dragging'
            },
        ),
        dag.AgGrid(
            id='row-dragging-managed-dragging-options',
            rowData=df.to_dict("records"),
            columnDefs=columnDefs,
            defaultColDef={"filter": True},
            columnSize="sizeToFit",
            dashGridOptions={"rowDragManaged": True}
        ),
    ],
)


@callback(
    Output("row-dragging-managed-dragging-options", "dashGridOptions"),
    Input("chk-row-dragging-options", "value"),
    prevent_initial_call=True,
)
def update_dragging_options(options):
    return {
        "animateRows": 'animate' in options,
        "suppressMoveWhenRowDragging": 'suppress_move' in options,
        "rowDragMultiRow": 'multi' in options,
        "rowSelection": "multiple" if 'multi' in options else "single",
    }


if __name__ == "__main__":
    app.run(debug=True)

The logic for managed dragging has the following constraints:

These constraints can be bypassed by using unmanaged row dragging.

See the AG Grid docs for more details
on unmanaged row dragging.

Entire Row Dragging

When using row dragging it is also possible to reorder rows by clicking and dragging anywhere on the row without the
need for a drag handle by enabling the grid option:

dashGridOptions = {"rowDragEntireRow": True}

The drag handle can be removed by not setting rowDrag=True in Columns Definition.

Range Selection (enterprise feature) is not supported when rowDragEntireRow is enabled.

The example below demonstrates entire row dragging with Multi-Row Dragging. In this example:

import dash_ag_grid as dag
from dash import Dash, html
import pandas as pd

app = Dash(__name__)

df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)

columnDefs = [
    {'field': 'athlete'},
    {'field': 'country'},
    {'field': 'age'},
    {'field': 'year'},
    {'field': 'sport'},
    {'field': 'total'},
]

app.layout = html.Div(
    [
        dag.AgGrid(
            id='row-dragging-entire-row-dragging',
            rowData=df.to_dict("records"),
            columnDefs=columnDefs,
            defaultColDef={"filter": True},
            columnSize="sizeToFit",
            dashGridOptions={
                "rowDragManaged": True,
                "rowDragMultiRow": True,
                "rowSelection": "multiple",
                "rowDragEntireRow": True,
            }
        ),
    ],
)

if __name__ == "__main__":
    app.run(debug=True)

Customization

There are some options that can be used to customize the Row Drag experience, so it has a better integration with your
application.

Custom Row Drag Text

When a row drag starts, a “floating” DOM element is created to indicate which row is being dragged. By default, this DOM
element will contain the same value as the cell that started the row drag. You can override that text by passing a
function to the rowDragText property of Grid Options.

dashGridOptions = {"rowDragText": {"function": "params.defaultTextValue + ' (age: ' + params.rowNode.data.age + ')'"}}

rowDragText (Function) A function that should return a string to be displayed by the rowDragComp while dragging a
row. If this is not set, the current cell value will be used. If rowDragText is set in the Column Definition, it
will take precedence over this, except when rowDragEntireRow=True.

The example below shows dragging with custom text. The following can be noted:

View the JavaScript functions used for this example

These JavaScript functions must be added to the dashAgGridFunctions.js file in the assets folder.
See JavaScript Functions
for more information.

var dagcomponentfuncs = (window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {});

const hostCities = {2000: "Sydney", 2004: "Athens", 2008: "Beijing", 2012: "London",}

dagfuncs.rowDragText = function (params) {
    const {year} = params.rowNode.data;
    if (year in hostCities) {
        return `${params.defaultTextValue} (${hostCities[year]} Olympics)`
    }
    return params.defaultTextValue;
}
import dash_ag_grid as dag
from dash import Dash, html
import pandas as pd

app = Dash(__name__)

df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)

columnDefs = [
    {'field': 'athlete', 'rowDrag': True},
    {'field': 'country'},
    {'field': 'age'},
    {'field': 'year'},
    {'field': 'sport'},
    {'field': 'total'},
]

app.layout = html.Div(
    [
        dag.AgGrid(
            id="row-dragging-custom-row-drag",
            rowData=df.to_dict("records"),
            columnDefs=columnDefs,
            defaultColDef={"filter": True},
            columnSize="sizeToFit",
            dashGridOptions={
                "rowDragManaged": True,
                "rowDragText": {"function": "rowDragText(params)"}
            }
        ),
    ],
)

if __name__ == "__main__":
    app.run(debug=True)

Custom Row Drag Text with Multiple Draggers

If the grid has more than one column set with rowDrag=True, rowDragText can be set in the Column Definition.

columnDefs = [
    {
        'field': 'athlete',
        'rowDrag': True,
        "rowDragText": {"function": "athleteRowDragText(params)"}
    },
    {'field': 'country', 'rowDrag': True},
]

The example below shows dragging with custom text and multiple column draggers. The following can be noted:

View the JavaScript functions used for this example

These JavaScript functions must be added to the dashAgGridFunctions.js file in the assets folder.
See JavaScript Functions
for more information.

var dagcomponentfuncs = (window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {});

const hostCities = {2000: "Sydney", 2004: "Athens", 2008: "Beijing", 2012: "London",}

dagfuncs.rowDragText = function (params) {
    const {year} = params.rowNode.data;
    if (year in hostCities) {
        return `${params.defaultTextValue} (${hostCities[year]} Olympics)`
    }
    return params.defaultTextValue;
}

dagfuncs.athleteRowDragText = function (params) {
    return `${params.rowNodes.length} athlete(s) selected`
}
import dash_ag_grid as dag
from dash import Dash, html
import pandas as pd

app = Dash(__name__)

df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)

columnDefs = [
    {
        'field': 'athlete',
        'rowDrag': True,
        "rowDragText": {"function": "athleteRowDragText(params)"}
    },
    {'field': 'country', 'rowDrag': True},
    {'field': 'age'},
    {'field': 'year'},
    {'field': 'sport'},
    {'field': 'total'},
]

app.layout = html.Div(
    [
        dag.AgGrid(
            id="row-dragging-multiple-row-draggers",
            rowData=df.to_dict("records"),
            columnDefs=columnDefs,
            defaultColDef={"filter": True},
            columnSize="sizeToFit",
            dashGridOptions={
                "rowDragManaged": True,
                "rowDragMultiRow": True,
                "rowSelection": 'multiple',
                "rowDragText": {"function": "rowDragText(params)"}
            }
        ),
    ],
)

if __name__ == "__main__":
    app.run(debug=True)

Row Dragger inside Custom Cell Renderers

Due to the complexity of some applications, it could be handy to render the Row Drag Component inside
a Custom Cell Renderer. This can be achieved by
using the registerRowDragger method in the Custom Cell Renderer:

// this will hold the reference to the element you want to act as row dragger.
const myRef = React.useRef(null);

// synchronize the element with the registerRowDragger function
React.useEffect(() => {
    props.registerRowDragger(myRef.current, props.startDragPixels);
});

// then use the reference in the actual element
React.createElement('i', {className: 'fas fa-arrows-alt-v', ref: myRef})

When using registerRowDragger you should not set the property rowDrag=True in the Column Definition. Doing that
will cause the cell to have two row draggers.

Custom Start Drag Pixels:

By default, the drag event only starts after the Row Drag Element has been dragged by 4px, but sometimes it might be
useful to start the drag with a different drag threshold, for example, start dragging as soon as the mousedown event
happens (dragged by 0px). For that reason, the registerRowDragger takes a second parameter to specify the number of
pixels that will start the drag event.

The example below shows a Custom Cell Renderer, using registerRowDragger to render the Row Dragger inside itself.

View the CSS classes used for this example

These CSS classes must be added to any *.css file in the assets folder.
See Loading CSS files for more information.

.ag-ltr .ag-cell.custom-athlete-cell.ag-cell-focus:not(.ag-cell-range-selected):focus-within {
    border: 1px solid #ff7b7b;
}

.ag-cell.custom-athlete-cell {
    padding-left: 0 !important;
    padding-right: 0 !important;
}

.ag-cell.custom-athlete-cell > div {
    height: 100%;
}

.my-custom-cell-renderer {
    display: flex;
    font-size: 0.7rem;
    background-color: #4180d6;
    color: white;
    padding: 0.25rem;
    align-items: center;
    justify-content: space-between;
    box-sizing: border-box;
    height: 100%;
}

.my-custom-cell-renderer > * {
    line-height: normal;
}

.my-custom-cell-renderer i {
    visibility: hidden;
    cursor: move;
    color: orange;
}

.my-custom-cell-renderer:hover i {
    visibility: visible;
}

.my-custom-cell-renderer .athlete-info {
    display: flex;
    flex-direction: column;
    width: 85px;
    max-width: 85px;
}

.my-custom-cell-renderer .athlete-info > span {
    overflow: hidden;
    text-overflow: ellipsis;
}

View Custom Cell Renderer used for this example

This JavaScript function must be added to the dashAgGridComponentFunctions.js file in the assets folder.
See Custom Components for more
information.

var dagcomponentfuncs = (window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {});

dagcomponentfuncs.RowDraggingCustomCellRenderer = function (props) {

    const myRef = React.useRef(null);

    React.useEffect(() => {
        props.registerRowDragger(myRef.current, props.startDragPixels);
    });

    return React.createElement('div', {className: 'my-custom-cell-renderer'},
        [
            React.createElement('div', {className: 'athlete-info'}, [
                React.createElement('span', null, props.data.athlete),
                React.createElement('span', null, props.data.country),
            ]),
            React.createElement('span', null, props.data.year),
            React.createElement('i', {className: 'fas fa-arrows-alt-v', ref: myRef})
        ]
    );
};

Note the following:

import dash_ag_grid as dag
from dash import Dash, dcc, html, Input, Output, Patch, callback
import dash_bootstrap_components as dbc
import pandas as pd

app = Dash(__name__, external_stylesheets=[dbc.icons.FONT_AWESOME])

df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)

columnDefs = [
    {
        'field': 'athlete',
        'cellClass': 'custom-athlete-cell',
        'cellRenderer': "RowDraggingCustomCellRenderer",
    },
    {'field': 'country'},
    {'field': 'age'},
    {'field': 'year'},
    {'field': 'sport'},
    {'field': 'total'},
]

app.layout = html.Div(
    [
        dcc.Input(id='input-start-drag-pixels', placeholder="Default: 4px", type="number", min=0,
                  className='text-center', style={"width": 150}),
        dag.AgGrid(
            id='row-dragging-custom-start-drag-pixels',
            rowData=df.to_dict("records"),
            columnDefs=columnDefs,
            defaultColDef={"filter": True},
            columnSize="sizeToFit",
            dashGridOptions={"rowDragManaged": True}
        ),
    ],
)


@callback(
    Output("row-dragging-custom-start-drag-pixels", "columnDefs"),
    Input("input-start-drag-pixels", "value"),
    prevent_initial_call=True,
)
def update_start_drag_pixels(pixels):
    columnDefs_patch = Patch()
    columnDefs_patch[0]["cellRendererParams"] = {"startDragPixels": pixels if pixels is not None else 4}
    return columnDefs_patch


if __name__ == "__main__":
    app.run(debug=True)

Full Width Row Dragging

It is possible to drag Full Width Rows by registering
a Custom Row Dragger.

Note the following:

View the CSS classes used for this example

These CSS classes must be added to any *.css file in the assets folder.
See Loading CSS files for more information.

.full-width-panel {
    white-space: normal;
    height: 100%;
    width: 100%;
    border: 2px solid blue;
    box-sizing: border-box;
    padding: 5px;
    background-color: lightblue;
}

.full-width-flag {
    float: left;
    padding: 6px;
}

.full-width-summary {
    float: left;
    margin-right: 10px;
}

.full-width-panel label {
    padding-top: 3px;
    display: inline-block;
    font-size: 12px;
}

.full-width-center {
    overflow-y: scroll;
    border: 1px solid grey;
    padding: 2px;
    height: 100%;
    box-sizing: border-box;
    font-family: cursive;
    background-color: #fafafa;
}

.full-width-center p {
    color: black;
    margin-top: 0px !important;
}

.full-width-title {
    font-size: 20px;
}

View Custom Cell Renderer used for this example

This JavaScript function must be added to the dashAgGridComponentFunctions.js file in the assets folder.
See Custom Components for more
information.

var dagcomponentfuncs = (window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {});

dagcomponentfuncs.fullWidthCellRendererCountry = function (props) {
    return React.createElement('span', null, [
        React.createElement('img', {
            width: "17", height: "12", style: {verticalAlign: "baseline", marginRight: "5px"},
            src: "/assets/images/ag-grid/flags/" + props.data.code + ".svg"
        }, null),
        props.value,
    ])
}

dagcomponentfuncs.RowDraggingFullWidthRow = function (props) {

    const latinText = `<p>Sample Text in a Paragraph<p>
<p>Lorem ipsum dolor sit amet, his mazim necessitatibus te, mea volutpat intellegebat at. Ea nec perpetua liberavisse,
et modo rebum persius pri. Velit recteque reprimique quo at. Vis ex persius oporteat, esse voluptatum moderatius te vis.
 Ex agam suscipit aliquando eum. Mediocrem molestiae id pri, ei cibo facilisis mel. Ne sale nonumy sea. Et vel lorem
 omittam vulputate. Ne prima impedit percipitur vis, erat summo an pro. Id urbanitas deterruisset cum, at legere
 oportere has. No saperet lobortis elaboraret qui, alii zril at vix, nulla soluta ornatus per ad. Feugiat consequuntur
 vis ad, te sit quodsi persequeris, labore perpetua mei ad. Ex sea affert ullamcorper disputationi, sit nisl elit
 elaboraret te, quodsi doctus verear ut eam. Eu vel malis nominati, per ex melius delenit incorrupte. Partem
 complectitur sed in. Vix dicta tincidunt ea. Id nec urbanitas voluptaria, pri no nostro disputationi. Falli
 graeco salutatus pri ea.<p>
 <p>Quo ad omnesque phaedrum principes, tale urbanitas constituam et ius, pericula consequat ad est. Ius tractatos
 referrentur deterruisset an, odio consequuntur sed ad. Ea molestie adipiscing adversarium eos, tale veniam sea no.
 Mutat nullam philosophia sed ad. Pri eu dicta consulatu, te mollis quaerendum sea. Ei doming commodo euismod vis.
 Cu modus aliquip inermis his, eos et eirmod regione delicata, at odio definiebas vis.<p>`;

    const myRef = React.useRef(null);

    React.useEffect(() => {
        props.registerRowDragger(myRef.current, undefined, props.data.name);
    });

    return React.createElement('div', {className: 'full-width-panel', ref: myRef}, [
        React.createElement('div', {className: 'full-width-flag'},
            React.createElement('img', {
                width: "68", height: "40",
                src: "/assets/images/ag-grid/flags/" + props.data.code + ".svg"
            }, null),
        ),
        React.createElement('div', {className: 'full-width-summary'}, [
            React.createElement('span', {className: 'full-width-title'}, props.data.name),
            React.createElement('br'),
            React.createElement('label', {style: {display: "inline"},}, [
                React.createElement('b', null, "Population: "),
                props.data.population
            ]),
            React.createElement('br'),
            React.createElement('label', {style: {display: "inline"},}, [
                React.createElement('b', null, "Known For: "),
                props.data.summary
            ]),
            React.createElement('br'),
        ]),
        React.createElement('div', {
            className: 'full-width-center',
            dangerouslySetInnerHTML: {__html: latinText},
        })
    ]);
}
import dash_ag_grid as dag
from dash import Dash, html

app = Dash(__name__)

raw_data = rowData = [
    {
        # These attributes appear in the top level rows of the grid
        'name': 'Ireland', 'continent': 'Europe', 'language': 'English', 'code': 'ie',
        # These are used in the panel
        'population': 4000000, 'summary': 'Master Drinkers',
    },
    # And then repeat for all the other countries
    {'name': 'Spain', 'continent': 'Europe', 'language': 'Spanish', 'code': 'es', 'population': 4000000,
     'summary': 'Bull Fighters'},
    {'name': 'United Kingdom', 'continent': 'Europe', 'language': 'English', 'code': 'gb', 'population': 4000000,
     'summary': 'Center of the World'},
    {'name': 'France', 'continent': 'Europe', 'language': 'French', 'code': 'fr', 'population': 4000000,
     'summary': 'Best Lovers'},
    {'name': 'Germany', 'continent': 'Europe', 'language': 'German', 'code': 'de', 'population': 4000000,
     'summary': 'Always on Time'},
    {'name': 'Sweden', 'continent': 'Europe', 'language': 'Swedish', 'code': 'se', 'population': 4000000,
     'summary': 'Home of Vikings'},
    {'name': 'Norway', 'continent': 'Europe', 'language': 'Norwegian', 'code': 'no', 'population': 4000000,
     'summary': 'Best Vikings'},
    {'name': 'Italy', 'continent': 'Europe', 'language': 'Italian', 'code': 'it', 'population': 4000000,
     'summary': 'Pizza Pizza'},
    {'name': 'Greece', 'continent': 'Europe', 'language': 'Greek', 'code': 'gr', 'population': 4000000,
     'summary': 'Many Gods'},
    {'name': 'Iceland', 'continent': 'Europe', 'language': 'Icelandic', 'code': 'is', 'population': 4000000,
     'summary': 'Exploding Volcano'},
    {'name': 'Portugal', 'continent': 'Europe', 'language': 'Portuguese', 'code': 'pt', 'population': 4000000,
     'summary': 'Ship Builders'},
    {'name': 'Malta', 'continent': 'Europe', 'language': 'Maltese', 'code': 'mt', 'population': 4000000,
     'summary': 'Fishermen'},
    {'name': 'Brazil', 'continent': 'South America', 'language': 'Portuguese', 'code': 'br', 'population': 4000000,
     'summary': 'Best Footballers'},
    {'name': 'Argentina', 'continent': 'South America', 'language': 'Spanish', 'code': 'ar', 'population': 4000000,
     'summary': 'Beef Steaks'},
    {'name': 'Colombia', 'continent': 'South America', 'language': 'Spanish', 'code': 'co', 'population': 4000000,
     'summary': 'Wonderful Hospitality'},
    {'name': 'Peru', 'continent': 'South America', 'language': 'Spanish', 'code': 'pe', 'population': 4000000,
     'summary': 'Paddington Bear'},
    {'name': 'Venezuela', 'continent': 'South America', 'language': 'Spanish', 'code': 've', 'population': 4000000,
     'summary': 'Never Been, Dunno'},
    {'name': 'Uruguay', 'continent': 'South America', 'language': 'Spanish', 'code': 'uy', 'population': 4000000,
     'summary': 'Excellent Food'}
]

columnDefs = [
    {'field': 'name', 'cellRenderer': "fullWidthCellRendererCountry"},
    {'field': 'continent'},
    {'field': 'language'},
]

app.layout = html.Div(
    [
        dag.AgGrid(
            id='row-dragging-full-width-row',
            rowData=raw_data,
            columnDefs=columnDefs,
            defaultColDef={"filter": True, 'flex': 1},
            dashGridOptions={
                # Sets row height=100 if name in ['Peru', 'France', 'Italy']
                "getRowHeight": {"function": "['Peru', 'France', 'Italy'].includes(params.data.name) && 100"},
                # Sets full width rows if name in ['Peru', 'France', 'Italy']
                "isFullWidthRow": {"function": "['Peru', 'France', 'Italy'].includes(params.rowNode.data.name)"},
                # Custom renderer for full width rows
                "fullWidthCellRenderer": "RowDraggingFullWidthRow",
                # Enable row dragging
                "rowDragManaged": True,
            },
            style={"height": 600}
        ),
    ],
)

if __name__ == "__main__":
    app.run(debug=True)