RFC přidat metodu setReadonly() do Forms\Controls\BaseControl
- m.brecher
- Generous Backer | 863
Ahoj,
Běžně požadovanou funkcí ve formulářích je použití prvku, který zobrazí hodnotu ale nejde editovat (obdoba readonly property objektu).
Typické použití je při vytváření nového záznamu předat do formuláře výchozí data, která se mají uživateli zobrazit, bez možnosti editace a po odeslání formuláře uložit do databáze. Existující metodu Nette formulářů setDisabled() sice lze použít pro zobrazení needitovatelné hodnoty, ale hodnota se na server neodesílá, což v řadě případů nevyhovuje.
HTML nabízí atribut readonly, který ale nemá optimální funkci (může získat focus, což je matoucí) a nelze ho použít pro některé prvky (select nebo checkbox).
Ideální tedy je použít pro readonly prvky formulářů setDisabled(), který by ale odesílal hodnotu na server.
Nette formuláře nabízejí možnost kombinací dvou metod tuto funkci readonly k disablovanému prvku přidat:
// stávající řešení readonly:
$form->addText('name', 'Caption')->setDisabled()->setOmitted(false);
Tato kombinace je uvedena v dokumentaci zde: https://doc.nette.org/…rms/controls#…
Funkce je přesně taková, jakou potřebujeme, ale kód je obtížně čitelný a obtížně se pamatuje. Přitom každý by očekával, že Nette nabídne vedle setDisabled() i metodu setReadonly().
Navrhuji proto doplnit do třídy Nette\Forms\Controls\BaseControl tuto metodu následujícím způsobem:
public function setReadonly(bool $value = true): static
{
$this->disabled = $value;
$this->omitted = $value ? false : null;
return $this;
}
Kód ve formulářích bude dobře čitelný:
// disablovaný prvek:
$form->addText('name', 'Caption')->setDisabled();
// readonly prvek:
$form->addText('name', 'Caption')->setReadonly();
Funguje i vypnutí tohoto nastavení:
// nastavení readonly:
$form->addText('name', 'Caption')->setReadonly();
// vypnutí readonly:
$form->addText('name', 'Caption')->setReadonly(false);
Dalším benefitem je našeptávání v IDE.
Zde je příslušný PR do repozitáře nette/forms: https://github.com/…rms/pull/324
Editoval m.brecher (29. 3. 1:51)
- m.brecher
- Generous Backer | 863
@MajklNajt
kedy potrebuješ do databáze posielať dáta, ktoré tam už sú?
Např. když vytvářím nový článek, potřebuji uložit do created_at aktuální datum, a potřebuji datum zobrazit + blokovat editaci. U nového záznamu datum v databázi ještě není.
$form->addDate('created_at', 'Datum založení')
->setReadonly();
// .....
$form['created_at']->setDefaultValue(DateTime:from('now'));
Editoval m.brecher (29. 3. 18:06)
- David Grudl
- Nette Core | 8218
Mě se to pojmenování setReadOnly nelíbí, protože to nevystihnuje podstatu, tedy že rozdíl mezi readonly a disabled je v tom, že se položky neobjeví ve vrácených datech. Tady situaci komplikuje fakt, že disabled položky se v datech objeví, tedy Nette je tam předává i když je prohlížeče neposílají. Je to proto, že na fóru vzniklo milion dotazů „proč po odeslání nevidím disabled data“, jelikož moře programátorů nevědělo, jak disabled atribut funguje.
Ta metoda by se měla jmenovat spíš nějak jako setUnsent(), ale to je taky nesrozumitelné.
- m.brecher
- Generous Backer | 863
@DavidGrudl
readonly
Ideální název pro prvek fomuláře, který nelze editovat, ale data zobrazuje a odesílá. Klíčové slovo readonly je takto univerzálně používáno napříč programovacími jazyky. I PHP readonly properties nelze editovat, ale data poskytují.
disabled
HTML norma definuje, že atribut disabled blokuje editaci a funkci formulářového prvku. Data se nesmazávají ale naopak zobrazují. Data se ale neodesílají a zda se data v disablovaném prvku po submitu zobrazí záleží ne na normě html, ale na způsobu zpracování formuláře na serveru. Pokud po submitu do formuláře vykreslíme data, která formulář odeslal, tak z disablovaných prvků data zmizí – protože je formulář neodeslal. Takové chování nedává smysl – data vidím a po odeslání zmizí. Můžeme ale data po submitu u disablovaných prvků obnovit z default values a potom data nezmizí. To je žádoucí a užitečná funkce. V současné implementaci setDisabled() v nette formulářích to přesně takto je a je to tak správně.
Můžeme si vybrat ze dvou variant, které se obě hodí:
a) setDisabled() – data vidím, data nezmizí, nelze editovat, data se na
server NEODEŠLOU
b) setReadonly() – data vidím, data nezmizí, nelze editovat, data se na
server ODEŠLOU
Názvy mě připadají výstižné, v souladu s html normou i s všeobecným povědomím o disabled a readonly
nedostatky disabled v nette
V Nette formulářích se nastavením setDisabled() aktuální default value prvku smaže a data se tedy musí do prvku dodat později. To je v rozporu s normou html, která neříká nic o smazání dat, pouze se blokuje funkce prvku, blokuje se focus a neodesílají se data. Ale data která v prvku jsou odeslána ze serveru se zobrazí a nemažou. Proto bych myšlenku, že setDisabled() má aktuální hodnotu v prvku formuláře smazat úplně opustil. Jinak bude docházet k matoucím situacím, kdy někdy data v disabled prvku budou a jindy ne.
erased
Pokud by měl někdo potřebu mít metodu na spolehlivé smazání dat z formulářového prvku, můžeme přidat metodu setErased(), která data v prvku spolehlivě smaže, ale ne v závislosti na pořadí volání metod, ale vždy za každých okolností. Mazání dat bych ale z metody setDisabled() určitě navždy odebral.
Editoval m.brecher (7. 4. 0:53)
- David Grudl
- Nette Core | 8218
Aha, já v tom stávající řešení readonly:
četl
setOmitted()
místo setOmitted(false)
. Pak ten název
setReadOnly dává smysl.
- Taco
- Člen | 50
Hele, vám to tak funguje?
$form[‚foo‘]->setDisabled()->setOmitted(true); // není vůbec ve
výpise
$form[‚foo‘]->setDisabled()->setOmitted(false); // ve výpise je, ale
není vyplněn; aby ne, když se vůbec nepošle
Aktuálně to řeším tím, že si posílám tu hodnotu ještě jednou
v hidden, což pochopitelně není vůbec hezké.
Nebo si to nastavuju pomocí setDefaultValue(), ale to si zase vynucuje, abych
tu entity vytahoval znova z databáze.
nette/forms v3.2.0
Editoval Taco (20. 4. 2:10)
- m.brecher
- Generous Backer | 863
@Taco
$form[‚foo‘]->setDisabled()->setOmitted(true); // není vůbec ve výpise
$form['foo']->setDisabled()->setOmitted(true) // nedává smysl
Výše uvedený kód – kombinace ->setDisabled() + ->setOmitted(true) nedává smysl, protože ->setDisabled() vyřadí $value inputu z $values odeslaného formuláře. ->setOmitted(true) dělá totéž
$form['foo']->setDisabled()->setOmitted(false) // funguje jako readonly
Výše uvedená kombinace funguje jako readonly nastavení na inputu, data vložená ->setDefaultValue() se zobrazí a nejdou editovat a jsou v odeslaných $values formuláře, ale za těchto dvou podmínek:
a) nejprve se na inputu zavolá ->setDisabled(), až
potom se volá ->setDefaultValue()
b) ->setDefaultValue() se musí zavolat před událostí
anchor formuláře
Pokud není splněno b) tak se může stát, že po submitu formuláře $value z disablovaného inputu zmizí.
Když je splněno a) i b), potom ->setDisabled()->setOmitted(false) i samotné ->setDisabled() funguje spolehlivě.
$form[‚foo‘]->setDisabled()->setOmitted(false); // ve výpise je, ale není vyplněn; aby ne, když se vůbec nepošle
Toto by mohl být případ, kdy není splněno pravidlo b)
@DavidGrudl v nedávném komentáři k disablování prvků uvedl, že default value lze nastavit u submitnutého formuláře „dokud neví že byl submitnutý“ tj. před událostí anchor https://forum.nette.org/…ba-framework#…
Takže to vypadá, že možnost udržet $value v disablovaném inputu a mít možnost readonly feature je možná nezamýšlený vedlejší efekt toho, že před anchorem formulář neví jestli byl submitnutý. Protože disabled prvek data skutečně neodešle a setOmitted(false) je do prvku dostane ze setDefaultValue(). Pokud by tam setDefaultValue() nebylo, nebo pokud setDefaultValue() zavoláme po anchoru, tak ani setOmitted(false) nepomůže.
Závěr:
Jakmile nějak pokročíme v setReadonly(), měli bychom přepracovat disablování prvků. Čím jsem si jistý je, že:
- setDisabled() by v žádném případě neměl $value v inputu mazat, protože toto mazání je k ničemu a způsobuje část problému.
- u disablovaného a readonly prvku by mělo být možné nastavit setDefaultValue() i v submitnutém formuláři a kdykoliv během životního cyklu formuláře, tedy např. i v události onRender[].
Já jsem dřív default values do formulářů plnil v onRender[] což si myslím je správné řešení, ale právě kvůli disablovaným prvkům jsem od toho upustil a krkolomě řeším jak naplnit formulář před anchorem, žádná událost před anchorem není. Dá se s tím žít, ale komplikuje to život. Mělo by se to vyřešit.
Mazání dat z inputů
Tato feature není moc často potřeba, ale jsou situace, kdy se hodí. Mazání dat ale musí být implementováno JINOU metodou než disablování. Potřebujeme mít obojí – 100% mazání a 100% disablování. Dnes to máme pohromadě v jedné metodě. Současně s přepracováním setDisabled() by se mohla přidat pro mazání speciální metoda např. setErased() aby bylo jasné, že vymaže data. Tato metoda by disablovala prvek jako setDisabled() a naíc by 100% vymazala $value z prvku.
Editoval m.brecher (20. 4. 15:21)
- Taco
- Člen | 50
m.brecher napsal(a):
$form['foo']->setDisabled()->setOmitted(false) // funguje jako readonly
Výše uvedená kombinace funguje jako readonly nastavení na inputu, data vložená ->setDefaultValue() se zobrazí a nejdou editovat a jsou v odeslaných $values formuláře, ale za těchto dvou podmínek:
a) nejprve se na inputu zavolá ->setDisabled(), až potom se volá ->setDefaultValue()
b) ->setDefaultValue() se musí zavolat před událostí anchor formulářePokud není splněno b) tak se může stát, že po submitu formuláře $value z disablovaného inputu zmizí.
Když je splněno a) i b), potom ->setDisabled()->setOmitted(false) i samotné ->setDisabled() funguje spolehlivě.
$form[‚foo‘]->setDisabled()->setOmitted(false); // ve výpise je, ale není vyplněn; aby ne, když se vůbec nepošle
Toto by mohl být případ, kdy není splněno pravidlo b)
Tak jsem si to právě vyzkoušel, a je to trochu jinak.
Ano, podmínka a) musí být splněna, což je IMHO jen komplikace a nemá to
vliv na podstatu věci.
Podmínka b) neplatí. Nezáleží na tom.
Podstata věci je, se musí zavolat ->setDefaultValue() vždy (což je ostatně logické vzhledem k tomu, jak funguje html). Na tom jsem se spálil já, protože jsem si navykl plnit formulář jen jednou při inicializaci. No, tak v tom případě to s volbou setDisabled() dělat nemůžu.
Poznámka: disablování prvků by se mělo upravit tak, aby fungovalo spolehlivě 100% za všech okolností, aby to člověk použil a nemusel řešit, že různé věci okolo smí nebo nesmí. Jakmile nějak pokročíme v setReadonly(), tak bych se na tento problém podíval.
Se obávám, že bez nějakého hnusného hacku to nepůjde. Protože ten formulář tam tu hodnotu nemůže doplnit, když ji nemá = nepřišla v POST.
- David Grudl
- Nette Core | 8218
To chování vychází z toho, že každý prvek má jednu aktuální hodnotu. Když volám setDefaults() nebo setDefaultValue() a nevím, jestli formulář byl nebo nebyl odeslán, hodnota se přijme. Protože pokud se pak zjistí, že odeslán byl, přepíše se hodnotou odeslanou uživatelem.
A do tohoto vstupuje disabled.
Pokud je prvek disabled a zjistí se, že formulář byl odeslán, odeslaná hodnota se ignoruje a zůstává původní výchozí hodnota.
Pokud se ale prvek stane disabled až ve chvíli, kdy již byla přijata hodnota zvenčí, není jiná cesta než je smazat.
- m.brecher
- Generous Backer | 863
@Taco
Podmínka b) neplatí. Nezáleží na tom.
Před 4 měsíci se o disablování vedla diskuse, kde se tento problém zmiňoval: https://forum.nette.org/…ba-framework#…
Zkusil jsem si teď ověřit moje tvrzení b) na nette/forms v3.2.1:
$form->addText('disabled', 'Disablováno')->setDisabled();
$form->onRender[] = fn() => $form['disabled']->setDefaultValue('abc');
$form->onValidate[] = fn() => $form->addError('error'); // vyvolám chybu, aby formulář zůstal v submitu
a potvrzuji, že máš pravdu a bod b) neplatí. Super, jeden problém je pryč.
Se obávám, že bez nějakého hnusného hacku to nepůjde. Protože ten formulář tam tu hodnotu nemůže doplnit, když ji nemá = nepřišla v POST.
Samozřejmě že funkce readonly jak ji navrhuji vyžaduje, aby se si vývojář nastavil setDefaults() a to jak pro vykreslený, tak i pro submitnutý formulář. Protože readonly prvek by měl být realizován jako html disabled s dodatečným doplněním default values po submitu.
- m.brecher
- Generous Backer | 863
@DavidGrudl
Pokud se ale prvek stane disabled až ve chvíli, kdy již byla přijata hodnota zvenčí, není jiná cesta než je smazat.
Ano, máš pravdu.
Jak se o disabled diskutovalo zde: https://forum.nette.org/…ba-framework#… tak dnes jsem provedl krátký test a problém s mizejícími daty v disabled prvku je spolehlivě vyřešen. To je super.
Pokud by se přidala metoda setReadonly(), tak by se do dokumentace mohlo zmínit, že pro správnou funkci setReadonly() je potřeba řádně nastavit setDefaultValue() i pro submitnutý formulář, protože reálně se data neodesílají ale berou se právě z default hodnoty.
Editoval m.brecher (20. 4. 16:48)
- m.brecher
- Generous Backer | 863
@Taco
Nebo si to nastavuju pomocí setDefaultValue(), ale to si zase vynucuje, abych tu entity vytahoval znova z databáze.
pokud chceš pracovat s readonly prvky setDisabled()->setOmitted(false), tak musíš setDefaultValue() nastavovat jak u neodeslaného, tak i u odeslaného formuláře.
Já používám Nette Database Explorer a mám systém, kdy záznam se kterým formulář pracuje vždycky tahám z databáze a v presenteru ověřuji existenci záznamu (nemusí existovat) až potom vytvářím formulář.
tu entitu vytahoval znova z databáze
dospěl jsem k názoru, že záznam z databáze editovaný ve formuláři skoro vždycky potřebuji 3 x až 4 x použít a proto používám cachování v modelové třídě. Do modelové třídy nasetuji id záznamu a přes unifikovanou metodu getOne() tahám cachovaný ActiveRow záznam.
Zjednodušený kód:
class ArticleModel
{
public readonly int $id;
public readonly ?ActiveRow $row;
public function setup(?int $id, ....): void // setup v presenteru
{
$this->id = $id;
}
public function getOne(): ?ActiveRow
{
return $this->row ?? $this->row = $this->database->table('article')->get($this->id);
}
}
Záznam použiji:
- v presenteru – ověření existence záznamu
- ve formuláři pro naplnění selectů
- v setDefaults()
- v handleru formuláře – handler volá metodu modelové třídy (updateOne(), deleteOne()) a metoda modelové třídy interně pracuje s cachovaným $row záznamem
Velkou výhodou je zjednodušení kódu, nemusím řešit předávání $id, jednoduše se $id jednou předá do modelové třídy a záznam $row je k dispozici tolikrát, kolikrát je potřeba. Formuláře tak jak je stavím já vůbec $id nepotřebují a pracují jenom s modelovou třídou, kterou si vyžádají pomocí DI.
Pokud pracuješ s Doctrine, tak tam nevím jak duplicitu sql řešit, protože s Doctrine nemám zkušenosti.
- Taco
- Člen | 50
m.brecher napsal(a):
@Taco
Se obávám, že bez nějakého hnusného hacku to nepůjde. Protože ten formulář tam tu hodnotu nemůže doplnit, když ji nemá = nepřišla v POST.
Samozřejmě že funkce readonly jak ji navrhuji vyžaduje, aby se si vývojář nastavil setDefaults() a to jak pro vykreslený, tak i pro submitnutý formulář. Protože readonly prvek by měl být realizován jako html disabled s dodatečným doplněním default values po submitu.
Což mě osobně přijde pěkně na prd. Když vidím metodu
setReadonly()
tak očekávám, že to bude jen pro čtení.
Podobně, když vidím, že je položka setDisabled()
, že je
disablovaná. V obou případech bych očekával, že hodnota přijde.
V případě disabled možná chybně.
Vzhledem k tomu, že nejde rozumě a bez kouzel zajistit, aby tam ta
hodnota, u disabled (a v důsledku ani u readonly), byla, tak bych na to
rezignoval. A nechal bych to na setHtmlAttribute("disabled")
.
Protože to alespoň jasně přenechává zodpovědnost za chování
na html.
- m.brecher
- Generous Backer | 863
@Taco
Což mě osobně přijde pěkně na prd. Když vidím metodu setReadonly() tak očekávám, že to bude jen pro čtení. Podobně, když vidím, že je položka setDisabled(), že je disablovaná. V obou případech bych očekával, že hodnota přijde. V případě disabled možná chybně.
Jak se od sebe setReadonly() a setDisabled() liší je potřeba si nastudovat. Html norma definuje atribut readonly, ten je ale špatně implementován. Html readonly prvky sice nejde editovat a odesílají value, ale mají focus, což je matoucí, a navíc nejdou nastavit na všech potřebných typech prvků.
To už je lepší aby framework měl vlastní implementaci readonly pomocí html atributu disabled, a počítat s tím, že do prvků musím dodat default values. Je ale určitě lepší používat setReadonly() než kombinaci setDisabled()->setOmitted(false). Líp to asi udělat nejde.
Editoval m.brecher (20. 4. 18:41)
- Taco
- Člen | 50
m.brecher napsal(a):
To už je lepší aby framework měl vlastní implementaci readonly pomocí html atributu disabled, a počítat s tím, že do prvků musím dodat default values.
Vzhledem k tomu, že jsem se na tom opakovaně spálil, tak mi to lepší nepřijde. Je to neintuitivní a nepřímočaré.
Představuji si, že když bych si funkcionalitu musel řešit sám pomocí setHtmlAttribute(), že by mě to trklo spíš. Dle hesla ať to FW dělá pořádně nebo vůbec.
- m.brecher
- Generous Backer | 863
@DavidGrudl
máš pravdu, že ohledně disabled prvků se v nette forms nic nezměnilo. Chyba tam ale skutečně je a konečně jsem vypátral, kde přesně. Disablovaný prvek po submitu formuláře při určité nepříznivé konstelaci skutečně data ztratí. Testoval jsem takovýto jednoduchý formulář:
$form->addText('disabled', 'Disablovaný')
->setDisabled()->setOmitted(false);
$form->addSubmit('send', 'Odeslat')
->onClick[] = $form->handleSend(...);
$form['disabled']->setDefaultValue('abcd'); // OK
$form->setDefaults(['disabled' => 'abcd']); // OK
$form->onRender[] = fn() => $form['disabled']->setDefaultValue('abcd'); // OK
$form->onRender[] = fn() => $form->setDefaults(['disabled' => 'abcd']); // NOT OK
$form->onValidate[] = fn() => $form->addError('is_submitted');
return $form;
V testovacím formuláři jsem zkoušel kombinovat nastavení default value před/po anchoru a metodou setDefaultValue() a setValues(). Vždycky jsem zkusil jeden řádek s nastavením default values a ve 3 případech to funguje OK a v jednom případě data po submitu zmizí. Problém má metoda setDefaults(), zatímco setDefaultValue() je OK.
Editoval m.brecher (20. 4. 21:20)
- Martk
- Člen | 661
OT: Jsem asi blbej, ale musím se zeptat. Proč zobrazovat datum vydání článku při vstoupení do formuláře, když datum je ještě neznámé? Když bude muset jet např. do nemocnice a pak vydá rozepsaný článek, tak najednou bude o 5 hodin starý a ne mezi novými, tam je rozhodující přece to kliknutí na odeslat. Pochopil bych ještě editovatelný čas a nemyslím to nějak zle, jen z přílišné zvědavosti se musím zeptat.
- Pavel Kravčík
- Člen | 1194
Martk napsal(a):
OT: Jsem asi blbej, ale musím se zeptat. Proč zobrazovat datum vydání článku při vstoupení do formuláře, když datum je ještě neznámé? Když bude muset jet např. do nemocnice a pak vydá rozepsaný článek, tak najednou bude o 5 hodin starý a ne mezi novými, tam je rozhodující přece to kliknutí na odeslat. Pochopil bych ještě editovatelný čas a nemyslím to nějak zle, jen z přílišné zvědavosti se musím zeptat.
Protože ho chceš nastavit do minulosti například. Abys měl každý měsíc třeba 2 články, ale píšeš je až v dubnu… protože jsi líný prase a chtěl si je napsat v lednu. :)
- Martk
- Člen | 661
Abych to upřesnil, tak se jedná o disabled prvek při vytváření nového článku, takže běžný uživatel to nedokáže nastavit do minulosti aniž by mu běžel od ledna nonstop počítač s otevřeným tabem na formuláři. Proto jsem na konci věty psal, že bych pochopil, kdyby to byl editovatelný čas :)
Ptám se na tohle: Např. když vytvářím nový článek, potřebuji uložit do created_at aktuální datum, a potřebuji datum zobrazit + blokovat editaci.
V tomto případě se totiž „aktuální datum“ bude lišit od data vytvoření článku. Článek píšeš 30 minut, něco akutního ti do toho vleze, rozepsaný článek ze včerejška apod. a najednou to je včerejší článek a to asi nebude úplně cíl autora. Takže se mi to zdá spíše jako bug než feature. Datum vytvoření článku by se měl přiřadit teprve až v onSuccess a zobrazit jako disabled v editaci.
Lépe už to asi nedokážu popsat a nerad bych to tady zaspamoval, spíše mě zajímal odpověď autora.
Editoval Martk (22. 4. 13:09)
- m.brecher
- Generous Backer | 863
@Martk
Proč zobrazovat datum vydání článku při vstoupení do formuláře, když datum je ještě neznámé?
setReadonly() alias pro setDisabled()->setOmitted(false) není ve skutečnosti realizován html atributem readonly, ale disabled a data po submitu si znovu natáhne z nějaké metody, kterou si vývojář implementuje. Takže pokud použiješ třeba:
public function getDeaultArticleData(): array
{
return [
'craeted_at' => new DateTime();
'state' => 'Draft',
];
}
Takže se ti neuloží přesně to datum, které je ve formuláři, ale datum skutečného uložení. Pokud uživatel píše článek o půlnoci, může se stát, že se mu uloží jiné datum, než vidí ve formuláři, to se ale bude lišit o jeden den.
Článek byl jenom příklad, readonly prvek můžeš použít, když budeš vystavovat fakturu, nebo nějaký dokument, který má algoritmem daný tvar, který vygeneruješ, chceš, aby ho uživatel viděl ale nemohl ho měnit. Tam všude readonly zjednodušuje život, nepoužije se moc často, ale jsou situace, kdy se hodí.
- m.brecher
- Generous Backer | 863
@Taco
Představuji si, že když bych si funkcionalitu musel řešit sám pomocí setHtmlAttribute(), že by mě to trklo spíš. Dle hesla ať to FW dělá pořádně nebo vůbec.
Myslím, že Nette Framework podceňuješ. Pokud totiž použiješ html atribut readonly nebo disabled místo setDisabled()/setReadonly(), může Ti kdokoliv do „readonly“ nebo „disabled“ prvku podstrčit cokoliv a pak se může stát, že si uživatel přidělí vyšší roli než na kterou nemá právo apod…
Testoval jsem zda Nette zabezpečuje setDisabled() prvky proti hacknutí a výsledky potvrdily, že poskytuje maximální zabezpečení a to jak disablované inputy, tak i disablované submitButtons. Pokud útočník podstrčí do disabled/readonly nějakou svoji nebezpečnou hodnotu, Nette Framework to ignoruje.
Další použití setReadonly() bude u input hidden, protože i když je input hidden skrytý, hacknout jde úplně stejně jako viditelný.
V dokumentaci Nette k disablovaným prvkům bohužel není žádná zmínka o existujícím skvělém zabezpečení proti útoku, ani varování před html „readonly“ nebo „disabled“ prvků https://doc.nette.org/…rms/controls#…
Editoval m.brecher (22. 4. 15:40)
- Martk
- Člen | 661
Nepochopeno z důvodu, že tady neustále čtu, že se mají data odesílat na server, takže z toho vyplývalo, že je potřeba hodnota od uživatele i když je zrovna neaktuální/nevalidní. Ale ve skutečnosti jen chceš nastavenou hodnotu od programátora a ne odeslanou. Díky za upřesnění.
Existující metodu Nette formulářů setDisabled() sice lze použít pro zobrazení needitovatelné hodnoty, ale hodnota se na server neodesílá, což v řadě případů nevyhovuje.
- setReadonly() – data vidím, data nezmizí, nelze editovat, data se na server ODEŠLOU
Editoval Martk (22. 4. 15:19)
- m.brecher
- Generous Backer | 863
@Martk
Nepochopeno z důvodu, že tady neustále čtu, že se mají data odesílat na server
Ano, napsal jsem to příliš stručně takže to mohlo být nesprávně pochopeno. Proto si i myslím, že by se disabled/readonly prvky měly v dokumentaci víc vysvětlit – jak fungují, že vyžadují setDefaultData() a jak je to se zabezpečením proti útoku viz: https://forum.nette.org/…epsit-funkci#…
- Taco
- Člen | 50
m.brecher napsal(a):
@Taco
Představuji si, že když bych si funkcionalitu musel řešit sám pomocí setHtmlAttribute(), že by mě to trklo spíš. Dle hesla ať to FW dělá pořádně nebo vůbec.
Myslím, že Nette Framework podceňuješ. Pokud totiž použiješ html atribut readonly nebo disabled místo setDisabled()/setReadonly(), může Ti kdokoliv do „readonly“ nebo „disabled“ prvku podstrčit cokoliv a pak se může stát, že si uživatel přidělí vyšší roli než na kterou nemá právo apod…
Já Nette rozhodně nepodceňuji.
Opakováním pro zdůraznění: Mě sejří, že nemůžu vytvořit formulář, kterému nastavím hodnotu jednou při inicializaci. S volbou setDisabled() to přestane fungovat. Nejen, že mi to tu hodnotu nevrátí, ale při první chybě se to celé rozbije. Vím jak funguje html, a vadí mi to u něj úplně stejně.
Výsledek v mém případě je ten, že metodu setDisabled nepoužívám vůbec, a řeším to pomocí CSS, nebo jinak.
Argumentace podstrkáváním mě přijde zcela scestná. Protože všechna data posílaná a získaná od klienta jsou potencionálně nebezpečná bez ohledu na disabled. Třeba aktuální problém: chci editovat záznam, a chci disablovat typ záznamu, protože ten se měnit nemůže. I kdyby si to uživatel na své straně upravil, tak mi to chcípne na validaci, kterou samozřejmě dělám. Takže disabled tak jak existuje je pro mě jenom komplikace.
- m.brecher
- Generous Backer | 863
@Taco
Mě sejří, že nemůžu vytvořit formulář, kterému nastavím hodnotu jednou při inicializaci.
s tím je nejlépe se smířit, protože např. pro select nebo radio prvky musíš také dodat data vždycky – při inicializaci i po submitu. Já formuláře dědím z abstraktního předka a ten naplní formulář daty automaticky a ručně řešit to nemusím. Když nastavím prvku setDisabled(), nemusím už dělat nic dalšího, protože data se tam plní automaticky vždycky. Je to celé rozsáhlé a OT tohoto vlákna tak jenom to hlavní:
V modelové třídě mám VŽDY tyto metody kterými plním data do formuláře:
public function getDefaultOne(): array
{
return [
'state' => 'Draft',
'created_at' => new DateTime()
];
}
public function getOne(): ?ActiveRow
{
return $this->database->table('article')->get($this->id);
}
Formulář dědí z abstraktního předka FormControl, který dědí z UI\Control. FormControl má v sobě mimo jiné tento kód:
abstract protected function build(): BaseForm;
public function startup(): void
{
if(isset($this->model) && $this->model->mode === Mode::Update && !$this->model->getOne()){
$this->error('Záznam nebyl nalezen');
}
// ......
$form = $this->build();
$this->addComponent($form, 'form');
// .......
$form->onRender[] = $this->setDefaultData(...); / zde se automaticky a vždycky naplní data
}
private function setDefaultData(BaseForm $form): void // vybere z modelu správnou metodu podle módu formuláře
{
match($this->model->mode){
Mode::Create => $form->setDefaults($this->model->getDefaultOne()),
Mode::Update => $form->setDefaults($this->model->getOne()),
default => throw .....,
};
}
Finální třídu formuláře podědíš z FormControl, implementuješ metodu build() která vytvoří formulář. Abstraktní předek se automaticky postará o naplnění formuláře daty a nemusíš psát hromadu nudného opakujícího se kódu. Potomek FormControl dělá kompletní servis formuláři, který má kompozicí vnořen ve svojí metodě. Obstará úkony, které v Nette aplikacích běžně dělá presenter nebo factory. Presentery, modelové třídy i finální formuláře mám jednoduché, protože ten kdo nejvíc pracuje a maká je abstraktní předek FormControl. Presenter v akci přidá finální třídu formuláře a spustí na ní startup().
Napsal jsem to sem proto, že si myslím, že problém nudného opakujícího se kódu vyřeší jedině nějaký systém a chytře vymyšlené abstraktní třídy, které by převzaly opakující se činnosti na svoje bedra.
Editoval m.brecher (23. 4. 1:55)
- Taco
- Člen | 50
m.brecher napsal(a):
@Taco
Mě sejří, že nemůžu vytvořit formulář, kterému nastavím hodnotu jednou při inicializaci.
s tím je nejlépe se smířit, protože např. pro select nebo radio prvky musíš také dodat data vždycky – při inicializaci i po submitu.
Select či Radio options nejsou value hodnoty. Tudíž je to něco jiného. Tudíž se s tím nesmiřuju :-)
Abys dobře pochopil sílu mého odporu, důsledně všude nepoužívám setDefaultValue() nikdy a vůbec. Formulář plním na jednom místě. Ze zásady :-)
Editoval Taco (23. 4. 2:15)
- Kamil Valenta
- Člen | 815
Pavel Kravčík napsal(a):
Martk napsal(a):
OT: Jsem asi blbej, ale musím se zeptat. Proč zobrazovat datum vydání článku při vstoupení do formuláře, když datum je ještě neznámé? Když bude muset jet např. do nemocnice a pak vydá rozepsaný článek, tak najednou bude o 5 hodin starý a ne mezi novými, tam je rozhodující přece to kliknutí na odeslat. Pochopil bych ještě editovatelný čas a nemyslím to nějak zle, jen z přílišné zvědavosti se musím zeptat.
Protože ho chceš nastavit do minulosti například. Abys měl každý měsíc třeba 2 články, ale píšeš je až v dubnu… protože jsi líný prase a chtěl si je napsat v lednu. :)
Pokud chci umožnit publikovat v minulosti, pak přece nebudu řešit readonly / disabled.
Naopak mně připadá nežádoucí, aby se disabled / readonly prvky na server odesílaly. Proč, když na ně uživatel nesmí sahat? Pokud si backend ta data neobstará jinak a sáhne pro ně do values, slepě důvěřuje, že uživatel ta data skutečně nezměnil. Jenže toho readonly / disabled zárukou není.
- m.brecher
- Generous Backer | 863
@KamilValenta
Pokud si backend ta data neobstará jinak a sáhne pro ně do values, slepě důvěřuje, že uživatel ta data skutečně nezměnil. Jenže toho readonly / disabled zárukou není.
Pokud použiješ html atributy disabled nebo readonly tak ano, jde to podstrčit/hacknout. Ale když použiješ metody Nette setDisabled() nebo setReadonly() (alias pro setDisabled()->setOmitted(false)) tak Nette proti podstrčení 100% chrání, jenom to není napsané v dokumentaci. Když se použije setDisabled()->setOmitted(false) tak se data jakoby „odesílají“, ale ve skutečnosti se neodesílají, ale znovu se vytáhnou ze setDefaultValue().
Položím otázku – věděl Jsi, že metoda setDisabled() poskytuje 100% ochranu proti podstrčení dat ??
- Kamil Valenta
- Člen | 815
m.brecher napsal(a):
tak se data jakoby „odesílají“
Pokud řešíme „jakoby odesílají“ vs. „odesílají“, tak je to
v pořádku.
Já myslel, že vlákno je o odesílaných datech na server, protože
v úvodním postu jsi napsal:
„Ideální tedy je použít pro readonly prvky formulářů setDisabled(),
který by ale odesílal hodnotu na server.“
A taková metoda imho potřeba není právě pro její nebezpečnost.
Položím otázku – věděl Jsi, že metoda setDisabled() poskytuje 100% ochranu proti podstrčení dat ??
Ano.
- m.brecher
- Generous Backer | 863
Ideální tedy je použít pro readonly prvky formulářů setDisabled(), který by ale odesílal hodnotu na server.
To jsem napsal blbě, myslel jsme tím „kdy by hodnota byla ve výsledku“ takže to zmátlo více lidí.
Ano.
Já ne, protože v dokumentaci to napsané není a tak jsem předpokládal, že případně podstrčená data si musím ošetřit sám. Takže setReadonly() postavené na setDisabled() je bezpečné na rozdíl od html readonly tak je to o důvod navíc proč tato nová metoda byla užitečná. Ale do dokumentace bych přidal podrobnější informace a) že setReadonly() je bezpečné, b) pro svoji funkci vyžaduje nastavení setDefaultValue() c) interně nepoužívá html readonly ale disabled.
Davidovi přijde název setReadonly() matoucí, protože by vývojáři mohli předpokládat, že použije html readonly a navrhuje jiný název, třeba preserveDefaultValue(), nebo setPreserve(). Mě se ale zdá, že jediný srozumitelný název je setReadonly(), protože funkce je „bezpečné readonly“. Nebo teď mě napadlo setSafeReadonly(), když se Davidovi setReadonly() nelíbí.
- jeremy
- Člen | 54
Já ne, protože v dokumentaci to napsané není a tak jsem předpokládal, že případně podstrčená data si musím ošetřit sám.
Deaktivace
prvku – Mně z dokumentace přijde jasný, že
setDisabled()
automaticky ochrání před podvržením dat. Je tam
vysvětlený téměr všechno o čem tu mluvíš. Vzhledem k tomu jak málo by
se něco takovýho stejně dalo využít, proč prostě v takovém případě
nenapsat o pár znaků navíc.
setReadOnly()
je matoucí, vzhledem k tomu jak
readonly
v html normálně funguje.
Ještě k tomu příkladu jak jsi uváděl před tím. Datum, které se
ukáže uživateli ve formuláři je pouze informace kdy právě uživatel
začal vytvářet příspěvek. Přijde mi jako špatný desgin takovou
informaci pojmenovat created at
, protože to nevystihuje co ta
informace říká.
Lepší by to bylo pojmenovat například draft started at
a jak se
formulář odešle, tak do databáze created_at
uložit opravdový
datum vytvoření příspěvku.
Popravdě bych řekl, že readonly
má málo kdy využití
v dobře navrženym formuláři.
Ps: Myslim, že to v dokumentaci bylo už před tím, ale nejsem si na 100% jistý. Každopádně bylo to tam ještě než David napsal, že neví proč by měl něco do dokumentace přidávat, protože mu to přišlo jasné.
Editoval jeremy (23. 4. 14:27)
- m.brecher
- Generous Backer | 863
@jeremy
Mně z dokumentace přijde jasný, že setDisabled() automaticky ochrání před podvržením dat.
Máš pravdu, bezpečnostní aspekty disabled a readonly jsou nyní v dokumentaci velmi dobře popsány. Už jen zbývá vyřešit název metody, která by zapouzdřila nyní používanou dvojici setDisabled()->setOmitted(false). Najít název, ze kterého by bylo jasné, že se jedná o bezpečnou variantu k nezabezpečenému html readonly.
- jeremy
- Člen | 54
Já bych to nechal jako setDisabled()
protože to pořád je
disabled prvek. Pokud na zkratce pro
setDisabled()->setOmitted(false)
tak trváš, tak mi jako
nejlepší nápad přijde přidat parameter: bool $preserveDefault
→
setDisabled(bool $value = true, bool $preserveDefault = false)
.
Pak by použití bylo: setDisabled(preserveDefault: true)
.
(Popřípadě to zkrátit jen na bool $preserve
)
- Kamil Valenta
- Člen | 815
To skutečně každá dvojice metod, ze které je na první pohled jasný význam, potřebuje wrapper metodu, kde k názvu vs. významu potřebujeme 2 vlákna fóra a upravenou dokumentaci?
Pokud je navíc ojedinělá využitelnost pro pár lidí, proč si takový wrapper nedáte do společného předka pro Form?
- m.brecher
- Generous Backer | 863
@KamilValenta
Pokud je navíc ojedinělá využitelnost pro pár lidí, proč si takový wrapper nedáte do společného předka pro Form?
Metoda setReadonly() by se měla přidat do BaseControl, což je abstraktní třída, ze které dědí formulářové prvky. V PHP je možné do abstraktní třídy přidat metodu, která by byla přítomná v odvozených třídách jedině přímým zápisem do kódu třídy.