I am trying to make a custom WP_List_Table
extension to display a custom post type.
Here is what the table and the DOM look like. You can see that it is rendering the table rows empty, and no header. There should be two posts so that much is working.
Here is the code for the page:
<?php
function customer_list_page() {
if (!class_exists('WP_List_Table')) {
require_once(ABSPATH . 'wp-admin/includes/class-wp-list-table.php');
}
class CustomerList extends WP_List_Table {
public function __construct() {
parent::__construct([
'singular' => __('Customer', 'text-domain'),
'plural' => __('Customers', 'text-domain'),
'ajax' => false
]);
}
public static function get_customers() {
$query = new WP_Query(array(
'post_type' => 'customer'
));
$customer_posts = $query->posts;
$customers = array();
foreach($customer_posts as $customer_post) {
$new_customer = array();
$new_customer['postId'] = $customer_post->ID;
$new_customer['firstName'] = get_post_meta($customer_post->ID, 'lws-first-name', true);
$new_customer['lastName'] = get_post_meta($customer_post->ID, 'lws-last-name', true);
$new_customer['notes'] = get_post_meta($customer_post->ID, 'lws-notes', true);
$new_customer['customerId'] = get_post_meta($customer_post->ID, 'lws-customer-id', true);
$new_customer['email'] = get_post_meta($customer_post->ID, 'lws-email', true);
$new_customer['phone'] = get_post_meta($customer_post->ID, 'lws-phone', true);
$new_customer['mobile'] = get_post_meta($customer_post->ID, 'lws-mobile', true);
$new_customer['emailNotifications'] = get_post_meta($customer_post->ID, 'lws-email-notifications', true);
$new_customer['acceptChecks'] = get_post_meta($customer_post->ID, 'lws-accept-checks', true);
$new_customer['altContact'] = get_post_meta($customer_post->ID, 'lws-alt-contact', true);
$new_customer['altPhone'] = get_post_meta($customer_post->ID, 'lws-alt-phone', true);
$new_customer['street'] = get_post_meta($customer_post->ID, 'lws-street', true);
$new_customer['city'] = get_post_meta($customer_post->ID, 'lws-city', true);
$new_customer['state'] = get_post_meta($customer_post->ID, 'lws-state', true);
$new_customer['zip'] = get_post_meta($customer_post->ID, 'lws-zip', true);
$new_customer['pets'] = json_decode(get_post_meta($customer_post->ID, 'lws-pets', true));
array_push($customers, $new_customer);
}
return $customers;
}
public static function delete_customer($id) {
wp_delete_post($id);
}
public static function record_count() {
return wp_count_posts('customer')->draft;
}
public function no_items() {
_e('No customers to display', 'text-domain');
}
function column_name($customer) {
$delete_nonce = wp_create_nonce('lws-delete-customer');
$title = '<strong>' . $customer['firstName'] . ' ' . $customer['lastName'] . '</strong>';
$actions = [
'delete' => sprintf('<a href="?page=%s&action=%s&customer=%s&_wpnonce=%s">Delete</a>', esc_attr($_REQUEST['page']), 'delete', absint($customer['postId']), $delete_nonce)
];
return $title . $this->row_actions($actions);
}
function column_default($customer, $column_name) {
return $customer[$column_name];
}
function column_cb($customer) {
return sprintf(
'<input type="checkbox" name="bulk-delete[]" value="%s"/>', $customer['postId']
);
}
function get_columns() {
return [
'cb' => '<input type="checkbox" />',
'customerId' => __('Customer ID', 'text-domain'),
'name' => __('Name', 'text-domain'),
'email' => __('E-Mail', 'text-doamin'),
'phone' => __('Phone #', 'text-domain')
];
}
public function get_sortable_columns() {
return [
'customerId' => ['customerId', true],
'name' => ['name', false],
'email' => ['emial', false],
'phone' => ['phone', false]
];
}
public function get_bulk_actions() {
return [
'bulk-delete' => 'Delete'
];
}
public function prepare_items() {
$this->_column_headers = $this->get_column_info();
$this->process_bulk_actions();
$per_page = $this->get_items_per_page('customers_per_page', 20);
$current_page = $this->get_pagenum();
$total_items = self::record_count();
$this->set_pagination_args([
'total_items' => $total_items,
'per_page' => $per_page
]);
$this->items = self::get_customers($per_page, $current_page);
}
public function process_bulk_action() {
if($this->current_action() === 'delete') {
$nonce = esc_attr($_REQUEST['_wpnonce']);
if(!wp_verify_nonce($nonce, 'lws-delete-customer')) {
die('What the heck');
}
else {
self::delete_customer(absint($_GET['customer']));
wp_redirect(esc_url(add_query_arg()));
exit;
}
}
if(
(isset($_POST['action']) && $_POST['action'] == 'bulk-delete') ||
(isset($_POST['action2']) && $_POST['action2'] == 'bulk-delete')
) {
$delete_ids = esc_sql($_POST['bulk-delete']);
foreach($delete_ids as $id) {
self::delete_customer($id);
}
wp_redirect(esc_url(add_query_arg()));
exit;
}
}
}
$customers_table = new CustomerList();
?>
<div class="wrap">
<h2>Customers</h2>
<form method="post">
<?php
$customers_table->prepare_items();
$customers_table->display();
?>
</form>
</div>
<?php
}
?>
I can't figure it out from my debugging. As far as I can tell everything is nearly identical to what I followed here.
I am trying to make a custom WP_List_Table
extension to display a custom post type.
Here is what the table and the DOM look like. You can see that it is rendering the table rows empty, and no header. There should be two posts so that much is working.
Here is the code for the page:
<?php
function customer_list_page() {
if (!class_exists('WP_List_Table')) {
require_once(ABSPATH . 'wp-admin/includes/class-wp-list-table.php');
}
class CustomerList extends WP_List_Table {
public function __construct() {
parent::__construct([
'singular' => __('Customer', 'text-domain'),
'plural' => __('Customers', 'text-domain'),
'ajax' => false
]);
}
public static function get_customers() {
$query = new WP_Query(array(
'post_type' => 'customer'
));
$customer_posts = $query->posts;
$customers = array();
foreach($customer_posts as $customer_post) {
$new_customer = array();
$new_customer['postId'] = $customer_post->ID;
$new_customer['firstName'] = get_post_meta($customer_post->ID, 'lws-first-name', true);
$new_customer['lastName'] = get_post_meta($customer_post->ID, 'lws-last-name', true);
$new_customer['notes'] = get_post_meta($customer_post->ID, 'lws-notes', true);
$new_customer['customerId'] = get_post_meta($customer_post->ID, 'lws-customer-id', true);
$new_customer['email'] = get_post_meta($customer_post->ID, 'lws-email', true);
$new_customer['phone'] = get_post_meta($customer_post->ID, 'lws-phone', true);
$new_customer['mobile'] = get_post_meta($customer_post->ID, 'lws-mobile', true);
$new_customer['emailNotifications'] = get_post_meta($customer_post->ID, 'lws-email-notifications', true);
$new_customer['acceptChecks'] = get_post_meta($customer_post->ID, 'lws-accept-checks', true);
$new_customer['altContact'] = get_post_meta($customer_post->ID, 'lws-alt-contact', true);
$new_customer['altPhone'] = get_post_meta($customer_post->ID, 'lws-alt-phone', true);
$new_customer['street'] = get_post_meta($customer_post->ID, 'lws-street', true);
$new_customer['city'] = get_post_meta($customer_post->ID, 'lws-city', true);
$new_customer['state'] = get_post_meta($customer_post->ID, 'lws-state', true);
$new_customer['zip'] = get_post_meta($customer_post->ID, 'lws-zip', true);
$new_customer['pets'] = json_decode(get_post_meta($customer_post->ID, 'lws-pets', true));
array_push($customers, $new_customer);
}
return $customers;
}
public static function delete_customer($id) {
wp_delete_post($id);
}
public static function record_count() {
return wp_count_posts('customer')->draft;
}
public function no_items() {
_e('No customers to display', 'text-domain');
}
function column_name($customer) {
$delete_nonce = wp_create_nonce('lws-delete-customer');
$title = '<strong>' . $customer['firstName'] . ' ' . $customer['lastName'] . '</strong>';
$actions = [
'delete' => sprintf('<a href="?page=%s&action=%s&customer=%s&_wpnonce=%s">Delete</a>', esc_attr($_REQUEST['page']), 'delete', absint($customer['postId']), $delete_nonce)
];
return $title . $this->row_actions($actions);
}
function column_default($customer, $column_name) {
return $customer[$column_name];
}
function column_cb($customer) {
return sprintf(
'<input type="checkbox" name="bulk-delete[]" value="%s"/>', $customer['postId']
);
}
function get_columns() {
return [
'cb' => '<input type="checkbox" />',
'customerId' => __('Customer ID', 'text-domain'),
'name' => __('Name', 'text-domain'),
'email' => __('E-Mail', 'text-doamin'),
'phone' => __('Phone #', 'text-domain')
];
}
public function get_sortable_columns() {
return [
'customerId' => ['customerId', true],
'name' => ['name', false],
'email' => ['emial', false],
'phone' => ['phone', false]
];
}
public function get_bulk_actions() {
return [
'bulk-delete' => 'Delete'
];
}
public function prepare_items() {
$this->_column_headers = $this->get_column_info();
$this->process_bulk_actions();
$per_page = $this->get_items_per_page('customers_per_page', 20);
$current_page = $this->get_pagenum();
$total_items = self::record_count();
$this->set_pagination_args([
'total_items' => $total_items,
'per_page' => $per_page
]);
$this->items = self::get_customers($per_page, $current_page);
}
public function process_bulk_action() {
if($this->current_action() === 'delete') {
$nonce = esc_attr($_REQUEST['_wpnonce']);
if(!wp_verify_nonce($nonce, 'lws-delete-customer')) {
die('What the heck');
}
else {
self::delete_customer(absint($_GET['customer']));
wp_redirect(esc_url(add_query_arg()));
exit;
}
}
if(
(isset($_POST['action']) && $_POST['action'] == 'bulk-delete') ||
(isset($_POST['action2']) && $_POST['action2'] == 'bulk-delete')
) {
$delete_ids = esc_sql($_POST['bulk-delete']);
foreach($delete_ids as $id) {
self::delete_customer($id);
}
wp_redirect(esc_url(add_query_arg()));
exit;
}
}
}
$customers_table = new CustomerList();
?>
<div class="wrap">
<h2>Customers</h2>
<form method="post">
<?php
$customers_table->prepare_items();
$customers_table->display();
?>
</form>
</div>
<?php
}
?>
I can't figure it out from my debugging. As far as I can tell everything is nearly identical to what I followed here.
Share Improve this question edited Mar 14, 2020 at 11:42 chrispytoes asked Mar 14, 2020 at 1:18 chrispytoeschrispytoes 2243 silver badges8 bronze badges 2 |1 Answer
Reset to default 12You're getting the blank rows because your column headers are registered late. And you should register the headers (i.e. initialize the list table class instance) before admin notices are rendered on the page, i.e. before WordPress fires hooks like admin_notices
.
But your customer_list_page()
function, which I believe, is a callback for either add_menu_page()
or add_submenu_page()
, is only being called after the admin notices (and other stuff) are displayed, hence get_column_headers()
(that's used by WP_List_Table::get_column_info()
) didn't recognize the column headers for your list table. And that is because get_column_headers()
stores the column headers in a static variable/array which once set, will not be modified anymore, so the next time the function is called for your screen/page, the function returns the "old" column headers.
More specifically, your class instance ($customers_table = new CustomerList()
) should be initialized before WP_Screen::render_screen_meta()
is called in wp-admin/admin-header.php
. And if you look at WP_List_Table::single_row_columns()
which is used (although not directly) by WP_List_Table::display()
, the body/content rows will only be displayed if there are valid column headers registered for the list table. Hence that explains why the column headers are mandatory for the content rows to be displayed. Well, just like a human can't live without a head... ( :D )
If you can't initialize the class instance before admin notices are rendered... you can manually set the $_column_headers
value.
So in the prepare_items()
method, just replace this:
$this->_column_headers = $this->get_column_info();
with this:
$this->_column_headers = [
$this->get_columns(),
[], // hidden columns
$this->get_sortable_columns(),
$this->get_primary_column_name(),
];
But try to always initialize it "on-time". :)
And here's an example using the load-<page hook>
hook:
class My_Plugin {
private $list_table;
public function __construct() {
add_action( 'admin_menu', [ $this, 'add_admin_menus' ] );
}
public function add_admin_menus() {
$hook_name = add_menu_page( 'Customers', 'Customers',
'edit_posts', 'my-page', [ $this, 'render_admin_page' ] );
// Initialize the list table instance when the page is loaded.
add_action( "load-$hook_name", [ $this, 'init_list_table' ] );
}
public function init_list_table() {
$this->list_table = new CustomerList;
}
public function render_admin_page() {
?>
<div class="wrap my-plugin">
<h1>Customers</h1>
<?php $this->list_table->prepare_items(); ?>
<?php $this->list_table->display(); ?>
</div>
<?php
}
}
if ( is_admin() ) {
new My_Plugin;
}
And just a gentle reminder to double-check for typos like the emial
in your get_sortable_columns()
... =) Happy coding!
'email' => ['emial', false],
- thatemial
should beemail
.. 2) Inrecord_count()
, why are you counting draft posts? You could've used$query->found_posts
inget_customers()
. – Sally CJ Commented Mar 14, 2020 at 15:24