WP_List_Table – una guía paso a paso

Plugin

Este artículo es una traducción y adaptación de WPengineer.com. El artículo original se puede consultar aquí.

En WordPress, la clase WP_List_Table se usa para mostrar datos, por ejemplo, usuarios, plugins, comentarios o entradas. La clase contiene todos los métodos necesarios para mostrar, clasificar, paginar y buscar datos. ¿Qué es más obvio que usarlos para nuestros propios plugins?

Este artículo trata de proporcionar una guía completa de todos los pasos necesarios para crear una tabla adaptada a nuestras necesidades.

  1. Trabajo preliminar
  2. Lo básico
  3. Ordenamiento
  4. Acciones
  5. Acciones conjuntas
  6. Paginación
  7. Búsqueda
  8. Opciones de pantalla
  9. Estilizando la tabla
  10. Otras personalizaciones
  11. Enlaces web

Trabajo preliminar

Para fines de prueba, creamos un pequeño plugin que agrega un elemento de menú:

<?php  /* Nombre del plugin: Ejemplo de tabla de mi lista */ ?>  
<div class="wrap" >  
  <div id="icon-users" class="icon32" ></div>  
  <h2>Mi tabla de prueba</h2>
</div>
<?php

Lo básico

Para empezar, crearemos una tabla con la funcionalidad básica.

Reemplazando la clase original

La documentación de WordPress recomienda hacer una copia de la clase original (WP_List_Table), ya que ésta puede cambiar sin previo aviso en el futuro, lo que rompería la funcionalidad de nuestro complemento.

Para conseguir esto debemos copiar al directorio de nuestro plugin el archivo wp-admin/includes/class-wp-list-table.php, cambiando el nombre de la clase y toda referencia a la misma que se encuentre en dicho archivo. En este caso llamaremos al nuevo archivo my_plugin_list_table.php.

En este ejemplo, reemplazaremos el nombre de la clase WP_List_Table por My_New_List_Table.

Lo primero que debemos hacer es cargarla ahí donde vaya a ser usada:

require_once ( RUTA_A_MI_PLUGIN . 'my-plugin-list-table.php' ); 

Para crear una tabla, debemos crear una clase hija de My_New_List_Table:

class My_Table extends My_New_List_Table { }  


$myListTable = new My_Table();  

Con fines de demostración inicializaremos algunos datos de muestra. Usualmente estos datos se leen de la base de datos o alguna otra fuente de información:

var $ example_data = array ( 
  array ( 'ID' => 1 , 'booktitle' => 'Quarter Share' , 
                      'author' => 'Nathan Lowell' , 
                      'isbn' => '978-0982514542' ), 
  
  array ( 'ID' => 2 , 'booktitle' => '7th Son: Descent' ,
                      'author' => 'JC Hutchins' ,
                      'isbn' => '0312384378' ),

  array ( 'ID' => 3 , 'booktitle' => 'Shadowmagic' ,
                      'author' => 'John Lenahan' ,
                      'isbn' => '978-1905548927' ), 

  array ( 'ID' => 4 , 'booktitle' => 'The Crown Conspiracy' ,
                      'author' => 'Michael J. Sullivan' ,
                      'isbn' => '978-0979621130' ), 

  array ( 'ID' => 5 , 'booktitle' => 'Max Quick:El bolsillo y el colgante' ,
                      'autor' => 'Mark Jeffrey' ,
                      'isbn' => '978-0061988929' ), 

  array ( 'ID' => 6 , 'booktitle' => 'Jack Wakes Up: A Novel' ,
                      'author' => 'Seth Harwood' ,
                      'isbn' => '978-0307454355' ) );

Antes de poder mostrar los datos en la tabla, tenemos que definir algunos métodos y variables:

function get_columns () { 
  $columns = array (
        'booktitle' => 'Titulo', 
        'author' => 'Autor', 
        'isbn' => 'ISBN' ); 

  return $columns; 
}
    
function prepare_items () { 
  $columns = $this -> get_columns (); 
  $hidden = array(); 
  $sortable = array(); 
  $this->_column_headers = array( $columns ,$hidden , $sortable ); 
  $this->items = $this->example_data;
} 

El método get_columns() es necesario para etiquetar las columnas en la parte superior e inferior de la tabla. Las claves del arreglo deben ser las mismas que en la matriz de datos, de lo contrario no se mostrarán las columnas respectivas.

La función prepare_items define dos arreglos que controlan el comportamiento de la tabla:

  • $hidden establece las columnas ocultas.
  • $sortable determina si la tabla puede ordenarse por tales columnas.

Finalmente, el método asigna los datos de ejemplo a la variable de representación de datos de la clase, llamada items.

Antes de mostrar realmente cada columna, WordPress busca métodos llamados column_{nombre_de_clave}, por ejemplo function column_booktitle. Debería existir un método para cada columna definida. Para evitar la necesidad de crear un método para cada columna, en el método column_default, se procesará cualquier columna para la cual no se haya definido ningún método especial:

function column_default( $item, $column_name ) {
  switch( $column_name ) { 
    case 'booktitle':
    case 'author':
    case 'isbn':
      return $item[ $column_name ];
    default:
      return print_r( $item, true ) ; // Mostramos todo el arreglo para resolver problemas
  }
}

En nuestro ejemplo, el método devolverá el título para cada columna y, si no la encuentra, mostrará el contenido del areglo $item para fines de depuración.

Estos son los ingredientes esenciales para definir una clase de tabla personalizada. Lo que se debe hacer a continuación es agregar una página de administración al backend, crear una instancia de nuestra clase, preparar los elementos y llamar al método display() para mostrar la tabla:

 function my_add_menu_items(){
    add_menu_page( 'Mi tabla', 'Mi tabla', 'activate_plugins', 'my_list_test', 'mi_pagina de lista' );
}
add_action( 'admin_menu', 'my_add_menu_items' );

function my_render_list_page(){
  $myListTable = new My_Table();
  echo '<div class="wrap"><h2>Mi prueba de tabla</h2>'; 
  $myListTable->prepare_items(); 
  $myListTable->display(); 
  echo '</div>'; 
}

Esta es la versión mínima posible de una tabla:

Ordenamiento

Hasta el momento los elementos aparecen en el orden en que están definidos en el código, ya que la clase My_Table no contiene ningún código para ordenar. Lo que sí contiene es algo de código para marcar ciertas columnas como ordenables. En la sección «Conceptos básicos» ya existía una línea $sortable = array(); que ahora se puede cambiar a:

$sortable = $this->get_sortable_columns();

Adicionalmente necesitamos el método:

function get_sortable_columns() {
  $sortable_columns = array(
    'booktitle' => array( 'booktitle',false ),
    'author' => array( 'author', false ),
    'isbn'  => array( 'isbn', false )
  );
  return $sortable_columns;
}

De esta manera, los encabezados de columna mencionados anteriormente se cambian a enlaces y muestran pequeños triángulos si el ratón se desplaza sobre ellos. El segundo parámetro en el arreglo de valores de $sortable_columns toma en cuenta una posible columna ordenada previamente. Si el valor es true, se asume que la columna está en orden ascendente, si el valor es false la columna se supone que está en orden descendente o no ordenado. Esto es necesario para que el triángulo pequeño al lado del nombre de la columna indique el orden de clasificación en la dirección correcta:

Si hace clic en el encabezado de la columna, la página se vuelve a cargar y $_GET contiene algo como esto:

array
  'page' => string 'Mi tabla'
  'orderby' => string 'booktitle'
  'order' => string 'asc'

Con esta información puede escribir un método para ordenar nuestros datos:

function usort_reorder( $a, $b ) {
  // Si no se especifica columna, por defecto el título
  $orderby = ( ! empty( $_GET['orderby'] ) ) ? $_GET['orderby'] : 'booktitle';
  // Si no hay orden, por defecto asendente
  $order = ( ! empty($_GET['order'] ) ) ? $_GET['order'] : 'asc';
  // Determina el orden de ordenamiento
  $result = strcmp( $a[$orderby], $b[$orderby] );
  // Envía la dirección de ordenamiento final a usort
  return ( $order === 'asc' ) ? $result : -$result;
}

Para ordenar realmente los datos tenemos que ampliar prepare_items():

function prepare_items() {
  [..]
  usort( $this->example_data, array( &$this, 'usort_reorder' ) );
  $this->items = $this->example_data;
}

Si está recuperando los datos de la base de datos (lo que es más probable), es mejor utilizar los ORDERBY de SQL directamente.

Acciones

Si no solo desea mostrar los elementos, sino también manipularlos, debe definir algunas acciones:

function column_booktitle( $item ) {
  $actions = array(
            'edit'      => sprintf('<a href="?page=%s&action=%s&book=%s">Editar</a>',$_REQUEST['page'],'edit',$item['ID'] ),
            'delete'    => sprintf('<a href="?page=%s&action=%s&book=%s">Papelera</a>',$_REQUEST['page'],'delete',$item['ID'] ),
        );

  return sprintf('%1$s %2$s', $item['booktitle'], $this->row_actions( $actions ) );
}

Estas acciones aparecerán si el usuario pasa el cursor del ratón sobre el registro:

Si se hace clic en uno de los enlaces de acción, el formulario devolverá, por ejemplo, los siguientes datos en $_GET:

array
  'page' => string 'Mi tabla'
  'action' => string 'delete'
  'book' => string '2'

Acciones conjuntas

Las acciones conjuntas se implementan sobrescribiendo el método get_bulk_actions() y devolviendo un arreglo asociado:

function get_bulk_actions() {
  $actions = array(
    'delete' => 'Borrar'
  );
  return $actions;
}

Esto sólo coloca el menú desplegable y el botón de aplicar encima y debajo de la tabla:

Las casillas de verificación de las filas deben definirse por separado. Como se mencionó anteriormente, hay un método column_{clave de la columna} para representar una columna. La columna column_cb es un caso especial:

function column_cb($item) {
  return sprintf(
         '<input type="checkbox" name="book[]" value="%s" />', $item['ID']
         );    
}

Actualmente, este método no se procesará porque tenemos que informar a la clase sobre la nueva columna extendiendo el método get_columns():

function get_columns() {
  $columns = array(
    'cb' => '<input type="checkbox" />',
[..]
}

Esto también coloca la casilla de verificación «seleccionar todo» en la barra de título:

Si no desea mostrar la casilla de verificación en el título, simplemente se debe establecer el valor en una cadena vacía. Sin embargo, todavía se debe definir el par clave/valor, de lo contrario no se mostrarán casillas de verificación.

Si se presiona «Aplicar», el formulario devolverá varias variables: action y action2 que contendrán la acción seleccionada o -1 si el usuario no seleccionó ninguna acción, y si se seleccionó alguna casilla de verificación, en nuestro caso books, por ejemplo:

'action' => string 'delete'
'book' => 
  array
    0 => string '2'
    1 => string '6'
'action2' => string '-1'

La variable action contiene la selección del cuadro superior, action2 la selección del cuadro de inferior y book la identificación de las filas seleccionadas, si corresponde, Se puede utilizar el método current_action() para consultar action/action2 :

$action = $ this->current_action();

devolverá action si tiene un valor, de lo contrario action2. Si no se establece nada, el método regresa FALSE.

Paginación

Lo primero es lo primero: WordPress no pagina sus datos de ninguna manera. Sólo contiene un método para mostrar una barra de navegación en la parte superior e inferior derecha de las tablas:

Debe indicar al método cuántos elementos tiene en total, cuántos elementos se mostrarán en una página, y lo más importante, los datos que se mostrarán en la página:

function prepare_items() {
  [...]
  $per_page = 5;
  $current_page = $this->get_pagenum();
  $total_items = count( $this->example_data );

  // Sólo necesario porque usamos nuestros datos de ejemplo
  $this->found_data = array_slice( $this->example_data, ( ( $current_page - 1 ) * $per_page ), $per_page );

  $this->set_pagination_args( array(
    'total_items' => $total_items,  // DEBEMOS calcular el número total de elementos
    'per_page'    => $per_page  // DEBEMOS determinar el número de elementos en cada página
  ) );
  $this->items = $this->found_data;
}                      
       

Búsqueda

Si tiene una gran cantidad de datos, un campo de búsqueda simplificará el acceso a ciertos elementos:

$myListTable->search_box( 'search' , 'search_id' ); 

El texto del botón search está definido por el primer parámetro, la identificación de la entrada por el segundo parámetro. El método crea el siguiente resultado:

<p class="search-box">
<label class="screen-reader-text" for="search_id-search-input">
search:</label> 
<input id="search_id-search-input" type="text" name="s" value="" /> 
<input id="search-submit" class="button" type="submit" name="" value="Buscar" />
</p>        

El método colocará el campo de entrada y el botón de búsqueda en el lado derecho y lo diseñará correctamente. El <form> no se genera. Tienes que agregarlo manualmente, en nuestro caso esto sería:

<form method="post">
  <input type="hidden" name="page" value="my_list_test" />
  <?php $this->search_box('search', 'search_id'); ?>
</form>

(El elemento oculto es necesario para cargar la página de la derecha).
Para reaccionar ante el comando de búsqueda, debe verificar el contenido $_POST['s'] y filtrar los datos en consecuencia antes de mostrar la tabla.

Opciones de pantalla

Todas las páginas principales del backend que contienen una tabla proveen una pantalla de opciones donde el usuario puede ajustar las columnas que se mostrarán y el número de filas.

Para agregar opciones a su complemento, necesita modificarse el código actual. Primero debemos asegurarnos de que las opciones de la pantalla se muestren solo en la página actual:

$hook = add_menu_page( 'Mi Tabla', 'Mi Tabla', 'activate_plugins', 'my_list_test', 'my_render_list_page' );
add_action( "load-$hook", 'add_options' );

function add_options() {
  $option = 'per_page';
  $args = array(
         'label' => 'Libros',
         'default' => 10,
         'option' => 'books_per_page'
         );
  add_screen_option( $option, $args );
}

Esto sólo muestra el campo de opción y el botón de aplicación, el almacenamiento y la carga de los datos deben definirse por separado. WordPress proporciona un filtro llamado set-screen-option para hacerse cargo de esto:

add_filter('set-screen-option', 'test_table_set_option', 10, 3);
function test_table_set_option($status, $option, $value) {
  return $value;
}

La opción se almacena en la tabla usermeta en la base de datos para que cada usuario tenga su propia configuración. Para recuperar la opción y ajustar la visualización de la tabla en consecuencia, el método prepare_items debe modificarse (extracto):

function prepare_items() {
[..]

  //Paginado
  $per_page = $this->get_items_per_page( 'books_per_page', 5 );
  $current_page = $this->get_pagenum();

  [...]

En lugar de simplemente asignar un número, se carga el valor especificado por el usuario. Si el usuario no ha cambiado el valor, no hay tal opción almacenada en la base de datos y se toma un valor predeterminado.

WordPress agrega automáticamente las casillas de verificación para ocultar/mostrar las columnas. Solo debe asegurarse de que su clase derivada esté instanciada antes de que el panel de opciones de pantalla se represente para que la clase principal pueda recuperar los nombres de las columnas. Para lograr esto, el código correspondiente se mueve al método add_options():

function add_options() {
    global $myListTable;
 
    $option = 'per_page';
    $args = array(
        'label' => 'Libros',
        'default' => 10,
        'option' => 'books_per_page'
    );
    add_screen_option( $option, $args );

    $myListTable = new My_Table;
} 

Las selecciones del usuario se guardan automáticamente a través de las funciones Ajax. Sin embargo, debe asegurarse de que las columnas estén ocultas si la página se carga inicialmente. El método get_column_info() devuelve todas las columnas, las ocultas y las que se pueden ordenar. En el método prepare_items() en lugar de:

$columns = $this->get_columns();
$hidden = array();
$sortable = $this->get_sortable_columns();
$this->_column_headers = array( $columns, $hidden, $sortable );

Ahora estaría:

$this->_column_headers = $this->get_column_info();

Y las columnas se configuran de acuerdo a las opciones de pantalla.

Nota: debe evitar usar algunas cadenas como nombres clave, ya que WordPress los trata de forma especial:

$special = array( '_title', 'cb', 'comment', 'media', 'name', 'title', 'username', 'blogname' );

Su tabla aún funcionaría, pero no podrá mostrar/ocultar las columnas.

Estilizando la tabla

Actualmente la tabla tiene el estilo de los valores por defecto de WordPress. Para cambiar esto tienes que adaptar las clases CSS que se asignan automáticamente a cada columna. El nombre de la clase consiste en la cadena «column-» y el nombre clave del arreglo $columns, por ejemplo, «column-isbn» o «column-author». Como ejemplo para redefinir el ancho de las columnas (para simplificar, los datos de estilo se escriben directamente en el encabezado HTML):

function _construct() {
  [...]
  add_action( 'admin_head', array( &$this, 'admin_header' ) );
  [...]
}

function admin_header() {
  $page = ( isset($_GET['page'] ) ) ? esc_attr( $_GET['page'] ) : false;
  if( 'my_list_test' != $page )
    return; 

  echo '<style type="text/css">';
  echo '.wp-list-table .column-id { width: 5%; }';
  echo '.wp-list-table .column-booktitle { width: 40%; }';
  echo '.wp-list-table .column-author { width: 35%; }';
  echo '.wp-list-table .column-isbn { width: 20%; }';
  echo '</style>';
}

Otras personalizaciones

Si no hay elementos en la lista, el mensaje estándar es: «No se encontraron elementos». Si desea cambiar este mensaje, puede sobrescribir el método no_items():

function no_items () { 
  _e( 'No se encontraron libros amigo.' );
}   

Enlaces web

Este artículo es una traducción y adaptación de WPengineer.com. El artículo original se puede consultar aquí.