最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

admin - Custom WP_List_Table displays blank rows

programmeradmin1浏览0评论

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
  • There are other issues in your code, but the answer focuses on showing the column headers (and eventually the whole table rows). However, I should still comment on 2 things: 1) There's a typo here: 'email' => ['emial', false], - that emial should be email.. 2) In record_count(), why are you counting draft posts? You could've used $query->found_posts in get_customers(). – Sally CJ Commented Mar 14, 2020 at 15:24
  • 1 @SallyCJ Thanks for pointing out the typo. As for counting on draft posts, that was just a temporary thing I was looking at and forgot to remove. – chrispytoes Commented Mar 14, 2020 at 15:37
Add a comment  | 

1 Answer 1

Reset to default 12

You'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!

发布评论

评论列表(0)

  1. 暂无评论