Jak zobrazovat chybové hlášky v šabloně? (Nette 3.0)

vb76
Člen | 16
+
0
-

Ahoj všem,

seznamuji se s Nette 3.0 a tvořím přihlašování uživatelů s ověřováním v databázi. Vycházím ze Sandboxu https://github.com/nette/sandbox Mám problém s tím, jak zobrazovat v šabloně chybové hlášky, uložené přes $form->addError. Pokud přidám do šablony toto:

<ul class="errors" n:if="$form->hasErrors()">
        <li n:foreach="$form->errors as $error">{$error}</li>
</ul>

zahlásí mi to Undefined variable: form. Poradíte mi, prosím, kde dělám chybu? Děkuji předem za informace.

App\Config\common.neon

parameters:

php:
	date.timezone: Europe/Prague

application:
	errorPresenter: Error
	mapping:
		*: App\*Module\Presenters\*Presenter

session:
	expiration: 14 days
	autoStart: true

services:
	- App\Model\UserModel
	- App\Forms\FormFactory
	- App\Forms\PrihlaseniFormFactory
	router: App\Router\RouterFactory::createRouter

App\Forms\FormFactory.php

<?php

declare(strict_types=1);

namespace App\Forms;

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


final class FormFactory
{
	use Nette\SmartObject;
	public function create(): Form
	{
		$form = new Form;
		return $form;
	}
}
?>

App\Forms\PrihlaseniFormFactory.php

<?php

declare(strict_types=1);

namespace App\Forms;

use Nette;
use Nette\Application\UI\Form;
use Nette\Security\User;


final class PrihlaseniFormFactory
{
	use Nette\SmartObject;

	/** @var FormFactory */
	private $factory;

	/** @var User */
	private $user;


	public function __construct(FormFactory $factory, User $user)
	{
		$this->factory = $factory;
		$this->user = $user;
	}

	public function create(callable $onSuccess): Form
	{
		$form = $this->factory->create();
		$form->addText('inputEmail', 'E-mail:')
			->setRequired('Zadejte e-mailovou adresu.')
			->setHtmlAttribute('placeholder', 'E-mail');
		$form->addPassword('inputPassword', 'Heslo:')
			->setRequired('Zadejte heslo.')
			->setHtmlAttribute('placeholder', 'Heslo');
		$form->addSubmit('buttonLogin', 'Přihlásit');

		$form->onSuccess[] = function (Form $form, \stdClass $values) use ($onSuccess): void {
			try {
				$this->user->login($values->inputEmail, $values->inputPassword);
			} catch (\Nette\Security\AuthenticationException $e) {
				$form->addError('Neplatné uživatelské jméno nebo heslo.');
				return;
			}
			$onSuccess();
		};

		return $form;
	}
}
?>

App\Model\UserModel.php

<?php

declare(strict_types=1);

namespace App\Model;

use Nette;
use Nette\Security\Passwords;

final class UserModel implements Nette\Security\IAuthenticator
{
	use Nette\SmartObject;

	private const
	TABLE_NAME = 'bm_users',
	COLUMN_ID = 'id',
	COLUMN_ACTIVE = 'active',
	COLUMN_NAME = 'username',
	COLUMN_PASSWORD_HASH = 'password',
	COLUMN_ROLE = 'role';

	/** @var Nette\Database\Context */
	private $database;

	/** @var Passwords */
	private $passwords;

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

	/**
	* Performs an authentication.
	* @throws Nette\Security\AuthenticationException
	*/
	public function authenticate(array $credentials): Nette\Security\IIdentity
	{
		[$username, $password] = $credentials;

		$row = $this->database->table(self::TABLE_NAME)
		->where(self::COLUMN_NAME, $username)
		->fetch();

		$row = $this->database->table(self::TABLE_NAME)
				->where(self::COLUMN_NAME, $username)
				->fetch();
		if(!$row)
		{
			throw new Nette\Security\AuthenticationException('The username is incorrect.', self::IDENTITY_NOT_FOUND);
		}
		elseif(!$this->passwords->verify($password, $row[self::COLUMN_PASSWORD_HASH]))
		{
			throw new Nette\Security\AuthenticationException('Heslo není správné!', self::INVALID_CREDENTIAL);
		}
		elseif($row[self::COLUMN_ACTIVE] == 1)
		{
			throw new Nette\Security\AuthenticationException('Uživatelský účet není aktivní!', self::NOT_APPROVED);
		}
		elseif($this->passwords->NeedsRehash($row[self::COLUMN_PASSWORD_HASH]))
		{
			$row->update(array(self::COLUMN_PASSWORD_HASH => $this->passwords->hash($password)));
		}
		$arr = $row->toArray();
		unset($arr[self::COLUMN_PASSWORD_HASH]);
		return new Nette\Security\Identity($row[self::COLUMN_ID], $row[self::COLUMN_ROLE], $arr);
	}

}
?>

App\Presenters\templates\Components\Forms.latte

{define form-1 $formName}
	<div n:foreach="$flashes as $flash" class="alert {$flash->type} alert-dismissible fade show" role="alert">
		{$flash->message|noescape}
		<button type="button" class="close" data-dismiss="alert" aria-label="Close">
			<span aria-hidden="true">&times;</span>
		</button>
	</div>

	<form n:name=$formName class=form-horizontal>
		<div class="form-group mb-2">
			{input inputEmail class => 'form-control form-control-sm'}
		</div>
		<div class="form-group mb-2">
			{input inputPassword class => 'form-control form-control-sm'}
		</div>
		<div class="text-left">
			{input buttonLogin class => 'btn btn-sm btn-success mb-0 font-weight-bold'}
			<a n:href="Prihlaseni:novyUcet" class="btn btn-sm btn-danger mb-0 float-right"><strong>Nový účetet</strong></a>
		</div>
		<div class="clearfix text-center mt-2">
			<a n:href="Prihlaseni:obnoveniHesla" class="fsize-3">Nepamatuji si heslo</a>
		</div>
	</form>
{/define}

App\Presenters\templates\@layout.latte

{import 'Components/Forms.latte'}
<!DOCTYPE html>
<html lang="cs-cz">
.
.
.
</html>

App\Presenters\templates\Homepage\default.latte

{block title}{/block}
{block description}{/block}
{block keywords}{/block}
{block content}

{include form-1 prihlaseniForm}

{/block}

App\Presenters\BasePresenter.php

<?php

declare(strict_types=1);

namespace App\Presenters;

use Nette;
use Nette\Application\UI\Form;
use App\Forms;


abstract class BasePresenter extends Nette\Application\UI\Presenter
{
	/** @persistent */
	public $backlink = '';

	/** @var Forms\PrihlaseniFormFactory */
	private $prihlaseniFactory;


	public function __construct(Forms\PrihlaseniFormFactory $prihlaseniFactory)
	{
		$this->prihlaseniFactory = $prihlaseniFactory;

	}

	/**
	 * Prihlaseni form factory.
	 */
	protected function createComponentPrihlaseniForm(): Form
	{
		return $this->prihlaseniFactory->create(function (): void {
			$this->redirect('Homepage:');
		});
	}

	public function actionOdhlaseni(): void
	{
		$this->getUser()->logout();
	}
}
?>
Marek Bartoš
Nette Blogger | 1280
+
-1
-

createComponentPrihlaseniForm() → proměnná se jmenuje prihlaseniForm

Edit: bullshit, proměnnou $form vytváří makro formuláře n:name

Editoval Mabar (25. 9. 2019 11:57)

vb76
Člen | 16
+
0
-

Mabar napsal(a):

createComponentPrihlaseniForm() → proměnná se jmenuje prihlaseniForm

Děkuji za informaci. Jen pro upřesnění, v prihlaseniForm budou informace uložené do proměnné form?
Jako konkrétně v šabloně vypíšu chybovou hlášku?
Musím to předat do šablony přes $this->template->xxx a definovat v presenteru?

Marek Bartoš
Nette Blogger | 1280
+
0
-

Z createComponent se ti vkládají proměnné do šablony automaticky, název proměnné se vytváří z názvu metody. A $prihlaseniForm je instance Form co si vracíš v createComponentPrihlaseniForm(), jen $form v šabloně nahraď za $prihlaseniForm

vb76
Člen | 16
+
0
-

Mabar napsal(a):

Z createComponent se ti vkládají proměnné do šablony automaticky, název proměnné se vytváří z názvu metody. A $prihlaseniForm je instance Form co si vracíš v createComponentPrihlaseniForm(), jen $form v šabloně nahraď za $prihlaseniForm

Děkuji za vysvětlení. Už to chápu.

vb76
Člen | 16
+
0
-

vb76 napsal(a):

Mabar napsal(a):

Z createComponent se ti vkládají proměnné do šablony automaticky, název proměnné se vytváří z názvu metody. A $prihlaseniForm je instance Form co si vracíš v createComponentPrihlaseniForm(), jen $form v šabloně nahraď za $prihlaseniForm

Děkuji za vysvětlení. Už to chápu.

Tak jsem našel chvilku se na to ještě mrknout. Přidal jsem do default.latte následující kód

<ul class="errors" n:if="$prihlaseniForm->hasErrors()">
	<li n:foreach="$prihlaseniForm->errors as $error">{$error}</li>
</ul>
{include form-1 prihlaseniForm}

a stále mi hlásí Undefined variable: prihlaseniForm. Vše ostatní je nezměněno viz zaslané informace výše. Zkoušel jsem dát také {$prihlaseniForm} a výsledek je stejný. Nějak si s tím nevím rady. Nakopnutí pomohlo pochopit logiku, ale proč se ta proměnná neplní nebo nepředává netuším.

Věděl by někdo čím by to mohlo být? Děkuji předem za pomoc!

Editoval vb76 (24. 9. 2019 20:44)

Marek Bartoš
Nette Blogger | 1280
+
0
-

Šablona default.latte, ve které se snažíš chyby vypsat, patří k té samé komponentě/presenteru, ve které vytváříš formulář? Každá šablona má dostupné jen proměnné komponenty, ke které patří.

vb76
Člen | 16
+
0
-

Mabar napsal(a):

Šablona default.latte, ve které se snažíš chyby vypsat, patří k té samé komponentě/presenteru, ve které vytváříš formulář? Každá šablona má dostupné jen proměnné komponenty, ke které patří.

Aha, v tom bude ten problém. Patří k Homepage Presenteru, který dědí od Base Presenteru, ve kterém je vytvářen formulář. Chtěl bych použít přihlašování ve všech presenterech /šablonách. Struktura Homepage:default má umístěn formulář jinde než ostatní šablony. Pro ty je umístěn v @layout.latte. Proto jsem to vložil do Base Presenteru. Viz struktura výše.

Půjde to?

Editoval vb76 (24. 9. 2019 22:28)

Šaman
Člen | 2667
+
+1
-

Jestli HomepagePresenter, dědí od Base Presenteru, ve kterém je vytvářen formulář, pak je formulář prakticky vytvářen v HomepagePresenter. (To je věc OOP. Neexistuje žádná instance BasePresenter, ale každý jeho potomek ho zároveň obsahuje.) Takže jestli jde jen o to, tak to půjde.

vb76
Člen | 16
+
0
-

Konkrétně mi nefunguje předávání proměnné $prihlaseniForm, která by měla obsahovat chybové hlášky přidané přes $form->addError() v PrihlaseniFormFactory. Formulář vytvářím v BasePreneter a v šabloně se snažím zobrazit chybová hlášení. Nicméně šablona se neváže k presenteru, ve kterém se formulář vytváří. Když si chci proměnnou $prihlaseniForm (chybovou hlášku) vypsat v Homepage:default, hlásí mi to Undefined variable: prihlaseniForm. S tímto konkrétně potrebuji poradit.

Šaman
Člen | 2667
+
0
-

Podařilo se ti v úvodním příspěvku poslat spoustu kódu, kromě toho jednoho důležitého :) Jak to do té šablony posíláš?
Samotná proměnná tam jen tak neexistuje, pro přístup k formuláři (obecně k libovolné vlastní komponentě v presenteru) použij $this['prihlaseniForm'].
Teda, Mabar psal že se v šabloně vytváři automaticky, ale o tom moc nevím. Já si je předávám, pokud je tam potřebuji jako proměnné (jinak bych teda předpokládal, že pokud se předají automaticky, tak do proměnné $prihlaseniFormControl)

Takže jestli nefunguje ani $prihlaseniFormControl, tak si to zkus předat v render metodě jako $this->template->prihlaseniForm = $this['prihlaseniForm'];
To předání můžeš zařídit i v BasePresenteru v metodě beforeRender a používat to v šabloně layoutu. Pak máš zaručené, že každá šablona (rozšiřující layout) a každý presenter (dědící od BasePresenteru) to bude umět vypsat.

vb76
Člen | 16
+
0
-

Ano, tohle funguje! Díky moc, Mabar a Šaman. Každý z Vás mi dal, jako začátečníkovi, mnoho cenných informací.

Marek Bartoš
Nette Blogger | 1280
+
+1
-

Tak se moc omlouvám, jak manuální renderování formulářů používám automaticky, tak jsem si zafixoval, že je ta proměnná vždycky dostupná :/ Raději jsem teď kouknul do kódu a proměnnou form vytváří makro n:name.

https://github.com/…rmMacros.php#…

<form n:name="form">
	<div n:if="$form->hasErrors()" n:foreach="$form->getErrors() as $error">
		<span>{$error}</span>
	</div>

Editoval Mabar (25. 9. 2019 11:56)

Šaman
Člen | 2667
+
0
-

Jop, tam je to jasné. V bloku formuláře je ta proměnná dostupná.
A jinam lze přidat do šablony ručně, jako jakákoliv jiná proměnná.