Il existe déjà plusieurs plugins changer l’ordre des posts par drag & drop dans l’admin et certains offrent des options sympathiques. Mais parfois pour X ou Y raisons, on ne souhaite pas ajouter un plugin. Voici un petit exemple:
Bref, e vous donne ici ma façon de faire.
Mise en place
Le module fonctionne avec deux fichiers, un script JS reorder-scripts.js et un fichier PHP reorder_admin.php.
Il y a aussi un petit gif pour afficher les temps de chargement lors des appels ajax.
Il faut placer les trois fichiers dans le même dossier quelque part dans le thème et ajouter le PHP dans votre « functions.php » via un simple require_once, par exemple :
require_once 'modules/reorder_admin/reorder_admin.php';
Le code fonctionne avec un ou plusieurs post type ( custom ou non ).
Le code va alors modifier l’argument hierarchical du post type et le passer à true lors du register_post_type() via le filter register_post_type_args.
Le fichier PHP va gérer les choses suivantes :
- Définition de trois constantes pour gérer les options
- Ajouter le Javascript qui va gérer le drag & drop et l’ajax dans l’admin
- Le callback du script JS qui va modifier le valeur de ‘menu_order’ des posts
- Un hook qui, lors de l’ajout d’un nouveau post, va l’ajouter en début ou en fin de liste
- Ajout de deux nouvelles colonnes dans la page de listing: une pour drag and drop et l’autre qui affiche le menu_order.
Le code
J’ai commenté le code si vous voulez prendre le temps de l’analyser.
Vous pouvez télécharger le zip ici.
Allez, on commence avec le fichier .php
<?php // la liste des post type qui doivent avoir le drag and drop define('ALLOWED_DRAG_DROP_PT', ['post','page','cpt']); // tableau qui indique quel post type sont supporté define('SHOW_NUMBER_COLUMN', true); // affiche ou non la deuxieme colonne dans le BO avec le menu_order define('ADD_ON_TOP', true); // if true, les nouveaux post seront ajoutés au début, sinon à la fin, attention au nombre de requette when on top! if( is_admin() ): // le callback du script ajax /js/admin-scripts.js pour changer l'ordre; // il n'est pas conditionné comme les autres car sinon il ne sera pas trouvé lors de l'appelle ajax => erreur 400 Bad request // ici on utilise uniquement le hook pour les users connectés: cf wp_ajax_nopriv add_action('wp_ajax_update_post_order', 'update_post_order'); //Hook lors de la creation d'un nouveau post, je l'ajoute à la fin add_action('save_post', 'on_create_post_save_menu_order', 10, 3); // modifie l'argument hierarchical du/des post type déclaré dans ALLOWED_DRAG_DROP_PT et le passe à true add_filter('register_post_type_args', 'set_hierarchical_for_reorder', 99, 2); // Fix le nom des post par default ( Articles ), qui deviennent "Pages", pour une raison inconue. // Du coup le <title> dans l admin indique Pages, je n'arrive pas a le changer add_action('admin_menu', 'fix_default_post_label'); function update_post_order() { /* Cette fonction ne sera pas appeller si il n'y a pas d'action ajax private dont action est update_post_order Cette fonction ne sera executer que si post type est dans la contante ALLOWED_DRAG_DROP_PT IMPORTANT : dans le cas ou les post existent deja, la mise à jour des post dans les autre page de l'admin ne se fera pas. */ $retour = []; $retour['post'] = $_POST; if(is_admin()){ $post_type = $_POST['postType']; if(in_array($post_type,ALLOWED_DRAG_DROP_PT)) { $order = $_POST['order']; foreach ($order as $menu_order => $post_id) { $post_id = intval(str_ireplace('post-', '', $post_id)); $menu_order = intval($menu_order); wp_update_post(array('ID' => $post_id, 'menu_order' => $menu_order)); $retour['new_order'][] = [ 'post_id' => $post_id, 'menu_order' => $menu_order, ]; } } } wp_send_json($retour); } function on_create_post_save_menu_order( $post_id, $post, $update ) { if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } // si c est un nouveau if(!$update){ $post_type = get_post_type($post_id); if(!ADD_ON_TOP) { //Ajout à la fin $args = array( 'numberposts' => 1, 'orderby' => 'menu_order', 'order' => 'DESC', 'post_type' => $post_type, ); $selected_post = get_posts( $args ); $new_menu_order = $selected_post[0]->menu_order + 1; }else{ // il vient se placer en tête et tous les autres prennent +1 $new_menu_order = 0; $args = array( 'numberposts' => -1, 'orderby' => 'menu_order', 'order' => 'ASC', 'post_type' => $post_type, ); $selected_posts = get_posts( $args ); foreach ($selected_posts as $index => $p) { wp_update_post( array( 'ID' => $p->ID, 'menu_order' => $index + 1 ) ); } } wp_update_post( array( 'ID' => $post_id, 'menu_order' => $new_menu_order ) ); } } function set_hierarchical_for_reorder($args, $postType) { if (in_array($postType, ALLOWED_DRAG_DROP_PT)) { $args['hierarchical'] = true; } return $args; } function fix_default_post_label() { global $menu,$submenu; $menu[5][0] = _x('Posts', 'post type general name'); $submenu['edit.php'][5][0] = __( 'All Posts' ); } endif; global $pagenow; // si on est dans l'admin et dans la liste des posts if(is_admin() && 'edit.php' === $pagenow ): $add_action = false; $cpt_slug = null; // on ne hook que si on est dans les post type concerné foreach (ALLOWED_DRAG_DROP_PT as $pt) { if(isset($_GET['post_type']) && $_GET['post_type'] === $pt){ $add_action = true; $cpt_slug = $_GET['post_type']; break; } // support pour les article par defaut de WP if(!isset($_GET['post_type']) && $pt === 'post') { $add_action = true; $cpt_slug = $pt; } } if($add_action): // ajout des scripts add_action( 'admin_enqueue_scripts', 'admin_enqueue_scripts' ); // On ajoute une nouvelle colonne dans l'admin add_action('manage_edit-' . $cpt_slug . '_columns', 'manage_reorder_posts_columns'); // Affichage des données de cette nouvelle colonne add_action('manage_' . $cpt_slug . '_posts_custom_column', 'data_columns_reorder_posts'); function admin_enqueue_scripts() { // on ajoute 2 scripts : jquery-ui-sortable qui est fourni nativement par WP // puis notre script; on ajout la dépendence de jquery-ui-sortable ( derniere paramettre ) // Ici on peut faire mieu : ajouter le script uniquement quand c'est necesaire: ici le script est ajouter partout dans l admin !!! wp_enqueue_script( 'jquery-ui-sortable' ); wp_enqueue_script( 'admin-scripts', get_stylesheet_directory_uri().'/modules/reorder_admin/reorder-scripts.js', ['jquery-ui-sortable'] ); //Je rajoute les variable que je souhaite rendre accessible dans le script que je viens d 'ajouter $file_path = __DIR__; $file_path =str_replace('\\', '/', $file_path); $url_path = str_replace($_SERVER['DOCUMENT_ROOT'], '', $file_path); $vars = [ 'currentFileFolder' => get_home_url().$url_path, 'ajaxurl' => admin_url('admin-ajax.php'), 'SHOW_NUMBER_COLUMN' => SHOW_NUMBER_COLUMN, ]; wp_localize_script('admin-scripts', 'vars', $vars); } function manage_reorder_posts_columns($columns) { $tab_new_cols['order'] = 'Changer l\'ordre'; if(SHOW_NUMBER_COLUMN) { $tab_new_cols['menu-order'] = 'Ordre menu'; } return array_merge($columns,$tab_new_cols); } function data_columns_reorder_posts($name) { $cid = get_the_ID(); switch ($name) { case 'menu-order': $thispost = get_post($cid); echo $thispost->menu_order; break; case 'order': ?><img src="data:image/jpg;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAEHSURBVHja7JdBCsIwEEXfWOkBvIErz1BwK3ixbgSPIXgTcdUDeADBldBty7hpoNRY09g0iA78TQnzOiGZ+RFVJUbMXRaJiO1zBqCqJy+yqr6VJZbAHSiBlUuOp5we4BQ4A9qoANIpwLsW1GgXGryxQI22ocAL4NoDvgGLEOBDD9ToODY4eVOt0RVIXHKKSwOx3GPtXEkJ0kC6P/eioQyKGZHi98ASazpFq3jutU0iH1+n/6n+CuszScVJY3O026fNYWtUikgyJrgGcod1uarWY5u9FLj0jMTLEO811PqsgcoCrYAstNnbxzB7ZsuLFvQ8lb0FWDWn/A4sfQy9r/X5+AkTbSw+BgBYV0nOumrEUwAAAABJRU5ErkJggg==" title="" alt="Move Icon" width="30" height="30" style="cursor:s-resize;"/><?php break; } } endif; endif;// END IF ADMIN
Et on continue avec le JS
jQuery(function($) { HTMLCollection.prototype.forEach = Array.prototype.forEach; // Retourne un helper avec la largeur des div lors du défilement. var fixHelper = function(e, ui) { ui.children().each(function() { $(this).width($(this).width()); }); return ui; }; // retour le slug du postype contenu dans le $_GET['post_type'] de l'url var getPostType = function (post_type) { var result = null; var tmp = []; var items = location.search.substr(1).split("&"); for (var index = 0; index < items.length; index++) { tmp = items[index].split("="); if (tmp[0] === post_type) result = decodeURIComponent(tmp[1]); } if(result === null){ result = 'post'; } // ajout le support des Article par defaut de WP return result; } //https://api.jqueryui.com/sortable/ $('.wp-list-table tbody').sortable({ axis: 'y', handle: '.column-order img', placeholder: 'ui-state-highlight', // element rajouté pendant le scroll helper: fixHelper, // fix la largeur des cellules quand on scroll start: function(event, ui) { $('.ui-state-highlight').height($(ui.item).height()); $(ui.item).css({ 'background': '#e0dddd', }); var t = $('.ui-state-highlight'); //Je recupere le nombre de colonnes dans le helper ( element que l'on scroll ) var cols = ui.helper[0].children; // et je compte le nombre d'éléments qui ont la class hidden var hidden_num = 0; var total_num = 0; cols.forEach(function(element, i){ if (element.classList.contains('hidden')) { hidden_num++; } total_num++; }); if(hidden_num != 0){ // je parcours les enfants du helper et j'applique au X premier la class hidden, ce qui supprime le bug d'affichage var children_helper = t[0].children; children_helper.forEach(function(element, i){ if(i >= hidden_num){ element.classList.add('hidden'); } }); // // et idem pour la largeur du placeholder quand le background est gris var children_placeholder = ui.placeholder[0].children; // // je les affiche tous children_placeholder.forEach(function(element, i){ element.classList.remove('hidden'); }); // // pour ensuite controller le nombre souhaité var col_restantes = total_num - hidden_num; children_placeholder.forEach(function(element, i){ if(i >= col_restantes){ element.classList.add('hidden'); } }); } if(vars.SHOW_NUMBER_COLUMN) { // crée un attribut temporaire sur l'élément avec l'ancien index $(this).attr('data-previndex', ui.item.index()); // Enfin, affichage du mini loader pendant le scroll var list_post_id = $(this).sortable('toArray'); var loader_html = '<img src="'+vars.currentFileFolder+'/ajax_loader_small.gif" alt="loader" />'; $( list_post_id ).each(function( index ) { // je stock le le current menu order dans le cas il ne changerait pas $('#'+list_post_id[index]).find('.menu-order').attr('data-prevmenuorder', $('#'+list_post_id[index]).find('.menu-order').html()); $('#'+list_post_id[index]).find('.menu-order').html(loader_html); }); } }, stop: function(event, ui) { $(ui.item).css({ 'background': '', 'width': '' }); if(vars.SHOW_NUMBER_COLUMN) { var newIndex = ui.item.index(); var oldIndex = $(this).attr('data-previndex'); // Si l'element n'a pas changé de position, alors le Update n'est pas executé, je ne dois plus afficher le mini loader if(newIndex == oldIndex) { var list_post_id = $(this).sortable('toArray'); $( list_post_id ).each(function( index ) { var prevmenuorder = $('#'+list_post_id[index]).find('.menu-order').attr('data-prevmenuorder'); $('#'+list_post_id[index]).find('.menu-order').html(prevmenuorder); }); } } }, update: function(event, ui) { var theOrder = $(this).sortable('toArray'); var postType = getPostType('post_type'); var datas = { action: 'update_post_order', postType: postType, order: theOrder }; // on vient mettre à jour les data des colonnes via ajax $.ajax(vars.ajaxurl, { data: datas, type: "POST", // beforeSend: function() {}, error: function(e) { console.log(e); }, success: function(data) { if(vars.SHOW_NUMBER_COLUMN) { $( data.new_order ).each(function( index ) { // On vient cibler la colonne qui affiche ne nombre de l'ordre et on remplace le mini loader par le nouveau numero var pid = '#post-'+$(data.new_order)[index].post_id; var new_num_order = $(data.new_order)[index].menu_order; $(pid).find('.menu-order').html(new_num_order); }); } } }); } }).disableSelection(); });
Et n’oublions pas le petit gif 🙂
Voilà, j’espère que ca vous aidera !