'Blazor Re-Ordering A list With Drag And Drop
I am learning Blazor having come from a WinForm UWP background. I have a list of Game:
public class Game
{
public string ID { get; set; }
public string Text { get; set; }
public override string ToString()
{
return Text;
}
}
List<Game> Games = new List<Game> {
new Game() { ID= "Game1", Text= "American Football"},
new Game() { ID= "Game2", Text= "Badminton" },
new Game() { ID= "Game3", Text= "Basketball" },
new Game() { ID= "Game4", Text= "Cricket"},
new Game() { ID= "Game5", Text= "Football" },
new Game() { ID= "Game6", Text= "Golf" },
new Game() { ID= "Game7", Text= "Hockey" },
new Game() { ID= "Game8", Text= "Rugby" },
new Game() { ID= "Game9", Text= "Snooker" },
new Game() { ID= "Game10", Text= "Tennis" },
}; I want to drag and drop to reorder my list. Here is my Element.
<ul ondragover="event.preventDefault();" style="margin:20px">
@foreach (var item in Games)
{
<li draggable="true" style="list-style-type:none; height:30px" @key="item" tabindex="1"
@onclick="@(()=> ClickItem(item))" @ondrop="@(()=> Drop(item))">
<span>@item.Text</span>
</li>
}
I select an item in the list with the @onclick:
Game currentGame;
int currentIndex;
void ClickItem(Game item)
{
currentIndex = Games.FindIndex(a => a.Text == item.Text);
currentGame = item;
}
And I handle the ondrop with:
void Drop(Game item)
{
var newIndex = Games.FindIndex(a => a.Text == item.Text);
Games.Insert(newIndex, currentGame);
Games.RemoveAt(currentIndex);
StateHasChanged();
}
Nothing happens in the Drop method the list does not change and the app breaks but throws no error and navigation stops.All the events are firing. Can't find any examples of S.O. to help.
Solution 1:[1]
I managed to create a working example in BlazorFiddle for you:
https://blazorfiddle.com/s/8jurefka
As per Henk's suggestion I replaced your ClickItem
with a StartDrag
method which handles the @ondrag
event. If you use @onclick
you have to click an item first before you can drag it. Using the @ondrag
event you can avoid this. I think this might have been the cause of the problems in some cases.
I also added a few debugging helpers so I could see what is happening.
Here is the same code as shown in the BlazorFiddle example:
@page "/"
<h1>DragDrop demo</h1>
<ul ondragover="event.preventDefault();" style="margin:20px">
@foreach (var item in Games)
{
if(item != null) //Change @ondrop to @ondragover to update UI in real time
{
<li draggable="true" style="list-style-type:none; height:30px"
@key="item.ID" tabindex="1"
@ondrop="@(()=> Drop(item))"
@ondrag="@(()=> StartDrag(item))">
<span>@item.Text</span> <small>@item.ID</small>
</li>
} else
{
<li>NULL??</li>
}
}
</ul>
<button @onclick="ReportList" >List</button>
@code
{
int currentIndex;
void StartDrag(Game item)
{
currentIndex = GetIndex(item);
Console.WriteLine($"DragStart for {item.ID} index {currentIndex}");
}
void ClickItem(Game item)
{
currentIndex = GetIndex(item);
}
int GetIndex(Game item)
{
return Games.FindIndex(a => a.ID == item.ID);
}
void Drop(Game item)
{
if (item != null)
{
Console.WriteLine($"Drop item {item.Text} ({item.ID})");
var index = GetIndex(item);
Console.WriteLine($"Drop index is {index}, move from {currentIndex}");
// get current item
var current = Games[currentIndex];
// remove game from current index
Games.RemoveAt(currentIndex);
Games.Insert(index, current);
// update current selection
currentIndex = index;
StateHasChanged();
}
else
{
Console.WriteLine("Drop - null");
}
}
void ReportList()
{
int i =0;
foreach (var item in Games)
{
Console.WriteLine($"{i++}: {item.ID} = {item.Text}");
}
}
public class Game
{
public string ID { get; set; }
public string Text { get; set; }
public override string ToString()
{
return Text;
}
}
List<Game> Games = new List<Game> {
new Game() { ID= "Game1", Text= "American Football"},
new Game() { ID= "Game2", Text= "Badminton" },
new Game() { ID= "Game3", Text= "Basketball" },
new Game() { ID= "Game4", Text= "Cricket"},
new Game() { ID= "Game5", Text= "Football" },
new Game() { ID= "Game6", Text= "Golf" },
new Game() { ID= "Game7", Text= "Hockey" },
new Game() { ID= "Game8", Text= "Rugby" },
new Game() { ID= "Game9", Text= "Snooker" },
new Game() { ID= "Game10", Text= "Tennis" },
};
}
Solution 2:[2]
You can do it in Blazor only - no external js library needed:
<ul ondragover="event.preventDefault();"
ondragstart="event.dataTransfer.setData('', event.target.id);">
@foreach (var item in Models.OrderBy(x => x.Order))
{
<li @ondrop="()=>HandleDrop(item)" @key="item">
<div @ondragleave="@(()=> {item.IsDragOver = false;})"
@ondragenter="@(()=>{item.IsDragOver = true;})"
style="@(item.IsDragOver?"border-style: solid none none none; border-color:red;":"")"
@ondragstart="() => draggingModel = item"
@ondragend="()=> draggingModel = null" draggable="true">@item.Name</div>
</li>
}
</ul>
@code
{
public List<Model> Models { get; set; } = new();
public class Model
{
public int Order { get; set; }
public string Name { get; set; } = "";
public bool IsDragOver{ get; set; }
}
protected override void OnInitialized()
{//fill names with "random" string
for (var i = 0; i < 10; i++)
{
Model m = new() { Order = i, Name = $"Item {i}" };
Models.Add(m);
}
base.OnInitialized();
}
private void HandleDrop(Model landingModel)
{//landing model -> where the drop happened
if (draggingModel is null) return;
int originalOrderLanding = landingModel.Order;//keep the original order for later
//increase model under landing one by 1
Models.Where(x => x.Order >= landingModel.Order).ToList().ForEach(x => x.Order++);
draggingModel.Order = originalOrderLanding;//replace landing model
int ii = 0;
foreach (var model in Models.OrderBy(x=>x.Order).ToList())
{
model.Order = ii++;//keep the numbers from 0 to size-1
model.IsDragOver = false;//remove drag over.
}
}
private Model? draggingModel;//the model that is being dragged
}
More info here.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | fretje |
Solution 2 | Alamakanambra |