Răsfoiți Sursa

Composant switch état en VueJs

Sangfroid 4 zile în urmă
părinte
comite
4b48ea3db0

+ 0 - 2
assets/app.js

@@ -5,10 +5,8 @@ import 'bootstrap';
 import '@fortawesome/fontawesome-free/css/all.css';
 import './js/colorModeBootstrap.js'
 import { switchView } from './js/switchViewVanilla.js';
-import { switchEtat } from './js/switchEtat.js';
 import './js/addCollectionVanilla.js'
 
 const sv = switchView("#switchview");
-const se = switchEtat ('[data-fonction="switch"]');
 
 registerVueControllerComponents(import.meta.glob('./vue/controllers/**/*.vue', { eager: true }));

+ 0 - 40
assets/js/switchEtat.js

@@ -1,40 +0,0 @@
-export const switchEtat = (selector) => {
-    const buttons = document.querySelectorAll(selector);
-    /** @type (Element) */
-    for (const button of buttons) {
-        button.addEventListener("click", (event) => {
-            event.preventDefault();
-            reqSwitch(button);
-        })
-    }
-
-    function reqSwitch(button) {
-        const icone = button.querySelector('i');
-        const id = button.dataset.content;
-        const state1 = button.dataset.iconeActif;
-        const state2 = button.dataset.iconeInactif;
-        const path = button.dataset.path;
-
-        const req = fetch(
-            path, {
-                method: "PATCH",
-                body: JSON.stringify({"id_film": id})
-            }
-        );
-
-        req
-            .then((response) => {
-                if (!response.ok) {
-                    throw new Error(`Erreur Http ${response.status}`);
-                }
-                return response.json();
-            })
-            .then((json) => {
-                if (icone.className === state1) {
-                    icone.className = state2;
-                } else {
-                    icone.className = state1;
-                }
-            })
-    }
-}

+ 91 - 0
assets/vue/controllers/SwitchEtat.vue

@@ -0,0 +1,91 @@
+<template>
+    <component
+        :is="tag"
+        :href="tag === 'a' ? '#' : undefined"
+        :type="tag === 'button' ? 'button' : undefined"
+        :class="classe"
+        :title="titre"
+        :data-bs-toggle="tooltip ? 'tooltip' : undefined"
+        :aria-busy="enChargement"
+        @click.prevent="switchEtat"
+    >
+        <i :class="iconeCourante"></i>
+    </component>
+</template>
+
+<script setup>
+import { computed, ref } from 'vue';
+
+const props = defineProps({
+    // Route du contrôleur qui bascule l'état
+    path: { type: String, required: true },
+    // Identifiant du film concerné
+    idFilm: { type: [String, Number], required: true },
+    // Classes Font Awesome de l'icône quand actif / inactif
+    iconeActif: { type: String, required: true },
+    iconeInactif: { type: String, required: true },
+    // État initial (définit quelle icône est affichée au montage)
+    actif: { type: Boolean, default: false },
+    // Texte de l'infobulle décrivant l'action
+    titre: { type: String, default: '' },
+    // Active l'attribut data-bs-toggle="tooltip" de Bootstrap
+    tooltip: { type: Boolean, default: false },
+    // Balise racine : 'a' (par défaut) ou 'button'
+    tag: { type: String, default: 'a' },
+    // Classes CSS additionnelles pour la balise (ex: 'btn text-primary')
+    classe: { type: String, default: '' },
+    // Animation Font Awesome appliquée à l'icône cible pendant la requête
+    // (fa-beat-fade, fa-beat, fa-flip, ... — vide pour désactiver)
+    animationChargement: { type: String, default: 'fa-beat-fade' },
+});
+
+const estActif = ref(props.actif);
+const enChargement = ref(false);
+
+// L'icône cible est celle qui sera affichée si la requête réussit
+const iconeCible = computed(() => (estActif.value ? props.iconeInactif : props.iconeActif));
+
+// Préserve les classes de taille (fa-lg, fa-2xl, ...) pendant le chargement
+const classesTaille = computed(() => {
+    const icone = enChargement.value ? iconeCible.value : (estActif.value ? props.iconeActif : props.iconeInactif);
+    return icone.split(' ').filter((c) => /^fa-(2?xs|sm|lg|2?xl|[0-9]x|fw)$/.test(c));
+});
+
+const iconeCourante = computed(() => {
+    if (enChargement.value) {
+        return [iconeCible.value, props.animationChargement, ...classesTaille.value]
+            .filter(Boolean)
+            .join(' ');
+    }
+    return estActif.value ? props.iconeActif : props.iconeInactif;
+});
+
+async function switchEtat() {
+    if (enChargement.value) {
+        return;
+    }
+    enChargement.value = true;
+    try {
+        const response = await fetch(props.path, {
+            method: 'PATCH',
+            body: JSON.stringify({ id_film: props.idFilm }),
+        });
+        if (!response.ok) {
+            throw new Error(`Erreur Http ${response.status}`);
+        }
+        await response.json();
+        estActif.value = !estActif.value;
+    } catch (error) {
+        console.error(error);
+    } finally {
+        enChargement.value = false;
+    }
+}
+</script>
+
+<style scoped>
+i {
+    display: inline-block;
+    transition: opacity 0.3s ease;
+}
+</style>

+ 17 - 30
templates/videotheque/liste_tableaux.html.twig

@@ -34,38 +34,25 @@
 			<tr data-auteur="{{ film.authered.nomComplet }}">
 				{% if is_granted('IS_AUTHENTICATED_REMEMBERED') %}
 					<td>
-						{%  if app.user.wantToSee(film) %}
-							{% set follow_icone = "fa-solid fa-bookmark fa-lg" %}
-							{% set follow_texte = "Supprimer ce film de votre liste à voir" %}
-						{% else %}
-							{% set follow_icone = "fa-regular fa-bookmark text-secondary fa-lg" %}
-							{% set follow_texte = "Ajouter ce film à votre liste à voir" %}
-						{% endif %}
-						<a href="#" type="button"
-									data-fonction="switch"
-									data-icone-actif="fa-solid fa-bookmark fa-lg"
-									data-icone-inactif="fa-regular fa-bookmark text-secondary fa-lg"
-									data-path="{{ path('maliste_modifier_a_voir') }}"
-									data-bs-toggle="tooltip"
-									title="{{ follow_texte }}"
-									data-content="{{ film.id }}">
-							<i class="{{ follow_icone }}"></i>
-						</a>
+						{% set follow_texte = app.user.wantToSee(film) ? "Supprimer ce film de votre liste à voir" : "Ajouter ce film à votre liste à voir" %}
+						<span {{ vue_component('SwitchEtat', {
+							path: path('maliste_modifier_a_voir'),
+							idFilm: film.id,
+							iconeActif: 'fa-solid fa-bookmark fa-lg',
+							iconeInactif: 'fa-regular fa-bookmark text-secondary fa-lg',
+							actif: app.user.wantToSee(film),
+							titre: follow_texte,
+							tooltip: true
+						}) }}></span>
 					</td>
 					<td>
-						{%  if app.user.haveSeen(film) %}
-							{% set vu_icone = '<i class="fa-solid fa-eye fa-lg text-success"></i>' %}
-						{% else %}
-							{% set vu_icone = '<i class="fa-solid fa-eye-slash fa-lg text-secondary"></i>' %}
-						{% endif %}				
-						<a href="#"	type="button"
-									data-fonction="switch"
-									data-path="{{ path('maliste_modifier_vus') }}"
-									data-content="{{ film.id }}"
-									data-icone-actif = "fa-solid fa-eye fa-lg text-success"
-									data-icone-inactif = "fa-solid fa-eye-slash fa-lg text-secondary">
-							{{ vu_icone | raw }}
-						</a>
+						<span {{ vue_component('SwitchEtat', {
+							path: path('maliste_modifier_vus'),
+							idFilm: film.id,
+							iconeActif: 'fa-solid fa-eye fa-lg text-success',
+							iconeInactif: 'fa-solid fa-eye-slash fa-lg text-secondary',
+							actif: app.user.haveSeen(film)
+						}) }}></span>
 					</td>
 					<td>
 						{%  if film.authered is defined %}

+ 21 - 36
templates/videotheque/liste_vignettes.html.twig

@@ -24,42 +24,27 @@
 				</h4>
 				<div class="d-flex">
 					{% if is_granted('IS_AUTHENTICATED_REMEMBERED') %}
-						<div class="me-3">
-							{%  if app.user.haveSeen(film) %}
-								{% set vu_icone = '<i class="fa-solid fa-eye fa-lg text-success"></i>' %}
-							{% else %}
-								{% set vu_icone = '<i class="fa-solid fa-eye-slash fa-lg text-secondary"></i>' %}
-							{% endif %}				
-							<button     type="button"
-										class="btn"
-										data-fonction="switch"
-										data-path="{{ path('maliste_modifier_vus') }}"
-										data-content="{{ film.id }}"
-										data-icone-actif = "fa-solid fa-eye fa-lg text-success"
-										data-icone-inactif = "fa-solid fa-eye-slash fa-lg text-secondary">
-								{{ vu_icone | raw }}
-							</button>
-						</div>
-						<div class="me-3">
-							{%  if app.user.wantToSee(film) %}
-								{% set follow_icone = "fa-solid fa-bookmark fa-lg" %}
-								{% set follow_texte = "Supprimer ce film de votre liste à voir" %}
-							{% else %}
-								{% set follow_icone = "fa-regular fa-bookmark text-secondary fa-lg" %}
-								{% set follow_texte = "Ajouter ce film à votre liste à voir" %}
-							{% endif %}
-							<button		type="button"
-										class="btn text-primary"
-										data-fonction="switch"
-										data-icone-actif="fa-solid fa-bookmark fa-lg"
-										data-icone-inactif="fa-regular fa-bookmark text-secondary fa-lg"
-										data-path="{{ path('maliste_modifier_a_voir') }}"
-										data-bs-toggle="tooltip"
-										title="{{ follow_texte }}"
-										data-content="{{ film.id }}">
-								<i class="{{ follow_icone }}"></i>
-							</button>
-						</div>
+						<div class="me-3" {{ vue_component('SwitchEtat', {
+							path: path('maliste_modifier_vus'),
+							idFilm: film.id,
+							iconeActif: 'fa-solid fa-eye fa-lg text-success',
+							iconeInactif: 'fa-solid fa-eye-slash fa-lg text-secondary',
+							actif: app.user.haveSeen(film),
+							tag: 'button',
+							classe: 'btn'
+						}) }}></div>
+						{% set follow_texte = app.user.wantToSee(film) ? "Supprimer ce film de votre liste à voir" : "Ajouter ce film à votre liste à voir" %}
+						<div class="me-3" {{ vue_component('SwitchEtat', {
+							path: path('maliste_modifier_a_voir'),
+							idFilm: film.id,
+							iconeActif: 'fa-solid fa-bookmark fa-lg',
+							iconeInactif: 'fa-regular fa-bookmark text-secondary fa-lg',
+							actif: app.user.wantToSee(film),
+							titre: follow_texte,
+							tooltip: true,
+							tag: 'button',
+							classe: 'btn text-primary'
+						}) }}></div>
 					{% endif %}
 					<div class="">
 						<button class="btn text-primary" data-bs-toggle="collapse" data-bs-target="#collapse-id-{{ film.id }}"><i data-collapser-target="button" class="fa-solid fa-chevron-circle-right fa-lg"></i></button>

+ 17 - 29
templates/videotheque/voirfilm.html.twig

@@ -3,35 +3,23 @@
 {% block title %}{{ parent() }} - Fiche film - {{ film.titre }}{% endblock %}
 {% block titre %}
     {% if is_granted('IS_AUTHENTICATED_REMEMBERED') %}
-        {%  if film.usersWantToView.contains(app.user) %}
-            {% set follow_icone = "fa-solid fa-bookmark" %}
-            {% set follow_texte = "Supprimer ce film de votre liste à voir" %}
-        {% else %}
-            {% set follow_icone = "fa-regular fa-bookmark text-secondary" %}
-            {% set follow_texte = "Ajouter ce film à votre liste à voir" %}
-        {% endif %}
-        <a href="#"
-            type="button"
-            data-fonction="switch"
-            data-icone-actif="fa fa-bookmark"
-            data-icone-inactif="fa-regular fa-bookmark text-secondary"
-            data-path="{{ path('maliste_modifier_a_voir') }}"
-            data-bs-toggle="tooltip"
-            title="{{ follow_texte }}"
-            data-content="{{ film.id }}">
-            <i class="{{ follow_icone }}"></i></a>
-        {% if film.usersWhoSeen.contains(app.user) %}
-            {% set vu_icone = '<i class="fa-solid fa-eye text-success"></i>' %}
-        {% else %}
-            {% set vu_icone = '<i class="fa-solid fa-eye-slash text-secondary"></i>' %}
-        {% endif %}				
-        <a href="#" type="button"
-                    data-fonction="switch"
-                    data-path="{{ path('maliste_modifier_vus') }}"
-                    data-content="{{ film.id }}"
-                    data-icone-actif = "fa-solid fa-eye text-success"
-                    data-icone-inactif = "fa-solid fa-eye-slash text-secondary">
-            {{ vu_icone | raw }}</a>
+        {% set follow_texte = film.usersWantToView.contains(app.user) ? "Supprimer ce film de votre liste à voir" : "Ajouter ce film à votre liste à voir" %}
+        <span {{ vue_component('SwitchEtat', {
+            path: path('maliste_modifier_a_voir'),
+            idFilm: film.id,
+            iconeActif: 'fa-solid fa-bookmark',
+            iconeInactif: 'fa-regular fa-bookmark text-secondary',
+            actif: film.usersWantToView.contains(app.user),
+            titre: follow_texte,
+            tooltip: true
+        }) }}></span>
+        <span {{ vue_component('SwitchEtat', {
+            path: path('maliste_modifier_vus'),
+            idFilm: film.id,
+            iconeActif: 'fa-solid fa-eye text-success',
+            iconeInactif: 'fa-solid fa-eye-slash text-secondary',
+            actif: film.usersWhoSeen.contains(app.user)
+        }) }}></span>
     {% endif %}
     {{ film.titre }}