Εμφάνιση περιεχομένου πρόποσης και ανταλλαγής με HTMX (και πυρήνα ASP.NET) ( ελληνικά (Greek_)

Εμφάνιση περιεχομένου πρόποσης και ανταλλαγής με HTMX (και πυρήνα ASP.NET)

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.

Saturday, 12 April 2025

//

Less than a minute

Εισαγωγή

HTMX είναι μια μεγάλη βιβλιοθήκη για να κάνετε τις εφαρμογές σας web πιο δυναμικές και ανταποκρινόμενες. Σε αυτή τη δημοσίευση, θα σας δείξω πώς να χρησιμοποιήσετε HTMX για να δείξετε μια ειδοποίηση πρόποση και να ανταλλάξετε περιεχόμενο στη σελίδα.

Ένας από τους "περιορισμούς" στο πρότυπο HTMX είναι ότι συνήθως έχετε μόνο ένα κομμάτι του περιεχομένου εναλλάσσονται από το πίσω άκρο. Ωστόσο, αυτό μπορεί να ξεπεραστεί με τη χρήση του HX-Trigger κεφαλίδες και ένα μικρό javascript.

ΤόστCity name (optional, probably does not need a translation)

Χρησιμοποιώ αυτό το απλό σύστημα ειδοποιήσεων εδώ και καιρό. Είναι μια απλή λειτουργία που παίρνει ένα μήνυμα, διάρκεια, και τον τύπο (επιτυχία, σφάλμα, προειδοποίηση) και δείχνει μια ειδοποίηση πρόποση στη σελίδα.

Η Javascript

// HTMX toast notification
// Simple HTMX toast handler for use with hx-on::after-request
window.showToast = (message, duration = 3000, type = 'info') => {
    const toast = document.getElementById('toast');
    const toastMessage = document.getElementById('toast-message');
    const toastText = document.getElementById('toast-text');
    const toastIcon = document.getElementById('toast-icon');

    // Reset classes
    toastMessage.className = 'alert shadow-lg gap-2 transition-all duration-300 ease-in-out cursor-pointer';
    toastIcon.className = 'bx text-2xl';

    // Add DaisyUI alert type
    const alertClass = `alert-${type}`;
    toastMessage.classList.add(alertClass);

    // Add icon class
    const iconMap = {
        success: 'bx-check-circle',
        error: 'bx-error-circle',
        warning: 'bx-error',
        info: 'bx-info-circle'
    };
    const iconClass = iconMap[type] || 'bx-bell';
    toastIcon.classList.add(iconClass);

    // Set the message
    toastText.textContent = message;

    // Add slide-in animation
    toastMessage.classList.add('animate-slide-in');
    toast.classList.remove('hidden');

    // Allow click to dismiss
    toastMessage.onclick = () => hideToast();

    // Auto-dismiss
    clearTimeout(window.toastTimeout);
    window.toastTimeout = setTimeout(() => hideToast(), duration);

    function hideToast() {
        toastMessage.classList.remove('animate-slide-in');
        toastMessage.classList.add('animate-fade-out');
        toastMessage.onclick = null;

        toastMessage.addEventListener('animationend', () => {
            toast.classList.add('hidden');
            toastMessage.classList.remove('animate-fade-out');
        }, { once: true });
    }
};

Αυτό χρησιμοποιεί λίγο HTML snippet I ορίσετε στο _Διάταξη.cshtml αρχείο (με τη χρήση του προτιμώμενού μου Tailwind CSS & DaisyUI). Σημειώστε το "μπλοκ συντήρησης κλάσης" στο τέλος. Αυτό είναι ένα μικρό κόλπο για να εξασφαλιστεί ότι οι τάξεις διατηρούνται στην τελική έξοδο HTML. Αυτό είναι πραγματικά για μου failwind setup, όπως μόνο κοιτάζω cshtml.

<div
        id="toast"
        class="toast toast-bottom fixed z-50 hidden w-full md:w-auto max-w-sm right-4 bottom-4"
>
    <div
            id="toast-message"
            class="alert shadow-lg gap-2 transition-all duration-300 ease-in-out cursor-pointer"
    >
        <i id="toast-icon" class="bx text-2xl"></i>
        <span id="toast-text">Notification message</span>
    </div>
</div>

<!-- class-preserving dummy block -->
<div class="hidden">
    <div class="alert alert-success alert-error alert-warning alert-info"></div>
    <i class="bx bx-check-circle bx-error-circle bx-error bx-info-circle bx-bell"></i>
    <div class="animate-slide-in animate-fade-out"></div>
</div>

Ανεμοστρόβιλος

Εδώ ορίζω τι αρχεία για να "δροσερό-shake" από, καθώς και να καθορίσει ορισμένες κατηγορίες animation που χρησιμοποιεί το τοστ.

const defaultTheme = require("tailwindcss/defaultTheme");

module.exports = {
  content: ["./Views/**/*.cshtml", "./Areas/**/*.cshtml"],
  safelist: ["dark"],
  darkMode: "class",
  theme: {
    extend: {
      keyframes: {
        'slide-in': {
          '0%': { opacity: 0, transform: 'translateY(20px)' },
          '100%': { opacity: 1, transform: 'translateY(0)' },
        },
        'fade-out': {
          '0%': { opacity: 1 },
          '100%': { opacity: 0 },
        },
      },
      animation: {
        'slide-in': 'slide-in 0.3s ease-out',
        'fade-out': 'fade-out 0.5s ease-in forwards',
      },
  },
  plugins: [require("daisyui")],
};

Σφυρηλατημένα

Το μυστικό του να κάνεις όλα αυτά να δουλέψουν είναι να χρησιμοποιήσεις το Λειτουργία κεφαλίδας HTMX.

Τώρα "κανονικά" θα ορίστε αυτό στον πραγματικό σας κώδικα html / ξυράφι:

<div hx-get="/clicked" hx-trigger="click[ctrlKey]">Control Click Me</div>

Ή μπορείτε να το προσδιορίσετε σε μια εκδήλωση μετά από αίτημα. Οπότε κάνεις κάτι και μετά προκαλεί ένα νέο γεγονός.

<button 
    hx-get="/api/do-something"
    hx-swap="none"
    hx-on::afterRequest="window.showToast('API call complete!', 3000, 'success')"
    class="btn btn-primary"
>
    Do Something
</button>

Αυτό είναι χρήσιμο αν απλά θέλετε να "κάνετε κάτι στη συνέχεια να υποδείξετε ότι έγινε" αλλά στην περίπτωσή μου θέλω να ανταλλάξω κάποιο περιεχόμενο και να δείξω μια πρόποση.

            Response.Headers.Append("HX-Trigger", JsonSerializer.Serialize(new
            {
                showToast = new
                {
                    toast = result.Message,
                    issuccess = result.Success
                }
            }));

Στην περίπτωσή μου η σκανδάλη μου ονομάζεται showToast Και περνάω με ένα μήνυμα και μια σημαία επιτυχίας. Έτσι, έχω ορίσει έναν ακροατή εκδηλώσεων γι' αυτό το γεγονός. Αυτό στη συνέχεια καλεί την showToast λειτουργεί και περνά στο μήνυμα και τη σημαία επιτυχίας.

// Handles HX-Trigger: { "showToast": { "toast": "...", "issuccess": true } }
document.body.addEventListener("showToast", (event) => {
    const { toast, issuccess } = event.detail || {};
    const type = issuccess === false ? 'error' : 'success';
    showToast(toast || 'Done!', 3000, type);
});

ASP.NET

Τότε γιατί το χρησιμοποιώ αυτό; Λοιπόν, σε ένα πρόσφατο έργο εργασίας ήθελα να αναλάβω δράση σε έναν χρήστη που εμφανίζεται σε ένα τραπέζι. Ήθελα να κάνω μια πρόποση και να ανταλλάξω το περιεχόμενο της σειράς χρηστών με το νέο περιεχόμενο.

userrow.png

Όπως μπορείτε να δείτε, έχω ένα BUNCH κουμπιά που "κάνουν πράγματα" στο χρήστη. Ήθελα να κάνω μια πρόποση και να ανταλλάξω το περιεχόμενο της σειράς χρηστών με το νέο περιεχόμενο.

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

    private async Task ApplyAction(string email, string useraction)
    {
        if (!string.IsNullOrWhiteSpace(useraction) &&
            Enum.TryParse<UserActionType>(useraction, true, out var parsedAction))
        {
            RequestResult result;

            switch (parsedAction)
            {
                case UserActionType.FlipRoles:
                    result = await userActionService.FlipRestaurantPermissions(email);
                    break;
                case UserActionType.UnflipRoles:
                    result = await userActionService.UnFlipRestaurantPermissions(email);
                    break;
                case UserActionType.Enable2FA:
                    result = await userActionService.ToggleMFA(email, true);
                    break;
                case UserActionType.Disable2FA:
                    result = await userActionService.ToggleMFA(email, false);
                    break;
                case UserActionType.RevokeTokens:
                    result = await userActionService.RevokeTokens(email);
                    break;
                case UserActionType.Lock:
                    result = await userActionService.Lock(email);
                    break;
                case UserActionType.Unlock:
                    result = await userActionService.Unlock(email);
                    break;
                case UserActionType.Nuke:
                    result = await userActionService.Nuke(email);
                    break;
                case UserActionType.Disable:
                    result = await userActionService.DisableUser(email);
                    break;
                case UserActionType.Enable:
                    result = await userActionService.EnableUser(email);
                    break;
                case UserActionType.ResetPassword:
                    result = await userActionService.ChangePassword(email);
                    break;
                case UserActionType.SendResetEmail:
                    result = await userActionService.SendResetEmail(email);
                    break;
                default:
                    result = new RequestResult(false, "Unknown action");
                    break;
                  
            }

            Response.Headers.Append("HX-Trigger", JsonSerializer.Serialize(new
            {
                showToast = new
                {
                    toast = result.Message,
                    issuccess = result.Success
                }
            }));

        }
    }

Μπορείς να δεις ότι προσθέτω και το... HX-Trigger Πήγαινε στην απάντηση. Αυτό είναι ένα αντικείμενο JSON με το showToast κλειδί και μια αξία ενός αντικειμένου με το toast και issuccess Τα κλειδιά. Η toast Το κλειδί είναι το μήνυμα για να δείξει στην κοινοποίηση πρόποση και το issuccess Το κλειδί είναι ένας boolean που δείχνει αν η δράση ήταν επιτυχής ή όχι.

Στη συνέχεια, στην _Row Μερική Έχω τα χαρακτηριστικά HX (χρησιμοποιώντας HTMX.Net) για να ενεργοποιήσει τη δράση.

                     <!-- Revoke Login Tokens -->
                            <button class="btn btn-xs btn-error border whitespace-normal text-wrap tooltip tooltip-left" data-tip="Revoke login tokens"
                                    hx-get hx-indicator="#loading-modal" hx-target="closest tr" hx-swap="outerHTML"
                                    hx-action="Row" hx-controller="Users"
                                    hx-route-email="@user.Email" hx-route-useraction="@UserActionType.RevokeTokens"
                                    hx-confirm="Are you sure you want to revoke the login tokens for this user?">
                                <i class="bx bx-power-off"></i> Revoke
                            </button>

Μπορείς να δεις ότι χρησιμοποιώ τον στόχο. closest tr να ανταλλάξει ολόκληρη τη σειρά με το νέο περιεχόμενο. Αυτός είναι ένας απλός τρόπος για να ενημερώσετε το περιεχόμενο της γραμμής χωρίς να χρειάζεται να κάνετε μια πλήρη σελίδα ανανέωση.

Μερική προβολή

Αυτό είναι πραγματικά πολύ απλό και μια μεγάλη τεχνική για ASP.NET Core με HTMX. Μπορείτε να χρησιμοποιήσετε προαιρετικά HTMX.Nets Αίτημα.IsHtmxΑυτό εδώ, αλλά σε αυτή την περίπτωση το χρησιμοποιώ μόνο από μια κλήση HTMX.

    [Route("row")]
 
    public async Task<IActionResult> Row(string email, string? useraction = null)
    {

        if(!string.IsNullOrEmpty(useraction))
          await ApplyAction(email, useraction);

        var userRow = await userViewService.GetSingleUserViewModel(email);
        return PartialView("_Row", userRow);
    }

Σε αυτή την περίπτωση η Μερική άποψη _Row είναι μια απλή γραμμή πίνακα με τις πληροφορίες του χρήστη και τα κουμπιά για την εκτέλεση των ενεργειών.

Πρόσθετα χαρακτηριστικά HTMX

Χρησιμοποιώ επίσης μερικά ακόμα χαρακτηριστικά HTMX για να βελτιώσω την εμπειρία του χρήστη.

Φόρτωση

Χρησιμοποιώ επίσης ένα απλό loading modal να αναφέρει ότι το αίτημα βρίσκεται σε εξέλιξη. Αυτός είναι ένας απλός τρόπος για να δείξει ο χρήστης ότι κάτι συμβαίνει στο παρασκήνιο.

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

Επιβεβαίωση

Χρησιμοποιώ επίσης το hx-confirm χαρακτηριστικά για την εμφάνιση διαλόγου επιβεβαίωσης πριν από την εκτέλεση της δράσης. Αυτός είναι ένας απλός τρόπος για να διασφαλιστεί ότι ο χρήστης θέλει πραγματικά να εκτελέσει τη δράση. Αυτή η χρήση SweetAlert2 για να εμφανιστεί ένας διάλογος επιβεβαίωσης.

Τώρα, αν δεν το κάνετε αυτό, το HTMX εξακολουθεί να λειτουργεί, αλλά χρησιμοποιεί τον τυπικό διάλογο "επιβεβαίωση" του προγράμματος περιήγησης, ο οποίος μπορεί να είναι λίγο δύσκολος για το χρήστη.

// HTMX confirm with SweetAlert2
window.addEventListener('htmx:confirm', (e) => {
    const message = e.detail.question;
    if (!message) return;

    e.preventDefault();

    Swal.fire({
        title: 'Please confirm',
        text: message,
        icon: 'warning',
        showCancelButton: true,
        confirmButtonText: 'Yes',
        cancelButtonText: 'Cancel',
        theme: 'dark',
    }).then(({ isConfirmed }) => {
        if (isConfirmed) e.detail.issueRequest(true);
    });
});

Συμπέρασμα

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

logo

©2024 Scott Galloway