diff --git a/BlazorSortableList.Lib/SortableList.razor b/BlazorSortableList.Lib/SortableList.razor index f0b13a4..2bcfec2 100644 --- a/BlazorSortableList.Lib/SortableList.razor +++ b/BlazorSortableList.Lib/SortableList.razor @@ -4,6 +4,8 @@ @inject IJSRuntime JS +@implements IAsyncDisposable + @typeparam T
diff --git a/BlazorSortableList.Lib/SortableList.razor.cs b/BlazorSortableList.Lib/SortableList.razor.cs index 313991f..a466504 100644 --- a/BlazorSortableList.Lib/SortableList.razor.cs +++ b/BlazorSortableList.Lib/SortableList.razor.cs @@ -13,6 +13,14 @@ public partial class SortableList private DotNetObjectReference>? selfReference; + private IJSObjectReference? _module; + + /// + /// The id the JS instance was registered under. is a mutable parameter, so it + /// cannot be trusted to still hold the key the instance is stored against by disposal time. + /// + private string? _initializedId; + private string _cssForSelection; private string? _multiDragKey = string.Empty; @@ -68,7 +76,40 @@ public partial class SortableList [Parameter] public RenderFragment? SortableItemTemplate { get; set; } - public void Dispose() => selfReference?.Dispose(); + public async ValueTask DisposeAsync() + { + try + { + if (_module != null) + { + try + { + if (_initializedId != null) + { + await _module.InvokeVoidAsync("destroy", _initializedId); + } + } + finally + { + await _module.DisposeAsync(); + } + } + } + catch (JSDisconnectedException) + { + // The circuit is already gone, so there is nothing left to clean up on the JS side. + } + catch (JSException) + { + // A failing teardown on the JS side must not stop the managed references from being released. + } + finally + { + _module = null; + selfReference?.Dispose(); + selfReference = null; + } + } [JSInvokable] public void OnDeselectJS(string fromId, int index) @@ -146,12 +187,13 @@ protected override async Task OnAfterRenderAsync(bool firstRender) if (firstRender) { selfReference = DotNetObjectReference.Create(this); - var module = await JS.InvokeAsync("import", "./_content/BlazorSortableList/SortableList.razor.js"); + _module = await JS.InvokeAsync("import", "./_content/BlazorSortableList/SortableList.razor.js"); //await JS.InvokeVoidAsync("console.log", $"***id:{Id}, group:{Group},pull: {Pull},put:{Put},sort:{Sort}, handle:{Handle}, filter:{Filter}, forceFallback:{ForceFallback}"); - await module.InvokeAsync( + _initializedId = Id; + await _module.InvokeVoidAsync( "init", - Id, + _initializedId, Group, Pull, Put, diff --git a/BlazorSortableList.Lib/SortableList.razor.js b/BlazorSortableList.Lib/SortableList.razor.js index eb13009..f5271fb 100644 --- a/BlazorSortableList.Lib/SortableList.razor.js +++ b/BlazorSortableList.Lib/SortableList.razor.js @@ -1,3 +1,13 @@ +const instances = new Map(); + +export function destroy(id) { + const sortable = instances.get(id); + if (sortable) { + sortable.destroy(); + instances.delete(id); + } +} + export function init(id, group, pull, put, sort, handle, filter, component, forceFallback, cssForSelection, multiDragKey, avoidImplicitDeselect, fallbackOnBody, swapThreshold) { const DEBUG_MODE = true; @@ -51,6 +61,9 @@ export function init(id, group, pull, put, sort, handle, filter, component, forc // } // }); + // A component re-initialising under an id we already track would otherwise orphan the previous instance. + destroy(id); + var sortable = new SortableCtor(htmlElement, { animation: 200, group: { @@ -187,4 +200,6 @@ export function init(id, group, pull, put, sort, handle, filter, component, forc } } }); + + instances.set(id, sortable); }