Update pagination posts
This commit is contained in:
parent
f7f8399b63
commit
25f32cefbb
|
@ -32,21 +32,11 @@ namespace ASIS.Shared;
|
||||||
[DataContract(IsReference = true)]
|
[DataContract(IsReference = true)]
|
||||||
public class PaginationOptions
|
public class PaginationOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// This constructor exists so that MVC can instantiate the object before mapping is contents
|
|
||||||
/// </summary>
|
|
||||||
public PaginationOptions() { }
|
|
||||||
|
|
||||||
public PaginationOptions(int pageNumber, int? resultsPerPage)
|
|
||||||
{
|
|
||||||
this.PageNumber = pageNumber;
|
|
||||||
this.ResultsPerPage = resultsPerPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int? _resultsPerPage;
|
private int? _resultsPerPage;
|
||||||
private int _pageNumber;
|
private int _pageNumber;
|
||||||
|
|
||||||
/// <summary>The number of results per page</summary>
|
/// <summary>The number of results per page</summary>
|
||||||
|
/// <remarks><c>null</c> for unlimited</remarks>
|
||||||
[DataMember]
|
[DataMember]
|
||||||
public int? ResultsPerPage
|
public int? ResultsPerPage
|
||||||
{
|
{
|
||||||
|
@ -61,9 +51,7 @@ public class PaginationOptions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>The page number to get</summary>
|
||||||
/// The page number to get
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>One indexed</remarks>
|
/// <remarks>One indexed</remarks>
|
||||||
[DataMember]
|
[DataMember]
|
||||||
public int PageNumber
|
public int PageNumber
|
||||||
|
@ -78,7 +66,38 @@ public class PaginationOptions
|
||||||
this._pageNumber = value;
|
this._pageNumber = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This constructor exists so that MVC can instantiate the object before mapping is contents
|
||||||
|
/// </summary>
|
||||||
|
public PaginationOptions() { }
|
||||||
|
|
||||||
|
public PaginationOptions(int pageNumber, int? resultsPerPage)
|
||||||
|
{
|
||||||
|
this.PageNumber = pageNumber;
|
||||||
|
this.ResultsPerPage = resultsPerPage;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This exists because <see cref="TagHelper"/>s can't be generic, so passing them an actual
|
||||||
|
/// <see cref="PaginatedResults{T}"/> wouldn't work
|
||||||
|
/// </summary>
|
||||||
|
public interface IPaginatedResults
|
||||||
|
{
|
||||||
|
public int PageNumber { get; }
|
||||||
|
public int LastPageNumber { get; }
|
||||||
|
public int? ResultsPerPage { get; }
|
||||||
|
public int CurrentPageResultsCount { get; }
|
||||||
|
public int TotalResultsCount { get; }
|
||||||
|
public int FirstResultNumber { get; }
|
||||||
|
public int LastResultNumber { get; }
|
||||||
|
public bool IsFirstPage { get; }
|
||||||
|
public bool IsLastPage { get; }
|
||||||
|
public int PreviousPageNumber { get; }
|
||||||
|
public int NextPageNumber { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An extension of <see cref="PaginationOptions"/> that contains the results from a query and uses
|
/// An extension of <see cref="PaginationOptions"/> that contains the results from a query and uses
|
||||||
|
@ -89,7 +108,7 @@ public class PaginationOptions
|
||||||
/// </typeparam>
|
/// </typeparam>
|
||||||
/// <typeparam name="U">The data type of the results</typeparam>
|
/// <typeparam name="U">The data type of the results</typeparam>
|
||||||
[DataContract(IsReference = true)]
|
[DataContract(IsReference = true)]
|
||||||
public class PaginatedResults<T> : PaginationOptions where T : class
|
public class PaginatedResults<T> : PaginationOptions, IPaginatedResults where T : class
|
||||||
{
|
{
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
public PaginatedResults(int pageNumber, int? resultsPerPage, IEnumerable<T> results,
|
public PaginatedResults(int pageNumber, int? resultsPerPage, IEnumerable<T> results,
|
||||||
|
@ -118,21 +137,21 @@ public class PaginatedResults<T> : PaginationOptions where T : class
|
||||||
/// The number of results on the current page
|
/// The number of results on the current page
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataMember]
|
[DataMember]
|
||||||
public int? CurrentPageResultsCount { get => this.Results.Count(); }
|
public int CurrentPageResultsCount { get => this.Results.Count(); }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The number of the first result on the current page. This is calculated using
|
/// The number of the first result on the current page. This is calculated using
|
||||||
/// <see cref="ResultsPerPage"/>, <see cref="PageNumber"/>, and <see cref="TotalResults"/>.
|
/// <see cref="ResultsPerPage"/>, <see cref="PageNumber"/>, and <see cref="TotalResults"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataMember]
|
[DataMember]
|
||||||
public int? FirstResultNumber
|
public int FirstResultNumber
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (this.ResultsPerPage is null)
|
if (this.ResultsPerPage is null)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
return (this.ResultsPerPage * (this.PageNumber - 1)) + 1;
|
return ((int)this.ResultsPerPage * (this.PageNumber - 1)) + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,14 +160,14 @@ public class PaginatedResults<T> : PaginationOptions where T : class
|
||||||
/// <see cref="ResultsPerPage"/>, <see cref="PageNumber"/>, and <see cref="TotalResults"/>.
|
/// <see cref="ResultsPerPage"/>, <see cref="PageNumber"/>, and <see cref="TotalResults"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataMember]
|
[DataMember]
|
||||||
public int? LastResultNumber
|
public int LastResultNumber
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (this.ResultsPerPage is null)
|
if (this.ResultsPerPage is null)
|
||||||
return this.Results.Count();
|
return this.Results.Count();
|
||||||
|
|
||||||
return (this.ResultsPerPage * (this.PageNumber - 1)) + this.CurrentPageResultsCount;
|
return ((int)this.ResultsPerPage * (this.PageNumber - 1)) + this.CurrentPageResultsCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,15 +176,14 @@ public class PaginatedResults<T> : PaginationOptions where T : class
|
||||||
/// <see cref="TotalResultsCount"/>
|
/// <see cref="TotalResultsCount"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataMember]
|
[DataMember]
|
||||||
public int? LastPageNumber
|
public int LastPageNumber
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (this.ResultsPerPage is null)
|
if (this.ResultsPerPage is null)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
return (int)Math.Ceiling(this.TotalResultsCount
|
return (int)Math.Ceiling(this.TotalResultsCount / (double)this.ResultsPerPage);
|
||||||
/ (double)this.ResultsPerPage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,28 +27,36 @@ The query parameter given as `page-number-key` will be changed or added to each
|
||||||
The `pager-details` tag helper:
|
The `pager-details` tag helper:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
using ASIS.SecretsManagement.Shared;
|
using ASIS.Shared;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using Microsoft.AspNetCore.Mvc.TagHelpers;
|
using Microsoft.AspNetCore.Mvc.TagHelpers;
|
||||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
|
|
||||||
|
namespace ASIS.Shared.TagHelpers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Displays details about pagination in the form "Showing results x - y of z" on the left and
|
/// Displays details about pagination in the form "Showing results x - y of z" on the left and
|
||||||
/// "Page x of y" on the right
|
/// "Page x of y" on the right
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PagerDetailsTagHelper : TagHelper
|
public class PaginationDetailsTagHelper : TagHelper
|
||||||
{
|
{
|
||||||
public PaginatedResults<object> Results { get; set; }
|
public required IPaginatedResults Results { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name for the item to be displayed, e.g. <c>results</c>. This will be used in the format
|
||||||
|
/// "Showing <c>results</c> 1-10 of 100".
|
||||||
|
/// </summary>
|
||||||
|
public string ResultName { get; set; } = "results";
|
||||||
|
|
||||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||||
{
|
{
|
||||||
output.TagName = "div";
|
output.TagName = "div";
|
||||||
output.TagMode = TagMode.StartTagAndEndTag;
|
output.TagMode = TagMode.StartTagAndEndTag;
|
||||||
output.AddClass("paginationDetails", HtmlEncoder.Default);
|
output.AddClass("pagination-details", HtmlEncoder.Default);
|
||||||
|
|
||||||
TagBuilder resultsNumDetails = new("span");
|
TagBuilder resultsNumDetails = new("span");
|
||||||
resultsNumDetails.InnerHtml.Append("Showing results "
|
resultsNumDetails.InnerHtml.Append($"Showing {this.ResultName} "
|
||||||
+ $"{this.Results.FirstResultNumber} - {this.Results.LastResultNumber} "
|
+ $"{this.Results.FirstResultNumber} - {this.Results.LastResultNumber} "
|
||||||
+ $"of {this.Results.TotalResultsCount}");
|
+ $"of {this.Results.TotalResultsCount}");
|
||||||
output.Content.AppendHtml(resultsNumDetails);
|
output.Content.AppendHtml(resultsNumDetails);
|
||||||
|
@ -64,7 +72,6 @@ public class PagerDetailsTagHelper : TagHelper
|
||||||
And the `pager` tag helper:
|
And the `pager` tag helper:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
using ASIS.SecretsManagement.Shared;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||||
|
@ -72,6 +79,8 @@ using Microsoft.AspNetCore.WebUtilities;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
|
namespace ASIS.Shared.TagHelpers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a list of links to pages for the current list of data. This will replace the page number
|
/// Creates a list of links to pages for the current list of data. This will replace the page number
|
||||||
/// parameter in the query string using the <see cref="PageNumberKey"/>.
|
/// parameter in the query string using the <see cref="PageNumberKey"/>.
|
||||||
|
@ -79,56 +88,64 @@ using System.Collections.Immutable;
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Page 1 is assumed to be the default and the page number parameter will be excluded
|
/// Page 1 is assumed to be the default and the page number parameter will be excluded
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class PagerTagHelper : TagHelper
|
public class PaginationTagHelper : TagHelper
|
||||||
{
|
{
|
||||||
|
public required IPaginatedResults Results { get; set; }
|
||||||
|
|
||||||
/// <summary>The name of the page number parameter in the query string</summary>
|
/// <summary>The name of the page number parameter in the query string</summary>
|
||||||
public string PageNumberKey { get; set; }
|
public required string PageNumberKey { get; set; }
|
||||||
public PaginatedResults<object> Results { get; set; }
|
|
||||||
|
|
||||||
[ViewContext, HtmlAttributeNotBound]
|
[ViewContext, HtmlAttributeNotBound]
|
||||||
public ViewContext ViewContext { get; set; }
|
public required ViewContext ViewContext { get; set; }
|
||||||
|
|
||||||
private string CurrentPagePath { get => this.ViewContext.HttpContext.Request.Path; }
|
private string CurrentPagePath
|
||||||
|
{
|
||||||
|
get => this.ViewContext.HttpContext.Request.PathBase
|
||||||
|
+ this.ViewContext.HttpContext.Request.Path;
|
||||||
|
}
|
||||||
private ImmutableDictionary<string, StringValues> QueryStringValues { get; set; }
|
private ImmutableDictionary<string, StringValues> QueryStringValues { get; set; }
|
||||||
|
= ImmutableDictionary.Create<string, StringValues>();
|
||||||
|
|
||||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||||
{
|
{
|
||||||
output.TagName = "nav";
|
output.TagName = "nav";
|
||||||
output.TagMode = TagMode.StartTagAndEndTag;
|
output.TagMode = TagMode.StartTagAndEndTag;
|
||||||
|
output.AddClass("wsu-pagination", HtmlEncoder.Default);
|
||||||
output.Attributes.Add("aria-label", "pagination");
|
output.Attributes.Add("aria-label", "pagination");
|
||||||
|
|
||||||
var paginationList = new TagBuilder("ul");
|
TagBuilder paginationList = new("ul");
|
||||||
paginationList.AddCssClass("pagination");
|
paginationList.AddCssClass("wsu-pagination__menu");
|
||||||
|
|
||||||
this.QueryStringValues = QueryHelpers
|
this.QueryStringValues = QueryHelpers
|
||||||
.ParseQuery(this.ViewContext.HttpContext.Request.QueryString.Value)
|
.ParseQuery(this.ViewContext.HttpContext.Request.QueryString.Value)
|
||||||
.ToImmutableDictionary()
|
.ToImmutableDictionary()
|
||||||
.Remove(this.PageNumberKey);
|
.Remove(this.PageNumberKey);
|
||||||
|
|
||||||
paginationList.InnerHtml.AppendHtml(this.CreatePreviousPageItem());
|
|
||||||
|
|
||||||
foreach (int i in Enumerable.Range(1, this.Results.LastPageNumber))
|
foreach (int i in Enumerable.Range(1, this.Results.LastPageNumber))
|
||||||
{
|
{
|
||||||
paginationList.InnerHtml.AppendHtml(this.CreatePageItem(i, i.ToString()));
|
paginationList.InnerHtml.AppendHtml(this.CreatePageItem(i, i.ToString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
paginationList.InnerHtml.AppendHtml(this.CreateNextPageItem());
|
output.Content.AppendHtml(this.CreatePreviousPageItem());
|
||||||
|
|
||||||
output.Content.AppendHtml(paginationList);
|
output.Content.AppendHtml(paginationList);
|
||||||
|
output.Content.AppendHtml(this.CreateNextPageItem());
|
||||||
}
|
}
|
||||||
|
|
||||||
private TagBuilder CreatePageItem(int pageNumber, string linkText)
|
private TagBuilder CreatePageItem(int pageNumber, string linkText)
|
||||||
{
|
{
|
||||||
var listItem = new TagBuilder("li");
|
TagBuilder listItem = new("li");
|
||||||
var link = new TagBuilder("a");
|
listItem.AddCssClass("wsu-pagination__menu-page");
|
||||||
|
|
||||||
|
TagBuilder link = new("a");
|
||||||
|
link.Attributes.Add("aria-label", $"Goto Page {pageNumber}");
|
||||||
|
|
||||||
if (pageNumber == this.Results.PageNumber)
|
if (pageNumber == this.Results.PageNumber)
|
||||||
link.AddCssClass("currentPage");
|
link.Attributes.Add("aria-current", "true");
|
||||||
|
|
||||||
link.InnerHtml.AppendHtml(linkText);
|
link.InnerHtml.AppendHtml(linkText);
|
||||||
|
|
||||||
// Leave off the number for page 1
|
// Leave off the number for page 1
|
||||||
var queryStringWithPageNumber = pageNumber == 1
|
ImmutableDictionary<string, StringValues> queryStringWithPageNumber = pageNumber == 1
|
||||||
? this.QueryStringValues
|
? this.QueryStringValues
|
||||||
: this.QueryStringValues.Add(this.PageNumberKey, pageNumber.ToString());
|
: this.QueryStringValues.Add(this.PageNumberKey, pageNumber.ToString());
|
||||||
|
|
||||||
|
@ -141,14 +158,18 @@ public class PagerTagHelper : TagHelper
|
||||||
|
|
||||||
private TagBuilder CreatePreviousPageItem()
|
private TagBuilder CreatePreviousPageItem()
|
||||||
{
|
{
|
||||||
var listItem = new TagBuilder("li");
|
TagBuilder link = new("a");
|
||||||
var link = new TagBuilder("a");
|
link.AddCssClass("wsu-pagination__previous");
|
||||||
|
link.AddCssClass("wsu-button");
|
||||||
|
link.AddCssClass("wsu-button--style-outline");
|
||||||
|
link.Attributes.Add("aria-label", "Goto Previous Page");
|
||||||
|
|
||||||
if (this.Results.IsFirstPage)
|
if (this.Results.IsFirstPage)
|
||||||
link.AddCssClass("disabled");
|
link.Attributes.Add("disabled", "disabled");
|
||||||
|
|
||||||
// Leave off the number for page 1
|
// Leave off the number for page 1
|
||||||
var queryStringWithPageNumber = this.Results.PreviousPageNumber == 1
|
ImmutableDictionary<string, StringValues> queryStringWithPageNumber =
|
||||||
|
this.Results.PreviousPageNumber == 1
|
||||||
? this.QueryStringValues
|
? this.QueryStringValues
|
||||||
: this.QueryStringValues
|
: this.QueryStringValues
|
||||||
.Add(this.PageNumberKey, this.Results.PreviousPageNumber.ToString());
|
.Add(this.PageNumberKey, this.Results.PreviousPageNumber.ToString());
|
||||||
|
@ -156,44 +177,35 @@ public class PagerTagHelper : TagHelper
|
||||||
link.Attributes.Add("href", QueryHelpers.AddQueryString(
|
link.Attributes.Add("href", QueryHelpers.AddQueryString(
|
||||||
this.CurrentPagePath, queryStringWithPageNumber));
|
this.CurrentPagePath, queryStringWithPageNumber));
|
||||||
|
|
||||||
link.InnerHtml.AppendHtml("«");
|
link.InnerHtml.AppendHtml("Previous");
|
||||||
|
|
||||||
listItem.InnerHtml.AppendHtml(link);
|
return link;
|
||||||
return listItem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TagBuilder CreateNextPageItem()
|
private TagBuilder CreateNextPageItem()
|
||||||
{
|
{
|
||||||
var listItem = new TagBuilder("li");
|
TagBuilder link = new("a");
|
||||||
var link = new TagBuilder("a");
|
link.AddCssClass("wsu-pagination__next");
|
||||||
|
link.AddCssClass("wsu-button");
|
||||||
|
link.AddCssClass("wsu-button--style-outline");
|
||||||
|
link.Attributes.Add("aria-label", "Goto Next Page");
|
||||||
|
|
||||||
if (this.Results.IsLastPage)
|
if (this.Results.IsLastPage)
|
||||||
link.AddCssClass("disabled");
|
link.Attributes.Add("disabled", "disabled");
|
||||||
|
|
||||||
// Leave off the number for page 1
|
// Leave off the number for page 1
|
||||||
var queryStringWithPageNumber = this.Results.NextPageNumber == 1
|
ImmutableDictionary<string, StringValues> queryStringWithPageNumber =
|
||||||
|
this.Results.NextPageNumber == 1
|
||||||
? this.QueryStringValues
|
? this.QueryStringValues
|
||||||
: this.QueryStringValues.Add(this.PageNumberKey, this.Results.NextPageNumber.ToString());
|
: this.QueryStringValues
|
||||||
|
.Add(this.PageNumberKey, this.Results.NextPageNumber.ToString());
|
||||||
|
|
||||||
link.Attributes.Add("href", QueryHelpers.AddQueryString(
|
link.Attributes.Add("href", QueryHelpers
|
||||||
this.CurrentPagePath, queryStringWithPageNumber));
|
.AddQueryString(this.CurrentPagePath, queryStringWithPageNumber));
|
||||||
|
|
||||||
link.InnerHtml.AppendHtml("»");
|
link.InnerHtml.AppendHtml("Next");
|
||||||
|
|
||||||
listItem.InnerHtml.AppendHtml(link);
|
return link;
|
||||||
return listItem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class PaginatedResultsExtensions
|
|
||||||
{
|
|
||||||
/// <summary>Razor doesn't like casting, so use this instead</summary>
|
|
||||||
public static PaginatedResults<object> ToObjectPaginatedResults<T>(
|
|
||||||
this PaginatedResults<T> paginatedResults) where T : class
|
|
||||||
{
|
|
||||||
return new PaginatedResults<object>(paginatedResults.PageNumber,
|
|
||||||
paginatedResults.ResultsPerPage, paginatedResults.Results,
|
|
||||||
paginatedResults.TotalResultsCount);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
Loading…
Reference in a new issue