WordPress 後台自訂文章列表介面

WordPress 後台自訂文章列表介面

wordpress-list-table

WordPress 後台內建的文章列表除了是使用者非常熟悉的操作介面外,更是擁有許多方便實用的功能,但由於該介面是提供給 Post 專用的顯示列表,如果今天是我們自行新增的資料內容而非 Custom Post Type 的時候,預設的文章列表介面就無法使用。

遇到這樣的情況就只有兩種方式可以處理,第一個是建立空白的 Option Page 手刻前端介面,除了表格樣式要自行設計外,還有分頁、篩選、項目顯示設定等功能都需要自己實作,處理起來會花上不少時間。

第二種方法是可以採用 WordPress 內建的類別 WP_List_Table 來處理,透過繼承的方式覆寫相關的方法,可以很快的完成文章列表,我在開發 WooCommerce 推播通知外掛的時候,就是使用這個作法來顯示自訂資料表的內容,這樣做的好處是除了節省手刻的時間外,還能維持統一的使用體驗。

由於 WP_List_Table 有非常多的屬性與方法,這邊我們先專注在以下幾個目標:

  • 顯示特定自訂資料表格的內容
  • 根據資料時間篩選出當週的內容
  • 指定欄位排序
  • 資料單筆/批次刪除
  • 分頁導覽
  • 新增列表頁選單

要注意的是這邊只有實作內容列表的顯示與刪除,如果需要能夠編輯內容則要另外處理編輯頁面,這部分留待下次再來處理。

自訂文章列表介面的邏輯是新增一個繼承 WP_List_Table 的子類別,然後再加入後台選單呼叫該子類別來顯示表格,下面在介紹子類別的寫法時我會先以完成的截圖說明,但要記得在還沒有註冊選單呼叫子類別前都是看不到的。

1. 顯示特定自訂資料表格的內容

WP_List_Table 中最關鍵的方法是用來準備資料內容的 prepare_items() 方法,該方法需要將資料存進 $item 屬性,而這屬性就是內容列表,基本架構如下:

<?php

defined( 'ABSPATH' ) || exit;

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


class MyTable extends WP_List_Table {

	/**
	 * 指定代稱
	 */
	public function __construct() {
		parent::__construct(
			array(
				'singular' => __( 'My Table', 'dwp' ),
				'plural'   => __( 'My Table', 'dwp' ),
				'ajax'     => true,
			)
		);
	}
    
    public function prepare_items(){
      // TODO
    }

}

首先我們要先確認 WP_List_Table 有正確載入,有載入才進行繼承,建構式的部分主要是設定自訂列表的代稱,這代稱會影響 CSS 的 class 名稱,如果有需要在該列表到使用到 Ajax 的話,將 ajax 設為 true 會自動帶入給 JS 使用的變數。

prepare_items 的內容如下:

/**
 * 處理資料顯示
 */
public function prepare_items() {

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

	$per_page     = $this->get_items_per_page( 'items_per_page', 20 );
	$current_page = $this->get_pagenum();
	$total_items  = $this->get_total_items();

	$this->set_pagination_args(
		array(
			'total_items' => $total_items,
			'per_page'    => $per_page,
		)
	);

	$this->items = $this->get_datas( $per_page, $current_page );
}

我以下圖標記各行程式碼所控制的地方:

wordpress-list-table

資料表格最重要的就是表格標題與內容,分別由 $this->_column_headers$this->items 來定義。首先,取得標題是用 get_column_info() 方法,該方法裡面有一個 get_default_primary_column_name() 方法,它會呼叫由 get_columns() 所回傳的陣列,因此要建立標題透過以下程式碼:

/**
 * 欄位標頭
 *
 * @return array
 */
public function get_columns() {
	$columns = array(
		'cb'   => '<input type="checkbox" />',
		'key1' => __( 'title 1', 'dwp' ),
		'key2' => __( 'title 2', 'dwp' ),
		'key3' => __( 'title 3', 'dwp' ),
	);
	return $columns;
}

結果如下:

wordpress-list-table

接下來內容的部分我們設計了一個 get_datas() 方法,帶有兩個參數,分別是 $per_page 每頁顯示筆數,以及 $current_page 目前所在頁數,該方法裡面用 SQL 語法來取得內容,並根據傳入的參數來決定顯示筆數與頁面:

/**
 * 資料 Query
 *
 * @param int $per_page posts per page.
 * @param int $page_number current page number.
 *
 * @return array $result
 */
public static function get_datas( $per_page = 5, $page_number = 1 ) {

	global $wpdb;

	$sql  = "SELECT * FROM {$wpdb->prefix}mytable";
	$sql .= " LIMIT $per_page";
	$sql .= " OFFSET " . ( $page_number - 1 ) * $per_page;

	$result = $wpdb->get_results( $sql, 'ARRAY_A' );

	return $result;
}

mytable 是自訂資料表的名稱,使用 LIMIT 來限制資料筆數,以及 OFFSET 來控制顯示第幾頁的資料,最後將查詢結果回傳,並存入 $this->items 屬性,接下來我們需要針對表格的標題來顯示對應的資料。

這邊用到的方法是 column_default(),帶有兩個參數,分別是 $item 也就是稍早存入 $this->items 的查詢結果,另一個是 $column_name 也就是透過 get_columns() 新增的陣列,為了方便閱讀我把兩個方法放在一起:

public function get_columns() {
	$columns = array(
		'cb'   => '<input type="checkbox" />',
		'key1' => __( 'title 1', 'dwp' ),
		'key2' => __( 'title 2', 'dwp' ),
		'key3' => __( 'title 3', 'dwp' ),
	);
	return $columns;
}

public function column_default( $item, $column_name ) {
	switch ( $column_name ) {
		case 'key1':
			return $item['key1'] .'這是第一個欄位的內容';
		case 'key2':
			return $item['key2'] .'這是第二個欄位的內容';
		default:
	}
	return $item[ $column_name ]; // key3 直接返回內容
}

這邊有個前提是自訂資料表欄位的 key 名稱,要跟表格標題陣列的 key 一樣,這樣在 column_default() 裡面就可以直接返回 $item[$column_name] 來取得值,如果你希望在標題一的欄位輸入不同的結果,可能是加文字或是刪除按鈕,就可以用判斷式來個別處理。

2.根據資料時間篩選出當週的內容

接下來我們想要增加資料篩選的模式,當以週為單位時,列表只會顯示本週的內容,時間的判斷是以自訂資料欄位 data_date 為基準,我們要先加入篩選按鈕以及點擊後所帶入的參數,使用的方法為 get_views()

/**
 * 篩選資料按鈕
 */
protected function get_views() {
	$date_week = add_query_arg(
		array(
			'date_week_start' => date( 'Y-m-d', time() + ( 1 - date( 'w' ) ) * 24 * 3600 ),
			'date_week_end'   => date( 'Y-m-d', time() + ( 1 - date( 'w' ) ) * 24 * 3600 + 6 * 86400 ),
		),
		'edit.php?post_type=wc-notify&page=wc-notify-history'
	);

	$status_links = array(
		// Translators: %s: all.
		'all'   => sprintf( __( '<a href="%s">All</a>', 'dwp' ), admin_url( 'edit.php?post_type=wc-notify&page=wc-notify-history' ) ),
		// Translators: %s: week.
		'week'  => sprintf( __( '<a href="%s">Week</a>', 'dwp' ), $date_week ),
		// Translators: %s: month.
	);
	return $status_links;
}

我們會用網址的參數 get_datas() 去查詢符合條件的內容,因此使用 add_query_arg() 來帶入一週開始與結束的日期,也就是 https://abc.com/wp-admin/edit.php?page=my-table-list&date_week_start=本週開始日&date_week_end=本週結束日這樣的格式,加入後就可以在左上角看到篩選按鈕:

wordpress-list-table

完成介面後我們要修改 get_datas() 的查詢結果,新增 SQL WHERE 的語法:

/**
 * 資料 Query
 *
 * @param int $per_page posts per page.
 * @param int $page_number current page number.
 *
 * @return array $result
 */
public static function get_datas( $per_page = 5, $page_number = 1 ) {

	global $wpdb;

	$sql  = "SELECT * FROM {$wpdb->prefix}mytable";

	// 加入當週數據
	if ( ! empty( $_GET['date_week_start'] ) && ! empty( $_GET['date_week_end'] ) && empty( $_GET['date_start'] ) ) {
		$sql .= ' WHERE data_date >="' . esc_sql( $_GET['date_week_start'] ) . '" AND data_date <= "' . esc_sql( $_GET['date_week_end'] ) . '"';
	}

	$sql .= " LIMIT $per_page";
	$sql .= " OFFSET " . ( $page_number - 1 ) * $per_page;

	$result = $wpdb->get_results( $sql, 'ARRAY_A' );

	return $result;
}

先判斷網址是否帶有 date_week_startdate_week_end 兩個參數,有的話去比對日期大等於一週開始日以及小於等於一週結束日的內容,這樣就能拿到符合篩選結果的列表。

3.指定欄位排序

接下來我們希望可以按照日期從新到舊或是舊到新進行排序,只要點擊日期的標題即可:

wordpress-list-table get sortable columns

實作的邏輯跟日期篩選很像,也就是在點擊日期標題的時候網址會帶入 orderby 以及 order 這兩個參數,前者是欄位名稱,後者是排序邏輯,要將標題增加排序功能使用的方法是 get_sortable_columns(),回傳一個欄位 key ⇒ 欄位名稱的陣列:

/**
 * 欄位允許排序
 *
 * @return array
 */
public function get_sortable_columns() {
	$sortable_columns = array(
		'data_date' => array( 'data_date', 'asc' ),
	);
	return $sortable_columns;
}

$sortable_columns 的格式是欄位的 key 以及欄位名稱帶有排序條件的陣列,array(‘data_date’,’asc’) 的意思是當表格標題為 data_date 的時候,點下去時會用升冪也就是舊到新的順序排,如果第二個參數 asc 改成 true 的話,則預設會以降冪新到舊的方式排序。

因此當點擊 data_date 的標題後,網址自動就會帶入 orderby=data_date 以及 order=asc 這兩個參數,我們就可以在 get_datas() 裡面用這兩個參數來改變查詢結果,新增如下:

/**
 * 資料 Query
 *
 * @param int $per_page posts per page.
 * @param int $page_number current page number.
 *
 * @return array $result
 */
public static function get_datas( $per_page = 5, $page_number = 1 ) {

	global $wpdb;

	$sql  = "SELECT * FROM {$wpdb->prefix}mytable";

	// 加入當週數據
	// ...

	// 加入排序條件
	if ( ! empty( $_GET['orderby'] ) ) {
		$sql .= ' ORDER BY ' . esc_sql( $_GET['orderby'] );
		$sql .= ! empty( $_GET['order'] ) ? ' ' . esc_sql( $_GET['order'] ) : ' ASC';
	} else {
		$sql .= ' ORDER BY id DESC';
	}

	$sql .= " LIMIT $per_page";
	$sql .= " OFFSET " . ( $page_number - 1 ) * $per_page;

	$result = $wpdb->get_results( $sql, 'ARRAY_A' );

	return $result;
}

當網址帶有 orderby 的參數時,則使用帶入的參數值作為排序欄位,然後再拿 order 的值作為排序升/降冪的判斷,如果沒有的話預設就是用內容的 ID 以降冪進行排序。

4.資料單筆/批次刪除

我們先處理單筆刪除的部分,為了保持一致的使用體驗,我們把它設計成跟文章一樣,只有當滑鼠停留在內容標題時才會出現刪除按鈕:

wordpress-list-table

要增加這個刪除紀錄按鈕的在之前我們使用過的 column_default() 方法裡面,也就是根據表格標題輸出內容的地方,在這邊我們除了輸出資料外,還要增加一個 row_actions() 方法,這個方法能幫我們加入滑鼠停留時才顯示的按鈕。

假設我們的欄位名稱叫做 user_id,就可以判斷當 $column_nameuser_id 時輸出 row_actions()

/**
 * 欄位值設定
 *
 * @param array  $item
 * @param string $column_name
 *
 * @return mixed
 */
public function column_default( $item, $column_name ) {
	switch ( $column_name ) {
		case 'user_id':
			$delete_nonce = wp_create_nonce( 'history_delete' );
			$actions      = array(
				'delete' => sprintf( '<a href="edit.php?post_type=wc-notify&page=%s&action=%s&data=%s&_wpnonce=%s">' . __( 'Delete History', 'wc-notify' ) . '</a>', esc_attr( $_GET['page'] ), 'delete', absint( $item['id'] ), $delete_nonce ),
			);
			return ( '0' != $item['user_id'] ) ? '<a href="' . admin_url( 'user-edit.php?user_id=' . $item['user_id'] ) . '">' . get_userdata( $item['user_id'] )->user_login . '</a>' . $this->row_actions( $actions ) : '<span>-</span>' . $this->row_actions( $actions );
		default:
	}
	return $item[ $column_name ];
}

首先,因為我們要做的是刪除行為,為了安全起見,使用避免跨站請求的 Nonce 驗證來多一層安全防護,然後是要提供給 row_actions() 參數的 $actions 陣列,該陣列定義了 key 以及輸出的 HTML,並讓點擊的網址帶入四個參數,分別是目前所在頁數 page、動作辨識名稱 aciton、要刪除的資料 ID data,以及安全驗證 _wpnonce,最後在輸出欄位內容的地方使用 $this->row_actions( $actions ) 來產生按鈕。

接下來我們設計一個 delete_history() 方法來刪除資料,帶有一個資料 ID 的參數:

/**
 * 刪除資料
 *
 * @param int $id data ID
 */
public function delete_history( $id ) {
	global $wpdb;
	$wpdb->delete(
		"{$wpdb->prefix}mytable",
		array( 'id' => $id ),
		array( '%d' )
	);
}

然後再設計一個 process_bulk_action() 方法,這個方法會在 prepare_items() 裡面呼叫:

/**
 * 處理動作
 */
public function process_bulk_action() {
	if ( 'delete' === $this->current_action() ) {
		$nonce = esc_attr( $_GET['_wpnonce'] );
		if ( ! wp_verify_nonce( $nonce, 'history_delete' ) ) {
			die( '發生錯誤!' );
		} else {
			$this->delete_history( absint( $_GET['data'] ) );
			wp_safe_redirect( admin_url( 'edit.php?post_type=wc-notify&page=wc-notify-history' ) );
			exit;
		}
	}
}

在這方法裡面我們先用 current_action() 取得動作的名稱,也就是我們在上一個步驟 row_actions( $actions ) 裡面所設定的 action 代稱,然後檢查 Nonce 後呼叫 delete_history() 方法,並帶入從網址中取得的資料 ID,最後將頁面重新導向回列表頁,這樣就完成了資料的刪除。

至於批次刪除的部分我們要先增加批次操作下拉選單的選項,使用的是 get_bulk_actions() 方法,以陣列的方式加入:

/**
 * 批次操作選單
 *
 * @return array
 */
public function get_bulk_actions() {
	$actions = array(
		'bulk-delete' => __( '一次刪除一大堆', 'dwp' ),
	);
	return $actions;
}

這樣就可以在批次操作的下拉選單看到我們加入的選項:

wp-list-table

然後我們需要加入內容的勾選方塊,這樣才能取得已勾選的項目,使用的是 column_cb() 方法:

/**
 * 批次操作 checkbox
 *
 * @param array $item
 *
 * @return string
 */
public function column_cb( $item ) {
	return sprintf(
		'<input type="checkbox" name="bulk-delete[]" value="%s" />',
		$item['id']
	);
}

加入後才可以在每一列的最前面看到勾選方塊:

wp-list-table

回到上面完成的 process_bulk_action() 方法,加入以下程式碼:

/**
 * 處理動作
 */
public function process_bulk_action() {
	// 單筆刪除
	// ...

	// 批次刪除
	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 ) {
			$this->delete_history( $id );
		}
			wp_safe_redirect( admin_url( 'edit.php?post_type=wc-notify&page=wc-notify-history' ) );
			exit;
	}
}

之所以會有 action 與 action2 是因為批次操作在頁首與頁尾都有,因此需要判斷兩個:

wp-list-table

取得勾選中的資料 ID 後,用迴圈去執行 delete_history() 方法即可完成批次刪除的功能。

5.分頁導覽

使用 WP_List_Table 最方便的是只要使用內建的 set_pagination_args() 的方法就能搞定分頁導覽的功能,該方法帶入一個陣列參數,只要計算出總資料筆數以及每頁顯示筆數帶入即可,這個方法一樣放在 prepare_items() 裡面:

/**
 * 處理資料顯示
 */
public function prepare_items() {

	// 略

	$per_page = $this->get_items_per_page( 'items_per_page', 20 );

	$this->set_pagination_args(
		array(
            'per_page'    => $per_page,
			'total_items' => $this->get_total_items(),
			
		)
	);
}

取得每頁顯示筆數的方法 get_items_per_page() 我們會使用稍後註冊的 items_per_page 這個 key,第二個參數是預設顯示筆數,取得該參數後帶入 set_pagination_args() 中作為 per_page 的值即可。

其次是取得總資料筆數,這個筆數會根據篩選的結果進行變化,譬如我們以當週的條件來篩選,總筆數就會跟全部顯示的數量不一樣,為此我們設計了一個 get_total_items() 方法來取得這個數字:

/**
 * 取得資料筆數
 *
 * @return null|string
 */
public function get_total_items() {
	global $wpdb;
	$sql = "SELECT COUNT(*) FROM {$wpdb->prefix}mytable";

	// 當週數據
	if ( ! empty( $_REQUEST['date_week_start'] ) && ! empty( $_REQUEST['date_week_end'] ) && empty( $_REQUEST['date_start'] ) ) {
		$sql .= ' WHERE notify_time >="' . esc_sql( $_REQUEST['date_week_start'] ) . '" AND notify_time <= "' . esc_sql( $_REQUEST['date_week_end'] ) . '"';
	}

	return $wpdb->get_var( $sql );
}

我們使用 SQL 的 COUNT 語法來取得總資料筆數,並在帶有篩選條件時改變結果。

經過以上五個步驟我們設計好的子類別大概會長這樣:

<?php

defined( 'ABSPATH' ) || exit;

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


class MyTable extends \WP_List_Table {

	/**
	 * 指定代稱
	 */
	public function __construct() {
		parent::__construct(
			array(
				'singular' => __( 'My Table', 'wc-notify' ),
				'plural'   => __( 'My Table', 'wc-notify' ),
				'ajax'     => true,
			)
		);
	}

	/**
	 * 資料 Query
	 *
	 * @param int $per_page
	 * @param int $page_number
	 *
	 * @return mixed
	 */
	public function get_datas( $per_page = 5, $page_number = 1 ) {

		global $wpdb;

		$sql = "SELECT * FROM {$wpdb->prefix}mytable";

		// 當週數據
		if ( ! empty( $_GET['date_week_start'] ) 。& ! empty( $_GET['date_week_end'] ) && empty( $_GET['date_start'] ) ) {
			$sql .= ' WHERE data_date >="' . esc_sql( $_GET['date_week_start'] ) . '" AND data_date <= "' . esc_sql( $_GET['date_week_end'] ) . '"';
		}

		// 排序
		if ( ! empty( $_REQUEST['orderby'] ) ) {
			$sql .= ' ORDER BY ' . esc_sql( $_REQUEST['orderby'] );
			$sql .= ! empty( $_REQUEST['order'] ) ? ' ' . esc_sql( $_REQUEST['order'] ) : ' ASC';
		} else {
			$sql .= ' ORDER BY id DESC';
		}

		$sql .= " LIMIT $per_page";
		$sql .= ' OFFSET ' . ( $page_number - 1 ) * $per_page;

		$result = $wpdb->get_results( $sql, 'ARRAY_A' );

		return $result;
	}

	/**
	 * 取得資料筆數
	 *
	 * @return null|string
	 */
	public function get_total_items() {
		global $wpdb;
		$sql = "SELECT COUNT(*) FROM {$wpdb->prefix}mytable";

		// 當週數據
		if ( ! empty( $_REQUEST['date_week_start'] ) && ! empty( $_REQUEST['date_week_end'] ) && empty( $_REQUEST['date_start'] ) ) {
			$sql .= ' WHERE data_date >="' . esc_sql( $_REQUEST['date_week_start'] ) . '" AND data_date <= "' . esc_sql( $_REQUEST['date_week_end'] ) . '"';
		}

		return $wpdb->get_var( $sql );
	}

	/**
	 * 沒有資料時的顯示文字
	 */
	public function no_items() {
		echo esc_html( __( 'No Data', 'dwp' ) );
	}

	/**
	 * 篩選資料按鈕
	 */
	protected function get_views() {
		$date_week = add_query_arg(
			array(
				'date_week_start' => date( 'Y-m-d', time() + ( 1 - date( 'w' ) ) * 24 * 3600 ),
				'date_week_end'   => date( 'Y-m-d', time() + ( 1 - date( 'w' ) ) * 24 * 3600 + 6 * 86400 ),
			),
			'edit.php?post_type=wc-notify&page=wc-notify-history'
		);


		$status_links = array(
			// Translators: %s: all.
			'all'   => sprintf( __( '<a href="%s">All</a>', 'dwp' ), admin_url( 'edit.php?post_type=wc-notify&page=wc-notify-history' ) ),
			// Translators: %s: week.
			'week'  => sprintf( __( '<a href="%s">Week</a>', 'dwp' ), $date_week ),
		);
		return $status_links;
	}


	/**
	 * 欄位值設定
	 *
	 * @param array  $item
	 * @param string $column_name
	 *
	 * @return mixed
	 */
	public function column_default( $item, $column_name ) {
		switch ( $column_name ) {
			case 'user_id':
				$delete_nonce = wp_create_nonce( 'history_delete' );
				$actions      = array(
					'delete' => sprintf( '<a href="edit.php?post_type=wc-notify&page=%s&action=%s&data=%s&_wpnonce=%s">' . __( 'Delete History', 'dwp' ) . '</a>', esc_attr( $_REQUEST['page'] ), 'delete', absint( $item['id'] ), $delete_nonce ),
				);
				return ( '0' != $item['user_id'] ) ? '<a href="' . admin_url( 'user-edit.php?user_id=' . $item['user_id'] ) . '">' . get_userdata( $item['user_id'] )->user_login . '</a>' . $this->row_actions( $actions ) : '<span>-</span>' . $this->row_actions( $actions );
			case 'user_info':
				return $item[ $column_name ];
			case 'order_id':
				return ( '0' != $item[ $column_name ] ) ? '<a href="' . admin_url( 'post.php?post=' . $item[ $column_name ] ) . '&action=edit">#' . $item[ $column_name ] . '</a>' : '-';
			default:
		}
		return $item[ $column_name ];
	}

	/**
	 * 欄位標頭
	 *
	 * @return array
	 */
	public function get_columns() {
		$columns = array(
			'cb'             => '<input type="checkbox" />',
			'user_id'        => __( 'User ID', 'dwp' ),
			'user_info'      => __( 'User Info', 'dwp' ),
			'order_id'       => __( 'Order ID', 'dwp' ),
			'notify_type'    => __( 'Notify Type', 'dwp' ),
			'notify_content' => __( 'Notify Content', 'dwp' ),
			'status'         => __( 'Status', 'dwp' ),
			'data_date'      => __( 'Notify Time', 'dwp' ),
		);
		return $columns;
	}

	/**
	 * 允許排序欄位
	 *
	 * @return array
	 */
	public function get_sortable_columns() {
		$sortable_columns = array(
			'data_date' => array( 'data_date', 'asc' ),
		);
		return $sortable_columns;
	}

	/**
	 * 批次操作 checkbox
	 *
	 * @param array $item
	 *
	 * @return string
	 */
	function column_cb( $item ) {
		return sprintf(
			'<input type="checkbox" name="bulk-delete[]" value="%s" />',
			$item['id']
		);
	}

	/**
	 * 批次操作選單
	 *
	 * @return array
	 */
	public function get_bulk_actions() {
		$actions = array(
			'bulk-delete' => __( 'Delete', 'dwp' ),
		);
		return $actions;
	}


	/**
	 * 處理資料顯示
	 */
	public function prepare_items() {

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

		$per_page     = $this->get_items_per_page( 'items_per_page', 20 );
		$current_page = $this->get_pagenum();

		$this->set_pagination_args(
			array(
				'total_items' => $this->get_total_items(),
				'per_page'    => $per_page,
			)
		);

		$this->items = $this->get_datas( $per_page, $current_page );
	}

	/**
	 * 刪除資料
	 *
	 * @param int $id ID
	 */
	public function delete_history( $id ) {
		global $wpdb;
		$wpdb->delete(
			"{$wpdb->prefix}mytable",
			array( 'id' => $id ),
			array( '%d' )
		);
	}

	/**
	 * 處理批次操作
	 */
	public function process_bulk_action() {
		if ( 'delete' === $this->current_action() ) {
			$nonce = esc_attr( $_GET['_wpnonce'] );

			if ( ! wp_verify_nonce( $nonce, 'history_delete' ) ) {
				die( '發生錯誤!' );
			} else {
				$this->delete_history( absint( $_GET['data'] ) );
				wp_safe_redirect( admin_url( 'edit.php?post_type=wc-notify&page=wc-notify-history' ) );
				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 ) {
				$this->delete_history( $id );
			}
			wp_safe_redirect( admin_url( 'edit.php?post_type=wc-notify&page=wc-notify-history' ) );
			exit;
		}
	}
}

6.新增列表頁選單

完成 MyTable 類別後接下來我們實作它,實作的邏輯是先新增一個後台選單,然後在該選單的頁面中建立 MyTable 實體,並呼叫內建的方法以顯示表格,最後再加入後台右上角「顯示項目設定」的每頁顯示筆數,就能讓使用者自訂一頁要顯示幾筆資料。

首先是新增後台選單,由於我想要把這個選單放在指定的文章底下,因此我用的是 add_submenu_page() 來增加子選單,只要把該函式放在勾點 admin_menu 即可:

<?php

add_action(
	'admin_menu',
	function() {
		$my_table = new MyTable();
		$hook     = add_submenu_page(
			'edit.php?post_type=wc-notify',
			__( 'History', 'dwp' ),
			__( 'History', 'dwp' ),
			'manage_options',
			'wc-notify-history',
			function() { ?>
				<div class="wrap">
					<h2><?php echo esc_html( __( 'Notify History', 'wc-notify' ) ); ?></h2>
					<div id="poststuff">
						<div id="post-body" class="metabox-holder">
							<div id="post-body-content">
								<div class="meta-box-sortables ui-sortable">
									<form method="post">
										<?php
										$my_table->views();
										$my_table->prepare_items();
										$my_table->display();
										?>
									</form>
								</div>
							</div>
						</div>
						<br class="clear">
					</div>
				</div>
				<?php
			},
		);
	}
);

可以看到我們先建立 MyTable 類別的實體以供後續表格內容使用,並且依序呼叫顯示篩選按鈕的 views()、準備顯示資料的 prepare_items() 以及實際顯示表格的 display() 這三個方法,這樣就能將列表顯示出來。

其次要在 admin_menu 裡面加入「顯示項目設定」的功能,這邊會採用動態勾點 load-$hook 也就是 add_submenu_page() 的回傳結果,並使用 add_screen_option() 方法來加入設定項的代稱以及預設值:

<?php
add_action(
	'admin_menu',
	function() {
		$my_table = new MyTable();
		$hook     = add_submenu_page(
			// 略
		);
		add_action(
			"load-$hook",
			function() {
				$args = array(
					'label'   => __( '一頁要顯示幾筆隨便你', 'dwp' ),
					'default' => 20,
					'option'  => 'items_per_page',
				);
				add_screen_option( 'per_page', $args );
			}
		);
	}
);

還記得我們在 prepare_items() 裡面用到的 get_items_per_page(‘items_per_page’,20) 嗎?第一個參數就是在 add_screen_option() 裡面設定的,完成後就可以在顯示設定裡面看到我們自行加入的欄位:

wp-list-table

最後一個關鍵是要用勾點 set-screen-option 儲存修改的資料,沒有這個勾點你會發現不管怎麼改每頁顯示筆數只會停留在預設值:

<?php

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

透過以上的六個步驟就能建立一個基礎的自訂文章列表介面,雖然還是滿費事的,但我覺得應該還是會比自己手刻來得快,如果你有更好更快速的方法歡迎分享給我吧~

文章標籤wp-list-tablewpdb

目錄

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料

Picture of 賴俊吾 / Oberon Lai
賴俊吾 / Oberon Lai

現為全職 WordPress 工程師,網站開發經歷 11 年,專攻前端工程與 WordPress 佈景主題、外掛客製化開發

訂閱電子報

Hi,我是 Oberon,我會固定在每週五早上發送接案心得以及與 WordPress 相關的電子報,同時也會分享一些實用的開發知識,讓你在 WordPress 的接案路上不孤單!

專注於分享 WordPress 開發、接案技巧、專案管理等自由工作者必備知識與心得

© 2024 想點創意科技有限公司

想點創意科技有限公司 | 統一編號 90516823
Designed by Hend Design | 隱私權政策

訂閱電子報

Hi,我是 Oberon,我會固定在每週五早上發送接案心得以及與 WordPress 相關的電子報,同時也會分享一些實用的開發知識,讓你在 WordPress 的接案路上不孤單!