Jeden standalone formulář pro přidávání a editaci

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

Zdravím, začínám s nette a při vytváření formuláře jsem narazil na „háček“ – jak získat hodnotu hidden prvku už při vytváření formuláře resp jak byste řešili následující form vy?

//Demonstrativni kod.. :)
class MyForm extends BaseForm { //vychazi z ui/form

	public function __construct($id = null) {
		parent::__construct();
		$this->addText('name', 'Name');
		$this->addTextArea('note', 'Note');

		$this->addHidden('id', $id);

		if ($this['id']->getValue()) {
			$this->addSubmit('save', 'Save')->onClick[] = $nejakySaveCallback;
			$this->addSubmit('remove', 'Remove')->onClick[] = $prozmenuRemoveCallback;
		} else {
			$this->addSubmit('add', 'Add')->onClick[] = $pridavaciCallback;
		}
	}

}

Řešení: Presenter (container) se musí připojit už při vytváření:

use Nette\ComponentModel\IContainer

class MyForm extends BaseForm { //vychazi z ui/form

	public function __construct(IContainer $container = null, $name = null, $id = null) {
		parent::__construct($container, $name);
		$this->addText('name', 'Name');
		$this->addTextArea('note', 'Note');

		$this->addHidden('id', $id);

		if ($this['id']->getValue()) { //melo by byt id z POST, ale nevrati nic, tedy nikdy se tato cast vete nespusti
			$this->addSubmit('save', 'Save')->onClick[] = $nejakySaveCallback;
			$this->addSubmit('remove', 'Remove')->onClick[] = $prozmenuRemoveCallback;
		} else {
			$this->addSubmit('add', 'Add')->onClick[] = $pridavaciCallback;
		}
	}

}

Editoval Caine (30. 12. 2011 17:16)

bucrijos
Člen | 6
+
0
-
  1. Ten tvůj id nemá žádnou hodnotu (value), takže ti $this[‚id‘]->getValue() nemůže nic získat.
  2. $this[‚id‘]->getValue() funguje až po odeslání formuláře.
Filip Procházka
Moderator | 4668
+
0
-

Především bych v konstruktoru zavolal parent::__construct().

Caine
Člen | 216
+
0
-

@bucrijos
První bod jsem jen zapomenul uvést, takže zdroj. kód jsem doplnil, aby to nemátlo. ID je nutný samozřejmě získat odněkud zvenku, ale jakmile se klikne na nějakej submit, ono ID je k dispozici už jen v hidden prvku, což souvisí právě s bodem 2. Tak jak to tedy udělat?:)

@JuniorJR
Myslim, že to problém neřeší, protože po odeslání (submit) už ID z konstruktoru neni k dispozici, což jsi nemoh vědet, protože jsem to tam zapomněl dopsat.. :)

Caine
Člen | 216
+
0
-

@HosipLan jj já vim, to jsem tu taky zapomněl uvést:)

JuniorJR
Člen | 181
+
0
-

Můžeš pro představu uvést UseCase toho, čeho se snažíš dosáhnout?

Caine
Člen | 216
+
0
-

JuniorJR Samostatnej formulář, kterej se dá použít kdekoliv (neni vázanej na jeden presenter). Např klikne se na odkaz, kde ID je známo např z url, ten přes ajax načte takovejhle formulář, kterej se po úpravách odešle, jenže tam už Id z url není k dispozici a mělo by se teda brát z onoho hidden prvku.

JuniorJR
Člen | 181
+
0
-

V tom případě logickou chybu nevidím a tak jak to máš, by to mělo fungovat dle očekávání. Jak vytváříš daný formulář?

Editoval JuniorJR (23. 12. 2011 20:51)

Caine
Člen | 216
+
0
-

Přes createComponent…() v presenteru.

Caine
Člen | 216
+
0
-

Už to tu leží pár dní… Nikoho nenapadá, jak to nějak pěkně vyřešit?

JuniorJR
Člen | 181
+
0
-

Zkus si během vytvoření daného formuláře dumpnout hodnotu ID.

Caine
Člen | 216
+
0
-

@natrim To fungovat nebude, protože se pak pokaždý nastaví value na hodnotu $id a pokud $id bude právě null, přepíše se i postnutá hodnota hidden prvku na null. Vypadá to, že to nijak jednoduše řešit nejde. Škoda.

Tomáš Votruba
Moderator | 1114
+
0
-

Jestli to dobře chápu, potřebuješ mít pro jeden presenter dvě view add.latte a edit.latte, přičemž do obou použiješ {control myForm}. Zatím řešíš pouze vytvoření formuláře, takže se toho budu držet.

class MyForm extends BaseForm { // vychazí z UI/Form

        public function __construct($id = null) {
                parent::__construct();

                $this->addText('name', 'Name');
                $this->addTextArea('note', 'Note');

		$id = $this->getParam("id"); // získám parametr z url, např. mysite.com/edit/27/
		if($id) { // máme id -> editujeme
	                $this->addHidden('id', $id);
			$this->addSubmit('save', 'Save')->onClick[] = $nejakySaveCallback;
                        $this->addSubmit('remove', 'Remove')->onClick[] = $prozmenuRemoveCallback;
		}
		else { // nemáme id -> přidáváme
                        $this->addSubmit('add', 'Add')->onClick[] = $pridavaciCallback;
                }
        }
}

Jestli jsem nepochopil, tak mne oprav. Určitě to nějak vyřešíme :)

Editoval Schmutzka (28. 12. 2011 2:45)

Caine
Člen | 216
+
0
-

Ve skutečnosti ten formulář chci mít nezávislej na presenteru/akcích a aby se načítal přes ajax. Tzn getParam(„id“) lze z url získat jen při prvotním načtení formuláře, ale po odeslání formuláře už Id v url nebude, tedy vždy se spustí jen větev bez Id („Add“). Jedině měnit url pro zpracování formuláře nebo Id z hidden prvku získat přes getHttpRequest()->getPost(‚id‘), ale obě řešení mi přijdou krkolomný.

Tomáš Votruba
Moderator | 1114
+
0
-

Po odeslání je id v hidden inputu. Pak tedy nejspíš invaliduješ snippet a může tuto hodnotu nastavit zpět. Buď přes proměnnou ($this->template...), nebo možná přímo ve formuláři ($form["id"]->setValue()).

Zkoušels z toho něco? Co ti zatím vyhovuje nejlépe? Jak to vypadá teď? :)

Caine
Člen | 216
+
0
-

Mno můj hlavní problém je, že po odeslání formuláře se sice Id odešle, ale nedá se k němu pěkně dostat přes getValue(), protože hodnoty z post se se svejma prvkama propojej až po připojení formu k presenteru (nebo tak nějak jsem to pochopil), ale já bych k těm hodnotám chtěl přistupovat už při vytváření formuláře, takže to jedině můžu nějak obejít např. přes již zmíněný getHttpRequest()->getPost(‚id‘).

davidm
Člen | 81
+
0
-

Jestli to nebude tim ze metoda $this->getParam ve formularich neexistuje … kdyz uz to chces delat timto zpusobem tak:

class MyForm extends BaseForm { // vychazí z UI/Form

        public function __construct($parent, $name)
	{
                parent::__construct($paretn, $name);

                $this->addText('name', 'Name');
                $this->addTextArea('note', 'Note');

		// ve 2.0 je getParam nahrazeny za getParameter
                $id = $this->presenter->getParameter("id");

                if($id) { // máme id -> editujeme
                        $this->addHidden('id', $id);
                        $this->addSubmit('save', 'Save')->onClick[] = $nejakySaveCallback;
                        $this->addSubmit('remove', 'Remove')->onClick[] = $prozmenuRemoveCallback;
                }
                else { // nemáme id -> přidáváme
                        $this->addSubmit('add', 'Add')->onClick[] = $pridavaciCallback;
                }
        }
}

a musis v presenteru ten formular pripojit aby si moh pouzivat getPresenter():

createComponentMyForm($name)
{
	return new MyForm($this, $name)
}
Caine
Člen | 216
+
0
-

Díky za tipy, ale pořád si nerozumíme. Ten formulář není to, co používám ve zdrojácích, ale jen demonstrativní ukázka toho, čeho bych chtěl dosáhnout, takže hledat chybky ve zdejším zápisu je nesmysl:) Snažím se tu řešit princip jak to řešit, nikoliv přímo implementaci. Když píšu, že Id není přes getParam() k dispozici (resp jen při prvotním vytváření formuláře), tak tím není myšleno to, že by pozdějš k dispozici být mělo.

Fungovat by to mělo takto. Mám obrázek, kde můžou být nějaký body. Klikáním na obrázek se přes Ajax načítá formulář. Když kliknu na místo, kde bod není, tak se načte formulář pro přidávání, když kliknu na existující bod (obsahující Id), načte se formulář editační. Problém je v tom, že u editačního fomuláře html atribut action už neobsahuje url s ID (proto chci použít hidden prvek s ID), tzn po odeslání formuláře už nejde z getParam() získat Id protože prostě není odkud, jen z hidden prvku (resp POSTu).

davidm
Člen | 81
+
0
-

Nvm jestli rozumim co presne chces, ale urcite to mas reseny pres signaly … tak treba nejak takhle

public function handleImageClicked($id = NULL)
{
	if ($id) {
		$this['myForm']['id']->setValue($id);
	} else {
		unset($this['myForm']['id'];
	}
	// invalidate ...
}

Editoval davidm (29. 12. 2011 19:26)

Caine
Člen | 216
+
0
-

To řeší prvotní předávání Id. Problém je, ale v tomhle:

if ($this['id']->getValue()) { //melo by byt id z POST, ale nevrati nic, tedy nikdy se tato cast vete nespusti
	$this->addSubmit('save', 'Save')->onClick[] = $nejakySaveCallback;
        $this->addSubmit('remove', 'Remove')->onClick[] = $
} else {
	$this->addSubmit('add', 'Add')->onClick[] = $
}

getValue() vždy vrátí vždycky null. Logicky by to fungovat takhle mělo, ale někdě uvnitř si prvky svoje hodnoty získávaj až po zavolání funkce isSuccess (Nette/Forms) nebo až po připojení do presenteru (..UI/Forms). Takže asi lepší řešení než vzít hodnotu hidden prvku přímo z httpRequestu asi neni:(

davidm
Člen | 81
+
0
-

to ani nic jinyho vratit nemuze, da se to resit hodne zpusobama, napriklad muzes mit dva formulare pro Add a pro Edit pak v tom handle ten pozadovanej predas do $this->template->form a v sablone budes formular vykreslovat {$form}

nebo treba muzes pripojovat ten form pres addComponent:

public function handleImageClicked($id = NULL)
{
	$this->addComponent(new MyForm($id), 'myForm');

        // invalidate ...


class MyForm extends BaseForm { // vychazí z UI/Form

        public function __construct($id = NULL)
        {
                parent::__construct();

                $this->addText('name', 'Name');
                $this->addTextArea('note', 'Note');

                if($id) { // máme id -> editujeme
                        $this->addHidden('id', $id);
                        $this->addSubmit('save', 'Save')->onClick[] = $nejakySaveCallback;
                        $this->addSubmit('remove', 'Remove')->onClick[] = $prozmenuRemoveCallback;
                }
                else { // nemáme id -> přidáváme
                        $this->addSubmit('add', 'Add')->onClick[] = $pridavaciCallback;
                }
        }
}
Caine
Člen | 216
+
0
-

Ani takhle to řešit nepůjde (elegantně).. Při rozdílu v jen pár tlačítkách, dva formuláře zavrhuju úplně. A i kdybych použil dva formuláře, tak se problém s ID deleguje jen o úroveň výš, kde bych stejně musel použít přímo httpRequest, abych zjistil, který form vytvořit. Druhý navrhový způsob zase postrádá smysl, tedy alespoň pokud bych nepoužil postup z prvního způsobu, protože odkud se veme Id, když po odeslání je jen v hidden prvku a tím pádem se ani nikdy větev s vytvářením samotného hidden prvku nikdy po odeslání nespustí?:)

davidm
Člen | 81
+
0
-

spusti .. ty ten formular budes vytvaret az v tom signalu {vubec nepouzivas createComponent), takze do konstruktoru predas to id z toho handleClicked

Caine
Člen | 216
+
0
-

A jak se pak formulář vytvoří po odeslání? Když odešlu formulář, ten handle se nespustí.

davidm
Člen | 81
+
0
-

A zkousel si to? Idealni je mit ty dva formulare (muzes je mit klidne v jedny tride, neco jako BaseMyForm) a mit tam metody addEditFields a addCreateFields)

Editoval davidm (29. 12. 2011 22:37)

davidm
Člen | 81
+
0
-
class MyForm extends BaseForm { // vychazí z UI/Form

        public function __construct($id = NULL)
        {
                parent::__construct();

                $this->addText('name', 'Name');
                $this->addTextArea('note', 'Note')
        }

	public function addEditFields()
	{
                        $this->addHidden('id', $id);
                        $this->addSubmit('save', 'Save')->onClick[] = $nejakySaveCallback;
                        $this->addSubmit('remove', 'Remove')->onClick[] = $prozmenuRemoveCallback;
	}

	pulic function addCreateFields()
	{
                        $this->addSubmit('add', 'Add')->onClick[] = $pridavaciCallback;
	}
}

//presenter

public function createComponentAddForm()
{
	$form = new MyForm();
	$form->addCreateFields();
	return $form;
}

public function createComponentEditForm()
{
	$form = new MyForm();
	$form->addEditFields();
	return $form;
}

public function handleClicked($id = NULL)
{
	if ($id === NULL) {
		$this->template->form = $this['addForm'];
	} else {
		$this['editForm']['id']->setValue($id);
		$this->Template->form = $this['editForm'];
	}
	// invalidate
}

jestli ani tohle ne tak uz me nastves :)

Editoval davidm (29. 12. 2011 22:43)

Caine
Člen | 216
+
0
-

Handle se spouští přes ?do=nazevHandlu, formulář se odesílá přes ?do=frmSubmit (nebo tak nějak).. Takže spustit se může jen jedno nebo druhý, nebo ne?;) Krom toho, formulář si do svý action url nepřebírá parametry z aktuální url, tzn když přes ajax načtu form např z /nejaka/adresa/?id=x, tak při vytvoření je parametr id k dispozici, ale action url formuláře bude jen /nejaka/adresa/?do=formSubmit (to je vlastně důvod proč potřebuju to Id předávat v hidden).

davidm
Člen | 81
+
0
-

reseni mas nad tim, sem psal moc pomalu :)

Caine
Člen | 216
+
0
-

Nepsal si rychle, to byla reakce na tvý řešení;) Pořád ti tam uniká to podstatný – formulář se musí taky nějak odeslat a vysvětlení, proč to nebude fungovat, jsem napsal v předchozím postu..

davidm
Člen | 81
+
0
-

to bude fungovat protoze ten formular je vytvorenej tim createComponentXxx … to co se deje po odeslani mas definovany v callbacku to handle ti slouzi jen pro vykresleni a pro nastaveni toho id … :P

Caine
Člen | 216
+
0
-

Vlastně máš pravdu. Neuvědomil jsem si, že form se vytvoří i jen na základě ?do=nazevFormu-submit. Káždopádně ze 3 metod se stalo metod 6 a to mi už jako moc elegantní řešení nepřijde (jako drbat se levou rukou za pravym uchem:) a v podstatě je jednoduší to Id vycucat z httpRequest.

bene
Člen | 82
+
0
-
Caine
Člen | 216
+
0
-

@bene Díky díky díky, to je přesně ono!

Citováno z odkazu výše:

…takže když se předá presenter už v konstruktoru, tak při vytvoření nejakého prvku formu a přidání ho do formuláře se do něj automaticky vloží hodnota, pokud byl form odeslán…

Vyzkoušel jsem a funguje to.

Ačkoliv jsem ten topic navštívil ještě než jsem napsal tenhle (vlastně jsem projel skoro každej topic ve formulářích), ta podstatná informace mi tam nějak unikla nebo spíš jsem tomu ještě nerozumněl tolik, abych ji jako podstatnou dokázal vyhodnotit:)

Takže problém je vyřešen. Jupí:)

Bumerank
Člen | 30
+
0
-

Já řeším něco s velice podobným konceptem (1 šablona pro 2 akce – vytvoření a editace katalogu) a mám problém při editaci záznamu. Vytváření komponenty formuláře a předvyplňování polí:

<?php
    protected function createComponentCatalogEditForm($name, $id = null){

	$form = new Form($this, $name);

        // group with basic info
        $form->addGroup("Základní informace");
        $form->addText("id", "ID:")->setDisabled();
        $form->addText("name", "Název:");

        // group with URLs TODO - add buttons to open new tab and go to URL in the field
        $form->addGroup("URL Adresy");
        $form->addText("url", "URL:")
                ->setRequired('Zadejte prosím URL!')
                ->addRule(Form::FILLED, "Prosím, zadejte URL katalogu.")
                ->addRule(Form::URL, "Neplatný tvar URL adresy.");
        $form->addText("add_url", "URL začátku registrace:")
                ->addRule(Form::FILLED, "Prosím, zadejte URL začátku registrace.");
        $form->addText("evaluation_url", "URL vyhodnocení:");

	// dále vyplním hodnoty políček, pokud je předáno ID katalogu (pokud není, tak je šablona použita pro vytvoření nového záznamu)
	if($this->processed_catalog != null)
        {
            $form["id"]->value = "111"; //$this->processed_catalog["er_catalog_id_catalog"];
            $form["name"]->defaults = $this->processed_catalog["er_catalog_name"];

            $form["url"]->setDefaults($this->processed_catalog["er_catalog_url"]);
            $form["add_url"]->defaults = $this->processed_catalog["er_catalog_add_url"];
            $form["evaluation_url"]->defaults = $this->processed_catalog["er_catalog_evaluation_url"];
	}

        $form->onSuccess[] = callback($this, 'doSave');

        return $form;
}
?>

A problém je v tom, že se mi pak odesílají vždy původně předvyplněné hodnoty, namísto toho, co upravil uživatel. Zkoušel jsem předvyplňovat takto:

  • value = „hodnota“;
  • setValue(„hodnota“);
  • setDefaults(„hodnota“); ..tady mi to napíše, že to nezná metodu setDefaults()
  • defaults = „hodnota“; ..nezná vlastnost defaults

A když už jsem u tohoto formuláře – jde nastavit nějakými standardními vlastnostmi Nette, aby mohlo být políčko buď prázdné, nebo musí odpovídat třeba masce e-mailu, nebo si musím napsat svůj validátor? U e-mailu by mi to až tak nevadilo – horší by bylo u kontroly MIME typu (chtěl jsem použít na prvek Upload toto: ->addRule(Form::MIME_TYPE, „Soubor musí být textový (*.txt,…)!“, „text“) )

Bumerank
Člen | 30
+
0
-

tak na to s defaultními hodnotami jsem už přišel…ale na validaci, kdy políčko není nutné vyplnit, ale pokud je vyplněné, tak musí splňovat daná kritéria – ví někdo poradit? :)

Jan Endel
Člen | 1016
+
0
-

Dle mého děláš plnění ve špatném místě, takováto operace patří do actionEdit, můžeš se podívat do kuchařky, pak to pohodlně zavoláš nějak takto (v poli $defaults jsou nabindované proměnné podle klíčů ve formuláři, protože používáš jiný klíč v poli a jiný pro název prvku, nechápu trošku proč):

public function actionEdit($id)
{
	$defaults = //převedení klíču z původního pole na prvky formuláře
	$form = $this->getComponent('catalogEditForm');
	$form->setDefaults($defaults);
}
Bumerank
Člen | 30
+
0
-

mezi formulářovými prvky jsou i takové, jejichž hodnotu nikam neukládám a jsou tam pouze jako fíčura UI (např. radio buttonem volím jestli chci jeden z atributů zadat jako text do textarea, nebo uploadovat soubor) a trochu vidím bezpečnostní díru v tom, kdyby se inputy jmenovaly stejně jak sloupce v DB, tak jsem to raději udělal takto…nicméně toto už mi funguje :)

spíš mě těď trápí druhá část – validace…právě na té možnosti vložit text/soubor je ta zrada – buď by upload prošel jako prázdný, nebo jako textový soubor (podle MIME typu)

Jan Endel
Člen | 1016
+
0
-

Mít stejně pojmenované inputy jako db není nic špatného, když si dá člověk pozor a co se týče té validace, tak by mohla pomoci dokumentace hlavně část o validačních podmínkách.

Bumerank
Člen | 30
+
0
-

díky moc – pomohla :) ..stačilo před pravidlo s MIME typem přidat

<?php

->addCondition(Form::FILLED)

?>