Přidávání políček k formuláři v továrničce
- Pavel Kravčík
- Člen | 1205
Přečetl jsem snad všechno z fóra a možná jsem tupý, že mi to zatím tolik nepomohlo.
Hledám řešení, resp. best practise pro následují:
Mám seznam klientů. Na to je udělaný jeden delší formulář (IČ,RČ,Ares apod.). Teď jde o to, že tenhle formulář se využívá na dalších místech a v jiných modulech. Například v modulu Akvizice. Předpokládejme, že mám dvě věci.
- Adresář (klientForm obyčejný)
- Akvizice (klientForm rozšířený o 10 políček)
Uživatel používá Akvizice. Přijde. Vyplní jméno, RČ a klikne na snippet → vyhledej v adresáři. Pokud najde, doplní mu aktuální údaje z Adresáře. Tohle je vše v pohodě. Akorát černá kaňka → všechno kolem klientForm je v modulu Akvizice duplikované. Tudíž manuální vykreslování copy&paste do latte v Akvizicích, to samé v presenteru Akvizice ve funkci createComponentKlient().
Teď jsem dostal prostor pro větší refactoring. Takže bych to rád udělal nějak „humánně“. Zkoušel jsem vlastní komponentu přes autowire s vlastním renderem a šablonou. To je sice asi dobrý nápad, protože vše si nastavím v té komponentě. Ale jak jí pak dokážu rozšířit i třeba o nové latte? Nebo je to lepší udělat nějakým způsobem přes Presenter? Vytvořit si KlientPresenter, mít v něm formulář a pak ho dědit a rozšiřovat?
Díky za rady
- Pavel Kravčík
- Člen | 1205
A když bych chtěl dvě továrničky spojit? To asi nejde, že ne?
A problém nastane, když použiji container a pak už nebudu schopnej používat vlastní template jen jednu ne?
V podstatě mi jde o nějaké skládání formulářů s jedním template.
- Pavel Kravčík
- Člen | 1205
To vidět nechceš. :D V současné chvíli se to dělá následovně. Chceš použít klientForm v jiném modelu. Vykopíruješ si z latte klienta html (manuální vykreslení), vykopíruješ si z createComponentKlientForm všechny addText() a ctrl+v do požadovaného modulu. Takže pokud ten klientForm chci použít v 10 modulech tak ho mám pak 11 natvrdo napsaný. Což je peklo. :) Já k tomu přišel a snadno jsem si tak navykl na spoustu takových zlozvyků. Teď jsem objevil továrničky, autowire a podobně. A rád bych to předělal. Přemýšlím však, jaký je správný způsob.
Představa je to vyrobit univerzálně.
- 1 latté
- 1 definice containeru/továrničky
A pak když bych potřeboval klientForm i akviziceForm je nějak spojit. To je jen nápad. Nejdřív to chci dobře navrhnout, než to začnu programovat. Zatím si jen zkouším továrničky a podobně.
Add Containery: To pak přijdu o možnost mít ten formulář kompletně připravený, ne? Snippety, závislosti a podobně.
S Nette nemám delší zkušenosti, proto se ptám.
Editoval kzk_cz (7. 1. 2015 8:16)
- Etch
- Člen | 403
V rychlosti:
AddressContainer:
namespace Forms;
use Nette\Forms\Container;
class AddressContainer extends Container{
public function __construct(){
parent::__construct();
$this->addText('street', 'Street');
}
}
NameContainer:
namespace Forms;
use Nette\Forms\Container;
class NameContainer extends Container{
public function __construct(){
parent::__construct();
$this->addText('firstname', 'Firstname');
$this->addText('lastname', 'Lastname');
}
}
HomepagePresenter:
namespace App\Presenters;
use Forms\AddressContainer;
use Forms\NameContainer;
use Nette\Application\UI\Form;
class HomepagePresenter extends BasePresenter{
private $data;
public function renderDefault(){
$this->template->data = $this->data;
}
protected function createComponentNameForm(){
$control = new Form();
$control['name'] = new NameContainer();
$control->addSubmit('send', 'Send');
$control->onSuccess[] = array($this, 'nameForm_onSuccess');
return $control;
}
protected function createComponentAddressForm(){
$control = new Form();
$control['address'] = new AddressContainer();
$control->addSubmit('send', 'Send');
$control->onSuccess[] = array($this, 'addressForm_onSuccess');
return $control;
}
protected function createComponentComplexForm(){
$control = new Form();
$control['name'] = new NameContainer();
$control['address'] = new AddressContainer();
$control->addSubmit('send', 'Send');
$control->onSuccess[] = array($this, 'complexForm_onSuccess');
return $control;
}
public function addressForm_onSuccess(Form $sender){
dump($sender->values);
}
public function nameForm_onSuccess(Form $sender){
dump($sender->values);
}
public function complexForm_onSuccess(Form $sender){
if(!$this->isAjax()){
dump($sender->values);
}
$this->data = $sender->values;
$this->redrawControl('data');
}
}
default.latte
{block content}
{snippet data}
{? dump($data)}
{/snippet}
<h3>Name Form</h3>
{control nameForm}
<h3>Address Form</h3>
{control addressForm}
<h3>Complex Form</h3>
{control complexForm}
<h3>Complex Form (manual render + ajax)</h3>
{include 'form.latte'}
{/block}
form.latte
{block content}
{form complexForm, class => 'ajax'}
{ifset $presenter['complexForm']['name']}
{formContainer name}
{label firstname/}{input firstname}<br>
{label lastname/}{input lastname}<br>
{/formContainer}
{/ifset}
{ifset $presenter['complexForm']['address']}
{formContainer address}
{label street/}{input street}<br>
{/formContainer}
{/ifset}
{input send}
{/form}
{/block}
Editoval Etch (7. 1. 2015 11:45)
- Pavel Kravčík
- Člen | 1205
Jasně rozumím. Takže je lepší použít containery a dědit si presentery. A celé to pak vypisovat nějakým „builderem“ jako je ten form.latte.
Děkuju za rozepsání. Určitě se bude někomu hodit!
- Pavel Kravčík
- Člen | 1205
Pokud mám ten formulář několik handle funkcí. Třeba ten, co Ti podle psč načte jméno obce a podobně. Takový formulář bych rád používal jinde. Takže jsem myslel, že když si ten formulář nadefinuji třeba v FormPresenter a pak ho jen zdědím, tak budu schopen použít všechny jeho funkce a podobně.
Asi jsem lama, ale proto se ptám. :)
- Pavel Kravčík
- Člen | 1205
Asi to špatně vysvětluju. Dám přímo konkrétní příklad. Jeden z těch co jsme testoval a zatím se mi líbil nejvíce.
klient.config.neon
services:
implement: App\IKlientFormFactory
Továrnička
<?php
namespace App;
use Nette;
use Nette\Application\UI;
/**
* Komponenta KLIENT FORM
*/
class klientForm extends UI\Control
{
public $onFormSuccess;
public function __construct()
{
/** Sem natáhnu parametry */
parent::__construct();
}
/**
* @return Form
*/
public function createComponentKlientForm()
{
$form = new UI\Form;
//inputy
$form->addSubmit('send', 'Odeslat');
$form->onSuccess[] = $this->processForm;
return $form;
}
public function processForm($form, $values)
{
$this->onFormSuccess();
}
/**
* Nastavíme vlastní šablonu pro formulář
*/
public function render()
{
$this->template->render(__DIR__.'/klientForm.latte');
}
}
interface IKlientFormFactory
{
/**
* @return KlientForm
*/
function create();
}
Tady si připravím formulář se všemi funkcemi a závislosti. Teď bych rád tenhle formulář nějakým způsobem použil na deseti různých místech s tím, že bych ho rád rozšířil. A ideálně, kdybych ho mohl rozšířit o jinou továrničku. Abych prostě měl vše na jednom místě (latte a definici).
Což je asi neřešitelné ne?
- Pavel Kravčík
- Člen | 1205
No protože v tom presenteru bych měl „createComponentsForm“ a „handle“ funkce.
Jak říkám, do tajů Nette teprve pronikám – takže jsem rád za každou diskuzi a radu. A s tímhle si popravě moc rady nevím, jak to řešit funkčně a čistě. Proto se radši zeptám 10× jak kůň, než to pak 50 hodin programovat špatně. :)
- h4kuna
- Backer | 740
Metody handle* si napiš do třídy klientForm. Využij eventy například onDelete.
V presenteru pak budeš mít
/** @var \App\IKlientFormFactory @inject */
public $iKlientFormFactory
protected function createComponentForm() {
$service = $this->iKlientFormFactory->create();
$service->onDelete[] = function(/* parametry */) {
// Něco smazni.
};
return $service;
}
A v klientForm
public $onDelete = [];
public function handleDelete() {
$this->onDelete(/* parametry */);
}
A v jiným presenteru mužeš jinak nadefinovat onDelete. Eventy jsou pole takže pokud potřebuješ nějaké společné chování tak si jej dej do klientForm.
Editoval h4kuna (7. 1. 2015 16:20)
- Pavel Kravčík
- Člen | 1205
@h4kuna, @Etch: Díky. Už mi to dává smysl.
Jsem NN (NetteNoob). Ale už jsem i díky Vám dosáhl požadované funkčnosti. :)