Aujourd’hui, pour continuer la série des meta box, je vous propose de découvrir comment réaliser une “To Do List” dynamique.
Cette metabox contient un nombre indéfini de champs textes, qui peuvent être modulés par le biais de boutons d’ajout/suppression de ligne.
Je vais vous montrer comment la créer, pas à pas, et vous expliquer son fonctionnement. Il risque d’y avoir des bouts de php/js partout mais rassurez-vous, vous trouverez le code complet en fin d’article.
Initialisation de la metabox « liste de tâches »
Comme lorsque l’on créer n’importe quelle metabox, il faut toujours commencer par l’initialiser. Cette étape sert à donner un nom à notre meta box, et à lui préciser où et sur quel type de contenu elle doit s’afficher.
Rien de bien sorcier donc…
add_action( 'add_meta_boxes', 'mes_metaboxes' ); function mes_metaboxes() { add_meta_box( 'to_do_list', 'choses à faire', 'to_do_list', 'post', 'normal', 'default' ); }
Pour la fonction de construction de base, nous allons simplement :
Récupérer des tâche potentiellement enregistrées
Faire une boucle et afficher un champ pour chaque entrée existante, avec “descr_chose[]” en guise d’attribut name
Afficher un champ vide, seul, si aucune tâche n’est enregistrée
Pour l’instant la metabox ne peut enregistrer qu’une seule entrée, donc forcément le foreach ne sert à rien… mais nous allons vite en avoir besoin.
Pour la sauvegarde, on prépare une simple boucle qui, pour chaque input[name= »descr_choses »], ajoutera une valeur à la meta_key “_descr_chose”.
add_action('save_post','ma_sauvegarde'); function ma_sauvegarde($post_id){ if((!defined( 'DOING_AJAX' ) || !DOING_AJAX ) && isset($_POST['descr_chose'])){ // je supprime les anciennes valeur... delete_post_meta($post_id, '_descr_chose');
//...et j'enregistre toutes les nouvelles foreach($_POST['descr_chose'] as $d){ //si elles ne sont pas vides if(!empty($d)) add_post_meta($post_id, '_descr_chose', $d); } } }
Aurais-je omis de vous parler des nonces ? Oui en fait j’avoue… mais je vais réparer mon erreur ^^
Les nonces (number used once) sont des outils qui permettent de protéger notre CMS des failles de sécurité type CSRF. En gros, dans chaque metabox nous allons rajouter un nombre généré aléatoirement, dans un champ caché. Avant la sauvegarde des données de la metabox, la valeur du champ sera comparée à la valeur attendue.
Si elle diffère, on ne sauvegardera rien car cela voudra dire que quelqu’un a profité du fait que l’on soit connecté à WordPress pour nous faire mettre à jour des données contre notre gré.
WordPress propose des fonctions toutes faites pour ça. Ici on va utiliser wp_nonce_field() pour créer le nonce, et check_admin_referer() pour tester sa valeur avant de sauvegarder.
Ainsi, dans notre fonction constructrice, en dehors de la div “#all_things” on ajoute :
Actuellement notre metabox n’est pas du tout opérationnelle. Nous ne pouvons pas ajouter ni supprimer de tâches.
Pour rendre cela possible nous allons rajouter quelques boutons et faire un peu de javascript.
En premier lieu nous avons besoin d’un bouton pour ajouter un élément. Il ne s’affichera que si le javascript est activé. Copiez le code ci-dessous juste après la <div id= »all_things »></div>.
Ensuite il nous faut un bouton pour supprimer les éléments, ci-besoin. Rajouter ce code avant la fermeture des <div> ayant la classe “item-chose” :
Nous allons maintenant pouvoir faire interagir ces boutons avec du javascript.
Un javascript pour ajouter/supprimer des champs
Toujours dans le code de construction de la meta box, en dessous du reste, ajouter une balise <script> avec le code suivant :
// jQuery no-conflict, car on est dans l'admin jQuery(document).ready(function($){ //je créer une fonction function remove_chose(){ // lorsque l'on clic sur le bouton de suppression, // son parent est supprimé $('.suppr-chose').on('click',function(){ $(this).parent().remove(); }); } // je lance cette fonction remove_chose();
// lorsque l'on clique sur "ajouter une tâche"... $('#ajout-chose').on('click',function(){ // ... on duplique/vide qui va bien, à la suite... $('.item-chose:last').clone().appendTo('#all_things'); $('.item-chose:last input').val(''); // ... et on relance la fonction remove_chose remove_chose(); }); });
Vous avez remarqué que l’on relance la fonction remove_chose() après l’ajout d’un nouvel élément de formulaire ?
C’est parce que l’on veut qu’il puisse être supprimé. Il faut donc lui attacher le même écouteur d’événement qu’aux champs déjà existants.
Le problème de get post meta
On en avait déjà parlé dans l’article de découverte des metaboxes, des fois la fonction get_post_meta() n’est pas toujours adaptée pour récupérer des metadonnées. Ici par exemple, si on essaie la meta box, nous allons constater que les tâches sont bien enregistrées, mais qu’elles ressortent dans n’importe quel ordre… C’est un peu problématique.
Et encore là ça va, mais attendez que l’on veuille ajouter des informations complémentaires à chaque tâche. Ça risque de faire n’importe quoi…
La solution consiste à créer une fonction que l’on utilisera à la place de get_post_meta() et qui ressortira les mêmes infos, mais ordonnées par “meta_id”.
function get_post_meta_ordered($id,$meta_key){ global $wpdb; $output = array(); $sql = "SELECT m.meta_value FROM ".$wpdb->postmeta." m where m.meta_key = '".$meta_key."' and m.post_id = '".$id."' order by m.meta_id"; $results = $wpdb->get_results( $sql ); foreach( $results as $result ){ $output[] = $result->meta_value; } return array_filter($output); }
Maintenant ça fonctionne bien, les tâches ressortent dans le bon ordre.
On passe à la suite…
Ajout d’un deuxième champ pour la date
Maintenant que nous pouvons ajouter et modifier des tâches de la liste, il serait bien de pouvoir y associer une durée.
Ce n’est pas très complexe. Il suffit d’ajouter, à l’intérieur de chaque div ayant la classe “item_chose” (avant le bouton « supprimer »), un second champ de type texte :
Il faut penser à ajouter cet élément à trois endroits :
Dans la boucle qui affiche les données sauvegardées
À la suite de ce qui est affiché si aucune donnée n’est présente
On récupère et on sauvegarde les durées de la même manière que les tâches.
Il faut juste, dans le foreach de to_do_list(), utiliser la clef de l’élément en cours pour afficher la bonne durée.
$duree_chose = get_post_meta_ordered($post->ID,'_duree_chose'); $to_do = get_post_meta_ordered($post->ID,'_duree_chose'); // je test s'il y a autant de tâches que de durées if(count( $to_do )>0 && count( $to_do )==count( $duree_chose ) ): foreach($to_do as $k => $thing){ ?> <div class="item-chose"><label for="">Description : </label><input id="" class="description_des_choses" style="width: 50%;" type="text" name="descr_chose[]" value="<?php echo $thing; ?>" /> <label for="">Durée allouée : </label><input id="" class="duree_des_choses" style="width: 56px;" type="text" name="duree_chose[]" value="<?php echo $duree_chose[$k]; ?>" />h <a class="suppr-chose button-secondary hide-if-no-js" href="javascript:void(0);">supprimer</a> <span class="hide-if-js"><em>Pour supprimer, videz les contenus.</em></span></div> <?php } //...
Vu que le tableau des dates et celui des tâches ont systématiquement autant d’éléments, et qu’ils sortent dans le même ordre, ils correspondront à coup sûr.
Obtenir la durée totale
Si vous souhaitez obtenir la durée de toutes les tâches confondues, il suffit de faire :
Cela peut servir autant en front office que dans l’admin.
Récapitulons…
Au fil de cet article on a construit la meta box petit bout par petit bout. Il est temps de revoir son code en entier :
//Initialisation add_action('add_meta_boxes','mes_metaboxes'); function mes_metaboxes(){ add_meta_box('things', 'choses à faire', 'things_to_do', 'post', 'normal', 'default'); }
//fonction alternative à get_post_meta function get_post_meta_ordered($id,$meta_key){ global $wpdb; $output = array(); $sql = "SELECT m.meta_value FROM ".$wpdb->postmeta." m where m.meta_key = '".$meta_key."' and m.post_id = '".$id."' order by m.meta_id"; $results = $wpdb->get_results( $sql ); foreach( $results as $result ){ $output[] = $result->meta_value; } return array_filter($output); }
// Fonction de construction de la metabox function things_to_do($post){ global $wpdb;
Ha une suite, bien sympa encore une fois ! Je vais devoir créer un dossier wabeo moi.
Je me permet de faire quelques corrections :
1) Erreur d’affichage de la description :
Ligne 635 :
foreach($to_do as $k => $thing){
au lieu de
foreach($to_do as $k => $t){
puisque tu affiches « $t » dessous.
2) Optimisation code :
Ligne 612 :
$results = $wpdb->get_results( $sql );
foreach( $results as $result )
{
$output[] = $result->meta_value;
}
return $output;
au lieu de
return $wpdb->get_col( $sql );
En effet, $wpdb->get_col() fait ce que tu souhaites en sortie et récupère bien une colonne de résultats.
3) Opti encore
Ligne 678 :
foreach($_POST[‘descr_chose’] as $d){
add_post_meta($post_id, ‘_descr_chose’, $d);
}
au lieu de
add_post_meta($post_id, ‘_descr_chose’, $_POST[‘descr_chose’]);
Oui, pourquoi découper en morceau si c’est quand même pour tout récupérer en en plus se casser la tête à faire une requête maison pour mettre tout ça en ordre !
4) Nonce field
Ton nonce field est correct, mais un vrai bon nonce field se décompose comme ceci :
« verbe-nom_quelID »
ce qui donne pour toi
wp_nonce_field(‘update-taches_’.$post->ID, ‘_wpnonce_update_taches’);
au lieu de
wp_nonce_field(‘update_taches’, _wpnonce_update_taches’);
Sinon ton nonce sera unique pour tous tes posts. Aussi le fait de découper avec – puis ensuite _ est utile si on souhaite expliquer le nonce en cas d’erreur de vérification, par défaut on a « Etes vous sur de vouloir faire cela ? » Mais cela quoi ?? On peut donc utiliser wp_explain_nonce() avec le filtre ‘explain_nonce_’ . $verb . ‘-‘ . $noun
Et dans le même point j’ajoute que le check_admin_referer() n’est pas obligé de se trouver dans le IF, je l’ai donc volontairement mis sous le IF pour le montrer, la fonctione ne retourne pas false, mais un die, donc bon …
5) Pas de noJS ?
Dommage ! Mon client est aveugle et utilise un screen reader, il ne peut pas interagir avec ces box. (oui les aveugles ont internet et surfent …)
6) jQuery sans live
« Live » c’est la vie ! ^^
Last) Voici mon code complet retouché:
1) j’y ai ajouté une gestion du no JS en affichant/cachant certains éléments, en no JS, pour ajouter un 2eme element, il faut sauvegarder entre 2 (oui c’est moins bien, mais c’est no JS !)
2) J’ai modifié le jQuery pour supprimer la fonction remove_chose() et jouer avec un live et aussi un clone.
3) La fonction ma_sauvegarde a donc été modifiée aussi concernant le nonce, le check admin ref et la sauvegarde des données directement en array.
4) Bref un peu tout, je te laisse me poser des questions sur ce que tu comprends pas dans mes modifs au cas où.
Faut que je regarde ça. Y’a deux trois point où je doute que ce soit mieux par contre :
– concernant la fonction live de jQuery, je suis pas sûr mais il me semble qu’elle est juste super gourmande (d’où ma pirouette…)
– Il y a une différence entre pousser un tableau dans une meta et ajouter un tas de meta_value différentes dans autant de meta_key. Si j’ai fait comme ça, c’est que je trouve ça plus simple pour intéragir avec par la suite (pour des meta_query, par exemple…) mais forcément j’ai pas abordé ça ici :-/
Merci pour le reste je connaissait pas 🙂 , et merci de m’avoir repris sur les nonces (je découvre ce truc et ça reste un peu flou encore)
Alors oui tu as raison sur les meta_query tout à fait, et comme tu le dis, ne l’abordant pas (et même, comment l’aborder avec une todo list !?) je n’y avais pas pensé.
Donc ai-je bien fait, je ne sais pas …
Pour le .live() si c’est trop gourmand, mea culpa, ne touche pas ton code, quant à mon .clone() ça te plait ? 😉
Je retouche le code, j’ai fait une tite bourde qui crée un Warning.
Je me permet de faire quelques corrections :
1) Erreur d’affichage de la description :
Ligne 635 :
foreach($to_do as $k => $thing){
au lieu de
foreach($to_do as $k => $t){
puisque tu affiches « $t » dessous.
2) Optimisation code :
Ligne 612 :
$results = $wpdb->get_results( $sql );
foreach( $results as $result )
{
$output[] = $result->meta_value;
}
return $output;
au lieu de
return $wpdb->get_col( $sql );
En effet, $wpdb->get_col() fait ce que tu souhaites en sortie et récupère bien une colonne de résultats.
3) Opti encore
Ligne 678 :
foreach($_POST[‘descr_chose’] as $d){
add_post_meta($post_id, ‘_descr_chose’, $d);
}
au lieu de
add_post_meta($post_id, ‘_descr_chose’, $_POST[‘descr_chose’]);
Oui, pourquoi découper en morceau si c’est quand même pour tout récupérer en en plus se casser la tête à faire une requête maison pour mettre tout ça en ordre !
4) Nonce field
Ton nonce field est correct, mais un vrai bon nonce field se décompose comme ceci :
« verbe-nom_quelID »
ce qui donne pour toi
wp_nonce_field(‘update-taches_’.$post->ID, ‘_wpnonce_update_taches’);
au lieu de
wp_nonce_field(‘update_taches’, _wpnonce_update_taches’);
Sinon ton nonce sera unique pour tous tes posts. Aussi le fait de découper avec – puis ensuite _ est utile si on souhaite expliquer le nonce en cas d’erreur de vérification, par défaut on a « Etes vous sur de vouloir faire cela ? » Mais cela quoi ?? On peut donc utiliser wp_explain_nonce() avec le filtre ‘explain_nonce_’ . $verb . ‘-‘ . $noun
Et dans le même point j’ajoute que le check_admin_referer() n’est pas obligé de se trouver dans le IF, je l’ai donc volontairement mis sous le IF pour le montrer, la fonctione ne retourne pas false, mais un die, donc bon …
5) Pas de noJS ?
Dommage ! Mon client est aveugle et utilise un screen reader, il ne peut pas interagir avec ces box. (oui les aveugles ont internet et surfent …)
6) jQuery sans live
« Live » c’est la vie ! ^^
Last) Voici mon code complet retouché:
1) j’y ai ajouté une gestion du no JS en affichant/cachant certains éléments, en no JS, pour ajouter un 2eme element, il faut sauvegarder entre 2 (oui c’est moins bien, mais c’est no JS !)
2) J’ai modifié le jQuery pour supprimer la fonction remove_chose() et jouer avec un live et aussi un clone.
3) La fonction ma_sauvegarde a donc été modifiée aussi concernant le nonce, le check admin ref et la sauvegarde des données directement en array.
4) Bref un peu tout, je te laisse me poser des questions sur ce que tu comprends pas dans mes modifs au cas où.
Code sous pastebin : http://pastebin.com/iXscWrRF
Tu gères !
Faut que je regarde ça. Y’a deux trois point où je doute que ce soit mieux par contre :
– concernant la fonction live de jQuery, je suis pas sûr mais il me semble qu’elle est juste super gourmande (d’où ma pirouette…)
– Il y a une différence entre pousser un tableau dans une meta et ajouter un tas de meta_value différentes dans autant de meta_key. Si j’ai fait comme ça, c’est que je trouve ça plus simple pour intéragir avec par la suite (pour des meta_query, par exemple…) mais forcément j’ai pas abordé ça ici :-/
Merci pour le reste je connaissait pas 🙂 , et merci de m’avoir repris sur les nonces (je découvre ce truc et ça reste un peu flou encore)
Je mets à jour l’article rapidement…
Donc ai-je bien fait, je ne sais pas …
Pour le .live() si c’est trop gourmand, mea culpa, ne touche pas ton code, quant à mon .clone() ça te plait ? 😉
Je retouche le code, j’ai fait une tite bourde qui crée un Warning.
Je pense mettre à jour l’article ce soir avec tes idées, ça sera bien mieux 🙂
Merci pour le code !