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
عرض الخبز وتبادل المحتوى مع HTMX (و ASP.NET CoreM SK2
Saturday, 12 April 2025
المقدمة
HTMX هي مكتبة عظيمة لجعل تطبيقاتكم على الانترنت أكثر ديناميكية وتفاعلية.
واحد من 'محدوداتM SK1 في معيار HTMX (i.eMSC4 ليست مبادلات أوOB) هو أنه في العادة لديك قطعة واحدة فقط من المحتوى تم شراؤها من النهاية الخلفية. لكن هذا يمكن تجاوزه باستخدام HX-Trigger headers and a little javascript.
ملاحظة: يمكنك استخدام hx-swap-oob لتبادل إثنين مختلفين المحتوى عناصر ولكن هذا أكثر تعقيداً وليس بهذه السهولة في استخدامها لفعل بعض الأشياء JavaScripty.
فطيرة التوست
I'لقد استخدمت هذا الاختلاف من نظام الإبلاغ عن الخبز البسيط لبعض الوقت الآن. إنها ' عبارة عن ميزة بسيطة تأخذ رسالة ,, ,مدة , , ,و تقوم بطباعة .( ,تنجح ,, ,خطأ ,,, ,تحذير ,،) , و تظهر إشارة التحميص على الصفحة .
هذا 'أول نسخة' لديه بعض المزيد من الbling حول الأيقوناتM SK2 الرسوم المتحركة وهلم جرا ...
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.
<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' منها كما أقوم أيضا بتحدي بعض مجموعات الرسوم المتحركة التي يستخدمها الخبز
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 Function and passes in the message and success flag.
// 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
إذاً لماذا أستخدم هذا؟

كما ترون، لدي مجموعة من الأزرار التي ' تقوم بأشياء ' إلى المستخدم | . | أردت أن أعرض إبلاغاً عن الخبز وتبادل محتويات صف المستخدم مع محتويات جديدة |. |
لذا في جهازي المتحكم لدي مفتاح بسيط 'switch' الذي يأخذ اسم العمل , يقوم بأشياء ثم يعيد النتيجة الجديدة للطلب
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) لإطلاق الإجراء
<!-- 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
[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 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 لإظهار محادثة تأكيد.
الآن إذا كنت DON'T تفعل هذا, HTMX لا يزال يعمل لكنه يستخدم المتصفح القياسي 'confirmM SK3 ប្រអប់ الذي يمكن أن يكون مزعجاً قليلاً للمستخدم
// 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 لإظهار الإبلاغ عن الخبز وتبادل المحتوى على الصفحة. هذه طريقة عظيمة لجعل تطبيقاتكم على الإنترنت أكثر ديناميكية وتفاعلية