Ein Alpine.js und HTMX aktivierter Tag-Helfer für ASP.NET Core (Deutsch (German))

Ein Alpine.js und HTMX aktivierter Tag-Helfer für 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

Einleitung

Nur eine schnelle, Ich hatte einen Bedarf in einem Arbeitsprojekt für die Fähigkeit, "leeren" URL-Parameter von einer URL. Dies ist nützlich, wenn Sie eine URL mit mehreren Parametern haben und eine oder mehrere davon entfernen möchten (z.B. für einen Suchfilter).

Das Problem

Mein aktuelles Projekt verwendet Old-School Query Strings (es ist eine Admin-Website, also braucht nicht die Fanciness von 'nice' URLs). Also ende ich mit einer URL wie dieser:

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

Nun können diese mit jeder Seite variieren, so dass ich am Ende mit einem BUNCH in der Seiten-URL und ich muss in der Lage sein, sie zu löschen, ohne eine Reihe von Boilerplatte, um es zu tun.

Sie können dies als Teil der Eingabesteuerung, die Sie verwenden, so zum Beispiel neben jedem Kontrollkästchen (oder einem ausgefallenen Platzhalter Stil klar Symbol) aber Sie können diese Technik auch für diese verwenden. In diesem Fall wollte ich jedoch zwei wesentliche Dinge tun:

  1. In der Lage sein, einen benannten Parameter zu löschen
  2. In der Lage sein, eine Liste von Parametern zu löschen.
  3. Alle Parameter löschen
  4. Lassen Sie es zurück mit HTMX
  5. Lassen Sie es meine Ladeanzeige verwenden meine Ladeanzeige.

Die Lösung

In meinem Projekt benutze ich bereits

  • HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX
  • Alpine.js
  • ASP.NET Kern
  • RückwindCSS
  • DaisyUI

Meine Lösung konzentrierte sich also darauf, diese zu verwenden, um eine schön aussehende, funktionale Lösung mit minimalem Code zu erhalten.

Der Tag-Helfer

Mein TagHelper ist ziemlich einfach, alles was ich tue, ist eine <a> Tag mit ein paar Attributen Ich werde später in das Alpine Modul übergeben und wir sind fertig.

[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>");
    }
}

Parameter

Im Gebrauch sieht das so aus, zuerst für 'alle Parameter löschen'. Also schaue ich mir nur die Context.Request.Query wenn es irgendwelche Parameter gibt, die ich das kleine rendern x Symbol, um den Benutzer alle Parameter löschen zu lassen.


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

Alternativ für benannte Parameter kann ich dies tun


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

Das würde natürlich diesen einzelnen Parameter klarstellen.

Oder sogar


<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>

Dies löscht dann alle benannten Parameter aus dem String.

Das target Attribut

YOu haben auch die Möglichkeit, in einem target Attribut, das als das hx-target Attribut. Dies ist nützlich, wenn Sie einen bestimmten Teil der Seite mit dem neuen Inhalt aktualisieren möchten.


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

In meinem Fall (weil ich es geschrieben habe) habe ich das Ziel an meine #page-content - div. - Nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein.

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

Das Ergebnis

Diese führen zur Rendering des folgenden HTML:

  • Alle: So bekommen wir HTML mit dem x-all Attribut und nein x-param Attribut.
<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>

  • Einzeln Wir bekommen HTML mit dem x-param Attribut und nein x-all Attribut.
<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>
  • Vielfach Wir bekommen HTML mit dem x-param Attribut mit Komma getrennter Zeichenfolge und nein x-all Attribut.
<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>

Jeder von ihnen hat auch die beiden alpinen Attribute x-data und x-on:click.prevent die verwendet werden, um das Alpine Modul einzurichten und die Funktion aufzurufen, um die Parameter zu löschen.

Wir werden sehen, wie das weitergeht...

Das Alpine Modul

Dies wird natürlich durch den Einsatz von Alpine.js ermöglicht, um unsere Anfrage und HTMX zu konfigurieren, um sie auszuführen.

Wie Sie im Code unten sehen können, habe ich ein einfaches Modul, das die path der aktuellen Seite und verwendet dann die URL API, um die Abfrage-String zu parsen (Sie können auch in einem anderen übergeben, aus welchem Grund auch immer :)).

Wir bekommen dann das Element, das geklickt wurde und überprüfen, ob es die x-all Attribut; wenn es tut, löschen wir alle Parameter aus der URL, sonst teilen wir die x-param Attribut durch Kommas und löschen Sie jeden dieser Parameter.

Dann erstellen wir eine neue URL mit dem aktualisierten Abfrage-String und verwenden HTMX, um eine Anfrage an diese URL zu stellen.

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;

Das showAlert Funktion mit SweetAlert2

Sie werden auch feststellen, dass ich eine showAlert Funktion. Dies ist nur ein einfacher Wrapper rund um den SweetAlert2-Ladeindikator, den ich in meinem Projekt verwende. Sie können dies natürlich durch alles ersetzen, was Sie tun wollen."

Dies ist leicht von der Letztes Mal haben wir es gesehen.......................................................................................................... So konnte ich die showAlert Funktion und stellen sie externen Modulen zur Verfügung. Was lassen Sie mich es in beiden der param-clearer Modul und die hx-indicator Modul.

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 Erinnerung nutzt dies die path als Schlüssel zu wissen, wann die Warnung zu verstecken.

HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX HTMX

Schließlich verwenden wir htmx.ajax um die Anfrage zu stellen. Dies ist eine einfache GET-Anfrage an die neue URL, die wir mit dem aktualisierten Query String erstellt haben.

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

Schlussfolgerung

Dies ist eine einfache Möglichkeit, URL-Parameter mit einem Tag Helper und Alpine.js zu löschen. Es ermöglicht Ihnen, alle Parameter, oder nur spezifische, mit minimalem Code zu löschen.

logo

©2024 Scott Galloway