# عرض الخبز وتبادل المحتوى مع HTMX (و ASP.NET CoreM SK2

<datetime class="hidden">2025-04-12T13:33</datetime>

<!--category-- ASP.NET Core, HTMX -->
# المقدمة

HTMX هي مكتبة عظيمة لجعل تطبيقاتكم على الانترنت أكثر ديناميكية وتفاعلية.

واحد من 'محدوداتM SK1 في معيار HTMX (i.eMSC4 [ليست مبادلات أوOB](https://htmx.org/attributes/hx-swap-oob/)) هو أنه في العادة لديك قطعة واحدة فقط من المحتوى تم شراؤها من النهاية الخلفية. لكن هذا يمكن تجاوزه باستخدام `HX-Trigger` headers and a little javascript.

**ملاحظة: يمكنك استخدام [`hx-swap-oob`](https://htmx.org/attributes/hx-swap-oob/) لتبادل إثنين مختلفين *المحتوى* عناصر ولكن هذا أكثر تعقيداً وليس بهذه السهولة في استخدامها لفعل بعض الأشياء JavaScripty.**

[TOC]

# فطيرة التوست

I'لقد استخدمت هذا الاختلاف من نظام الإبلاغ عن الخبز البسيط [لبعض الوقت الآن](https://www.mostlylucid.net/blog/acopybuttonforhightlightjs#showtoast-function). إنها ' عبارة عن ميزة بسيطة تأخذ رسالة ,, ,مدة , , ,و تقوم بطباعة .( ,تنجح ,, ,خطأ ,,, ,تحذير ,،) , و تظهر إشارة التحميص على الصفحة .

هذا 'أول نسخة' لديه بعض المزيد من الbling حول الأيقوناتM SK2 الرسوم المتحركة وهلم جرا ...

## Javascript

```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 صغيرة أعرفها في _التخطيط.ឯកសារ cshtml (باستخدام CSS المفضل لدي |Tailwind |& | DaisyUI | ). لاحظوا أن block | class | preservation | Block | Class | at the end | this is a little trick to ensure that the classes are preserved in the final HTML output | This is really for my tailwind setup as I only look at `cshtml`.

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

## الرياح الهوائية

هنا أقوم بتحديد أي فايلات 'treeM SK1shake' منها كما أقوم أيضا بتحدي بعض مجموعات الرسوم المتحركة التي يستخدمها الخبز

```javascript
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 وظيفة مُثبطة للأرقام](https://htmx.org/headers/hx-trigger/).

الآن 'مع العادة' ستقولون [يحدد هذا في HTML الخاص بك / شفرة الشفرة](https://htmx.org/attributes/hx-trigger/):

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

أو يمكنك تعريفه في حدث بعد الطلب.

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

هذا مفيد إذا كنت تريد فقط ' أن تفعل شيئاً ثم تشير إلى ذلك ' تم إنجازه ' لكن في حالتي أريد أن أتبادل بعض المحتوى وأظهر الخبز

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

في حالتي اسم محركي `showToast` وأقوم بنقل رسالة وعلم النجاح . لذا أنا في نظامي البرمجة لقد قمت بتحديد مستمع للأحداث لهذا الحدث . هذا يتصل بعد ذلك في `showToast` Function and passes in the message and success flag.

```javascript
// 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](userrow.png)

كما ترون، لدي مجموعة من الأزرار التي ' تقوم بأشياء ' إلى المستخدم | . | أردت أن أعرض إبلاغاً عن الخبز وتبادل محتويات صف المستخدم مع محتويات جديدة |. |

لذا في جهازي المتحكم لدي مفتاح بسيط 'switch' الذي يأخذ اسم العمل , يقوم بأشياء ثم يعيد النتيجة الجديدة للطلب

```csharp
    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` header to the response. هذا جسم JSON مع `showToast` مفاتيح و قيمة لشيء مع `toast` و `issuccess` المفاتيح. `toast` key هو الرسالة التي يجب أن تظهر في الإبلاغ عن الخبز `issuccess` key هو بولين يشير إلى أن العمل كان ناجحا أم لا.

ثم في `_Row` جزئياً لدي خصائص HX (باستخدام HTMXM SK1Net) لإطلاق الإجراء

```html
                     <!-- 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.Net بشكل اختياري`s `طلب.IsHtmx` هنا ولكن في هذه الحالة أنا أستخدم هذا فقط من HTMX callback

```csharp
    [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` لإظهار أن الطلب في حال تقدمه. هذه طريقة بسيطة لإظليل المستخدم أن شيئا ما يحدث في الخلفية.

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

## التأكيد

أستخدم أيضاً `hx-confirm` atribut to show a confirmation dialog before the action is performed. This is a simple way to ensure that the user really wants to perform the action [SweetAlert2](https://sweetalert2.github.io/) لإظهار محادثة تأكيد.

الآن إذا كنت DON'T تفعل هذا, HTMX لا يزال يعمل لكنه يستخدم المتصفح القياسي 'confirmM SK3 ប្រអប់ الذي يمكن أن يكون مزعجاً قليلاً للمستخدم

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