Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 4 - Dokončení kalkulačky v Nette

V minulé lekci, První aplikace v Nette, jsme se zaměřili na MVP a dokončili model.

V dnešní lekci se můžeme vrhnout na presenter. Takže bez dalšího otálení jdeme na to! :)

Presenter

app/Presenters/CalculatorPresenter.php

V šabloně Nette webového projektu máme ve složce app/Presenters/ již vytvořený soubor HomepagePresener.php. My ho však smažeme nebo přejmenujeme a vytvoříme si zde náš vlastní CalculatorPresenter.php a v něm třídu CalculatorPresenter, která dědí z Nette\Application\UI\Presenter. Tím jsme si vytvořili základní kostru presenteru, do které ještě doplníme výchozí metodu pro vykreslování šablony, aby ve výsledku vypadala nějak takto:

<?php

declare(strict_types=1);

namespace App\Presenters;

use Nette\Application\UI\Presenter;

/**
 * Presenter kalkulačky.
 * @package App\Presenters
 */
final class CalculatorPresenter extends Presenter
{
    /** @var int|null výsledek operace nebo null */
    private $result = null;

    /** Výchozí vykreslovací metoda tohoto presenteru. */
    public function renderDefault()
    {
        // Předání výsledku do šablony.
        $this->template->result = $this->result;
    }
}

Jak můžeme vidět, předáváme zde výsledek do šablony, abychom ho mohli zobrazit. Výsledek je zatím null, ale to za chvíli napravíme ;)

Získání modelu z presenteru

K tomu, abychom získali výsledek, budeme potřebovat přístup k modelu. Ten se v Nette standardně zajišťuje pomocí Dependency Injection (zkráceně DI) a to tak, že v konfiguračním souboru common.neon zaregistrujeme náš model jako anonymní službu (service) a pak si ho necháme injektovat do našeho presenteru.

app/config/common.neon

Nejdříve tedy najdeme tento soubor a upravíme přidáním služby:

...
services:
    router: App\Router\RouterFactory::createRouter
    - App\Model\CalculatorManager

Nyní máme několik možností, jak a kde si nechat službu injektovat. Já si zvolím standardní a asi nejpoužívanější postup a to přes konstruktor presenteru. Do naší třídy CalculatorPresenter tedy přidáme následující kód:

// ...
/** @var CalculatorManager Instance třídy modelu pro práci s operacemi kalkulačky. */
private $calculatorManager;

/**
 * Konstruktor s injektovaným modelem pro práci s operacemi kalkulačky.
 * @param CalculatorManager $calculatorManager automaticky injektovaná třída modelu pro práci s operacemi kalkulačky
 */
public function __construct(CalculatorManager $calculatorManager)
{
    parent::__construct();
    $this->calculatorManager = $calculatorManager;
}
// ...

Nesmíme také zapomenout na import třídy CalculatorManager:

use App\Model\CalculatorManager;

Nyní máme přístup k našemu modelu přes $this->calculatorManager.

Nette formuláře v Presenterech

Máme již vše potřebné pro výpočet a předání výsledku do šablony. Je tedy potřeba se zamyslet nad tím, jak budeme získávat data pro výpočet od uživatele. Odpověď je jednoduchá, přes formulář. To v Nette znamená, že si potřebujeme v presenteru zadefinovat formulář, který pak předáme do šablony. Z něj získáme data, která předáme modelu pro výpočet a výsledek uložíme do proměnné $this->result. Nette nám pro tento účel přináší systém komponent. Nám jen stačí pomocí ní zadefinovat podobu formuláře a funkci pro jeho zpracování:

// ...
/** Definice konstant pro zprávy formuláře. */
const
    FORM_MSG_REQUIRED = 'Tohle pole je povinné.',
    FORM_MSG_RULE = 'Tohle pole má neplatný formát.';

// ...
/**
 * Vrátí formulář kalkulačky.
 * @return Form formulář kalkulačky
 */
protected function createComponentCalculatorForm()
{
    $form = new Form;
    // Získáme existující operace kalkulačky a dáme je do výběru operací.
    $form->addRadioList('operation', 'Operace:', $this->calculatorManager->getOperations())
        ->setDefaultValue(CalculatorManager::ADD)
        ->setRequired(self::FORM_MSG_REQUIRED);
    $form->addText('x', 'První číslo:')
        ->setHtmlType('number')
        ->setDefaultValue(0)
        ->setRequired(self::FORM_MSG_REQUIRED)
        ->addRule(Form::INTEGER, self::FORM_MSG_RULE);
    $form->addText('y', 'Druhé číslo:')
        ->setHtmlType('number')
        ->setDefaultValue(0)
        ->setRequired(self::FORM_MSG_REQUIRED)
        ->addRule(Form::INTEGER, self::FORM_MSG_RULE)
        // Ošetříme dělení nulou.
        ->addConditionOn($form['operation'], Form::EQUAL, CalculatorManager::DIVIDE)
        ->addRule(Form::PATTERN, 'Nelze dělit nulou.', '^[^0].*');
    $form->addSubmit('calculate', 'Spočítej výsledek');
    $form->onSuccess[] = [$this, 'calculatorFormSucceeded'];

    return $form;
}

/**
 * Funkce se vykonaná při úspěšném odeslání formuláře kalkulačky a zpracuje odeslané hodnoty.
 * @param Form $form        formulář kalkulačky
 * @param ArrayHash $values odeslané hodnoty formuláře
 */
public function calculatorFormSucceeded(Form $form, ArrayHash $values)
{
    // Necháme si vypočítat výsledek podle zvolené operace a zadaných hodnot.
    $this->result = $this->calculatorManager->calculate($values->operation, $values->x, $values->y);
}

Musíme na začátku souboru zase importovat potřebné třídy:

use Nette\Application\UI\Form;
use Nette\Utils\ArrayHash;

Je toho více, takže to vezmeme hezky popořadě. První metoda je speciální metoda pro tvorbu komponent, která se definuje jako createComponent<název_komponenty>() a vrací požadovanou komponentu. Jen upozorňuji, že u této metody stačí, aby byla protected. Pokud metoda splňuje tyto podmínky, můžeme ji poté zavolat přímo ze šablony a to pomocí speciálního Latte makra {control}, což si za chvíli ukážeme.

Dále pár slov k samotnému formuláři. Definujeme zde celkem 4 prvky:

  1. Pole radio inputů, které slouží pro výběr operace výpočtu. Zde si můžete všimnout volání metody z modelu, která nám vrátí univerzálně všechny dostupné operace. Dále nastavujeme výchozí hodnotu na sčítání a že je povinné toho pole vyplnit.
  2. Text input pro zadání prvního čísla, kde nastavíme, že se jedná o typ inputu number, což platí pro prohlížeče podporující HTML5. Dále určíme výchozí hodnotu 0 a validační pravidlo, že toto pole musí být vyplněno a pouze hodnotou celého čísla.
  3. Text input pro zadání druhého čísla, který je identický s tím prvním, pouze obsahuje navíc pravidlo, kde ošetříme dělení nulou, jak jsem slíbil. Teď by asi byla na místě diskuze, proč jsem to ošetřil zde a ne již v modelu. Samozřejmě to lze udělat oběma způsoby. Já jsem se ale rozhodl demonstrovat, že presenter by měl zpracovat všechna data z šablony a do modelu už by měla jít jen ta data, se kterými tento model umí pracovat. Navíc zde může dojít k validaci již na straně uživatele a celkově je tento způsob efektivnější, než například vyhazovat nějaké výjimky z modelu :)
  4. Submit button jako jednoduché tlačítko pro odeslání formuláře.

Nakonec se formuláři předá funkce, která se volá při jeho úspěšném odeslání a do parametru dostane uživatelem zadané hodnoty. Chtěl bych zde upozornit na to, že úspěšné odeslání formuláře znamená jeho kompletní validaci na straně klienta i serveru podle zadaných pravidel, tzn. že do metody calculatorFormSucceeded() se dostanou už jen hodnoty, se kterými umí náš model pracovat. V této metodě pak jen stačí zavolat univerzální výpočetní funkci z modelu, předat jí potřebné hodnoty od uživatele a výsledek uložit do $this->result. Nyní je náš presenter již kompletní, ovšem cesta k němu není úplně jasná. Pojďme to napravit.

Routování v Nette

Jelikož jsme nahradili původní třídu HomepagePresenter naší vlastní, musíme upravit i router, který k němu určoval cestu, tj. pod jakou URL ho najdeme. V Nette k tomu slouží tzv. RouterFactory a opět věřím, že ti bystřejší z vás si všimli její definice v souboru common.neon při nastavování DI. Nyní ji potřebujeme upravit tak, aby pod výchozí URL byl právě náš presenter.

app/router/RouterFactory.php

Takže si otevřeme příslušný soubor a takto si ho upravíme:

<?php

declare(strict_types=1);

namespace App\Router;

use Nette;
use Nette\Application\Routers\RouteList;


final class RouterFactory
{
    use Nette\StaticClass;

    public static function createRouter(): RouteList
    {
        $router = new RouteList;
        $router->addRoute('<presenter>/<action>[/<id>]', 'Calculator:default');
        return $router;
    }
}

Nyní je pod výchozí URL adresou náš presenter a jeho akce default (metoda renderDefault()). Teď už zbývá jen poslední část skládačky a tou je šablona.

Šablona (View)

Jak již bylo řešeno v první lekci, Nette používá vlastní šablonovací systém Latte. V něm si teď vytvoříme šablonu pro naši aplikaci. Jako vzor si necháme původní šablonu, jen ji trochu upravíme.

app/presenters/templates/Calculator/default.latte

Nejdříve půjdeme do složky app/Presenters/templates/ a zde přejmenujeme složku Homepage/ na Calculator/. V ní se poté nachází soubor default.latte, který poslouží jako šablona pro naši akci default a my ho pouze upravíme:

{block content}
<div id="banner">
    <h1 n:block=title>Kalkulačka</h1>
</div>

<div id="content">
    <h3 n:ifset=$result>Výsledek je: {$result}</h3>
    {control calculatorForm}
</div>

<style>
...

Jak je vidět, <style> zachováme a upravíme pouze kód nad ním.

Nejprve upravíme <h1>, který díky makru n:block=title slouží zároveň i jako html <title> pro celou stránku.

Následně vymažeme obsah <div id="content"> a doplníme naše vypsání výsledku např. do <h3>. Pomocí {$result} se zde zobrazí data předaná z presenteru a pomocí makra n:ifset se výsledek zobrazí pouze pokud není null.

Celý definovaný formulář pak vykreslíme do šablony pomocí již zmíněného makra {control <název_komponenty>} a názvu našeho formuláře (metody createComponent*()). A tím je naše práce na šabloně i na celém projektu hotová. Pokud nyní otevřeme URL projektu, uvidíme zde plně funkční kalkulačku:

Základy Nette frameworku

Můžete na ní vyzkoušet všechny chytáky, které vás napadnou :)

Pokud vám není cokoli jasné, stáhněte si projekt z přílohy a projeďte si jak se data předávají z modelu do presenteru a odtud do šablony. Kódu v aplikaci tolik nemáme, po chvíli byste se měli zorientovat. Pokud ani to nepomůže, určitě vám MVC/MVP objasní seriál Jednoduchý redakční systém v PHP objektově (MVC).

To je pro dnešní lekci vše.

V příští lekci, Jednoduchý redakční systém v Nette - Struktura projektu, začneme úplně novou pořádnou aplikaci v Nette ;)


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 1306x (877.59 kB)
Aplikace je včetně zdrojových kódů v jazyce PHP

 

Předchozí článek
První aplikace v Nette
Všechny články v sekci
Základy Nette frameworku
Přeskočit článek
(nedoporučujeme)
Jednoduchý redakční systém v Nette - Struktura projektu
Článek pro vás napsal Jindřich Máca
Avatar
Uživatelské hodnocení:
58 hlasů
Autor se věnuje převážně webovým technologiím, ale má velkou zálibu ve všem vědeckém, nejen ze světa IT. :-)
Aktivity