Creating and Editing Posts

What a great time. We have a super cool new blog, people are arguing in comments and we have finally some time for more programming. Though we like Adminer, it is not that comfortable to write blog posts in it. Perhaps it's the right time to add a simple form for adding new posts directly from our app. Let’s do it.

Let’s start by designing the UI:

  1. On the homepage, let’s add a “Write new post” link.
  2. It will show a form with title and textarea for content.
  3. When you click a Save button, it’ll save the blog post.

Later we’ll also add authentication and allow only logged-in users to add new posts. But let’s do that later. What code will we need to write to make it work?

  1. Create a new presenter with a form for adding posts.
  2. Define a callback that will be triggered after successful submission of the form and that will save the new post to the database.
  3. Create a new template for the form.
  4. Add a link to the form to the main page template.

New Presenter

Name the new presenter EditPresenter and save it in app/UI/Edit/. It also needs to connect to the database, so here again we write a constructor that will require a database connection:

<?php
namespace App\UI\Edit;

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

final class EditPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private Nette\Database\Explorer $database,
	) {
	}
}

Form for Saving Posts

Forms and components have been already covered when we were adding support for comments. If you're confused about the topic, go checkout how forms and components work again, we'll wait here ;)

Now add this method to the EditPresenter:

protected function createComponentPostForm(): Form
{
	$form = new Form;
	$form->addText('title', 'Title:')
		->setRequired();
	$form->addTextArea('content', 'Content:')
		->setRequired();

	$form->addSubmit('send', 'Save and publish');
	$form->onSuccess[] = $this->postFormSucceeded(...);

	return $form;
}

Saving New Post from Form

Continue by adding a handler method.

private function postFormSucceeded(array $data): void
{
	$post = $this->database
		->table('posts')
		->insert($data);

	$this->flashMessage('Post was published', 'success');
	$this->redirect('Post:show', $post->id);
}

Just a quick explanation: it fetches the values from the form, inserts them into the database, creates a message for the user that the post was saved successfully, and redirects to the page where that post is published so that you can see how it looks like.

Page for Creating a New Post

Let’s just create the template Edit/create.latte:

{block content}
<h1>New post</h1>

{control postForm}

Everything should be clear by now. The last line shows the form we are about to create.

We could also create a corresponding renderCreate method, but it is not necessary. We don't need to get any data from the database and pass it to the template, so that method would be empty. In such cases, the method may not exist at all.

You probably already know how to add a link to EditPresenter and its create action. Try it out.

Just add to the app/UI/Home/default.latte file:

<a n:href="Edit:create">Write new post</a>

Editing Posts

Let’s also add the capability to edit existing posts. It shall be pretty simple – we already have postForm and we can use it for editing as well.

We’ll add a new edit page to the EditPresenter:

public function renderEdit(int $id): void
{
	$post = $this->database
		->table('posts')
		->get($id);

	if (!$post) {
		$this->error('Post not found');
	}

	$this->getComponent('postForm')
		->setDefaults($post->toArray());
}

And create the template Edit/edit.latte:

{block content}
<h1>Edit post</h1>

{control postForm}

And update the method postFormSucceeded, which will be able either to add a new post (as it does now), or to edit existing ones:

private function postFormSucceeded(array $data): void
{
	$id = $this->getParameter('id');

	if ($id) {
		$post = $this->database
			->table('posts')
			->get($id);
		$post->update($data);

	} else {
		$post = $this->database
			->table('posts')
			->insert($data);
	}

	$this->flashMessage('Post was published', 'success');
	$this->redirect('Post:show', $post->id);
}

When id parameter is provided, it means that a post is being edited. In such case, we’ll check that the post really exists and if so, we’ll update it in the database. If the id is not provided, it means that a new post shall be added.

But where does the id come from? It is the parameter passed to renderEdit method.

You can now add a link to the app/UI/Post/show.latte template:

<a n:href="Edit:edit $post->id">Edit this post</a>

Summary

The blog is working, people are commenting rapidly and we no longer rely on Adminer for adding new posts. It is fully independent and even normal people can post there. But wait, that’s probably not ok, that anyone, I mean really anyone on the Internet, can post on our blog. Some form of authentication is required so that only logged-in users would be able to post. We'll add that in the next chapter.