Formulář s Multiplier a Kdyby/Replicator používající ajax

Croc
Člen | 270
+
0
-

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

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

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

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

Aha, díky. Nicméně je to úplně stejné. Stránka se neobnoví a v konzoli se vrátí celá stránka :(

EDIT: Pomohlo doplnění callbacku na redraw přímo. Viz kód víše.

Editoval Croc (26. 6. 2017 21:48)

Croc
Člen | 270
+
0
-

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

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

Tak to jsem nevěděl že to jde takhle použít. Super, moc děkuju :)

Croc
Člen | 270
+
0
-

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

Nevíte někdo prosím kde je problém? Moc děkuju, už si opravdu nevím rady :(

Croc
Člen | 270
+
0
-

Jen trochu upřesním chování.

  1. nastavím default hodnoty (vytvoří se 2 containery s id 0 a 1)
  2. pomocí tlačítka add přidám třetí (id 2) a vyplním
  3. smažu první container (id 0) – (v pořádku se smaže a refreshne se snippet)
  4. 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í).
  5. 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
+
0
-

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…

Croc
Člen | 270
+
+1
-

Tak Kdyby/Replicator je prakticky nyní nepoužitelný. Doporučuji používat WebChemistry/Multiplier, tam se výše popisovaný problém neobjevuje…