WP_List_Tableクラスを使ってみた

WP_List_Tableクラスを使ってみた - 管理画面の多数使用されている表の正体に迫る

説明

WordPressの管理画面ではデータを一覧表示しているページがいくつもある。今回この仕組みを自分のプラグインでも使ってみたいと思い、調査しながらプラグインに組み込んでみた。

拡張して使うWP_List_Tableクラス

さっそく管理画面の投稿一覧ページをトレースしていくと、WP_List_Tableクラス(実態は/wp-admin/includes/class-wp-list-table.phpにて定義)にたどり着く。投稿一覧ページではこのWP_List_Tableクラスを拡張して投稿情報を表示しており、そのほかのページでも同様である。自身のプラグインでWP_List_Tableクラスを利用する場合、まずはWP_List_Tableクラスを拡張したクラスを準備することになる。

表示したいタイプによって準備するメソッドは変わる

作成するクラスのひな型は次の通りである。一括処理する場合やカラムでソートする場合、ページネーションする場合など、状況によってはほかのメソッドも必要になるが、まずは次の5つを定義する。

class Gettext_Msg_List_Table extends WP_List_Table {
	/**
	 * 初期化時の設定を行う
	 */
	public function __construct( $args = array() ) {
	}

	/**
	 * 表で使用されるカラム情報の連想配列を返す
	 * @return array
	 */
	public function get_columns() {
	}

	/**
	 * プライマリカラム名を返す
	 * @return string
	 */
	protected function get_primary_column_name() {
	}

	/**
	 * 表示するデータを準備する
	 */
	public function prepare_items() {
	}

	/**
	 * 1行分のデータを表示する
	 * @param array $item
	 */
	public function single_row( $item ) {
	}
}

コンストラクタでは元のWP_List_Tableクラスのコンストラクタへの連想配列のパラメータを引き渡す。キー'plural'の値は複数形のデータ名(翻訳してないもの)を指定。キー'screen'の値はフィルターの名前に影響するもので標準的な使い方の場合はnullでよいのだが、任意の名前に変更できるようコンストラクタが受け取ったパラメータを引き継げるようにするほうが望ましい(こちらの理由は後ほど)。

public function __construct( $args = array() ) {
	parent::__construct( array(
			'plural' => 'msgs',
			'screen' => isset( $args['screen'] ) ? $args['screen'] : null,
	) );
}

get_columnsメソッドは表の見出しを定義する重要なメソッドになり、見出しの内容を連想配列で返す。連想配列のキーが各カラムの名前となり、その値に表示される内容を記述する。キー'cb'はチェックボックスの指定で、選択した内容をまとめて処理する場合に指定する。

public function get_columns() {
	return array(
			'cb'		=> '<input type="checkbox" />',
			'no'		=> __( 'No.' ),
			'msgctxt'	=> __( 'msgctext' ),
			'msgid'		=> __( 'msgid' ),
			'msgstr'	=> __( 'msgstr' ),
	);
}

get_primary_column_nameメソッドには、上記のget_columnsメソッドで指定するキーの中で'cb'以外のものをプライマリカラム名として返す。このメソッドは省略可能で、省略時には'cb'の次のものが適用されるが、ここで明示的に記述しておくほうがわかりやすい。

protected function get_primary_column_name() {
	return 'no';
}

prepare_itemsメソッドには表示するデータを準備する処理を記述する。このメソッドは標準ではパラメータはないが、今回は別途取得済みのデータを表示するため、パラメータでの受け渡しを行っている。表示するデータ(配列)は、メンバー変数itemsに格納する。なお表示するデータが何らかのルールで並ばせたい場合はitemsに格納する前にソート処理を行う必要がある。
データの格納が終わったら、set_pagination_argsメソッドでページネーション関連データを指定する。この指定は一覧表の右上部分の表示に関わっており、ページネーションしない場合は「n個の項目」という表示になる。

public function prepare_items( $items = null ) {
	if ( !is_null( $items ) ) {
		$this->items = $items;

		$this->set_pagination_args( array(
				'total_items' => count( $this->items ),
				'per_page' => 999,
		) );
	}
}

single_rowメソッドには1件(1行)のデータを表示する処理を記述する。このメソッドは、一覧表を表示する際に使用されるdisplayメソッドから呼び出される。td要素のclass属性は「表示オプション」によるカラムの表示・非表示切替に使用されるため、この機能を有効にするのであれば指定しなければならない。

public function single_row( $item ) {
	list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
?>
<tr>
<?php
	foreach ( $columns as $column_name => $column_display_name ) {
		$classes = "$column_name column-$column_name";
		$extra_classes = '';
		if ( in_array( $column_name, $hidden ) ) {
			$extra_classes = ' hidden';
		}

		switch ( $column_name ) {
			case 'cb':
				$checkbox_id =  "checkbox_".$item->get( 'no' );
				$checkbox = "<label class='screen-reader-text' for='" . $checkbox_id . "' >" . sprintf( __( 'Select %s' ), $item->msgid ) . "</label>"
					. "<input type='checkbox' name='checked[]' value='" . $item->get( 'no' ). "' id='" . $checkbox_id . "' />";
				echo "<th scope='row' class='check-column'>$checkbox</th>";
				break;
			case 'no':
?>
<td class="<?php echo esc_attr( $classes.$extra_classes ); ?>"><?php echo esc_html( $item->get( 'no' ) ); ?></td>
<?php
				break;
			case 'msgid':
?>
<td class="<?php echo esc_attr( $classes.$extra_classes ); ?>"><textarea id="msgid_<?php echo esc_attr( $item->no); ?>" name="msgid[<?php echo esc_attr( $item->no ); ?>]" rows="2" class="fit-width"><?php echo esc_html( $item->msgid ); ?></textarea></td>
<?php				
				break;
			// 一部省略
		}
	}
?>
</tr>
<?php
}
もう少し機能拡張

表示に必要な最低限の機能は上記で対応できるが、そのほかのメソッドを追加することで機能を拡張できる。まずは表の左上に表示されるプルダウンメニューだが、これはfunction get_bulk_actionsメソッドを追加する。返り値となる連想配列の内容はキーがoption要素のvalue属性に、キーの値がプルダウンメニューに表示される内容となる。

protected function get_bulk_actions() {
	return array( 'delete-selected' => __( 'Delete' ) );
}

なおプルダウンメニューで「削除」が選択され、「適用」ボタンが押された場合の処理については、別途定義する必要はある。

さてカラム名の横に▲や▼が表示され、そのカラムをクリックすると、表示内容がソートされて表示される機能もよく見かけると思う。この機能はget_sortable_columnsメソッドを追加し、ソートしたいカラム情報の連想配列を返すことで実現できる。

protected function get_sortable_columns() {
	return array(
			'no'	=> array( 'no', true ),
			'msgid'	=> 'msgid' );
}

この定義により対象のカラムには昇順または降順にソートするためのa要素(リンク)が追加される。実際に指定されたカラムでソートする処理に関しては、prepare_itemsメソッドあたりで$_REQUEST['orderby']および$_REQUEST['order']の値に応じた処理を行うことになる。

オブジェクトを作るタイミングは重要

WP_List_Tableクラスを拡張した独自クラスの定義が終わったら、今度は実際に表示する部分を定義する。ここでは例として管理画面の設定メニューに追加するケースを紹介する。

class Plugin_Translate {
	const PROPERTIES_NAME = 'translate';
	const DOMAIN_NAME = 'translate';
	var $wp_list_table; 

	public function __construct() {
		add_action( 'admin_menu', array( $this, 'admin_menu' ) );
	}

	public function admin_menu() {
		add_options_page(
			__( 'Translate', self::DOMAIN_NAME ),
			__( 'Translate', self::DOMAIN_NAME ),
			'manage_options',
			self::PROPERTIES_NAME,
			array( $this, 'properties' ) );
		if ( isset( $_GET['page'] ) && self::PROPERTIES_NAME === $_GET['page'] ) {
			add_action( 
				'load-settings_page_'.self::PROPERTIES_NAME,
				array( $this, 'load' ) );
		}
	}

	public function load() {
		require_once 'includes/class-gettext-msg-list-table.php';
		$this->wp_list_table = new Gettext_Msg_List_Table();
	}

	public function properties() {
		// 途中省略
?>
<form method="post" id="bulk-action-form">
<?php
$this->wp_list_table->prepare_items( $msgs );
$this->wp_list_table->display();
?>
</form>
<?php
	}
}

$plugin_translate = new Plugin_Translate();

WP_List_Tableクラスを拡張して使用する場合に注意したいポイントは、そのクラスのオブジェクトを作成するタイミングである。まずプラグイン自身がロードされるタイミング(ここではコンストラクタ内)ではWP_List_Tableクラスを定義している/wp_admin/includes/class-wp-list-table.phpがロードされていないため、require_onceにてphpファイルを読み込む時点で「Fatal error」になってしまう。

次にこのプラグインの設定ページを表示するpropertiesメソッドを考えるわけだが、その場合はオブジェクトを生成することはできるが、肝心の表は何も表示されない。この状況を作った原因は次の通りである。

  1. すべての管理画面ページでは序盤にWP_Screenオブジェクトが生成される。このページのスクリーンIDは'settings_page_translate'となる。
  2. 管理画面ページの上部には「表示オプション」を出力するが、その準備としてget_column_headers関数(/wp_admin/screen.php)でそのページのカラム情報を取得する。
  3. get_column_headers関数ではパラメータで受け取ったスクリーンIDのカラム情報を保持しており、保持されていない場合は'manage_{スクリーンID}_columns'フィルターによってカラム情報を取得してそれを保持する。この段階では'manage_settings_page_translate_columns'フィルターには何も登録されていないため、初期値として空の配列が保持されることになる。
  4. 本プラグインのpropertiesメソッドにてGettext_Msg_List_Tableオブジェクトの生成される。このコンストラクタから呼び出されるWP_List_Tableクラスのコンストラクタでは、'manage_settings_page_translate_columns'フィルターとしてGettext_Msg_List_Tableオブジェクトのget_columnsメソッドが指定される。
  5. Gettext_Msg_List_Tableオブジェクトにて表を出力する際(displayメソッド)は自身のget_column_infoメソッドを呼び、その中でget_column_headers関数が呼び出している。
  6. 呼び出されたget_column_headers関数は、スクリーンID'settings_page_translate'のカラム情報として空の配列を保持しているため、'manage_settings_page_translate_columns'フィルターを使用せず、空の配列を返す。
  7. Gettext_Msg_List_Tableオブジェクトにて表を出力処理に戻り、カラム情報が空の配列だったため、結果として表の見出しや中身が表示されない。

この状況はWP_List_Tableクラスを拡張したクラス(ここではGettext_Msg_List_Table)の各メソッドの記述内容によって多少の状況は異なり、今回のように何も表示されないケースのほか、表の中身のみが表示されるケースもありうる。この症状を解決する手段は、次の2通り考えられる。

  1. 管理画面ページ上部の「表示オプション」を表示する前にGettext_Msg_List_Tableオブジェクトの生成する(今回採用した方法)。
  2. Gettext_Msg_List_Tableオブジェクトの生成後にGettext_Msg_List_Tableオブジェクトを生成する際に現在のスクリーンIDとは異なるものを指定する。
    require_once 'includes/class-gettext-msg-list-table.php';
    $this->wp_list_table = new Gettext_Msg_List_Table( array( 'screen'=>self::PROPERTIES_NAME ) );
    

このほかにも対処方法は考えられるが、上記の2案を状況に応じて使い分けるのがシンプルである。

拡張性に富んだWP_List_Tableクラス

WP_List_Tableクラスにはこのページで取り上げた機能のほか、キーワード検索やページネーション、AJAXサポート機能など拡張性に富んでおり、少ないコードディングで目的の機能を実現できるようになっている。管理画面の各ページで使用されているさまざまな表の実態は、/wp_admin/includes/class-wp-class-〇〇-list-table.phpという名前で存在しており、これらのソースコードがとても参考になる。

今回、現在作成中のプラグインで使ったWP_List_Tableクラスについて使用方法や悩まされた点をメモとしてを残してみた。最終的なソースコードはとてもシンプルで、今後作成するプラグインでも活用していきたい。

【訂正】single_rowメソッド(public)とsingle_row_columnsメソッド(protected)が誤っていたので修正しました。自身でtr要素を出力したい場合はsingle_rowメソッドを、tr要素を出力しない場合はsingle_row_columnsメソッドを使用すること(single_row_columnsメソッド内でtr要素を出力すると重複していまうので要注意)。

お勧めコンテンツ

email_exists(2013年3月4日 登録)

int email_exists( string $email )
メールアドレス(のユーザ)が登録済みか調べる

wp_scheduled_delete(2014年10月8日 登録)

void wp_scheduled_delete( )
古くなったゴミ箱の投稿とコメントを削除する。

get_the_category_by_ID(2011年6月10日 登録)

mixed get_the_category_by_ID( int $cat_ID )
カテゴリーIDからカテゴリー名を取得する。

the_generator(2011年2月7日 登録)

void the_generator( string $type )
generatorタグを表示する。

is_new_day(2011年12月19日 登録)

int is_new_day( )
現在の投稿記事の投稿日が直前の投稿記事と同じか調べる。

最終更新日時 : 2017-09-04 15:07