summaryrefslogtreecommitdiff
path: root/demos/blog-tutorial/protected/pages/Day4
diff options
context:
space:
mode:
authorxue <>2007-06-26 15:59:16 +0000
committerxue <>2007-06-26 15:59:16 +0000
commitf92ca63a225d43e7ecca4ebe528c2a1e206a40f1 (patch)
tree0fb92aafab5fedc866c6cafd49433d72bf719114 /demos/blog-tutorial/protected/pages/Day4
parentc31e5ab3ebd4ae1561cbdaeaa504d1095dcbbb0a (diff)
Completed day 4 for blog tutorial.
Diffstat (limited to 'demos/blog-tutorial/protected/pages/Day4')
-rw-r--r--demos/blog-tutorial/protected/pages/Day4/CreateEditPost.page133
-rw-r--r--demos/blog-tutorial/protected/pages/Day4/CreateListPost.page183
-rw-r--r--demos/blog-tutorial/protected/pages/Day4/CreateNewPost.page135
-rw-r--r--demos/blog-tutorial/protected/pages/Day4/CreateReadPost.page135
-rw-r--r--demos/blog-tutorial/protected/pages/Day4/Overview.page26
-rw-r--r--demos/blog-tutorial/protected/pages/Day4/output.gifbin0 -> 3406 bytes
-rw-r--r--demos/blog-tutorial/protected/pages/Day4/output2.gifbin0 -> 6326 bytes
-rw-r--r--demos/blog-tutorial/protected/pages/Day4/output3.gifbin0 -> 11874 bytes
-rw-r--r--demos/blog-tutorial/protected/pages/Day4/output4.gifbin0 -> 11916 bytes
9 files changed, 612 insertions, 0 deletions
diff --git a/demos/blog-tutorial/protected/pages/Day4/CreateEditPost.page b/demos/blog-tutorial/protected/pages/Day4/CreateEditPost.page
new file mode 100644
index 00000000..95d00765
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day4/CreateEditPost.page
@@ -0,0 +1,133 @@
+<com:TContent ID="Main">
+
+<h1>Creating <tt>EditPost</tt> Page</h1>
+
+<p>
+The <tt>EditPost</tt> page is provided to authors and the administrator to edit existing blog posts. Like the <a href="?page=Day4.CreateNewPost">NewPost</a> page, it displays a form to collect the change to the title and content of a post.
+</p>
+
+<p>
+We create two files <tt>protected/pages/posts/EditPost.page</tt> and <tt>protected/pages/posts/EditPost.php</tt> to save the page template and page class, respectively.
+</p>
+
+<h2>Creating Page Template</h2>
+<p>
+The <tt>EditPost</tt> page template is very similar to the <tt>NewPost</tt> template. Only the page title and the button caption are different.
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="prado">
+&lt;%@ Title="My Blog - Edit Post" %>
+
+&lt;com:TContent ID="Main">
+
+<h1>Edit Post</h1>
+
+<span>Title:</span>
+&lt;com:TRequiredFieldValidator
+ ControlToValidate="TitleEdit"
+ ErrorMessage="Please provide a title."
+ Display="Dynamic" />
+<br/>
+&lt;com:TTextBox ID="TitleEdit" Columns="50" />
+
+<br/>
+<span>Content:</span>
+&lt;com:TRequiredFieldValidator
+ ControlToValidate="ContentEdit"
+ ErrorMessage="Please provide content."
+ Display="Dynamic" />
+<br/>
+&lt;com:THtmlArea ID="ContentEdit" />
+
+<br/>
+&lt;com:TButton Text="Save" OnClick="saveButtonClicked" />
+
+&lt;/com:TContent>
+</com:TTextHighlighter>
+
+
+<h2>Creating Page Class</h2>
+
+<p>
+The <tt>EditPage</tt> page class is slightly complex than <tt>NewPage</tt> because it needs to load the specified post data first. It also needs to perform additional authorization check. In particular, it needs to ensure that a post can only be editted by the author or the administrator. Such authorization check is not provided by PRADO itself.
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="php">
+class EditPost extends TPage
+{
+ /**
+ * Initializes the inputs with existing post data.
+ * This method is invoked by the framework when the page is being initialized.
+ * @param mixed event parameter
+ */
+ public function onInit($param)
+ {
+ parent::onInit($param);
+ // Retrieves the existing user data. This is equivalent to:
+ // $postRecord=$this->getPost();
+ $postRecord=$this->Post;
+ // Authorization check: only the author or the administrator can edit the post
+ if($postRecord->author_id!==$this->User->Name && !$this->User->IsAdmin)
+ throw new THttpException(500,'You are not allowed to edit this post.');
+
+ if(!$this->IsPostBack) // if the page is initially requested
+ {
+ // Populates the input controls with the existing post data
+ $this->TitleEdit->Text=$postRecord->title;
+ $this->ContentEdit->Text=$postRecord->content;
+ }
+ }
+
+ /**
+ * Saves the post if all inputs are valid.
+ * This method responds to the OnClick event of the "Save" button.
+ * @param mixed event sender
+ * @param mixed event parameter
+ */
+ public function saveButtonClicked($sender,$param)
+ {
+ if($this->IsValid) // when all validations succeed
+ {
+ // Retrieves the existing user data. This is equivalent to:
+ // $postRecord=$this->getPost();
+ $postRecord=$this->Post;
+
+ // Fetches the input data
+ $postRecord->title=$this->TitleEdit->SafeText;
+ $postRecord->content=$this->ContentEdit->SafeText;
+
+ // saves to the database via Active Record mechanism
+ $postRecord->save();
+
+ // redirects the browser to the ReadPost page
+ $url=$this->Service->constructUrl('posts.ReadPost',array('id'=>$postRecord->post_id));
+ $this->Response->redirect($url);
+ }
+ }
+
+ /**
+ * Returns the post data to be editted.
+ * @return PostRecord the post data to be editted.
+ * @throws THttpException if the post data is not found.
+ */
+ protected function getPost()
+ {
+ // the ID of the post to be editted is passed via GET parameter 'id'
+ $postID=(int)$this->Request['id'];
+ // use Active Record to look for the specified post ID
+ $postRecord=PostRecord::finder()->findByPk($postID);
+ if($postRecord===null)
+ throw new THttpException(500,'Post is not found.');
+ return $postRecord;
+ }
+}
+</com:TTextHighlighter>
+
+<h2>Testing</h2>
+<p>
+To test the <tt>EditPost</tt> page, login first and visit the following URL: <tt>http://hostname/blog/index.php?page=EditPost&id=1</tt>. This URL can also be reached by clicking on the <tt>Edit</tt> link on a post detail page.
+</p>
+
+<img src="<%~ output4.gif %>" class="output" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/protected/pages/Day4/CreateListPost.page b/demos/blog-tutorial/protected/pages/Day4/CreateListPost.page
new file mode 100644
index 00000000..e25c0609
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day4/CreateListPost.page
@@ -0,0 +1,183 @@
+<com:TContent ID="Main">
+
+<h1>Creating <tt>ListPost</tt> Page</h1>
+
+<p>
+The <tt>ListPost</tt> page shows the latest blog posts in a list. If there are too many posts, they will be displayed in several pages.
+</p>
+
+<p>
+Before we proceed with the implementation, we would like to point our homepage to the upcoming <tt>ListPage</tt> page, because we want the users to see latest posts when they hit the website. To do so, we modify the application configuration <tt>protected/application.xml</tt> as follows,
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="xml">
+......
+<services>
+ <service id="page" class="TPageService" DefaultPage="posts.ListPost">
+ <pages MasterClass="Application.layouts.MainLayout" />
+ </service>
+</services>
+</com:TTextHighlighter>
+
+<p>
+We now create the template and class files for the <tt>ListPost</tt> page: <tt>protected/pages/posts/ListPost.page</tt> and <tt>protected/pages/posts/ListPost.php</tt>.
+</p>
+
+<h2>Creating Page Template</h2>
+<p>
+Based on the functionality requirement of the <tt>ListPost</tt> page, we will use two controls in the page template:
+</p>
+<ul>
+<li><a href="http://www.pradosoft.com/demos/quickstart/?page=Controls.Repeater">TRepeater</a>: 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).</li>
+<li><a href="http://www.pradosoft.com/demos/quickstart/?page=Controls.Pager">TPager</a>: 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 <a href="http://www.pradosoft.com/demos/quickstart/?page=Controls.List">list control</a> (e.g. <tt>TListBox</tt>) or <a href="http://www.pradosoft.com/demos/quickstart/?page=Controls.Data">data control</a> (e.g. <tt>TRepeater</tt>).</li>
+</ul>
+
+<p>
+Below is the content in the page template:
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="prado">
+&lt;%@ Title="My Blog" %>
+
+&lt;com:TContent ID="Main">
+
+&lt;com:TRepeater ID="Repeater"
+ ItemRenderer="Application.pages.posts.PostRenderer"
+ AllowPaging="true"
+ AllowCustomPaging="true"
+ PageSize="5"
+ />
+
+&lt;com:TPager ControlToPaginate="Repeater" OnPageIndexChanged="pageChanged" />
+
+&lt;/com:TContent>
+</com:TTextHighlighter>
+
+<p>
+In the repeater, we specify that the repeated content is to be displayed using the item renderer <tt>PostRenderer</tt> which we will create soon after. In order for PRADO to find this class, we give the complete namespace path <tt>Application.pages.posts.PostRenderer</tt>, meaning the class file is <tt>protected/pages/posts/PostRenderer.php</tt>.
+</p>
+
+<p>
+We also set a few other properties of repeater to enable paging. And we set <tt>ControlToPaginate</tt> property of the pager so that it knows whose repeated content should be paginated.
+</p>
+
+
+<h2>Creating Page Class</h2>
+
+<p>
+From the above page template, we see that we need to write a page class that implements the event handler: <tt>pageChanged()</tt> (attached to the pager's <tt>OnPageIndexChanged</tt> 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:
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="php">
+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);
+ }
+}
+</com:TTextHighlighter>
+
+<h2>Creating <tt>PostRenderer</tt></h2>
+
+<p>
+We still need to create the item renderer class <tt>PostRenderer</tt>. 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 <tt>PostRenderer.tpl</tt> and <tt>PostRenderer.php</tt> files under the <tt>protected/pages/posts</tt> directory, respectively.
+</p>
+
+<h3>Creating Renderer Template</h3>
+<p>
+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 <tt>ReadPost</tt> which shows more details of the selected post.
+</p>
+<p>
+The expression <tt>$this->Data</tt> refers to the data item passed to the repeater. In our case, it is a <tt>PostRecord</tt> object. Notice how we retrieve the author name of a post by <tt>$this->Data->author->username</tt>.
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="prado">
+<h3>
+&lt;com:THyperLink Text="&lt;%# $this->Data->title %>"
+ NavigateUrl="&lt;%# $this->Service->constructUrl('posts.ReadPost',array('id'=>$this->Data->post_id)) %>" />
+</h3>
+
+<p>
+Author:
+&lt;com:TLiteral Text="&lt;%# $this->Data->author->username %>" /><br/>
+Time:
+&lt;com:TLiteral Text="&lt;%# date('m/d/Y h:m:sa', $this->Data->create_time) %>" />
+</p>
+
+<p>
+&lt;com:TLiteral Text="&lt;%# $this->Data->content %>" />
+</p>
+</com:TTextHighlighter>
+
+<h3>Creating Renderer Class</h3>
+<p>
+The renderer class is very simple. It extends from <tt>TRepeaterItemRenderer</tt> and contains no other code.
+</p>
+<com:TTextHighlighter CssClass="source" Language="php">
+class PostRenderer extends TRepeaterItemRenderer
+{
+}
+</com:TTextHighlighter>
+
+<h2>Testing</h2>
+<p>
+To test the <tt>ListPost</tt> page, visit the URL <tt>http://hostname/blog/index.php</tt> (remember we have set <tt>ListPost</tt> 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 <tt>NewPost</tt>, we can add more posts and come back to test the paging again.
+</p>
+
+<img src="<%~ output.gif %>" class="output" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/protected/pages/Day4/CreateNewPost.page b/demos/blog-tutorial/protected/pages/Day4/CreateNewPost.page
new file mode 100644
index 00000000..5011c346
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day4/CreateNewPost.page
@@ -0,0 +1,135 @@
+<com:TContent ID="Main">
+
+<h1>Creating <tt>NewPost</tt> Page</h1>
+
+<p>
+The <tt>NewPost</tt> page is provided to authenticated users for creating new blog posts. It needs to display a form that collects the information about the new post, including the post title and the post body content.
+</p>
+
+<p>
+Because <tt>NewPost</tt> can only be accessed by authenticated users, we add a page configuration file <tt>config.xml</tt> under the directory <tt>protected/pages/posts</tt>. The configuration specifies that guest users cannot access <tt>NewPost</tt> and <tt>EditPost</tt> which is to be introduced in the next section.
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="xml">
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <authorization>
+ <deny pages="NewPost,EditPost" roles="?" />
+ </authorization>
+</configuration>
+</com:TTextHighlighter>
+
+<p>
+As the number of our pages expands, we would like to modify <tt>MainLayout</tt> so that in the footer of our blog pages there are links to various pages, including the homepage, the <a href="?page=Day3.CreateNewUser">NewUser</a> page (visible to the administrator only), and the upcoming <tt>NewPost</tt> page (visible to authenticated users only).
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="prado">
+<div id="footer">
+<a href="&lt;%= $this->Service->DefaultPageUrl %>">Home</a>
+
+&lt;com:THyperLink Text="New Post"
+ NavigateUrl="&lt;%= $this->Service->constructUrl('posts.NewPost') %>"
+ Visible="&lt;%= !$this->User->IsGuest %>" />
+
+&lt;com:THyperLink Text="New User"
+ NavigateUrl="&lt;%= $this->Service->constructUrl('users.NewUser') %>"
+ Visible="&lt;%= $this->User->IsAdmin %>" />
+...other links...
+</div>
+</com:TTextHighlighter>
+
+<p>
+We now create two files <tt>protected/pages/posts/NewPost.page</tt> and <tt>protected/pages/posts/NewPost.php</tt> to save the page template and page class, respectively.
+</p>
+
+<h2>Creating Page Template</h2>
+<p>
+The <tt>NewPost</tt> page template contains a <a href="http://www.pradosoft.com/demos/quickstart/?page=Controls.TextBox">TTextBox</a> to collect the post title and a <a href="http://www.pradosoft.com/demos/quickstart/?page=Controls.HtmlArea">THtmlArea</a> to collect the post content. The latter is a WYSIWYG HTML editor. To ensure the user input is valid, we associate validators with these input controls.
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="prado">
+&lt;%@ Title="My Blog - New Post" %>
+
+&lt;com:TContent ID="Main">
+
+<h1>Create New Post</h1>
+
+<span>Title:</span>
+&lt;com:TRequiredFieldValidator
+ ControlToValidate="TitleEdit"
+ ErrorMessage="Please provide a title."
+ Display="Dynamic" />
+<br/>
+&lt;com:TTextBox ID="TitleEdit" Columns="50" />
+
+<br/>
+<span>Content:</span>
+&lt;com:TRequiredFieldValidator
+ ControlToValidate="ContentEdit"
+ ErrorMessage="Please provide content."
+ Display="Dynamic" />
+<br/>
+&lt;com:THtmlArea ID="ContentEdit" />
+
+<br/>
+&lt;com:TButton Text="Create" OnClick="createButtonClicked" />
+
+&lt;/com:TContent>
+</com:TTextHighlighter>
+
+
+<h2>Creating Page Class</h2>
+
+<p>
+From the above page template, we see that we mainly need to write a page class that implements the event handler: <tt>createButtonClicked()</tt> (attached to the <tt>Create</tt> button's <tt>OnClick</tt> event).
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="php">
+class NewPost extends TPage
+{
+ /**
+ * Creates a new post if all inputs are valid.
+ * This method responds to the OnClick event of the "create" button.
+ * @param mixed event sender
+ * @param mixed event parameter
+ */
+ public function createButtonClicked($sender,$param)
+ {
+ if($this->IsValid) // when all validations succeed
+ {
+ // populates a PostRecord object with user inputs
+ $postRecord=new PostRecord;
+ // using SafeText instead of Text avoids Cross Site Scripting attack
+ $postRecord->title=$this->TitleEdit->SafeText;
+ $postRecord->content=$this->ContentEdit->SafeText;
+ $postRecord->author_id=$this->User->Name;
+ $postRecord->create_time=time();
+ $postRecord->status=0;
+
+ // saves to the database via Active Record mechanism
+ $postRecord->save();
+
+ // redirects the browser to the newly created post page
+ $url=$this->Service->constructUrl('posts.ReadPost',array('id'=>$postRecord->post_id));
+ $this->Response->redirect($url);
+ }
+ }
+}
+</com:TTextHighlighter>
+
+<h2>Testing</h2>
+<p>
+To test the <tt>NewPost</tt> page, login first and click on the <tt>New Post</tt> link button in the footer of the homepage. Our browser will display the following result with the URL <tt>http://hostname/blog/index.php?page=NewPost</tt>.
+</p>
+
+<com:InfoBox>
+When you visit the <tt>NewPost</tt> page for the first time, you may notice that it takes several seconds for the page to be displayed. This is because PRADO needs to unpack and publish the javascript code and images for the <tt>THtmlArea</tt> control used in the page. This is done once and for all.
+</com:InfoBox>
+
+<com:TipBox>
+To test the pagination feature that we developed for the <a href="?page=Day4.CreateListPost">ListPost</a> page, we can create five or more posts and see what happens to the homepage. The pager in <tt>ListPost</tt> displays five posts each page.
+</com:TipBox>
+
+<img src="<%~ output3.gif %>" class="output" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/protected/pages/Day4/CreateReadPost.page b/demos/blog-tutorial/protected/pages/Day4/CreateReadPost.page
new file mode 100644
index 00000000..d9b66d33
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day4/CreateReadPost.page
@@ -0,0 +1,135 @@
+<com:TContent ID="Main">
+
+<h1>Creating <tt>ReadPost</tt> Page</h1>
+
+<p>
+The <tt>ReadPost</tt> page shows the detailed content of a blog post. For authorized users, it also shows link buttons that would allow them to edit or delete the post.
+</p>
+
+<p>
+We create two files <tt>protected/pages/posts/ReadPost.page</tt> and <tt>protected/pages/posts/ReadPost.php</tt> to save the page template and page class, respectively.
+</p>
+
+<h2>Creating Page Template</h2>
+<p>
+The <tt>ReadPost</tt> page template is very similar to the <tt>PostRenderer</tt> template, both presenting the content of a post. The difference is that <tt>ReadPost</tt> needs to display two link buttons when the current user is authorized to edit or delete the post.
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="prado">
+&lt;com:TContent ID="Main">
+
+<h2>
+&lt;com:TLiteral Text="&lt;%= $this->Post->title %>" />
+</h2>
+
+&lt;com:TControl Visible="&lt;%= $this->canEdit() %>">
+ <a href="&lt;%= $this->Service->constructUrl('EditPost',array('id'=>$this->Post->post_id))%>">Edit</a> |
+ &lt;com:TLinkButton Text="Delete"
+ OnClick="deletePost"
+ Attributes.onclick="javascript:if(!confirm('Are you sure?')) return false;" />
+&lt;/com:TControl>
+
+<p>
+Author:
+&lt;com:TLiteral Text="&lt;%= $this->Post->author->username %>" /><br/>
+Time:
+&lt;com:TLiteral Text="&lt;%= date('m/d/Y h:m:sa', $this->Post->create_time) %>" />
+</p>
+
+<p>
+&lt;com:TLiteral Text="&lt;%= $this->Post->content %>" />
+</p>
+
+&lt;/com:TContent>
+</com:TTextHighlighter>
+
+<p>
+Many PHP expressions are used in the above template. The expression <tt>$this->Post</tt> refers to a property defined in the <tt>ReadPost</tt> page class. It represents the <tt>PostRecord</tt> object corresponding to the post currently being viewed.
+</p>
+
+<com:InfoBox>
+Although we use expressions widely in templates, we do not overuse them. A major guideline in determining whether we should use an expression in a template is that <i>the expression should be a property or a simple presentational transformation of the property</i>. By following this guideline, we ensure content and presentation are well separated without losing sufficient flexibility.
+</com:InfoBox>
+
+<p>
+We also notice in the above template that two link buttons are enclosed within a <tt>TControl</tt> whose visibility is determined by the expression <tt>$this->canEdit()</tt>. For the <tt>Delete</tt> link button, we use javascript confirmation dialog to obtain user's confirmation when he clicks to delete the post.
+</p>
+
+<com:InfoBox>
+All PRADO controls have a very useful property named <tt>Attributes</tt> which can accept arbitrary name-value pairs. Most PRADO controls will render the name-value pairs in <tt>Attributes</tt> literally in the corresponding HTML tag. For example, in the <tt>Delete</tt> link button above, we define an <tt>onclick</tt> which is rendered as the <tt>onclick</tt> attribute in the resulting <tt>&lt;a&gt;</tt> tag.
+</com:InfoBox>
+
+
+<h2>Creating Page Class</h2>
+
+<p>
+From the above page template, we see that we need to write a page class that implements the event handler: <tt>deletePost()</tt> (attached to the <tt>Delete</tt> button's <tt>OnClick</tt> event). We also need to retrieve the post data specified by the post ID passed via the <tt>id</tt> GET parameter. </p>
+
+<com:InfoBox>
+We implement the post deletion feature in the <tt>ReadPost</tt> page because it is so natural to do so here. When the user clicks on the <tt>Delete</tt> button, a javascript confirmation dialog will pop up. If the user confirms it, the deletion will be carried in response to the <tt>OnClick</tt> event of the <tt>Delete</tt> button.
+</com:InfoBox>
+
+<com:TTextHighlighter CssClass="source" Language="php">
+class ReadPost extends TPage
+{
+ private $_post;
+ /**
+ * Fetches the post data.
+ * This method is invoked by the framework when initializing the page
+ * @param mixed event parameter
+ */
+ public function onInit($param)
+ {
+ parent::onInit($param);
+ // post id is passed via the 'id' GET parameter
+ $postID=(int)$this->Request['id'];
+ // retrieves PostRecord with author information filled in
+ $this->_post=PostRecord::finder()->withAuthor()->findByPk($postID);
+ if($this->_post===null) // if post id is invalid
+ throw new THttpException(500,'Unable to find the specified post.');
+ // set the page title as the post title
+ $this->Title=$this->_post->title;
+ }
+
+ /**
+ * @return PostRecord the PostRecord currently being viewed
+ */
+ public function getPost()
+ {
+ return $this->_post;
+ }
+
+ /**
+ * Deletes the post currently being viewed
+ * This method is invoked when the user clicks on the "Delete" button
+ */
+ public function deletePost($sender,$param)
+ {
+ // only the author or the administrator can delete a post
+ if(!$this->canEdit())
+ throw new THttpException('You are not allowed to perform this action.');
+ // delete it from DB
+ $this->_post->delete();
+ // redirect the browser to the homepage
+ $this->Response->redirect($this->Service->DefaultPageUrl);
+ }
+
+ /**
+ * @return boolean whether the current user can edit/delete the post being viewed
+ */
+ public function canEdit()
+ {
+ // only the author or the administrator can edit/delete a post
+ return $this->User->Name===$this->Post->author_id || $this->User->IsAdmin;
+ }
+}
+</com:TTextHighlighter>
+
+<h2>Testing</h2>
+<p>
+To test the <tt>ReadPost</tt> page, visit the URL <tt>http://hostname/blog/index.php</tt> and click on the title of the only post. Our browser will display the following result with the URL <tt>http://hostname/blog/index.php?page=ReadPost&id=1</tt>. Note, if we do not login, the two link buttons will be invisible.
+</p>
+
+<img src="<%~ output2.gif %>" class="output" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/protected/pages/Day4/Overview.page b/demos/blog-tutorial/protected/pages/Day4/Overview.page
new file mode 100644
index 00000000..cbff07c5
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day4/Overview.page
@@ -0,0 +1,26 @@
+<com:TContent ID="Main">
+
+<h1>Post Management Overview</h1>
+
+<p>
+In this section, we create pages that are related with post management. In particular, we implement the CRUD (Create-Retrieve-Update-Delete) operations with respect to blog posts.
+</p>
+
+<p>
+According to the requirements, we need to create the following pages organized under a new directory <tt>protected/pages/posts</tt>.
+</p>
+
+<ul>
+ <li><tt>ListPost</tt> displays posts by their creation time in descending order.</li>
+ <li><tt>ReadPost</tt> displays the detail of a post.</li>
+ <li><tt>NewPost</tt> allows registered users to create a new post.</li>
+ <li><tt>EditPost</tt> allows the author or the administrator to edit an existing post.</li>
+</ul>
+
+<p>
+After finishing this section, we shall expect to see the following directories and files:
+</p>
+
+<img src="%~ directories.gif %>" class="output" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/protected/pages/Day4/output.gif b/demos/blog-tutorial/protected/pages/Day4/output.gif
new file mode 100644
index 00000000..8c1caea8
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day4/output.gif
Binary files differ
diff --git a/demos/blog-tutorial/protected/pages/Day4/output2.gif b/demos/blog-tutorial/protected/pages/Day4/output2.gif
new file mode 100644
index 00000000..7078e6c6
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day4/output2.gif
Binary files differ
diff --git a/demos/blog-tutorial/protected/pages/Day4/output3.gif b/demos/blog-tutorial/protected/pages/Day4/output3.gif
new file mode 100644
index 00000000..ff1834a4
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day4/output3.gif
Binary files differ
diff --git a/demos/blog-tutorial/protected/pages/Day4/output4.gif b/demos/blog-tutorial/protected/pages/Day4/output4.gif
new file mode 100644
index 00000000..b1208a0d
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day4/output4.gif
Binary files differ