List Tables

If the menu type is list_table, the callback defines the list table instead of the menu page. The list table callback involves defining and returning an array consisting of strings, arrays or callbacks. You can also use callbacks in the list table definition for dynamic strings or arrays.

The documentation is based on Cars sample dataset and code.

function saturn_tables_cars_list_table_func($page = "", $item = array(), $page_number = 1) {

}

Menu Title (string)

array key: menu_title

The Saturn Tables list table callable menu title array item will override the menu title defined in the menu declaration if set. This allows menu titles to be dynamic or to be removed if desired.

Behavior:

menu_title not set: Default menu from menu declaration
menu_title set and blank: No menu title displayed
menu_title defined: Overrides default menu title

Menu title in sample list table

$add_new_link =  admin_url("/admin.php/?page=my-cars-add-edit-car&id=0");
$list_table_definitions['menu_title'] = '<h1 class="wp-heading-inline">View Cars</h1><a href="' . $add_new_link . '" class="page-title-action">Add New</a>';

Notices (callable)

array key: notices

Standard admin notices are available through a Saturn Tables callable and are hooked to the admin_notices hook. In general, you need to make use of form or query string variables to manipulate admin notices. Since admin notices in Saturn Tables are called via a hook the are echoed out and not returned programmatically.

For more on admin notices go to https://codex.wordpress.org/Plugin_API/Action_Reference/admin_notices

function saturn_tables_cars_admin_notices() {

    if (isset($_GET['delete']) && ($model = $_GET['delete']) ) {
    echo '<div class="notice notice-success is-dismissible"><p>' . __( "Model $model has been deleted.", "saturn-tables" ) . '</p></div>';
    } elseif (isset($_GET['trash']) && ($model = $_GET['trash'])) {
    echo '<div class="notice notice-success is-dismissible"><p>' . __( "Model $model has been placed in the trash.", "saturn-tables" ) . '</p></div>';
    } elseif (isset($_GET['bulk']) && ($bulk = $_GET['bulk']) == 'trash') {
    $count = $_GET['count'];
    echo '<div class="notice notice-success is-dismissible"><p>' . __( "$count item(s) have been placed in the trash.", "saturn-tables" ) . '</p></div>';
    }  elseif (isset($_GET['bulk']) && ($bulk = $_GET['bulk']) == 'delete') {
    $count = $_GET['count'];
    echo '<div class="notice notice-success is-dismissible"><p>' . __( "$count item(s) have been deleted.", "saturn-tables" ) . '</p></div>';
    }    
}

Header (string)

array key: header

The header array item is a HTML string that is displayed between the menu title and the list table. It is displayed within the form so form variables can be embedded, any HTML content will be displayed.

$list_table_definitions['header'] = '<p>The cars database is a short list of car information.</p>';

Columns (array)

array key: columns
array values: array(columns key => column name ...)

The columns array item defines the columns that will be displayed when rendering the list table, Aside from the checkbox definition, column array keys must match the array keys of the items, or rows, returned by the list table query when the get_data array is formed with the database query.

$list_table_definitions['columns'] = array('cb' => '<input type="checkbox" />', 'make' => "Make", 'model' => "Model", 'mpg' => "MPG", 'cylinders' => "Cylinders", 'weight' => "Weight", 'model_year' => "Model Year", 'country' => "Country");      

Get Data (callable)

array key: get_data
callable return value: array of array(column key => column value ...)

The get_data array returns the rows of data to be displayed and used in list table processing generally called items in list table construction. The array is returned in rows of associative arrays with the associative keys corresponding to the keys of the columns array. This array of arrays can correspond directly to a $wpdb result set if the result set is returned in ARRAY_A format. This array and database query must also account for pagination and any search terms or WHERE clauses. These parameters can be brought into the callable via query string.

WordPress $wpdb documentation available at https://codex.wordpress.org/Class_Reference/wpdb

$list_table_definitions['get_data'] = 'saturn_tables_cars_return_items';
function saturn_tables_cars_return_items($per_page, $page_number) {

    global $wpdb;

    $offset = ($per_page * ($page_number -1));

    $orderby_clause = "";
    if (isset($_GET['orderby']) && isset($_GET['order'])) {
        $orderby = esc_sql($_GET['orderby']);
        $order = esc_sql($_GET['order']);
        $orderby_clause = "ORDER BY $orderby $order";
    }

    $where_clause[] =  1;
    if (isset($_GET['status'])) {
        $status = ($_GET['status'] == "trash") ? 1 : 0;
        $where_clause[] =  "status = $status";
    } else {
        $where_clause[] =  "status = 0";
    }
    if (isset($_GET['s'])) {
        $search = esc_sql($_GET['s']);
        $where_clause[] =  "CONCAT_WS(' ', make, model) LIKE '%$search%'";
    }
    if (isset($_GET['make'])) {
        $make = esc_sql($_GET['make']);
        $where_clause[] =  ($make == "All Makes") ? 1 : "make = '$make'";
    }
    $where_clause = implode(" AND ", $where_clause);

    $query = "SELECT id, make, model, mpg, cylinders, weight, model_year, country FROM saturn_tables_cars WHERE $where_clause $orderby_clause LIMIT $per_page OFFSET $offset";
    $items = $wpdb->get_results($query, ARRAY_A);
    $wpdb->flush();

    return $items;

}

Get Data Count (callable)

array key: get_data_count
callable returns: integer

The get_data_count functionality returns the total count of rows returned in the list table for display and pagination. This count should take into account the search terms and WHERE clauses but ignore pagination, the query is similar to the get_data query without pagination.

WordPress $wpdb documentation available at https://codex.wordpress.org/Class_Reference/wpdb

$list_table_definitions['get_data_count'] = 'saturn_tables_cars_return_items_count';
function saturn_tables_cars_return_items_count() {

    global $wpdb;

    $where_clause[] =  1;
    if (isset($_GET['status'])) {
        $status = ($_GET['status'] == "trash") ? 1 : 0;
        $where_clause[] =  "status = $status";
    }
    if (isset($_GET['s'])) {
        $search = esc_sql($_GET['s']);
        $where_clause[] =  "CONCAT_WS(' ', make, model) LIKE '%$search%'";
    }
    if (isset($_GET['make'])) {
        $make = esc_sql($_GET['make']);
        $where_clause[] =  ($make == "All Makes") ? 1 : "make = '$make'";
    }
    $where_clause = implode(" AND ", $where_clause);

    $query = "SELECT count(*) FROM saturn_tables_cars WHERE $where_clause";

    return  $wpdb->get_var($query);

}

Column Markup

array key: column_markup
array values: array(column key => array(markup...))

The column_markup definition defines the markup such as applying a bold font weight, font color or links surrounding a data value. Generally it would be a bold link to edit the row itself through an input form or maybe a link on a data value to apply a filter based on the value. The column markup simply applies formatting and HTML to the data based on the column key.

$list_table_definitions['column_markup'] = call_user_func('saturn_tables_cars_list_table_markup', $page, $item, $page_number);
function saturn_tables_cars_list_table_markup($page, $item, $page_number) {

    $item_id = (isset($item['id']) && ((int)$item['id'] > 0)) ? $item['id'] : 0;
    $make = (isset($item['make'])) ? $item['make'] : "";

    return array('make' => sprintf('<strong><a href="?page=%s&id=%d">%s</a></strong>', 'saturn-tables-cars-add-edit-car', $item_id,  $make ) );

}

Link Actions (array)

array key: link_actions
array values: array(column key => array(action links...))

The link_actions definition defines the links that generally appear when hovering over the primary data column. The actions usually consist of links which either open up editing pages or similar, or cause a delete through the process_link_actions callable. Although actions are defined as an array, since they usually include per rows values and are different in Standard or Trash views a defined callable is usually best. In general an action must link to an appropriate query string with values that will be used in processing via process_link_actions.

$list_table_definitions['link_actions'] = call_user_func('saturn_tables_cars_list_table_actions', $page, $item, $page_number);
function saturn_tables_cars_list_table_actions ( $page, $item, $page_number) {

    $item_id = (isset($item['id']) && ((int)$item['id'] > 0)) ? $item['id'] : 0;

    if (isset($_GET['status']) && ($_GET['status'] == "trash")) {
            $links = array( 'make' => array('delete' => sprintf( '<a href="?page=%s&delete_id=%d&paged=%d">Delete Permanently</a>', $page, $item_id, $page_number ),
                                            'restore' => sprintf( '<a href="?page=%s&restore_id=%d&paged=%d">Restore</a>', $page, $item_id, $page_number )));

    } else {
            $links = array( 'make' => array('trash' => sprintf( '<a href="?page=%s&trash_id=%d&paged=%d">Trash</a>', $page, $item_id , $page_number ),
                                            'edit' => sprintf( '<a href="?page=%s&id=%d">Edit</a>', 'saturn-tables-cars-add-edit-car', $item_id ) ) );

    }
    return $links;
}

Process Link Actions (callable)

array key: process_link_actions
redirects and exits on execution

The process_link_actions callable executes on list table construction and is designed to use query string values including a test value to invoke execution. If this test value is not set this callable should do nothing. If the test value is set the link action; usually delete, trash or edit, is executed and there is a redirect without this test value causing process_link_actions to then do nothing. On redirect usually some values, such as pagination or view should be specified. The sample code works from the query string so the test value that causes execution must be intentionally removed before redirect or the list table will redirect endlessly on construction. Note that this callable work directly from the links specified in the actions list table definition value.

$list_table_definitions['process_link_actions'] = 'saturn_tables_cars_process_link_actions';
function saturn_table_cars_process_link_actions() {

    global $wpdb;

    if ( (isset($_GET['delete_id']) &&  $delete_id = (int)$_GET['delete_id']) > 0) {

        $model = $wpdb->get_var("SELECT model FROM saturn_tables_cars WHERE id = $delete_id");
        $wpdb->delete( 'saturn_tables_cars', array('id' => $delete_id),  array('id' => '%d'));

        //redirect without delete_id
        parse_str($_SERVER['QUERY_STRING'], $query_string);
        unset($query_string['delete_id']);
        $query_string['delete'] = $model;
        $query_string['status'] = "trash";
        $redirect = admin_url() . "?" . http_build_query($query_string);
        wp_redirect( $redirect );       
        exit;
    } 
    elseif ( isset($_GET['trash_id']) && ($trash_id = (int)$_GET['trash_id']) > 0) {

        $model = $wpdb->get_var("SELECT model FROM saturn_tables_cars WHERE id = $trash_id");
        $wpdb->update( 'saturn_tables_cars',  array('status' => 1), array('id' => $trash_id),  array('%d'),  array('%d') );

        //redirect without trash_id
        parse_str($_SERVER['QUERY_STRING'], $query_string);
        unset($query_string['trash_id']);
        $query_string['trash'] = $model;
        $redirect = admin_url() . "?" . http_build_query($query_string);
        wp_redirect( $redirect );       
        exit;
    }
    elseif ( isset($_GET['restore_id']) && ($restore_id = (int)$_GET['restore_id']) > 0) {

        $model = $wpdb->get_var("SELECT model FROM saturn_tables_cars WHERE id = $restore_id");
        $wpdb->update( 'saturn_tables_cars',  array('status' => 0), array('id' => $restore_id),  array('%d'),  array('%d') );

        //redirect without trash_id
        parse_str($_SERVER['QUERY_STRING'], $query_string);
        unset($query_string['restore_id']);
        $query_string['restore'] = $model;
        $query_string['status'] = "trash";
        $redirect = admin_url() . "?" . http_build_query($query_string);
        wp_redirect( $redirect );       
        exit;
    } 
}

Checkbox Id (string)

array key: checkbox_id

This value specifies the column key used in get_data to define the form value for the checkbox if the checkbox cb is present. Saturn Tables specifically reserves the checkbox cb. Generally the checkbox_id value should be the primary key of the table, it should certainly be unique for each row. The checkbox values are generally used in bulk actions.

$list_table_definitions['checkbox_id'] = 'id';

Bulk Actions (array)

array key: bulk_actions
array value: array(bulk action key and name => bulk action dropdown value)

The bulk_actions array populates the bulk actions dropdown with the array key becoming the option value and the array value becoming the corresponding dropdown display value. The list table bulk action dropdown name is action for the top and action2 for the bottom, which both should be retrieved from the query string when processing the bulk action.

$list_table_definitions['bulk_actions'] = isset($_GET['status']) && ($_GET['status'] == "trash") ? array( 'bulk_delete' => 'Delete' ) : array( 'bulk_trash' => 'Trash' );

Process Bulk Actions (callable)

array key: process_bulk_actions
redirects and exits on execution

The process_bulk_actions callable executes on list table construction and is designed to use query string values based on action and action2 to invoke execution. If action and action2 are not set to a value used for processing this callable should do nothing. Like link actions bulk actions are commonly used to delete or put things in the trash. The process_bulk_actions callable should redirect after processing and usually some values, such as pagination or view should be specified on redirect. The bulk action generally uses checkbox array cb[] during processing and redirects when action or action2 are set.

function saturn_tables_cars_process_bulk_actions() {

    global $wpdb;

    if (isset($_GET['action']) && ($_GET['action'] == 'bulk_trash') || (isset($_GET['action2']) && ($_GET['action2'] == 'bulk_trash' ))) {      

        $trash_ids = esc_sql( $_GET['cb'] );

        $count =  0;        
        foreach ($trash_ids as $id) {
            $affected = $wpdb->update( 'saturn_tables_cars',  array('status' => 1), array('id' => $id),  array('%d'),  array('%d') );
            $count += $affected;
        }

        parse_str($_SERVER['QUERY_STRING'], $query_string);
        unset($query_string['action'],$query_string['action2']);
        $query_string['bulk'] = "trash";
        $query_string['count'] = $count;
        $redirect = admin_url() . "?" . http_build_query($query_string);
        wp_redirect( $redirect );       
        exit;

    } elseif (isset($_GET['action']) && ($_GET['action'] == 'bulk_delete') || (isset($_GET['action2']) && ($_GET['action2'] == 'bulk_delete' ))) {

        $delete_ids = esc_sql( $_GET['cb'] );

        $count =  0;        
        foreach ($delete_ids as $id) {
            $affected = $wpdb->delete( 'saturn_tables_cars', array('id' => $id),  array('id' => '%d'));;
            $count += $affected;
        }

        parse_str($_SERVER['QUERY_STRING'], $query_string);
        unset($query_string['action'],$query_string['action2']);
        $query_string['bulk'] = "delete";
        $query_string['status'] = "trash";
        $query_string['count'] = $count;
        $redirect = admin_url() . "?" . http_build_query($query_string);
        wp_redirect( $redirect );       
        exit;
    }
}

Views (array)

array key: views
array(key => link)

The views definition provides links generally used for different views such as All, Trash or Active. Views generally require some logic, as the active views should not be a link, but instead should be bold. So in the example dataset views, like link_actions, are returned by a callable via call_user_func. Views are generally links with query string variables defining how the data should be returned. Note both get_data and get_data_count must accommodate the view definition.

function saturn_tables_cars_views() {

    if (isset($_GET['status']) && $_GET['status'] == "trash") {
        $all = '<a href="' . admin_url("admin.php?page=my-cars-table&status=all") . '">' . __("All") . '</a>';
        $trash = '<strong>' . __("Trash") . '</strong>';
    } else {
        $all = '<strong>' . __("All") . '</strong>';
        $trash = '<a href="' . admin_url("admin.php?page=my-cars-table&status=trash") . '">' . __("Trash") . '</a>';    
    }

    return array( "all" => $all, "trash" => $trash);
}

Search (array)

array key: search
array(label => label name, id => search id)

The search definition utilizes the standard search functionality used with List Tables. Simply defining the search array will cause the search box with query string variable name “s” to appear. The label will define the search button label and the id will define the text input id. The search terms must be accommodated via get_data and get_data_count for searching.

$list_table_definitions['search'] = array('label'=> "Search Make and Model", 'id' => "search_id");

Extra Navigation (callable)

array key: extra_navigation
callable returning HTML output

The extra_navigation definition is designed so that a filter can be accommodated, generally dropdowns, but it also could be radio buttons or similar. The extra_navigation is outputted into the List Table form and the form values must be processed via query string in get_data and get_data_count. Typically the extra_navigation is followed by the filter button, however the filter button could be omitted for various reasons, including a Javascript submit.

$list_table_definitions['extra_navigation'] = "saturn_tables_cars_make_dropdown";
function saturn_tables_cars_make_dropdown() {

    global $wpdb;

    $makes = $wpdb->get_col( "SELECT DISTINCT make FROM saturn_tables_cars ORDER BY make" );

    $output = '<label for="filter-by-make" class="screen-reader-text">Filter by Make</label>';  
    $output .= '<select name="make" id="filter-by-make">';
    $output .= '<option>All Makes</option>';
    foreach ($makes as $make) {
        $selected = (isset($_GET['make']) && ($make == $_GET['make'])) ? " selected" : "";
        $output .= "<option$selected>" . esc_html($make) ."</option>";
    }
    $output .= '</select>';

    return $output;
}

Filter Button (array)

array key: filter_button
array mimicking WordPress function submit_button( $text, $type, $name, $wrap, $other_attributes )

The filter_button definition defines a submit button where the array keys are text, type, name, wrap and other_attributes. Note that the default values may be different than the default values of the WordPress function, specifically the type of button is not primary but is button. See the WordPress documentation for available values including defaults. The filter_button will submit the form so it is advisable to check for it in the process_bulk_actions callable if there are concerns.

WordPress submit_button documentation is here https://codex.wordpress.org/Function_Reference/submit_button

$list_table_definitions['filter_button'] = array('text' => "Filter By Make");

Sortable (array)

array key: sortable
array (column key => array( column key, sorted boolean)

The sortable definition defines which columns are sortable, and links the column header to place sort variables in the query string. There are 2 sort variables that are placed in the query string, orderby which defines the column key to sort on, and order which specifies descending or ascending (asc or desc). The true/false boolean determines whether the first sort will be ascending or descending, true implies the column is sorted ascending and the next sort should be descending, false sets the first sort to ascending. Once the sort is set with the column header the query string variables need to be processed in get_data. List Tables fully rely on the items returned in get_data, note that sorting and paginating do not need to be considering when getting the record count in get_data_count. Only search terms and WHERE clauses need to be considered there for limiting the result set.

The implementation of sorting is done in the example here https://saturntables.com/list-tables/get-data/

$list_table_definitions['sortable'] = array( 'make' => array( 'make', true ), 'model' => array( 'model', false ) );

Footer (string)

array key: footer

The footer array item is a HTML string that is displayed below the list table. It is displayed within the form so form variables can be embedded, any HTML content will be displayed.

$list_table_definitions['footer'] = '<p>Powered by <a href="https://saturntables.com/" target="_blank">Saturn Tables</a></p>';