【WordPress】WP_List_Tableでデータの一覧表示をしてみる

この記事はブログとプログラミングで生計をたてている海外在住ウェブディベロッパーがわかりやすさにこだわって作成しました

こちらの記事は次のWP Engineerサイトを引用、翻訳させていただきました。

WP_List_Table – a step by step guide

 

 

下準備

評価用の簡単なプラグインを作成します

 

<?php
/*
Plugin Name: My List Table Example
*/
?>
<div class="wrap">
<div id="icon-users" class="icon32"></div>
<h2>My List Table Test/h2>
</div>
<?php
基本的な使い方

ダウンロードしてきたWP_List_Tableクラスファイルを読み込みます

if( ! class_exists( 'WP_List_Table' ) ) {
    require_once( ABSPATH. 'wp-admin/includes/class-wp-list-table.php' );
}

 

WP_List_Tableクラスを継承した新しいクラスを作成します。

class My_List_Table extends WP_List_Table{
}

$myListTable= new My__List_Table();

 

サンプルデータを作ります

var $example_data= array(
  array('ID' => 1,'booktitle' => 'Quarter Share', 'author' => 'Nathan Lowell',
        'isbn' => '978-0982514542'),
  array('ID' => 2, 'booktitle' => '7th Son: Descent','author' => 'J. C. Hutchins',
        'isbn' => '0312384378'),
  array('ID' => 3, 'booktitle' => 'Shadowmagic', 'author' => 'John Lenahan',
        'isbn' => '978-1905548927'),
  array('ID' => 4, 'booktitle' => 'The Crown Conspiracy', 'author' => 'Michael J. Sullivan',
        'isbn' => '978-0979621130'),
  array('ID' => 5, 'booktitle'     => 'Max Quick: The Pocket and the Pendant', 'author'    => 'Mark Jeffrey',
        'isbn' => '978-0061988929'),
  array('ID' => 6, 'booktitle' => 'Jack Wakes Up: A Novel', 'author' => 'Seth Harwood',
        'isbn' => '978-0307454355')
);

 

メソッドや変数に必要な情報を定義します

function get_columns(){
  $columns= array(
    'booktitle' => 'Title',
    'author'    => 'Author',
    'isbn'      => 'ISBN'
  );
  return $columns;
}

function prepare_items() {
  $columns= $this->get_columns();
  $hidden= array();
  $sortable= array();
  $this->_column_headers= array($columns, $hidden, $sortable);
  $this->items= $this->example_data;
}

 

get_columns() は、テーブルの各列の項目名を定義します。キーに対して実際に表示する名称またはタグを指定します。キーはデータベースの項目名と同じであるか、関数で定義されている必要があります。さもなければその項目は画面に表示されません。

prepare_items は、以下の2つの配列を定義します。

  • $hidden は非表示の項目名を定義  (see Screen Options),
  • $sortable はソート可能かどうかを定義

最後に、itemsに表示するデータを定義します。

 

WordPressはデータを表示する前に、表示テーブルの列の項目名ごとに存在する column_{key_name}(例えば function column_booktitle)を探して、その関数によりデータ項目(セル)の表示内容を決定します。表の項目名とデータベースのカラム名が同じ場合は column_default で定義することもできます。

function column_default( $item, $column_name) {
  switch( $column_name) { 
    case 'booktitle':
    case 'author':
    case 'isbn':
      return $item[ $column_name];
    default:
      return print_r( $item, true ) ; //Show the whole array for troubleshooting purposes
  }
}

このサンプルでは、メソッドは表の項目名($column_name)に対してデータベースのカラム名($item)を返します。項目名が存在しなければデバッグ用にすべてのカラム名を表示します。

これらはWP_List_Tableクラスをカスタマイズするための基本的な設定です。管理画面に表示するには、このカスタマイズされたクラスをインスタンス化し、 display() メソッドを呼び出すだけです。

function my_add_menu_items(){
    add_menu_page( 'My Plugin List Table', 'My List Table Example', 'activate_plugins', 'my_list_test', 'my_render_list_page' );
}
add_action( 'admin_menu', 'my_add_menu_items' );

function my_render_list_page(){
  $myListTable= new My_Example_List_Table();
  echo'<div class="wrap"><h2>My List Table Test</h2>'; 
  $myListTable->prepare_items(); 
  $myListTable->display(); 
  echo'</div>'; 
}

This is the minimal version of a WP_List_Table possible:

ソート

At the moment the items appear in the order they are defined in the code since the WP_List_Table class does not contain any code for sorting. What it does contain is some code to mark certain columns as sortable. In section "Basics" there already was a line $sortable= array(); which now will be changed to:

$sortable= $this->get_sortable_columns();

Additionally we need the method:

function get_sortable_columns() {
  $sortable_columns= array(
    'booktitle'  => array('booktitle',false),
    'author' => array('author',false),
    'isbn'   => array('isbn',false)
  );
  return $sortable_columns;
}

This way the above mentioned column headers are changed to links and display small triangles if the mouse hovers over them. The second parameter in the value array of $sortable_columns takes care of a possible pre-ordered column. If the value is true the column is assumed to be ordered ascending, if the value is false the column is assumed descending or unordered. This is needed for the small triangle beside the column name indicating the sort order to show in the correct direction:

If you click on the column header the page is reloaded and $_GET contains something like this:

array
 'page' => string 'my_list_test' (length=12)
  'orderby' => string 'booktitle' (length=5)
  'order' => string 'asc' (length=3)

With this information you can write a method for sorting our example data:

function usort_reorder( $a, $b) {
  // If no sort, default to title
  $orderby= ( ! empty( $_GET['orderby'] ) ) ? $_GET['orderby'] : 'booktitle';
  // If no order, default to asc
  $order= ( ! empty($_GET['order'] ) ) ? $_GET['order'] : 'asc';
  // Determine sort order
  $result= strcmp( $a[$orderby], $b[$orderby] );
  // Send final sort direction to usort
  return ( $order=== 'asc' ) ? $result: -$result;
}

To actually sort the data we have to extend prepare_items():

function prepare_items() {
  [..]
  usort( $this->example_data, array( &$this, 'usort_reorder' ) );
  $this->items= $this->example_data;
}

If you're retrieving the data from the database (which is most likely) it's of course best to use SQL's ORDERBY directly.

アクション

If you not only want to display the items but also want to manipulate them you have to define some actions:

function column_booktitle($item) {
  $actions= array(
            'edit'      => sprintf('<a href="?page=%s&action=%s&book=%s">Edit</a>',$_REQUEST['page'],'edit',$item['ID']),
            'delete'    => sprintf('<a href="?page=%s&action=%s&book=%s">Delete</a>',$_REQUEST['page'],'delete',$item['ID']),
        );

  return sprintf('%1$s %2$s', $item['booktitle'], $this->row_actions($actions) );
}

These actions will appear if the user hovers the mouse cursor over the table:

If you click on one of the action links the form will return for example the following data in $_GET:

array
 'page' => string 'my_list_test' (length=12)
  'action' => string 'delete' (length=6)
  'book' => string '2' (length=1)
バルク処理

Bulk action are implemented by overwriting the method get_bulk_actions() and returning an associated array:

function get_bulk_actions() {
  $actions= array(
    'delete'    => 'Delete'
  );
  return $actions;
}

This only puts the dropdown menu and the apply button above and below the table:

The checkboxes for the rows have to be defined separately. As mentioned above there is a method column_{column} for rendering a column. The cb-column is a special case:

function column_cb($item) {
        return sprintf(
            '<input type="checkbox" name="book[]" value="%s" />', $item['ID']
        );    
    }

This method currently will not be processed because we have to tell the class about the new column by extending the method get_columns():

function get_columns() {
  $columns= array(
    'cb'        => '<input type="checkbox" />',
[..]
}

This will also put the "select all" checkbox in the title bar:

If you don't want to display the checkbox in the title you simply set the value to an empty string. Nevertheless you still have to define the key/value pair otherwise no checkboxes are shown at all:

If "Apply" is pressed the form will return various variables: action and action2 contain the selected action or -1 if the user chose no action, and if any checkbox was selected the marked rows, in our case books, for example:

'action' => string 'delete' (length=6)
'book' => 
  array
   0 => string '2' (length=1)
    1 => string '6' (length=1)
'action2' => string '-1' (length=2)

action contains the selection from the upper select box, action2 the selection from the lower select box, and book the id of the selected rows, if any. You can use the method current_action() to query action/action2:

$action= $this->current_action();

It will return action if it's set, otherwise action2. If nothing is set the method returns FALSE.

ペジネーション

First things first: WordPress does not paginate your data in any way. It only contains a method to display a navigation bar on the top and bottom right of the table:

You have to tell the method how many items you have in total, how many items shall be displayed on a page, and most important, the data to be displayed on the page:

function prepare_items() {
  [...]
  $per_page= 5;
  $current_page= $this->get_pagenum();
  $total_items= count($this->example_data);

  // only ncessary because we have sample data
  $this->found_data= array_slice($this->example_data,(($current_page-1)*$per_page),$per_page);

  $this->set_pagination_args( array(
    'total_items' => $total_items,                  //WE have to calculate the total number of items
    'per_page'    => $per_page                    //WE have to determine how many items to show on a page
  ) );
  $this->items= $this->found_data;
}

As pointed out in the comment the array_slice is only necessary because we use sample data. If you're retrieving the data from a database you only need to load the necessary data by using SQL's LIMIT.

サーチ

If you have a huge amount of data a search field will simplify accessing certain items:

$myListTable->search_box('search', 'search_id');

The button text search is defined by the first parameter, the id of the input by the second parameter. The method creates the following output:

<p class="search-box">
<label class="screen-reader-text" for="search_id-search-input">
search:</label> 
<input id="search_id-search-input" type="text" name="s" value="" /> 
<input id="search-submit" class="button" type="submit" name="" value="search" />
</p>

The method will place the input field and the search button on the right side and style it correctly. The <form> element is not generated. You have to add it manually, in our case this would be:

<form method="post">
  <input type="hidden" name="page" value="my_list_test" />
  <?php $this->search_box('search', 'search_id'); ?>
</form>

(The hidden element is needed to load the right page.)
To react to the search command you need to check the content of $_POST['s'] and filter your data accordingly before displaying the table.

表示オプション

All core backend pages containing a WP_List_Table provide a "Screen Options" slide-in where the user can adjust the columns to be shown and the number of rows to be displayed.
To add options to your plugin you need to change your current code. First you have to make sure that the screen options are displayed only on the current page:

$hook= add_menu_page('My Plugin List Table', 'My List Table Example', 'activate_plugins', 'my_list_test', 'my_render_list_page');
add_action( "load-$hook", 'add_options' );

function add_options() {
  $option= 'per_page';
  $args= array(
         'label' => 'Books',
         'default' => 10,
         'option' => 'books_per_page'
         );
  add_screen_option( $option, $args);
}

This only displays the option field and apply button, saving and loading the data has to be defined separately. WordPress provides a filter called set-screen-option to take care of this:

add_filter('set-screen-option', 'test_table_set_option', 10, 3);
function test_table_set_option($status, $option, $value) {
  return $value;
}

The option is stored in the table usermeta in the database so each user has his own setting. To retrieve the option and adjust the table display accordingly the method prepare_items has to be altered (excerpt):

function prepare_items() {
[..]

  //paging
  $per_page= $this->get_items_per_page('books_per_page', 5);
  $current_page= $this->get_pagenum();

  [...]

Instead of simply assigning a number the user specified value is loaded. If the user hasn't changed the value there is no such option stored in the database and a default value is taken.

Adding the checkboxes for hiding/showing the columns is done by WordPress automatically. You just have to make sure that your derived class is instantiated before the screen option panel is rendered so that the parent class can retrieve the column names. To accomplish this the corresponding code is moved into the method add_options():

function add_options() {
    global $myListTable;
 
    $option= 'per_page';
    $args= array(
        'label' => 'Books',
        'default' => 10,
        'option' => 'books_per_page'
    );
    add_screen_option( $option, $args);

    $myListTable= new My_Example_List_Table;
}

The user's selections are automatically saved via Ajax functions. Nevertheless you have take care by yourself that the columns are hidden if the page is loaded initially. The method get_column_info() returns all, the hidden and the sortable columns. In the method prepare_items() instead of

$columns= $this->get_columns();
$hidden= array();
$sortable= $this->get_sortable_columns();
$this->_column_headers= array($columns, $hidden, $sortable);

it's now

$this->_column_headers= $this->get_column_info();

and the columns are set according to the screen options.

Annotation: you should avoid some strings as keynames since they are treated by WordPress specially:

$special= array('_title', 'cb', 'comment', 'media', 'name', 'title', 'username', 'blogname');

Your table would still work, but you won't be able to show/hide the columns.

スタイルの変更

Currently the table is styled to the WordPress defaults. To change this you have to adapt the CSS classes which are automatically assigned to each column. The class name consists of the string "column-" and the key name of the $columns array, e.g. "column-isbn" or "column-author". As an example the width of the columns will be redefined (for simplicity the style data is written directly into the HTML header):

function _construct() {
  [...]
  add_action( 'admin_head', array( &$this, 'admin_header' ) );
  [...]
}

function admin_header() {
  $page= ( isset($_GET['page'] ) ) ? esc_attr( $_GET['page'] ) : false;
  if( 'my_list_test' != $page)
    return; 

  echo'<style type="text/css">';
  echo'.wp-list-table .column-id { width: 5%; }';
  echo'.wp-list-table .column-booktitle { width: 40%; }';
  echo'.wp-list-table .column-author { width: 35%; }';
  echo'.wp-list-table .column-isbn { width: 20%; }';
  echo'</style>';
}
その他カスタマイズ

If there are no items in the list the standard message is "No items found." is displayed. If you want to change this message you can overwrite the method no_items():

function no_items() {
  _e( 'No books found, dude.' );
}

 

PHP/Javascript/WORDPRESS案件全般承ります

オーストラリアで主に日系企業や個人のお客様からのご依頼でお仕事をしております。この記事についてのご質問またはお困りのことがございましたら、お気軽にお問い合わせください。

タイトルとURLをコピーしました