Back to "إظهار محتوى الخبز والسحب مع HTMX (ومصدر ASP.net)"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

ASP.NET Core HTMX

إظهار محتوى الخبز والسحب مع HTMX (ومصدر ASP.net)

Saturday, 12 April 2025

أولاً

HTMX هي مكتبة كبيرة لجعل تطبيقات الويب الخاص بك أكثر ديناميكية واستجابة. في هذا الموقع، سأريكم كيف تستخدمون HTMX لعرض رسالة نخبية وتبادل المحتوى على الصفحة.

واحدة من "الحدود" في معيار HTMX هي أن لديك عادة فقط قطعة واحدة من المحتوى مبادلة من الطرف الخلفي. ومع ذلك يمكن التغلب على ذلك باستخدام HX-Trigger رأساً و القليل من الـ (جافاكر)

حتى الآن

لقد كنت استخدم هذا البديل من هذا النظام البسيط لإخطارات النخب لفترة من الوقت/ / / / إنها اقتران بسيط يأخذ رسالة، مدة، ونوع (النجاح، الخطأ، الإنذار) ويُظهر إشعاراً بالنخب على الصفحة.

هذا "النسخة الأخيرة" لديها المزيد من التدليك حول الأيقونات، الرسومات...................................................................................................................................................................................................................................................................................................................................................................................................................

الـ مُنكر مُنكر

// 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 });
    }
};

هذا استخدام a قليلاً HTML snippit أنا تعريف بوصة _التصميم. cshtml ملف (باستخدام أفضل ملفاتي في Tayilwind CSS & DesieUI). ملاحظة "الطبقة المحافظة على كتلة" في النهاية. هذه خدعة صغيرة للتأكد من أن الفصول محفوظة في مخرجات HTML النهائية. هذا حقًا من أجل إيقاعي بينما أنظر فقط إلى 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>

ثنياط

هنا أُعرّفُ ملفاتَ إلى "تَرّي تَهْزُّ" مِنْ فضلاً عِنْدَهُ تعريف بَعْض فئاتِ الرسوم المتحركةِ التي يَستعملها المحمص.

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

مُسوّج

السر في جعل هذا كل ما يعمل هو استخدام مُحرِس مُسند مُراس مُراس مُراس المُراس.

الآن '.......................................................................................... عرّف هذا في 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);
});

المحتويات

إذاً لماذا أستخدم هذا؟ في مشروع عمل حديث أردت اتخاذ بعض الإجراءات على مستخدم عرض في الجدول. أردت أن أعرض تنبيهاً محمصاً ومبادلة محتوى صف المستخدم مع المحتوى الجديد.

userrow.png

كما ترون لدي مجموعة من الأزرار التي "تعمل أشياء" للمستخدم. أردت أن أعرض تنبيهاً محمصاً ومبادلة محتوى صف المستخدم مع المحتوى الجديد.

اذاً في سيطرتي لدي "مفتاح" بسيط يأخذ اسم الفعل، يقوم بالاشياء ثم يرجع نتيجة الطلب الجديد.

    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 رأس إلى الإستجابة. هذا هو كائن JJson مع showToast وقيمة قيمة جسم مع toast وقد عقد مؤتمراً بشأن issuccess -مفاتيح مفاتيح المفاتيح -مفاتيح مفاتيح المفاتيح الـ toast هي الرسالة التي ستظهر في الإخطار المحمّص و... issuccess (أ) ما إذا كان العمل ناجحاً أم لا.

ثم في _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 Corre مع HTMX. يمكنك اختيارياً استخدام HTMX. Nets طلب. ASHTMX 'هنا ولكن في هذه الحالة أنا فقط من أي وقت مضى استخدام هذا من أي وقت مضى من أي اتصال 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 هو a بسيط جدول صف مع مستخدم معلومات و إلى تنفيذ.

ميزات إضافية HTMX

أنا أيضاً أستخدم زوجين من سمات HTMX لجعل تجربة المستخدم أفضل.

1 مد-1 1 ف-5 1 ف-3 1 ف-3 1 ف-3 1 ف-3 1 ف-3 1 ف-3 1 ف-3 1 ف-3 1 ف-3 1 ف-3 1 ف-3 1 ف-3

أنا أيضاً أستخدم أيضاً بسيطاً 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 إلى إظهار a تأكيد حوار قبل إجراء هو. هذه طريقة بسيطة للتأكد من أن المستخدم يريد حقا لأداء العمل. هذه الاستخدامات Ail Altert 2 إلى a تأكيد حوار.

الآن إذا كنت لا تفعل هذا، HTMX لا يزال يعمل لكنه يستخدم حوار المتصفح 'concity' المعياري الذي يمكن أن يكون قليلاً من المعايرة للمستخدم.

// 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 لعرض رسالة نخبية ومحتويات مبادلة على الصفحة. هذه طريقة عظيمة لجعل تطبيقات الإنترنت الخاصة بك أكثر ديناميكية واستجابة.

logo

©2024 Scott Galloway