Meta box : reliez des contenus entre eux
Pour illustrer la metabox du jour, le mieux est de se mettre en situation…
Imaginons que nous réalisons un site WordPress pour un établissement qui organise des colloques et des conférences. Sur ce site, mon client souhaiterai y présenter les évènements, en détail, ainsi que les intervenants.
On a plein de données à renseigner pour l’un comme pour l’autre. Nous allons donc créer deux types de contenus.
Mais bien sûr il va falloir les lier, pour dire quel conférencier participe à quel colloque. Il nous faut une metabox !
Problème : Il y a plusieurs conférenciers pour chaque évènement, je ne peut donc pas utiliser un champ select pour faire la liaison. Je ne peux pas non plus utiliser des cases à cocher car j’ai environ 150 intervenants dans ma base, ça va faire un paquet de checkbox…
L’idéal serait une metabox avec un champ de recherche pour trouver les conférenciers. Lorsque l’on trouverait celui qui nous intéresse nous pourrions l’ajouter à une liste qui sera sauvegardée dans une metadonnée du colloque.
C’est ce que nous allons faire…
On va avoir besoin de ça
Avant de se jeter dans le code, commençons par faire l’inventaire de ce dont nous allons avoir besoin.
- Comme toujours, il faudra commencer par les fonctions habituelles d’initilisation/construction/sauvegarde de la meta box
- Pour le champ de recherche, un système d’autocomplétion serait parfait. On commencerait à saisir le nom d’un conférencier qu’il apparaitrait dans le champ. Un clic dessus nous permettrai de l’ajouter à une liste ainsi qu’a un champ caché. Pour le système d’autocomplétion, je propose jquery-ui-autocomplete.
- Il nous faut donc aussi un tableau javascript de tous les intervenants potentiels…
- … et un champ caché …
- … et une liste non-ordonnée, en HTML, pour faire un retour visuel à l’utilisateur
- Qui dit HTML dit aussi un peu de css pour la mise en forme
- Pour finir bah il reste le gros javascript de bœuf. On va s’inspirer de celui-ci pour le faire évoluer selon nos besoins.
Le code PHP
Notre metabox va reposer à 100% sur jquery autocomplete, il faut donc l’ajouter à l’admin WordPress, sans oublier les styles qui vont bien.
Le code suivant est à ajouter au fichier functions.php (vous trouverez le style ici). Pensez à mettre à jour les chemins…
add_action( 'admin_enqueue_scripts', 'my_admin_scripts_method' );
function my_admin_scripts_method() {
if( is_admin() ){
wp_enqueue_script( 'jquery-ui-autocomplete' );
wp_register_style( 'jquery.ui.theme', get_bloginfo( 'template_url' ) . '/css/jquery-ui-1.8.19.custom.css');
wp_enqueue_style( 'jquery.ui.theme' );
}
}
Maintenant qu’on a mis en place la librairie, nous pouvons attaquer la metabox.
J’initialise…
// Initialisation de la metabox, pour le CPT "conference"
add_action('add_meta_boxes','mes_metaboxes');
function mes_metaboxes(){
add_meta_box('conferenciers_presents', 'Conférenciers présents', 'conferenciers_concernes', 'conference', 'side', 'default');
}
… je construis…
//Construction
function conferenciers_concernes($post){
//je récupère la meta potentiellement sauvegardée
$conferenciers_presents = get_post_meta($post->ID,'_conferenciers_presents',false);
//je créer un nonce
wp_nonce_field('update-conferenciers_'.$post->ID, '_wpnonce_update_conferenciers);
//mon widget
echo '<div class="ui-widget">';
// le champ qui servira de support à autocomplete
echo '<label for="nom">Nom : </label><input id="nom" type="text" />';
// la liste des conférenciers concernés (assurant un retour visuel pour l'utilisateur)
echo '<ul>';
// j'y affiche toutes les entrées déjà sauvegardées dans la meta</ul>
if( ! empty( $conferenciers_presents) )
foreach( $conferenciers_presents as $c )
echo '<li data-id="' . $c . '"><span class="erase">x</span> ' . get_the_title($c) . '</li>
echo '<ul>';
// mon champ caché, que je mettrai à jour et sauvegarderai
// il contient déjà les valeurs de la meta
echo'<input id="conf_presents" type="hidden" name="conf_presents" value="'.implode(',',$conferenciers_presents).'" />';
//fin du widget
echo '</div>';
… et je sauvegarde.
//sauvegarde
add_action('save_post','sauvegarde_metabox');
function sauvegarde_metabox($post_id){
//s'il s'agit bien d'une sauvegarde volontaire
if( ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) && isset($_POST['conf_presents'])){
//test du nonce
check_admin_referer( 'update-conferenciers_'.$post_id,'_wpnonce_update_conferenciers' );
// je suprrime tout
delete_post_meta($post_id,"_conferenciers_presents");
//j'éclate mon input
$conf = explode(',',$_POST['conf_presents']);
foreach($conf as $c){
//pour chaque entrée j'ajoute une meta
add_post_meta($post_id, "_conferenciers_presents", intval($c));
}
}
}
Pour l’instant notre meta box ne fait rien du tout. Pour cause on vient juste d’en poser les fondations ; rien en sera actif sans le javascript.
3 tonnes de javascript
On arrive à la grosse partie. Avant toute chose, autocomplete va avoir besoin d’une liste des éléments qui peuvent être appelés. Ce qu’on veut c’est lister tous les intervenants dans la base.
Il faut donc créer un tableau en js, et le remplir via une boucle WordPress renvoyant la totalité des conférencier.
Créer une balise <script> dans la metabox, et ajouter ici ce code :
//no-conflict
jQuery(function($) {
// un tableau avec tous les conférenciers que l'on peut sélectionner
var availableTags = <?php $confe = get_posts('post_type=conferencier&posts_per_page=-1');
foreach($confe as $cf){
echo '{value:"'.$cf->ID.'",label:"'.esc_js($cf->post_title).'"},'."\n";
}?>
});
On a maintenant tout ce qu’il nous faut pour coder l’ajout d’un intervenant, à savoir : un tableau qui donne pour chaque conférencier son nom et son ID
L’ajout d’un intervenant
Toujours dans la même balise <script>, on conçoit la fonction d’autocomplétion.
//autocomplete sur le champ #nom
$( "#nom" ).autocomplete({
// je mets le tableau précédemment crée en source
source: availableTags,
// lorsqe l'on sélectionne un élément
select: function(event,ui){
//je crée un nouveau
<ul>
<li>var li = '</li>
<li data-id="' + ui.item.value + '"><span class="erase">x</span> ' + ui.item.label + '</li>
<li>';
//je fais un tableaux des conférencier déjà ajouté
var all_conf_presents = new Array();
all_conf_presents =($('#conf_presents').val()!='') ? $('#conf_presents').val().split(',') : [];
// si il est déjà dans la liste, j'en tiens pas compte
if($.inArray(ui.item.value,all_conf_presents)!="-1"){
$(this).val('');
}else{
//sinon je l'ajoute à la liste
all_conf_presents.push(ui.item.value);
//je pousse cette liste dans le champ caché
$('#conf_presents').val(all_conf_presents);
//et j'ajoute la nouvelle entrée dans le <ul>
var $cp= $( "#conferenciers_presents" );
$cp.append(li);
$(this).val('');</ul>
}
//juste pour que la sélection d'un élément ne remplisse pas le input (comportement normal)
return false;
}
});
Voilà, dès maintenant la meta box nous permet d’ajouter des entrées. Pas de problème avec la sauvegarde non plus. Par contre il n’est toujours pas possible de supprimer un élément.
Suppression d’un conférencier
Encore et toujours dans la même balise <script>, à la suite du reste, on ajoute le code ci-dessous qui va nous permettre de retirer/dissocier un intervenant de l’événement.
L’écouteur d’événement est contenu dans fonction, de façon à pouvoir le re-associer facilement aux éléments qui seront ajoutés par la suite (j’aime pas la fonction “live” de jQuery et j’assume…).
//function qui me sert à supprimer l'ID d'un conférencier dans #conf_presents
function removeByElement(arrayName,arrayElement){
for(var i=0; i if(arrayName[i]==arrayElement)
arrayName.splice(i,1);
}
}
//évènement de suppression de conférencier
function listenerremove(){
$( "#conferenciers_presents" ).find('li .erase').on('click',function(){
// suppression élément
var $elem = $(this).parent('li'); //je cible l'élément à supprimer
//je construit un talbeau avec les conférencier actuellement liés
var all_conf_presents = new Array();
all_conf_presents =$('#conf_presents').val().split(',');
//je récupère l'ID à retirer
var dataval = $elem.attr('data-id');
// je supprime l'ID du tableau
removeByElement(all_conf_presents,dataval);
//je supprime le conférencier dans la liste
$elem.remove();
//je supprime son ID dans le champ caché
$('#conf_presents').val(all_conf_presents);
});
}
//je lance la fonction
listenerremove();
Il ne faut pas oublier d’ajouter “listenerremove();” dans la fonction d’ajout, juste avant la fermeture du else :
var $cp= $( "#conferenciers_presents" );
$cp.append(li);
//ici
listenerremove();
}
Il ne nous reste plus qu’à ajouter un peu de CSS pour style le bouton de suppression. Dans la fonction de construction de la meta box, ajouter une balise <style>, et collez-y ces styles :
.erase{
background:#2e2e2e;
color:#FFF;
padding:0 4px;
-moz-border-radius:10px;
-webkit-border-radius:10px;
-o-border-radius:10px;
border-radius:10px;
}
.erase:hover{
background:#F20;
cursor:pointer;
}
Le code dans son intégralité
Vous devriez maintenant obtenir ce résultat, dans votre fichier “functions.php” :
// chargement des scripts
add_action('admin_enqueue_scripts', 'my_admin_scripts_method');
function my_admin_scripts_method(){
if(is_admin()){
wp_enqueue_script('jquery-ui-autocomplete');
wp_register_style('jquery.ui.theme', get_bloginfo('template_url') . '/css/jquery-ui-1.8.19.custom.css');
wp_enqueue_style('jquery.ui.theme');
}
}
// Initialisation de la metabox, pour le CPT "conference"
add_action('add_meta_boxes','mes_metaboxes');
function mes_metaboxes(){
add_meta_box('conferenciers_presents', 'Conférenciers présents', 'conferenciers_concernes', 'conference', 'side', 'default');
}
//Construction
function conferenciers_concernes($post){
$conferenciers_presents = get_post_meta($post->ID,'_conferenciers_presents',false);
wp_nonce_field('update-conferenciers_'.$post->ID, '_wpnonce_update_conferenciers');
echo '<div class="ui-widget">';
echo '<label for="nom">Nom : </label><input id="nom" type="text" />';
echo '</div><ul>';
if(!empty( $conferenciers_presents)){
foreach($conferenciers_presents as $c){
echo '<li data-id="' . $c . '"><span class="erase">x</span> ' . get_the_title($c) . '</li>';
}
}
echo '</ul>';
echo'<input id="conf_presents" type="hidden" name="conf_presents" value="'.implode(',',$conferenciers_presents).'" />';
echo '</div>';
?>
<script type="text/javascript">// <![CDATA[
jQuery(function($) {
// un tableau avec tous les conférenciers que l'on peut sélectionner
var availableTags = [<?php
$confe = get_posts('post_type=conferencier&posts_per_page=-1');
foreach($confe as $cf){
echo '{value:"'.$cf->ID.'",label:"'.esc_js($cf->post_title).'"},'."\n";
}
?>];
//autocomplete sur le champ #nom
$("#nom").autocomplete({
source: availableTags,
select: function(event,ui){
var li = '<li data-id="' + ui.item.value + '"><span class="erase">x</span> ' + ui.item.label + '</li>';
var all_conf_presents = new Array();
all_conf_presents =($('#conf_presents').val()!='') ? $('#conf_presents').val().split(',') : [];
if($.inArray(ui.item.value,all_conf_presents)!="-1"){
$(this).val('');
}else{
all_conf_presents.push(ui.item.value);
$('#conf_presents').val(all_conf_presents);
var $cp= $( "#conferenciers_presents" );
$cp.append(li);
$(this).val('');
listenerremove();
}
return false;
}
});
//function qui me sert à supprimer l'ID d'un conférencier dans #conf_presents
function removeByElement(arrayName,arrayElement){
for(var i=0; i<arrayName.length;i++ ){
if(arrayName[i]==arrayElement)
arrayName.splice(i,1);
}
}
//évènement de suppression de conférencier
function listenerremove(){
$("#conferenciers_presents").find('li .erase').on('click',function(){
var $elem = $(this).parent('li');
var all_conf_presents = new Array();
all_conf_presents =$('#conf_presents').val().split(',');
var dataval = $elem.attr('data-id');
removeByElement(all_conf_presents,dataval);
$elem.remove();
$('#conf_presents').val(all_conf_presents);
});
}
listenerremove();
});
// ]]></script>
<?php }
//sauvegarde
add_action('save_post','sauvegarde_metabox');
function sauvegarde_metabox($post_id){
if( ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) && isset($_POST['conf_presents'])){
check_admin_referer( 'update-conferenciers_'.$post_id,'_wpnonce_update_conferenciers' );
delete_post_meta($post_id,"_conferenciers_presents");
$conf = explode(',',$_POST['conf_presents']);
foreach($conf as $c){
add_post_meta($post_id, "_conferenciers_presents", intval($c));
}
}
}
Utilisation de cette meta box
Notre meta box de liaison inter-contenus est opérationnelle à 100%. Grâce à elle on pourra faire ressortir les conférenciers associé à chaque colloque, lorsque l’on sera sur la page de la conférence, grâce à la fonction get_post_meta().
Nous pourrons aussi, sur la page du conférencier, lister ces interventions, via une requête personnalisée du style :
$q = new WP_Query(
array(
'post_type' => 'conference',
'posts_per_page' => '-1',
'meta_query' => array(
'operator' => 'AND',
array(
'key' => '_conferenciers_presents',
'value' => $post->ID,
'compare' => '='
)
)
)
);
Si vous avez des suggestions pour améliorer cette metabox, n’hésitez pas à me les faire connaître.
Bon usage

13 commentaires
Après l’article est plus dans l’esprit “Do it Yourself” C’est pas que j’aime pas les plugins (bien au contraire ^^) mais je n’ai pas toujours tout le temps besoin de toutes les fonctionnalités du truc…
Sinon c’est sympa aussi de voir comment ça marche
Mais t’as raison, pour des utilisation avancés, ton plugin fera gagner du temps !
Je suis totalement d’accord que parfois le développement perso est largement suffisant
Dans l’ensemble il vaut mieux utiliser un plugin de ce type qui est codé de façon magistrale et surtout par un core dev de WordPress
mise à jour de façon récurrente. J4ai posté un problème sur le github et j’ai eu une réponse dans la journée
Je suis de l’avis de Raherian pour une véritable utilisation MAIS j’adore encore plus fouioller le code et réussir à faire ça à la main, avec2 bouts de ficelle (bon faut que ça reste propre et un minimum opti).
Encore bravo, tu as dû passer un sacré bout de temps pour tous ces tutos meta box (c’est pas fini peut etre ?? :p)
Petit détail (que tu avais aussi fait dans ton précédent tuto) c’est que tu testes « if($conferenciers_presents[0]!=NULL) » mais si « $conferenciers_presents[0] » n’existe pas du tout (il peut exister et valoir null) alors une notice apparait ! Il te faut tester avec isset() : « if( isset( $conferenciers_presents[0] ) ) » ou avec count() « if( count( $conferenciers_presents ) > 0 ) ».
Je vais essayer de reproduire le tuto car il me semble que WP intègre un système qu’on peut détourner pour faire la recherche d’un post en ajax au lieu de tout intégrer mes 15000 conférienciers :p, il l’utilise dans la page des médias.
Je te trouver ça
C’est un poil plus complexe, mais à l’arrivée je ne fais aucune requête de tous mes posts pour les mettre dans un ui-autocomplete, à la place, je fais des requêtes ajax selon le texte frappé, bien plus léger donc je pense.
Non ?
Je dois faire une démo ??
devient
puisque #conf_present est un INPUT est non pas une DIV
Aussi, correction sécu+fonctionnelle : Ne jamais afficher des infos sans les sanitizer !
Voici la modif :
devient :
J’ai ajouté « esc_js() » qui est là pou « escaper » du contenu pour le « js ».
Fait le test avec un conférencier qui contient une double quote (« ) dans son nom, et boum, tu te retrouves avec de la bad concat en JS, forcément ça pète
J’ai supprimé le order by title car on s’en fiche bien gras, et en plus, le tri sur une chaine est moins bon en perf que sur une date (me trompe-je Rahe ?)
Et comme j’ai dis au dessus :
devient
Et pour info, j’ai du ne pas mettre le CSS perso car ça me fait un carré voir avec texte en noir
Bravo pour ce tuto tout de même, par contre, je pense qu’un débutant sera perdu car tu dis « créez une balise SCRIPT dans la meta box », mais c’est quoi la metabox ? je la mets ou ? etc, je te conseille de donner le code complet, final à utiliser. En ajoutant bien sur des recommandations comme « mon custom post type est ‘conférencier’ à vous de mettre le votre ».
Dans tous les cas, pour un dev WP, ça psoe pas de soucis, j’ai réussi à le faire fonctionner ;p
Allez je jete un oeil pour mon find_posts_div()
devient
j’ai ajouté « \n » aussi pour que dans le source on y voit clair :p
Je suis conscient que ce ne doit pas être très clair pour les débutants, j’espère progresser dans la rédaction des prochains articles
(ainsi qu’en sécu^^)
Et sinon je tiens à dire merci pour vos remarques !!
Pour les requêtes en ajax avec $.findPosts() je vais tester, ne t’embêtes pas à faire une démo
Seul petit détail, pour le test :
j’ai préféré faire :
Je pense que ça revient au même.
Sinon c’est le cinquième article sur les metabox que je fais. C’est juste pour moi l’occasion de partager des bouts de code que j’utilise pas mal au quotidien.
Des fois des plugins peuvent faire la même chose, et je réinvente sans doute la roue… mais au moins je sais ce qu’ils ont dans le ventre, et ils font exactement ce dont j’ai besoin. Ils ne m’ajoutent pas des dizaines d’options (ce que j’apprécie fortement
) et quelque part je trouve ça plus clair pour mes clients.
J’ai prévu un dernier article dans la série mais j’annonce la couleur : ça fera exactement la même chose que “next gen gallery” mais en plus simple… J’vais pas le faire tout de suite car je pars en vacances, mais à mon retour
Après ça sera l’occaz’ de parler d’autres choses que de metabox
en attendant je vais aller jeter un oeil du côté de “media.dev.js”
(empty() renvoie vrai aussi pour NULL et pour une variable déclarée mais non ‘remplie’)
=> empty() ne vaut pas isset()
Je ne sais pas si tu l’as remarqué mais lors de la sauvegarde si aucun conférencier n’est entré le nom de l’article est automatiquement enregistré comme conférencier. Je tente depuis plusieurs heure de régler cela mais sans succès
merci pour cet article et merci surtout pour les commentaires… Je viens d’apprendre la différence entre !empty et isset alors que j’ai toujours été persuadé que la fonction faisait la même chose.
Et félicitation pour ton blog, c’est rare de tomber sur un design si abouti.
Commenter