Feel free to share demo-related thoughts here.
If you have technical questions, please create a support ticket in the DevExpress Support Center.
Thank you for the feedback!
If you have technical questions, please create a support ticket in the DevExpress Support Center.
Backend API
<div class="widget-container">
<div>
@await Html.PartialAsync("_ItemDragging", "PlannedTasks")
</div>
<div>
@await Html.PartialAsync("_ItemDragging", "DoingTasks")
</div>
</div>
<script>
function onDragStart(e) {
var items = e.component.option("items");
e.itemData = items[e.fromIndex];
}
function onAdd(e) {
var store = e.component.getDataSource().store(),
values = e.itemData;
values.sort = e.toIndex;
store.insert(values).then(() => {
var changes = [{ type: "insert", data: values }];
store.push(changes);
});
}
function onRemove(e) {
var store = e.component.getDataSource().store(),
key = e.itemData.ID;
store.remove(key).then(() => {
var changes = [{ type: "remove", key: key }];
store.push(changes);
});
}
function onReorder(e) {
var store = e.component.getDataSource().store(),
key = e.itemData.ID,
data = { Sort: e.toIndex };
store.update(key, data).then(() => {
var changes = [{ type: "update", key: key, data: data }];
store.push(changes);
});
}
</script>
@model string
@(Html.DevExtreme().List()
.ID(@Model)
.Height(380)
.DataSource(d => d.Mvc()
.Controller("ListItemDraggingEditing")
.Key("ID")
.LoadAction(string.Format("Get{0}", @Model))
.InsertAction(string.Format("Insert{0}", @Model))
.UpdateAction(string.Format("Update{0}", @Model))
.DeleteAction(string.Format("Delete{0}", @Model))
)
.DataSourceOptions(dso => dso.Paginate(false))
.DataSourceOptions(o => o.ReshapeOnPush(true).Sort("Sort"))
.DisplayExpr("Text")
.ItemDragging(id => id
.AllowReordering(true)
.Group("tasks")
.OnDragStart("onDragStart")
.OnAdd("onAdd")
.OnRemove("onRemove")
.OnReorder("onReorder")
)
)
using DevExtreme.NETCore.Demos.Models.SampleData;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
namespace DevExtreme.NETCore.Demos.Controllers {
public class ListController : Controller {
public ActionResult ItemDragging() {
return View();
}
}
}
using DevExtreme.AspNet.Data;
using DevExtreme.AspNet.Mvc;
using DevExtreme.NETCore.Demos.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using System.Linq;
namespace DevExtreme.NETCore.Demos.Controllers.ApiControllers {
[Route("api/[controller]/[action]")]
public class ListItemDraggingEditingController : Controller {
InMemoryListPlannedDataContext _plannedTasksData;
InMemoryListDoingDataContext _doingTasksData;
public ListItemDraggingEditingController(IHttpContextAccessor httpContextAccessor, IMemoryCache memoryCache) {
_plannedTasksData = new InMemoryListPlannedDataContext(httpContextAccessor, memoryCache);
_doingTasksData = new InMemoryListDoingDataContext(httpContextAccessor, memoryCache);
}
[HttpGet]
public object GetPlannedTasks(DataSourceLoadOptions loadOptions) {
return DataSourceLoader.Load(_plannedTasksData.ListItems, loadOptions);
}
[HttpGet]
public object GetDoingTasks(DataSourceLoadOptions loadOptions) {
return DataSourceLoader.Load(_doingTasksData.ListItems, loadOptions);
}
[HttpPost]
public IActionResult InsertPlannedTasks(string values) {
var listItem = new ListTaskItem();
JsonConvert.PopulateObject(values, listItem);
if(!TryValidateModel(listItem))
return BadRequest(ModelState.GetFullErrorMessage());
return InsertTask(listItem, _plannedTasksData);
}
[HttpPost]
public IActionResult InsertDoingTasks(string values) {
var listItem = new ListTaskItem();
JsonConvert.PopulateObject(values, listItem);
if(!TryValidateModel(listItem))
return BadRequest(ModelState.GetFullErrorMessage());
return InsertTask(listItem, _doingTasksData);
}
IActionResult InsertTask(ListTaskItem listItem, InMemoryListTasksDataContext<ListTaskItem> tasksData) {
var sortedTasks = tasksData.ListItems.OrderBy(t => t.Sort);
for(var i = listItem.Sort; i < tasksData.ListItems.Count; i++) {
sortedTasks.ElementAt(i).Sort++;
}
tasksData.ListItems.Add(listItem);
tasksData.SaveChanges();
return Ok(listItem);
}
[HttpPut]
public IActionResult UpdatePlannedTasks(int key, string values) {
var listItem = _plannedTasksData.ListItems.First(a => a.ID == key);
var oldOrderIndex = listItem.Sort;
JsonConvert.PopulateObject(values, listItem);
if(!TryValidateModel(listItem))
return BadRequest(ModelState.GetFullErrorMessage());
return UpdateTask(listItem, oldOrderIndex, _plannedTasksData);
}
[HttpPut]
public IActionResult UpdateDoingTasks(int key, string values) {
var listItem = _doingTasksData.ListItems.First(a => a.ID == key);
var oldOrderIndex = listItem.Sort;
JsonConvert.PopulateObject(values, listItem);
if(!TryValidateModel(listItem))
return BadRequest(ModelState.GetFullErrorMessage());
return UpdateTask(listItem, oldOrderIndex, _doingTasksData);
}
IActionResult UpdateTask(ListTaskItem listItem, int oldOrderIndex, InMemoryListTasksDataContext<ListTaskItem> tasksData) {
tasksData.ListItems.Remove(listItem);
var sortedTasks = tasksData.ListItems.OrderBy(t => t.Sort);
AdjustSort(listItem, oldOrderIndex, sortedTasks);
tasksData.ListItems.Add(listItem);
tasksData.SaveChanges();
return Ok(listItem);
}
void AdjustSort(ListTaskItem listItem, int oldOrderIndex, IOrderedEnumerable<ListTaskItem> sortedTasks) {
if(oldOrderIndex != listItem.Sort) {
if(oldOrderIndex < listItem.Sort) {
for(var i = oldOrderIndex; i < listItem.Sort; i++) {
if(sortedTasks.ElementAt(i).Sort > 0) {
sortedTasks.ElementAt(i).Sort--;
}
}
} else if(oldOrderIndex > listItem.Sort) {
for(var i = listItem.Sort; i < oldOrderIndex; i++) {
if(sortedTasks.ElementAt(i).Sort < sortedTasks.Count()) {
sortedTasks.ElementAt(i).Sort++;
}
}
}
}
}
[HttpDelete]
public void DeletePlannedTasks(int key) {
DeleteTask(key, _plannedTasksData);
}
[HttpDelete]
public void DeleteDoingTasks(int key) {
DeleteTask(key, _doingTasksData);
}
private void DeleteTask(int key, InMemoryListTasksDataContext<ListTaskItem> tasksData) {
var listItem = tasksData.ListItems.First(a => a.ID == key);
tasksData.ListItems.Remove(listItem);
var sortedTasks = tasksData.ListItems.OrderBy(t => t.Sort);
for(var i = listItem.Sort; i < tasksData.ListItems.Count; i++) {
sortedTasks.ElementAt(i).Sort--;
}
tasksData.SaveChanges();
}
}
}
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using System.Collections.Generic;
using System.Linq;
namespace DevExtreme.NETCore.Demos.Models {
public class InMemoryListDoingDataContext : InMemoryListTasksDataContext<ListTaskItem> {
public InMemoryListDoingDataContext(IHttpContextAccessor contextAccessor, IMemoryCache memoryCache)
: base(contextAccessor, memoryCache) {
}
public override ICollection<ListTaskItem> ListItems => ItemsInternal;
protected override IEnumerable<ListTaskItem> Source => SampleData.SampleData.ListDoingTasks.ToList<ListTaskItem>();
protected override int GetKey(ListTaskItem item) => item.ID;
protected override void SetKey(ListTaskItem item, int key) => item.ID = key;
}
}
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using System.Collections.Generic;
using System.Linq;
namespace DevExtreme.NETCore.Demos.Models {
public class InMemoryListPlannedDataContext : InMemoryListTasksDataContext<ListTaskItem> {
public InMemoryListPlannedDataContext(IHttpContextAccessor contextAccessor, IMemoryCache memoryCache)
: base(contextAccessor, memoryCache) {
}
public override ICollection<ListTaskItem> ListItems => ItemsInternal;
protected override IEnumerable<ListTaskItem> Source => SampleData.SampleData.ListPlannedTasks.ToList<ListTaskItem>();
protected override int GetKey(ListTaskItem item) => item.ID;
protected override void SetKey(ListTaskItem item, int key) => item.ID = key;
}
}
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
namespace DevExtreme.NETCore.Demos.Models {
public abstract class InMemoryListTasksDataContext<T> {
IHttpContextAccessor _contextAccessor;
IMemoryCache _memoryCache;
public InMemoryListTasksDataContext(IHttpContextAccessor contextAccessor, IMemoryCache memoryCache) {
_contextAccessor = contextAccessor;
_memoryCache = memoryCache;
}
protected ICollection<T> ItemsInternal {
get {
var session = _contextAccessor.HttpContext.Session;
if(!session.IsAvailable)
throw new NotSupportedException("Session is required");
var key = session.Id + "_" + GetType().FullName;
return _memoryCache.GetOrCreate(key, entry => {
session.SetInt32("dirty", 1);
entry.SlidingExpiration = TimeSpan.FromMinutes(10);
return DeepClone(Source);
});
}
}
public abstract ICollection<T> ListItems { get; }
protected abstract IEnumerable<T> Source { get; }
public void SaveChanges() {
foreach(var item in ItemsInternal.Where(i => GetKey(i) == 0))
SetKey(item, ItemsInternal.Max(GetKey) + 1);
}
protected abstract int GetKey(T item);
protected abstract void SetKey(T item, int key);
static ICollection<T> DeepClone(IEnumerable<T> source) {
return JsonConvert.DeserializeObject<List<T>>(JsonConvert.SerializeObject(source));
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
namespace DevExtreme.NETCore.Demos.Models {
public class ListTaskItem {
public int ID { get; set; }
public string Text { get; set; }
public int Sort { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
namespace DevExtreme.NETCore.Demos.Models.SampleData {
public partial class SampleData {
public static IEnumerable<ListTaskItem> ListDoingTasks = new[] {
new ListTaskItem {
ID = 1,
Text = "Prepare 2019 Financial",
Sort = 0
},
new ListTaskItem {
ID = 2,
Text = "Prepare 2019 Marketing Plan",
Sort = 1
},
new ListTaskItem {
ID = 3,
Text = "Update Personnel Files",
Sort = 2
},
new ListTaskItem {
ID = 4,
Text = "Review Health Insurance Options Under the Affordable Care Act",
Sort = 3
}
};
public static IEnumerable<ListTaskItem> ListPlannedTasks = new[] {
new ListTaskItem {
ID = 5,
Text = "New Brochures",
Sort = 0
},
new ListTaskItem {
ID = 6,
Text = "2019 Brochure Designs",
Sort = 1
},
new ListTaskItem {
ID = 7,
Text = "Brochure Design Review",
Sort = 2
},
new ListTaskItem {
ID = 8,
Text = "Website Re-Design Plan",
Sort = 3
},
new ListTaskItem {
ID = 9,
Text = "Rollout of New Website and Marketing Brochures",
Sort = 4
},
new ListTaskItem {
ID = 10,
Text = "Create 2018 Sales Report",
Sort = 5
},
new ListTaskItem {
ID = 11,
Text = "Direct vs Online Sales Comparison Report",
Sort = 6
},
new ListTaskItem {
ID = 12,
Text = "Review 2018 Sales Report and Approve 2019 Plans",
Sort = 7
},
new ListTaskItem {
ID = 13,
Text = "Submit Signed NDA",
Sort = 8
},
new ListTaskItem {
ID = 14,
Text = "Update Revenue Projections",
Sort = 9
},
new ListTaskItem {
ID = 15,
Text = "Review Revenue Projections",
Sort = 10
},
new ListTaskItem {
ID = 16,
Text = "Comment on Revenue Projections",
Sort = 11
},
new ListTaskItem {
ID = 17,
Text = "Scan Health Insurance Forms",
Sort = 12
},
new ListTaskItem {
ID = 18,
Text = "Sign Health Insurance Forms",
Sort = 13
},
new ListTaskItem {
ID = 19,
Text = "Follow up with West Coast Stores",
Sort = 14
},
new ListTaskItem {
ID = 20,
Text = "Follow up with East Coast Stores",
Sort = 15
},
new ListTaskItem {
ID = 21,
Text = "Submit Refund Report for 2019 Recall",
Sort = 16
},
new ListTaskItem {
ID = 22,
Text = "Give Final Approval for Refunds",
Sort = 17
},
new ListTaskItem {
ID = 23,
Text = "Prepare Product Recall Report",
Sort = 18
},
new ListTaskItem {
ID = 24,
Text = "Review Product Recall Report by Engineering Team",
Sort = 19
},
new ListTaskItem {
ID = 25,
Text = "Review Training Course for any Omissions",
Sort = 20
},
new ListTaskItem {
ID = 26,
Text = "Review Overtime Report",
Sort = 21
},
new ListTaskItem {
ID = 27,
Text = "Submit Overtime Request Forms",
Sort = 22
},
new ListTaskItem {
ID = 28,
Text = "Overtime Approval Guidelines",
Sort = 23
},
new ListTaskItem {
ID = 29,
Text = "Create Report on Customer Feedback",
Sort = 24
},
new ListTaskItem {
ID = 30,
Text = "Review Customer Feedback Report",
Sort = 25
}
};
}
}
.widget-container {
display: flex;
}
.widget-container > * {
height: 400px;
width: 50%;
padding: 10px;
}
.dx-scrollview-content {
min-height: 380px;
}
The following steps describe how to configure this functionality:
-
Enable item drag and drop
Set the itemDragging.allowReordering to true. -
Add the lists to the same group
Set the group property of both components to the same value to allow a user to drag and drop items between them. -
Reorder list items in code
Use the onDragStart event handler to store the dragged item's data. When a user drops the item, the onRemove and onAdd functions allow you to remove the item from its initial position and add it to the new location.