If you have technical questions, please create a support ticket in the DevExpress Support Center.
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 };
},
});
const processReorder = async (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();
};
const onReorder = (e) => {
e.promise = processReorder(e);
};
const App = () => (
<DataGrid
height={440}
dataSource={tasksStore}
showBorders={true}
>
<RowDragging
allowReordering={true}
onReorder={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>
);
export default App;
xxxxxxxxxx
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 };
},
});
const processReorder = async (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();
};
const onReorder = (e) => {
e.promise = processReorder(e);
};
const App = () => (
<DataGrid
height={440}
dataSource={tasksStore}
showBorders={true}
>
<RowDragging
allowReordering={true}
onReorder={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>
);
export default App;
xxxxxxxxxx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.tsx';
ReactDOM.render(
<App />,
document.getElementById('app'),
);
xxxxxxxxxx
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,
},
'openai': {
'esModule': true,
},
},
paths: {
'npm:': 'https://unpkg.com/',
'bundles:': 'bundles/',
'externals:': 'bundles/externals/',
},
defaultExtension: 'js',
map: {
'ts': 'npm:plugin-typescript@8.0.0/lib/plugin.js',
'typescript': 'npm:typescript@4.2.4/lib/typescript.js',
'jszip': 'npm:jszip@3.10.1/dist/jszip.min.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/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@3.4.4/build/global/luxon.min.js',
'es6-object-assign': 'npm:es6-object-assign',
'devextreme': 'npm:devextreme@link:../../packages/devextreme/artifacts/npm/devextreme/cjs',
'devextreme-react': 'npm:devextreme-react@link:../../packages/devextreme-react/npm/cjs',
'devextreme-quill': 'npm:devextreme-quill@1.7.1/dist/dx-quill.min.js',
'devexpress-diagram': 'npm:devexpress-diagram@2.2.5/dist/dx-diagram.js',
'devexpress-gantt': 'npm:devexpress-gantt@4.1.54/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/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.8/standalone.js',
'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js',
},
packages: {
'devextreme': {
defaultExtension: 'js',
},
'devextreme-react': {
main: 'index.js',
},
'devextreme/events/utils': {
main: 'index',
},
'devextreme/localization/messages': {
format: 'json',
defaultExtension: 'json',
},
'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);
xxxxxxxxxx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js';
ReactDOM.render(<App />, document.getElementById('app'));
xxxxxxxxxx
<html lang="en">
<head></head>
<body class="dx-viewport">
<div class="demo-container">
<div id="app"></div>
</div>
</body>
</html>
xxxxxxxxxx
.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;
}
xxxxxxxxxx
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));
}
}
}
xxxxxxxxxx
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;
}
}
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.