Překreslení specifického snippetu ve snippetArea
- og
- Člen | 5
Ahoj, mám dotaz ohledně snippetů/snippetArea.
Snažím se dosáhnout něčeho takového
Tzn. formulář pro vytváření družiny (= 4 postavy). Každá postava
může být různé třídy (Archer, Knight atp.).
Podle třídy se určují defaultní hodnoty atributů (might, speed, luck
atp.).
Tady nastává kámen ůrazu.
Chtěl bych dostáhnout toho, aby se mi při změně třídy jedné z postav předvyplnily defaultní hodnoty atribut pro danou postavu. Ajaxově.
Momentálně jsem v místě, kdy mám celý form obalen {snippetArea form} a attributy pro jednotlivé postavy mám obalené ve {snippet character_one_section} (následuje character_two_section atd.).
Při změně selectu s třídami triggeruju v JS kliknutí na skrytý button. Toto kliknutí odchytávám v onSuccess formu a následně v handle metodě překresluji snippety. Problém je v tom, že místo toho, aby se mi překreslily atributy jenom pro jednu postavu (tu, jejíž select vyvolal po změně odeslání formu), překreslí se mi attributy pro všechny postavy. Zkrátka, ta vnější snippetArea překreslí uplně všechno a bez ní se nepřekreslí zase nic. Potřeboval bych překreslit jenom jeden specifický snippet uvnitř snippetAreai…
Jde to? Níže přikládám kód.
KOMPONENTA
<?php
class CreateCharacterForm extends BaseControl
{
/**
* @var array
*/
public $onSuccess = [];
/**
* @var ICharacterService
*/
private $characterService;
public function __construct(ICharacterService $characterService)
{
$this->characterService = $characterService;
}
public function render(): void
{
$this->template->attributes = Attributes::ATTRIBUTES_LIST;
$this->template->characters = CreateCharacterFormBuilder::CHARACTER_FORM_LIST;
$this->template->render(__DIR__ . '/templates/default.latte');
}
protected function createComponentForm(): Form
{
$form = (new CreateCharacterFormBuilder)->build(new Form);
$form->onSuccess[] = function (Form $form) {
/** @var ArrayHash $values */
$values = $form->getValues();
if (!$form['create']->isSubmittedBy()) {
$this->handleSetAttributes($values, Attributes::DEFAULT_ATTRIBUTES_CHARACTER_LIST[$values->currentCharacter]);
}
if ($form['create']->isSubmittedBy()) {
$character = $this->characterService->createCharacter($values);
$this->onSuccess($character);
}
};
return $form;
}
/**
* @param ArrayHash $values
* @param array $attributes
*/
private function handleSetAttributes(ArrayHash $values, array $attributes): void
{
$this->template->defaults = $attributes;
$this->template->minValues = Attributes::MIN_ATTRIBUTES_CHARACTER_LIST[$values->currentCharacter];
$this->template->characters = CreateCharacterFormBuilder::CHARACTER_FORM_LIST;
$this->redrawControl('form');
$this->redrawControl($values->currentSection . '_section');
}
}
interface ICreateCharacterForm
{
/**
* @return CreateCharacterForm
*/
public function create(): CreateCharacterForm;
}
?>
FORMBUILDER
<?php
class CreateCharacterFormBuilder
{
public const CHARACTER_FORM_LIST = [
self::CHARACTER_ONE,
self::CHARACTER_TWO,
self::CHARACTER_THREE,
self::CHARACTER_FOUR,
];
private const CHARACTER_ONE = 'character_one';
private const CHARACTER_TWO = 'character_two';
private const CHARACTER_THREE = 'character_three';
private const CHARACTER_FOUR = 'character_four';
public function build(Form $form): Form
{
foreach (self::CHARACTER_FORM_LIST as $characterPrefix) {
$character = $form->addContainer($characterPrefix . '_section');
$character->addText($characterPrefix . '_name', 'Name')->isRequired();
$classes = [null];
foreach (Character::CHARACTER_TYPE_LIST as $k => $value) {
$classes[$k] = $value;
}
$character->addSelect($characterPrefix . '_class', 'Character class', $classes)
->setHtmlAttribute('class', 'character-class')
->setHtmlAttribute('data-section', $characterPrefix);
$character->addSubmit($characterPrefix . '_submitClass')
->setDefaultValue($character)
->setHtmlAttribute('class', 'd-none submit-class');
$attributes = $character->addContainer($characterPrefix . '_attributes');
foreach (Attributes::ATTRIBUTES_LIST as $k => $stat) {
$attributes->addInteger($k, $stat)
->setHtmlAttribute('onkeydown', 'return false')
->setHtmlAttribute('max', Attributes::STATISTIC_MAX);
}
}
$form->addHidden('currentSection');
$form->addHidden('currentCharacter');
$form->addHidden('bonusPoints')
->setDefaultValue(50)
->setHtmlAttribute('id', 'bonus-points');
$form->addSubmit('create', 'Create');
return $form;
}
}
?>
LATTE
<?php
{snippetArea form}
<form n:name="form" class="ajax">
<div class="row">
{foreach $characters as $character}
<div class="col-3">
{formContainer $character . _section}
<div class="form-group">
{label $character . _class}
<select n:name="$character . _class"></select> <input n:name="$character . _submitClass">
</div>
<div class="form-group">
{label $character . _name}
<input n:name="$character . _name">
</div>
{formContainer $character . _attributes}
<div n:snippet="$character . _section">
{foreach $attributes as $k => $val}
<div class="form-group">
{label $k}
<div class="d-flex">
<input n:name="$k"
class="attributes mr-4" value="{isset($defaults) && array_key_exists($k, $defaults) ? $defaults[$k]}" min="{isset($minValues) && array_key_exists($k, $minValues) ? $minValues[$k]}">
<div class="attribute-control d-flex">
<a href="#" class="increase mr-3">+</a>
<a href="#" class="decrease">-</a>
</div>
</div>
</div>
{/foreach}
</div>
{/formContainer}
{/formContainer}
</div>
{/foreach}
</div>
<div class="form-group">
<input n:name="bonusPoints" type="number" disabled value="50">
</div>
<input n:name="currentSection"> <input n:name="currentCharacter"> <input n:name="create">
</form>
{/snippetArea}
?>
- Šaman
- Člen | 2659
Nechce se mi studovat ten kód, ale problematika dračáku je mi sympatická, takže :)
- tohle je už záležitost s řekněmež standardním řešením, viz. závislé selectboxy (jen je nutné hledat řešení pro Nette 2.4 a 3 – dříve se to dělalo jinak a nefungovala snippetArea)
- koukám, že mám už řešení na gistu
- důležité je, že máš formulář ve snippetArea a to co se má překreslit ve snippetu. Dále handle, který zjistí nové hodnoty, nastaví je a překreslí snippet. A pak malý JS, který při onChange zavolá onen handle i s parametrem.
- jediný problém je, že v případě externího JS by se nedalo použít makro {link}, navíc obsluha formuláře patří do jeho definice, proto nastavuji nějaký data atribut link a ten si pak JS načte a na ten bude odesílat hodnoty.
- a nevím, jestli se už něco nezměnilo, ale ten parametr $name v createComponent byl (myslím, někdy) nutný
Tohle by mělo být poměrně čisté řešeni bez dalších nástrojů, předpokládá jen iniciovaný nette.ajax a načtené jquery.
koukám na svůj test na localhostu a tam už mám odkaz na ten link v definici formuláře, jak jsem psal výše. Na gistu je t předané v šabloně, oboje je funkční.
<?php
$form->addSelect('country', 'Země:', $this->countries)
->setAttribute('data-link', $this->link("invalidateTestForm!"))
->setPrompt('- vyberte zemi -')
->setRequired('vyberte zemi');
?>
P.S. Funguje to i v komponentě, příklad výše je okleštěny základ, abych to nezahlcoval dalšími nesouvisejícími problémy.
Editoval Šaman (19. 11. 2019 13:43)
- og
- Člen | 5
@Šaman Ahoj, díky za tipy a ukázky kódu :)
Myslím, že v principu nám to funguje stejně s tim rozdílem, že já
při změně v selectu triggeruju přes JS kliknutí na hidden submit button a
pak v komponentě kontroluju, jestli na něj bylo kliknuto a pokud ano, tak
překlresluji v handle metodě snippet s attributama. (ještě nejsem ve
„hře“ moc dlouho, takže nedokážu odhadnout na kolik je toto řešení
čisté)
Takže snippety se mi překreslují, ale moc :)
zkusim udělat zjednodušenou ukázku toho, co se mi děje.
ten můj form vypadá zjednodušení takhle:
<?php
{snippetArea form}
<form>
<div class="character_1">
<select>select s třídama postav pro hrdinu č. 1</select>
{snippet character_1_section}
inputy s attributama...
{/snippet}
</div>
<div class="character_2">
<select>select s třídama postav pro hrdinu č. 2</select>
{snippet character_2_section}
inputy s attributama...
{/snippet}
</div>
<div class="character_3">
<select>select s třídama postav pro hrdinu č. 3</select>
{snippet character_3_section}
inputy s attributama...
{/snippet}
</div>
<div class="character_4">
<select>select s třídama postav pro hrdinu č. 4</select>
{snippet character_4_section}
inputy s attributama...
{/snippet}
</div>
</form>
{/snippetArea}
?>
Já bych chtěl, aby se mi při změně selectu pro „hrdinu“ č. 1 překreslil snippet s attributama pouze pro hrdinu č. 1.
takže při změně v selectu pro prvního hrdinu volám v handle metodě pro překreslování:
<?php
$this->redrawControl('form');
$this->redrawControl('character_1_section');
?>
Jenže mě se překreslej attributy pro všechny 4 „hrdiny“ nehledě na
to, že dávám redrawControl() jenom na snippet
character_1_section
.
Když pošlu pryč tu snippetAreau, která obaluje celej form, tak se zase
nepřekreslí nic. Už sem zkoušel různě obalovat do snippetů/snippetAreaů
jednotlivý části toho formuláře, ale ještě sem nepřišel na tu správnou
kombinaci..
- Šaman
- Člen | 2659
Střílím od boku, ale koukám, že celý ten formulář se jmenuje „Form“
<?php
protected function createComponentForm(): Form
?>
Takže možná překresluješ celou komponentu. Doporučuji vždy do názvu
dát o co se jedná (CharacterForm
,
CharacterFormControl
pokud by to celé bylo v samostatné
komponentě, která ale NENÍ formulář a jen ho obsahuje…
CharacterFormSnippetArea
,…)
- Šaman
- Člen | 2659
Hoď to někam na GitHub, nebo podobně. Takhle z patra se těžko radí.
Může to být někde jinde, než kam otázkami míříš.
Jinak snippetArea by měla zařídit, že se provede veškerý kód (třeba
protože potřebuješ aby existoval form
, když překresluješ jeho
inputy), ale odesílat by se měly myslím jen snippety.