Wordpress Plugin Boilerplate - How to use WP_List_Table class correctly?

Viewed 460

I'm using the Wordpress Plugin Boilerplate as foundation for my own plugin. In the admin area I intend to display data using the WP_List_Table class of Wordpress. I know I have to create my own child class to access it. Doing so is not the issue, however I get the following error:

Fatal error: Uncaught Error: Call to a member function render_screen_reader_content() on

During my research I discovered a few cases with the same issue (1, 2), but none of the solutions worked in my case.

Using the structure of the boilerplate, I created the child class in a file inside the includes folder:

if ( !class_exists( 'WP_List_Table' ) ) {
    require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
}
if ( !class_exists( 'Hedwig_tables' ) ) {
    class Hedwig_tables extends WP_List_Table {
        private array $hd_columns;
        private array $hd_data;
        private array $hd_hidden;
        private array $hd_sortable;
        private array $hd_column_names;

        public function __construct() {
            //parent::__construct();
        }

        public function set_column_names(array $column_names) {
            $this->hd_column_names = $column_names;
        }

        public function set_columns(array $columns) {
            $this->hd_columns = $columns;
        }

        public function set_data(array $data) {
            $this->hd_data = $data;
        }

        public function set_hidden(array $hidden) {
            $this->hd_hidden = $hidden;
        }

        public function set_sortable(array $sortable) {
            $this->hd_sortable = $sortable;
        }

        public function prepare_items() {
            $this->_column_headers = array($this->hd_columns, $this->hd_hidden, $this->hd_sortable);
            $this->items = $this->hd_data;
        }

        public function column_default( $item, $column_name ): mixed {
            if (in_array($column_name, $this->hd_column_names)) {
                return $item[ $column_name ];
            }
            return print_r($item, true);
        }
    }
}

The file is then loaded in the boilerplate's load_dependencies() function in class-plugin-name.php inside the includes folder.

In the boilerplate's class-plugin-name-admin.php (inside the admin folder) I created a function which generates the admin menu entry.

    public function add_hedwig_page() {

        $this->plugin_screen_hook_suffix = add_menu_page(
            __( 'Hedwig Settings', 'Hedwig' ),
            __( 'Hedwig Settings', 'Hedwig' ),
            'manage_options',
            $this->plugin_name,
            array( $this, 'hedwig_admin_display_page' ),
            'dashicons-buddicons-activity'
        );

        $this->plugin_screen_hook_suffix = add_submenu_page(
            $this->plugin_name,
            __( 'Hedwig Settings', 'Hedwig' ),
            __( 'Hedwig Settings', 'Hedwig' ),
                'manage_options',
            $this->plugin_name,
            array( $this, 'hedwig_admin_display_page' )
        );

    public function hedwig_admin_display_page() {
        include_once 'partials/hedwig-admin-display.php';
    }

Inside the display.php a function is called which I created inside the class-plugin-name-admin.php which creates the object for the child class of WP_List_Table.

    public function get_data() {
        $hedwig_list_table = new Hedwig_tables();
        $sql = "SELECT id, value FROM y";
        $results = $this->wpdb->get_results($sql, ARRAY_A);
        
        if (count($results)<=0) {
            ?>
            <div class="hedwig-msg-error"><?php _e('No data found.','Hedwig');?></div>
            <?php
            return false;
        }
        
        $hedwig_list_table->set_columns(
            array(
                'id' => __('ID','Hedwig'),
                'value' => __('Art','Hedwig')
            )
        );
        $hedwig_list_table->set_column_names(
            array(
                'id',
                'value'
            )
        );
        $hedwig_list_table->set_data($results);
        $hedwig_list_table->set_hidden(array());
        $hedwig_list_table->set_sortable(array());
        $hedwig_list_table->prepare_items();
        $hedwig_list_table->display();
        
        return true;
    }

Based on my aforementioned research the issue must be somewhere along the line of when the object for the child class is created (see this answer). I tried using add_actions() at different places (on __construct of the admin class, inside the run() function of the plugin-name.php trying to either load it after the menu items are generated or loading the class as a $GLOBALS. Everything I came up with failed. I used to create some smaller plugins without a boilerplate, but in this project I actually want to do the switch to this OOP and get new plugins kickstarted this way.

Update #1

Still got no solution, but I stumbled upon another solution which looked promising. However, using a function when creating the menu item for initialising the child class doesn't work either.

public function add_hedwig_page() {

    $this->plugin_screen_hook_suffix = add_menu_page(
        __( 'Hedwig Settings', 'Hedwig' ),
        __( 'Hedwig Settings', 'Hedwig' ),
        'manage_options',
        $this->plugin_name,
        function() { 
                    $this->hedwig_list_table = new Hedwig_tables();
                    $this->hedwig_admin_display_page();
        },
        'dashicons-buddicons-activity'
    );

    $this->plugin_screen_hook_suffix = add_submenu_page(
        $this->plugin_name,
        __( 'Hedwig Settings', 'Hedwig' ),
        __( 'Hedwig Settings', 'Hedwig' ),
            'manage_options',
        $this->plugin_name,
        function() { 
                    $this->hedwig_list_table = new Hedwig_tables();
                    $this->hedwig_admin_display_page();
        }
    );

public function hedwig_admin_display_page() {
    include_once 'partials/hedwig-admin-display.php';
}
1 Answers

You shouldn't load this class like your custom classes since this is an extension of a core class and it has many more dependencies to rely on. Don't use add_action or $GLOBALS to initialize this class at all.

If you load it in boilerplate's load_dependencies() function in class-plugin-name.php inside the includes folder it will be instantiated too early and it will not function properly.

Instead, call it only when needed, inside your partials/hedwig-admin-display.php that you will use to output your markup for that page.

Something like this should work in your example:

<?php $table = new Hedwig_tables(); ?>

<h1>Hedwig_tables</h1>
    <div class="wrap">
        <div id="poststuff">
            <div id="post-body" class="metabox-holder">
                <div id="post-body-content">
                    <div class="meta-box-sortables ui-sortable">
                        <?php $table->prepare_items();?>
                        <form method="get">
                            <?php $table->display(); ?>
                        </form>
                    </div>
                </div>
            </div>
            <br class="clear">
        </div>
    </div>
Related