Een Alpine.js en HTMX ingeschakelde tag helper voor ASP.NET Core (Nederlands (Dutch))

Een Alpine.js en HTMX ingeschakelde tag helper voor ASP.NET Core

Comments

NOTE: Apart from English (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.

Wednesday, 23 April 2025

//

Less than a minute

Inleiding

Even snel, ik had in een werkproject behoefte aan de mogelijkheid om URL-parameters van een URL te 'clearen'. Dit is handig als je een URL hebt met meerdere parameters, en je wilt er één of meerdere verwijderen (bijvoorbeeld voor een zoekfilter).

Het probleem

Mijn huidige project maakt gebruik van old-school query strings (het is een admin site dus heeft niet de fantasie van 'aardige' urls nodig). Dus ik eindig met een URL als deze:

/products?category=electronics&search=wireless+headphones&sort=price_desc&inStock=true&page=3

Nu kunnen deze variëren met elke pagina, dus ik kan eindigen met een BUNCH in de pagina URL en ik moet in staat zijn om ze uit te wissen zonder het schrijven van een heleboel boilerplate om het te doen.

Je kunt dit doen als onderdeel van welke input control je ook gebruikt, dus bijvoorbeeld naast elke checkbox (of een chique plaatshouder stijl helder pictogram) maar je kunt deze techniek ook gebruiken voor die. In dit geval wilde ik echter twee belangrijke dingen doen:

  1. In staat zijn om een benoemde parameter te wissen
  2. In staat zijn om een lijst van parameters te wissen.
  3. In staat zijn om alle parameters te wissen
  4. Laat het terug posten met HTMX
  5. Laat het mijn laadindicator gebruiken. mijn laadindicator.

De oplossing

In mijn project gebruik ik al

  • HTMX
  • Alpine.jsunit synonyms for matching user input
  • ASP.NET-kern
  • TailwindCSS
  • DaisyUI

Dus mijn oplossing was gericht op het gebruik van deze om een mooi uitziende, functionele oplossing met minimale code te krijgen.

De Tag Helper

Mijn TagHelper is vrij eenvoudig, alles wat ik doe is het creëren van een <a> tag met een paar eigenschappen Ik pas later in de Alpine Module en we zijn klaar.

[HtmlTargetElement("clear-param")]
public class ClearParamTagHelper : TagHelper
{
    [HtmlAttributeName("name")]
    public string Name { get; set; }
    
    [HtmlAttributeName("all")]
    public bool All { get; set; }= false;
    
    [HtmlAttributeName("target")]
    public string Target { get; set; } = "#page-content";

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "a";
        output.Attributes.SetAttribute("x-data", "window.queryParamClearer({})");

        if (All)
        {
        output.Attributes.SetAttribute("x-all", All);
        }
        else
        {
            output.Attributes.SetAttribute("x-param", Name);
        }
        output.Attributes.SetAttribute("data-target", Target);
        output.Attributes.SetAttribute("x-on:click.prevent", "clearParam($event)");
        output.Content.SetHtmlContent(@"
            <div class='w-6 h-6 flex items-center justify-center bg-red-600 text-white rounded-full'>
                <i class='bx bx-x text-lg'></i>
            </div>");
    }
}

Parameters

In gebruik ziet dit er als volgt uit, eerst voor 'alle parameters wissen'. Dus ik kijk gewoon naar de Context.Request.Query Als er parameters zijn, maak ik het kleintje. x pictogram om de gebruiker alle parameters te laten wissen.


@if(Context.Request.Query.Any())
{
<label class="param-label">
    <clear-param all="true"></clear-param>
    clear all
</label>
}
</div>

Als alternatief voor benoemde parameters kan ik dit doen


<div class="param-label">
    <clear-param name="myParam"></clear-param>
    <p>My Param: @Model.MyParam</p>
</div>

Dat zou natuurlijk die ene parameter duidelijk maken.

Of zelfs...


<div class="param-label">
    <clear-param name="myParam1,myParam2,myParam3"></clear-param>
    <p>My Param: @Model.MyParam1</p>
    <p>My Param: @Model.MyParam2</p>
    <p>My Param: @Model.MyParam3</p>
</div>

Dit verwijdert dan alle genoemde parameters van de string.

De target attribuut

YOu hebt ook de mogelijkheid om door te geven in een target attribuut dat zal worden gebruikt als de hx-target attribuut. Dit is handig als u een specifiek deel van de pagina wilt bijwerken met de nieuwe inhoud.


<div class="param-label">
    <clear-param name="myParam" target="#my-thing"></clear-param>
    <p>My Param: @Model.MyParam</p>
</div>

In mijn geval (omdat ik het schreef) heb ik het doel in gebreke gelaten aan mijn #page-content Div.

    [HtmlAttributeName("target")]
    public string Target { get; set; } = "#page-content";

Het resultaat

Dit resulteert in de weergave van de volgende HTML:

  • Allemaal: Dus we krijgen HTML met de x-all attribuut en geen x-param attribuut.
<a x-data="window.queryParamClearer({})" x-all="True" data-target="#page-content" x-on:click.prevent="clearParam($event)">
    <div class="w-6 h-6 flex items-center justify-center bg-red-600 text-white rounded-full">
        <i class="bx bx-x text-lg"></i>
    </div>
</a>

  • Single We krijgen HTML met de x-param attribuut en geen x-all attribuut.
<a x-data="window.queryParamClearer({})" x-param="myParam" data-target="#page-content" x-on:click.prevent="clearParam($event)">
    <div class="w-6 h-6 flex items-center justify-center bg-red-600 text-white rounded-full">
        <i class="bx bx-x text-lg"></i>
    </div>
</a>
  • Meerdere We krijgen HTML met de x-param attribuut met een komma gescheiden tekenreeks en geen x-all attribuut.
<a x-data="window.queryParamClearer({})" x-param="myParam1,myParam2,myParam3" data-target="#page-content" x-on:click.prevent="clearParam($event)">
    <div class="w-6 h-6 flex items-center justify-center bg-red-600 text-white rounded-full">
        <i class="bx bx-x text-lg"></i>
    </div>
</a>

Elk van hen heeft ook de twee Alpine attributen x-data en x-on:click.prevent die worden gebruikt om de Alpine module op te zetten en de functie aan te roepen om de parameters te ontruimen.

We zullen zien hoe dat nu werkt...

De Alpine Module

Dit wordt natuurlijk mogelijk gemaakt door het gebruik van Alpine.js om ons verzoek te configureren en HTMX om het uit te voeren.

Zoals je kunt zien in de code hieronder, Ik heb een eenvoudige module die de path van de huidige pagina en vervolgens gebruik maakt van de URL API om de query string te ontleden (je zou ook in een andere om welke reden dan ook kunnen doorgeven):).

We krijgen dan het element dat werd geklikt en controleren of het de x-all attribuut; als het doet we verwijderen alle parameters van de URL, anders splitsen we de x-param attribuut door komma's en verwijder elk van deze parameters.

Vervolgens maken we een nieuwe URL met de bijgewerkte query string en gebruiken we HTMX om een verzoek aan die URL te doen.

export function queryParamClearer({ path = window.location.pathname }) {
    return {
        clearParam(e) {
            const el = e.target.closest('[x-param],[x-all]');
            if (!el) return;

            const url = new URL(window.location.href);

            if (el.hasAttribute('x-all')) {
                // → delete every single param
                // we copy the keys first because deleting while iterating modifies the collection
                Array.from(url.searchParams.keys())
                    .forEach(key => url.searchParams.delete(key));
            } else {
                // → delete only the named params
                (el.getAttribute('x-param') || '')
                    .split(',')
                    .map(p => p.trim())
                    .filter(Boolean)
                    .forEach(key => url.searchParams.delete(key));
            }

            const qs = url.searchParams.toString();
            const newUrl = path + (qs ? `?${qs}` : '');

            showAlert(newUrl);
            htmx.ajax('GET', newUrl, {
                target: el.dataset.target || el.getAttribute('hx-target') || 'body',
                swap: 'innerHTML',
                pushUrl: true
            });
        }
    };
}

//In your entry point / anywhere you want to register the module
import { queryParamClearer } from './param-clearer.js'; // webpackInclude: true

window.queryParamClearer = queryParamClearer;

De showAlert functie met behulp van SweetAlert2

Je zult ook merken dat ik een showAlert functie. Dit is gewoon een eenvoudige wikkel rond de SweetAlert2 laadindicator die ik gebruik in mijn project. U kunt dit natuurlijk vervangen door alles wat u wilt doen.'

Dit is enigszins getweaked van de De laatste keer dat we het zagen.. Zodat ik de showAlert functioneren en ter beschikking stellen van externe modules. Wat me in staat stelt om het te gebruiken in zowel de param-clearer module en de hx-indicator module.

export function registerSweetAlertHxIndicator() {
    document.body.addEventListener('htmx:configRequest', function (evt) {
        const trigger = evt.detail.elt;

        const indicatorAttrSource = getIndicatorSource(trigger);
        if (!indicatorAttrSource) return;

        // ✅ If this is a pageSize-triggered request, use our custom path
        let path;
        if (evt.detail.headers?.['HX-Trigger-Name'] === 'pageSize') {
            path = getPathWithPageSize(evt.detail);
            console.debug('[SweetAlert] Using custom path with updated pageSize:', path);
        } else {
            path = getRequestPath(evt.detail);
        }

        if (!path) return;
        evt.detail.indicator = null;
        showAlert(path);
    });
}

export function showAlert(path)
{
    const currentPath = sessionStorage.getItem(SWEETALERT_PATH_KEY);

    // Show SweetAlert only if the current request path differs from the previous one
    if (currentPath !== path) {
        closeSweetAlertLoader();
        sessionStorage.setItem(SWEETALERT_PATH_KEY, path);


        Swal.fire({
            title: 'Loading...',
            allowOutsideClick: false,
            allowEscapeKey: false,
            showConfirmButton: false,
            theme: 'dark',
            didOpen: () => {
                // Cancel immediately if restored from browser history
                if (sessionStorage.getItem(SWEETALERT_HISTORY_RESTORED_KEY) === 'true') {
                    sessionStorage.removeItem(SWEETALERT_HISTORY_RESTORED_KEY);
                    Swal.close();
                    return;
                }

                Swal.showLoading();
                document.dispatchEvent(new CustomEvent('sweetalert:opened'));

                // Set timeout to auto-close if something hangs
                clearTimeout(swalTimeoutHandle);
                swalTimeoutHandle = setTimeout(() => {
                    if (Swal.isVisible()) {
                        console.warn('SweetAlert loading modal closed after timeout.');
                        closeSweetAlertLoader();
                    }
                }, SWEETALERT_TIMEOUT_MS);
            },
            didClose: () => {
                document.dispatchEvent(new CustomEvent('sweetalert:closed'));
                sessionStorage.removeItem(SWEETALERT_PATH_KEY);
                clearTimeout(swalTimeoutHandle);
                swalTimeoutHandle = null;
            }
        });
    }
}

//Register it
import { registerSweetAlertHxIndicator, showAlert } from './hx-sweetalert-indicator.js';
registerSweetAlertHxIndicator();
window.showAlert = showAlert;

Als herinnering gebruikt dit de path als de sleutel om te weten wanneer het alarm te verbergen.

HTMX

Eindelijk gebruiken we... htmx.ajax om het verzoek in te dienen. Dit is een eenvoudig GET-verzoek naar de nieuwe URL die we gemaakt hebben met de bijgewerkte query string.

   htmx.ajax('GET', newUrl, {
                target: el.dataset.target || el.getAttribute('hx-target') || 'body',
                swap: 'innerHTML',
                pushUrl: true
            });

Conclusie

Dit is een eenvoudige manier om URL parameters te wissen met behulp van een tag helper en Alpine.js. Het stelt u in staat om alle parameters, of alleen specifieke, met minimale code te wissen.

logo

©2024 Scott Galloway