Home » Code » WordPress后台表格数据展示类WP_Table_List的使用

WordPress后台表格数据展示类WP_Table_List的使用

表格(table)一直是后台展示数据的利器,在WordPress中,文章、评论等就是以表格的形式展示的。它有一个专门用于生成展示表格的类,名称为WP_Table_List,位于wp-admin/includes/下面。针对不同情况,继承自它适当的覆盖某些方法即可以满足各种需求。下面根据一个需求简要说说如何使用,详细的请查看官方文档。

这次是弄一个插件,做的一个调查问卷,不过这个调查问卷有点特殊——科技美学那岩的四大旗舰盲评投票。最新一季第六季已经开始投票,但电脑网页版体验不够友好(其实要看出差别也是电脑看比较好),很多网友也在吐槽。我看到时那岩网站是用的WordPress搭建,故想着我能不能自己做出这么一个功能来,于是,就有了这回事——搞一个WordPress插件,功能是创建编辑盲评、展示并收集答卷,最后进行简单统计。

其实之前就弄过一般情况的调查问卷,要出选择、填空等多种类型的题工作量大不少,这里简单一点,只搞展示4张图片这一种题型。很容易的想到需要以下数据实体:

  • 调查表(four_big),字段有这个调查的标题与描述等
  • 一个调查的多个场景(four_big_group),字段有这个场景的标题与描述、展示顺序等
  • 一个场景下的4张图片(four_big_group_item),这里一个调查下的4张图片,记录媒体ID即可,当然也有展示顺序
  • 一个调查收集到的多个答卷(four_big_submit),主要字段是提交的IP,所属地区,提交时间等
  • 一个答卷下各个场景的答案(four_big_submit_value),记录一个场景选择的是第几个

以上5个实体的增删改查,都是类似的,只是换一下表名,加多一个条件而已,因此非常适合使用WP_Table_List来展示。建表略,如何开始开发一个WordPress插件略,我们先创建一个名为Four_Big_Table的类,封装一些公用的属性方法,作为以上5个实体的基类。为了将必要的方法说全,本应放到子类的方法也一并写里边了。

展示调查(four_big)的结果

展示调查(four_big)的结果

首先,要继承自WP_Table_List,必须先将其引入。

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

构造函数中,WP_Table_List接受几个参数,都是可选,其中特别说明一下screen这个,最好给一个值,否则容易导致一个screen的函数报一个变量未定义的Notice。具体看文档。

public function __construct($modName)
{
	parent::__construct(array('screen' => 'four_big'));//不写screen容易报错一个Notice错误
	//其他业务代码...
}

展示表格之前需要准备总记录数、每页显示记录条数用于分页,当然当前页的数据是必不可少的。当前数据与总记录数是要查询数据库的,你可以分开写,但我喜欢写一块,因为它们的查询条件一样的。这里是随意命名,不是实现或覆盖父类的方法。

public function getDataAndTotal($per, $page, $where = '1')
{
	$dataSql = "SELECT * FROM {$this->_table} WHERE $where";
	$totalSql = "SELECT COUNT(*) FROM {$this->_table} WHERE $where";
	if (!empty($_REQUEST['orderby']))
	{
		$dataSql .= " ORDER BY ".esc_sql($_REQUEST['orderby']);
		$dataSql .= !empty($_REQUEST['order']) ? ' '.esc_sql($_REQUEST['order']) : ' ASC';
	}
	else 
	{
		switch ($this->mod)
		{
			case 'four_big_group':
			case 'four_big_group_item':
				$orderby = '`order` ASC';
				break;
			case 'four_big':
				$orderby = 'ID DESC';
				break;
			default:
				$orderby = 'ID DESC';
		}
		$dataSql .= " ORDER BY $orderby";
	}
	$dataSql .= " LIMIT $per";
	$dataSql .= " OFFSET ".($page - 1) * $per;
	$data = $this->_db->get_results($dataSql, ARRAY_A);
	$total = $this->_db->get_var($totalSql);
	return array('data' => $data, 'total' => $total);
}

以上_table是真实表名,mod是模型名,_db是数据库操作类$wpdb,这些根据当前业务自定义的。这里是定义一个比较通用的求当前数据与总记录数的方法。

要展示表格,当然要知道有哪些字段要展示,这里需要实现父类的一个方法get_columns(),返回一个数组。key是字段名,value是显示到表头的值。

public function get_columns()
{
	$columns = array(
		'cb' => '<input type="checkbox" />',
		'title' => '标题',
		'description' => '描述',
		'create_time' => '创建时间',
	        'action' => '操作',
	);
	return $columns;
}

其中cb表示生成一个checkbox,这个需要批量操作就是固定的。有了字段就会循环当前页数据去展示每一行,每一行中每一个单元格td的值通常也需要处理一翻才显示出来,如整型的时间戳就需要通过date()函数格式化后再输出。但很多字段是直接原样输出的,因此,先实现一个父类里的column_default方法,表示默认的处理td数据方法。

public function column_default($item, $column_name)
{
	switch ($column_name)
	{
		case 'title':
		case 'value':
		case 'ip':
		case 'agent':
		case 'address':
		case 'ID':
		case 'media_id':
		case 'order':
			return $item[$column_name];
		case 'description':
			return mb_substr($item['description'], 0, 40);
		case 'create_time':
			return date('Y-m-d H:i:s', $item['create_time']);
		default:
			return "column_default中没有这个$column_name";
	}
}

以上,很多字段是直接输出的,只需简单处理的也直接写上了。至于特殊的字段,再定义一个名为“column_特殊字段名”的方法来处理。如上边图中“操作”这个action字段就是需要特殊处理。

public function column_action($item)
{
	$actions = array(
		'edit' => sprintf(
			'<a href="?page=%s&action=edit&ID=%s">编辑</a>',
			$_REQUEST['page'], 
			$item['ID']
		),
		'viewgroup' => sprintf(
			'<a href="?page=%s&mod=four_big_group&action=list&parent=%s">场景</a>',
			$_REQUEST['page'],
			$item['ID']
		),
	    'submit' => sprintf(
	        '<a href="?page=%s&mod=four_big_submit&action=list&parent=%s">答卷</a>',
	        $_REQUEST['page'],
	        $item['ID']
	    ),
		'view' => sprintf(
			'<a href="%s?ID=%s" target="_blank">预览</a>',
			site_url().'/fourbig',
			$item['ID']
		),
		'delete' => sprintf(
			'<a href="?page=%s&action=delete&ID=%s&_wpnonce=%s">删除</a>', 
			$_REQUEST['page'], 
			$item['ID'],
			$this->createNonce('delete')
		),
	);

	return $this->row_actions($actions, true);
}

row_actions()这个是父类中的方法,用于生成action操作按钮,就是把上边的几个a链接拼接一下。添加true参数表示一直显示。在WordPress中其实见得多的是为false的情况,鼠标在哪一行才显示,如下面的显示场景(four_big_group)的table:

展示场景(four_big_group)的table

展示场景(four_big_group)的table

展示了数据和分页,基本一个表格就完了。但都已经在每行前边加了checkbox选择框,肯定得有批量操作跟它对应,比如批量删除。要生成批量操作的下拉select,实现get_bulk_actions()方法即可。

public function get_bulk_actions() {
    $actions = array(
            'bulk-delete'    => '删除'//key-val,key是option的value,val是option的显示文本
    );
    return $actions;
}

既然有了批量操作,就得响应处理这些批量操作。在WordPress中,一般是直接表单提交来处理,整个table得用一个form来包裹(渲染表格时不会自动生成),点击“应用”按钮就会提交上去了。表格上下两个批量操作选择select的name是action和action2。删除有批量操作POST过来的批量删除和点击链接的单个删除,一并处理。这里自定义一个处理函数process_bulk_action():

public function process_bulk_action()
{
    $action = $this->current_action();
    if ($action === 'delete')
    {
        $nonce = esc_attr($_REQUEST['_wpnonce']);
        if (!$this->verifyNonce($nonce, 'delete'))
        {
            wp_die('error nonce');
        }
        $ID = trim($_REQUEST['ID']);
        if (!ctype_digit($ID))
        {
            wp_die('error ID');
        }
        $result = $this->deleteByID($ID);
        $this->msg(sprintf($result . '个项目被删除。  <a href="%s">返回列表</a>', $_SERVER['HTTP_REFERER']));
        wp_die();
    }
    elseif ($action === 'bulk-delete')
    {
        $ids = $_REQUEST['ID'];
        foreach ($ids as $ID)
        {
            $this->deleteByID($ID);
        }
        $this->msg(count($ids) . '个项目被删除。 ');
    }
}

这里,单个删除是通过访问a链接,生成的删除链接也使用action参数来指定操作(这里我为delete),就可以使用父类的current_action来接收了(实际它是接收批量操作select的action和action2)。不太清楚的时候看具体生成代码及使用var_dump()大法即可。删除完毕后最好是直接跳转回列表,但WordPress这个插件页面中间输出的,前边已经输出了很多东西,无法header跳转了。因此对于单个删除是a链接的GET请求,直接die掉提示一下即可(URL变了,继续展示数据很奇怪,再说如果继续刷新会因参数不全而出错),批量删除的POST操作,可以不管继续取数据展示。msg()方法只是输出一段带HTML提示代码而已。

最后,就是生成展示表格了,需要实现父类中的prepare_items()方法。

public function prepare_items()
{
    if (!empty($_REQUEST['parent']))
    {
        //取当前页数据和总记录的条件,如场景有个条件是属于哪个问卷的。
        $where = 'parent=' . $_REQUEST['parent'];
    }
    else
    {
        $where = '1';
    }
    $this->process_bulk_action();//处理批量操作(包括单个删除)

    $columns = $this->get_columns();//可见的显示的字段,上边有说
    $hidden = array();//隐藏的不可见的字段,用于后续ajax操作等
    $sormod = $this->get_sortable_columns();//可以排序的字段,为可见字段中的部分
    $primary = array('title');//主字段,就是加粗一点
    $this->_column_headers = array($columns, $hidden, $sormod, $primary);//存储以上字段

    $per = $this->get_items_per_page($this->mod . '_per_page', $this->per);//参数1是存储键,参数2是默认值
    $page = $this->get_pagenum();//父类中的方法,获取当前页码
    $dataAndTotal = $this->getDataAndTotal($per, $page, $where);//自定义的方法,获取当前页数据和总记录数
    $this->set_pagination_args(array(
            'total_items' => $dataAndTotal['total'],
            'per_page' => $per,
    ));//父类方法,设置分页参数
    $this->items = $dataAndTotal['data'];//设置当前页数据,准备完毕!

}

其中,为_column_headers设置值,父类中有get_column_info()方法,但其实这个方法是不建议开发者使用的,一般手动设置。get_items_per_page()父类中的方法,一般WordPress后台页面顶端都有个“显示选项”的按钮,点一下会出现可以设置一些参数或者选择哪些显示,这个就是跟那个有关的,可以设置每一页显示多少条。这里没搞那就是取参数2的默认值。数据都准备完毕,就差输出表格了,调用父类中的display()方法即可。但有时在表格上边或者左边右边的还需要加一些提示啊按钮之类的,封装一个方法展示吧。

public function displayTable()
    {
?>
<div class="wrap">
    <div id="icon-users" class="icon32"><br/></div>
    <h2>
<?php
    echo sprintf('【%s】下的场景列表', $this->parent['title']);
    echo sprintf('<a href="%s" class="page-title-action">添加</a>', $this->getActionURL('add'));
    echo sprintf('<a class="page-title-action" href="%s" style="float:right">返回</a>', $this->getBaseURL());
?>  
    </h2>
    <form method="post" action="">
    <input type="hidden" name="page" value="<?php echo $_REQUEST['page']?>" />
<?php   

    $this->display();
?>
    </form>
</div>
<?php
    }

最后,获得类的实例,准备数据,展示即可。

$table = new Four_Big_Table('four_big');

$table->prepare_items();

$table->displayTable();

至于其他添加、编辑之类的,其实不属于Table范围了,可以在prepare_items()之前,定义相应方法处理即可。

参考链接:
https://codex.wordpress.org/Class_Reference/WP_List_Table
http://www.sitepoint.com/using-wp_list_table-to-create-wordpress-admin-tables/

One comment

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Time limit is exhausted. Please reload CAPTCHA.