DevExtreme v23.1 is now available.

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

Your search did not match any results.
Data Grid

Remote Reordering

This demo shows how to use drag and drop to reorder records stored on the server. This functionality requires that records' order indexes are in an individual data field (OrderIndex in this demo) and sorted against that field.

Row drag and drop is configured in the rowDragging object. Set allowReordering to true to enable this feature. To specify the highlight mode of the row's drop position, use the dropFeedbackMode property. In this demo, it is set to "push": rows move up or down with animation to create space for the new position of the row.

When a row is dropped, the onReorder event handler is called. Use it to update the record's OrderIndex on the server. In this demo, we use the onReorder function's toIndex parameter to obtain the position at which a user dropped the row. The position is then used to get the new order index. The store's update method sends this index to the server where the records are sorted and returned to the client. Server-side implementation is available in the ASP.NET Core and ASP.NET MVC 5 versions of this demo under the DataGridRowReorderingController.cs tab.

Backend API
Copy to CodeSandBox
Apply
Reset
import React from 'react'; import DataGrid, { Column, RowDragging, Scrolling, Lookup, Sorting, } from 'devextreme-react/data-grid'; import { createStore } from 'devextreme-aspnet-data-nojquery'; const url = 'https://js.devexpress.com/Demos/Mvc/api/RowReordering'; const tasksStore = createStore({ key: 'ID', loadUrl: `${url}/Tasks`, updateUrl: `${url}/UpdateTask`, onBeforeSend: (method, ajaxOptions) => { ajaxOptions.xhrFields = { withCredentials: true }; }, }); const employeesStore = createStore({ key: 'ID', loadUrl: `${url}/Employees`, onBeforeSend: (method, ajaxOptions) => { ajaxOptions.xhrFields = { withCredentials: true }; }, }); class App extends React.Component { constructor(props) { super(props); this.onReorder = this.onReorder.bind(this); } onReorder(e) { e.promise = this.processReorder(e); } async processReorder(e) { const visibleRows = e.component.getVisibleRows(); const newOrderIndex = visibleRows[e.toIndex].data.OrderIndex; await tasksStore.update(e.itemData.ID, { OrderIndex: newOrderIndex }); await e.component.refresh(); } render() { return ( <React.Fragment> <DataGrid height={440} dataSource={tasksStore} showBorders={true} > <RowDragging allowReordering={true} onReorder={this.onReorder} dropFeedbackMode="push" /> <Scrolling mode="virtual" /> <Sorting mode="none" /> <Column dataField="ID" width={55} /> <Column dataField="Owner" width={150}> <Lookup dataSource={employeesStore} valueExpr="ID" displayExpr="FullName" /> </Column> <Column dataField="AssignedEmployee" caption="Assignee" width={150}> <Lookup dataSource={employeesStore} valueExpr="ID" displayExpr="FullName" /> </Column> <Column dataField="Subject" /> </DataGrid> </React.Fragment> ); } } export default App;
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.1.5/css/dx.light.css" /> <link rel="stylesheet" type="text/css" href="styles.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> <script type="text/javascript"> System.import("./index.js"); </script> </head> <body class="dx-viewport"> <div class="demo-container"> <div id="app"></div> </div> </body> </html>
.options { padding: 20px; background-color: rgba(191, 191, 191, 0.15); margin-top: 20px; } .caption { font-size: 18px; font-weight: 500; } .option { width: 24%; display: inline-block; margin-top: 10px; }
window.config = { transpiler: 'plugin-babel', meta: { 'devextreme/localization.js': { 'esModule': true, }, 'devextreme-aspnet-data-nojquery': { 'esModule': true, }, }, paths: { 'npm:': 'https://unpkg.com/', }, defaultExtension: 'js', map: { '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@2.9.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.1.5/cjs', 'devextreme-react': 'npm:devextreme-react@23.1.5', 'jszip': 'npm:jszip@3.7.1/dist/jszip.min.js', 'devextreme-quill': 'npm:devextreme-quill@1.6.2/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.2.1/dist/dx-diagram.js', 'devexpress-gantt': 'npm:devexpress-gantt@4.1.48/dist/dx-gantt.js', '@devextreme/runtime': 'npm:@devextreme/runtime@3.0.11', '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', // 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/events': { main: 'index', }, 'es6-object-assign': { main: './index.js', defaultExtension: 'js', }, }, packageConfigPaths: [ 'npm:@devextreme/*/package.json', 'npm:@devextreme/runtime@3.0.11/inferno/package.json', ], babelOptions: { sourceMaps: false, stage0: true, react: true, }, }; System.config(window.config);
using DevExtreme.AspNet.Data; using DevExtreme.AspNet.Mvc; using Newtonsoft.Json; using System; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Formatting; using System.Web.Http; using DevExtreme.MVC.Demos.Models.DataGrid; using DevExtreme.MVC.Demos.Models.SampleData; namespace DevExtreme.MVC.Demos.Controllers { [Route("api/RowReordering/{action}", Name = "DataGridRowReordering")] public class DataGridRowReorderingController : ApiController { InMemoryRowReorderingTasksDataContext _context = new InMemoryRowReorderingTasksDataContext(); [HttpGet] public HttpResponseMessage Tasks(DataSourceLoadOptions loadOptions) { return Request.CreateResponse(DataSourceLoader.Load(_context.Tasks.OrderBy(t => t.OrderIndex), loadOptions)); } [HttpPut] public HttpResponseMessage UpdateTask(FormDataCollection form) { var key = Convert.ToInt32(form.Get("key")); var values = form.Get("values"); var task = _context.Tasks.First(o => o.ID == key); var oldOrderIndex = task.OrderIndex; JsonConvert.PopulateObject(values, task); var newOrderIndex = task.OrderIndex; Validate(task); if(oldOrderIndex != newOrderIndex) { task.OrderIndex = oldOrderIndex; var sortedTasks = _context.Tasks.OrderBy(t => t.OrderIndex).ToList(); if(oldOrderIndex < newOrderIndex) { for(var i = oldOrderIndex + 1; i <= newOrderIndex; i++) { sortedTasks[i].OrderIndex--; }; } else { for(var i = newOrderIndex; i < oldOrderIndex; i++) { sortedTasks[i].OrderIndex++; }; } task.OrderIndex = newOrderIndex; } if(!ModelState.IsValid) return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState.GetFullErrorMessage()); _context.SaveChanges(); return Request.CreateResponse(HttpStatusCode.OK, task); } // additional actions [HttpGet] public HttpResponseMessage Employees(DataSourceLoadOptions loadOptions) { return Request.CreateResponse(DataSourceLoader.Load(SampleData.CustomEditorsEmployees, loadOptions)); } } }
using System; using System.Collections.Generic; namespace DevExtreme.MVC.Demos.Models.DataGrid { public class InMemoryRowReorderingTasksDataContext : InMemoryDataContext<RowReorderingTask> { public ICollection<RowReorderingTask> Tasks => ItemsInternal; protected override IEnumerable<RowReorderingTask> Source => SampleData.SampleData.RowReorderingTasks; protected override int GetKey(RowReorderingTask item) => item.ID; protected override void SetKey(RowReorderingTask item, int key) => item.ID = key; } }