diff --git a/js/Client/axios.js b/js/Client/axios.js new file mode 100644 index 0000000000000000000000000000000000000000..61f37e7ffb10ca4fe39ccfd5614cec12e6790185 --- /dev/null +++ b/js/Client/axios.js @@ -0,0 +1,77 @@ +import axios from 'axios'; +import flashMessenger from './flashMessenger'; + +// uvAxios est un clone du axops standard +let uvAxios = { ...axios}; + +/* Tunning d'Axios pour gérer l'interconnexion avec le serveur avec gestion des toasts */ + +// Permet d'afficher une animation de chargement dans un popover si l'objet submitter a été transmis +uvAxios.interceptors.request.use(config => { + if (config.submitter) { + let msg = config.msg ? config.msg : 'Action en cours'; + if (config.popover != undefined) { + config.popover.dispose(); + } + config.popover = new bootstrap.Popover(config.submitter, { + content: "<div class=\"spinner-border text-primary\" role=\"status\">\n" + + " <span class=\"visually-hidden\">Loading...</span>\n" + + "</div> " + msg, + html: true, + trigger: 'focus' + }); + config.popover.show(); + } + return config; +}); + +// On capte la réponse pour afficher le résultat si on avait un submitter, sinon on affiche des toasts +uvAxios.interceptors.response.use(response => { + response.messages = response.data.messages; + response.data = response.data.data; + response.hasErrors = response.messages && response.messages.error && response.messages.error.length > 0 ? true : false; + + if (response.config.popover) { + var popover = response.config.popover; + + let content = ''; + for (ns in response.messages) { + for (mid in response.messages[ns]) { + content += '<div class="alert fade show alert-' + (ns == 'error' ? 'danger' : ns) + '" role="alert">' + response.messages[ns][mid] + '</div>'; + } + } + + // S'il y a un truc à afficher + if (content) { + popover._config.content = content; + popover.setContent(); + setTimeout(() => { + popover.dispose(); + }, 5000) + } else { + // la popover est masquée si tout est fini + popover.dispose(); + } + } + if (response.messages) { + flashMessenger.toasts(response.messages); + } + + return response; +}, (error) => { + let message = error.response.data; + + if (error.response.status == 403){ + message = '<h4>403 - Accès interdit</h4><br />Vous n\'êtes pas autorisé(e) à faire cette action.'; + }else{ + message = error.response.data; + } + + flashMessenger.toast(message, 'error'); +}); + +uvAxios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; + +export default { + uvAxios +} \ No newline at end of file diff --git a/js/Client/flashMessenger.js b/js/Client/flashMessenger.js index 049dc592521dc3ccac408d0789040fed42ffcc94..69571935018c60df50d637ebd486333bf7a3664c 100644 --- a/js/Client/flashMessenger.js +++ b/js/Client/flashMessenger.js @@ -1,5 +1,4 @@ -function toasts(messages) -{ +function toasts(messages) { for (let s in messages) { for (let m in messages[s]) { toast(messages[s][m], s); @@ -8,6 +7,50 @@ function toasts(messages) } +function toastContainer() { + let toastContainer = document.getElementById('unicaen-vue-toast-container'); + if (!toastContainer) { + toastContainer = document.createElement('div'); + toastContainer.id = 'unicaen-vue-toast-container'; + toastContainer.classList.add('toast-container', 'position-fixed', 'top-0', 'end-0', 'p-3'); + document.body.appendChild(toastContainer); + } + return toastContainer; +} + + +function prepareMessage(message) { + message = removeAlert(message); + message = removeIcon(message); + + return message; +} + + +function removeAlert(message) { + const el = document.createElement("div"); + el.innerHTML = message; + + // Si on trouve une alert, alors on la retire pour éviter de faire double affichage + const alert = el.querySelector('.alert'); + if (alert) { + return alert.innerHTML; + } else { + return el.innerHTML; + } +} + + +function removeIcon(message) { + const el = document.createElement("div"); + el.innerHTML = message; + + // on masque l'icône /!\ qui fait doublon si on en trouve + const fasIcons = el.querySelectorAll('i.fas'); + fasIcons.forEach(icon => icon.style.display = "none"); + + return el.innerHTML; +} function toast(message, severity) { @@ -24,41 +67,57 @@ function toast(message, severity) { error: 'exclamation-triangle' }; - let toastContainer = document.getElementById('unicaen-vue-toast-container'); - if (!toastContainer) { - toastContainer = document.createElement('div'); - toastContainer.id = 'unicaen-vue-toast-container'; - toastContainer.classList.add('toast-container', 'position-fixed', 'top-0', 'end-0', 'p-3'); - document.body.appendChild(toastContainer); - } + // Crée le bouton de fermeture + const closeButton = document.createElement('button'); + closeButton.classList.add('btn-close', 'btn-close-white', 'h5'); + closeButton.style.float = 'right'; + closeButton.setAttribute('data-bs-dismiss', 'toast'); + closeButton.setAttribute('aria-label', 'Close'); + + // Crée l'icône + const icon = document.createElement('i'); + icon.classList.add('icon', 'fas', `fa-${iconClasses[severity]}`); + icon.style.float = 'left'; + icon.style.fontSize = '26pt'; + icon.style.paddingLeft = '.4rem'; + icon.style.marginTop = '.4rem'; + icon.style.paddingRight = '1rem'; + + // Crée le corps du toast + const body = document.createElement('div'); + body.classList.add('toast-body'); + body.innerHTML = prepareMessage(message); + + // Crée le contenu du toast en ajoutant les éléments créés précédemment + const content = document.createElement('div'); + content.appendChild(closeButton); + content.appendChild(icon); + content.appendChild(body); // Création de l'élément HTML pour le toast const toast = document.createElement('div'); - toast.classList.add('toast', 'show', 'text-white', bgClasses[severity] ? bgClasses[severity] : 'bg-secondary'); + toast.classList.add('toast', 'text-white', bgClasses[severity] ? bgClasses[severity] : 'bg-secondary'); toast.setAttribute('role', 'alert'); toast.setAttribute('aria-live', 'assertive'); toast.setAttribute('aria-atomic', 'true'); - if (severity === 'error' && message.length > 500){ + // On élargit le toast pour ahhicher tout ça... + if (severity === 'error' && message.length > 500) { toast.setAttribute('style', 'width:700px'); } - - const toastContent = - '<button class="btn-close btn-close-white h5" style="float:right" data-bs-dismiss="toast" aria-label="Close"></button>' + - '<i class="icon fas fa-' + iconClasses[severity] + '" style="float: left;font-size: 26pt;padding-left: .4rem;margin-top:.4rem;padding-right: 1rem;"></i>' + - '<div class="toast-body">' + message + ' </div>'; - - toast.innerHTML = toastContent; + toast.appendChild(content); // Ajout du toast à l'élément du conteneur de toasts - toastContainer.appendChild(toast); + toastContainer().appendChild(toast); - // Masquage du toast si ce n'est pas une erreur - if (severity !== 'error') { - setTimeout(() => { - toast.classList.remove('show'); - }, 5000); - } + // Création et affichage du toast avec bootstrap + const options = { + animation: true, + delay: 5000, + autohide: severity !== 'error' + }; + let bsToast = new bootstrap.Toast(toast, options); + bsToast.show(); } diff --git a/js/Client/unicaenVue.js b/js/Client/unicaenVue.js index 471090325852e3d2c720e94747f22cf081cb5f13..ac1debf3ac43d9bd604ae99de032584b53fe671f 100644 --- a/js/Client/unicaenVue.js +++ b/js/Client/unicaenVue.js @@ -1,8 +1,8 @@ -import axios from 'axios'; +import axios from './axios'; import flashMessenger from './flashMessenger'; const unicaenVue = { - axios: axios, + axios: axios.uvAxios, flashMessenger: flashMessenger, /** @@ -31,84 +31,6 @@ const unicaenVue = { }; -/* Tunning d'Axios pour gérer l'interconnexion avec le serveur avec gestion des alertes */ -unicaenVue.axios.interceptors.request.use(config => { - if (config.submitter) { - let msg = config.msg ? config.msg : 'Action en cours'; - if (config.popover != undefined) { - config.popover.dispose(); - } - config.popover = new bootstrap.Popover(config.submitter, { - content: "<div class=\"spinner-border text-primary\" role=\"status\">\n" + - " <span class=\"visually-hidden\">Loading...</span>\n" + - "</div> " + msg, - html: true, - trigger: 'focus' - }); - config.popover.show(); - } - return config; -}); - -unicaenVue.axios.interceptors.response.use(response => { - response.messages = response.data.messages; - response.data = response.data.data; - response.hasErrors = response.messages && response.messages.error && response.messages.error.length > 0 ? true : false; - - if (response.config.popover) { - var popover = response.config.popover; - - let content = ''; - for (ns in response.messages) { - for (mid in response.messages[ns]) { - content += '<div class="alert fade show alert-' + (ns == 'error' ? 'danger' : ns) + '" role="alert">' + response.messages[ns][mid] + '</div>'; - } - } - - // S'il y a un truc à afficher - if (content) { - popover._config.content = content; - popover.setContent(); - setTimeout(() => { - popover.dispose(); - }, 3000) - } else { - // la popover est masquée si tout est fini - popover.dispose(); - } - } - if (response.messages) { - flashMessenger.toasts(response.messages); - } - - return response; -}, (error) => { - let message = error.response.data; - - if (error.response.status == 403){ - message = '<h4>403 - Accès interdit</h4><br />Vous n\'êtes pas autorisé(e) à faire cette action.'; - }else if (error.response.status == 500) { - const text = document.createElement("div"); - text.innerHTML = error.response.data; - - // on masque l'icône /!\ qui fait doublon si on en trouve - const fasIcons = text.querySelectorAll('i.fas'); - fasIcons.forEach(icon => icon.style.display = "none"); - - message = text.querySelector('.alert').innerHTML; - if (message === undefined){ - message = error.response.data; - } - }else{ - message = error.response.data; - } - - flashMessenger.toast(message, 'error'); -}); - -unicaenVue.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; - - // workaround pour rendre unicaenVue disponible de partout window.unicaenVue = unicaenVue;