List Table Generator
A WP_List_Table subclass with sortable columns, row and bulk actions, search and the admin page that renders it.
The table
Columns
4 columns · 3 sortable
Titletitle
SortablePrimary
Authorauthor
SortablePrimary
Statusstatus
SortablePrimary
Createdcreated
SortablePrimary
Actions
2 row · 1 bulk · 3 views
Table extras
Search box
search_box() above the table, wired to the s parameter.
Per-page screen option
add_screen_option plus the set-screen-option filter that lets it save.
<?php // Generated with WP CodeKit — powered by GrowQuest (https://growquest.io). /** * Plugin Name: Briefs list table * Description: An admin table of briefs with sorting, search and bulk actions. * Version: 1.0.0 * Requires PHP: 7.4 */ defined( 'ABSPATH' ) || exit; if ( ! class_exists( 'WP_List_Table' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php'; } /** * A table of briefs. */ class Acme_Briefs_Table extends WP_List_Table { /** * Tell the parent what one row is called. */ public function __construct() { parent::__construct( array( 'singular' => 'brief', 'plural' => 'briefs', 'ajax' => false, ) ); } /** * The columns, in order. * * @return array */ public function get_columns() { return array( 'cb' => '<input type="checkbox" />', 'title' => __( 'Title', 'acme' ), 'author' => __( 'Author', 'acme' ), 'status' => __( 'Status', 'acme' ), 'created' => __( 'Created', 'acme' ), ); } /** * Which columns can be sorted, and which is sorted by default. * * @return array */ public function get_sortable_columns() { return array( 'title' => array( 'title', true ), 'status' => array( 'status', false ), 'created' => array( 'created', false ), ); } /** * Which column carries the row actions. * * @return string */ protected function get_default_primary_column_name() { return 'title'; } /** * The checkbox column. * * @param array $item One row. * @return string */ public function column_cb( $item ) { return sprintf( '<input type="checkbox" name="briefs[]" value="%s" />', esc_attr( $item['id'] ) ); } /** * The bulk actions dropdown. * * @return array */ public function get_bulk_actions() { return array( 'delete' => __( 'Delete permanently', 'acme' ), ); } /** * Run a bulk action. Called before anything is fetched. */ protected function process_bulk_action() { $action = $this->current_action(); if ( ! $action ) { return; } check_admin_referer( 'bulk-' . $this->_args['plural'] ); if ( ! current_user_can( 'edit_others_posts' ) ) { wp_die( esc_html__( 'You are not allowed to do that.', 'acme' ) ); } $ids = isset( $_REQUEST['briefs'] ) ? array_map( 'absint', (array) $_REQUEST['briefs'] ) : array(); if ( ! $ids ) { return; } switch ( $action ) { case 'delete': // Delete permanently — act on $ids here. break; } } /** * The status links above the table. * * @return array */ protected function get_views() { $current = isset( $_REQUEST['status'] ) ? sanitize_key( $_REQUEST['status'] ) : 'all'; $base = remove_query_arg( array( 'status', 'paged' ) ); $views = array(); $views['all'] = sprintf( '<a href="%1$s"%2$s>%3$s</a>', esc_url( add_query_arg( 'status', 'all', $base ) ), 'all' === $current ? ' class="current"' : '', esc_html( __( 'All', 'acme' ) ) ); $views['draft'] = sprintf( '<a href="%1$s"%2$s>%3$s</a>', esc_url( add_query_arg( 'status', 'draft', $base ) ), 'draft' === $current ? ' class="current"' : '', esc_html( __( 'Drafts', 'acme' ) ) ); $views['review'] = sprintf( '<a href="%1$s"%2$s>%3$s</a>', esc_url( add_query_arg( 'status', 'review', $base ) ), 'review' === $current ? ' class="current"' : '', esc_html( __( 'In review', 'acme' ) ) ); return $views; } /** * The primary column, with its row actions. * * @param array $item One row. * @return string */ public function column_title( $item ) { $actions = array( 'edit' => sprintf( '<a href="%1$s">%2$s</a>', esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'edit', 'brief' => $item['id'] ) ), 'acme_edit_' . $item['id'] ) ), esc_html( __( 'Edit', 'acme' ) ) ), 'delete' => sprintf( '<a href="%1$s" class="submitdelete">%2$s</a>', esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'delete', 'brief' => $item['id'] ) ), 'acme_delete_' . $item['id'] ) ), esc_html( __( 'Delete', 'acme' ) ) ), ); return sprintf( '<strong>%1$s</strong>%2$s', esc_html( $item['title'] ), $this->row_actions( $actions ) ); } /** * @param array $item One row. * @return string */ public function column_status( $item ) { return sprintf( '<span class="acme-status acme-status--%1$s">%2$s</span>', esc_attr( $item['status'] ), esc_html( ucfirst( $item['status'] ) ) ); } /** * @param array $item One row. * @return string */ public function column_created( $item ) { return esc_html( mysql2date( get_option( 'date_format' ), $item['created'] ) ); } /** * Anything without its own column_ method lands here. * * @param array $item One row. * @param string $column_name The column. * @return string */ public function column_default( $item, $column_name ) { return isset( $item[ $column_name ] ) ? esc_html( $item[ $column_name ] ) : ''; } /** * Shown when there is nothing to list. */ public function no_items() { esc_html_e( 'No briefs yet.', 'acme' ); } /** * Fetch, paginate and hand the rows to the parent. * * Order matters: bulk action, then fetch, then pagination, then items. */ public function prepare_items() { $this->_column_headers = array( $this->get_columns(), array(), $this->get_sortable_columns() ); $this->process_bulk_action(); $per_page = $this->get_items_per_page( 'acme_briefs_per_page', 20 ); $paged = $this->get_pagenum(); $offset = ( $paged - 1 ) * $per_page; global $wpdb; $table = $wpdb->prefix . 'acme_briefs'; $orderby = isset( $_REQUEST['orderby'] ) ? sanitize_key( $_REQUEST['orderby'] ) : 'title'; $order = isset( $_REQUEST['order'] ) && 'desc' === strtolower( $_REQUEST['order'] ) ? 'DESC' : 'ASC'; $search = isset( $_REQUEST['s'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : ''; // Only ever sort by a column you declared sortable. $allowed = array_keys( $this->get_sortable_columns() ); if ( ! in_array( $orderby, $allowed, true ) ) { $orderby = 'title'; } $where = 'WHERE 1=1'; $args = array(); if ( '' !== $search ) { $where .= ' AND title LIKE %s'; $args[] = '%' . $wpdb->esc_like( $search ) . '%'; } $args[] = $per_page; $args[] = $offset; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $sql = "SELECT * FROM {$table} {$where} ORDER BY {$orderby} {$order} LIMIT %d OFFSET %d"; $items = $wpdb->get_results( $wpdb->prepare( $sql, $args ), ARRAY_A ); $total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table} {$where}" ); $this->set_pagination_args( array( 'total_items' => $total, 'per_page' => $per_page, 'total_pages' => (int) ceil( $total / $per_page ), ) ); $this->items = $items; } }