htmlcss.fr Des tutos ou tutoriels Wordpress, html, php ou javascript.

Re order maison pour WordPress

R

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 !

htmlcss.fr Des tutos ou tutoriels Wordpress, html, php ou javascript.

François Riant

Je m’appelle François Riant. Je travail dans les métiers du web depuis 2006. Mon expérience m’a amené à changer plusieurs fois de technologie.

Aujourd’hui je cherche à partager mon expérience et j’y trouve du de plaisir. Je travail actuellement chez W2P Digital.
Je ne prends donc pas de mission en freelance.

Si vous avez une remarque ou une question; vous pouvez me joindre sur francois.riant@gmail.com