// Copyright (c) MudBlazor 2021 // MudBlazor licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Diagnostics.CodeAnalysis; using Connected.Annotations; using Connected.Extensions; using Connected.Utilities; using Microsoft.AspNetCore.Components; namespace Connected.Components; public partial class Pagination : UIComponent { #region Css Classes private string Classname => new CssBuilder("mud-pagination") .AddClass($"mud-pagination-{Variant.ToDescriptionString()}") .AddClass($"mud-pagination-{Size.ToDescriptionString()}") .AddClass("mud-pagination-disable-elevation", DisableElevation) .AddClass("mud-pagination-rtl", RightToLeft) .AddClass(Class) .Build(); private string ItemClassname => new CssBuilder("mud-pagination-item") .AddClass("mud-pagination-item-rectangular", Rectangular) .Build(); private string SelectedItemClassname => new CssBuilder(ItemClassname) .AddClass("mud-pagination-item-selected") .Build(); #endregion #region Parameter private int _count = 1; /// /// The number of pages. /// [Parameter] [Category(CategoryTypes.Pagination.Behavior)] public int Count { get => _count; set { _count = Math.Max(1, value); Selected = Math.Min(Selected, _count); } } private int _boundaryCount = 2; /// /// The number of items at the start and end of the pagination. /// [Parameter] [Category(CategoryTypes.Pagination.Appearance)] public int BoundaryCount { get => _boundaryCount; set { _boundaryCount = Math.Max(1, value); } } private int _middleCount = 3; /// /// The number of items in the middle of the pagination. /// [Parameter] [Category(CategoryTypes.Pagination.Appearance)] public int MiddleCount { get => _middleCount; set { _middleCount = Math.Max(1, value); } } private bool _selectedFirstSet; private int _selected = 1; /// /// The selected page number. /// [Parameter] [Category(CategoryTypes.Pagination.Behavior)] public int Selected { get => _selected; set { if (_selected == value) return; //this is required because _selected will stay 1 when Count is not yet set if (!_selectedFirstSet) { _selected = value; _selectedFirstSet = true; } else _selected = Math.Max(1, Math.Min(value, Count)); SelectedChanged.InvokeAsync(_selected); } } /// /// The variant to use. /// [Parameter] [Category(CategoryTypes.Pagination.Appearance)] public Variant Variant { get; set; } = Variant.Text; /// /// The color of the component. It supports the theme colors. /// [Parameter] [Category(CategoryTypes.Pagination.Appearance)] public ThemeColor Color { get; set; } = ThemeColor.Primary; /// /// If true, the pagination buttons are displayed rectangular. /// [Parameter] [Category(CategoryTypes.Pagination.Appearance)] public bool Rectangular { get; set; } /// /// The size of the component.. /// [Parameter] [Category(CategoryTypes.Pagination.Appearance)] public Size Size { get; set; } = Size.Medium; /// /// If true, no drop-shadow will be used. /// [Parameter] [Category(CategoryTypes.Pagination.Appearance)] public bool DisableElevation { get; set; } /// /// If true, the pagination will be disabled. /// [Parameter] [Category(CategoryTypes.Pagination.Behavior)] public bool Disabled { get; set; } /// /// If true, the navigate-to-first-page button is shown. /// [Parameter] [Category(CategoryTypes.Pagination.Behavior)] public bool ShowFirstButton { get; set; } /// /// If true, the navigate-to-last-page button is shown. /// [Parameter] [Category(CategoryTypes.Pagination.Behavior)] public bool ShowLastButton { get; set; } /// /// If true, the navigate-to-previous-page button is shown. /// [Parameter] [Category(CategoryTypes.Pagination.Behavior)] public bool ShowPreviousButton { get; set; } = true; /// /// If true, the navigate-to-next-page button is shown. /// [Parameter] [Category(CategoryTypes.Pagination.Behavior)] public bool ShowNextButton { get; set; } = true; /// /// Invokes the callback when a control button is clicked. /// [Parameter] public EventCallback ControlButtonClicked { get; set; } /// /// Invokes the callback when selected page changes. /// [Parameter] public EventCallback SelectedChanged { get; set; } /// /// Custom first icon. /// [Parameter] [Category(CategoryTypes.Pagination.Appearance)] public string FirstIcon { get; set; } = Icons.Material.Filled.FirstPage; /// /// Custom before icon. /// [Parameter] [Category(CategoryTypes.Pagination.Appearance)] public string BeforeIcon { get; set; } = Icons.Material.Filled.NavigateBefore; /// /// Custom next icon. /// [Parameter] [Category(CategoryTypes.Pagination.Appearance)] public string NextIcon { get; set; } = Icons.Material.Filled.NavigateNext; /// /// Custom last icon. /// [Parameter] [Category(CategoryTypes.Pagination.Appearance)] public string LastIcon { get; set; } = Icons.Material.Filled.LastPage; [CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; } #endregion #region Methods /*generates an array representing the pagination numbers, e.g. for Count==11, MiddleCount==3, BoundaryCount==1, Selected==6 the output will be the int array [1, 2, -1, 5, 6, 7, -1, 10, 11] -1 is displayed as "..." in the ui*/ private IEnumerable GeneratePagination() { //return array {1, ..., Count} if Count is small if (Count <= 4 || Count <= 2 * BoundaryCount + MiddleCount + 2) return Enumerable.Range(1, Count).ToArray(); var length = 2 * BoundaryCount + MiddleCount + 2; var pages = new int[length]; //set start boundary items, e.g. if BoundaryCount == 3 => [1, 2, 3, ...] for (var i = 0; i < BoundaryCount; i++) { pages[i] = i + 1; } //set end boundary items, e.g. if BoundaryCount == 3 and Count == 11 => [..., 9, 10, 11] for (var i = 0; i < BoundaryCount; i++) { pages[length - i - 1] = Count - i; } //calculate start value for middle items var startValue = 0; if (Selected <= BoundaryCount + MiddleCount / 2 + 1) startValue = BoundaryCount + 2; else if (Selected >= Count - BoundaryCount - MiddleCount / 2) startValue = Count - BoundaryCount - MiddleCount; else startValue = Selected - MiddleCount / 2; //set middle items, e.g. if MiddleCount == 3 and Selected == 5 and Count == 11 => [..., 4, 5, 6, ...] for (var i = 0; i < MiddleCount; i++) { pages[BoundaryCount + 1 + i] = startValue + i; } //set start delimiter "..." when selected page is far enough to the end, dots are represented as -1 pages[BoundaryCount] = (BoundaryCount + MiddleCount / 2 + 1 < Selected) ? -1 : BoundaryCount + 1; //set end delimiter "..." when selected page is far enough to the start, dots are represented as -1 pages[length - BoundaryCount - 1] = (Count - BoundaryCount - MiddleCount / 2 > Selected) ? -1 : Count - BoundaryCount; //remove ellipsis if difference is small enough, e.g convert [..., 5 , -1 , 7, ...] to [..., 5, 6, 7, ...] for (var i = 0; i < length - 2; i++) { if (pages[i] + 2 == pages[i + 2]) pages[i + 1] = pages[i] + 1; } return pages; } //triggered when the user clicks on a control button, e.g. the navigate-to-next-page-button private void OnClickControlButton(Page page) { ControlButtonClicked.InvokeAsync(page); NavigateTo(page); } //Last line cannot be tested because Page enum has 4 items /// /// Navigates to the specified page. /// /// The target page. page=Page.Next navigates to the next page. [ExcludeFromCodeCoverage] public void NavigateTo(Page page) { Selected = page switch { Page.First => 1, Page.Last => Math.Max(1, Count), Page.Next => Math.Min(Selected + 1, Count), Page.Previous => Math.Max(1, Selected - 1), _ => Selected }; } /// /// Navigates to the specified page. /// /// The target page. pageIndex=2 navigates to the 3. page. public void NavigateTo(int pageIndex) { Selected = pageIndex + 1; } #endregion }