Formulare: jak bez znevalidneni formulare neprovest onSubmit, zrusit fallback na prvni submit tlacitko

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

Scenar s prikladem, o kterem jsme se bavili na posledni sobote po uxcampu:

Mame formular, ve kterem budeme chtit specialni SubmitButton, ktery bude pridavat do formulare nove prvky. Neni problem si k tlacitku zaregistrovat handler pro udalost onClick, ktery do formulare dalsi prvky prida, ale v takovem pripade jiz nebudeme chtit, aby doslo k provedeni formularove udalosti onSubmit. Prisli jsme na dva zpusoby jak toho docilit, ani jeden z nich se nam nezda idealni:

  • formular / nejaky z form. prvku invalidovat zavolanim metody addError
  • v handleru onClick vynulovat (nastavit na prazdne pole) zaregistrovane handlery pro formularovou udalost onSubmit

prvni pripad neni semanticky spravne, protoze formular v podstate nevalidni neni – jen se nejakym zpusobem zmenil jeho stav, druhy asi neni uplne cisty, protoze sahame nekam, kam bychom zrejme nemeli…

Dale pripominam, ze jsme se bavili o tom, jestli by nestalo za to zmenit soucasne chovani formularu – v krajnim pripade se za tlacitko, ktere odeslalo formular povazovano prvni nalezene.

V souvislosti s timto prikladem me jeste napadlo, jestli by se nemohla k formularum pridat udalost onAnchored, ktera by se volala ve chvili, kdy by bylo mozne pouzit metodu getHttpData (bez rozdilu, zda jde o Form nebo AppForm)

Nasleduje kod, na kterem je situace zhruba popsana, radi se dozvime jak problem resi ostatni.

class TestItem extends TextInput
{

	private $removeButton;

	public function __construct()
	{
		parent::__construct();
	}

	/**
	 * Vraci textovou reprezentaci cesty k formularovemu prvku se suffixem _removeButton
	 * @return string
	 */
	public function getRemoveButtonName()
	{
		return str_replace(self::NAME_SEPARATOR, '_', $this->lookupPath('\Nette\Form', TRUE)) . '_removeButton';
	}

	protected function attached($form)
	{
		parent::attached($form);
		if ($form instanceof Form)
		{
			// Vytvorime tlacitko pro odebrani prvku
			$removeButton = new SubmitButton('Odeber');
			$removeButton->onClick[] = array($this, 'onClickRemoveButton');
			$removeButton->onInvalidClick[] = array($this, 'onClickRemoveButton');
			$removeButton->setValidationScope(NULL);

			// Pripojime tlacitko na formular
			$form->addComponent($removeButton, $this->getRemoveButtonName());

			$this->removeButton = $removeButton;
		}
	}

	protected function detached($form)
	{
		if ($form instanceof Form)
		{
			// Odpojime tlacitko z formu
			$form->removeComponent($this->removeButton);
		}
		parent::detached($form);
	}

	public function onClickRemoveButton(SubmitButton $removeButton)
	{
		$form = $this->getForm();
		$this->getParent()->removeComponent($this);

		// Na tomto miste bych rad zabranil onSubmitu formulare

		// Bud muzu nastavit chybu, to ale neni semanticky spravne
		$form->addError('Polozka byla odebrana.');

		// Nebo vynuluju handlery onSubmit na formulari - to je trochu hack
		$form->onSubmit = array();
	}

	public function getControl()
	{
		$form = $this->getForm();
		return Html::el('div')
			->add(parent::getControl())
			->add($form[$this->getRemoveButtonName()]->getControl());
	}
}



class TestForm extends AppForm
{
	const CONTAINER_NAME = 'container';
	const ITEM_DESC_NAME = 'itemDesc';

	public function __construct(IComponentContainer $parent = NULL, $name = NULL)
	{
		parent::__construct($parent, $name);
		$this->setUp();
	}

	protected function attached($presenter)
	{
		parent::attached($presenter);

		// Odeslana data mam u AppForm k dispozici az po pripojeni k presenteru
		if ($presenter instanceof Presenter)
		{

			// Nebylo by lepsi mit moznost pouzit napr callback onAnchored?
			$data = $this->getHttpData();

			// Pridame polozky do konejneru
			if ($this->isSubmitted() && isset($data[self::CONTAINER_NAME]))
			{
				foreach($data[self::CONTAINER_NAME] as $name => $value)
				{
					$testItem = new TestItem();
					$this[self::CONTAINER_NAME]->addComponent($testItem, $name);
				}
			}

		}
	}


	protected function setUp()
	{
		$this->addContainer(self::CONTAINER_NAME);
		$this->addText(self::ITEM_DESC_NAME, 'Hodnota pridane polozky');

		$addButton = $this->addSubmit('addItem1', 'Pridej polozku');

		$addButton->setValidationScope(NULL);
		$addButton->onClick[] = array($this, 'onClickAddButton');
		$addButton->onInvalidClick[] = array($this, 'onClickAddButton');

		$this->addSubmit('send', 'Odeslat formular');

		$this->onSubmit[] = array($this, 'onSubmitForm');
		$this->onInvalidSubmit[] = array($this, 'onInvalidSubmitForm');
	}

	public function onSubmitForm(Form $form)
	{
		die('onSubmit');
	}

	public function onInvalidSubmitForm(Form $form)
	{
		die('onInvalidSubmit');
	}

	public function onClickAddButton(SubmitButton $addButton)
	{
		$testItem = new TestItem();

		// Kolik mame polozek TestItem v kontejneru?
		$count = count($this[self::CONTAINER_NAME]->getComponents(FALSE, 'TestItem'));

		// Pridame polozku do kontejneru
		$this[self::CONTAINER_NAME]->addComponent($testItem, $count);

		// Nastavime hodnotu
		$testItem->setValue($this[self::ITEM_DESC_NAME]->getValue());

		// Vynulujeme hodnotu pole pro zadani hodnoty nove vytvareneho prvku
		$this[self::ITEM_DESC_NAME]->setValue(NULL);

		// Na tomto miste bych rad zabranil onSubmitu formulare

		// Bud muzu nastavit chybu, to ale neni semanticky spravne
		$addButton->addError('Polozka byla pridana.');

		// Nebo vynuluju handlery onSubmit na formulari - to je trochu hack
		$this->onSubmit = array();

	}


}
redhead
Člen | 1313
+
0
-

Co se týče toho prvního, tak v onSubmit metodě můžeš kontrolovat „neodeslanost“ tím přidávacím tlačítkem:

public function onFormSubmitted($form)
{
	if(!$form['pridavaci_tlacitko']->isSubmittedBy())
	{
		//vykonani pri odeslani
	}
}

Takhle se vyhneš vykonáním příkazů při submit..

honza.trtik
Člen | 10
+
0
-

redhead napsal(a):

Co se týče toho prvního, tak v onSubmit metodě můžeš kontrolovat „neodeslanost“ tím přidávacím tlačítkem:

public function onFormSubmitted($form)
{
	if(!$form['pridavaci_tlacitko']->isSubmittedBy())
	{
		//vykonani pri odeslani
	}
}

Takhle se vyhneš vykonáním příkazů při submit..

To ale neni reseni, pokud chci vytvorit znovupouzitelnou komponentu, ktera nezavisi na konkretnim formulari (viz TestControl::onClickRemoveButton v prikladu). Na formulari navic muze byt zaregistrovan libovolny pocet handleru onSubmit, ktere nemusim mit vzdy plne pod kontrolou…

mlha
Člen | 58
+
0
-

Mě osobně se v Nette velmi nelíbi ten fallback na první submit button.
Při odeslání formuláře na straně prohlížeče to chápu.
V PHP bych ale měl vždy vědět zda byl form odeslán tím kterým tlačítkem.

Příklad:

  • Odešlu formulář tlačítkem A,

V PHP kdy se formulář vytváři:

  • vytvořím tlačítko A
  • test zda bylo tl. A použito – aktuálně tento test vždy projde, protože je tl. A jediné
  • konstrukce další části formuláře s tlačítkem B

Nově by tedy mohla nastat situace, že formulář tvrdí, že je odeslán, ale neví o submitu, kterým se tak stalo (odesílací submit nebyl přidán do formu). Ale toto mi přijde zcela v pořádku.

Editoval mlha (12. 10. 2010 12:13)