Row Drag and Drop
The Drag and Drop functionality for the Grid rows allows you to move a row or a multitude of rows between different parents in the same Grid or between different Telerik Grid instances.
This article contains the following sections:
Basics
To enable the Drag and Drop functionality:
-
Set the
RowDraggable
parameter of the<TelerikGrid>
totrue
-
Use the
OnRowDrop
event to handle the drag and drop operations and modify the data source as per your business logic.
The row drag and drop functionality works with a dedicated column which is always rendered as the first column when the feature is enabled.
If the user drags selected rows, the current row selection will be cleared on row drop.
OnRowDrop Event
The OnRowDrop
event fires when the user drops a row into a new location. It allows you to manipulate your data collection based on where the user dropped the element.
Event Arguments
The OnRowDrop
event provides an object of type GridRowDropEventArgs<T>
to its event handler which exposes the following fields:
Parameter | Type | Description |
---|---|---|
Item | object | Represents the dragged row. You can cast this object to your model class. |
DestinationItem | object | Represents the row over which the Item is dropped. You can cast this object to your model class. |
Items | object | Represents the dragged row. You can cast this object to your model class. |
DropPosition | enum | Its members allow you to determine the exact position of the dropped item relative to the position of the DestinationItem . |
DestinationGrid | object | The reference of the Grid in which the row is dropped. This is applicable when you drag and drop rows between different grids. |
DestinationIndex | string | The index where the drop will happen in the second component. |
DestinationComponentId | string | The Id of the second component in which the drop will happen. |
GridRowDraggableSettings
The GridRowDraggableSettings
is a child tag under the <GridSettings>
, which is a child tag of the <TelerikGrid>
. It exposes the following parameters:
DragClueField
-string
- defines which field will be used to render the drag clue text. By default, this parameter will take the value of the first bound column of the first dragged row.
You can find examples of its usage below.
Examples
This section contains the following examples:
- Drag and Drop a Row in the same Grid
- Drag and Drop a Row between Grids
- Drag and Drop between Grid, TreeList, TreeView and Scheduler
- Drag and Drop multiple Rows
Drag and Drop a Row in the same Grid
@* Drag a row and drop it in the Grid. *@
<TelerikGrid Data="@MyData" Height="400px"
Pageable="true"
Resizable="true"
Reorderable="true"
RowDraggable="true"
OnRowDrop="@((GridRowDropEventArgs<SampleData> args) => OnRowDropHandler(args))">
<GridSettings>
<GridRowDraggableSettings DragClueField="@nameof(SampleData.Name)"></GridRowDraggableSettings>
</GridSettings>
<GridColumns>
<GridColumn Field="@(nameof(SampleData.Id))" Width="120px" />
<GridColumn Field="@(nameof(SampleData.Name))" Title="Employee Name" Groupable="false" />
<GridColumn Field="@(nameof(SampleData.Team))" Title="Team" />
<GridColumn Field="@(nameof(SampleData.HireDate))" Title="Hire Date" />
</GridColumns>
</TelerikGrid>
@code {
private void OnRowDropHandler(GridRowDropEventArgs<SampleData> args)
{
//The data manipulations in this example are to showcase a basic scenario.
//In your application you should implement them as per the needs of the project.
MyData.Remove(args.Item);
var destinationItemIndex = MyData.IndexOf(args.DestinationItem);
if (args.DropPosition == GridRowDropPosition.After)
{
destinationItemIndex++;
}
MyData.Insert(destinationItemIndex, args.Item);
}
public List<SampleData> MyData = Enumerable.Range(1, 30).Select(x => new SampleData
{
Id = x,
Name = "name " + x,
Team = "team " + x % 5,
HireDate = DateTime.Now.AddDays(-x).Date
}).ToList();
public class SampleData
{
public int Id { get; set; }
public string Name { get; set; }
public string Team { get; set; }
public DateTime HireDate { get; set; }
}
}
Drag and Drop a Row between Grids
When you drag and drop items from one Grid to another, the OnRowDrop
event fires for the source Grid instance and provides information about the destination Grid. Thus you can update the data sources of both Grids. The Grid instances must be bound to the same model type. It is also possible to bind the Grids to different models, but they must be derived from the same interface.
The target drop area in the Grid is the <table>
element. Users cannot drop items in the empty space below the last table row and this includes the NoDataTemplate
too. There are two ways to prevent possible confusion and enhance the UX:
- Do not set a Grid
Height
, so that there is no empty space below the last table row. - Define a
NoDataTemplate
, which fills the Grid data area.
The following example demonstrates both these options. You can also check how to drag and drop rows in a Grid hierarchy.
Drag and drop items between Grids
<TelerikGrid @ref="@FirstGridRef"
Data="@MyData"
Pageable="true"
PageSize="5"
RowDraggable="true"
OnRowDrop="@((GridRowDropEventArgs<SampleData> args) => OnRowDropHandler(args))">
<GridSettings>
<GridRowDraggableSettings DragClueField="@nameof(SampleData.Name)"></GridRowDraggableSettings>
</GridSettings>
<GridColumns>
<GridColumn Field="@(nameof(SampleData.Id))" Width="120px" />
<GridColumn Field="@(nameof(SampleData.Name))" Title="Employee Name" />
<GridColumn Field="@(nameof(SampleData.Team))" />
<GridColumn Field="@(nameof(SampleData.HireDate))" Title="Hire Date" DisplayFormat="{0:d}" />
</GridColumns>
</TelerikGrid>
<TelerikGrid Data="@MySecondGridData"
Pageable="true"
PageSize="5"
RowDraggable="true"
OnRowDrop="@((GridRowDropEventArgs<SampleData> args) => OnSecondGridRowDropHandler(args))"
Height="300px">
<GridSettings>
<GridRowDraggableSettings DragClueField="@nameof(SampleData.Name)"></GridRowDraggableSettings>
</GridSettings>
<NoDataTemplate>
<div style="padding:85px 0">Drag and drop rows here...</div>
</NoDataTemplate>
<GridColumns>
<GridColumn Field="@(nameof(SampleData.Id))" Width="120px" />
<GridColumn Field="@(nameof(SampleData.Name))" Title="Employee Name" />
<GridColumn Field="@(nameof(SampleData.Team))" />
<GridColumn Field="@(nameof(SampleData.HireDate))" Title="Hire Date" DisplayFormat="{0:d}" />
</GridColumns>
</TelerikGrid>
@code {
private TelerikGrid<SampleData> FirstGridRef { get; set; }
private void OnRowDropHandler(GridRowDropEventArgs<SampleData> args)
{
//The data manipulations in this example are to showcase a basic scenario.
//In your application you should implement them as per the needs of the project.
MyData.Remove(args.Item);
InsertItem(args);
}
private void OnSecondGridRowDropHandler(GridRowDropEventArgs<SampleData> args)
{
//The data manipulations in this example are to showcase a basic scenario.
//In your application you should implement them as per the needs of the project.
MySecondGridData.Remove(args.Item);
InsertItem(args);
}
private void InsertItem(GridRowDropEventArgs<SampleData> args)
{
var destinationData = args.DestinationGrid == FirstGridRef ? MyData : MySecondGridData;
var destinationIndex = 0;
if (args.DestinationItem != null)
{
destinationIndex = destinationData.IndexOf(args.DestinationItem);
if (args.DropPosition == GridRowDropPosition.After)
{
destinationIndex += 1;
}
}
destinationData.InsertRange(destinationIndex, args.Items);
}
private List<SampleData> MyData = Enumerable.Range(1, 12).Select(x => new SampleData
{
Id = 100 + x,
Name = "Name " + (100 + x),
Team = "Team " + (x % 5 + 1),
HireDate = DateTime.Now.AddDays(-x).Date
}).ToList();
private List<SampleData> MySecondGridData = Enumerable.Range(1, 3).Select(x => new SampleData
{
Id = 200 + x,
Name = "Name " + (200 + x),
Team = "Team " + (x % 3 + 1),
HireDate = DateTime.Now.AddDays(-x * 2).Date
}).ToList();
public class SampleData
{
public int Id { get; set; }
public string Name { get; set; }
public string Team { get; set; }
public DateTime HireDate { get; set; }
}
}
Drag and Drop between Grid, TreeList, TreeView and Scheduler
The functionality allows dragging items between Grid, TreeList, TreeView, and Scheduler. To achieve it, set the Draggable
/RowDraggable
parameter, and implement it through an event - OnDrop
/OnRowDrop
.
Drag and Drop from Scheduler to Grid, TreeList, TreeView is not yet supported. Only the reversed way.
Drag and Drop between Grid and TreeList
@using System.Collections.Generic;
@using System.Collections.ObjectModel;
@inject PersonService PersonService;
@inject TreeListService TreeService;
<TelerikGrid Data="@GridData"
Id="Grid1"
Pageable="true"
Width="450px"
PageSize="10"
Sortable="true"
RowDraggable="true"
SelectionMode="@GridSelectionMode.Multiple"
@ref="@GridRef"
OnRowDrop="@((GridRowDropEventArgs<Person> args) => GridRowDrop(args))">
<GridSettings>
<GridRowDraggableSettings DragClueField="@nameof(Person.Name)"></GridRowDraggableSettings>
</GridSettings>
<GridColumns>
<GridColumn Field=@nameof(Person.EmployeeId) Editable="false" />
<GridColumn Field=@nameof(Person.Name) />
</GridColumns>
</TelerikGrid>
<TelerikTreeList Data="@TreeData"
Id="TreeList1"
ItemsField="Items"
Pageable="true"
Width="550px"
RowDraggable="true"
@ref="@TreeListRef"
ParentIdField="ParentId"
OnRowDrop="@((TreeListRowDropEventArgs<FlatItem> args) => TreeListDrop(args))">
<TreeListSettings>
<TreeListRowDraggableSettings DragClueField="@nameof(FlatItem.StringProp)"></TreeListRowDraggableSettings>
</TreeListSettings>
<TreeListColumns>
<TreeListColumn Field="Id"></TreeListColumn>
<TreeListColumn Field="StringProp" Expandable="true"></TreeListColumn>
</TreeListColumns>
</TelerikTreeList>
@code {
public List<Person> GridData { get; set; }
public TelerikGrid<Person> GridRef { get; set; }
public TelerikTreeList<FlatItem> TreeListRef { get; set; }
public List<FlatItem> TreeData { get; set; }
protected override async Task OnInitializedAsync()
{
await LoadData();
}
private async Task LoadData()
{
var people = await PersonService.GetPeopleAsync();
GridData = people.Take(10).ToList();
var treeListData = await TreeService.GetFlatItemsAsync();
TreeData = treeListData.Take(10).ToList();
}
private void GridRowDrop(GridRowDropEventArgs<Person> args)
{
foreach (var item in args.Items)
{
GridData.Remove(item);
}
if (args.DestinationComponentId == "TreeList1")
{
var destinationItem = (FlatItem)TreeListRef.GetItemFromDropIndex(args.DestinationIndex);
args.Items
.Select(item => new FlatItem() { StringProp = item.Name, Id = Guid.NewGuid() }).ToList()
.ForEach(item => UpdateTreeList(item, destinationItem, (TreeListRowDropPosition)(int)args.DropPosition));
}
else if (args.DestinationComponentId == "Grid1")
{
InsertItemsIntoGrid(args.Items, args.DestinationItem, args.DropPosition);
}
}
private void TreeListDrop(TreeListRowDropEventArgs<FlatItem> args)
{
var item = args.Item as FlatItem;
if (args.DestinationComponentId == "TreeList1")
{
var destinationItem = (FlatItem)TreeListRef.GetItemFromDropIndex(args.DestinationIndex);
UpdateTreeList(item, destinationItem, (TreeListRowDropPosition)(int)args.DropPosition);
}
else if (args.DestinationComponentId == "Grid1")
{
var sourceItems = TreeData
.Where(x => x.ParentId == item.Id)
.Select(item => new Person() { Name = item.StringProp, EmployeeId = GridData.Max(x => x.EmployeeId) + 1 });
TreeData.Remove(item);
var destinationItem = GridRef.GetItemFromDropIndex(args.DestinationIndex);
InsertItemsIntoGrid(sourceItems, destinationItem, (GridRowDropPosition)(int)args.DropPosition);
GridRef.Rebind();
}
}
private void InsertItemsIntoGrid(IEnumerable<Person> items, Person destinationItem, GridRowDropPosition dropPosition)
{
var destinationIndex = 0;
if (destinationItem != null)
{
destinationIndex = GridData.IndexOf(destinationItem);
if (dropPosition == GridRowDropPosition.After)
{
destinationIndex += 1;
}
}
GridData.InsertRange(destinationIndex, items);
TreeListRef.Rebind();
}
private void UpdateTreeList(FlatItem item, FlatItem destinationItem, TreeListRowDropPosition dropPosition)
{
var destinationIndex = 0;
if (destinationItem != null)
{
destinationIndex = TreeData.IndexOf(destinationItem);
if (dropPosition == TreeListRowDropPosition.Over)
{
item.ParentId = destinationItem.Id;
}
else if (dropPosition == TreeListRowDropPosition.After)
{
destinationIndex += 1;
}
else if (dropPosition == TreeListRowDropPosition.Before)
{
destinationIndex += 1;
}
TreeData.Insert(destinationIndex, item);
}
TreeData = new List<FlatItem>(TreeData);
}
}
Drag and Drop between Grid and TreeView
@* Drag and drop in Grid and TreeView. *@
@using System.Collections.Generic;
@using System.Collections.ObjectModel;
@inject PersonService PersonService;
<TelerikGrid Data="@GridData"
Id="Grid1"
Width="450px"
RowDraggable="true"
@ref="@GridRef"
OnRowDrop="@((GridRowDropEventArgs<Person> args) => GridRowDrop(args))">
<GridSettings>
<GridRowDraggableSettings DragClueField="@nameof(Person.Name)"></GridRowDraggableSettings>
</GridSettings>
<GridColumns>
<GridColumn Field=@nameof(Person.EmployeeId) Editable="false" />
<GridColumn Field=@nameof(Person.Name) />
</GridColumns>
</TelerikGrid>
<TelerikTreeView @ref="@TreeRef"
Id="TreeView1"
Data="@TreeData"
OnDrop="@TreeViewDrop"
Draggable="true">
<TreeViewBindings>
<TreeViewBinding TextField="Text" ParentIdField="ParentId" />
</TreeViewBindings>
</TelerikTreeView>
@code {
public List<Person> GridData { get; set; }
public TelerikGrid<Person> GridRef { get; set; }
public TelerikTreeView TreeRef { get; set; }
public TreeViewObservableFlatDataService TreeService { get; set; }
public ObservableCollection<BaseFlatItem> TreeData { get; set; }
protected override async Task OnInitializedAsync()
{
await LoadData();
}
private async Task LoadData()
{
var people = await PersonService.GetPeopleAsync();
GridData = people.Take(10).ToList();
TreeService = new TreeViewObservableFlatDataService("Item");
TreeData = TreeService.GetFlatItems();
}
private void GridRowDrop(GridRowDropEventArgs<Person> args)
{
foreach (var item in args.Items)
{
GridData.Remove(item);
}
if (args.DestinationComponentId == "TreeView1")
{
var destinationItem = (BaseFlatItem)TreeRef.GetItemFromDropIndex(args.DestinationIndex);
args.Items
.Select(item => new BaseFlatItem() { Text = item.Name, Id = Guid.NewGuid() })
.ToList()
.ForEach(item => UpdateTreeView(item, destinationItem, args.DropPosition));
}
else if (args.DestinationComponentId == "Grid1")
{
InsertItemsIntoGrid(args.Items, args.DestinationItem, args.DropPosition);
}
}
private void TreeViewDrop(TreeViewDropEventArgs args)
{
var item = args.Item as BaseFlatItem;
if (args.DestinationComponentId == "TreeView1")
{
UpdateTreeView(item, args.DestinationItem as BaseFlatItem, (GridRowDropPosition)(int)args.DropPosition);
}
else if (args.DestinationComponentId == "Grid1")
{
var sourceItems = TreeService.GetChildItems(item)
.Append(item)
.Select(item => new Person() { Name = item.Text, EmployeeId = GridData.Max(x => x.EmployeeId) + 1 });
TreeService.Remove(item, true);
var destinationItem = GridRef.GetItemFromDropIndex(args.DestinationIndex);
InsertItemsIntoGrid(sourceItems, destinationItem, (GridRowDropPosition)(int)args.DropPosition);
GridRef.Rebind();
}
}
private void InsertItemsIntoGrid(IEnumerable<Person> items, Person destinationItem, GridRowDropPosition dropPosition)
{
var destinationIndex = 0;
if (destinationItem != null)
{
destinationIndex = GridData.IndexOf(destinationItem);
if (dropPosition == GridRowDropPosition.After)
{
destinationIndex += 1;
}
}
GridData.InsertRange(destinationIndex, items);
}
private void UpdateTreeView(BaseFlatItem item, BaseFlatItem destinationItem, GridRowDropPosition dropPosition)
{
if (dropPosition == GridRowDropPosition.Over)
{
TreeService.AddChild(item, destinationItem);
}
else if (dropPosition == GridRowDropPosition.After)
{
TreeService.AddNextSibling(item, destinationItem);
}
else if (dropPosition == GridRowDropPosition.Before)
{
TreeService.AddPrevSibling(item, destinationItem);
}
}
}
Drag and Drop between Grid and Scheduler
@* Drag and drop in Grid and TreeView. *@
@using System.Collections.Generic;
@using System.Collections.ObjectModel;
@inject PersonService PersonService;
@inject AppointmentService appointmentService;
@inject ResourceService resourceService;
<TelerikGrid Data="@GridData"
Id="Grid1"
Width="450px"
RowDraggable="true"
@ref="@GridRef"
OnRowDrop="@((GridRowDropEventArgs<Person> args) => GridRowDrop(args))">
<GridSettings>
<GridRowDraggableSettings DragClueField="@nameof(Person.Name)"></GridRowDraggableSettings>
</GridSettings>
<GridColumns>
<GridColumn Field=@nameof(Person.EmployeeId) Editable="false" />
<GridColumn Field=@nameof(Person.Name) />
</GridColumns>
</TelerikGrid>
<TelerikScheduler @bind-Date="@SelectedDate"
Height="600px" Data="@Data"
@ref="@SchedulerRef"
Id="Scheduler1"
OnCreate="@AddAppointment"
OnUpdate="@UpdateAppointment"
OnDelete="@DeleteAppointment"
AllowDelete="true"
AllowUpdate="true"
AllowCreate="true"
Class="my-class"
OnCancel="@(() => Console.WriteLine("CANCEL"))">
<SchedulerSettings>
<SchedulerGroupSettings Resources="@GroupingResources" Orientation="@SchedulerGroupOrientation.Horizontal"></SchedulerGroupSettings>
</SchedulerSettings>
<SchedulerViews>
<SchedulerDayView></SchedulerDayView>
<SchedulerMultiDayView></SchedulerMultiDayView>
<SchedulerWeekView></SchedulerWeekView>
<SchedulerMonthView></SchedulerMonthView>
<SchedulerTimelineView NumberOfDays="2"></SchedulerTimelineView>
</SchedulerViews>
<SchedulerResources>
<SchedulerResource Field="Room" Title="Edit Room" Data="@SchedulerResources"></SchedulerResource>
<SchedulerResource Field="Manager" Data="@SchedulerManagers"></SchedulerResource>
</SchedulerResources>
</TelerikScheduler>
@code {
public DateTime SelectedDate { get; set; } = new DateTime(2019, 11, 11, 6, 0, 0);
List<Appointment> Data = new List<Appointment>();
List<Resource> SchedulerResources = new List<Resource>();
List<Resource> SchedulerManagers = new List<Resource>();
List<string> GroupingResources = new List<string> { "Room", "Manager" };
public List<Person> GridData { get; set; }
public TelerikGrid<Person> GridRef { get; set; }
public TelerikScheduler<Appointment> SchedulerRef { get; set; }
protected override async Task OnInitializedAsync()
{
await LoadData();
}
private async Task LoadData()
{
var people = await PersonService.GetPeopleAsync();
GridData = people.Take(10).ToList();
Data = appointmentService.GetAppointments();
SchedulerResources = await resourceService.GetResourcesAsync();
SchedulerManagers = await resourceService.GetManagersAsync();
}
private void GridRowDrop(GridRowDropEventArgs<Person> args)
{
foreach (var item in args.Items)
{
GridData.Remove(item);
}
if (args.DestinationComponentId == "Scheduler1")
{
foreach (var item in args.Items)
{
DropAppointment(args.DestinationIndex, item);
}
SchedulerRef.Rebind();
}
else if (args.DestinationComponentId == "Grid1")
{
InsertItemsIntoGrid(args.Items, args.DestinationItem, args.DropPosition);
}
}
private void DropAppointment(string index, Person item)
{
var slot = SchedulerRef.GetTimeSlotFromDropIndex(index);
var appointment = new Appointment()
{
Start = slot.Start,
IsAllDay = slot.IsAllDay,
End = slot.End,
Title = item.Name
};
if (slot.Resources != null && slot.Resources.Count > 0)
{
foreach (var resource in slot.Resources)
{
if (resource.Key == "Room")
{
appointment.Room = resource.Value.ToString();
}
if (resource.Key == "Manager")
{
appointment.Manager = resource.Value.ToString();
}
}
}
Data.Add(appointment);
}
private void InsertItemsIntoGrid(IEnumerable<Person> items, Person destinationItem, GridRowDropPosition dropPosition)
{
var destinationIndex = 0;
if (destinationItem != null)
{
destinationIndex = GridData.IndexOf(destinationItem);
if (dropPosition == GridRowDropPosition.After)
{
destinationIndex += 1;
}
}
GridData.InsertRange(destinationIndex, items);
}
void UpdateAppointment(SchedulerUpdateEventArgs args)
{
Appointment item = (Appointment)args.Item;
var matchingItem = Data.FirstOrDefault(a => a.Id == item.Id);
if (matchingItem != null)
{
matchingItem.Title = item.Title;
matchingItem.Description = item.Description;
matchingItem.Start = item.Start;
matchingItem.End = item.End;
matchingItem.IsAllDay = item.IsAllDay;
matchingItem.Room = item.Room;
matchingItem.Manager = item.Manager;
}
}
void AddAppointment(SchedulerCreateEventArgs args)
{
Appointment item = args.Item as Appointment;
Data.Add(item);
}
void DeleteAppointment(SchedulerDeleteEventArgs args)
{
Appointment item = (Appointment)args.Item;
Data.Remove(item);
}
}
Drag and Drop multiple Rows
You can drag and drop multiple rows in one or between multiple instances of the Grid. To enable it, you should set the SelectionMode
parameter of the TelerikGrid to GridSelectionMode.Multiple
. Then, if you drag a selected row, you will effectively drag all the selected rows.
When you select multiple rows, the row drag clue will be N items selected
where N
is the number of selected rows.
@* Select multiple rows and reorder them in the Grid. *@
<TelerikGrid Data="@MyData" Height="400px"
Pageable="true"
Resizable="true"
Reorderable="true"
SelectionMode="@GridSelectionMode.Multiple"
RowDraggable="true"
OnRowDrop="@((GridRowDropEventArgs<SampleData> args) => OnRowDropHandler(args))">
<GridSettings>
<GridRowDraggableSettings DragClueField="@nameof(SampleData.Name)"></GridRowDraggableSettings>
</GridSettings>
<GridColumns>
<GridColumn Field="@(nameof(SampleData.Id))" Width="120px" />
<GridColumn Field="@(nameof(SampleData.Name))" Title="Employee Name" Groupable="false" />
<GridColumn Field="@(nameof(SampleData.Team))" Title="Team" />
<GridColumn Field="@(nameof(SampleData.HireDate))" Title="Hire Date" />
</GridColumns>
</TelerikGrid>
@code {
private void OnRowDropHandler(GridRowDropEventArgs<SampleData> args)
{
//The data manipulations in this example are to showcase a basic scenario.
//In your application you should implement them as per the needs of the project.
if (args.Items.Contains(args.DestinationItem))
{
return;
}
foreach (var item in args.Items)
{
MyData.Remove(item);
}
var destinationItemIndex = MyData.IndexOf(args.DestinationItem);
if (args.DropPosition == GridRowDropPosition.After)
{
destinationItemIndex++;
}
MyData.InsertRange(destinationItemIndex, args.Items);
}
public List<SampleData> MyData = Enumerable.Range(1, 30).Select(x => new SampleData
{
Id = x,
Name = "name " + x,
Team = "team " + x % 5,
HireDate = DateTime.Now.AddDays(-x).Date
}).ToList();
public class SampleData
{
public int Id { get; set; }
public string Name { get; set; }
public string Team { get; set; }
public DateTime HireDate { get; set; }
}
}
Limitations
List on known limitations for the Grid Drag and Drop features:
- Grouping is not supported.