Formulář s Multiplier a Kdyby/Replicator používající ajax
- Croc
- Člen | 270
Zdravím,
mám takový problém. Mám formulář v komponentě s manuálním
vykreslením použivající několikrát replicator a ten celý formulář je
1–2× použitý na stránce pomocí multiplieru. Toto je funkční, nicméně
ho potřebuji nyní zaajaxovat.
Potřebuji zaajaxovat, aby se refreshnul pouze odeslaný formulář i když
jsou např. 2 na stránce pomocí multiplieru.
Potřebuji zaajaxovat kdyby/replicator, aby se při přidání/odebrání
nerefreshovala celá stránka.
Hodně zjednodušený kód je zde:
<?php
// presenter
protected function createComponentItemForm() {
return new Multiplier(function ($id) {
$item = $this->itemsData[$id];
$form = $this->itemFormFactory->create($item);
$form->onSave[] = function($form) {
//....
};
return $form;
});
}
// komponenta formuláře
// vytvoření replikatoru obsahuje tyto tlačítka
// ...
$group->addSubmit('odstran', 'Odstraň')
->setAttribute('class', 'ajax')
->setValidationScope(FALSE)
->addRemoveOnClick();
// ...
$groups->addSubmit('pridej', 'Přidej')
->setAttribute('class', 'ajax')
->setValidationScope(FALSE)
->addCreateOnClick(FALSE);
// ...
public function HeaderFormAddElementClicked(SubmitButton $button) {
$groups = $button->parent;
if ($groups->isAllFilled()) {
$button->parent->createOne();
$this->getPresenter()->redrawControl('snippet'); // pro ukázku dummy název snippetu
}
}
public function HeaderFormRemoveElementClicked(SubmitButton $button) {
$groups = $button->parent->parent;
$groups->remove($button->parent, TRUE);
$this->getPresenter()->redrawControl('snippet'); // pro ukázku dummy název snippetu
}
// latte template pro vykresní formuláře komponenty
{form itemForm}
{foreach $form->getComponents() as $sid => $cntrl}
{if $cntrl instanceof Kdyby\Replicator\Container}
<div class="col-sm-12">
<br>
<h3>{$cntrl->getCurrentGroup()->getOption('label')}</h3>
<hr>
</div>
{foreach $cntrl->getControls() as $key => $c}
{if $c instanceof Nette\Forms\Controls\TextInput || $c instanceof Nette\Forms\Controls\SelectBox || $c instanceof Nette\Forms\Controls\SubmitButton
|| $c instanceof Nette\Forms\Controls\Checkbox || $c instanceof Nette\Forms\Controls\CheckboxList}
{label $c /}{input $c}
{/if}
{/foreach}
{else}
// vykreslení normálních formulářových polí
{/if}
{/foreach}
{/form}
Používám nette 2.4.x.
Našel jsem toto téma: https://forum.nette.org/…mazani-prvku
Ale u uvedeného řešení mi to hlásí že _form je deprecated. Navíc tam není použití s multiplierem.
Má otázka tedy zní, kam umístit snippety do šablony formuláře, s jakým názvem (dynamický? statický?) a jak ty snipety správně invalidovat.
Moc děkuju, opravdu si už nevím rady
Editoval Croc (24. 6. 2017 19:27)
- Mysteria
- Člen | 797
Místo $_form
se v nejnovějším Nette používá
{var $form = $control['myForm']}
a
protected function beforeRender() {
parent::beforeRender();
if($this->isAjax()) $this->template->getLatte()->addProvider('formsStack', [$this['myForm']]);
}
V Latte pak zhruba takto:
<form n:name='myForm' class='form-inline ajax' role='form'>
<div n:snippet='myForm'>
{var $form = $control['myForm']}
<div n:inner-foreach="$form['containerOne']->controls as $input" class='form-group'>{input $input}</div>
<div n:inner-foreach="$form['containerTwo']->controls as $input" class='form-group'>{input $input}</div>
<div n:inner-foreach="$form['containerThree']->controls as $input" class='form-group'>{input $input}<input n:if='$iterator->last' n:name='submit'></div>
</div>
</form>
To by ti mělo vyřešit problém s deprecated $_form
.
Co se týká zbytku, tak to už ti neporadím, protože Replicator v kombinaci s Multiplierem jsem nikdy nepoužil.
Editoval Mysteria (25. 6. 2017 10:37)
- Croc
- Člen | 270
Díky moc za tip. Udělal jsem si vedlejší projekt, ve kterém chci uvedené rozchodit. Zatím testuji samostatný replikátor bez multiplieru. Problém mám takový, že se sice požadavek odešle, v konzoli se vrátí celá stránka (správně), ale nepřekreslí se. Mám to postavené takto:
ReplicatorFormControl.php
<?php
namespace App\Forms;
use Nette;
use Nette\Application\UI\Control;
use Nette\Application\UI\Form;
use Nette\Forms\Controls\SubmitButton;
/**
* Class ReplicatorFormControl
* @package App\Forms
*/
class ReplicatorFormControl extends Control {
public $onSave = array();
/**
* ReplicatorFormControl constructor.
*/
public function __construct() {
parent::__construct();
}
/**
*
*/
protected function beforeRender() {
parent::beforeRender();
if($this->isAjax()) $this->template->getLatte()->addProvider('formsStack', [$this['replicatorForm']]);
}
/**
*
*/
public function render() {
//$this['replicatorForm']->render();
$this->template->setFile(__DIR__ . '/replicatorForm.latte');
$this->template->render();
}
/**
* @return Form
*/
public function createComponentReplicatorForm() {
$form = new Form;
$presenter = $this;
$invalidateCallback = function () use ($presenter) {
/** @var \Nette\Application\UI\Presenter $presenter */
$presenter->redrawControl('replicator');
};
$removeEvent = Nette\Utils\Callback::closure($this, 'HeaderFormRemoveElementClicked');
$i = 0;
$form->addGroup('Replicator fields');
/* replicator part */
$groups = $form->addDynamic('replicator', function (Nette\Forms\Container $group) use ($removeEvent, $invalidateCallback) {
// create empty replicator inputs from assoc array
$group->addText('test1', 'test1');
$group->addSubmit('remove', 'Remove')
->setAttribute('class', 'btn btn-danger ajax')
->setValidationScope(FALSE) # disables validation
->addRemoveOnClick($invalidateCallback);
}, 1);
$groups->addSubmit('add', 'Add')
->setAttribute('class', 'btn btn-success ajax')
->setValidationScope(FALSE)
->addCreateOnClick(FALSE, $invalidateCallback);
/* end replicator part */
$form->addProtection('Error, try again!');
$form->addSubmit('save', 'Uložit')
->setAttribute('class', 'btn-primary')
->setAttribute('id','submit')
->onClick[] = array($this, 'formSucceeded');
return $form;
}
/**
* @param SubmitButton $button
*/
public function HeaderFormAddElementClicked(SubmitButton $button) {
$groups = $button->parent;
if ($groups->isAllFilled()) {
$button->parent->createOne();
//$this->redrawControl('replicator');
}
}
/**
* @param SubmitButton $button
*/
public function HeaderFormRemoveElementClicked(SubmitButton $button) {
$groups = $button->parent->parent;
$groups->remove($button->parent, TRUE);
//$this->redrawControl('replicator');
}
/**
* @param $form
* @param $values
*/
public function formSucceeded($form, $values) {
if (!$this->user->isLoggedIn()) {
$this->getPresenter()->redirect('Homepage:');
}
$this->onSave();
}
}//class
/**
* Interface IReplicatorFormControl
* @package App\Forms
*/
interface IReplicatorFormControl {
/**
* @return ReplicatorFormControl
*/
public function create();
}
replicatorForm.latte
{form replicatorForm}
<div class="col-md-12">
{snippet replicator}
{var $form = $control['replicatorForm']}
{var $oldGroup = ''}
{foreach $form->getComponents() as $sid => $cntrl}
{if $cntrl instanceof Kdyby\Replicator\Container}
<div class="col-md-12">
<br>
<h3>{$cntrl->getCurrentGroup()->getOption('label')}</h3>
<hr>
</div>
{foreach $cntrl->getControls() as $key => $c}
{if $c instanceof Nette\Forms\Controls\TextInput || $c instanceof Nette\Forms\Controls\SelectBox || $c instanceof Nette\Forms\Controls\SubmitButton
|| $c instanceof Nette\Forms\Controls\Checkbox || $c instanceof Nette\Forms\Controls\CheckboxList}
{if $c->getName() == 'add'}
<div class="col-md-12">
<br>
{elseif $c->getName() == 'remove'}
<div class="col-md-3 ">
<br>
{else}
<div class="col-md-3 ">
{/if}
{label $c /}{input $c}
</div>
{if $c->getName() == 'remove'}
<div class="col-md-12">
<br>
</div>
{/if}
{/if}
{/foreach}
{else}
{var $newGroup = $cntrl->getOption('group')}
{if $cntrl instanceof Nette\Forms\Controls\TextInput || $cntrl instanceof Nette\Forms\Controls\SelectBox
|| $cntrl instanceof Nette\Forms\Controls\SubmitButton || $cntrl instanceof Nette\Forms\Controls\Checkbox
|| $cntrl instanceof Nette\Forms\Controls\CheckboxList}
{if $newGroup != $oldGroup}
<div class="col-md-12">
<br>
<h3>{$cntrl->getOption('group')}</h3>
<hr>
</div>
{var $oldGroup = $newGroup}
{/if}
<div class="col-md-3">
{label $cntrl /}{input $cntrl}
</div>
{/if}
{/if}
{/foreach}
{/snippet}
</div>
{/form}
Nevíte kde by mohl být problém?
EDIT: Kód upraven dle rad.
Editoval Croc (26. 6. 2017 21:48)
- David Matějka
- Moderator | 6445
cela stranka se vracet nema – spatne to invalidujes. kodem
$this->getPresenter()->redrawControl('replicator');
invalidujes snippet replicator v presenteru. ty ho mas ale v komponente. takze jen
$this->redrawControl('replicator');
- Croc
- Člen | 270
Zaajaxování replikátoru mám. Moc děkuju za rady.
Nyní řeším zaajaxování Multiplieru.
Mám formulář:
Presenter.php
/**
* @return Nette\Application\UI\Multiplier
*/
protected function createComponentMultiplierForm() {
return new Nette\Application\UI\Multiplier(function ($id) {
$form = $this->multiplierFormFactory->create();
$form->onSave[] = function() use ($id) {
$this->flashMessage('Formulář úspěšně uložen!', 'success');
$this->redrawControl('multiplier');
$this->redrawControl('flash');
};
return $form;
});
}
multiplierFormControl.php
<?php
namespace App\Forms;
use Nette;
use Nette\Application\UI\Control;
use Nette\Application\UI\Form;
use Nette\Forms\Controls\SubmitButton;
/**
* Class MultiplierFormControl
* @package App\Forms
*/
class MultiplierFormControl extends Control {
public $onSave = array();
/**
* MultiplierFormControl constructor.
*/
public function __construct() {
parent::__construct();
}
/**
*
*/
protected function beforeRender() {
parent::beforeRender();
if($this->isAjax()) $this->template->getLatte()->addProvider('formsStack', [$this['multiplierForm']]);
}
/**
*
*/
public function render() {
//$this['replicatorForm']->render();
$this->template->setFile(__DIR__ . '/multiplierForm.latte');
$this->template->render();
}
/**
* @return Form
*/
public function createComponentMultiplierForm() {
$form = new Form;
$form->addGroup('Date');
$form->addText('date', 'Date: ')
->setAttribute('class','date');
$form->addText('text', 'Text: ');
$form->addProtection('Error, try again!');
$form->addSubmit('save', 'Uložit')
->setAttribute('class', 'btn-primary ajax')
->setAttribute('id','submit')
->onClick[] = array($this, 'formSucceeded');
return $form;
}
/**
* @param $values
*/
public function formSucceeded($values) {
$this->onSave();
}
}//class
/**
* Interface IMultiplierFormControl
* @package App\Forms
*/
interface IMultiplierFormControl {
/**
* @return MultiplierFormControl
*/
public function create();
}
multiplierForm.latte
{form multiplierForm}
{snippet multiplier}
{var $form = $control['multiplierForm']}
// generování formu
{/snippet}
{/form}
Pokud nyní odešlu jeden z vygenerovaných formulářů, tak redraw provede, ale pouze snippetu flash, snippet multiplier se vůbec nepošle. Nevíte prosím čím by to mohlo být?
- David Matějka
- Moderator | 6445
zase to invalidujes na spatne komponente, ten snippet mas uvnitr mutliplier form, takze to ma vypadat takhle:
$form = $this->multiplierFormFactory->create();
$form->onSave[] = function() use ($id, $form) {
$this->flashMessage('Formulář úspěšně uložen!', 'success');
$form->redrawControl('multiplier');
$this->redrawControl('flash');
};
- Croc
- Člen | 270
Ještě bych se chtěl zeptat. Mám takový problém s chováním replikátoru po nastavení defaultních hodnot. Pokud nejsou nastaveny defaultní hodnoty a hodnoty doplňuji, replikátor funguje v pořádku (odebírání a přidávání). Ovšem pokud pokud nastavím defaultní hodnoty a například odeberu jeden řádek, tak pak následně nemůžu přidat nový. Klikám a nic se nepřidává. Je tam ještě jedna kombinace, při které se neodstraní řádky s inputy, ale jen se vyprázdní a následně je musím mazat od zadu aby se smazali.
Nicméně pokud kouknu do konzole, tak se vrátí celá stránka (v pořádku) a ne snippet.
ReplicatorFormControl.php
<?php
namespace App\Forms;
use Nette;
use Nette\Application\UI\Control;
use Nette\Application\UI\Form;
use Nette\Forms\Controls\SubmitButton;
/**
* Class ReplicatorFormControl
* @package App\Forms
*/
class ReplicatorFormControl extends Control {
public $onSave = array();
/**
* @var array
*/
private $item;
/**
* ReplicatorFormControl constructor.
* @param array $item
*/
public function __construct(array $item) {
parent::__construct();
$this->item = $item;
}
/**
*
*/
protected function beforeRender() {
parent::beforeRender();
if($this->isAjax()) $this->template->getLatte()->addProvider('formsStack', [$this['replicatorForm']]);
}
/**
*
*/
public function render() {
//$this['replicatorForm']->render();
$this->template->setFile(__DIR__ . '/replicatorForm.latte');
$this->template->render();
}
/**
* @return Form
*/
public function createComponentReplicatorForm() {
$form = new Form;
$presenter = $this;
$invalidateCallback = function () use ($presenter) {
/** @var \Nette\Application\UI\Presenter $presenter */
$presenter->redrawControl('replicator');
};
$removeEvent = Nette\Utils\Callback::closure($this, 'HeaderFormRemoveElementClicked');
$i = 0;
$form->addGroup('Date');
$form->addText('date', 'Date: ')
->setAttribute('class','date')
->setRequired();
$form->addGroup('Replicator fields');
/* replicator part */
$replicator = $form->addDynamic('replicator', function (Nette\Forms\Container $group) use ($removeEvent, $invalidateCallback) {
// create empty replicator inputs from assoc array
$group->addText('test1', 'Test-1: ');
$group->addText('test2', 'Test-2: ');
$group->addSubmit('remove', 'Remove')
->setAttribute('class', 'btn btn-danger ajax')
->setValidationScope(FALSE) # disables validation
->addRemoveOnClick($invalidateCallback);
}, 0);
$replicator->addSubmit('add', 'Add')
->setAttribute('class', 'btn btn-success ajax')
->setValidationScope(FALSE)
->addCreateOnClick(FALSE, $invalidateCallback);
/* end replicator part */
if ($this->item) {
$replicator->setDefaults($this->item);
}
$form->addProtection('Error, try again!');
$form->addSubmit('save', 'Uložit')
->setAttribute('class', 'btn-primary ajax')
->setAttribute('id','submit')
->onClick[] = array($this, 'formSucceeded');
return $form;
}
/**
* @param SubmitButton $button
*/
public function HeaderFormAddElementClicked(SubmitButton $button) {
$groups = $button->parent;
if ($groups->isAllFilled()) {
$button->parent->createOne();
//$this->redrawControl('replicator');
}
}
/**
* @param SubmitButton $button
*/
public function HeaderFormRemoveElementClicked(SubmitButton $button) {
$groups = $button->parent->parent;
$groups->remove($button->parent, TRUE);
//$this->redrawControl('replicator');
}
/**
* @param $form
* @param $values
*/
public function formSucceeded($form, $values) {
$this->onSave();
}
}//class
/**
* Interface IReplicatorFormControl
* @package App\Forms
*/
interface IReplicatorFormControl {
/**
* @param array $item
* @return ReplicatorFormControl
*/
public function create(array $item);
}
Presenter.php
<?php
namespace App\Presenters;
use App\Forms\IMultiplierFormControl;
use App\Forms\IMultiplierReplicatorFormControl;
use App\Forms\IReplicatorFormControl;
use Nette;
class HomepagePresenter extends Nette\Application\UI\Presenter {
/**
* @var IReplicatorFormControl
* @inject
*/
public $replicatorFormFactory;
/**
* @var array
*/
public $items;
/**
* @return \App\Forms\ReplicatorFormControl
*/
protected function createComponentReplicatorForm() {
$form = $this->replicatorFormFactory->create($this->items);
$form->onSave[] = function($form, $values) {
};
return $form;
}
/**
*
*/
public function actionReplicator() {
$this->items = array(
0 => array(
'test1' => 'ahoj',
'test2' => 'Franto'
),
1 => array(
'test1' => 'ahojky',
'test2' => 'Zuzko'
),
);
}
/**
*
*/
public function renderReplicator() {
$this->template->items = $this->items;
}
}
Nevíte prosím kde mám chybu? :(
Editoval Croc (28. 6. 2017 12:27)
- Croc
- Člen | 270
Jen trochu upřesním chování.
- nastavím default hodnoty (vytvoří se 2 containery s id 0 a 1)
- pomocí tlačítka add přidám třetí (id 2) a vyplním
- smažu první container (id 0) – (v pořádku se smaže a refreshne se snippet)
- následně chci přidat čtvrtý container – (id 3). A tam nastane problém. Vrátí se celá stránka a nově přidaný container je v ní na prvním místě (ale na stránce se nezobrazí).
- následně smažu třetí container (id 2) (ale na stránce je vykreslen jako poslední) – tím se opravdu smaže a provede se redraw a tím se na stránce vykreslí onen čtvrtý container na prvním místě…
Když se kouknu na čtvrtý container, tak má id 0. Což je špatně, měl by mít 3…
Opravdu nevíte co s tím?
EDIT: S ajaxem toto chování nemá nic společného, děje se i bez něj (jen se ta stránka vykreslí). Jinak je docela problém, že nově přidaný container dostane stejné ID jako předešlý smazaný.
Pokud mám nasetované výchozí hodnoty a smažu jakýkoliv neposlední container, tak další nově přidaný dostane ID, které je první volné.
EDIT2: Tak problém je v tom, že pokud nasetuju defaultní hodnoty, smažu container a následně přidám container, tak nevím jak se teda přidá, ale metoda createOne v souboru Container.php v Replicatoru se nezavolá. Pokud tedy vyplním chybně přidaný container a přidám další, createOne už se zavolá…
Editoval Croc (22. 7. 2017 16:56)
- Croc
- Člen | 270
S defaultníma hodnotama se ten replicator (i multiplier od WebChemistry) chová úplně naprd… Nasetuju do replikátoru pomocí pole 3 skupiny atributů. Vymažu první, vymažu druhou a najednou se objeví prázdná skupina atributů, vymažu třetí a také se objeví prázdná skupina atributů. Po postupném smazání všech tří skupin je výsledek 2 prázdné skupiny atributů, které nejdou smazat…
Opravdu tohle nikdo nepozoroval? Díky moc za případnou pomoc…