NOTE: Apart from
(and even then it's questionable, I'm Scottish). These are machine translated in languages I don't read. If they're terrible please contact me.
You can see how this translation was done in this article.
Monday, 17 March 2025
//Less than a minute
Ως μέρος της συνεχιζόμενης σοφίας μου με paging tag helper έχω τώρα χωρίσει PageSize στο δικό του βοηθό ετικέτας. Αυτό είναι για να κάνει την ετικέτα βοηθός πιο ευέλικτη και να επιτρέψει για πιο περίπλοκα σενάρια τηλεειδοποίησης.
Και πάλι, όλα αυτά είναι σε εξέλιξη. Είμαι ήδη... έως 0,9.0 τη στιγμή της γραφής, αλλά να χρησιμοποιείτε με δικό σας κίνδυνο. Είναι δωρεάν αλλά ΔΕΝ YET της ποιότητας απελευθέρωσης IMHO (Δεν έχω καν πλήρη δείγματα Ακόμα!
Δείτε το κωδικός για τον βοηθό ετικετών εδώ.
Όπως και με άλλες taghelpers μου αυτό έχει σχεδιαστεί για μια περίπτωση χρήσης που συναντώ συχνά? πώς να αλλάξετε εύκολα το μέγεθος σελίδας σε λίστες αποτελεσμάτων. Με την πρώτη ματιά αυτό φαίνεται αρκετά απλό, αλλά μπορεί να γίνει TRICKY μόλις προσθέσετε HTMX στο μείγμα.
Έτσι, για τη χρήση μου, οι απαιτήσεις ήταν οι ακόλουθες:
Με αυτό κατά νου σχεδίασα το TagHelper να είναι ως εξής:
<page-size
hx-target="#list"
hx-swap="innerHTML"
model="Model">
</page-size>
<div id="list">
<partial name="_ResultsList" model="Model"/>
</div>
Έτσι, σε αυτή την περίπτωση και πάλι παίρνει πρότυπο μου paging μοντέλο:
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; }
}
(') Δελτίο ΕΚ 6-1982, σημείο 2.1.78. Page
, TotalItems
, PageSize
, ViewType
και LinkUrl
για το τηλεφώνημα.
Μπορείτε επίσης να τα προσδιορίσετε με τη μορφή χαρακτηριστικών του μοντέλου:
<page-size
hx-target="#list"
hx-swap="innerHTML"
total-items="100"
page-size="10"
view-type="DaisyAndTailwind">
</page-size>
Πού στο... hx-target
Εδώ είναι ο στόχος για το αίτημα HTMX και το hx-swap
είναι ο στόχος για την απάντηση.
Σε αυτή την περίπτωση θα χρησιμοποιήσει το καθορισμένο PageSize
και TotalItems
και ViewType
για να καταστήσει τον επιλογέα μεγέθους σελίδας.
Δυστυχώς αυτή τη φορά είχα μια απαίτηση που δεν μπορούσα να χειριστώ καθαρά την πλευρά του διακομιστή. Δηλαδή ήθελα να διατηρήσω τους παραματιστές εγχόρδων στις σελίδες. Για να επιτευχθεί αυτό, έχω δύο κομμάτια JS που θα φορτώσουν στον έλεγχο ανάλογα με το HTMX που χρησιμοποιείται oor δεν
@if (Model.UseHtmx)
{
@Html.HTMXPageSizeChange()
}
else
{
@Html.PageSizeOnchangeSnippet()
}
Εάν χρησιμοποιείται HTMX, το καθιστά αυτό στη σελίδα:
(() => {
if (window.__pageSizeListenerAdded) return;
document.addEventListener('htmx:configRequest', event => {
const { elt } = event.detail;
if (elt?.matches('[name="pageSize"]')) {
const params = new URLSearchParams(window.location.search);
params.set('pageSize', elt.value); // This will update or add the pageSize param
event.detail.parameters = Object.fromEntries(params.entries());
}
});
window.__pageSizeListenerAdded = true;
})();
Το οποίο είναι ένα απλό κομμάτι JavaScript το οποίο θα εισάγει τις τρέχουσες παραμέτρους ερωτηματογραφίας στο αίτημα.
Εναλλακτικά, εάν το HTMX ΔΕΝ είναι ενεργοποιημένο, αυτό θα το καταστήσει:
(function attachPageSizeListener() {
// Ensure we only attach once if this script is included multiple times
if (window.__pageSizeListenerAttached) return;
window.__pageSizeListenerAttached = true;
// Wait for the DOM to be fully loaded
document.addEventListener("DOMContentLoaded", () => {
document.body.addEventListener("change", handlePageSizeChange);
});
function handlePageSizeChange(event) {
// Check if the changed element is a page-size <select> inside .page-size-container
const select = event.target.closest(".page-size-container select[name='pageSize']");
if (!select) return;
const container = select.closest(".page-size-container");
if (!container) return;
// Default to "true" if there's no .useHtmx input
const useHtmx = container.querySelector("input.useHtmx")?.value === "true";
if (useHtmx) {
// If using HTMX, we do nothing—HTMX will handle the request
return;
}
// Either use a linkUrl from the container or the current page URL
const linkUrl = container.querySelector("input.linkUrl")?.value || window.location.href;
const url = new URL(linkUrl, window.location.origin);
// Copy existing query params from current location
const existingParams = new URLSearchParams(window.location.search);
for (const [key, value] of existingParams.entries()) {
url.searchParams.set(key, value);
}
// If user picked the same pageSize as what's already in the URL, do nothing
if (url.searchParams.get("pageSize") === select.value) {
return;
}
// Update the pageSize param
url.searchParams.set("pageSize", select.value);
// Redirect
window.location.href = url.toString();
}
})();
Μια αλλαγή που θα κάνω είναι να αλλάξω το όνομα της μεταβλητής "σημαία." __pageSizeListenerAttached
καθώς είναι λίγο πολύ γενικό και το χρησιμοποιώ τόσο για αιτήματα HTMX όσο και για μη HTMX.
Ο ίδιος ο βοηθός ετικετών είναι αρκετά απλός, ο κύριος κώδικας απλά κοσμεί μερικές ιδιότητες και στη συνέχεια καθιστά την προβολή Component.
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
// We want to render a <div> by default
output.TagName = "div";
// Remove any leftover attributes that we handle ourselves
RemoveUnwantedAttributes(output);
// Determine final PagerId
var pagerId = PagerId ?? PageSizeModel?.PagerId ?? $"pager-{Guid.NewGuid():N}";
// Assign ID to the outer div
output.Attributes.SetAttribute("id", pagerId);
// Build or fallback to a link URL
var finalLinkUrl = BuildLinkUrl();
if (string.IsNullOrEmpty(finalLinkUrl))
{
// If we can't build a URL, show a fallback message or short-circuit
output.Content.SetHtmlContent(
"<p style=\"color:red\">No valid link URL found for PageSize control.</p>");
return;
}
var finalPageSize = PageSize ?? PageSizeModel?.PageSize ?? 10;
// Clamp the page size to MaxPageSize
var finalTotalItems = TotalItems ?? Model?.TotalItems ?? PageSizeModel?.TotalItems ?? 0;
if (finalTotalItems == 0) throw new ArgumentNullException(nameof(finalTotalItems), "TotalItems is required");
var maxPageSize = Math.Min(finalTotalItems, MaxPageSize);
// Fallback to model's properties if not set
var finalViewType = PageSizeModel?.ViewType ?? Model?.ViewType ?? ViewType;
var pageSizeSteps = new int[] { 10, 25, 50, 75, 100, 125, 150, 200, 250, 500, 1000 };
if (!string.IsNullOrEmpty(PageSizeSteps))
pageSizeSteps = PageSizeSteps.Split(',').Select(s =>
{
if (int.TryParse(s, out var result))
return result;
else
{
throw new ArgumentException("Invalid page size step", nameof(PageSizeSteps));
}
}).ToArray();
var pageSizes = CalculatePageSizes(finalTotalItems, maxPageSize, pageSizeSteps);
var useHtmx = PageSizeModel?.UseHtmx ?? UseHtmx;
var useLocalView = PageSizeModel?.UseLocalView ?? UseLocalView;
// Acquire the IViewComponentHelper
var viewComponentHelper = ViewContext.HttpContext.RequestServices
.GetRequiredService<IViewComponentHelper>();
((IViewContextAware)viewComponentHelper).Contextualize(ViewContext);
// Construct the PageSizeModel (if not provided) with final settings
var pagerModel = new PageSizeModel
{
ViewType = finalViewType,
UseLocalView = useLocalView,
UseHtmx = useHtmx,
PageSizes = pageSizes,
PagerId = pagerId,
Model = Model,
LinkUrl = finalLinkUrl,
MaxPageSize = maxPageSize,
PageSize = finalPageSize,
TotalItems = finalTotalItems
};
// Safely invoke the "PageSize" view component
try
{
var result = await viewComponentHelper.InvokeAsync("PageSize", pagerModel);
output.Content.SetHtmlContent(result);
}
catch (Exception ex)
{
// Optional: Log or display an error
output.Content.SetHtmlContent(
$"<p class=\"text-red-500\">Failed to render PageSize component: {ex.Message}</p>"
);
}
}
Ένα δύσκολο κομμάτι χειρίζεται σωστά το PageSizeSteps
γνώριμη ιδιότητα. Αυτή είναι μια ξεχωριστή λίστα με τα μεγέθη σελίδων από τα οποία μπορεί να επιλέξει ο χρήστης. Ή μπορείς να περάσεις στα δικά σου βήματα. Μπορείτε επίσης να καθορίσετε το MaxPageSize που είναι το μέγιστο μέγεθος σελίδας που μπορεί να επιλεγεί.
private List<int> CalculatePageSizes(int totalItems, int maxxPageSize, int[] pageSizeSteps)
{
var pageSizes = new List<int>();
// 1. Include all fixed steps up to both TotalItems and MaxPageSize
foreach (var step in pageSizeSteps)
{
if (step <= totalItems && step <= maxxPageSize)
pageSizes.Add(step);
}
// 2. If TotalItems exceeds the largest fixed step, keep doubling
int lastFixedStep = pageSizeSteps.Last();
if (totalItems > lastFixedStep)
{
int next = lastFixedStep;
while (next < totalItems && next < maxxPageSize)
{
next *= 2; // double the step
if (next <= totalItems && next <= maxxPageSize)
{
pageSizes.Add(next);
}
}
}
// 3. Include TotalItems if it's not already in the list
if (totalItems <= maxxPageSize && !pageSizes.Contains(totalItems))
{
pageSizes.Add(totalItems);
}
pageSizes.Sort();
return pageSizes;
}
Όπως και με τον άλλο μου βοηθό ετικετών χρησιμοποιώ το TagHelper->ViewComponent μοτίβο για να κάνω το εξάρτημα προβολής. Αυτό μου επιτρέπει να κρατήσω τον βοηθό ετικετών απλό και το σύμπλεγμα συστατικών προβολής. Σας επιτρέπει επίσης να αλλάξετε απόψεις για διαφορετικές εμπειρίες; ή ακόμη και να ενέσετε τη δική σας άποψη και να χρησιμοποιήσετε ότι (χρησιμοποιώντας use-local-view
).
public class PageSizeViewComponent : ViewComponent
{
public IViewComponentResult Invoke(PageSizeModel model)
{
if(model.Model != null)
{
model.LinkUrl ??= model.Model.LinkUrl;
if(model.Model is IPagingSearchModel searchModel)
{
model.SearchTerm ??= searchModel.SearchTerm;
}
}
var viewName = "Components/Pager/Default";
var useLocalView = model.UseLocalView;
return (useLocalView, model.ViewType) switch
{
(true, ViewType.Custom) when ViewExists(viewName) => View(viewName, model),
(true, ViewType.Custom) when !ViewExists(viewName) => throw new ArgumentException("View not found: " + viewName),
(false, ViewType.Bootstrap) => View("/Areas/Components/Views/PageSize/BootstrapView.cshtml", model),
(false, ViewType.Plain) => View("/Areas/Components/Views/PageSize/PlainView.cshtml", model),
(false, ViewType.TailwindAndDaisy) => View("/Areas/Components/Views/PageSize/Default.cshtml", model),
_ => View("/Areas/Components/Views/PageSize/Default.cshtml", model)
};
// If the view exists in the app, use it; otherwise, use the fallback RCL view
}
/// <summary>
/// Checks if a view exists in the consuming application.
/// </summary>
private bool ViewExists(string viewName)
{
var result = ViewEngine.FindView(ViewContext, viewName, false);
return result.Success;
}
}
Λοιπόν, αυτό ήταν για τον βοηθό ετικέτας PageSize. Ακόμα δουλεύω πάνω στα έγγραφα και τα παραδείγματα αλλά ο κώδικας είναι καλύτερος καθημερινά.