Single Post Page

Let’s add another page to our blog, which will display the content of one particular blog post.

We need to create a new render method, that will fetch one specific blog post and pass it to the template. Having this view in HomepagePresenter is not nice, because it’s about a blog post, not homepage. So, let’s create a new class PostPresenter and place it to app/presenters/PostPresenter.php. It will need a database connection, so put the database injection code there again.

The PostPresenter should look like this:

<?php
namespace App\Presenters;

use Nette;
use Nette\Application\UI\Form;


class PostPresenter extends Nette\Application\UI\Presenter
{
    /** @var Nette\Database\Context */
    private $database;

    public function __construct(Nette\Database\Context $database)
    {
        $this->database = $database;
    }

    public function renderShow($postId)
    {
        $this->template->post = $this->database->table('posts')->get($postId);
    }
}

We have to set a correct namespaces App\Presenters for our presenter. It depends on [presenter mapping |https://github.com/nette/tutorial-quickstart/blob/v2.4/app/config/config.neon#L6-L7.

The renderShow method requires one argument – the ID of the post to be displayed. Then, it loads the post from the database and passes the result to the template.

In the app/presenters/templates/Homepage/default.latte template we add a link to the Post:show action:

...
<h2><a href="{link Post:show $post->id}">{$post->title}</a></h2>
...

Macro {link} generates URL address which points to the action Post:show. This macro also forwards the ID of the post as argument.

The same we can write shortly using n:macro:

...
<h2><a n:href="Post:show $post->id">{$post->title}</a></h2>
...

Attribute n:href is alias for {link} macro.

The template for Post:show action does not yet exist. We can open link to this post. Tracy will show an error, why app/templates/Post/show.latte doesn't exist. If you see any other error report you probably have to turn on mod_rewrite in your webserver.

So well create `app/templates/Post/show.latte with this content:

{block content}

<p><a n:href="Homepage:default">← back to posts list</a></p>

<div class="date">{$post->created_at|date:'F j, Y'}</div>

<h1 n:block="title">{$post->title}</h1>

<div class="post">{$post->content}</div>

Let’s have a look at the individual parts.

The first line starts the definition of a named block called “content” that we saw earlier. It will be displayed in a layout template.

The second line provides a back link to the list of blog posts, so the user can navigate smoothly back and forth on our blog. We use n:href attribute again, therefore Nette will take care of generating the URL for us. The link points to the default action of the Homepage presenter (you could also write n:href="Homepage:" as the default action can be omitted).

The third line formats the publication timestamp with a filter, as we already know.

The fourth line displays the title of the blog post as an <h1> heading. There is a part that you may not be familiar with, and that is n:block="title". Can you guess what it does? If you’ve read the previous parts carefully, we've mentioned n: attributes. This is another example. It is equivalent to:

{block title}<h1>{$post->title}</h1>{/block}

In simple words, it re-defines a block called title. The block is defined in layout template (/app/presenters/templates/@layout.latte:11) and like with OOP overriding, it gets overridden here. Therefore, the page’s <title> will contain the title of the displayed post. We’ve overridden the title of the page and all we needed was n:block="title". Great, huh?

The fifth and the last line of the template displays full content of your post.

Checking Post ID

What happens if someone alters the URL and inserts postId which does not exist? We should provide the user with a nice “page not found” error. Let’s update the render method in app/presenters/PostPresenter.php:

public function renderShow($postId)
{
    $post = $this->database->table('posts')->get($postId);
    if (!$post) {
        $this->error('Post not found');
    }

    $this->template->post = $post;
}

If the post cannot be found, calling $this->error(...) will show a 404 page with a nice and understandable message. Note, that in your development environment (on your laptop), you won’t see the error page. Instead, Tracy will show the exception with full details, which is pretty convenient for development. You can check both modes, just change the value passed to setDebugMode in bootstrap.php.

Summary

We have a database with blog posts and a web app with two views – first one displays the summary of all recent posts and the second one displays one specific post.