Open MarvinKlein1508 opened 8 months ago
Exactly! Here comes the rescue, but not battlefield tested. And I am sorry, not doing PR, so use and re-use as desired. Note the added FromId
, ToId
(also in the .js
) with the list id. Your logic then needs to work with the strings to identify the list. You might need to change your logic/list, so you have an id for each list.
Also, I am returning the list itself, which seems easier to work with in my case. Obviosly, if you are moving across multiple lists, you need to identify the other list through the Id.
public class SortAction<T>()
{
public required int OldIndex { get; set; }
public required int NewIndex { get; set; }
public required string ToId { get; set; }
public required string FromId { get; set; }
public required List<T> Items { get; set; }
}
SortableList.razor
@using System.Collections.Generic
@using System.Diagnostics.CodeAnalysis
@inject IJSRuntime JS
@typeparam T
<div id="@Id" class="@Class">
@foreach (var item in Items)
{
@if (SortableItemTemplate is not null)
{
@SortableItemTemplate(item)
}
}
@if (Footer is not null)
{
@Footer
}
</div>
@code {
[Parameter] public RenderFragment<T>? SortableItemTemplate { get; set; }
[Parameter] public RenderFragment? Footer { get; set; }
[Parameter, AllowNull] public List<T> Items { get; set; }
[Parameter] public EventCallback<SortAction<T>> OnUpdate { get; set; }
[Parameter] public EventCallback<SortAction<T>> OnRemove { get; set; }
[Parameter]
public string Id { get; set; } = Guid.NewGuid().ToString();
[Parameter]
public string Group { get; set; } = Guid.NewGuid().ToString();
[Parameter]
public string? Pull { get; set; }
[Parameter]
public bool Put { get; set; } = true;
[Parameter]
public bool Sort { get; set; } = true;
[Parameter]
public string Handle { get; set; } = string.Empty;
[Parameter]
public string? Filter { get; set; }
[Parameter] public string? Class { get; set; }
private DotNetObjectReference<SortableList<T>>? selfReference;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
selfReference = DotNetObjectReference.Create(this);
var module = await JS.InvokeAsync<IJSObjectReference>("import", "./Components/Core/SortableList.razor.js");
await module.InvokeAsync<string>("init", Id, Group, Pull, Put, Sort, Handle, Filter, selfReference);
}
}
[JSInvokable]
public void OnUpdateJS(int oldIndex, int newIndex, string toId, string fromId)
{
Console.WriteLine($"OnUpdateJS: {oldIndex} {newIndex} {toId} {fromId}");
// invoke the OnUpdate event passing in the oldIndex and the newIndex
OnUpdate.InvokeAsync(new SortAction<T>
{
OldIndex = oldIndex,
NewIndex = newIndex,
ToId = toId,
FromId = fromId,
Items = Items
});
}
[JSInvokable]
public void OnRemoveJS(int oldIndex, int newIndex, string toId, string fromId)
{
Console.WriteLine($"OnRemoveJS: {oldIndex} {newIndex} {toId} {fromId}");
// remove the item from the list
OnRemove.InvokeAsync(new SortAction<T>
{
OldIndex = oldIndex,
NewIndex = newIndex,
ToId = toId,
FromId = fromId,
Items = Items
});
}
public void Dispose() => selfReference?.Dispose();
}
SortableList.razor.js
export function init(id, group, pull, put, sort, handle, filter, component) {
var sortable = new Sortable(document.getElementById(id), {
animation: 200,
group: {
name: group,
pull: pull || true,
put: put
},
filter: filter || undefined,
sort: sort,
forceFallback: true,
handle: handle || undefined,
onUpdate: (event) => {
// Revert the DOM to match the .NET state
event.item.remove();
event.to.insertBefore(event.item, event.to.childNodes[event.oldIndex]);
// Notify .NET to update its model and re-render
component.invokeMethodAsync('OnUpdateJS', event.oldDraggableIndex, event.newDraggableIndex, event.to.id, event.from.id);
},
onRemove: (event) => {
if (event.pullMode === 'clone') {
// Remove the clone
event.clone.remove();
}
event.item.remove();
event.from.insertBefore(event.item, event.from.childNodes[event.oldIndex]);
// Notify .NET to update its model and re-render
component.invokeMethodAsync('OnRemoveJS', event.oldDraggableIndex, event.newDraggableIndex, event.to.id, event.from.id);
}
});
}
Hi @VaclavElias
thanks I think this will help me a lot. I'll keep you informed once I tested it with my implementation.
@MarvinKlein1508 let me know if this works well, and I'll open this PR to add these additional properties. Very nice callout from @VaclavElias.
It works for me well. I use it in Kanban, so I need 3 lists, and the only way is to use the provided ids. Also, no hacking here, just using additional parameters provided by the JS library itself.
I like using SortAction
because it provides me also the items right away. Works in my case. It could provide also an item itself (old?), but I didn't explore that option at the moment because I use it only in my kanban 😀
Hi!
First off, nice work! I've tried to implement it within my BlazorForms project, which allows the user to built rich interactive forms using drag & drop and blazor.
My issue is that I cannot figure out how to work with dynamic lists. For example I have a class which hols a list of another class and this one is holding another list of another class again. Basically this:
One instanciated model may look like this:
Using generics I pass the source list to my sorting function like this:
The list itself is implemented like this:
This works fine for me. I'm able to sort all rows and all elements. However I want to be able to move one
FormElement
to anotherFormRow
. So basically remove it from source and then add it to the target list.But there is no way for me to determine the target list. The List of
FormRow
from theForm
class can have unlimited amount of rows.Do you have any clue how this can be achieved?