Komponenta – změna view (+ ajax)
- Kcko
- Člen | 470
Komponenta na formulář, v presenteru zavolána přes factory, k tomu
namíchaný ajax, funguje.
Nicméně nemůžu přijít na způsob jak po odeslání formuláře onen
formulář vymazat a nechat tam jenom success zprávu (a tady by mě zajímalo
jestli to jde udělat a) flash-kou a za b) přes jiný view / render /
handle )
Kódy
Presenter:
protected function createComponentRegistrationForm()
{
$control = $this->registrationFormFactory->create();
return $control;
}
Komponenta (očištěná od věcí, které nejsou důležité):
<?php
namespace App\FrontModule\Components;
use Nette\Application\UI,
Nette,
Nette\Mail\Message,
Nette\Mail\SendmailMailer,
App;
interface IRegistrationFormFactory
{
/** @return RegistrationForm */
public function create ();
}
class RegistrationForm extends UI\Control
{
protected $userModel;
public function __construct (App\Model\User $user)
{
parent::__construct();
$this->userModel = $user;
}
public function handleSave()
{
// nebo renderSave nebo jak kua?!
}
public function render()
{
$this->template->setFile(__DIR__ . '/../templates/components/registration-form.latte');
$this->template->render();
}
public function createComponentForm()
{
$form = new UI\Form;
.
.
.
$form->onSuccess[] = array($this, 'registerFormSubmitted');
return $form;
}
public function registerFormSubmitted($form)
{
$values = $form->getValues(TRUE);
$isRegistered = $this->userModel->isEmailDuplicity($values["email"]);
if ($isRegistered)
{
$form->addError("Tento email je již registrován, zvolte si jiný");
$this->redrawControl();
return;
}
$this->flashMessage("Registrace dokončena", "alert-success");
if ($this->presenter->isAjax())
{
$this->redrawControl('ajaxform');
$form->setValues(array(), TRUE);
}
else
{
$this->redirect("this");
}
}
}
Šablona formuláře:
{snippet ajaxform}
<div n:foreach="$flashes as $flash" class="flash alert {$flash->type}">{$flash->message}</div>
{form form "novalidate" => "", "class" => "ajax"}
...
{/form}
{/snippet}
Může mi tedy prosím někdo říct jak následující kód inteligetně a čistě upravit tak, abych po odeslání mohl vyměnit render za jiný (nechci na původním místě zobrazovat formulář, jen místo něj zobrazit hlášku / případně jiné HTML v rámci oné komponenty = v jiném view?)
PS Můžu si do šablony přidat nějaké označení jestli byl formulář již poslán či nikoliv, ale to není co bych chtěl (chtěl bych to vyřešit v jiném renderu – jestli to tedy jde vůbec).
Editoval Kcko (27. 9. 2014 2:14)
- Šaman
- Člen | 2666
- Buď ta komponenta bude stavová a sama si určí, jestli vykreslovat formulář, nebo ne. Pak ji musíš navázat na úložiště (třeba jen session), kde si bude stav uchovávat.
- Po odeslání formuláře přesměrovat na jiný pohled presenteru.
- Nebo trochu jiná varianta první možnosti – ten stav si můžeš předávat v URL a šablona presenteru, nebo komponenty podle existence nějaké proměnné (ne)vykreslí formulář (nebo i celou komponentu).
P.S. Pak budeš mít handleSave (není dobré ukládat data v pohledu, tedy render metodě – co když dá někdo F5-obnovit stránku?) a z ní redirektuješ na jiný pohled, jehož šablona komponentu vůbec nevykreslí. Tedy možnost 2.
Editoval Šaman (27. 9. 2014 2:17)
- Kcko
- Člen | 470
@Šaman
Díky za komentář.
- Tohle není možné (resp. ano, ale příčí se mi logika i pracnost). Pokud bych přesměroval na jiný view/render v Presenteru, tak budu muset zduplikovat šablonu, tj. v mém případě default.latte, protože formulář se nachází na homepage webu, a jediné co v té zduplikované šabloně vyměním bude controlka formuláře za výpis hlášky // třeba flashmessages nebo to tam bude jako překlad … → to je dost ugly ne, mít 2 view, které se liší jen tím že v jednom je controlka a v druhém ne a místo ní je tam napsano odeslano.
1+3) Ještě později v noci než jsem málem pošel :-) jsem to tak udělal, posílám si do šablony proměnnou, zda-li je odesláno či nikoliv a dle toho kontrolku vypíšu.
Tj.
{snippet ajaxform}
<div n:foreach="$flashes as $flash" class="flash alert {$flash->type}">{$flash->message}</div>
{if !isset($sent)}
{form form "novalidate" => "", "class" => "ajax"}
a callback formuláře – konec metody
$this->flashMessage("Registrace dokončena", "alert-success");
if ($this->presenter->isAjax())
{
$this->template->sent = 1;
$this->redrawControl('ajaxform');
$form->setValues(array(), TRUE);
}
else
{
$this->redirect("this");
}
Ale nelíbí se mi to. Moje představa byla, že kontrolce řeknu o změnu pohledu a v jiné renderovací metodě vypíšu nějaké jiné HTML s hláškou o odeslání. (Sem už trošku kantare po probdělé noci, tak bych možná potřeboval kus kódu).
Editoval Kcko (27. 9. 2014 9:49)
- Zax
- Člen | 370
Bohužel, komponenty v Nette pohledy nemají, tedy aspoň ne pohledy, mezi kterými by šlo v rámci komponenty přepínat.
Sám jsem strávil celkem značné množství času tím, že jsem zkoušel komponenty různě hackovat, protože:
- Nejsou pohledy
- Nebaví mě pořád všude psát „if is ajax“
- Nebaví mě neustále určovat cestu k šablonám
Bohužel se to ani ve finále neobešlo bez jistých (drobných) kompromisů.
Kdyby tě zajímaly detaily, můžeš si počíst zde https://github.com/…doc/index.md#…, kdyby byl zájem, můžu komponentu vyčlenit do samostatného repa a uvolnit ji třeba pod licencí MIT.
- Kcko
- Člen | 470
@Zax aha, tak jsem myslel, že to je pouze mojí neznalostí a
pronikáním do Nette.
Určite můžeš :) zájem bude.
Jinak jsem tu komponentu upravil, aby dělala to co chci, zbývá poslední krok k úspěchu, co mám napsat do Ajaxu, aby mi vykreslil / přesměroval na handle jako v případě else větve? Viz zcela dole.
class RegistrationForm extends UI\Control
{
protected $view;
public function handleDone()
{
$this->view = 'registration-form-save';
}
public function render()
{
if (!$this->view)
$this->template->setFile(__DIR__ . '/../templates/components/registration-form.latte');
else
$this->template->setFile(__DIR__ . '/../templates/components/'.$this->view.'.latte');
$this->template->render();
}
public function registerFormSubmitted($form)
{
...
if ($this->presenter->isAjax())
{
/* CO BUDE TADY ??? */
}
else
{
$this->redirect("done!"); // FUNGUJE mimo ajax, nacte se spravna sablona
}
}
}
Editoval Kcko (27. 9. 2014 11:01)
- Zax
- Člen | 370
$view dát public a přidat anotaci @persistent
(tak to mám já
ve svých komponentách), čímž si přidáš možnost měnit pohledy přes URL
a tedy i odkazovat na ně.
Ten submit bych přepsal zhruba takto
if($this->presenter->isAjax()) {
$this->view = 'registration-form-save';
$this->redrawControl();
} else {
// normální redirect s persistentním parametrem
$this->redirect('this', ['view' => 'registration-form-save']);
}
Bacha na jednu věc – parametr $view je přístupný v URL,
takže si ho při nastavování šablony ověř např. přes whitelist, jinak
tam budeš mít díru. (já to řeším ověřením jestli existuje
metoda view<View>()
)
BTW místo toho $this->view = 'registration-form-save';
by
možná bylo hezčí použít nějaký obecnější
$this->forward('this', ['view' => 'registration-form-save']);
a od toho už je to jen krůček k něčemu jako
$this->go('this', ['view' => 'registration-form-save']);
,
které ti udělá i tu kontrolu „if is ajax“. Narazíš ale na to, že
forward ti zruší překreslování snippetů, proto jsem napsal i vlastní
forward (trochu zbastlený, testy na něj nemám, ale pohání mi už mnoho
komponent, takže je funkční ;-) )
Brzy tu komponentu vyčlením do samostatného repa.
- Kcko
- Člen | 470
@Zax zajímavé řešení.
- Moc nerozumím tomu white listu. ve $view je pouze šablona, nejsou to žadné metody, jak to tedy nastavujes?
Já bych na to šel asi takhle jednoduše.
/** @persistent */
public $view;
protected $allowedView = ["foo", "bar"];
public function render()
{
if (!$this->view)
$view = $allowedView[0];
else
{
if (!in_array($this->view, $allowedView))
$view = $allowedView[0];
else
$view = $this->view;
}
$this->template->setFile(__DIR__ . '/../templates/components/'.$view);
$this->template->render();
}
- Upravil jsem na persistentní a public, pokud pošlu bez ajaxu funguje,
pokud s tak nikolivěk a je zcela jedno jestli mám
- kontrolku v šabloně presenteru obalenou snippetem viz
{snippet ajaxform}
{control registrationForm}
{/snippet}
nebo snippetem obalím formulář v šabloně komponenty
{snippet ajaxform}
<div n:foreach="$flashes as $flash" class="flash alert {$flash->type}">{$flash->message}</div>
{form form "novalidate" => "", "class" => "ajax"}
...
Editoval Kcko (27. 9. 2014 13:20)
- Kcko
- Člen | 470
Moje chyba! Chci překreslit snippet který je v šabloně presenteru tak
z controlky musím volat $this->presenter->redrawControl
a
už to KONEČNĚ FUNGUJE
Zeptám se už na (snad) poslední věc. Kdybych nechtěl měnit view přes
URL a měnil ho přes handle (tak jak jsem to měl původně, což je defakto
whitelist, jak to tomu ajaxu řeknu, že chci
„přesměrovat“ na handle a překreslit … bez ajaxu je to jednoduche
$this->redirect('save!')
, ale ted vůbec nevím jak na to
v odchytnutí ajaxu.
- Zax
- Člen | 370
Signály (handle* metody) jsou určeny pro nějakou akci (v url je
?do=
) a má po nich následovat přesměrování na normální
adresu bez signálu. To co ty zkoušíš by sice šlo, ale nemá se to tak
dělat :-)
Komponentu jsem nahrál na github a přidal pár testů
(doporučuju projít zdroják, není v tom nic extra složitého, jen pár
hacků ;-)). Měla by být dostupná přes composer
(zaxxx/zaxcontrol
). Tvá situace by šla řešit cca takto:
class RegistrationForm extends Zax\Application\UI\Control
{
public function viewDefault() {}
public function viewDone() {}
public function registerFormSubmitted($form)
{
...
$this->redrawControl();
$this->go('this', ['view' => 'Done']);
}
}
K tomu přidáš složku templates
, do ní dvě šablony
Default.latte
a Done.latte
a mělo by to frčet.
Nezapomeň obě šablony obalit snippetem (IMHO je lepší to dělat přímo
v komponentě než v presenteru).
Jsou tam ještě cesty, jak to trochu víc zautomatizovat, ale tím tě zatěžovat nebudu, je to dost magické :-D
Editoval Zax (27. 9. 2014 13:37)