diff --git a/BlazorReorder.sln b/BlazorReorder.sln
index e620a90..1b60571 100644
--- a/BlazorReorder.sln
+++ b/BlazorReorder.sln
@@ -5,7 +5,7 @@ VisualStudioVersion = 17.2.32314.265
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorReorderExample", "BlazorReorderExample\BlazorReorderExample.csproj", "{43EF61CE-4621-48DB-8648-AA686391D140}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorReorderList", "BlazorReorderList\BlazorReorderList.csproj", "{3DCD1165-CC1D-4015-A6D4-CE97E8A4A72B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorReorderList", "BlazorReorderList\BlazorReorderList.csproj", "{3DCD1165-CC1D-4015-A6D4-CE97E8A4A72B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/BlazorReorderExample/Pages/BetweenLists.razor b/BlazorReorderExample/Pages/BetweenLists.razor
new file mode 100644
index 0000000..3596788
--- /dev/null
+++ b/BlazorReorderExample/Pages/BetweenLists.razor
@@ -0,0 +1,62 @@
+@page "/BetweenLists"
+
+Blazor Reorder Example
+
+
Drag between lists
+
+
+
+
+
+
+
@context.title
+
@context.details Go
+
+
+
+
+
+
+
+
+
@context.title
+
@context.details Go
+
+
+
+
+
+
+
+
+
@context.title
+
@context.details Go
+
+
+
+
+
+
+@code {
+
+ public List list1 = new()
+ {
+ new ListItem("Google", "https://google.com", "Again looking for a bug ..."),
+ new ListItem("StackOverflow", "https://stackoverflow.com", "Could be this the solution?"),
+ new ListItem("GitHub", "https://github.com", "Let's get awesome code"),
+ new ListItem("Twitter", "https://twitter.com", "I want to rest"),
+ new ListItem("Another", "https://another.com", "The solution must be somewhere!!!")
+ };
+
+ public List list2 = new()
+ {
+ new ListItem("Facebook", "https://facebook.com", "Meta"),
+ new ListItem("Instagram", "https://instagram.com", "Meta"),
+ new ListItem("Whatsapp", "https://web.whatsapp.com", "Meta"),
+ };
+
+ public List list3 = new()
+ {
+ new ListItem("Oculus", "https://oculus.com", "Meta"),
+ };
+}
diff --git a/BlazorReorderExample/Pages/Styled.razor b/BlazorReorderExample/Pages/Styled.razor
index 376718b..86d3d70 100644
--- a/BlazorReorderExample/Pages/Styled.razor
+++ b/BlazorReorderExample/Pages/Styled.razor
@@ -2,7 +2,7 @@
Blazor Reorder Example
-Hello, sorted world!
+Custom styles
Welcome to your new reorderer list.
diff --git a/BlazorReorderExample/Pages/Styled.razor.css b/BlazorReorderExample/Pages/Styled.razor.css
index ed31007..628b56c 100644
--- a/BlazorReorderExample/Pages/Styled.razor.css
+++ b/BlazorReorderExample/Pages/Styled.razor.css
@@ -12,6 +12,7 @@
}
::deep .dragging {
+ background: #ffd800;
box-shadow: 0 4px 20px #ffd800AA;
opacity: 0.8;
position: absolute;
diff --git a/BlazorReorderExample/Program.cs b/BlazorReorderExample/Program.cs
index 4c8572b..bff5390 100644
--- a/BlazorReorderExample/Program.cs
+++ b/BlazorReorderExample/Program.cs
@@ -7,7 +7,7 @@ builder.RootComponents.Add("#app");
builder.RootComponents.Add("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
-builder.Services.AddTransient>();
+builder.Services.AddScoped>();
await builder.Build().RunAsync();
diff --git a/BlazorReorderExample/Shared/NavMenu.razor b/BlazorReorderExample/Shared/NavMenu.razor
index 842181f..f3c4b0c 100644
--- a/BlazorReorderExample/Shared/NavMenu.razor
+++ b/BlazorReorderExample/Shared/NavMenu.razor
@@ -19,6 +19,11 @@
Styled
+
+
+ BetweenLists
+
+
diff --git a/BlazorReorderList/Reorder.razor b/BlazorReorderList/Reorder.razor
index fd668f0..8c1edb3 100644
--- a/BlazorReorderList/Reorder.razor
+++ b/BlazorReorderList/Reorder.razor
@@ -1,27 +1,30 @@
@typeparam TItem
+@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.JSInterop
-@inject ReorderJsInterop js
+@inject ReorderService rs
+@inject NavigationManager nav
+@implements IDisposable
-@if (debug)
+@if (Debug)
{
@log
@log2
}
-@if(ghost != null)
+@if(rs.isDragging && elemPosition.x != -1000)
{
- @ChildContent(ghost)
+ @ChildContent(rs.selected)
}
@if (itemElem != null)
{
-
+
@foreach (var item in Items.Select((v, i) => (v, i)))
{
-
await onPress(e, item.v, item.i)">
@ChildContent(item.v)
@@ -33,30 +36,40 @@
{
[Parameter, EditorRequired] public RenderFragment
ChildContent { get; set; } = null!;
[Parameter, EditorRequired] public List Items { get; set; } = null!;
- [Parameter] public bool withShadow { get; set; } = true;
- [Parameter] public bool debug { get; set; } = false;
+ [Parameter] public bool Copy { get; set; } = false;
+ [Parameter] public bool WithShadow { get; set; } = true;
+ [Parameter] public bool Debug { get; set; } = false;
private bool shouldRender = true; // cancel re-rendering
private DotNetObjectReference>? dotNetHelper; //js-interop 2-ways
- ElementReference container;
- ElementReference[]? itemElem;
- ElementReference ghostElem;
- TItem? ghost;
- point elemPosition = new point(0, 0);
- point elemClickPosition = new point(0, 0);
+ Dictionary itemElem = new();
+ ElementReference? sortable;
+ point elemPosition = new point(-1000, 0);
point ghostTrans = new point(0, 0);
+ point clickPosition = new point(0, 0);
int elemWidth = 0;
- int elemIndex = -1;
int newElemIndex = -1;
string log = "", log2 = "";
- protected override void OnParametersSet()
+ protected override void OnInitialized()
{
- if (Items == null) return;
+ nav.LocationChanged += HandleLocationChanged;
+ }
- var count = Items.Count;
- itemElem = new ElementReference[count];
+ public void Dispose()
+ {
+ nav.LocationChanged -= HandleLocationChanged;
+ }
+
+ public void HandleLocationChanged(object? sender, LocationChangedEventArgs e)
+ {
+ if (dotNetHelper == null) return;
+ base.InvokeAsync(async () =>
+ {
+ await rs.removeEvents(dotNetHelper);
+ StateHasChanged();
+ });
}
protected override async Task OnAfterRenderAsync(bool firstRender)
@@ -64,7 +77,7 @@
if (firstRender)
{
dotNetHelper = DotNetObjectReference.Create(this);
- await js.initEvents(dotNetHelper);
+ await rs.initEvents(dotNetHelper);
}
}
@@ -72,77 +85,69 @@
{
if (itemElem == null) return;
shouldRender = false; // Because the method triggers re-render, the click propagation is canceled, if you have a link/or any click event inside it's going to stop working
- ghost = item;
- ghostElem = itemElem[index];
- elemIndex = index;
- elemWidth = await js.getWidth(ghostElem);
- elemPosition = await js.getPosition(ghostElem);
- elemClickPosition = await js.getPoint(m);
+ var ghostElem = itemElem[index];
+ elemWidth = await rs.getWidth(ghostElem);
+ elemPosition = await rs.getPosition(ghostElem);
+ clickPosition = await rs.getPoint(m);
+ rs.Set(Items, item, index, clickPosition);
}
[JSInvokable]
public void onRelease(MouseEventArgs m)
{
- if (Items == null) return;
- if (ghost != null)
+ elemPosition = new point(-1000, 0);
+ if (rs.isDragging && rs.originItems == Items)
{
- ghost = default(TItem);
- ghostElem = default;
- ghostTrans = new point(0, 0);
- elemPosition = new point(0, 0);
- elemClickPosition = new point(0, 0);
- elemWidth = 0;
+ rs.Reset();
// flip
- if (!withShadow)
+ if (!WithShadow)
{
- var item = Items[elemIndex];
+ var item = Items[rs.elemIndex];
shouldRender = false;
- Items.RemoveAt(elemIndex);
+ Items.RemoveAt(rs.elemIndex);
Items.Insert(newElemIndex, item);
- shouldRender = true;
- Log("mouseup" + ghost);
+ Log("mouseup");
}
- StateHasChanged();
+ shouldRender = true;
}
+ StateHasChanged();
}
[JSInvokable]
public async Task onMove(point pos)
{
- if (Items == null || itemElem == null) return;
- if (ghost != null)
+ if (itemElem == null) return;
+ if (rs.isDragging)
{
- ghostTrans = new point(pos.x - elemClickPosition.x, pos.y - elemClickPosition.y);
- Log($"onMove ({ghostTrans.x}, {ghostTrans.y}) {ghost}");
+ shouldRender = false;
+ ghostTrans = new point(pos.x - rs.elemClickPosition.x, pos.y - rs.elemClickPosition.y);
+ Log($"onMove ({elemPosition.x}, {elemPosition.y})");
// check if current drag item is over another item and swap places
- for(var b = 0; b < itemElem.Length; ++b)
+ for (var b = 0; b < itemElem.Count; ++b)
{
- if (b == elemIndex) continue;
var subItem = itemElem[b];
+ if (rs.originItems == Items && b == rs.elemIndex) continue;
- if (await isOnTop(subItem, pos))
+ // If is on top of an element or if top top of a empty list -> confirm drag
+ if (await isOnTop(subItem, pos) ||
+ Items.Count == 0 && await isOnTop((ElementReference) sortable, pos))
{
// reorder
newElemIndex = b;
- var item = Items[elemIndex];
- var item2 = Items[newElemIndex];
- // flip
- if (withShadow)
+ if (WithShadow)
{
- shouldRender = false;
- Items.RemoveAt(elemIndex);
- Items.Insert(newElemIndex, item);
- elemIndex = newElemIndex;
- shouldRender = true;
+ rs.originItems.RemoveAt(rs.elemIndex);
+ Items.Insert(newElemIndex, rs.selected);
+ rs.elemIndex = newElemIndex;
+ if (rs.originItems != Items) rs.originItems = Items;
+ break;
}
-
- Log($"Sobre {item2}");
- break;
}
}
+ shouldRender = true;
StateHasChanged();
}
}
@@ -155,7 +160,8 @@
// checks if mouse x/y is on top of an item
async Task isOnTop(ElementReference item, point pos)
{
- var box = await js.getClientRect(item);
+ var box = await rs.getClientRect(item);
+ if (box.width < 0) return false;
Log($"\npos x: {pos.x}, y: {pos.y}\n item: left:{box.left}, width:{box.width}\nitem: top:{box.top}, height:{box.height}", true);
var isx = (pos.x > box.left && pos.x < (box.left + box.width));
var isy = (pos.y > box.top && pos.y < (box.top + box.height));
@@ -166,11 +172,10 @@
void Log(string info, bool sublog = false)
{
- if (debug)
+ if (Debug)
{
if (sublog) log2 = info;
else log = info;
}
}
-
}
diff --git a/BlazorReorderList/Reorder.razor.css b/BlazorReorderList/Reorder.razor.css
index 1085ee7..d7a962c 100644
--- a/BlazorReorderList/Reorder.razor.css
+++ b/BlazorReorderList/Reorder.razor.css
@@ -1,3 +1,7 @@
+.sortable {
+ min-height: 10px;
+}
+
.item {
cursor: move;
user-select: none;
@@ -12,10 +16,11 @@
}
.dragging {
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
- opacity: 0.8;
- position: absolute;
- z-index: 999;
+ background: #e0ffff;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
+ opacity: 0.8;
+ position: absolute;
+ z-index: 999;
}
.dragging:hover {
diff --git a/BlazorReorderList/ReorderJsInterop.cs b/BlazorReorderList/ReorderService.cs
similarity index 58%
rename from BlazorReorderList/ReorderJsInterop.cs
rename to BlazorReorderList/ReorderService.cs
index 5745535..c6d7ae0 100644
--- a/BlazorReorderList/ReorderJsInterop.cs
+++ b/BlazorReorderList/ReorderService.cs
@@ -4,14 +4,36 @@ using Microsoft.JSInterop;
namespace BlazorReorderList;
-public class ReorderJsInterop : IAsyncDisposable
+public class ReorderService : IAsyncDisposable
{
private readonly Lazy> moduleTask;
+ public List? originItems;
+ public int elemIndex = -1;
+ public TItem selected = default(TItem);
+ public point elemClickPosition = new point(0, 0);
+ public bool isDragging = false;
- public ReorderJsInterop(IJSRuntime jsRuntime)
+ public ReorderService(IJSRuntime jsRuntime)
{
moduleTask = new(() => jsRuntime.InvokeAsync(
- "import", "./_content/BlazorReorderList/ReorderJsInterop.js").AsTask());
+ "import", "./_content/BlazorReorderList/ReorderJsInterop.js").AsTask());
+ }
+
+ public void Set(List list, TItem item, int index, point clickPoint)
+ {
+ isDragging = true;
+ originItems = list;
+ selected = item;
+ elemIndex = index;
+ elemClickPosition = clickPoint;
+ }
+
+ public void Reset()
+ {
+ isDragging = false;
+ originItems = default(List);
+ selected = default(TItem);
+ elemClickPosition = new point(0, 0);
}
public async ValueTask initEvents(DotNetObjectReference> dotNetInstance)
@@ -20,6 +42,12 @@ public class ReorderJsInterop : IAsyncDisposable
await module.InvokeVoidAsync("initEvents", dotNetInstance);
}
+ public async ValueTask removeEvents(DotNetObjectReference> dotNetInstance)
+ {
+ var module = await moduleTask.Value;
+ await module.InvokeVoidAsync("removeEvents", dotNetInstance);
+ }
+
public async ValueTask getWidth(ElementReference el)
{
var module = await moduleTask.Value;
diff --git a/BlazorReorderList/wwwroot/ReorderJsInterop.js b/BlazorReorderList/wwwroot/ReorderJsInterop.js
index 7a50de4..99fb633 100644
--- a/BlazorReorderList/wwwroot/ReorderJsInterop.js
+++ b/BlazorReorderList/wwwroot/ReorderJsInterop.js
@@ -2,6 +2,7 @@
var _w = window,
_b = document.body,
_d = document.documentElement;
+var dotNetInstance = [];
// get position of mouse/touch in relation to viewport
export function getPoint(e) {
@@ -13,11 +14,33 @@ export function getPoint(e) {
return { x: pointX, y: pointY };
}
-export function initEvents(dotNetInstance) {
- window.addEventListener("mousemove", (e) => dotNetInstance.invokeMethodAsync("onMove", getPoint(e)), true);
- window.addEventListener("touchmove", (e) => dotNetInstance.invokeMethodAsync("onMove", getPoint(e)), true);
- window.addEventListener("mouseup", (e) => dotNetInstance.invokeMethodAsync("onRelease", e), true);
- window.addEventListener("touchend", (e) => dotNetInstance.invokeMethodAsync("onRelease", e), true);
+// init events for each list (if the event exists it's ignored)
+export function initEvents(dotNet) {
+ dotNetInstance.push(dotNet);
+ window.addEventListener("mousemove", onMove);
+ window.addEventListener("touchmove", onMove);
+ window.addEventListener("mouseup", onRelease);
+ window.addEventListener("touchend", onRelease);
+}
+
+// only remove from the collection
+export function removeEvents(dotNet) {
+ dotNetInstance = dotNetInstance.filter(x => x._id !== dotNet._id);
+}
+
+// only invoke events form the collection
+function onMove(e) {
+ var point = getPoint(e);
+ for (var i = 0; i < dotNetInstance.length; i++) {
+ dotNetInstance[i].invokeMethodAsync("onMove", point);
+ }
+}
+
+// only invoke events form the collection
+export function onRelease(e) {
+ for (var i = 0; i < dotNetInstance.length; i++) {
+ dotNetInstance[i].invokeMethodAsync("onRelease", e);
+ }
}
export function getWidth(e) {
@@ -27,5 +50,7 @@ export function getPosition(e) {
return { x: e.offsetLeft, y: e.offsetTop };
}
export function getClientRect(e) {
+ // blazor reset elements id in the middle of the query, so this invalidate the query and prevents errors
+ if (e === null || e === undefined) return { width: -1 };
return e.getBoundingClientRect();
}