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
HomePresenter
is not nice because it’s about a blog post, not the homepage. So, let’s create a new class
PostPresenter
and place it to app/UI/Post/
. It will need a database connection, so put the database
injection code there again.
The PostPresenter
should look like this:
<?php
namespace App\UI\Post;
use Nette;
use Nette\Application\UI\Form;
final class PostPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private Nette\Database\Explorer $database,
) {
}
public function renderShow(int $id): void
{
$this->template->post = $this->database
->table('posts')
->get($id);
}
}
We have to set a correct namespaces App\UI\Post
for our presenter. It depends on presenter mapping.
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 Home/default.latte
template we add a link to the Post:show
action:
...
<h2><a href="{link Post:show $post->id}">{$post->title}</a></h2>
...
Tag {link}
generates URL address which points to the action Post:show
. This tag also forwards the ID
of the post as an argument.
The same we can write shortly using n:attribute:
...
<h2><a n:href="Post:show $post->id">{$post->title}</a></h2>
...
Attribute n:href
is similar to the {link}
tag.
The template for Post:show
action does not yet exist. We can open a link to this post. Tracy will show an error, why 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 we'll create Post/show.latte
with this content:
{block content}
<p><a n:href="Home: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. As you can see, the end tag {/block}
is missing. It is optional.
The second line provides a backlink 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 Home
presenter (you could also write n:href="Home:"
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 a <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/UI/@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 id
which does not exist? We should provide the user with a nice
“page not found” error. Let’s update the render method in PostPresenter
:
public function renderShow(int $id): void
{
$post = $this->database
->table('posts')
->get($id);
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 – the first one displays the summary of all recent posts and the second one displays one specific post.