🎯 Exemples recommandés
Balanced sample collections from various categories for you to explore
Exemples HTMX
Exemples de la librairie HTMX pour les applications web modernes avec interactions HTML dynamiques
💻 HTMX Hello World html
🟢 simple
⭐
Configuration de base HTMX et exemple d'interactions AJAX
⏱️ 10 min
🏷️ htmx, javascript, ajax
Prerequisites:
Basic HTML knowledge
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTMX Hello World</title>
<!-- HTMX Library -->
<script src="https://unpkg.com/htmx.org@latest"></script>
<!-- Basic styling -->
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 2rem;
line-height: 1.6;
}
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
background: white;
}
.btn {
background: #007bff;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
}
.btn:hover {
background: #0056b3;
}
.loading {
opacity: 0.7;
transition: opacity 300ms;
}
</style>
</head>
<body>
<h1>HTMX Hello World</h1>
<!-- Basic Click Example -->
<div class="card">
<h2>Click to Load Content</h2>
<p>This example demonstrates HTMX's ability to load content with a simple click.</p>
<button
hx-get="/api/hello-world-message"
hx-target="#message-content"
hx-swap="innerHTML"
class="btn">
Load Message
</button>
<div id="message-content" class="mt-4">
<em>Click the button above to load a message from the server...</em>
</div>
</div>
<!-- Button State Example -->
<div class="card">
<h2>Button with Loading State</h2>
<p>Example showing HTMX request indicators and disabled state during loading.</p>
<button
hx-get="/api/delayed-response"
hx-target="#delayed-content"
hx-swap="innerHTML"
hx-indicator="#loading-indicator"
hx-disabled-elt="this"
class="btn">
Load Data (2 second delay)
</button>
<span id="loading-indicator" class="htmx-indicator">
⏳ Loading...
</span>
<div id="delayed-content" class="mt-4">
<em>Click to load data with artificial delay...</em>
</div>
</div>
<!-- Form Submission Example -->
<div class="card">
<h2>AJAX Form Submission</h2>
<p>Submit forms without page refresh using HTMX.</p>
<form
hx-post="/api/submit-form"
hx-target="#form-result"
hx-swap="innerHTML">
<div style="margin-bottom: 1rem;">
<label for="name">Name:</label><br>
<input type="text" id="name" name="name" required style="padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; width: 100%;">
</div>
<div style="margin-bottom: 1rem;">
<label for="email">Email:</label><br>
<input type="email" id="email" name="email" required style="padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; width: 100%;">
</div>
<button type="submit" class="btn">Submit Form</button>
</form>
<div id="form-result" class="mt-4"></div>
</div>
<!-- Real-time Updates Example -->
<div class="card">
<h2>Real-time Counter</h2>
<p>Update content dynamically using HTMX polling.</p>
<div
hx-get="/api/counter"
hx-trigger="every 1s"
hx-swap="innerHTML">
Counter: <strong id="counter">0</strong>
</div>
<p><small>This counter updates automatically every second.</small></p>
</div>
<!-- Dynamic Content Example -->
<div class="card">
<h2>Dynamic Content Loading</h2>
<p>Load different content based on user selection.</p>
<select
hx-get="/api/content/{value}"
hx-target="#dynamic-content"
hx-swap="innerHTML"
hx-trigger="change">
<option value="">Select an option...</option>
<option value="users">Users</option>
<option value="products">Products</option>
<option value="orders">Orders</option>
</select>
<div id="dynamic-content" class="mt-4">
<em>Select an option to load content...</em>
</div>
</div>
<!-- Keyboard Shortcuts Example -->
<div class="card">
<h2>Keyboard Shortcuts</h2>
<p>Trigger actions with keyboard events using HTMX.</p>
<div>
<input
type="text"
placeholder="Type 'hello' and press Enter"
hx-get="/api/search?q={value}"
hx-target="#search-results"
hx-swap="innerHTML"
hx-trigger="keyup[keyCode==13]"
style="padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; width: 100%;">
</div>
<div id="search-results" class="mt-4"></div>
<p><small>Press Enter when typing "hello" to trigger search.</small></p>
</div>
<script>
// Mock server responses for demonstration
// In a real application, these would be actual server endpoints
// Intercept htmx requests for demonstration
document.addEventListener('htmx:beforeRequest', function(event) {
const url = event.detail.requestConfig.url;
// Mock response for hello-world-message
if (url.includes('/api/hello-world-message')) {
event.preventDefault();
document.getElementById('message-content').innerHTML =
'<div style="background: #e7f3fe; padding: 1rem; border-radius: 4px;">' +
'<h3>Hello, HTMX! 🎉</h3>' +
'<p>This message was loaded dynamically without page refresh!</p>' +
'<p>Current time: ' + new Date().toLocaleTimeString() + '</p>' +
'</div>';
return;
}
// Mock response for delayed-response
if (url.includes('/api/delayed-response')) {
event.preventDefault();
document.getElementById('delayed-content').classList.add('loading');
setTimeout(() => {
document.getElementById('delayed-content').classList.remove('loading');
document.getElementById('delayed-content').innerHTML =
'<div style="background: #d4edda; padding: 1rem; border-radius: 4px;">' +
'<h3>Data Loaded Successfully! ✅</h3>' +
'<p>This content was loaded after a 2-second delay.</p>' +
'<ul><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>' +
'</div>';
}, 2000);
return;
}
// Mock response for form submission
if (url.includes('/api/submit-form')) {
event.preventDefault();
const formData = new URLSearchParams(event.detail.requestConfig.parameters);
const name = formData.get('name');
const email = formData.get('email');
document.getElementById('form-result').innerHTML =
'<div style="background: #d1ecf1; padding: 1rem; border-radius: 4px;">' +
'<h3>Form Submitted! 📝</h3>' +
'<p><strong>Name:</strong> ' + name + '</p>' +
'<p><strong>Email:</strong> ' + email + '</p>' +
'<p>Form submitted at: ' + new Date().toLocaleTimeString() + '</p>' +
'</div>';
return;
}
// Mock response for counter
if (url.includes('/api/counter')) {
event.preventDefault();
const counterElement = document.querySelector('#counter');
const currentCount = parseInt(counterElement.textContent);
counterElement.textContent = currentCount + 1;
return;
}
// Mock response for dynamic content
if (url.includes('/api/content/')) {
event.preventDefault();
const contentType = url.split('/').pop();
let content = '';
switch(contentType) {
case 'users':
content =
'<h3>Users 👥</h3>' +
'<ul><li>John Doe - [email protected]</li>' +
'<li>Jane Smith - [email protected]</li>' +
'<li>Bob Johnson - [email protected]</li></ul>';
break;
case 'products':
content =
'<h3>Products 🛍️</h3>' +
'<ul><li>Laptop - $999</li>' +
'<li>Mouse - $29</li>' +
'<li>Keyboard - $79</li></ul>';
break;
case 'orders':
content =
'<h3>Orders 📦</h3>' +
'<ul><li>Order #1234 - Pending</li>' +
'<li>Order #1235 - Shipped</li>' +
'<li>Order #1236 - Delivered</li></ul>';
break;
}
document.getElementById('dynamic-content').innerHTML = content;
return;
}
// Mock response for search
if (url.includes('/api/search')) {
event.preventDefault();
const searchParams = new URLSearchParams(url.split('?')[1]);
const query = searchParams.get('q');
if (query && query.toLowerCase().includes('hello')) {
document.getElementById('search-results').innerHTML =
'<div style="background: #fff3cd; padding: 1rem; border-radius: 4px;">' +
'<h3>Search Results for "' + query + '"</h3>' +
'<ul><li>Hello World Tutorial</li>' +
'<li>Hello HTMX Guide</li>' +
'<li>Hello JavaScript Book</li></ul>' +
'</div>';
} else {
document.getElementById('search-results').innerHTML =
'<p style="color: #dc3545;">Try typing "hello" and press Enter!</p>';
}
return;
}
});
</script>
</body>
</html>
💻 Opérations CRUD HTMX html
🟡 intermediate
⭐⭐⭐
Opérations CRUD complètes (Créer, Lire, Mettre à jour, Supprimer) avec HTMX
⏱️ 25 min
🏷️ htmx, crud, database, table
Prerequisites:
HTMX basics, understanding of CRUD operations
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTMX CRUD Operations</title>
<script src="https://unpkg.com/htmx.org@latest"></script>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
line-height: 1.6;
}
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
background: white;
}
.btn {
background: #007bff;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
margin-right: 0.5rem;
}
.btn:hover { background: #0056b3; }
.btn-success { background: #28a745; }
.btn-success:hover { background: #1e7e34; }
.btn-danger { background: #dc3545; }
.btn-danger:hover { background: #c82333; }
.btn-warning { background: #ffc107; color: #000; }
.btn-warning:hover { background: #e0a800; }
.table { width: 100%; border-collapse: collapse; }
.table th, .table td { padding: 0.75rem; border: 1px solid #ddd; text-align: left; }
.table th { background: #f8f9fa; }
.form-group { margin-bottom: 1rem; }
.form-group label { display: block; margin-bottom: 0.5rem; font-weight: bold; }
.form-control { width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; }
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: white;
margin: 15% auto;
padding: 2rem;
border-radius: 8px;
width: 80%;
max-width: 500px;
}
.close { float: right; font-size: 28px; font-weight: bold; cursor: pointer; }
.toast {
position: fixed;
top: 20px;
right: 20px;
background: #28a745;
color: white;
padding: 1rem;
border-radius: 4px;
display: none;
z-index: 2000;
}
.htmx-indicator { opacity: 0; transition: opacity 300ms; }
.htmx-indicator.htmx-request { opacity: 1; }
.highlight { animation: highlight 2s; }
@keyframes highlight { 0% { background: yellow; } 100% { background: transparent; } }
</style>
</head>
<body>
<h1>HTMX CRUD Operations</h1>
<!-- Create User Button -->
<div class="card">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h2>User Management</h2>
<button class="btn btn-success" onclick="openCreateModal()">
➕ Add New User
</button>
</div>
<!-- Users Table -->
<div id="users-container">
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Role</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="users-tbody">
<!-- Users will be loaded here -->
</tbody>
</table>
</div>
<div class="htmx-indicator">
⏳ Loading...
</div>
</div>
<!-- Create/Edit Modal -->
<div id="userModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal()">×</span>
<h2 id="modalTitle">Add New User</h2>
<form id="userForm">
<input type="hidden" id="userId" name="id">
<div class="form-group">
<label for="userName">Name:</label>
<input type="text" id="userName" name="name" class="form-control" required>
</div>
<div class="form-group">
<label for="userEmail">Email:</label>
<input type="email" id="userEmail" name="email" class="form-control" required>
</div>
<div class="form-group">
<label for="userRole">Role:</label>
<select id="userRole" name="role" class="form-control">
<option value="User">User</option>
<option value="Admin">Admin</option>
<option value="Moderator">Moderator</option>
</select>
</div>
<div class="form-group">
<label for="userStatus">Status:</label>
<select id="userStatus" name="status" class="form-control">
<option value="Active">Active</option>
<option value="Inactive">Inactive</option>
<option value="Suspended">Suspended</option>
</select>
</div>
<div>
<button type="submit" class="btn btn-success">Save User</button>
<button type="button" class="btn" onclick="closeModal()">Cancel</button>
</div>
</form>
</div>
</div>
<!-- Toast Notification -->
<div id="toast" class="toast"></div>
<script>
// Sample data
let users = [
{ id: 1, name: 'John Doe', email: '[email protected]', role: 'Admin', status: 'Active' },
{ id: 2, name: 'Jane Smith', email: '[email protected]', role: 'User', status: 'Active' },
{ id: 3, name: 'Bob Johnson', email: '[email protected]', role: 'Moderator', status: 'Inactive' }
];
let nextId = 4;
let editingUserId = null;
// Load users on page load
document.addEventListener('DOMContentLoaded', loadUsers);
function loadUsers() {
const tbody = document.getElementById('users-tbody');
tbody.innerHTML = '';
users.forEach(user => {
const row = document.createElement('tr');
row.id = 'user-row-' + user.id;
row.innerHTML = `
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.email}</td>
<td><span class="badge badge-primary">${user.role}</span></td>
<td>
<span class="badge badge-${user.status === 'Active' ? 'success' : user.status === 'Inactive' ? 'secondary' : 'danger'}">
${user.status}
</span>
</td>
<td>
<button class="btn btn-warning" onclick="editUser(${user.id})">Edit</button>
<button class="btn btn-danger" onclick="deleteUser(${user.id})">Delete</button>
</td>
`;
tbody.appendChild(row);
});
}
function openCreateModal() {
editingUserId = null;
document.getElementById('modalTitle').textContent = 'Add New User';
document.getElementById('userForm').reset();
document.getElementById('userModal').style.display = 'block';
}
function editUser(id) {
editingUserId = id;
const user = users.find(u => u.id === id);
document.getElementById('modalTitle').textContent = 'Edit User';
document.getElementById('userId').value = user.id;
document.getElementById('userName').value = user.name;
document.getElementById('userEmail').value = user.email;
document.getElementById('userRole').value = user.role;
document.getElementById('userStatus').value = user.status;
document.getElementById('userModal').style.display = 'block';
}
function closeModal() {
document.getElementById('userModal').style.display = 'none';
document.getElementById('userForm').reset();
editingUserId = null;
}
function deleteUser(id) {
if (confirm('Are you sure you want to delete this user?')) {
// Simulate HTMX delete request
const row = document.getElementById('user-row-' + id);
row.style.transition = 'opacity 300ms';
row.style.opacity = '0';
setTimeout(() => {
users = users.filter(u => u.id !== id);
loadUsers();
showToast('User deleted successfully!', 'success');
}, 300);
}
}
// Form submission with HTMX simulation
document.getElementById('userForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const userData = Object.fromEntries(formData.entries());
if (editingUserId) {
// Update existing user
const index = users.findIndex(u => u.id === parseInt(userData.id));
users[index] = { ...userData, id: parseInt(userData.id) };
showToast('User updated successfully!', 'success');
} else {
// Create new user
users.push({ ...userData, id: nextId++ });
showToast('User created successfully!', 'success');
}
loadUsers();
closeModal();
});
function showToast(message, type = 'success') {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.style.background = type === 'success' ? '#28a745' : '#dc3545';
toast.style.display = 'block';
setTimeout(() => {
toast.style.display = 'none';
}, 3000);
}
// HTMX-like event handling for demonstration
document.body.addEventListener('htmx:beforeRequest', function(event) {
console.log('HTMX request intercepted:', event.detail);
// Here you would make actual AJAX requests in a real app
});
// Close modal when clicking outside
window.onclick = function(event) {
const modal = document.getElementById('userModal');
if (event.target === modal) {
closeModal();
}
}
</script>
</body>
</html>
💻 Intégration HTMX WebSockets html
🟡 intermediate
⭐⭐⭐⭐
Mises à jour en temps réel en utilisant HTMX avec des connexions WebSocket
⏱️ 30 min
🏷️ htmx, websocket, real-time
Prerequisites:
HTMX basics, understanding of WebSockets
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTMX WebSockets Integration</title>
<script src="https://unpkg.com/htmx.org@latest"></script>
<script src="https://unpkg.com/htmx.org@latest/dist/ext/ws.js"></script>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 1000px;
margin: 0 auto;
padding: 2rem;
line-height: 1.6;
}
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
background: white;
}
.btn {
background: #007bff;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
margin-right: 0.5rem;
}
.btn:hover { background: #0056b3; }
.btn-success { background: #28a745; }
.btn-success:hover { background: #1e7e34; }
.btn-danger { background: #dc3545; }
.btn-danger:hover { background: #c82333; }
.btn-warning { background: #ffc107; color: #000; }
.btn-warning:hover { background: #e0a800; }
.chat-container {
height: 400px;
overflow-y: auto;
border: 1px solid #ddd;
padding: 1rem;
background: #f8f9fa;
border-radius: 4px;
}
.message {
margin-bottom: 1rem;
padding: 0.5rem;
border-radius: 4px;
}
.message-sent {
background: #007bff;
color: white;
margin-left: 20%;
text-align: right;
}
.message-received {
background: #e9ecef;
margin-right: 20%;
}
.message-system {
background: #fff3cd;
text-align: center;
font-style: italic;
margin: 0 20%;
}
.typing-indicator {
color: #6c757d;
font-style: italic;
}
.status-indicator {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 0.5rem;
}
.status-connected { background: #28a745; }
.status-disconnected { background: #dc3545; }
.status-connecting { background: #ffc107; }
.online-users {
list-style: none;
padding: 0;
}
.online-users li {
padding: 0.5rem;
border-bottom: 1px solid #eee;
}
.stock-ticker {
font-family: monospace;
font-size: 1.2rem;
padding: 1rem;
background: #f8f9fa;
border-radius: 4px;
margin-bottom: 1rem;
}
.stock-up { color: #28a745; }
.stock-down { color: #dc3545; }
.notification {
position: fixed;
top: 20px;
right: 20px;
background: #17a2b8;
color: white;
padding: 1rem;
border-radius: 4px;
max-width: 300px;
display: none;
z-index: 1000;
}
</style>
</head>
<body>
<h1>HTMX WebSockets Integration</h1>
<!-- Connection Status -->
<div class="card">
<h2>WebSocket Connection</h2>
<div>
<span class="status-indicator status-connecting" id="statusIndicator"></span>
<span id="connectionStatus">Connecting...</span>
</div>
<button class="btn" onclick="connectWebSocket()">Connect</button>
<button class="btn btn-danger" onclick="disconnectWebSocket()">Disconnect</button>
</div>
<!-- Real-time Chat -->
<div class="card">
<h2>Real-time Chat</h2>
<p>Send and receive messages in real-time using WebSockets.</p>
<div class="chat-container" id="chatMessages">
<div class="message message-system">
Welcome to the chat! Messages will appear here.
</div>
</div>
<form onsubmit="sendMessage(event)">
<div style="display: flex; margin-top: 1rem;">
<input type="text" id="messageInput"
placeholder="Type a message..."
style="flex: 1; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; margin-right: 0.5rem;">
<button type="submit" class="btn btn-success">Send</button>
</div>
</form>
<div id="typingIndicator" class="typing-indicator" style="display: none; margin-top: 0.5rem;">
Someone is typing...
</div>
</div>
<!-- Online Users -->
<div class="card">
<h2>Online Users</h2>
<div
hx-ext="ws"
ws-connect="/ws/users"
ws-send="get_users"
hx-trigger="every 5s"
hx-swap="innerHTML">
<ul class="online-users" id="onlineUsers">
<li>Loading users...</li>
</ul>
</div>
<small class="text-muted">Updates every 5 seconds</small>
</div>
<!-- Live Stock Ticker -->
<div class="card">
<h2>Live Stock Ticker</h2>
<p>Real-time stock price updates using WebSocket streaming.</p>
<div
hx-ext="ws"
ws-connect="/ws/stocks"
hx-trigger="receivedMessage from:body"
hx-swap="innerHTML"
id="stockTicker">
<div class="stock-ticker">
<div>AAPL: $150.25 <span class="stock-up">↑+2.15%</span></div>
<div>GOOGL: $2,845.30 <span class="stock-down">↓-0.85%</span></div>
<div>MSFT: $305.80 <span class="stock-up">↑+1.42%</span></div>
<div>TSLA: $245.60 <span class="stock-down">↓-3.21%</span></div>
</div>
</div>
<button class="btn btn-warning" onclick="toggleStockUpdates()">
Toggle Updates
</button>
</div>
<!-- Live Notifications -->
<div class="card">
<h2>Live Notifications</h2>
<p>Receive real-time notifications and system alerts.</p>
<div
hx-ext="ws"
ws-connect="/ws/notifications"
hx-trigger="receivedMessage from:body"
hx-swap="beforebegin"
id="notificationsList">
<!-- Notifications will be added here -->
</div>
<button class="btn btn-success" onclick="sendTestNotification()">
Send Test Notification
</button>
</div>
<!-- Real-time Collaboration -->
<div class="card">
<h2>Collaborative Document</h2>
<p>See live updates as users type in the shared document.</p>
<div
hx-ext="ws"
ws-connect="/ws/document"
ws-send="get_document"
hx-trigger="load"
id="documentContainer">
<textarea
id="sharedDocument"
placeholder="Start typing to see real-time collaboration..."
style="width: 100%; height: 200px; padding: 1rem; border: 1px solid #ddd; border-radius: 4px;"
onkeyup="syncDocument()"></textarea>
</div>
<div id="activeUsers" style="margin-top: 1rem; color: #6c757d;">
<small>Active editors: None</small>
</div>
</div>
<!-- Notification Toast -->
<div id="notification" class="notification"></div>
<script>
// WebSocket connection management
let ws = null;
let wsUrl = 'ws://localhost:8080'; // Mock WebSocket URL
let stockUpdateInterval = null;
let notificationInterval = null;
// Simulate WebSocket connection
function connectWebSocket() {
updateConnectionStatus('connecting');
// Simulate connection delay
setTimeout(() => {
updateConnectionStatus('connected');
startMockDataStream();
showToast('Connected to WebSocket!', 'success');
}, 1000);
}
function disconnectWebSocket() {
updateConnectionStatus('disconnected');
stopMockDataStream();
showToast('Disconnected from WebSocket', 'warning');
}
function updateConnectionStatus(status) {
const indicator = document.getElementById('statusIndicator');
const statusText = document.getElementById('connectionStatus');
indicator.className = 'status-indicator status-' + status;
switch(status) {
case 'connected':
statusText.textContent = 'Connected';
break;
case 'disconnected':
statusText.textContent = 'Disconnected';
break;
case 'connecting':
statusText.textContent = 'Connecting...';
break;
}
}
// Chat functionality
function sendMessage(event) {
event.preventDefault();
const input = document.getElementById('messageInput');
const message = input.value.trim();
if (message) {
addChatMessage(message, 'sent');
input.value = '';
// Simulate receiving a response
setTimeout(() => {
const responses = [
'That's interesting!',
'Tell me more.',
'I agree with you.',
'Great point!',
'Thanks for sharing!'
];
const randomResponse = responses[Math.floor(Math.random() * responses.length)];
addChatMessage(randomResponse, 'received');
}, 1000 + Math.random() * 2000);
// Show typing indicator
showTypingIndicator();
}
}
function addChatMessage(message, type) {
const container = document.getElementById('chatMessages');
const messageDiv = document.createElement('div');
messageDiv.className = 'message message-' + type;
messageDiv.textContent = message;
container.appendChild(messageDiv);
container.scrollTop = container.scrollHeight;
}
function showTypingIndicator() {
const indicator = document.getElementById('typingIndicator');
indicator.style.display = 'block';
setTimeout(() => {
indicator.style.display = 'none';
}, 2000);
}
// Mock online users
function updateOnlineUsers() {
const users = [
'John Doe',
'Jane Smith',
'Alice Johnson',
'Bob Wilson',
'Emma Davis'
];
const userList = document.getElementById('onlineUsers');
const randomCount = Math.floor(Math.random() * users.length) + 1;
const selectedUsers = users.slice(0, randomCount);
userList.innerHTML = selectedUsers.map(user =>
'<li>🟢 ' + user + '</li>'
).join('');
}
// Mock stock updates
function updateStocks() {
const stocks = [
{ symbol: 'AAPL', price: 150.25 },
{ symbol: 'GOOGL', price: 2845.30 },
{ symbol: 'MSFT', price: 305.80 },
{ symbol: 'TSLA', price: 245.60 }
];
const tickerHtml = stocks.map(stock => {
const change = (Math.random() - 0.5) * 10;
const changePercent = (change / stock.price * 100).toFixed(2);
const newPrice = (stock.price + change).toFixed(2);
const changeClass = change > 0 ? 'stock-up' : 'stock-down';
const changeSymbol = change > 0 ? '↑' : '↓';
return `
<div>${stock.symbol}: $${newPrice}
<span class="${changeClass}">${changeSymbol}${changePercent}%</span></div>
`;
}).join('');
document.getElementById('stockTicker').innerHTML =
'<div class="stock-ticker">' + tickerHtml + '</div>';
}
// Mock notifications
function generateNotification() {
const notifications = [
'New user registered',
'Server maintenance scheduled',
'Database backup completed',
'Security update available',
'Performance optimization done'
];
const notification = notifications[Math.floor(Math.random() * notifications.length)];
showNotification(notification);
}
// Document collaboration
function syncDocument() {
const content = document.getElementById('sharedDocument').value;
// Update active users indicator
const activeCount = Math.floor(Math.random() * 3) + 1;
const activeUsersText = activeCount > 0
? `Active editors: ${activeCount} user(s)`
: 'Active editors: None';
document.getElementById('activeUsers').innerHTML =
'<small>' + activeUsersText + '</small>';
}
// Utility functions
function startMockDataStream() {
// Update online users every 5 seconds
setInterval(updateOnlineUsers, 5000);
updateOnlineUsers();
// Update stocks every 2 seconds
stockUpdateInterval = setInterval(updateStocks, 2000);
updateStocks();
// Generate notifications every 10-30 seconds
notificationInterval = setInterval(generateNotification, 10000 + Math.random() * 20000);
}
function stopMockDataStream() {
if (stockUpdateInterval) {
clearInterval(stockUpdateInterval);
}
if (notificationInterval) {
clearInterval(notificationInterval);
}
}
function toggleStockUpdates() {
if (stockUpdateInterval) {
clearInterval(stockUpdateInterval);
stockUpdateInterval = null;
showToast('Stock updates paused', 'warning');
} else {
stockUpdateInterval = setInterval(updateStocks, 2000);
showToast('Stock updates resumed', 'success');
}
}
function sendTestNotification() {
showNotification('This is a test notification from the user!');
}
function showNotification(message) {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.style.display = 'block';
setTimeout(() => {
notification.style.display = 'none';
}, 5000);
}
function showToast(message, type) {
showNotification(message);
}
// Auto-connect on page load
document.addEventListener('DOMContentLoaded', () => {
connectWebSocket();
});
// Simulate WebSocket events
document.body.addEventListener('htmx:wsConnecting', function(event) {
console.log('WebSocket connecting...');
});
document.body.addEventListener('htmx:wsOpen', function(event) {
console.log('WebSocket connected');
});
document.body.addEventListener('htmx:wsClose', function(event) {
console.log('WebSocket disconnected');
});
document.body.addEventListener('htmx:wsMessage', function(event) {
console.log('WebSocket message received:', event.detail);
});
</script>
</body>
</html>
💻 Patterns Avancés HTMX html
🔴 complex
⭐⭐⭐⭐
Patterns avancés HTMX incluant le chargement paresseux, le défilement infini et le téléchargement de fichiers
⏱️ 40 min
🏷️ htmx, advanced, patterns
Prerequisites:
Advanced HTMX knowledge, JavaScript concepts
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTMX Advanced Patterns</title>
<script src="https://unpkg.com/htmx.org@latest"></script>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 1000px;
margin: 0 auto;
padding: 2rem;
line-height: 1.6;
}
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
background: white;
}
.btn {
background: #007bff;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
margin-right: 0.5rem;
}
.btn:hover { background: #0056b3; }
.btn-success { background: #28a745; }
.btn-success:hover { background: #1e7e34; }
.btn-warning { background: #ffc107; color: #000; }
.btn-warning:hover { background: #e0a800; }
.loading {
opacity: 0.7;
transition: opacity 300ms;
}
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.image-card {
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: transform 0.2s;
}
.image-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.image-card img {
width: 100%;
height: 150px;
object-fit: cover;
}
.image-card .caption {
padding: 0.5rem;
background: #f8f9fa;
font-size: 0.875rem;
}
.file-upload-area {
border: 2px dashed #ddd;
border-radius: 8px;
padding: 2rem;
text-align: center;
transition: border-color 0.3s;
cursor: pointer;
}
.file-upload-area.dragover {
border-color: #007bff;
background: #f8f9ff;
}
.progress-bar {
width: 100%;
height: 20px;
background: #e9ecef;
border-radius: 10px;
overflow: hidden;
margin: 1rem 0;
}
.progress-fill {
height: 100%;
background: #007bff;
transition: width 0.3s;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 0.875rem;
}
.autocomplete {
position: relative;
}
.autocomplete-results {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #ddd;
border-top: none;
border-radius: 0 0 4px 4px;
max-height: 200px;
overflow-y: auto;
z-index: 1000;
}
.autocomplete-item {
padding: 0.5rem;
cursor: pointer;
}
.autocomplete-item:hover, .autocomplete-item.selected {
background: #f8f9fa;
}
.debounce-indicator {
font-size: 0.875rem;
color: #6c757d;
margin-left: 0.5rem;
}
.infinite-scroll-container {
height: 400px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
}
.infinite-scroll-item {
padding: 1rem;
border-bottom: 1px solid #eee;
}
.infinite-scroll-item:last-child {
border-bottom: none;
}
.lazy-image {
min-height: 200px;
background: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
color: #6c757d;
}
</style>
</head>
<body>
<h1>HTMX Advanced Patterns</h1>
<!-- Lazy Loading with Intersection Observer -->
<div class="card">
<h2>Lazy Loading with Intersection Observer</h2>
<p>Content is loaded only when it becomes visible in the viewport.</p>
<div id="lazy-content">
<!-- Initially visible content -->
<p>This content is visible immediately.</p>
<!-- Lazy loaded content -->
<div
hx-get="/api/lazy-content"
hx-trigger="revealed"
hx-swap="innerHTML"
hx-indicator=".loading-indicator">
<div class="loading-indicator">
<div class="skeleton" style="height: 100px; margin: 1rem 0; border-radius: 4px;"></div>
</div>
</div>
</div>
</div>
<!-- Infinite Scroll -->
<div class="card">
<h2>Infinite Scroll</h2>
<p>Load more content as you scroll down.</p>
<div
class="infinite-scroll-container"
hx-get="/api/more-items?page=1"
hx-trigger="scroll(bottom)"
hx-swap="innerHTML"
hx-target="#items-container">
<div id="items-container">
<!-- Initial items -->
<div class="infinite-scroll-item">Item 1</div>
<div class="infinite-scroll-item">Item 2</div>
<div class="infinite-scroll-item">Item 3</div>
</div>
<div class="htmx-indicator">
<div style="text-align: center; padding: 1rem;">
<div class="spinner"></div> Loading more items...
</div>
</div>
</div>
</div>
<!-- Image Gallery with Lazy Loading -->
<div class="card">
<h2>Image Gallery with Lazy Loading</h2>
<p>Images are loaded as they come into view.</p>
<div class="image-grid">
<!-- Lazy loaded images -->
<div class="image-card">
<div
hx-get="/api/image/1"
hx-trigger="revealed"
hx-swap="innerHTML">
<div class="lazy-image">Loading image...</div>
</div>
<div class="caption">Mountain Landscape</div>
</div>
<div class="image-card">
<div
hx-get="/api/image/2"
hx-trigger="revealed"
hx-swap="innerHTML">
<div class="lazy-image">Loading image...</div>
</div>
<div class="caption">Ocean Sunset</div>
</div>
<div class="image-card">
<div
hx-get="/api/image/3"
hx-trigger="revealed"
hx-swap="innerHTML">
<div class="lazy-image">Loading image...</div>
</div>
<div class="caption">Forest Path</div>
</div>
<div class="image-card">
<div
hx-get="/api/image/4"
hx-trigger="revealed"
hx-swap="innerHTML">
<div class="lazy-image">Loading image...</div>
</div>
<div class="caption">City Skyline</div>
</div>
</div>
</div>
<!-- Debounced Search -->
<div class="card">
<h2>Debounced Search with Autocomplete</h2>
<p>Search with autocomplete and debounced requests.</p>
<div class="autocomplete">
<input
type="text"
id="searchInput"
placeholder="Search for products..."
hx-get="/api/search?q={value}"
hx-trigger="keyup changed delay:500ms"
hx-target="#search-results"
hx-swap="innerHTML"
class="form-control"
style="padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; width: 100%;">
<span class="debounce-indicator" id="debounceIndicator"></span>
<div id="search-results" class="autocomplete-results"></div>
</div>
</div>
<!-- File Upload with Progress -->
<div class="card">
<h2>File Upload with Progress</h2>
<p>Upload files with progress indicators.</p>
<form
hx-post="/api/upload"
hx-encoding="multipart/form-data"
hx-target="#upload-result"
hx-swap="innerHTML"
hx-on::before-request="showUploadProgress()">
<div
class="file-upload-area"
id="dropZone"
ondrop="handleDrop(event)"
ondragover="handleDragOver(event)"
ondragleave="handleDragLeave(event)">
<input
type="file"
id="fileInput"
name="file"
multiple
onchange="handleFileSelect(event)"
style="display: none;">
<div id="upload-prompt">
<div style="font-size: 3rem; margin-bottom: 1rem;">📁</div>
<p>Drag and drop files here or click to browse</p>
<button type="button" class="btn" onclick="document.getElementById('fileInput').click()">
Choose Files
</button>
</div>
<div id="upload-progress" style="display: none;">
<div class="progress-bar">
<div class="progress-fill" id="progressFill">0%</div>
</div>
<p id="upload-status">Uploading...</p>
</div>
</div>
<button type="submit" class="btn btn-success" style="margin-top: 1rem;">
Upload Files
</button>
</form>
<div id="upload-result" style="margin-top: 1rem;"></div>
</div>
<!-- Request Caching -->
<div class="card">
<h2>Request Caching</h2>
<p>Demonstrates HTMX caching capabilities.</p>
<button
hx-get="/api/cached-data"
hx-trigger="click"
hx-target="#cached-content"
hx-swap="innerHTML"
hx-cache="true">
Load Cached Data
</button>
<button
onclick="clearCache()"
class="btn btn-warning">
Clear Cache
</button>
<div id="cached-content" style="margin-top: 1rem;">
<em>Click to load data (will be cached for subsequent requests)</em>
</div>
<div id="cache-status" style="margin-top: 0.5rem; font-size: 0.875rem; color: #6c757d;"></div>
</div>
<!-- Request Validation -->
<div class="card">
<h2>Client-Side Validation</h2>
<p>Validate input before sending requests.</p>
<form
hx-post="/api/validate-form"
hx-target="#validation-result"
hx-swap="innerHTML"
hx-on::before-request="validateForm(event)">
<div class="form-group">
<label>Email:</label>
<input
type="email"
name="email"
required
class="form-control"
style="padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; width: 100%;">
</div>
<div class="form-group">
<label>Password (min 8 chars):</label>
<input
type="password"
name="password"
minlength="8"
required
class="form-control"
style="padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; width: 100%;">
</div>
<button type="submit" class="btn btn-success">Submit</button>
</form>
<div id="validation-result" style="margin-top: 1rem;"></div>
</div>
<!-- Custom Events -->
<div class="card">
<h2>Custom Events and Triggers</h2>
<p>Using custom events to trigger HTMX requests.</p>
<div id="custom-event-source">
<button onclick="triggerCustomEvent()" class="btn">
Trigger Custom Event
</button>
<div
hx-get="/api/custom-event-data"
hx-trigger="customLoad from:#custom-event-source"
hx-target="#custom-event-content"
hx-swap="innerHTML">
<div id="custom-event-content" style="margin-top: 1rem;">
<em>Waiting for custom event...</em>
</div>
</div>
</div>
</div>
<script>
// Mock API responses
document.addEventListener('htmx:beforeRequest', function(event) {
const url = event.detail.requestConfig.url;
// Mock lazy content
if (url.includes('/api/lazy-content')) {
event.preventDefault();
setTimeout(() => {
document.querySelector('[hx-get*="lazy-content"]').innerHTML =
'<div style="background: #e7f3fe; padding: 1rem; border-radius: 4px; margin-top: 1rem;">' +
'<h3>Lazy Loaded Content! 🎉</h3>' +
'<p>This content was loaded when it came into view.</p>' +
'<p>Current time: ' + new Date().toLocaleTimeString() + '</p>' +
'</div>';
}, 1000);
return;
}
// Mock infinite scroll
if (url.includes('/api/more-items')) {
event.preventDefault();
setTimeout(() => {
const pageMatch = url.match(/page=(\d+)/);
const page = pageMatch ? parseInt(pageMatch[1]) : 1;
if (page < 5) {
const container = document.getElementById('items-container');
for (let i = 1; i <= 3; i++) {
const itemNum = (page - 1) * 3 + i + 3;
const newItem = document.createElement('div');
newItem.className = 'infinite-scroll-item';
newItem.textContent = `Item ${itemNum}`;
container.appendChild(newItem);
}
// Update the trigger for next page
const trigger = document.querySelector('[hx-get*="more-items"]');
trigger.setAttribute('hx-get', `/api/more-items?page=${page + 1}`);
} else {
// No more items
document.querySelector('.htmx-indicator').innerHTML =
'<div style="text-align: center; padding: 1rem; color: #6c757d;">No more items to load</div>';
}
}, 500);
return;
}
// Mock image loading
if (url.includes('/api/image/')) {
event.preventDefault();
setTimeout(() => {
const imageId = url.split('/').pop();
const colors = ['#e3f2fd', '#f3e5f5', '#e8f5e8', '#fff3e0'];
const color = colors[parseInt(imageId) - 1];
event.target.closest('[hx-get*="image"]').innerHTML =
`<img src="https://picsum.photos/200/150?random=${imageId}" alt="Image ${imageId}"> `;
}, 800);
return;
}
// Mock search
if (url.includes('/api/search')) {
event.preventDefault();
// Show debounce indicator
document.getElementById('debounceIndicator').textContent = '⏳ Searching...';
setTimeout(() => {
const searchParams = new URLSearchParams(url.split('?')[1]);
const query = searchParams.get('q');
document.getElementById('debounceIndicator').textContent = '';
if (query && query.length > 0) {
const products = [
'Laptop Pro 15"',
'Wireless Mouse',
'Mechanical Keyboard',
'USB-C Hub',
'Monitor 4K',
'Webcam HD',
'Headphones Pro',
'Phone Stand'
];
const filtered = products.filter(p =>
p.toLowerCase().includes(query.toLowerCase())
);
const resultsHtml = filtered.length > 0
? filtered.map(p => `<div class="autocomplete-item">${p}</div>`).join('')
: '<div class="autocomplete-item">No results found</div>';
document.getElementById('search-results').innerHTML = resultsHtml;
} else {
document.getElementById('search-results').innerHTML = '';
}
}, 300);
return;
}
// Mock file upload
if (url.includes('/api/upload')) {
event.preventDefault();
simulateFileUpload();
return;
}
// Mock cached data
if (url.includes('/api/cached-data')) {
event.preventDefault();
const isCached = htmx.cache ? Object.keys(htmx.cache).includes(url) : false;
setTimeout(() => {
const timestamp = new Date().toLocaleTimeString();
document.getElementById('cached-content').innerHTML =
'<div style="background: #d4edda; padding: 1rem; border-radius: 4px;">' +
'<h3>Cached Data</h3>' +
'<p>Loaded at: ' + timestamp + '</p>' +
'<p>' + (isCached ? '📦 Served from cache' : '🌐 Fresh from server') + '</p>' +
'</div>';
document.getElementById('cache-status').textContent =
isCached ? '✅ This response was cached' : '📦 Response cached for future requests';
}, 500);
return;
}
// Mock custom event data
if (url.includes('/api/custom-event-data')) {
event.preventDefault();
setTimeout(() => {
document.getElementById('custom-event-content').innerHTML =
'<div style="background: #fff3cd; padding: 1rem; border-radius: 4px;">' +
'<h3>Custom Event Triggered! 🎯</h3>' +
'<p>Data loaded via custom event trigger.</p>' +
'<p>Event time: ' + new Date().toLocaleTimeString() + '</p>' +
'</div>';
}, 300);
return;
}
});
// File upload handlers
function handleFileSelect(event) {
const files = event.target.files;
if (files.length > 0) {
updateFileList(files);
}
}
function handleDrop(event) {
event.preventDefault();
event.stopPropagation();
const dropZone = document.getElementById('dropZone');
dropZone.classList.remove('dragover');
const files = event.dataTransfer.files;
if (files.length > 0) {
updateFileList(files);
}
}
function handleDragOver(event) {
event.preventDefault();
document.getElementById('dropZone').classList.add('dragover');
}
function handleDragLeave(event) {
event.preventDefault();
document.getElementById('dropZone').classList.remove('dragover');
}
function updateFileList(files) {
const fileList = Array.from(files).map(f => f.name).join(', ');
document.getElementById('upload-prompt').innerHTML =
'<div style="font-size: 1.5rem; margin-bottom: 1rem;">📄</div>' +
'<p><strong>Selected files:</strong></p>' +
'<p>' + fileList + '</p>';
}
function showUploadProgress() {
document.getElementById('upload-prompt').style.display = 'none';
document.getElementById('upload-progress').style.display = 'block';
}
function simulateFileUpload() {
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 15;
if (progress > 100) progress = 100;
document.getElementById('progressFill').style.width = progress + '%';
document.getElementById('progressFill').textContent = Math.round(progress) + '%';
if (progress >= 100) {
clearInterval(interval);
document.getElementById('upload-status').textContent = 'Upload complete!';
document.getElementById('upload-result').innerHTML =
'<div style="background: #d4edda; padding: 1rem; border-radius: 4px;">' +
'<h4>✅ Upload Successful!</h4>' +
'<p>Files have been uploaded successfully.</p>' +
'</div>';
// Reset after 2 seconds
setTimeout(() => {
document.getElementById('upload-prompt').style.display = 'block';
document.getElementById('upload-progress').style.display = 'none';
document.getElementById('progressFill').style.width = '0%';
document.getElementById('fileInput').value = '';
}, 2000);
}
}, 100);
}
// Validation
function validateForm(event) {
const form = event.target;
const email = form.querySelector('input[name="email"]').value;
const password = form.querySelector('input[name="password"]').value;
if (!email || !password) {
event.preventDefault();
document.getElementById('validation-result').innerHTML =
'<div style="background: #f8d7da; padding: 1rem; border-radius: 4px;">' +
'<p style="color: #721c24;">Please fill in all required fields.</p>' +
'</div>';
return false;
}
if (password.length < 8) {
event.preventDefault();
document.getElementById('validation-result').innerHTML =
'<div style="background: #f8d7da; padding: 1rem; border-radius: 4px;">' +
'<p style="color: #721c24;">Password must be at least 8 characters long.</p>' +
'</div>';
return false;
}
return true;
}
// Custom event
function triggerCustomEvent() {
const source = document.getElementById('custom-event-source');
const event = new CustomEvent('customLoad', {
bubbles: true,
detail: { timestamp: Date.now() }
});
source.dispatchEvent(event);
}
// Cache management
function clearCache() {
if (htmx && htmx.cache) {
htmx.cache = {};
document.getElementById('cached-content').innerHTML =
'<em>Cache cleared. Click to load fresh data.</em>';
document.getElementById('cache-status').textContent = '';
}
}
// Initialize
document.addEventListener('DOMContentLoaded', function() {
// Setup intersection observer for lazy loading
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.dispatchEvent(new Event('revealed'));
observer.unobserve(entry.target);
}
});
}, {
rootMargin: '50px'
});
// Observe lazy loading elements
document.querySelectorAll('[hx-trigger="revealed"]').forEach(el => {
observer.observe(el);
});
});
</script>
</body>
</html>