¿Cómo crear una tabla con estilo admin en WordPress?
¿Cuál es la forma recomendada de crear una página con una tabla, siguiendo el estilo de las tablas que muestran entradas o usuarios en el área de administración?
Estoy expandiendo el plugin Cache Images, y contiene una tabla con dominios y un número de imágenes de ese dominio. Por lo tanto, no hay una tabla equivalente existente sobre la cual pueda construir (en la primera versión de esta pregunta, pregunté sobre una tabla con entradas, pero ahí podría (tal vez) expandir la tabla de entradas existente).
¿Debería simplemente basarme en la página de vista general de entradas, y comenzar con un <table class="widefat">
, o hay mejores funciones que manejen esto ahora? ¿Conocen algún ejemplo limpio y vacío de una tabla con paginación en el que pueda basar mi trabajo?

Esto es lo que generalmente uso:
<table class="widefat fixed" cellspacing="0">
<thead>
<tr>
<th id="cb" class="manage-column column-cb check-column" scope="col"></th> // esta columna contiene casillas de verificación
<th id="columnname" class="manage-column column-columnname" scope="col"></th>
<th id="columnname" class="manage-column column-columnname num" scope="col"></th> // "num" añadido porque la columna contiene números
</tr>
</thead>
<tfoot>
<tr>
<th class="manage-column column-cb check-column" scope="col"></th>
<th class="manage-column column-columnname" scope="col"></th>
<th class="manage-column column-columnname num" scope="col"></th>
</tr>
</tfoot>
<tbody>
<tr class="alternate">
<th class="check-column" scope="row"></th>
<td class="column-columnname"></td>
<td class="column-columnname"></td>
</tr>
<tr>
<th class="check-column" scope="row"></th>
<td class="column-columnname"></td>
<td class="column-columnname"></td>
</tr>
<tr class="alternate" valign="top"> // esta fila contiene acciones
<th class="check-column" scope="row"></th>
<td class="column-columnname">
<div class="row-actions">
<span><a href="#">Acción</a> |</span>
<span><a href="#">Acción</a></span>
</div>
</td>
<td class="column-columnname"></td>
</tr>
<tr valign="top"> // esta fila contiene acciones
<th class="check-column" scope="row"></th>
<td class="column-columnname">
<div class="row-actions">
<span><a href="#">Acción</a> |</span>
<span><a href="#">Acción</a></span>
</div>
</td>
<td class="column-columnname"></td>
</tr>
</tbody>
</table>
Espero que esto ayude.

¿también es posible tener una paginación automática insertada así? (ej. mostrando posts 1-20)

@MichielStandaert si quieres un resultado paginado puedes usar paginate_links

¡Gracias! (Pero sigo preguntándome por qué no usaron :odd
para las filas en lugar de hacernos agregar una clase cada dos filas...)

Utiliza la API del Core, no solo su CSS
Normalmente solo utilizas una instancia de la clase WP_List_Table
.
Guías:
- Más información en el Codex aquí.
- Aquí también hay una guía de WP Engineer - demasiado para copiarla aquí.
- Y otra guía en Smashing Magazine online.
¿Beneficios?
¡SÍ!
Puedes añadir paginación, cajas de búsqueda, acciones y cualquier magia que puedas imaginar (y seas capaz de programar).

Pequeña pista como enlace para ver el marcado, clases para la interfaz de administración, sin otro objetivo que crear tablas: https://github.com/bueltge/WordPress-Admin-Style

>El acceso a esta clase está marcado como privado. Eso significa que no está destinado para su uso por parte de desarrolladores de plugins y temas, ya que puede cambiar sin previo aviso en cualquier versión futura de WordPress. Si aún deseas hacer uso de la clase, deberías hacer una copia para usar y distribuir con tu propio proyecto, o usarla bajo tu propio riesgo.

@AustinPray ¿Una copia? No, por favor no hagas eso. Hay versiones beta, RC y otras pre-release de WP disponibles. Simplemente actualiza tu implementación/extensión. Si realmente tienes que ir por otro camino, escribe algo mejor por tu cuenta. El código del núcleo no es tan bueno.

@kaiser No mates al mensajero, esas no son mis palabras. Estaba citando del WP Codex. Aunque registrarte para pruebas de regresión perpetuas con cada beta y RC no suena mucho mejor que copiar la clase. Estoy de acuerdo en que escribir tu propia clase simple es un mejor camino a seguir.

@AustinPray Sin resentimientos :) El Codex está escrito por personas como tú y yo. De hecho, puedes ir ahora y revertir esa declaración y la gente la citará.

Desafortunadamente, ambas guías están terriblemente desactualizadas, a partir de Wordpress 5, lo que descubrí por las malas al romper cosas (Además, la de Smashing Magazine tiene errores de sintaxis evidentes - alguien no dominó el copiar y pegar). No he encontrado una alternativa actualizada (el custom-list-table-example
es solo ligeramente mejor) y el Codex tiene muchas cosas faltantes. Logré escribir una implementación simple mirando el propio Wordpress, especialmente class-wp-users-list-table.php
que está casi completamente funcional pero más simple que otras clases de WP.

@Guss La respuesta es del 2012. Quizás quieras añadir tu propia respuesta para ofrecer una solución más actualizada :)

Utiliza este ejemplo (escrito como un plugin) para crear tus tablas de administración:
http://wordpress.org/extend/plugins/custom-list-table-example/
Utiliza la clase incorporada WP_List_Table.

Creo que esta debería ser la respuesta aceptada. También puedes ver este artículo en Smashing Magazine que plantea un enfoque similar: http://wp.smashingmagazine.com/2011/11/03/native-admin-tables-wordpress/

También puedes usar este pequeño plugin para ver las posibilidades del backend en WP: https://github.com/bueltge/WordPress-Admin-Style

Hay muchas buenas opciones aquí. Pero no hay una rápida y sencilla:
<table class="widefat striped fixed">
<thead>
<tr>
<th>Encabezado1</th>
<th>Encabezado2</th>
<th>Encabezado3</th>
</tr>
</thead>
<tbody>
<tr>
<td>Contenido1</td>
<td>Contenido2</td>
<td>Contenido3</td>
</tr>
</tbody>
<tfoot>
<tr>
<th>Pie1</th>
<th>Pie2</th>
<th>Pie3</th>
</tr>
</tfoot>
</table>
Explicación
- La clase
widefat
es usada por elcommon.css
, cargado en el administrador, para que parezca una tabla de WP. - La clase
striped
le da un aspecto de rayas (qué sorpresa). - La clase
fixed
añade el CSS:table-layout: fixed;

Para aquellos que buscan implementar WP_List_Table
, ten en cuenta que todas las guías que encontré están lamentablemente desactualizadas y te harán escribir código redundante o incluso te pedirán que hagas cosas que ya no funcionan.
Aquí tienes un ejemplo mínimo que funciona hasta cierto punto. Debería ser fácil de entender sin una "guía" y te ayudará a comenzar.
Incluye:
- filtros rápidos (vistas)
- cuadro de búsqueda
- acciones de fila
Faltan:
- configuración del tamaño de página (realmente no he visto que una página de WordPress use esto)
- acciones masivas
- filtros desplegables
class Mi_Tabla_Lista extends WP_List_Table {
function __construct() {
parent::__construct([
'singular' => 'empleado',
'plural' => 'empleados',
]);
}
function get_columns() {
return [
'name' => __('Nombre'),
'employer' => __('Empleador'),
'rank' => __('Rango'),
'phone' => __('Teléfono'),
'joined' => __('Fecha de Ingreso'),
];
}
/* Opcional - sin esto ninguna columna es ordenable */
public function get_sortable_columns() {
return [
// las claves son "nombre_columna" como arriba
// los valores son nombres de campo "orden" según lo que necesite tu modelo de datos
'name' => 'name',
'employer' => 'employer',
'rank' => 'rank',
'joined' => 'joined',
];
}
public function prepare_items() {
// soporte para el cuadro de búsqueda
$search = @$_REQUEST['s'] ? wp_unslash(trim($_REQUEST['s']))) : '';
// obtener la configuración de número de registros por página del almacenamiento de opciones
$per_page = $this->get_items_per_page('mi_tabla_lista_por_pagina');
// llenar el array de datos con los elementos de tu modelo. En mi implementación estos
// son instancias de StdClass con varios campos, pero puede ser cualquier cosa
// lo veremos en un momento cómo.
$this->items = obtener_elementos_modelo([
'offset' => ($this->get_pagenum()-1)*$per_page,
'count' => $per_page,
'orderby' => @$_GET["orderby"] ?: 'id', // campo de orden predeterminado, si no se especifica
'order' => @$_GET["order"] ?: 'ASC', // dirección de orden predeterminada
'search' => $search, // pasar campo de búsqueda si está configurado
'status' => @$_REQUEST['status'] // pasar filtro de vista, si está configurado [ver get_views()]
);
$this->set_pagination_args([
"total_items" => obtener_conteo_elementos_modelo(),
"per_page" => $per_page,
]);
// `obtener_conteo_elementos_modelo` debe ser el número total de registros después
// del filtrado (vistas y búsqueda) pero antes de la paginación. Esto puede ser difícil/ineficiente
// de hacer con MySQL. Si deseas poner aquí los resultados de `COUNT(*)`,
// nadie te culpará.
}
public function column_default($item, $column_name) {
// presentación predeterminada de columna
// La mayoría de los campos de mi objeto son imprimibles tal cual, así que tenemos un método
// genérico para manejar eso.
return $item->$column_name;
}
/* Opcional, a menos que tengas datos que requieran formato especial */
public function column_joined($item) {
// El campo 'joined' es un objeto DateTime y no puede ser convertido implícitamente
// a string por la lógica incorporada, así que necesitaremos hacerlo
return $item->joined->format("Y-m-d");
}
/* Opcional - dibujar filtros rápidos en la parte superior de la tabla */
public function get_views() {
$crear_enlace = function($valor_filtro, $nombre) { // herramienta DRY para creadores de vistas
$nombre_filtro = 'status';
return '<a href="'
. esc_url(add_query_arg($nombre_filtro, $valor_filtro)) . '" ' .
(@$_REQUEST[$nombre_filtro]==$valor_filtro ?
'class="current" aria-current="page"' : ''). ">" .
$nombre . "</a>";
};
return [
'all' => $crear_enlace(false, __('Todos')),
'green' => $crear_enlace('green', __('Novatos')),
'pros' => $crear_enlace('pro', __('Expertos')),
'bofh' => $crear_enlace('veterano', __('Tipos veteranos')),
];
}
/* Opcional: acciones de fila */
public function handle_row_actions($item, $column_name, $primary) {
$salida = parent::handle_row_actions($item, $column_name, $primary);
if ($column_name === $primary)
$salida .= $this->row_actions([
'editar' => sprintf('<a href="%s">%s</a>',
add_query_arg('employee_id', $item->id, admin_url('admin.php?page=editar-empleado')),
__("Editar")),
'eliminar' => sprintf('<a href="%s">%s</a>',
add_query_arg('employee_id', $item->id, admin_url('admin.php?page=eliminar-empleado')),
__("Eliminar")),
]);
return $salida;
}
}
Entonces la función de la página de administración (para add_menu_page
/add_submenu_page
) podría verse así:
function dibujarPaginaAdmin() {
$mi_tabla_lista = new Mi_Tabla_Lista();
$mi_tabla_lista->prepare_items();
?>
<div class="wrap">
<h1 class="wp-heading-inline"><?php _e('Título de la Página de Admin')?></h1>
<hr class="wp-header-end">
<?php $mi_tabla_lista->views() ?>
<form id="employee-filter" method="get">
<input type="hidden" name="page" value="<?php echo $_REQUEST['page']?>">
<?php $mi_tabla_lista->search_box(__('Buscar'), 'empleado') ?>
<?php $mi_tabla_lista->display(); ?>
</form>
</div>
<?php
}

¿Quizás quieras considerar agregar un filtro a la lista de tu tipo de entrada personalizada en el administrador? La respuesta enlazada abajo muestra cómo hacerlo con una taxonomía, pero podrías fácilmente usar otros criterios en tu hook restrict_manage_posts
:
Déjame saber si tienes más preguntas.

Me disculpo por la pregunta poco clara. En mi primer ejemplo era una tabla de publicaciones, y de hecho, podría intentar usar la tabla de publicaciones existente para eso (¿incluso si solo quiero mostrar los títulos de las publicaciones y luego todas las columnas personalizadas?). Pero ahora he editado mi pregunta con un ejemplo concreto: tengo una tabla de dominios, así que no hay una tabla existente equivalente que pueda expandir.

@Jan: Ah. Sí, creo que has dado con la verdad, que no hay una buena forma encapsulada de hacer esto aparte de escribir HTML (duplicado). A menudo he tenido el mismo problema. Tal vez crea un ticket en trac solicitando esta mejora y enlace la URL/número de ticket aquí para que podamos apoyarlo.
