diff --git a/doc/client.md b/doc/client.md index 8a947f52e34549200d5744106ae559633f6a53a7..31780c407861c3ac71a1642693b10dd8409080db 100644 --- a/doc/client.md +++ b/doc/client.md @@ -82,6 +82,6 @@ unicaenVue.axios.get( }); ``` -Lorsqu'il reçoit une réponse du serveur, si cette dernière comporte des messages issus du flashMessenger, il va les afficher sous formes d'alertes. -Les alertes sont personnalisées selon qu'il s'agit de success, warning, info ou error. Elles sont affichées pendant 5 secondes, sauf les erreurs qu'il faut fermer +Lorsqu'il reçoit une réponse du serveur, si cette dernière comporte des messages issus du flashMessenger, il va les afficher sous formes de toasts. +Les toasts sont personnalisées selon qu'il s'agit de success, warning, info ou error. Ils sont affichés pendant 3 secondes, sauf les erreurs qu'il faut fermer manuellement afin d'avoir le temps de lire le message et de le traiter. diff --git a/doc/serveur.md b/doc/serveur.md index 6fa0a5728921666366eaf11af47679e8d6993f67..485b3cb5c308e356b463cfa2324503ce253091df 100644 --- a/doc/serveur.md +++ b/doc/serveur.md @@ -112,6 +112,11 @@ Ce dernier va : - collecter les éventuels messages issus du `flashMessenger` - convertir le tout en Json pour envoi au client +AxiosModel peut prendre en construction kjusqu'à 3 paramètres : +1. Les données en tant que telles +2. Les prioriétés (utiles seulement si on traite des objets et facultatives si on transmet des objets implémentant AxiosInterface) +3. Les triggers (si nécessaire). Vous trouverez [plus d'infos sur les triggers ici](#les-triggers). + Voici un exemple d'action de contrôleur qui envoie des données au client en mode Ajax vers Axios : ```php @@ -133,6 +138,7 @@ use UnicaenVue\View\Model\AxiosModel; $this->flashMessenger()->addSuccessMessage('Tout va bien!'); // Et on retourne un AxiosModel qui présente le tout au client + // Ici, nous traitons un tableau en entrée, donc pas besoin de préciser des propriétés à extraire et nous n'avons pas besoin de triggers return new AxiosModel($contacts[$monId]); // Si on exporte un objet ou un tableau d'objets, on peut aussi fournir une lisye de propriétés, cf. ExiosExtractor @@ -361,6 +367,85 @@ var_dump($extracted); // 'ville' => string 'Caen' (length=4) ``` +#### Les triggers + +Il est possible de transformer les données traitées via Axios au moyen de triggers. +Ce sera utile si, par exemple, on veut ajouter des propriétés à la volée à un objet qui n'en était pas pourvu ou si on veut modifier +après coup la valeur de certaines données. + +Il s'agit d'un usage avancé du dispositif. + +Les triggers sont un tableau de fonctions dont les clés sont les chemins qui vont permettre de déterminer sur quelles données nous +voudrons agir, et les valeurs sont des fonctions anonymes dotées de deux paramètres $original : les données originales, c'est-à-dire avant extractions et $extracted, c'est-à-dire le résultat extrait de l'original. +La fonction anonyme devra renvoyer un résultat qui remplacera $extracted dans l'arborescence finale. + +Les chemins sont définis de la manière suivante : + * Si on a une liste d'objets, alors `'/'` permettra d'atteindre chaque occurence du tableau renvoyé. + * Si on y ajoute `[]`, ce qui donnera `'/[]'`, alors on pourra agir sur tout le tableau de données (à condition toutefois que ce soit bien une liste de données qu'on manipule). + * Si on veut accéder à des données plus en avant dans l'arborescence, alors on la parcourera avec des slashs `/``, comme des répertoires. + +Exemple de chemin plus complexe : prenons la structure de données citée [ci-dessous](#depuis-doctrine). +Si nous voulons agir sur les créateurs des volumes horaires, il faudra utiliser le chemin suivant : +`/volumesHoraires/histoCreateur`. +Nous pourrons ainsi traiter les créateurs des volumes horaires, et pas les créateurs des missions. + + + +Exemple d'utilisation des triggers : + +Prenons la structure de données ci-dessus, à savoir une liste de personnes ayant une adresse. + +Voici un exemple de trigger qui va ajouter l'âge aux personnes : + +```php +$personnes = [new Personne(), new Personne()]; + +$properties = ['nom', 'prenom', 'isMajeur', 'adresse']; + +$triggers = [ + // '/' signifie que nous agirons sur les données de premier niveau, qui sont ici des Personne. Le trigger agira pour chaque personne + '/' => function($original, $extracted){ + // $original contiendra l'objet correspondant à l'entité Personne + // $extracted contiendra le tableau de données déjà extrait + + $extracted['age'] = 37; // On ajoute ici une propriété en extraction qui n'a pas été générée avant. + // Nous pourrions tout aussi bien retirer une donnée, ou bien en changer le type ou la valeur. + + return $extracted; + }, + + // Autre exemple de trigger qui agira non plus sur chaque personne de la liste, mais sur l'ensemble de la liste + '/[]' => function($original, $extracted){ + // $original contient la liste des objets Personne + // $extracted contient un tableau des deux personnes converties en array + + unset($extracted[0]);// on supprime la première personne de la liste + + // il est obligatoire de retourner quelque chose + return $extracted; + }, + + // Encore un autre exemple : on va agir sur les adresses de chaques personnes + '/adresse' => function($original, $extracted){ + $extracted['pays'] = 'France'; + + return $extracted; + } +]; + +$extracted = \UnicaenVue\Axios\AxiosExtractor::extract($personnes,$properties,$triggers); +var_dump($extracted); +// Renvoie : +// array (size=4) +// 'nom' => string 'Dupont' (length=6) +// 'prenom' => string 'Robert' (length=6) +// 'isMajeur' => boolean true +// 'adresse' => array (size=2) +// 'cp' => int 14000 +// 'ville' => string 'Caen' (length=4) +``` + + ### Depuis Doctrine Axios peut également prendre en entrée des requêtes Doctrine `Doctrine\ORM\Query`. diff --git a/js/Client/flashMessenger.js b/js/Client/flashMessenger.js index e06b920d3e8b327a7afe521190a00dc345d7813a..049dc592521dc3ccac408d0789040fed42ffcc94 100644 --- a/js/Client/flashMessenger.js +++ b/js/Client/flashMessenger.js @@ -39,8 +39,8 @@ function toast(message, severity) { toast.setAttribute('aria-live', 'assertive'); toast.setAttribute('aria-atomic', 'true'); - if (severity === 'error'){ - toast.setAttribute('style', 'width:100%'); + if (severity === 'error' && message.length > 500){ + toast.setAttribute('style', 'width:700px'); } const toastContent = @@ -57,7 +57,7 @@ function toast(message, severity) { if (severity !== 'error') { setTimeout(() => { toast.classList.remove('show'); - }, 3000); + }, 5000); } } diff --git a/js/Client/unicaenVue.js b/js/Client/unicaenVue.js index 24b8f989b87757ed7643eaba52aea06a340517ea..471090325852e3d2c720e94747f22cf081cb5f13 100644 --- a/js/Client/unicaenVue.js +++ b/js/Client/unicaenVue.js @@ -83,11 +83,27 @@ unicaenVue.axios.interceptors.response.use(response => { return response; }, (error) => { - var text = $("<div>").html(error.response.data); + let message = error.response.data; - text.find('i.fas').hide(); + 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; - flashMessenger.toast(text.find('.alert').html(), 'error'); + // 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'; diff --git a/src/Axios/AxiosExtractor.php b/src/Axios/AxiosExtractor.php index 43f5285706dfcbf9f1497ac448f99497d0fda11b..8eaa0ba9f6cd40a6fcd3082ea99a0d24430eabf5 100644 --- a/src/Axios/AxiosExtractor.php +++ b/src/Axios/AxiosExtractor.php @@ -9,36 +9,49 @@ class AxiosExtractor { const DATETIME_FORMAT = 'Y-m-d\TH:i:s.u\Z'; // timestamp ISO 8601 pour HTML5 + protected array $triggers = []; - public static function extract($data, array $properties = []) + + public static function extract($data, array $properties = [], array $triggers = []) { $axios = new self; + $axios->triggers = $triggers; return $axios->extractData($data, $properties); } - public function extractData($data, array $properties = []) + protected function extractData($data, array $properties = [], string $path = '') { + $triggerPath = $path ? $path : '/'; if ($data instanceof Query) { - return $this->extractData($data->getResult(), $properties); + $result = $this->extractData($data->getResult(), $properties, ''); } elseif ($this->isList($data)) { - return $this->extractList($data, $properties); + $result = $this->extractList($data, $properties, $path); + $triggerPath .= '[]'; } elseif (is_array($data)) { - return $this->extractArray($data, $properties); + $result = $this->extractArray($data, $properties, $path); } elseif ($data instanceof \DateTime) { - return $data->format(self::DATETIME_FORMAT); + $result = $data->format(self::DATETIME_FORMAT); } elseif (is_object($data)) { - return $this->extractObject($data, $properties); + $result = $this->extractObject($data, $properties, $path); } else { - return $data; + $result = $data; + } + + if (array_key_exists($triggerPath, $this->triggers) && !$data instanceof Query){ + // trigger est un callable qui accepte deux arguments : le premier les la donnée originale, le second la donnée extraite + // il doit retourner une donnée qui remplacera la donnée extraite + $result = $this->triggers[$triggerPath]($data, $result); } + + return $result; } - protected function extractObject($data, array $properties): array + protected function extractObject($data, array $properties, string $path = ''): array { $result = []; @@ -81,7 +94,7 @@ class AxiosExtractor foreach ($methods as $method) { if (method_exists($data, $method)) { $value = $data->$method(); - $result[$property] = $this->extractData($value, $subProperties); + $result[$property] = $this->extractData($value, $subProperties, $path.'/'.$property); break; } } @@ -97,7 +110,7 @@ class AxiosExtractor - protected function extractArray(array $data, array $properties): array + protected function extractArray(array $data, array $properties, string $path = ''): array { $result = []; @@ -121,7 +134,7 @@ class AxiosExtractor } if (array_key_exists($property, $data)) { - $result[$property] = $this->extractData($data[$property], $subProperties); + $result[$property] = $this->extractData($data[$property], $subProperties, $path.'/'.$property); } } @@ -130,11 +143,11 @@ class AxiosExtractor - protected function extractList($list, array $properties = []): array + protected function extractList($list, array $properties = [], string $path = ''): array { $result = []; foreach ($list as $sobj) { - $result[] = $this->extractData($sobj, $properties); + $result[] = $this->extractData($sobj, $properties, $path); } return $result; diff --git a/src/View/Model/AxiosModel.php b/src/View/Model/AxiosModel.php index 30dac8e960a8ab91d7c9b4fd8868ddc55a3f743e..2a2d0fb22b34ffdba5749327ce40fe8b20a7f58a 100644 --- a/src/View/Model/AxiosModel.php +++ b/src/View/Model/AxiosModel.php @@ -12,12 +12,15 @@ class AxiosModel implements ModelInterface protected array $properties = []; + protected array $triggers = []; - public function __construct($data, array $properties = []) + + public function __construct($data, array $properties = [], array $triggers = []) { $this->data = $data; $this->properties = $properties; + $this->triggers = $triggers; } @@ -54,6 +57,22 @@ class AxiosModel implements ModelInterface + public function getTriggers(): array + { + return $this->triggers; + } + + + + public function setTriggers(array $triggers): AxiosModel + { + $this->triggers = $triggers; + + return $this; + } + + + public function getVariables() { return $this->data; diff --git a/src/View/Renderer/AxiosRenderer.php b/src/View/Renderer/AxiosRenderer.php index 53115afd39a6964340813039a7ab8c4a07ca8cb5..4b20f9557ca5cc2949efdcbcd4623fb1a51d2b0f 100755 --- a/src/View/Renderer/AxiosRenderer.php +++ b/src/View/Renderer/AxiosRenderer.php @@ -33,7 +33,7 @@ class AxiosRenderer extends JsonRenderer protected function makeData(AxiosModel $axiosModel): array { - $data = AxiosExtractor::extract($axiosModel->getData(), $axiosModel->getProperties()); + $data = AxiosExtractor::extract($axiosModel->getData(), $axiosModel->getProperties(), $axiosModel->getTriggers()); $namespaces = [ $this->flashMessenger::NAMESPACE_SUCCESS,