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>
@Html.Partial("_ItemDragging", "PlannedTasks")
</div>
<div>
@Html.Partial("_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.WebApi()
.RouteName("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.MVC.Demos.Models.SampleData;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
namespace DevExtreme.MVC.Demos.Controllers {
public class ListController : Controller {
public ActionResult ItemDragging() {
return View();
}
}
}
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;
namespace DevExtreme.MVC.Demos.Controllers.ApiControllers {
[Route("api/ListItemDraggingEditing/{action}", Name = "ListItemDraggingEditing")]
public class ListItemDraggingEditingController : ApiController {
InMemoryListPlannedDataContext _plannedTasksData = new InMemoryListPlannedDataContext();
InMemoryListDoingDataContext _doingTasksData = new InMemoryListDoingDataContext();
[HttpGet]
public HttpResponseMessage GetPlannedTasks(DataSourceLoadOptions loadOptions) {
return Request.CreateResponse(DataSourceLoader.Load(_plannedTasksData.ListItems, loadOptions));
}
[HttpGet]
public HttpResponseMessage GetDoingTasks(DataSourceLoadOptions loadOptions) {
return Request.CreateResponse(DataSourceLoader.Load(_doingTasksData.ListItems, loadOptions));
}
[HttpPost]
public HttpResponseMessage InsertPlannedTasks(FormDataCollection form) {
var values = form.Get("values");
var listItem = new ListTaskItem();
JsonConvert.PopulateObject(values, listItem);
Validate(listItem);
if(!ModelState.IsValid)
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState.GetFullErrorMessage());
return InsertTask(listItem, _plannedTasksData);
}
[HttpPost]
public HttpResponseMessage InsertDoingTasks(FormDataCollection form) {
var values = form.Get("values");
var listItem = new ListTaskItem();
JsonConvert.PopulateObject(values, listItem);
Validate(listItem);
if(!ModelState.IsValid)
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState.GetFullErrorMessage());
return InsertTask(listItem, _doingTasksData);
}
HttpResponseMessage 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 Request.CreateResponse(HttpStatusCode.Created, listItem);
}
[HttpPut]
public HttpResponseMessage UpdatePlannedTasks(FormDataCollection form) {
var key = Convert.ToInt32(form.Get("key"));
var values = form.Get("values");
var listItem = _plannedTasksData.ListItems.First(a => a.ID == key);
var oldOrderIndex = listItem.Sort;
JsonConvert.PopulateObject(values, listItem);
Validate(listItem);
if(!ModelState.IsValid)
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState.GetFullErrorMessage());
return UpdateTask(listItem, oldOrderIndex, _plannedTasksData);
}
[HttpPut]
public HttpResponseMessage UpdateDoingTasks(FormDataCollection form) {
var key = Convert.ToInt32(form.Get("key"));
var values = form.Get("values");
var listItem = _doingTasksData.ListItems.First(a => a.ID == key);
var oldOrderIndex = listItem.Sort;
JsonConvert.PopulateObject(values, listItem);
Validate(listItem);
if(!ModelState.IsValid)
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState.GetFullErrorMessage());
return UpdateTask(listItem, oldOrderIndex, _doingTasksData);
}
HttpResponseMessage 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 Request.CreateResponse(HttpStatusCode.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(FormDataCollection form) {
var key = Convert.ToInt32(form.Get("key"));
DeleteTask(key, _plannedTasksData);
}
[HttpDelete]
public void DeleteDoingTasks(FormDataCollection form) {
var key = Convert.ToInt32(form.Get("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 System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace DevExtreme.MVC.Demos.Models {
public class InMemoryListDoingDataContext : InMemoryListTasksDataContext<ListTaskItem> {
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 System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace DevExtreme.MVC.Demos.Models {
public class InMemoryListPlannedDataContext : InMemoryListTasksDataContext<ListTaskItem> {
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 System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Web;
namespace DevExtreme.MVC.Demos.Models {
public abstract class InMemoryListTasksDataContext<T> {
protected ICollection<T> ItemsInternal {
get {
var session = HttpContext.Current.Session;
var key = GetType().FullName;
if(session[key] == null)
session[key] = DeepClone(Source);
return (ICollection<T>)session[key];
}
}
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 JsonSerializer.Deserialize<List<T>>(JsonSerializer.Serialize(source));
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace DevExtreme.MVC.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;
using System.Web;
namespace DevExtreme.MVC.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.