Creating and Editing Posts

That's great! We have a super cool new blog, people are actively discussing in the comments, and we finally have some time for more programming. Although Adminer is a great tool, it's not very comfortable for writing new blog posts. Perhaps it's the right time to create a simple form for adding new posts directly from the application. Let's do it.

Let's start by designing the user interface:

  1. On the homepage, add a “Write new post” link.
  2. This link will display a form with a title field and a textarea for the post content.
  3. When we click the Save button, the post will be saved to the database.

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

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

New Presenter

Let's name the new presenter EditPresenter and save it in app/Presentation/Edit/EditPresenter.php. It also needs to connect to the database, so here again, we write a constructor that requires a database connection:

<?php
namespace App\Presentation\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 were already covered when we added support for comments. If it's still unclear, go review how forms and components work, 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 the handler method:

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

	$this->flashMessage('Post was published successfully.', '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

Now let's create the template Edit/create.latte:

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

{control postForm}

Everything should be clear by now. The last line renders the form we just created.

We could also create a corresponding renderCreate() method, but it's not necessary. We don't need to retrieve any data from the database or pass it to the template, so the method would be empty. In such cases, the method doesn't need to exist at all.

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

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

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

Editing Posts

Now let's add the capability to edit existing posts. It will be very simple. We already have the postForm form, 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 corresponding 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 successfully.', '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/Presentation/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.