# Χρήση SweetAlert2 για δείκτες HTMX

<datetime class="hidden">2025-04-21T20:05</datetime>

<!--category-- Javascript, HTMX -->
# Εισαγωγή

Σε ένα έργο που χρησιμοποιώ και καταχράζομαι το HTMX για να φτιάξω ένα admin UI. Ως μέρος αυτού χρησιμοποιώ το υπέροχο [SweetAlert2](https://sweetalert2.github.io/#examples)  Βιβλιοθήκη Javascript [για τους διαλόγους επιβεβαίωσης μου](https://www.mostlylucid.net/blog/showingtoastandswappingwithhtmx#additional-htmx-features).  Λειτουργεί υπέροχα, αλλά ήθελα επίσης να τα χρησιμοποιήσω για να αντικαταστήσω τους δείκτες φόρτωσης HTMX μου.

Αυτό αποδείχτηκε προκλητικό και σκέφτηκα να το καταγράψω εδώ για να σε γλιτώσω από τον ίδιο πόνο.

 <p style="color:red">Warning I'm a C# coder my Javascript is likely horrible.</p>
[TOC]

# Το Πρόβλημα

Οπότε το HTMX είναι πολύ έξυπνο. `hx-indicator` Κανονικά σας επιτρέπει να ρυθμίσετε έναν δείκτη φόρτωσης για τις αιτήσεις σας HTMX.  Κανονικά αυτό είναι ένα στοιχείο HTML στη σελίδα σας όπως

```html

<div id="loading-modal" class="modal htmx-indicator">
    <div class="modal-box flex flex-col items-center justify-center bg-base-200 border border-base-300 shadow-xl rounded-xl text-base-content dark text-center ">
        <div class="flex flex-col items-center space-y-4">
            <h2 class="text-lg font-semibold tracking-wide">Loading...</h2>
            <span class="loading loading-dots loading-xl text-4xl text-stone-200"></span>
        </div>
    </div>
</div>
```

Στη συνέχεια, όταν θέλετε να το χρησιμοποιήσετε θα διακοσμήσετε το αίτημά σας HTMX με `hx-indicator="#loading-modal"` και θα δείξει τη μέθοδο όταν το αίτημα βρίσκεται σε εξέλιξη.

Τώρα HTMX κάνει κάποια έξυπνη μαγεία χρησιμοποιώντας ένα `request` αντικείμενο [Ανιχνεύει εσωτερικά](https://github.com/bigskysoftware/htmx/blob/86893ebf4cb0f38484a522044f9a07cdd79398fa/src/htmx.js#L3293)

```javascript
  function addRequestIndicatorClasses(elt) {
    let indicators = /** @type Element[] */ (findAttributeTargets(elt, 'hx-indicator'))
    if (indicators == null) {
      indicators = [elt]
    }
    forEach(indicators, function(ic) {
      const internalData = getInternalData(ic)
      internalData.requestCount = (internalData.requestCount || 0) + 1
      ic.classList.add.call(ic.classList, htmx.config.requestClass)
    })
    return indicators
  }

```

Ως εκ τούτου, η αντικατάσταση τους αποτελεί μια μικρή πρόκληση. Πώς μπορείτε να παρακολουθείτε τα αιτήματα και στη συνέχεια να δείξει τη μέθοδο SweetAlert2 όταν το αίτημα είναι σε εξέλιξη και να το κρύψει όταν τελειώσει.

# Λύση

Έτσι έβαλα σε λειτουργία (όχι επειδή έπρεπε, επειδή έπρεπε να:)) για να αντικαταστήσω τον δείκτη φόρτωσης HTMX με έναν τρόπο SweetAlert2.
Τέλος πάντων, εδώ είναι ο κωδικός που βρήκα.

YOu'd ξεκινήσει είτε εισάγοντας SweetAlert2 στο HTML σας / εισαγωγή για webpack ή παρόμοια ([δείτε τους γιατρούς τους για αυτό).
](https://sweetalert2.github.io/#download)

```javascript
import Swal from 'sweetalert2';

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

        const path = getRequestPath(evt.detail);
        if (!path) return;

        evt.detail.indicator = null; // Disable HTMX's default class logic
        sessionStorage.setItem(SWEETALERT_PATH_KEY, path);

        Swal.fire({
            title: 'Loading...',
            allowOutsideClick: false,
            allowEscapeKey: false,
            showConfirmButton: false,
            theme: 'dark',
            didOpen: () => Swal.showLoading()
        });
    });

    function maybeClose(evt) {
        const activePath = sessionStorage.getItem(SWEETALERT_PATH_KEY);
        const path = getRequestPath(evt.detail);

        if (activePath && path && activePath === path) {
            Swal.close();
            sessionStorage.removeItem(SWEETALERT_PATH_KEY);
        }
    }

    document.body.addEventListener('htmx:afterRequest', maybeClose);
    document.body.addEventListener('htmx:responseError', maybeClose);
}

const SWEETALERT_PATH_KEY = 'swal-active-path';

function getIndicatorSource(el) {
    return el.closest('[hx-indicator], [data-hx-indicator]');
}


function getRequestPath(detail) {
    const basePath = detail?.pathInfo?.path ?? detail?.path ?? '';
    const responsePath = detail?.pathInfo?.responsePath ?? basePath;
    const elt = detail.elt;

    const isGet = (detail.verb ?? '').toUpperCase() === 'GET';
    const form = elt.closest('form');

    // ✅ If it's a GET form, append query params
    if (isGet && form) {
        const params = new URLSearchParams();

        for (const el of form.elements) {
            if (!el.name || el.disabled) continue;

            const type = el.type;
            if ((type === 'checkbox' || type === 'radio') && !el.checked) continue;
            if (type === 'submit') continue;

            params.append(el.name, el.value);
        }

        const queryString = params.toString();
        return queryString ? `${responsePath}?${queryString}` : responsePath;
    }

    // ✅ For all others, just return the response path
    return responsePath;
}
document.body.addEventListener('htmx:afterRequest', maybeClose);
document.body.addEventListener('htmx:responseError', maybeClose);


```

Ρυθμίζετε αυτό (αν χρησιμοποιείτε ESM) στο `main.js` αρχείο όπως αυτό

```javascript

import { registerSweetAlertHxIndicator } from './hx-sweetalert-indicator.js';
registerSweetAlertHxIndicator();
```

### Βρίσκοντας τα στοιχεία μας

Θα δεις ότι χρησιμοποιώ το... `getIndicatorSource` λειτουργία για την εύρεση του στοιχείου που προκάλεσε το αίτημα HTMX. Αυτό είναι σημαντικό καθώς πρέπει να γνωρίζουμε ποιο στοιχείο προκάλεσε το αίτημα ώστε να μπορέσουμε να κλείσουμε τη μέθοδο όταν τελειώσει. Αυτό είναι σημαντικό καθώς η HTMX έχει "κληρονομιά" οπότε πρέπει να σκαρφαλώσετε στο δέντρο για να βρείτε το στοιχείο που προκάλεσε το αίτημα.

```javascript
function getIndicatorSource(el) {
    return el.closest('[hx-indicator], [data-hx-indicator]');
}

```

Στη συνέχεια, σε οποιαδήποτε αίτηση HTMX (έτσι `hx-get` ή `hx-post`) μπορείτε να χρησιμοποιήσετε το `hx-indicator` χαρακτηριστικό για τον προσδιορισμό της μεθόδου SweetAlert2. Δεν χρειάζεται καν να καθορίσετε την τάξη όπως πριν, μόνο η παράμετρος που υπάρχει λειτουργεί.

Ας δούμε πώς λειτουργούν όλα αυτά:

## Να τα φτιάχνεις με τα λεφτά σου. ` registerSweetAlertHxIndicator()`

Αυτό λειτουργεί ως σημείο εισόδου.  Μπορείς να το δεις να μπαίνει μέσα στο... `htmx:configRequest` Γεγονός. Αυτό απολύεται όταν το HTMX πρόκειται να υποβάλει αίτημα.

Στη συνέχεια παίρνει το στοιχείο που πυροδότησε το γεγονός σε `evt.detail.elt` και ελέγχει εάν έχει `hx-indicator` γνώριμη ιδιότητα.

Τέλος, δείχνει τον τρόπο SweetAlert2 με τη χρήση `Swal.fire()`.

```javascript
rt function registerSweetAlertHxIndicator() {
    document.body.addEventListener('htmx:configRequest', function (evt) {
        const trigger = evt.detail.elt;
        const indicatorAttrSource = getIndicatorSource(trigger);
        if (!indicatorAttrSource) return;
```

## Παίρνοντας το μονοπάτι αίτησης

Αν το κάνει, παίρνει το μονοπάτι αίτησης χρησιμοποιώντας `getRequestPath(evt.detail)` και το αποθηκεύει στην αποθήκευση συνεδρίας.
Niw HTMX είναι ένα δύσκολο κάθαρμα, αποθηκεύει το μονοπάτι σε διάφορα μέρη ανάλογα με το πού βρίσκεστε στον κύκλο ζωής. Οπότε στον κώδικά μου κάνω τα πάντα. με `detail?.pathInfo?.path ?? detail?.path ?? '';`

Αποδεικνύεται ότι HTMX αποθηκεύτηκε το *αίτηση* Μονοπάτι στο `detail.path` και τη διαδρομή απόκρισης (για `    document.body.addEventListener('htmx:afterRequest', maybeClose); document.body.addEventListener('htmx:responseError', maybeClose);`) `detail.PathInfo.responsePath` Οπότε πρέπει να χειριστούμε και τα δύο.

Πρέπει επίσης να χειριστούμε `GET` μορφές, καθώς η ανταπόκρισή τους θα περιλαμβάνει πιθανώς τα στοιχεία URL που έχουν περάσει ως `<input >` τιμές έτσι ώστε η ανταπόκριση url μπορεί να καταλήξει να είναι διαφορετική.

```javascript
function getRequestPath(detail) {
    const basePath = detail?.pathInfo?.path ?? detail?.path ?? '';
    const responsePath = detail?.pathInfo?.responsePath ?? basePath;
    const elt = detail.elt;

    const isGet = (detail.verb ?? '').toUpperCase() === 'GET';
    const form = elt.closest('form');

    // ✅ If it's a GET form, append query params
    if (isGet && form) {
        const params = new URLSearchParams();

        for (const el of form.elements) {
            if (!el.name || el.disabled) continue;

            const type = el.type;
            if ((type === 'checkbox' || type === 'radio') && !el.checked) continue;
            if (type === 'submit') continue;

            params.append(el.name, el.value);
        }

        const queryString = params.toString();
        return queryString ? `${responsePath}?${queryString}` : responsePath;
    }

    // ✅ For all others, just return the response path
    return responsePath;
}
```

ΣΗΜΕΙΩΣΗ: Αυτό ισχύει ιδιαίτερα εάν χρησιμοποιείτε το `HX-Push-Url` κεφαλίδα για να αλλάξει το URL του αιτήματος που HTMX αποθηκεύει για την ιστορία.

Χρησιμοποιώ αυτό το μικρό `Response` μέθοδος επέκτασης για τον καθορισμό του `HX-Push-Url` κεφαλίδα στην εφαρμογή ASP.NET Core μου.

```csharp
public static class ResponseExtensions
{
    public static void PushUrl(this HttpResponse response, HttpRequest request)
    {
        response.Headers["HX-Push-Url"] = request.GetEncodedUrl();
    }
}

```

## Αποθήκευση του μονοπατιού

Εντάξει, τώρα έχουμε το μονοπάτι, τι θα κάνουμε με αυτό; Καλά για να παρακολουθείτε ποια αίτηση πυροδότησε τον τρόπο SweetAlert2 το αποθηκεύουμε σε `sessionStorage` χρήση `sessionStorage.setItem(SWEETALERT_PATH_KEY, path);`.

(Πάλι μπορείτε να το κάνετε πιο περίπλοκο και να διασφαλίσετε ότι έχετε μόνο ένα αν χρειάζεστε.)

## Εμφάνιση του τρόπου

Μετά απλά δείχνουμε τη μέθοδο SweetAlert2 χρησιμοποιώντας `Swal.fire()`.

```javascript
           Swal.fire({
    title: 'Loading...',
    allowOutsideClick: false,
    allowEscapeKey: false,
    showConfirmButton: false,
    theme: 'dark',
    didOpen: () => Swal.showLoading()
});
```

## Κλείσ' το.

Έτσι, τώρα που έχουμε τη μέθοδο ανοιχτή, πρέπει να την κλείσουμε όταν ολοκληρωθεί το αίτημα. Για να το κάνουμε αυτό ονομάζουμε `maybeClose` λειτουργία. Αυτό ονομάζεται όταν η αίτηση έχει ολοκληρωθεί (είτε με επιτυχία είτε με σφάλμα).
Χρήση `htmx:afterRequest` και `htmx:responseError` Γεγονότα.  Τα γεγονότα αυτά πυροβολούν μόλις η HTMX ολοκληρώσει μια αίτηση (σημειώστε, αυτά είναι σημαντικά, ειδικά για την `HX-Boost` που μπορεί να είναι λίγο αστείο σχετικά με τα γεγονότα που πυροδοτεί.)

```javascript
    document.body.addEventListener('htmx:afterRequest', maybeClose);
    document.body.addEventListener('htmx:responseError', maybeClose);
```

```javascript
    function maybeClose(evt) {
        const activePath = sessionStorage.getItem(SWEETALERT_PATH_KEY);
        const path = getRequestPath(evt.detail);

        if (activePath && path && activePath === path) {
            Swal.close();
            sessionStorage.removeItem(SWEETALERT_PATH_KEY);
        }
    }
```

Θα δείτε αυτή τη λειτουργία ελέγχει εάν η διαδρομή κατά τη συνεδρία αποθήκευσης είναι η ίδια με την πορεία του αιτήματος. Αν είναι, κλείνει τη μέθοδο και αφαιρεί το μονοπάτι από την αποθήκευση συνεδρίας.

# Μελλοντική Εργασία

Αυτή είναι μια αφελής εφαρμογή, αλλά... *Δουλεύει.* Κυρίως, στο μέλλον θα ήθελα να προσθέσω μερικά πράγματα:

1. Ανοίγουμε μόνο 1 φορά τη φορά. (Αυτό μιμείται τη συμπεριφορά HTMX).
2. Χρονικό όριο για ορφανές μεθόδους (αυτή τη στιγμή αν αλλάξετε την πλευρά του εξυπηρετητή URL απάντηση με έναν απροσδόκητο τρόπο θα παραμείνει ανοιχτό).

# Συμπέρασμα

Έτσι λοιπόν μπορείτε να χρησιμοποιήσετε το SweetAlert2 ως δείκτη φόρτωσης HTMX. Είναι ένα κομμάτι από ένα hack, αλλά λειτουργεί και είναι ένας ωραίος τρόπος για να χρησιμοποιήσετε την ίδια βιβλιοθήκη τόσο για τους δείκτες φόρτωσης και τους διαλόγους επιβεβαίωσης.9