InstantClientScript & validate{name}
- xificurk
- Člen | 121
Pokud chci mít na stránce více komponent stejného druhu (např.
PollControl z extras), které obsahují formulář, nastává problém
s kolizí id jednotlivých inputů a validačního skriptu. První problém se
dá řešit přidáním
FormControl::$idMask = 'frm-' . $this->getUniqueId() . '-%s-%s';
do továrničky na formulář. Druhý problém je už trochu horší oříšek a
krom nějaké vlastní ClientScrtipt třídy nevidím jednoduché řešení.
Otázka: Je nějaký závažný důvod, proč se nepoužívá obecně pro id inputů a validačního skriptu UniqueId místo Name? Např. proč se v třídě ClientScript používá https://github.com/…ntScript.php#L66 místo zakomentované verze?
Tento problém nastává také, pokud mám více (různých) komponent,
které obsahují formulář – nevidím důvod pro vymýšlení speciálního
názvu pro jediný formulář komponenty – používám prostě v komponentě
továrničku createComponentForm($name)
a v šabloně
{control form}
, což v konečném důsledku vede k tomu, že se
tlučou právě id jednotlivých prvků formuláře a validační skripty.
- xificurk
- Člen | 121
Oba problémy jsem prozatím vyřešil pomocí vlastního renrederu…
<?php
/**
* Initializes form.
* @return void
*/
protected function init()
{
parent::init();
FormControl::$idMask = 'frm-' . $this->form->getParent()->getUniqueId() . '-%s-%s';
}
/**
* Returns JavaScript handler.
* @return mixed
*/
public function getClientScript()
{
if ($this->clientScript === TRUE) {
$this->clientScript = new InstantClientScript($this->form);
$name = $this->form->getParent()->getUniqueId() . Form::NAME_SEPARATOR . $this->form->getName();
$name = ucfirst(strtr($name, Form::NAME_SEPARATOR, '_'));
$this->clientScript->validateFunction = 'validate' . $name;
}
return $this->clientScript;
}
?>
Ale stejně si myslím, že by bylo lepší to řešit nějak systémově v Nette.
- Honza Marek
- Člen | 1664
Někde jsem viděl resit. dg Nevím ani co to dělá ani kdo má na to právo :)
Jinak by bylo fajn mít taky něco jako FormControl::$nameMask, kvůli dalšímu problému
- mcmatak
- Člen | 504
ještě mi není jasné, kde se tohle volá?
/**
* Initializes form.
* @return void
*/
protected function init()
{
parent::init();
FormControl::$idMask = 'frm-' . $this->form->getParent()->getUniqueId() . '-%s-%s';
}
to v podstatě změní způsob tvorby formulářů úplně všude ne? nejen v rámci toho jednoho formuláře, teda asi je to v pořádku no, ale to dáváš kam tedy? do basepresenteru?
- Honza Marek
- Člen | 1664
Předpokládám, že to má v rendereru. Čili to změní před vykreslení pro všechny formuláře. Před dalším vykreslením zase pro všechny formuláře. Ale vykreslovat se bude jen ten aktuální, takže to bude fungovat.
- mcmatak
- Člen | 504
Chceš říct, že kvůli tomu je opravdu nutné dědit render a každému formu nastavovat render? To to můžu strčit do basepresenteru a je ne?
Každopádně ještě si říkám, že pokud nějaký javascript očekává nějaké konzistentní názvy ideček, tak by unikátní název mohl být docela problém??
- Honza Marek
- Člen | 1664
mcmatak napsal(a):
Chceš říct, že kvůli tomu je opravdu nutné dědit render a každému formu nastavovat render? To to můžu strčit do basepresenteru a je ne?
Můžeš si to strčit do BaseFormu. Kvůli $this->form->getParent()->getUniqueId() to nejde dát do BasePresenteru, pač to má v každém formu jinou hodnotu.
- mcmatak
- Člen | 504
ještě malý problém by mohl být v tom, že 0.91 aktuální stable má funkci getUniqueId takto
<?php
/**
* Returns a name that uniquely identifies component.
* @return string
*/
final public function getUniqueId()
{
return '';
}
?>
vlastně i aktuální verze na gitu používá final a vrací prázdný výsledek??
Editoval mcmatak (5. 11. 2009 12:07)
- mcmatak
- Člen | 504
takže ok, už sem pochopil jak by byl ten hack, ten tedy funguje! a to samozřejmě za podmínky, že si jednotlivé komponenty pojmenovávám jinak
<?php
public function createComponent($name) {
if (preg_match("/^loginForm\_/", $name)) {
return new LoginControl($this, $name);
if (preg_match("/^addToBasket/", $name)) {
return new addToBasket($this, $name);
} else {
return parent::createComponent($name);
}
}
a potom používám jako
{control addToBasket1}
{control addToBasket5}
{control addToBasket56}
{control addToBasket3}
{control loginForm_Quick}
{control loginForm_Page}
?>
a samořejmě za podmínky, že form má nastavený renderer na můj vlastní viz. ten hack odkaz
Editoval mcmatak (5. 11. 2009 16:16)
- mcmatak
- Člen | 504
Problém teda vidím hlavně v tom, že už začlenění komponenty do projektu není tak nezávislé, např. co když mám na jednotlivé idečka navěšený javascript. V případě, že komponentu přesunuji do jiného projektu, nebo do jiné komponenty, tak se idečka změní a javascript přestane fungovat.
- mcmatak
- Člen | 504
jelikož s tím stále bojuji, zjistil jsem, že idečka komponent se při xificurkově řešení nějak zpožďují, až při druhém renderování stejné komponenty dojde k úpravě ideček
jediné co pomohlo je toto řešení
<?php
class BaseForm extends AppForm {
public function __construct(IComponentContainer $parent = null, $name = null) {
parent::__construct($parent, $name);
FormControl::$idMask = 'frm-'.$this->getParent()->getUniqueId().'-%s-%s';
$this->setRenderer(new BaseConventionalRenderer());
}
}
/**
* Initializes form.
* @return void
*/
protected function init()
{
parent::init();
}
/**
* Returns JavaScript handler.
* @return mixed
*/
public function getClientScript()
{
if ($this->clientScript === TRUE) {
$this->clientScript = new InstantClientScript($this->form);
$name = $this->form->getParent()->getUniqueId() . Form::NAME_SEPARATOR . $this->form->getName();
$name = ucfirst(strtr($name, Form::NAME_SEPARATOR, '_'));
$this->clientScript->validateFunction = 'validate' . $name;
}
return $this->clientScript;
}
?>
při zapsání té změny FormControl do rendereru je už pozdě, nevím čím to je, ale dělá mi to tak vždycky
- mcmatak
- Člen | 504
takže ANI TO NEBUDE FUNGOVAT!!!
<?php
class Front_TestPresenter extends Front_BasePresenter {
public function createComponent($name) {
if (preg_match("/^addToBasket\_/", $name)) {
return new AddToBasketControl($this, $name);
} else {
return parent::createComponent($name);
}
}
public function renderDefault() {
$this->setLayout(FALSE);
$this['addToBasket_2']['form']->addText("varianta", "Varianta:", 20);
}
}
a následně v šabloně
{$control['addToBasket_1']['form']}
{$control['addToBasket_2']['form']}
{$control['addToBasket_3']['form']}
?>
tak se vše komplet rozsype, idečka se budou duplikovat, budou přeházená atd.
- mcmatak
- Člen | 504
podle mne jediné řešení je
nahradit ve FormControl
<?php
/**
* Returns control's HTML id.
* @return string
*/
public function getHtmlId()
{
if ($this->htmlId === FALSE) {
return NULL;
} elseif ($this->htmlId === NULL) {
$this->htmlId = sprintf(self::$idMask, "-".$this->getForm()->getParent()->getUniqueId()."-".$this->getForm()->getName(), $this->getHtmlName());
$this->htmlId = str_replace(array('[', ']'), array('-', ''), $this->htmlId);
}
return $this->htmlId;
}
?>
- David Grudl
- Nette Core | 8218
Tohle by nejspíš chtělo v prvé řadě kompletně přepsat InstantClientScript.
Oddělit samostatně validátory a „efekty“:
var nette = {
forms: {
validators: {
RadioList_filled: function (...) {
return ...;
},
...
},
showError: function(element, message) {
if (element) element.focus(); alert(message);
},
toggle: function(element, visible) {
if (element) element.style.display = visible ? "" : "none";
}
}
};
podobný objekt by se vytvořil jen jednou a bylo by možné ho umístit do samostatného skriptu. Sice InstantClientScript už by nebyl tak moc instantní, ale to nevadí.
Podobně by se zapouzdřili validace pro jednotlivé formuláře, ideálně
tak, aby nebyly závislé přímo na ID prvků, ale na name
. Ve
hře by bylo ID pouze formuláře. Tím by se celá validace stala v podstatě
nezávislou na ID.
Nechcete někdo zkusit napsat takový ukázkový JavaScript? Podle něj bych pak přepsal InstantClientScript.
- redhead
- Člen | 1313
David Grudl napsal(a):
Nechcete někdo zkusit napsat takový ukázkový JavaScript? Podle něj bych pak přepsal InstantClientScript.
Tak jsem se teda o něco pokusil. Nejede to sice na nette forms, ale běžný html. Je to dost nástřel, ale funguje. Zatím jsem zkusil 4 pravidla (filled, max_length, equals a email).
Jede to přes name polí místo ideček (pouze form má id) a je to rozděleno na „statické“ vyhodnocení validace a na specifické operace pro daný control (conditionOn a další zanořování).
Řekněte jesli tohle je way.
Editoval redhead (24. 11. 2009 23:29)
- Honza Marek
- Člen | 1664
David Grudl napsal(a):
Nechcete někdo zkusit napsat takový ukázkový JavaScript? Podle něj bych pak přepsal InstantClientScript.
Nejlepší ukázkový javascript bude asi jQuery Validation plugin ;) Kdyby se Nette zvládlo s tímhle skamarádit, tak by to bylo fakt hodně dobré.
Editoval Honza Marek (24. 11. 2009 23:46)
- David Grudl
- Nette Core | 8218
Taky jsem se o něco pokusil, je to v nightly build verzi. Jde sice zatím o pouhý mezikrok, který se odprostil od závislosti na HTML ID a používání globálních funkcí, ale mělo by to být plně funkční.
Zatím to nemám vyzkoušené, ale už by nad podobným JS výstupem mohlo jít napsat nádstavbu, která by prováděla hezčí live validaci, třeba za asistence jQuery.
Jestli by šlo využít jQuery Validation plugin netuším, vůbec ho neznám.
- David Grudl
- Nette Core | 8218
Jen drobná ukázka pro inspiraci, jak by šlo propojit aktuální InstantClientScript s jQuery a vytvořit plnohodnotnou live validaci:
Natáhneme jQuery a formulář zobrazíme klasickou cestou:
<script src="jquery.js"></script>
<?php echo $form ?>
Vytvořím obslužný skript pro live validaci:
var liveValidate = function(event) {
var error = nette.validateControl(this);
if (error) { // došlo k chybě, error obsahuje chybovou zprávu
$(this).after(error); // nějak ji umístíme za položu
} else {
// bez chyby, zpravu nějak zrušíme
}
}
A nakonec všem prvkům, které mají nějaká validační pravidla, nastavíme pomocí jQuery obsluhu vhodných události:
var form = document.forms[0]; // form bude náš formulář
for (var i in nette.getFormValidators(form)) {
$(form[i]).change(liveValidate).blur(liveValidate).keyup(liveValidate);
}
Výhoda tohoto řešení je v tom, že není potřeba nic konfigurovat na straně aplikace, přesněji řečeno není potřeba zasírat presentery JS kódem.
- redhead
- Člen | 1313
vyzkoušel jsem a funguje to fakt pěkně, ovšem pár připomínek:
1) BUG
Nějakým způsobem se tam pomíchají value a regulární výraz u REGEX operací, firebug hlásí:
nette.getValue(form.user).test is not a function
renderuje se tam místo RegExpu funkce nette.getValue..
BUG FIX
//InstantClientScript.php line: 232
return "res = $arg.test(val);";
- co se týče Live Form Validace, tak je tam nepatrný rozdíl, který asi jednoduše překódovat nepůjde a to, že například při validaci 1. hesla se validuje i 2. heslo (pokud už s ním uživatel něco dělal – kliknul, napsal, …). Polopatě: změním 1. heslo (napíšu špatně své heslo), pak 2. heslo (napíšu dobře své heslo) ⇒ 2. bude hlásit že se neshodují, když změním 1. heslo na to správné, 2. bude stále hlásit že se neshodují. Doufám, že je to srozumitelné :D . Já to řešil tak, že jsem pro každou operation condition volal validaci i odkazovaných controlů
- a mám ještě návrh oddělit funkci na zobrazení/skrytí chyby do svého „uložiště“:
<script>
nette.validateForm = function(sender) {
var form = sender.form || sender;
var validators = this.getFormValidators(form);
for (var name in validators) {
var error = validators[name](sender);
if (error) {
this.onError(form[name], error) //NEW
return false;
} else { //NEW
this.onHideError(form[name])
}
}
return true;
}
//funkce
nette.onError = function(control, error) {
alert(control);
control.focus();
}
nette.onHideError = function(control) { }
</script>
Tyhle funkce se pak dají snadněji měnit pouze přepsáním té funkce.. A v IE by se měl focus volat po alertu, protože to evidentně nechápe.
- rokerkony
- Člen | 122
redhead napsal(a):
vyzkoušel jsem a funguje to fakt pěkně, ovšem pár připomínek:
1) BUG
Nějakým způsobem se tam pomíchají value a regulární výraz u REGEX operací, firebug hlásí:
nette.getValue(form.user).test is not a function
renderuje se tam místo RegExpu funkce nette.getValue..
BUG FIX
//InstantClientScript.php line: 232 return "res = $arg.test(val);";
Připomínka že BUG stále platí… a fix funguje tak prosím o přesunutí do distribuce…