Snippets Table Dynamic – Bootstrap 5

🏷️ Extraits & Composants HTML 📅 07/04/2026 14:00:00 👤 Mezgani said
Bootstrap 5 Table Dynamique Data Table Table Responsive Snippets Html

Exemples de tables dynamiques avec fonctionnalités avancées : tri, recherche, pagination, en Bootstrap 5.

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8" />
    <meta name="copyright" content="MEZGANI Said" />
    <meta name="author" content="AngularForAll" />
    <meta name="robots" content="noindex, nofollow" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Snippets Table Dynamic 01 | AngularForAll</title>
<!-- Bootstrap 5 CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- Bootstrap Icons -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
    <!-- Google Fonts -->
    <link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,300;14..32,400;14..32,500;14..32,600;14..32,700&display=swap" rel="stylesheet">
    <!-- SortableJS pour le drag & drop -->
    <script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
    
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Inter', sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 2rem;
        }

        .container-custom {
            max-width: 1400px;
            margin: 0 auto;
        }

        /* Card principale */
        .main-card {
            background: white;
            border-radius: 20px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
            overflow: hidden;
            animation: fadeInUp 0.6s ease;
        }

        @keyframes fadeInUp {
            from {
                opacity: 0;
                transform: translateY(30px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }

        /* Header */
        .card-header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 2rem;
            border: none;
        }

        .card-header h2 {
            font-weight: 700;
            margin-bottom: 0.5rem;
        }

        /* Formulaire d'ajout */
        .add-form {
            background: #f8f9fa;
            padding: 1.5rem;
            border-radius: 15px;
            margin-bottom: 1.5rem;
        }

        /* Table styles */
        .table-container {
            overflow-x: auto;
            border-radius: 15px;
        }

        .table {
            margin-bottom: 0;
            min-width: 800px;
        }

        .table thead th {
            background: #2c3e50;
            color: white;
            font-weight: 600;
            padding: 1rem;
            border: none;
            position: sticky;
            top: 0;
            z-index: 10;
        }

        .table tbody tr {
            cursor: grab;
            transition: all 0.3s ease;
            background: white;
        }

        .table tbody tr:active {
            cursor: grabbing;
        }

        .table tbody tr:hover {
            background: #f8f9fa;
            transform: scale(1.01);
            box-shadow: 0 5px 15px rgba(0,0,0,0.1);
        }

        .table tbody tr.dragging {
            opacity: 0.5;
            cursor: grabbing;
        }

        .table tbody td {
            padding: 1rem;
            vertical-align: middle;
            border-bottom: 1px solid #eef2f6;
        }

        /* Handle de drag */
        .drag-handle {
            cursor: grab;
            font-size: 1.2rem;
            color: #95a5a6;
            transition: all 0.3s;
        }

        .drag-handle:active {
            cursor: grabbing;
        }

        .drag-handle:hover {
            color: #667eea;
        }

        /* Badges de statut */
        .badge-status {
            padding: 0.4rem 0.8rem;
            border-radius: 20px;
            font-weight: 500;
            font-size: 0.75rem;
        }

        .badge-active {
            background: #d4edda;
            color: #155724;
        }

        .badge-inactive {
            background: #f8d7da;
            color: #721c24;
        }

        .badge-pending {
            background: #fff3cd;
            color: #856404;
        }

        /* Boutons d'action */
        .action-btn {
            padding: 0.3rem 0.6rem;
            margin: 0 0.2rem;
            border-radius: 8px;
            transition: all 0.3s;
        }

        .action-btn:hover {
            transform: translateY(-2px);
        }

        /* Animation pour les nouvelles lignes */
        @keyframes highlight {
            0% {
                background-color: #fff3cd;
            }
            100% {
                background-color: transparent;
            }
        }

        .row-highlight {
            animation: highlight 1.5s ease;
        }

        /* Modal styles */
        .modal-content {
            border-radius: 20px;
        }

        /* Statistiques */
        .stats-card {
            background: white;
            border-radius: 15px;
            padding: 1rem;
            text-align: center;
            box-shadow: 0 2px 10px rgba(0,0,0,0.05);
        }

        /* Responsive */
        @media (max-width: 768px) {
            body {
                padding: 1rem;
            }
            
            .card-header {
                padding: 1.5rem;
            }
            
            .add-form {
                padding: 1rem;
            }
        }

        /* Scrollbar personnalisée */
        ::-webkit-scrollbar {
            width: 8px;
            height: 8px;
        }

        ::-webkit-scrollbar-track {
            background: #f1f1f1;
        }

        ::-webkit-scrollbar-thumb {
            background: #888;
            border-radius: 4px;
        }

        ::-webkit-scrollbar-thumb:hover {
            background: #555;
        }
    </style>
</head>
<body>
    <div class="container-custom">
        <div class="main-card">
            <!-- Header -->
            <div class="card-header">
                <div class="d-flex justify-content-between align-items-center flex-wrap gap-3">
                    <div>
                        <h2>
                            <i class="bi bi-table"></i> 
                            Table Dynamique avec Drag & Drop
                        </h2>
                        <p class="mb-0 opacity-75">
                            Glissez-déposez les lignes pour les réorganiser | Cliquez sur l'icône <i class="bi bi-grip-vertical"></i> pour déplacer
                        </p>
                    </div>
                    <button class="btn btn-light" onclick="exportToCSV()">
                        <i class="bi bi-download"></i> Exporter CSV
                    </button>
                </div>
            </div>

            <div class="p-4">
                <!-- Statistiques -->
                <div class="row g-3 mb-4">
                    <div class="col-md-3 col-6">
                        <div class="stats-card">
                            <i class="bi bi-people fs-2 text-primary"></i>
                            <h3 class="mb-0 mt-2" id="totalCount">0</h3>
                            <small class="text-muted">Total Lignes</small>
                        </div>
                    </div>
                    <div class="col-md-3 col-6">
                        <div class="stats-card">
                            <i class="bi bi-check-circle fs-2 text-success"></i>
                            <h3 class="mb-0 mt-2" id="activeCount">0</h3>
                            <small class="text-muted">Actifs</small>
                        </div>
                    </div>
                    <div class="col-md-3 col-6">
                        <div class="stats-card">
                            <i class="bi bi-clock-history fs-2 text-warning"></i>
                            <h3 class="mb-0 mt-2" id="pendingCount">0</h3>
                            <small class="text-muted">En attente</small>
                        </div>
                    </div>
                    <div class="col-md-3 col-6">
                        <div class="stats-card">
                            <i class="bi bi-building fs-2 text-info"></i>
                            <h3 class="mb-0 mt-2" id="departmentCount">0</h3>
                            <small class="text-muted">Départements</small>
                        </div>
                    </div>
                </div>

                <!-- Formulaire d'ajout -->
                <div class="add-form">
                    <h5 class="mb-3">
                        <i class="bi bi-plus-circle"></i> 
                        Ajouter une nouvelle ligne
                    </h5>
                    <div class="row g-3">
                        <div class="col-md-3">
                            <input type="text" class="form-control" id="inputName" placeholder="Nom complet">
                        </div>
                        <div class="col-md-2">
                            <input type="email" class="form-control" id="inputEmail" placeholder="Email">
                        </div>
                        <div class="col-md-2">
                            <select class="form-select" id="inputDepartment">
                                <option value="IT">IT</option>
                                <option value="Marketing">Marketing</option>
                                <option value="Ventes">Ventes</option>
                                <option value="RH">RH</option>
                                <option value="Finance">Finance</option>
                            </select>
                        </div>
                        <div class="col-md-2">
                            <select class="form-select" id="inputStatus">
                                <option value="active">Actif</option>
                                <option value="inactive">Inactif</option>
                                <option value="pending">En attente</option>
                            </select>
                        </div>
                        <div class="col-md-3">
                            <button class="btn btn-primary w-100" onclick="addRow()">
                                <i class="bi bi-plus-lg"></i> Ajouter
                            </button>
                        </div>
                    </div>
                </div>

                <!-- Table avec Drag & Drop -->
                <div class="table-container">
                    <table class="table table-hover" id="dataTable">
                        <thead>
                            <tr>
                                <th style="width: 50px">
                                    <i class="bi bi-grip-vertical"></i>
                                </th>
                                <th>#</th>
                                <th>Nom</th>
                                <th>Email</th>
                                <th>Département</th>
                                <th>Statut</th>
                                <th>Date d'ajout</th>
                                <th style="width: 100px">Actions</th>
                            </tr>
                        </thead>
                        <tbody id="tableBody">
                            <!-- Les lignes seront ajoutées dynamiquement -->
                        </tbody>
                    </table>
                </div>

                <!-- Message si aucune donnée -->
                <div id="emptyMessage" class="text-center py-5 text-muted" style="display: none;">
                    <i class="bi bi-inbox fs-1"></i>
                    <p class="mt-2">Aucune donnée. Cliquez sur "Ajouter" pour commencer.</p>
                </div>
            </div>
        </div>
    </div>

    <!-- Modal de confirmation pour la suppression -->
    <div class="modal fade" id="deleteModal" tabindex="-1">
        <div class="modal-dialog modal-dialog-centered">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">Confirmer la suppression</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body">
                    Êtes-vous sûr de vouloir supprimer cette ligne ?
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
                    <button type="button" class="btn btn-danger" id="confirmDeleteBtn">Supprimer</button>
                </div>
            </div>
        </div>
    </div>

    <!-- Bootstrap JS -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
    
    <script>
        // Données initiales
        let rowId = 4;
        let deleteTargetId = null;
        
        let data = [
            { id: 1, name: "Jean Dupont", email: "jean@example.com", department: "IT", status: "active", date: "2024-01-15" },
            { id: 2, name: "Marie Martin", email: "marie@example.com", department: "Marketing", status: "active", date: "2024-01-16" },
            { id: 3, name: "Pierre Durand", email: "pierre@example.com", department: "Ventes", status: "pending", date: "2024-01-17" }
        ];
        
        // Initialisation
        let sortable = null;
        
        function init() {
            renderTable();
            initDragAndDrop();
            updateStats();
        }
        
        // Rendre la table
        function renderTable() {
            const tbody = document.getElementById('tableBody');
            const emptyMessage = document.getElementById('emptyMessage');
            
            if (data.length === 0) {
                tbody.innerHTML = '';
                emptyMessage.style.display = 'block';
                return;
            }
            
            emptyMessage.style.display = 'none';
            
            tbody.innerHTML = data.map((item, index) => `
                <tr data-id="${item.id}">
                    <td class="drag-handle-cell">
                        <i class="bi bi-grip-vertical drag-handle"></i>
                    </td>
                    <td>${index + 1}</td>
                    <td>
                        <strong>${escapeHtml(item.name)}</strong>
                    </td>
                    <td>${escapeHtml(item.email)}</td>
                    <td>
                        <span class="badge bg-secondary">${escapeHtml(item.department)}</span>
                    </td>
                    <td>
                        <span class="badge-status ${getStatusClass(item.status)}">
                            ${getStatusText(item.status)}
                        </span>
                    </td>
                    <td>${formatDate(item.date)}</td>
                    <td>
                        <button class="btn btn-sm btn-outline-primary action-btn" onclick="editRow(${item.id})" title="Modifier">
                            <i class="bi bi-pencil"></i>
                        </button>
                        <button class="btn btn-sm btn-outline-danger action-btn" onclick="confirmDelete(${item.id})" title="Supprimer">
                            <i class="bi bi-trash"></i>
                        </button>
                    </td>
                </tr>
            `).join('');
            
            updateRowNumbers();
        }
        
        // Mettre à jour les numéros de ligne
        function updateRowNumbers() {
            const rows = document.querySelectorAll('#tableBody tr');
            rows.forEach((row, index) => {
                row.cells[1].textContent = index + 1;
            });
        }
        
        // Initialiser le Drag & Drop
        function initDragAndDrop() {
            const tbody = document.getElementById('tableBody');
            
            if (sortable) {
                sortable.destroy();
            }
            
            sortable = new Sortable(tbody, {
                handle: '.drag-handle',
                animation: 300,
                ghostClass: 'dragging',
                onEnd: function() {
                    // Mettre à jour l'ordre des données
                    const newOrder = [];
                    const rows = document.querySelectorAll('#tableBody tr');
                    rows.forEach(row => {
                        const id = parseInt(row.dataset.id);
                        const item = data.find(d => d.id === id);
                        if (item) newOrder.push(item);
                    });
                    data = newOrder;
                    updateRowNumbers();
                    showToast('Ordre mis à jour', 'success');
                }
            });
        }
        
        // Ajouter une ligne
        function addRow() {
            const name = document.getElementById('inputName').value.trim();
            const email = document.getElementById('inputEmail').value.trim();
            const department = document.getElementById('inputDepartment').value;
            const status = document.getElementById('inputStatus').value;
            
            if (!name || !email) {
                showToast('Veuillez remplir tous les champs', 'danger');
                return;
            }
            
            if (!isValidEmail(email)) {
                showToast('Email invalide', 'danger');
                return;
            }
            
            const newItem = {
                id: ++rowId,
                name: name,
                email: email,
                department: department,
                status: status,
                date: new Date().toISOString().split('T')[0]
            };
            
            data.push(newItem);
            renderTable();
            initDragAndDrop();
            updateStats();
            
            // Réinitialiser les champs
            document.getElementById('inputName').value = '';
            document.getElementById('inputEmail').value = '';
            
            // Mettre en surbrillance la nouvelle ligne
            setTimeout(() => {
                const newRow = document.querySelector(`#tableBody tr[data-id="${newItem.id}"]`);
                if (newRow) newRow.classList.add('row-highlight');
            }, 100);
            
            showToast('Ligne ajoutée avec succès', 'success');
        }
        
        // Modifier une ligne
        function editRow(id) {
            const item = data.find(d => d.id === id);
            if (!item) return;
            
            const newName = prompt('Nouveau nom:', item.name);
            if (newName && newName.trim()) {
                item.name = newName.trim();
            }
            
            const newEmail = prompt('Nouvel email:', item.email);
            if (newEmail && isValidEmail(newEmail)) {
                item.email = newEmail;
            }
            
            const newDepartment = prompt('Nouveau département (IT, Marketing, Ventes, RH, Finance):', item.department);
            if (newDepartment && ['IT', 'Marketing', 'Ventes', 'RH', 'Finance'].includes(newDepartment)) {
                item.department = newDepartment;
            }
            
            renderTable();
            initDragAndDrop();
            updateStats();
            showToast('Ligne modifiée', 'success');
        }
        
        // Confirmer la suppression
        function confirmDelete(id) {
            deleteTargetId = id;
            const modal = new bootstrap.Modal(document.getElementById('deleteModal'));
            modal.show();
        }
        
        // Supprimer une ligne
        function deleteRow() {
            if (deleteTargetId) {
                const index = data.findIndex(d => d.id === deleteTargetId);
                if (index !== -1) {
                    data.splice(index, 1);
                    renderTable();
                    initDragAndDrop();
                    updateStats();
                    showToast('Ligne supprimée', 'success');
                }
                deleteTargetId = null;
            }
        }
        
        // Mettre à jour les statistiques
        function updateStats() {
            document.getElementById('totalCount').textContent = data.length;
            document.getElementById('activeCount').textContent = data.filter(d => d.status === 'active').length;
            document.getElementById('pendingCount').textContent = data.filter(d => d.status === 'pending').length;
            
            const uniqueDepts = new Set(data.map(d => d.department));
            document.getElementById('departmentCount').textContent = uniqueDepts.size;
        }
        
        // Exporter en CSV
        function exportToCSV() {
            if (data.length === 0) {
                showToast('Aucune donnée à exporter', 'warning');
                return;
            }
            
            const headers = ['Nom', 'Email', 'Département', 'Statut', 'Date'];
            const rows = data.map(item => [
                item.name,
                item.email,
                item.department,
                getStatusText(item.status),
                item.date
            ]);
            
            const csvContent = [headers, ...rows]
                .map(row => row.map(cell => `"${cell}"`).join(','))
                .join('\n');
            
            const blob = new Blob(["\uFEFF" + csvContent], { type: 'text/csv;charset=utf-8;' });
            const link = document.createElement('a');
            const url = URL.createObjectURL(blob);
            link.href = url;
            link.setAttribute('download', 'table_data.csv');
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            URL.revokeObjectURL(url);
            
            showToast('Export CSV réussi', 'success');
        }
        
        // Utilitaires
        function getStatusClass(status) {
            switch(status) {
                case 'active': return 'badge-active';
                case 'inactive': return 'badge-inactive';
                case 'pending': return 'badge-pending';
                default: return 'badge-secondary';
            }
        }
        
        function getStatusText(status) {
            switch(status) {
                case 'active': return 'Actif';
                case 'inactive': return 'Inactif';
                case 'pending': return 'En attente';
                default: return status;
            }
        }
        
        function formatDate(dateString) {
            const date = new Date(dateString);
            return date.toLocaleDateString('fr-FR');
        }
        
        function isValidEmail(email) {
            const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
            return re.test(email);
        }
        
        function escapeHtml(str) {
            if (!str) return '';
            return str
                .replace(/&/g, '&amp;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;')
                .replace(/"/g, '&quot;')
                .replace(/'/g, '&#39;');
        }
        
        function showToast(message, type = 'success') {
            // Créer un toast temporaire
            const toast = document.createElement('div');
            toast.className = `toast-notification alert alert-${type}`;
            toast.innerHTML = `
                <i class="bi bi-${type === 'success' ? 'check-circle' : type === 'danger' ? 'exclamation-triangle' : 'info-circle'}"></i>
                ${message}
            `;
            toast.style.cssText = `
                position: fixed;
                bottom: 20px;
                right: 20px;
                z-index: 9999;
                animation: slideIn 0.3s ease;
                background: ${type === 'success' ? '#28a745' : type === 'danger' ? '#dc3545' : '#ffc107'};
                color: white;
                padding: 1rem 1.5rem;
                border-radius: 12px;
                box-shadow: 0 5px 20px rgba(0,0,0,0.2);
            `;
            
            document.body.appendChild(toast);
            
            setTimeout(() => {
                toast.style.animation = 'slideOut 0.3s ease';
                setTimeout(() => toast.remove(), 300);
            }, 3000);
        }
        
        // Initialiser
        document.addEventListener('DOMContentLoaded', () => {
            init();
            document.getElementById('confirmDeleteBtn').addEventListener('click', () => {
                deleteRow();
                bootstrap.Modal.getInstance(document.getElementById('deleteModal')).hide();
            });
        });
        
        // Styles d'animation supplémentaires
        const style = document.createElement('style');
        style.textContent = `
            @keyframes slideIn {
                from {
                    transform: translateX(100%);
                    opacity: 0;
                }
                to {
                    transform: translateX(0);
                    opacity: 1;
                }
            }
            @keyframes slideOut {
                from {
                    transform: translateX(0);
                    opacity: 1;
                }
                to {
                    transform: translateX(100%);
                    opacity: 0;
                }
            }
        `;
        document.head.appendChild(style);
    </script>
</body>
</html>

Ouvrir cet aperçu dans un nouvel onglet du navigateur

🔗 Ouvrir dans le navigateur