InstantClientScript & validate{name}

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

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.

Honza Kuchař
Člen | 1662
+
0
-

Jsem taky pro používání té zakomentované verze. Nebo to má nějaký háček?

xificurk
Člen | 121
+
0
-

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.

mcmatak
Člen | 490
+
0
-

David o tomhle problému ví, uháním ho kudy chodím, ale přimlouvám se, aby se na to podíval!!

Jak je to kouzelné heslo, které přizve dgx k přečtení příspěvku? Nemá tady nějakého robota, který by projížděl jestli se o něm nepíše? Třeba stačí napsat „David Grudl je …“

Davide?

Ondřej Brejla
Člen | 746
+
0
-

Myslíš…toto? :-)
řešit DG

Honza Marek
Člen | 1664
+
0
-

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

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

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

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??

xificurk
Člen | 121
+
0
-

jj, jak jsem psal už výše, je to ve vlastním rendereru, který dědí od ConventionalRenderer. Tak jako tak jsem už vlastní renderer používal, protože jsem potřeboval globálně změnit pár drobností, takže mě to moc netrápí.

Honza Marek
Člen | 1664
+
0
-

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

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

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

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

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

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

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

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.

_Martin_
Generous Backer | 679
+
0
-

David Grudl napsal(a):

Bohužel InstantClientScript neřeší problém, kdy mám ve dvou různých komponentách formuláře stejného jména a do HTML se mi tak duplikují ID atributy.

P.S. Čímž nepopírám užitečnost změny InstantClientScriptu.

redhead
Člen | 1313
+
0
-

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

Ukázka tu

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

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

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

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.

_Martin_
Generous Backer | 679
+
0
-

Zdá se, že to chodí. Skvěle =) Davide, nešla by ta ID řešit takhle?

mcmatak
Člen | 490
+
0
-

stačilo by tohle viz tento článek

_Martin_
Generous Backer | 679
+
0
-

mcmatak napsal(a): …

Tvé řešení nepočítá s tím, že formulář nemusí být součástí MVC. Jinak je vlastně stejné=)

redhead
Člen | 1313
+
0
-

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);";

  1. 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ů
  2. 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
+
0
-

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…

redhead
Člen | 1313
+
0
-

Fix David commitoval, aspoň co mi to posílal, ale nezkoušel jsem, zkus novou nightly build