Přidávání políček k formuláři v továrničce

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Pavel Kravčík
Člen | 1205
+
0
-

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

Etch
Člen | 403
+
0
-

Proč si „klientForm“ neuděláš jako kontejner a v továrně akvizic nesložíš formulář akvizic z daného konetjneru a těch dalších 10 položek?

Editoval Etch (6. 1. 2015 16:43)

Pavel Kravčík
Člen | 1205
+
0
-

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.

Etch
Člen | 403
+
0
-

Uved kod jak ty formuláře definuješ a vykresluješ teď…

Editoval Etch (6. 1. 2015 20:13)

Pavel Kravčík
Člen | 1205
+
0
-

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
+
+1
-

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
+
0
-

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!

Etch
Člen | 403
+
0
-

Proč by si měl dědit presentery??

Editoval Etch (7. 1. 2015 12:01)

Pavel Kravčík
Člen | 1205
+
0
-

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. :)

Etch
Člen | 403
+
0
-

Dát ten formulář do komponenty a v presenterech, kde ho budeš potřebovat danou komponentu vykreslit?

Dědit kvůli takovéhle věci presenter mi přijde jako zneužití dědičnosti, ale je to možná jen můj názor. :)

Editoval Etch (7. 1. 2015 12:20)

Pavel Kravčík
Člen | 1205
+
0
-

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?

Etch
Člen | 403
+
0
-

No dobře, ale to furt nějak neosvětluje to, proč by si chtěl kvůli tomuhle dědit presentery.

Pavel Kravčík
Člen | 1205
+
0
-

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
+
+1
-

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
+
+1
-

@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. :)