Skip to content

Commit

Permalink
+ Favorites reorder works (#135)
Browse files Browse the repository at this point in the history
* + SortableGrid
* + dynamic grid layout
* git mv favorites
* + ReorderFavoritesCommand
  • Loading branch information
NicklausBrain authored Dec 26, 2024
1 parent 37f6d39 commit 9252e1a
Show file tree
Hide file tree
Showing 9 changed files with 382 additions and 40 deletions.
1 change: 1 addition & 0 deletions My1kWordsEe/Components/App.razor
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
<script src="_content/Blazor.Bootstrap/blazor.bootstrap.js"></script>
</body>

Expand Down
46 changes: 46 additions & 0 deletions My1kWordsEe/Components/Layout/SortableGrid/SortableGrid.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
@namespace BlazorBootstrap
@inherits BlazorBootstrapComponentBase
@typeparam TItem

<div @ref="@Element" id="@Id" name="@Name" class="@ClassNames" style="@StyleNames" @attributes="@AdditionalAttributes">
@if (IsLoading)
{
if (LoadingTemplate is not null)
{
<div class="col col-12 col-sm-4 p-1">@LoadingTemplate</div>
}
else
{
<Spinner Type="SpinnerType.Dots" Color="SpinnerColor.Secondary" />
}
}
else if (Data?.Any() ?? false)
{
@foreach (var item in Data)
{
if (ItemTemplate is not null)
{
var disableItem = DisableItem?.Invoke(item) ?? false;
if (disableItem) // disable item
{
<div class="col col-12 col-sm-4 p-1 @filter.Replace(".", "") @DisabledItemCssClass">@ItemTemplate(item)</div>
}
else
{
<div class="col col-12 col-sm-4 p-1">@ItemTemplate(item)</div>
}
}
}
}
else
{
if (EmptyDataTemplate is not null)
{
<div class="col col-12 col-sm-4 p-1">@EmptyDataTemplate</div>
}
else
{
<div class="col col-12 col-sm-4 p-1">@EmptyText</div>
}
}
</div>
241 changes: 241 additions & 0 deletions My1kWordsEe/Components/Layout/SortableGrid/SortableGrid.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace BlazorBootstrap;

/// <summary>
/// Represents a sortable list component.
/// </summary>
/// <typeparam name="TItem">The type of items in the list.</typeparam>
public partial class SortableGrid<TItem> : BlazorBootstrapComponentBase
{
#region Fields and Constants

/// <summary>
/// A cancellation token source for managing asynchronous operations.
/// </summary>
private CancellationTokenSource cancellationTokenSource = default!;

/// <summary>
/// A CSS selector used to filter disabled items.
/// </summary>
private string filter = ".bb-sortable-list-item-disabled";

/// <summary>
/// A DotNetObjectReference that allows JavaScript interop with this component.
/// </summary>
private DotNetObjectReference<SortableGrid<TItem>>? objRef;

#endregion

#region Methods

/// <inheritdoc />
protected override async ValueTask DisposeAsyncCore(bool disposing)
{
if (disposing) Data = null!;

await base.DisposeAsyncCore(disposing);
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
await SortableGridJsInterop.InitializeAsync(Id!, Name!, Handle!, Group!, AllowSorting, Pull.ToSortableListPullMode(), Put.ToSortableListPutMode(), filter, objRef!);

await base.OnAfterRenderAsync(firstRender);
}

protected override async Task OnInitializedAsync()
{
objRef ??= DotNetObjectReference.Create(this);

await base.OnInitializedAsync();
}

[JSInvokable]
public async Task OnAddJS(int oldIndex, int newIndex)
{
if (OnAdd.HasDelegate)
await OnAdd.InvokeAsync(new SortableListEventArgs(oldIndex, newIndex));
}

[JSInvokable]
public async Task OnRemoveJS(int oldIndex, int newIndex, string fromListName, string toListName)
{
if (OnRemove.HasDelegate)
await OnRemove.InvokeAsync(new SortableListEventArgs(oldIndex, newIndex, fromListName, toListName));
}

[JSInvokable]
public async Task OnUpdateJS(int oldIndex, int newIndex)
{
if (OnUpdate.HasDelegate)
await OnUpdate.InvokeAsync(new SortableListEventArgs(oldIndex, newIndex));
}

#endregion

#region Properties, Indexers

protected override string? ClassNames =>
BuildClassNames(Class, ("row", true));

/// <summary>
/// Gets or sets a value indicating whether sorting is allowed for the list.
/// </summary>
/// <remarks>
/// Default value is true.
/// </remarks>
[Parameter]
public bool AllowSorting { get; set; } = true;

/// <summary>
/// Gets or sets the content to be rendered within the component.
/// </summary>
/// <remarks>
/// Default value is null.
/// </remarks>
[Parameter]
public RenderFragment ChildContent { get; set; } = default!;

/// <summary>
/// Gets or sets the items.
/// </summary>
/// <remarks>
/// Default value is null.
/// </remarks>
[Parameter]
public List<TItem> Data { get; set; } = default!;

/// <summary>
/// Gets or sets the CSS class applied to disabled items.
/// </summary>
/// <remarks>
/// Default value is null.
/// </remarks>
[Parameter]
public string? DisabledItemCssClass { get; set; } = default!;

/// <summary>
/// Gets or sets a delegate that determines whether an item should be disabled.
/// </summary>
[Parameter]
public Func<TItem, bool> DisableItem { get; set; } = default!;

/// <summary>
/// Gets or sets the empty data template.
/// </summary>
/// <remarks>
/// Default value is null.
/// </remarks>
[Parameter]
public RenderFragment EmptyDataTemplate { get; set; } = default!;

/// <summary>
/// Gets or sets the text to display when there are no records in the list.
/// </summary>
/// <remarks>
/// Default value is `No records to display`.
/// </remarks>
[Parameter]
public string EmptyText { get; set; } = "No records to display";

/// <summary>
/// Gets or sets the group name associated with the list.
/// </summary>
/// <remarks>
/// Default value is null.
/// </remarks>
[Parameter]
public string? Group { get; set; }

/// <summary>
/// Gets or sets the CSS selector for the drag handle element.
/// </summary>
/// <remarks>
/// Default value is null.
/// </remarks>
[Parameter]
public string? Handle { get; set; }

/// <summary>
/// Gets or sets the loading state.
/// </summary>
/// <remarks>
/// Default value is false.
/// </remarks>
[Parameter]
public bool IsLoading { get; set; }

/// <summary>
/// Gets or sets the template used to render individual items in the list.
/// </summary>
/// <remarks>
/// Default value is null.
/// </remarks>
[Parameter]
public RenderFragment<TItem>? ItemTemplate { get; set; }

/// <summary>
/// Gets or sets the loading template.
/// </summary>
/// <remarks>
/// Default value is null.
/// </remarks>
[Parameter]
public RenderFragment LoadingTemplate { get; set; } = default!;

/// <summary>
/// Gets or sets the name of the <see cref="SortableGrid{TItem}" /> component.
/// </summary>
/// <remarks>
/// Default value is null.
/// </remarks>
[Parameter]
public string? Name { get; set; }

/// <summary>
/// Gets or sets an event callback that fires when an item is added to the list.
/// </summary>
[Parameter]
public EventCallback<SortableListEventArgs> OnAdd { get; set; }

/// <summary>
/// Gets or sets an event callback that fires when an item is removed from the list.
/// </summary>
[Parameter]
public EventCallback<SortableListEventArgs> OnRemove { get; set; }

/// <summary>
/// Gets or sets an event callback that fires when an item is updated in the list.
/// </summary>
[Parameter]
public EventCallback<SortableListEventArgs> OnUpdate { get; set; }

/// <summary>
/// Gets or sets the pull mode for the sortable list.
/// </summary>
/// <remarks>
/// Default value is <see cref="SortableListPullMode.True" />.
/// </remarks>
[Parameter]
public SortableListPullMode Pull { get; set; } = SortableListPullMode.True;

/// <summary>
/// Gets or sets the put mode for the sortable list.
/// </summary>
/// <remarks>
/// Default value is <see cref="SortableListPutMode.True" />.
/// </remarks>
[Parameter]
public SortableListPutMode Put { get; set; } = SortableListPutMode.True;

/// <summary>
/// Provides JavaScript interop functionality for the Sortable List.
/// </summary>
[Inject]
private SortableListJsInterop SortableGridJsInterop { get; set; } = default!;

#endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
::deep .bb-sortable-list-handle {
cursor: grab !important;
}
Loading

0 comments on commit 9252e1a

Please sign in to comment.