Posloupnost onSuccess vs. vytvoření komponenty formuláře

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

Ahoj, potřeboval bych poradit s problémem s nesprávně seřazenými položkami z databáze, když ukládám formulář AJAXem. Celý formulář je komponenta a zabalený do snipettu objednav a v onSuccess() provádím $this->redrawControl('objednav');

Kreslím položky formuláře na základě jejich sloupce priorita v tabulce. Položky lze pomocí drag&drop přesouvat a měnit pořadí. Pomocí JS nastavuji v hidden poli priorita správné pořadí všech položek, sestupně 3… 2… 1.

Uložení hodnot po odeslání formuláře probíhá v onSuccess metodě. Po uložení se hodnoty v priorita změní správným způsobem, jenže formulář a položky se vykreslí dle stavu před uložením.

Nevím jak v Nette funguje posloupnost vzniku komponent a zprocesování onClick / onSuccess, ale jeví se mi to tak, že dojde napřed k vytvoření komponenty formuláře – createComponentObjednavkaForm() (kde se načtou data z DB v původním stavu, před zavoláním UPDATE sql) a teprve až pak se provede uložení změn do tabulky v DB – ObjednavkaFormSuccess(). Tím pádem JSON data, vrácená AJAXem, potažmo pořadí položek, mají původní stav, před uložením (ověřeno). Pokud pak stránku dodatečně obnovím F5, potom je vše v pořádku.

Domnívám se, že potřebuju v té onSuccess() metodě nějak znovu zavolat vytvoření komponenty formuláře, aby se znovu načetly data z tabulky po uložení. Nebo nějak změnit přímo objekt formuláře a ty komponenty v něm.

Data z DB se načítají přímo v té komponentě formuláře. Zkoušel jsem data načítat i v Action() a Render() metodě, ale nemělo to na nic vliv.

Poradí někdo, kde je háček? Už si absolutně nevím rady. Díky.

Editoval RadaR (7. 1. 2017 12:11)

ViPEr*CZ*
Člen | 811
+
0
-

Ano, formulář se nejprve sestaví. To aby Nette vědělo, která data z POST vůbec má zpracovávat a jak. Protože pak na druhou stranu v onSuccess používáte zajísté $form->getValules() a to by nemohlo právě fungovat.
Btw tohle by jste mohl vyřešit tak, že si form vykreslíte ručně. Prvky si seřadíte podle priorita. Jen to bude vyžadovat, aby DB věděla u každého řádku control name a musíte zajistit, aby toto name obsahoval form. Toť jedno možné řešení.

RadaR
Člen | 46
+
0
-

Values mi do onSuccess „přitečou“ parametrem z function ObjednavkaFormSuccess ($form, $values) ale to je asi jedno, jestli volám $form->getValues() nebo pracuji s $values. Ale chápu, kam míříte a uvedený důvod.

Můj formulář je celkem složitý a ručně už jej (pokud myslíme to samé?) kreslím. Obsahuje dva kontejnery (hlavičku a položky) přičemž položky obsahují ještě další kontejnery (technologie položek, těch může být 1:n).

Mohl byste prosím svoji radu trošku více rozepsat? Možná nějaký kousek kódu, pro příklad. Přiznám se, že to úplně nechápu.

A nešlo by nějak „vynutit“, na správném místě, znovuvytvoření té komponenty formuláře? Nebo těch položek v něm?

Hodím sem osekanou šablonu a presenter, snad se v tom půjde dát vyznat:

<?php
{block content}

<h1>Objednávka č. <strong>{$article->cobj}</strong></h1>

<hr>

{snippet objednav}

{form objednavkaForm}

{formContainer objednavka}
	{label id_zakaznik /}
	{input id_zakaznik class => 'form-control'}
	{input expres}{label expres /}
	{label poznamka_int /}{input poznamka_int class => 'form-control'}
{/formContainer}

<hr>

{formContainer objpol}
<h2>Položky <span class="btn btn-primary" id="product-add-new"><span class="glyphicon glyphicon-plus"></span> Přidat novou položku</span></h2>
<div class="products">

	{if count($formContainer->getComponents()) > 0}
	{foreach $formContainer->getComponents() as $idObjpol => $fieldObjpol}
		{formContainer $idObjpol}

			<!-- produkt -->
			<div class="product" id="objpol_{$idObjpol}" data-id="{$idObjpol}">
			<div class="handle"></div>
				{input dcera}
				{input priorita}

				{input deleteobjpol class => 'btn btn-danger btn-xs'}

				<div class="col-md-4">
				{formContainer vyrtechgroup_$idObjpol}
					{foreach $formContainer->getComponents() as $idVyrtech => $fieldVyrtech}
						{formContainer $idVyrtech}
							<div class="row technology" data-id="{$fieldVyrtech['id_vyrtech']->getHtmlId()}">
								<div class="col-md-5 technology-head">{label id_vyrtech /} {input id_vyrtech}</div>
								<div class="col-md-4">{input datum_vyhotoveni class => 'form-control'}</div>
								<div class="col-md-3"><div class="checkbox">{label hotovo /} {input hotovo}</div></div>
							</div>
						{/formContainer}
					{/foreach}
				{/formContainer}
				</div>
			<!-- /produkt -->

		{/formContainer}
	{/foreach}
	{/if}
	<div class="products-group empty"></div>
</div>
{/formContainer}

<div class="text-right">
{input btn_updateclose class => "btn btn-default"}
{input btn_update class => "btn btn-default"}
</div>

{/form}

{/snippet}
?>

Presenter a komponenta formu:

<?php
	protected function createComponentObjednavkaForm()
	{
		$form = new Form;

		$form->getElementPrototype()->class('ajax');	// přidá formuláři class="ajax"

		$form->setRenderer(new BootstrapVerticalRenderer);

		$objednavkaContainer = $form->addContainer("objednavka");

		$zakaznici = $this->dataManager->getZakaznik();
		if (count($zakaznici) > 0):
			foreach ($zakaznici as $values):
				$zakaznik[$values['id_zakaznik']] = $values['jmeno_prijmeni'];
			endforeach;
		else:
			$zakaznik[0] = 'neurčeno';
		endif;
		$objednavkaContainer->addSelect('id_zakaznik', 'Zákazník', $zakaznik)
				->setRequired(TRUE)
				->setDefaultValue($this->objednavkaRow->id_zakaznik);

		$objednavkaContainer->addCheckbox('expres', 'Expresní objednávka')
				->setDefaultValue($this->objednavkaRow->expres);

		$objednavkaContainer->addTextArea('poznamka_int', 'Poznámka - interní')
				->setDefaultValue($this->objednavkaRow->poznamka_int);


		// načtu si výrobní technologie
		$vyrtech = $this->dataManager->getVyrtech();


		// načtu si položky objednávky
		$rows = $this->dataManager->getObjpol($this->getParameter('id'));


		// formContainer pro položky objednávky
		$objpolContainer = $form->addContainer("objpol");
		foreach ($rows as $objpol):

			$fieldsObjpol = $objpolContainer->addContainer($objpol->id_objpol);

			$fieldsObjpol->addHidden('dcera', 'Dcera')
					->setDefaultValue($objpol->dcera);

			$fieldsObjpol->addText('priorita', 'Priorita')
					->setDefaultValue($objpol->priorita);

			$fieldsObjpol->addSubmit('deleteobjpol', 'Smazat')
					->onClick[] = [$this, 'DeleteObjPol'];


			$objtech = array();
			foreach ($objpol->related('objtech.id_objpol', 'objtech.id_objpol') as $item):
				$objtech[$item->id_vyrtech] = array('hotovo' => $item->hotovo, 'datum_vyhotoveni' => $item->datum_vyhotoveni);
			endforeach;

			// formContainer pro výrobní technologie
			$vyrtechContainer = $fieldsObjpol->addContainer("vyrtechgroup_". $objpol->id_objpol);

			foreach ($vyrtech as $item):
				$fieldsVyrtech = $vyrtechContainer->addContainer($item->id_vyrtech);

				$fieldsVyrtech->addCheckbox('id_vyrtech', $item->nazev)
						->setDefaultValue(isset($objtech[$item->id_vyrtech]) ? 1 : 0);

				$fieldsVyrtech->addText('datum_vyhotoveni', 'Datum vyhotovení')
						->setDefaultValue(isset($objtech[$item->id_vyrtech]['datum_vyhotoveni']) ? $objtech[$item->id_vyrtech]['datum_vyhotoveni']->format('d.m.Y') : '')
						->setRequired(FALSE)
						->setAttribute('data-date', '1')
						->setAttribute("placeholder", "dd.mm.rrrr")
						->addRule($form::PATTERN, "Datum musí být ve formátu dd.mm.rrrr", "(0[1-9]|[12][0-9]|3[01])\.(0[1-9]|1[012])\.(19|20)\d\d")
						->addFilter(function ($value) {	return trim($value) ? Nette\Utils\DateTime::from($value) : null; })
						->setNullable();

				$fieldsVyrtech->addCheckbox('hotovo', 'Hotovo')
						->setDefaultValue(isset($objtech[$item->id_vyrtech]) and $objtech[$item->id_vyrtech]['hotovo'] == 1 ? 1 : 0);
			endforeach;


		endforeach;


		$form->addSubmit('btn_update', 'Uložit objednávku');

		$form->addSubmit('btn_updateclose', 'Uložit a zavřít');

		$form->onSuccess[] = [$this, 'ObjednavkaFormSuccess'];

		return $form;
	}
?>

Editoval RadaR (7. 1. 2017 13:10)

F.Vesely
Člen | 368
+
0
-

Podle API dokumentace funkce getComponents() vrati ArrayIterator, ktery ma funkci uasort($cmp_function). Takze teoreticky neco jako toto, by mohlo fungovat, ale nemam to otestovane:

$formContainer->getComponents()->uasort(function($a, $b){
	return $a['priorita']->getValue() - $b['priorita']->getValue();
});
RadaR
Člen | 46
+
0
-

2 F.Vesely:

To vypadá hodně slibně! Velmi elegantně a jednoduše by to řešilo můj problém. Jenže bohužel to nic nedělá :( Kód jako takový proběhne bez chyb, ale když si vypíšu komponentu předtím a potom, pořadí se vůbec nezmění.

Provádím to v onSuccess metodě a zkoušel jsem to i mírně modifikovat, takto:

<?php
Debugger::barDump($form['objpol']->getComponents());
$form['objpol']->getComponents()->uasort(function($a, $b) {
	return ($a['priorita']->GetValue() > $a['priorita']->GetValue() ? 1 : -1);
});
Debugger::barDump($form['objpol']->getComponents());
?>

ale pořadí je v obou dumpech v TRACY stejné… Nepřehlédnul jsem něco? Předpokládám, že ->getComponents() vrací a pracuje přímo s instancí toho objektu, nevytváří kopii, že ano?

Zkusil jsem $test = $form['objpol']->getComponents() a v $test bylo pouze TRUE.

Díky za každé pošťouchnutí…

F.Vesely
Člen | 368
+
0
-

Nevim, jestli to vytvari primo kopii, ale rozhodne to nezmeni poradi v tom kontejneru. Musis to pouzit az pri vypisu, pokud tu funkci nechces mit v sablone, tak si ji uloz do promenne a tu si predej do sablony a pouzij.

RadaR
Člen | 46
+
0
-

Dal jsem na zkoušku tvůj kód do šablony. Umístil jsem jej před iteraci položek v kontejneru (úplnější šablona – viz můj post šablony výše), ale ani zde to bohužel nefunguje. Kód projde, chybu to nehlásí, ale na změnu pořadí to nemá žádný vliv.

Nejsem si jist, jestli v tom okamžiku, kdy se vykreslují data šablonu pro JSON, už jsou v šabloně ta správná data. Jestli se nepracuje pořád s těmi před uložením do DB. Tudíž by to celý nemělo smysl na tomnto místě řešit.

<?php
...
	{if count($formContainer->getComponents()) > 0}
	{php $formContainer->getComponents()->uasort(function($a, $b) {	return $a['priorita']->getValue() - $b['priorita']->getValue(); })}
	{foreach $formContainer->getComponents() as $idObjpol => $fieldObjpol}
		{formContainer $idObjpol}
...
?>

Editoval RadaR (7. 1. 2017 18:47)

F.Vesely
Člen | 368
+
0
-

Zkus

{if count($formContainer->getComponents()) > 0}
{foreach $formContainer->getComponents()->uasort(function($a, $b) { return $a['priorita']->getValue() - $b['priorita']->getValue(); })}
	{formContainer $idObjpol}
RadaR
Člen | 46
+
0
-

To už je asi moc velká divočina… Parse Error syntax error, unexpected '{'. A stejně potřebuju iterovat klíč ⇒ hodnota takže tam potřebuju i to as $idObjpol => $fieldObjpol a to když zkusím

<?php
{foreach $formContainer->getComponents()->uasort(function($a, $b) { return $a['priorita']->getValue() - $b['priorita']->getValue(); }) as $idObjpol => $fieldObjpol}
?>

tak dostanu jinou chybu Invalid argument passed to foreach; array or Traversable expected, boolean given.

CZechBoY
Člen | 3608
+
0
-

Uasort asi nebude metoda ale php funkce…
Dej ty komponenty jako prvni parametr, callback jako druhy.
http://php.net/…n.uasort.php

edit: uasort je metoda ArrayIterator.

Editoval CZechBoY (7. 1. 2017 22:00)

F.Vesely
Člen | 368
+
0
-

Pravda, omlouvam se. Ted jsem si to vyzkousel a tohle uz by melo fungovat:

{var $components = $form->getComponents()}
{php $components->uasort(...)}
{foreach $components as $id => $component}
...
F.Vesely
Člen | 368
+
0
-

@CZechBoY je to i metoda ArrayIterator viz ArrayIterator::uasort

CZechBoY
Člen | 3608
+
0
-

@F.Vesely :-o tak to ani nevim, ze takova trida existuje. A ani mi nedoslo, ze vlastne latte foreach obaluje jeste to pole. Diky za tipy ;-)

RadaR
Člen | 46
+
0
-

2 F.Vesely

Tohle už opravdu funguje! :) Díky moc. Já v mezičase přišel na to, jak to řešit v onSuccess události formuláře. Takže ať si každý zváží, co mu víc vyhovuje. Hodím sem obě varianty.

  1. Řešení v šabloně
<?php
...
{var $components = $formContainer->getComponents()}
{php $components->uasort(function($a, $b){ return $b['priorita']->getValue() - $a['priorita']->getValue();})}
{foreach $components as $idObjpol => $fieldObjpol}
...
?>
  1. Řešení v onSuccess události formuláře (naliju si jednotlivé kontejnery položek do array, odstraním je z komponenty, seřadím pole dle potřeby a vrátím zpátky do komponenty)
<?php
...
$components = $form['objpol']->getComponents();
$components->uasort(function($a, $b){ return $b['priorita']->value - $a['priorita']->value;});
foreach ($components as $key => $value):
	$form['objpol']->removeComponent($form['objpol'][$key]);
	$form['objpol']->addComponent($value, $key);
endforeach;
...
?>

Editoval RadaR (7. 1. 2017 20:35)