This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
На днях проект мав впроваджено впроваджену форму результатів. Допоміжний помічник отримання міток завжди був Pagination Tag Помічник Darrel O' Neil як я писав про тут Але з будь-якої причини це так. зупинено роботу для мене. Тож, замість того, щоб намагатися замислюватися над тим, що виглядає як покинуте проект, я вирішив сам побудувати його.
Як завжди, ви можете отримати джерело для цього на GitHub
Я маю зразок для цього проекту введений тут
Тут є зразки вихідних даних на зразок цього:
Для цього помічника міток у мене було декілька вимог:
Dafault.cshtml
вигляд.У майбутньому я додам можливості до:
Тепер інструмент довідки tags є чудовим новим пакунком Nuget, отже ви можете встановити його за допомогою такої команди:
dotnet add package mostlylucid.pagingtaghelper
Потім ти додаєш помічника до свого. _ViewImports.cshtml
файл, схожий на такий:
@addTagHelper *, mostlylucid.pagingtaghelper
Після цього ви можете просто почати користуватися ним; я надаю вам допоміжні класи, які ви можете використовувати для налаштування, зокрема
IPagingModel
Це "шахрайські речі" вам потрібно почати. Це простий інтерфейс, який ви можете реалізувати на вашій моделі, щоб зробити розбій. Зауважте, що ViewType
є необов' язковим тут, це типове значення TailwindANdDaisy
але ви можете встановити його Custom
, Plain
або Bootstrap
якщо ви хочете використовувати інший вид.
public enum ViewType
{
TailwindANdDaisy,
Custom,
Plain,
Bootstrap
}
АБО ви навіть можете вказати власний перегляд за допомогою пункту меню МіткаHelper use-local-view
власність.
namespace mostlylucid.pagingtaghelper.Models;
public interface IPagingModel
{
public int Page { get; set; }
public int TotalItems { get; set; }
public int PageSize { get; set; }
public ViewType ViewType { get; set; }
public string LinkUrl { get; set; }
}
namespace mostlylucid.pagingtaghelper.Models;
public interface IPagingSearchModel : IPagingModel
{
public string? SearchTerm { get; set; }
}
Я також реалізував це в проекті, щоб забезпечити базову лінію:
public abstract class BasePagerModel : IPagingModel
{
public int Page { get; set; } = 1;
public int TotalItems { get; set; } = 0;
public int PageSize { get; set; } = 10;
public ViewType ViewType { get; set; } = ViewType.TailwindANdDaisy;
public string LinkUrl { get; set; } = "";
}
public abstract class BasePagerSearchMdodel : BasePagerModel, IPagingSearchModel
{
public string? SearchTerm { get; set; }
}
Я огляну функціональність пошуку в наступній статті.
Після цього я вирахував, як би хотів, щоб програма TagHelper виглядала у використанні:
<paging
x-ref="pager"
hx-boost="true"
hx-indicator="#loading-modal"
hx-target="#user-list "
hx-swap="show:none"
model="Model"
pages-to-display="10"
hx-headers='{"pagerequest": "true"}'>
</paging>
Тут ви бачите, що я встановила декілька параметрів HTMX і модель, яку слід використовувати для обробки. Крім того, я встановила кількість сторінок, які слід показувати, і заголовки, які слід надіслати з запитом (це дозволяє мені використовувати HTMX для заповнення сторінки).
У компоненті також міститься BUNCH інших елементів налаштування, які я працюватиму у наступних статтях. Як ви можете бачити, це LOT можливого налаштування.
<paging css-class=""
first-last-navigation=""
first-page-text=""
next-page-aria-label=""
next-page-text=""
page=""
pages-to-display=""
page-size=""
previous-page-text=""
search-term=""
skip-forward-back-navigation=""
skip-back-text=""
skip-forward-text="true"
total-items=""
link-url=""
last-page-text=""
show-pagesize=""
use-htmx=""
use-local-view=""
view-type="Bootstrap"
htmx-target=""
id=""
></paging>
Інструмент довідки TagHelper досить простий, але має декілька властивостей, які надають користувачеві змогу налаштувати поведінку програми (ви можете побачити це нижче у розділі) перегляд ) окрім властивостей (які я не вставлятиму тут для короткочасності) код досить просто:
/// <summary>
/// Processes the tag helper to generate the pagination component.
/// </summary>
/// <param name="context">The tag helper context.</param>
/// <param name="output">The tag helper output.</param>
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "div";
//Remove all the properties that are not needed for the rendered content.
output.Attributes.RemoveAll("page");
output.Attributes.RemoveAll("link-url");
output.Attributes.RemoveAll("page-size");
output.Attributes.RemoveAll("total-items");
output.Attributes.RemoveAll("pages-to-display");
output.Attributes.RemoveAll("css-class");
output.Attributes.RemoveAll("first-page-text");
output.Attributes.RemoveAll("previous-page-text");
output.Attributes.RemoveAll("skip-back-text");
output.Attributes.RemoveAll("skip-forward-text");
output.Attributes.RemoveAll("next-page-text");
output.Attributes.RemoveAll("next-page-aria-label");
output.Attributes.RemoveAll("last-page-text");
output.Attributes.RemoveAll("first-last-navigation");
output.Attributes.RemoveAll("skip-forward-back-navigation");
output.Attributes.RemoveAll("model");
output.Attributes.RemoveAll("show-pagesize");
output.Attributes.RemoveAll("pagingmodel");
output.Attributes.RemoveAll("use-local-view");
var pagerId = PagerId ?? $"pager-{Guid.NewGuid():N}";
var linkUrl = LinkUrl ?? ViewContext.HttpContext.Request.Path;
PageSize = Model?.PageSize ?? PageSize ?? 10;
Page = Model?.Page ?? Page ?? 1;
ViewType = Model?.ViewType ?? ViewType;
TotalItems = Model?.TotalItems ?? TotalItems ?? 0;
if(Model is IPagingSearchModel searchModel)
SearchTerm = searchModel?.SearchTerm ?? SearchTerm ?? "";
output.Attributes.SetAttribute("id", pagerId);
var viewComponentHelper = (IViewComponentHelper)ViewContext.HttpContext.RequestServices.GetService(typeof(IViewComponentHelper))!;
((IViewContextAware)viewComponentHelper).Contextualize(ViewContext);
var pagerModel = PagerModel ?? new PagerViewModel()
{
ViewType = ViewType,
UseLocalView = UseLocalView,
UseHtmx = UseHtmx,
PagerId = pagerId,
SearchTerm = SearchTerm,
ShowPageSize = ShowPageSize,
Model = Model,
LinkUrl = linkUrl,
Page = Page,
PageSize = PageSize,
TotalItems = TotalItems,
PagesToDisplay = PagesToDisplay,
CssClass = CssClass,
FirstPageText = FirstPageText,
PreviousPageText = PreviousPageText,
SkipBackText = SkipBackText,
SkipForwardText = SkipForwardText,
NextPageText = NextPageText,
NextPageAriaLabel = NextPageAriaLabel,
LastPageText = LastPageText,
FirstLastNavigation = FirstLastNavigation,
SkipForwardBackNavigation = SkipForwardBackNavigation,
HtmxTarget = HtmxTarget,
};
var result = await viewComponentHelper.InvokeAsync("Pager", pagerModel);
output.Content.SetHtmlContent(result);
}
Вона складається з таких кроків:
div
Це контейнер пейджера.IPagingModel
і змусити пейджер працювати без додаткових налаштувань.PagerViewModel
з властивостями, встановленими у значення, які ми маємо, або типовими, якщо їх не вказано.Pager
Перегляд Компонувати з PagerViewModel
і встановити вміст виводу у результат.Знову все досить просто.
Перегляд вікна ViewComponent досить простий. Це просто цикл сторінок і декілька посилань на перші, останні, наступні і попередні сторінки.
@model mostlylucid.pagingtaghelper.Components.PagerViewModel
@{
var totalPages = (int)Math.Ceiling((double)Model.TotalItems! / (double)Model.PageSize!);
var pageSizes = new List<int>();
if (Model.ShowPageSize)
{
// Build a dynamic list of page sizes.
// Fixed steps as a starting point.
int[] fixedSteps = { 10, 25, 50, 75, 100, 125, 150, 200, 250, 500, 1000 };
// Add only those fixed steps that are less than or equal to TotalItems.
foreach (var step in fixedSteps)
{
if (step <= Model.TotalItems)
{
pageSizes.Add(step);
}
}
// If TotalItems is greater than the largest fixed step,
// add additional steps by doubling until reaching TotalItems.
if (Model.TotalItems > fixedSteps.Last())
{
int next = fixedSteps.Last();
while (next < Model.TotalItems)
{
next *= 2;
// Only add if it doesn't exceed TotalItems.
if (next < Model.TotalItems)
{
pageSizes.Add(next);
}
}
// Always include the actual TotalItems as the maximum option.
if (!pageSizes.Contains(Model.TotalItems.Value))
{
pageSizes.Add(Model.TotalItems.Value);
}
}
}
}
@if (totalPages > 1)
{
<div class="@Model.CssClass flex items-center justify-center" id="pager-container">
@if (Model.ShowPageSize)
{
var pagerId = Model.PagerId;
var htmxAttributes = Model.UseHtmx
? $"hx-get=\"{Model.LinkUrl}\" hx-trigger=\"change\" hx-include=\"#{pagerId} [name='page'], #{pagerId} [name='search']\" hx-push-url=\"true\""
: "";
<!-- Preserve current page -->
<input type="hidden" name="page" value="@Model.Page"/>
<input type="hidden" name="search" value="@Model.SearchTerm"/>
<input type="hidden" class="useHtmx" value="@Model.UseHtmx.ToString().ToLowerInvariant()"/>
if (!Model.UseHtmx)
{
<input type="hidden" class="linkUrl" value="@Model.LinkUrl"/>
}
<!-- Page size select with label -->
<div class="flex items-center mr-8">
<label for="pageSize-@pagerId" class="text-sm text-gray-600 mr-2">Page size:</label>
<select id="pageSize-@pagerId"
name="pageSize"
class="border rounded select select-primary select-sm pt-0 mt-0 min-w-[80px] pr-4"
@Html.Raw(htmxAttributes)>
@foreach (var option in pageSizes.ToList())
{
var optionString = option.ToString();
if (option == Model.PageSize)
{
<option value="@optionString" selected="selected">@optionString</option>
}
else
{
<option value="@optionString">@optionString</option>
}
}
</select>
</div>
}
@* "First" page link *@
@if (Model.FirstLastNavigation && Model.Page > 1)
{
var href = $"{Model.LinkUrl}?page=1&pageSize={Model.PageSize}";
if (!string.IsNullOrEmpty(Model.SearchTerm))
{
href += $"&search={Model.SearchTerm}";
}
<a class="btn btn-sm"
href="@href">
@Model.FirstPageText
</a>
}
@* "Previous" page link *@
@if (Model.Page > 1)
{
var href = $"{Model.LinkUrl}?page={Model.Page - 1}&pageSize={Model.PageSize}";
if (!string.IsNullOrEmpty(Model.SearchTerm))
{
href += $"&search={Model.SearchTerm}";
}
<a class="btn btn-sm"
href="@href">
@Model.PreviousPageText
</a>
}
@* Optional skip back indicator *@
@if (Model.SkipForwardBackNavigation && Model.Page > Model.PagesToDisplay)
{
<a class="btn btn-sm btn-disabled">
@Model.SkipBackText
</a>
}
@* Determine visible page range *@
@{
int halfDisplay = Model.PagesToDisplay / 2;
int startPage = Math.Max(1, Model.Page.Value - halfDisplay);
int endPage = Math.Min(totalPages, startPage + Model.PagesToDisplay - 1);
startPage = Math.Max(1, endPage - Model.PagesToDisplay + 1);
}
@for (int i = startPage; i <= endPage; i++)
{
var href = $"{Model.LinkUrl}?page={i}&pageSize={Model.PageSize}";
if (!string.IsNullOrEmpty(Model.SearchTerm))
{
href += $"&search={Model.SearchTerm}";
}
<a data-page="@i" class="btn btn-sm mr-2 @(i == Model.Page ? "btn-active" : "")"
href="@href">
@i
</a>
}
@* Optional skip forward indicator *@
@if (Model.SkipForwardBackNavigation && Model.Page < totalPages - Model.PagesToDisplay + 1)
{
<a class="btn btn-sm btn-disabled mr-2">
@Model.SkipForwardText
</a>
}
@* "Next" page link *@
@if (Model.Page < totalPages)
{
var href = $"{Model.LinkUrl}?page={Model.Page + 1}&pageSize={Model.PageSize}";
if (!string.IsNullOrEmpty(Model.SearchTerm))
{
href += $"&search={Model.SearchTerm}";
}
<a class="btn btn-sm mr-2"
href="@href"
aria-label="@Model.NextPageAriaLabel">
@Model.NextPageText
</a>
}
@* "Last" page link *@
@if (Model.FirstLastNavigation && Model.Page < totalPages)
{
var href = $"{Model.LinkUrl}?page={totalPages}&pageSize={Model.PageSize}";
if (!string.IsNullOrEmpty(Model.SearchTerm))
{
href += $"&search={Model.SearchTerm}";
}
<a class="btn btn-sm"
href="@href">
@Model.LastPageText
</a>
}
<!-- Page info text with left margin for separation -->
<div class="text-sm text-neutral-500 ml-8">
Page @Model.Page of @totalPages (Total items: @Model.TotalItems)
</div>
</div>
}
Мне не хватало от соответствующего составщика по размеру страницы.
Це простий список вибору, ви можете побачити, що спочатку я почав з визначення fixedSteps
які є лише кількома фіксованими кроками, які я хочу використати для падіння. Потім я прокручу їх і додаю до списку. У мене завжди є звичка - мати параметр "all," отже, я додаю елементи до списку, якщо їх там ще немає.
@{
var totalPages = (int)Math.Ceiling((double)Model.TotalItems! / (double)Model.PageSize!);
var pageSizes = new List<int>();
if (Model.ShowPageSize)
{
// Build a dynamic list of page sizes.
// Fixed steps as a starting point.
int[] fixedSteps = { 10, 25, 50, 75, 100, 125, 150, 200, 250, 500, 1000 };
// Add only those fixed steps that are less than or equal to TotalItems.
foreach (var step in fixedSteps)
{
if (step <= Model.TotalItems)
{
pageSizes.Add(step);
}
}
// If TotalItems is greater than the largest fixed step,
// add additional steps by doubling until reaching TotalItems.
if (Model.TotalItems > fixedSteps.Last())
{
int next = fixedSteps.Last();
while (next < Model.TotalItems)
{
next *= 2;
// Only add if it doesn't exceed TotalItems.
if (next < Model.TotalItems)
{
pageSizes.Add(next);
}
}
// Always include the actual TotalItems as the maximum option.
if (!pageSizes.Contains(Model.TotalItems.Value))
{
pageSizes.Add(Model.TotalItems.Value);
}
}
}
}
Потім я перенесу це на сторінку
@if (Model.ShowPageSize)
{
var pagerId = Model.PagerId;
var htmxAttributes = Model.UseHtmx
? $"hx-get=\"{Model.LinkUrl}\" hx-trigger=\"change\" hx-include=\"#{pagerId} [name='page'], #{pagerId} [name='search']\" hx-push-url=\"true\""
: "";
<!-- Preserve current page -->
<input type="hidden" name="page" value="@Model.Page"/>
<input type="hidden" name="search" value="@Model.SearchTerm"/>
<input type="hidden" class="useHtmx" value="@Model.UseHtmx.ToString().ToLowerInvariant()"/>
if (!Model.UseHtmx)
{
<input type="hidden" class="linkUrl" value="@Model.LinkUrl"/>
}
<!-- Page size select with label -->
<div class="flex items-center mr-8">
<label for="pageSize-@pagerId" class="text-sm text-gray-600 mr-2">Page size:</label>
<select id="pageSize-@pagerId"
name="pageSize"
class="border rounded select select-primary select-sm pt-0 mt-0 min-w-[80px] pr-4"
@Html.Raw(htmxAttributes)>
@foreach (var option in pageSizes.ToList())
{
var optionString = option.ToString();
if (option == Model.PageSize)
{
<option value="@optionString" selected="selected">@optionString</option>
}
else
{
<option value="@optionString">@optionString</option>
}
}
</select>
</div>
}
Ви можете бачити, що за бажання, використовувати деякі атрибути HTMX, щоб передати розмір сторінки серверу і оновити сторінку з збереженням поточного параметра і пошуку (якщо такі є).
Додатково, якщо ви визначаєте if use-htmx=false
як параметр помічника теґів, він не виведе ці дані, натомість надасть вам змогу використовувати декілька JS, які я надав як Помічник HTML для оновлення розмірів сторінки.
@Html.PageSizeOnchangeSnippet()
Це простий скрипт, який оновить розмір сторінки і перезавантажить її (зауважте, що це ще не працює для Простої CSS / Притопу, оскільки мені потрібно визначити назви властивостей тощо).
document.addEventListener("DOMContentLoaded", function () {
document.body.addEventListener("change", function (event) {
const selectElement = event.target.closest("#pager-container select[name='pageSize']");
if (!selectElement) return;
const pagerContainer = selectElement.closest("#pager-container");
const useHtmxInput = pagerContainer.querySelector("input.useHtmx");
const useHtmx = useHtmxInput ? useHtmxInput.value === "true" : true; // default to true
if (!useHtmx) {
const pageInput = pagerContainer.querySelector("[name='page']");
const searchInput = pagerContainer.querySelector("[name='search']");
const page = pageInput ? pageInput.value : "1";
const search = searchInput ? searchInput.value : "";
const pageSize = selectElement.value;
const linkUrl = pagerContainer.querySelector("input.linkUrl").value ?? "";
const url = new URL(linkUrl, window.location.origin);
url.searchParams.set("page", page);
url.searchParams.set("pageSize", pageSize);
if (search) {
url.searchParams.set("search", search);
}
window.location.href = url.toString();
}
});
});
Інтеграція HTMX досить проста, оскільки HTMX є каскадним до дочірніх елементів, ми можемо визначити параметри HTMX для батьківського елементу і вони будуть успадковані.
hx-boost="true"
- для цього використовується nifty можливість hx-boust щоб перехопити подію клацання і надіслати запит за допомогою HTMX.Це досить просто і використовує DaisUI, box icons і Tailwind, щоб створити простий завантажувальний модуль.
<div id="loading-modal" class="modal htmx-indicator">
<div class="modal-box flex flex-col items-center justify-center">
<h2 class="text-lg font-semibold">Loading...</h2>
<i class="bx bx-loader bx-spin text-3xl mt-2"></i>
</div>
</div>
hx- indicator="# завантаження- modal " потім вказує, що, якщо буде виконано запит на HTMX, буде показано swud, а потім сховано цей модуль.
Так що це частина 1, є очевидно, що LOT більше, щоб покрити, і я буду в наступних статтях; включаючи на зразок сайт, альтернативні погляди, функціональні можливості пошуку і нетиповий CSS.