Creating ListPost Page

The ListPost page shows the latest blog posts in a list. If there are too many posts, they will be displayed in several pages.

Before we proceed with the implementation, we would like to point our homepage to the upcoming ListPage page, because we want the users to see latest posts when they hit the website. To do so, we modify the application configuration protected/application.xml as follows,

......
<services>
  <service id="page" class="TPageService" DefaultPage="posts.ListPost">
    <pages MasterClass="Application.layouts.MainLayout" />
  </service>
</services>

We now create the template and class files for the ListPost page: protected/pages/posts/ListPost.page and protected/pages/posts/ListPost.php.

Creating Page Template

Based on the functionality requirement of the ListPost page, we will use two controls in the page template:

  • TRepeater: this control is mainly used to display a list of data items. The presentation of the each data item can be specified via an inline template or an external template control (the approach we will use here).
  • TPager: this control is used to paginate a list of data items. It interacts with end-users to determine which page of data to be displayed in a list control (e.g. TListBox) or data control (e.g. TRepeater).

Below is the content in the page template:

<%@ Title="My Blog" %>

<com:TContent ID="Main">

<com:TRepeater ID="Repeater"
	ItemRenderer="Application.pages.posts.PostRenderer"
	AllowPaging="true"
	AllowCustomPaging="true"
	PageSize="5"
	/>

<com:TPager ControlToPaginate="Repeater" OnPageIndexChanged="pageChanged" />

</com:TContent>

In the repeater, we specify that the repeated content is to be displayed using the item renderer PostRenderer which we will create soon after. In order for PRADO to find this class, we give the complete namespace path Application.pages.posts.PostRenderer, meaning the class file is protected/pages/posts/PostRenderer.php.

We also set a few other properties of repeater to enable paging. And we set ControlToPaginate property of the pager so that it knows whose repeated content should be paginated.

Creating Page Class

From the above page template, we see that we need to write a page class that implements the event handler: pageChanged() (attached to the pager's OnPageIndexChanged event). We also need to populate post data into the repeater according to the current paging setting. The following is the complete source code of the page class:

class ListPost extends TPage
{
	/**
	 * Initializes the repeater.
	 * This method is invoked by the framework when initializing the page
	 * @param mixed event parameter
	 */
	public function onInit($param)
	{
		parent::onInit($param);
		if(!$this->IsPostBack)  // if the page is requested the first time
		{
			// get the total number of posts available
			$this->Repeater->VirtualItemCount=PostRecord::finder()->count();
			// populates post data into the repeater
			$this->populateData();
		}
	}

	/**
	 * Event handler to the OnPageIndexChanged event of the pager.
	 * This method is invoked when the user clicks on a page button
	 * and thus changes the page of posts to display.
	 */
	public function pageChanged($sender,$param)
	{
		// change the current page index to the new one
		$this->Repeater->CurrentPageIndex=$param->NewPageIndex;
		// re-populate data into the repeater
		$this->populateData();
	}

	/**
	 * Determines which page of posts to be displayed and
	 * populates the repeater with the fetched data.
	 */
	protected function populateData()
	{
		$offset=$this->Repeater->CurrentPageIndex*$this->Repeater->PageSize;
		$limit=$this->Repeater->PageSize;
		if($offset+$limit>$this->Repeater->VirtualItemCount)
			$limit=$this->Repeater->VirtualItemCount-$offset;
		$this->Repeater->DataSource=$this->getPosts($offset,$limit);
		$this->Repeater->dataBind();
	}

	/**
	 * Fetches posts from database with offset and limit.
	 */
	protected function getPosts($offset, $limit)
	{
		// Construts a query criteria
		$criteria=new TActiveRecordCriteria;
		$criteria->OrdersBy['create_time']='desc';
		$criteria->Limit=$limit;
		$criteria->Offset=$offset;
		// query for the posts with the above criteria and with author information
		return PostRecord::finder()->withAuthor()->findAll($criteria);
	}
}

Creating PostRenderer

We still need to create the item renderer class PostRenderer. It defines how each post should be displayed in the repeater. We create it as a template control which allows to specify the post presentation using our flexible template syntax. The template and the class files are saved as PostRenderer.tpl and PostRenderer.php files under the protected/pages/posts directory, respectively.

Creating Renderer Template

The renderer template specifies the presentation of various fields in a post, including title, author name, post time and content. We link the post title to the ReadPost which shows more details of the selected post.

The expression $this->Data refers to the data item passed to the repeater. In our case, it is a PostRecord object. Notice how we retrieve the author name of a post by $this->Data->author->username.

<div class="post-box">
<h3>
<com:THyperLink Text="<%# $this->Data->title %>"
	NavigateUrl="<%# $this->Service->constructUrl('posts.ReadPost',array('id'=>$this->Data->post_id)) %>" />
</h3>

<p>
Author:
<com:TLiteral Text="<%# $this->Data->author->username %>" /><br/>
Time:
<com:TLiteral Text="<%# date('m/d/Y h:m:sa', $this->Data->create_time) %>" />
</p>

<p>
<com:TLiteral Text="<%# $this->Data->content %>" />
</p>
</div>

Creating Renderer Class

The renderer class is very simple. It extends from TRepeaterItemRenderer and contains no other code.

class PostRenderer extends TRepeaterItemRenderer
{
}

Testing

To test the ListPost page, visit the URL http://hostname/blog/index.php (remember we have set ListPost as our new homepage). We shall expect to see the following result. Since we only have one post at the moment, the pager will not show up. Later when we finish NewPost, we can add more posts and come back to test the paging again.