Search code examples
blazorblazor-server-side

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

  • 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" },
        };
    }