DevExtreme v23.2 is now available.

Explore our newest features/capabilities and share your thoughts with us.

Your search did not match any results.

Drag & Drop Between Two Grids

This functionality requires that data objects have a data field that identifies which grid they belong to. In this demo, this data field is Status.

To allow users to move rows between grids, follow these steps:

  1. Bind the grids to the same store
    The store should be able to update data. In this demo, the store is created using the createStore method (part of the DevExtreme.AspNet.Data extension). The specified updateUrl enables the store to update data.

  2. Specify grid identifiers
    Save them in the rowDragging.data property. The grids below have identifiers 1 and 2.

  3. Filter the grids to display different record sets
    Use the identifiers in the filterValue property to filter the grids. The grids below display only the records whose Status field equals the grid's identifier.

  4. Join the grids into one drag and drop group
    Set the rowDragging.group property to the same value for all grids to allow moving rows between them.

  5. Update the data field that specifies where the row belongs
    Implement the rowDragging.onAdd function. To access the target grid's identifier, use the toData function parameter. Call the store's update method to send this identifier to the server and push the same changes to the store on the client. The grids are refreshed automatically if you enable reshapeOnPush in the dataSource.

Backend API
import React from 'react'; import * as AspNetData from 'devextreme-aspnet-data-nojquery'; import Grid from './Grid.tsx'; const url = 'https://js.devexpress.com/Demos/Mvc/api/DnDBetweenGrids'; const tasksStore = AspNetData.createStore({ key: 'ID', loadUrl: `${url}/Tasks`, updateUrl: `${url}/UpdateTask`, onBeforeSend(method, ajaxOptions) { ajaxOptions.xhrFields = { withCredentials: true }; }, }); const App = () => ( <div className="tables"> <div className="column"> <Grid tasksStore={tasksStore} status={1} /> </div> <div className="column"> <Grid tasksStore={tasksStore} status={2} /> </div> </div> ); export default App;
import React from 'react'; import * as AspNetData from 'devextreme-aspnet-data-nojquery'; import Grid from './Grid.js'; const url = 'https://js.devexpress.com/Demos/Mvc/api/DnDBetweenGrids'; const tasksStore = AspNetData.createStore({ key: 'ID', loadUrl: `${url}/Tasks`, updateUrl: `${url}/UpdateTask`, onBeforeSend(method, ajaxOptions) { ajaxOptions.xhrFields = { withCredentials: true }; }, }); const App = () => ( <div className="tables"> <div className="column"> <Grid tasksStore={tasksStore} status={1} /> </div> <div className="column"> <Grid tasksStore={tasksStore} status={2} /> </div> </div> ); export default App;
import React, { useCallback, useState } from 'react'; import DataGrid, { Column, RowDragging, Scrolling, Lookup, } from 'devextreme-react/data-grid'; const priorities = [{ id: 1, text: 'Low', }, { id: 2, text: 'Normal', }, { id: 3, text: 'High', }, { id: 4, text: 'Urgent', }]; const Grid = ({ tasksStore, status }) => { const [filterExpr] = useState(['Status', '=', status]); const [dataSource] = useState({ store: tasksStore, reshapeOnPush: true, }); const onAdd = useCallback((e) => { const key = e.itemData.ID; const values = { Status: e.toData }; tasksStore.update(key, values).then(() => { tasksStore.push([{ type: 'update', key, data: values, }]); }); }, [tasksStore]); return ( <DataGrid dataSource={dataSource} height={440} showBorders={true} filterValue={filterExpr} > <RowDragging data={status} group="tasksGroup" onAdd={onAdd} /> <Scrolling mode="virtual" /> <Column dataField="Subject" dataType="string" /> <Column dataField="Priority" dataType="number" width={80} > <Lookup dataSource={priorities} valueExpr="id" displayExpr="text" /> </Column> <Column dataField="Status" dataType="number" visible={false} /> </DataGrid> ); }; export default Grid;
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.tsx'; ReactDOM.render( <App />, document.getElementById('app'), );
window.exports = window.exports || {}; window.config = { transpiler: 'ts', typescriptOptions: { module: 'system', emitDecoratorMetadata: true, experimentalDecorators: true, jsx: 'react', }, meta: { 'react': { 'esModule': true, }, 'typescript': { 'exports': 'ts', }, 'devextreme/time_zone_utils.js': { 'esModule': true, }, 'devextreme/localization.js': { 'esModule': true, }, 'devextreme/viz/palette.js': { 'esModule': true, }, 'devextreme-aspnet-data-nojquery': { 'esModule': true, }, }, paths: { 'npm:': 'https://unpkg.com/', }, defaultExtension: 'js', map: { 'ts': 'npm:plugin-typescript@4.2.4/lib/plugin.js', 'typescript': 'npm:typescript@4.2.4/lib/typescript.js', 'react': 'npm:react@17.0.2/umd/react.development.js', 'react-dom': 'npm:react-dom@17.0.2/umd/react-dom.development.js', 'prop-types': 'npm:prop-types@15.8.1/prop-types.js', 'devextreme-aspnet-data-nojquery': 'npm:devextreme-aspnet-data-nojquery@3.0.0/index.js', 'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js', 'luxon': 'npm:luxon@1.28.1/build/global/luxon.min.js', 'es6-object-assign': 'npm:es6-object-assign@1.1.0', 'devextreme': 'npm:devextreme@23.2.5/cjs', 'devextreme-react': 'npm:devextreme-react@23.2.5/cjs', 'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js', 'devextreme-quill': 'npm:devextreme-quill@1.6.4/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.2.5/dist/dx-diagram.js', 'devexpress-gantt': 'npm:devexpress-gantt@4.1.51/dist/dx-gantt.js', '@devextreme/runtime': 'npm:@devextreme/runtime@3.0.12', 'inferno': 'npm:inferno@7.4.11/dist/inferno.min.js', 'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js', 'inferno-create-element': 'npm:inferno-create-element@7.4.11/dist/inferno-create-element.min.js', 'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js', 'inferno-hydrate': 'npm:inferno-hydrate@7.4.11/dist/inferno-hydrate.min.js', 'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js', 'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js', 'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js', 'devextreme-cldr-data': 'npm:devextreme-cldr-data@1.0.3', // SystemJS plugins 'plugin-babel': 'npm:systemjs-plugin-babel@0.0.25/plugin-babel.js', 'systemjs-babel-build': 'npm:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js', // Prettier 'prettier/standalone': 'npm:prettier@2.8.4/standalone.js', 'prettier/parser-html': 'npm:prettier@2.8.4/parser-html.js', }, packages: { 'devextreme': { defaultExtension: 'js', }, 'devextreme-react': { main: 'index.js', }, 'devextreme/events/utils': { main: 'index', }, 'devextreme/localization/messages': { format: 'json', defaultExtension: '', }, 'devextreme/events': { main: 'index', }, 'es6-object-assign': { main: './index.js', defaultExtension: 'js', }, }, packageConfigPaths: [ 'npm:@devextreme/*/package.json', 'npm:@devextreme/runtime@3.0.12/inferno/package.json', ], babelOptions: { sourceMaps: false, stage0: true, react: true, }, }; System.config(window.config);
import React, { useCallback, useState } from 'react'; import DataGrid, { Column, RowDragging, Scrolling, Lookup, } from 'devextreme-react/data-grid'; const priorities = [ { id: 1, text: 'Low', }, { id: 2, text: 'Normal', }, { id: 3, text: 'High', }, { id: 4, text: 'Urgent', }, ]; const Grid = ({ tasksStore, status }) => { const [filterExpr] = useState(['Status', '=', status]); const [dataSource] = useState({ store: tasksStore, reshapeOnPush: true, }); const onAdd = useCallback( (e) => { const key = e.itemData.ID; const values = { Status: e.toData }; tasksStore.update(key, values).then(() => { tasksStore.push([ { type: 'update', key, data: values, }, ]); }); }, [tasksStore], ); return ( <DataGrid dataSource={dataSource} height={440} showBorders={true} filterValue={filterExpr} > <RowDragging data={status} group="tasksGroup" onAdd={onAdd} /> <Scrolling mode="virtual" /> <Column dataField="Subject" dataType="string" /> <Column dataField="Priority" dataType="number" width={80} > <Lookup dataSource={priorities} valueExpr="id" displayExpr="text" /> </Column> <Column dataField="Status" dataType="number" visible={false} /> </DataGrid> ); }; export default Grid;
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.js'; ReactDOM.render(<App />, document.getElementById('app'));
<!DOCTYPE html> <html> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/23.2.5/css/dx.light.css" /> <script src="https://unpkg.com/core-js@2.6.12/client/shim.min.js"></script> <script src="https://unpkg.com/systemjs@0.21.3/dist/system.js"></script> <script type="text/javascript" src="config.js"></script> <link rel="stylesheet" type="text/css" href="styles.css" /> <script type="text/javascript"> System.import("./index.tsx"); </script> </head> <body class="dx-viewport"> <div class="demo-container"> <div id="app"></div> </div> </body> </html>
.tables { display: flex; } .column:first-child { width: 50%; padding-right: 15px; } .column:last-child { width: 50%; padding-left: 15px; }