Vytvořit jako helper nebo jak nejlépe?
- Kcko
- Člen | 468
Zdar,
mám na webu X výpisů sportovních výsledků.
V těchto výpisech chci výsledky vypisovat určitou formou. Jednou zkratkovitě, jednou komplexně (např. s poločasem), jindy zas jinak.
Ve starém systému (vlastním, lehčí MVC s vlastním šablonovacím systémem a DIBI) jsem měl v šabloně něco jako
<? foreach ($matches as $match): ?>
// tabulkovy vypis týmů, datumu utkatní a dalších informací
...
// a ted pozor
$matchResult = new MatchResult($match); // uložení celého řádku o zápase do objektu
$match->evaluateResult(); // vezme informace o zápasu, tj, poločas, výsledek po základní hrací době, výsledek po prodloužení, penaltách atd.. a uloží to do nějakého 3 rozměrného pole abych si to potom přes další metodu mohl nějak pěkně vrátit.
echo $match->getResult('full'); // výsledek kompletní, nebo zkrácený, nebo různě graficky upravený...
<? endforeach; ?>
Mno a moje otázka zní, jak toto nejlépe přenést do Nette. V Nette si obdobně vypisuji zápasy a rád bych opět měl nějakou šikovnou utilitku jak zobrazovat výsledky těchto zápasů …
Můžu si udělat X helperů ale to není to pravé ořechové, (navíc bych neustále v každém helperu musel opakovat to vyhodnocování), objekt se mi zdá lepší =>
- metoda na vyhodnocení výsledku
- x renderovacích drobných metod, aby mi to vracelo výsledky v různých formátech.
V šabloně ale těžko budu volat instanci modelu (vypadá to hrozně a ani si nejsem jistý jestli to vůbec funguje)…
Může mi někdo poradit co s tím?
- Etch
- Člen | 403
class MatchResult extends Control{
const TEMPLATE_FULL = 'full';
const TEMPLATE_INLINE = 'inline';
public function renderFull($match){
$this->render($match, self::TEMPLATE_FULL);
}
public function renderInline($match){
$this->render($match, self::TEMPLATE_INLINE);
}
private function render($match, $templateName){
$template = $this->createTemplate();
$template->setFile(__DIR__.'/'.$templateName.'.latte');
$template->match = $this->evaluateResult($match);
$template->render();
}
private function evaluateResult($match){
return //...
}
}
Factory
interface MatchResultFactory{
/** @return MatchResult */
public function create();
}
config:
matchResult:
class: MatchResult
implement: MatchResultFactory
V presenteru
/** @var \MatchResultFactory @inject */
public $matchResultFactory;
protected function createComponentMatchResult(){
return $this->matchResultFactory->create();
}
a v šabloně pak
{foreach $matches as $match}
{control matchResult:full, $match}
{control matchResult:inline, $match}
{/foreach}
???
Editoval Etch (30. 11. 2014 23:08)
- Kcko
- Člen | 468
@Etch
Paráda! Rešit tuhle „drobnost“ komponentou mě tedy vůbec nenapadlo!
Můžu k tomu mít pár dotazů?
- inject do presenteru tímto způsobem nefunguje, resp. musím změnit viditelnost z private na public, je to tak správně?
- do neonu mi to tímto způsobem zapsat nejde, zapsal jsem to takhle – App\FrontModule\Components\MatchResultFactory je to v pořádku?
- Kdybych se rozhodl zobrazovat na 1 řádku víc různých typů výsledků (v tvém příkladě Inline i Full), tak se metoda evaluate provede 2x – zbytečně. Kam ji umístit? Do constructoru? Jde mi o to, abych ji vyjal z renderu … –
- Jelikož mám výpis zápasů jako komponentu, tak mi tato výsledková komponenta nefunguje, napíše to, že neexistuje. Co s tím jak ji předat?
Moje struktura
CompetitionPresenter.php //
– renderDefault(); // jen vypis soutezi
– renderDetail($id); // detail souteze
Detail soutěže se skládá pouze z výpisu fází k soutěži a
u každé volám komponenty zápasů a tabulek.
Takže to nejdůležitější vypadá takto
competition-detail.latte
===========================
{foreach $stageList as $stage}
Fáze: {$stage->name}
Zápasy: {control results, $stage->id}
Tabulky: {control tables, $stage->id}
{/foreach}
a právě v tech results vypisuji ty zápasy ve kterých potřebuji ještě vložit tu novou komponentu od Tebe a nejde to, viz #4
Díky moc.
Editoval Kcko (29. 11. 2014 13:48)
- Oli
- Člen | 1215
@Kcko
- Ano, tenhle způsob injectu musí být public. Proto je lepší používat inject tetody nebo contructor → kvůli zapouzdření
- V neonu by mělo uplně stačit následující. Nette si samo předá závislosti (musí to být Interface)
matchResult: MatchResultFactory # pokud s tím nebudeš dál pracovat tak to nemusíš ani pojmenovávat
- MatchResultFactory # bez jména
- Ne to nemůžeš. V tomhle případě v constructoru ještě tu proměnnou
$match
neznáš. Pokud by jsi to takhle chtěl, tak by jsi na to musel použít multiplier - jestliže to potřebuješ v komponentě, tak to předej rovnou té komponentě. Vubec to nestrkej do presenteru… Kdyžtak pošli kod, takhle neumím poznat, co kde nepředáváš dobře.
- Kcko
- Člen | 468
@Oli díky za odpověd,
- jasná (to se asi @Etch spletl).,
- prostě nefunguje
můj neon
services:
- Cardbook\Helpers\Helpers
- App\Model\Competition
- App\Model\Table
- App\Model\Result
- App\AclFactory
authorizator: @App\AclFactory::createAcl
authenticator: App\Model\UserManager
#- App\FrontModule\Components\MatchResultFactory
matchResult: MatchResultFactory # pokud s tím nebudeš dál pracovat tak to nemusíš ani pojmenovávat
- MatchResultFactory # bez jména
Ale to moje použití jako servisy je špatně? viz zakomentovaný řádek v neonu.
No co bych tedy potřeboval ukázat / vysvělit
- vyjmout tu metodu evaluate z renderu , (kdybych tam toho měl víc nechci aby se všehno dělo v renderu …)
- jak tedy předat komponentu do další komponenty?
Mám to takto:
CompetitionPresenter.php
<?php
namespace App\FrontModule\Presenters;
use Nette,
App,
App\Model,
App\FrontModule\Components,
Nette\Forms\Controls;
class CompetitionPresenter extends BasePresenter
{
protected $model;
protected $modelTable;
protected $modelResult;
/** @var Components\MatchResultFactory @inject */
public $matchResultFactory;
protected function createComponentMatchResult()
{
return $this->matchResultFactory->create();
}
public function inject(Model\Competition $model, Model\Table $modelTable, Model\Result $modelResult)
{
$this->model = $model;
$this->modelTable = $modelTable;
$this->modelResult = $modelResult;
}
public function beforeRender()
{
$this->template->competitions = $this->database->table('competition_season');
}
public function renderDefault()
{
}
public function renderDetail($id)
{
// info o soutezi
$this->template->competition_season = $this->database->table('competition_season')
->where('id', $id)->fetch();
$this->template->stageList = $this->model->getStages(
$this->template->competition_season->competition_id,
$this->template->competition_season->season_id
);
}
protected function createComponentResults()
{
$control = new Components\Results($this->database, $this->modelResult);
return $control;
}
protected function createComponentTables()
{
$control = new Components\Tables($this->database, $this->modelTable);
return $control;
}
protected function createComponentEditForm()
{
$form = new Nette\Application\UI\Form;
$form->addText('goal_home_90', 'Góly domácí po 90 min.');
$form->addText('goal_away_90', 'Góly hosté po 90 min.');
$form->addText('goal_home_et', 'Góly domácí ET');
$form->addText('goal_away_et', 'Góly hosté ET');
$form->addText('goal_home_penalty', 'Góly domácí penalty');
$form->addText('goal_away_penalty', 'Góly hosté penalty');
$form->addCheckbox('played', 'Odehráno');
$form->addHidden('id');
$form->addSubmit('btn', 'Uložit');
$form->onSuccess[] = $this->submitEditForm;
$renderer = $form->getRenderer();
$renderer->wrappers['controls']['container'] = NULL;
$renderer->wrappers['pair']['container'] = 'div class=form-group';
$renderer->wrappers['pair']['.error'] = 'has-error';
$renderer->wrappers['control']['container'] = 'div class=col-sm-9';
$renderer->wrappers['label']['container'] = 'div class="col-sm-3 control-label"';
$renderer->wrappers['control']['description'] = 'span class=help-block';
$renderer->wrappers['control']['errorcontainer'] = 'span class=help-block';
// make form and controls compatible with Twitter Bootstrap
$form->getElementPrototype()->class('form-horizontal');
foreach ($form->getControls() as $control) {
if ($control instanceof Controls\Button) {
$control->getControlPrototype()->addClass(empty($usedPrimary) ? 'btn btn-primary' : 'btn btn-default');
$usedPrimary = TRUE;
} elseif ($control instanceof Controls\TextBase || $control instanceof Controls\SelectBox || $control instanceof Controls\MultiSelectBox) {
$control->getControlPrototype()->addClass('form-control');
} elseif ($control instanceof Controls\Checkbox || $control instanceof Controls\CheckboxList || $control instanceof Controls\RadioList) {
$control->getSeparatorPrototype()->setName('div')->addClass($control->getControlPrototype()->type);
}
}
return $form;
}
public function submitEditForm($form)
{
$id = (int) $this->getParameter('id');
$values = $form->getValues();
$match = $this->database->table('match')->where('id', $id)->fetch();
$match->update($values);
//\Nette\Diagnostics\Debugger::barDump($values);
$this->redirect('Competition:detail', $match->competition_id);
}
public function actionEditMatch($id)
{
$this->template->match = $this->database->table('match')->where('id', $id)->fetch();
$form = $this['editForm'];
if (!$form->isSubmitted())
{
$defaults = $this->database->table('match')->where('id', $id)->fetch();
$form->setDefaults($defaults);
}
}
}
Competition/detail.php
{var $competName = $competition_season->competition->name}
{var $seasonName = $competition_season->season->name}
{block title} {$competName} - {$seasonName} {/block}
{block content}
<div class="row">
<div class="col-lg-8">
<h1 class="page-header">
{$competName} - {$seasonName}
</h1>
{foreach $stageList as $id => $stage}
{if $stage['row']['has_matches'] || $stage['row']['has_table']}
<h4>
<span class="glyphicon glyphicon-circle-arrow-right" aria-hidden="true"></span>
{$stage['name']}
</h4>
{if $stage['row']['has_matches']}
{control results, $id}
{/if}
{if $stage['row']['has_table']}
{control tables, $id}
{/if}
{sep} <hr /> {/sep}
{/if}
{/foreach}
</div>
Components/Results.php
<?
namespace App\FrontModule\Components;
use Nette\Application\UI,
Nette,
App;
class Results extends UI\Control
{
protected $database;
protected $modelResult;
public function __construct (Nette\Database\Context $database, App\Model\Result $modelResult)
{
$this->database = $database;
$this->modelResult = $modelResult;
}
public function render($stageId)
{
$this->template->setFile(__DIR__ . '/../templates/components/Results/list.latte');
$this->template->stageId = $stageId;
$this->template->modelResult = $this->modelResult;
$this->template->matches = $this->database->table('match')->where('stage_list_id', $stageId)->order('id');
$this->template->render();
}
}
Results/list.php
Tady potřebuju vyvolat další komponentu –
MatchResults
<table class="table table-striped table-bordered table-condensed">
{foreach $matches as $match}
{var $homeTeam = $match->ref('team', 'team_home_id')}
{var $awayTeam = $match->ref('team', 'team_away_id')}
<tr>
<td><a href="{plink Competition:editMatch $match->id}">{$match->id}</a></td>
<td class="text-right" style="width: 42%; padding-right: 2%;">{$homeTeam->name} <img src="/assets/gfx/flags/16/{$homeTeam->logo}" alt="" /></td>
<td class="text-center">{control matchResult:inline, []}</td>
<td class="text-left" style="width: 42%; padding-left: 2%;"><img src="/assets/gfx/flags/16/{$awayTeam->logo}" alt="" /> {$awayTeam->name}</td>
</tr>
{/foreach}
</table>
Components/MatchResult.php
Je to to samé od @Etch
<?
namespace App\FrontModule\Components;
use Nette\Application\UI,
Nette,
App;
interface MatchResultFactory
{
/** @return MatchResult */
public function create();
}
class MatchResult extends UI\Control
{
const TEMPLATE_FULL = 'full';
const TEMPLATE_INLINE = 'inline';
public function renderFull($match)
{
$this->render($match, self::TEMPLATE_FULL);
}
public function renderInline($match)
{
$this->render($match, self::TEMPLATE_INLINE);
}
private function render($match, $templateName)
{
$template = $this->createTemplate();
//$this->template->setFile(__DIR__ . '/../templates/components/Cards/nice-list.latte');
$template->setFile(__DIR__.'/../templates/components/MatchResult/'.$templateName.'.latte');
$template->match = $this->evaluateResult($match);
$template->render();
}
private function evaluateResult($match)
{
//@todo -- dodelat, nejake ulozeni do tohoto objektu nebo do vicerozmernho pole
}
}
Tak snad se v tom vyznáš :)
- Kcko
- Člen | 468
Edit: tak už jsem upravil kód takto:
protected function createComponentResults()
{
$control = new Components\Results($this->database, $this->modelResult);
$control->addComponent($this->matchResultFactory->create(), 'matchResult');
return $control;
}
a už to funguje. Jestli je to dobře ovšem nevím, tak zůstává stále můj předchozí vícedotaz.
- Etch
- Člen | 403
@Kcko
- Ano v mém původním příspěvku jsou nějaké drobné chyby (private místo public atd. nahoře opravím, aby to někoho třeba nemátlo)… Ve tři ráno mi to nějak nedocvaklo.
- Můj původní neon zápis nepředpokládal namespace u daných tříd.
Pokud sis tam ten namespace přidal, tak je samozřejmě nutné ho doplnit
i v definici v configu.
- App\FrontModule\Components\MatchResultFactory
je v tvém případě správná definice. - V tomto případě dost dobře nejde danou metodu dostat z render metody. Nejjednodušší je v dané metodě zajistit, aby výpočet proběhl pouze jednou.
private $results = array();
private function render($match, $templateName){
$template = $this->createTemplate();
$template->setFile(__DIR__.'/'.$templateName.'.latte');
$template->result = $this->getResult($match);
$template->render();
}
private getResult($match){
if(!array_key_exists($match['id'], $this->results)){
$this->results[$match['id']] = $this->evaluateResult($match);
}
return $this->results[$match['id']];
}
- Pokud danou komponentu potřebuješ v jiné komponentě, tak jí injectuj konstruktorem přímo do dané komponenty:
<?
namespace App\FrontModule\Components;
use Nette\Application\UI,
Nette,
App;
class Results extends UI\Control
{
protected $database;
protected $modelResult;
private $matchResultFactory;
public function __construct (Nette\Database\Context $database, App\Model\Result $modelResult, App\FrontModule\Components\MatchResultFactory $matchResultFactory)
{
$this->database = $database;
$this->modelResult = $modelResult;
$this->matchResultFactory = $matchResultFactory;
}
public function render($stageId)
{
$this->template->setFile(__DIR__ . '/../templates/components/Results/list.latte');
$this->template->stageId = $stageId;
$this->template->modelResult = $this->modelResult;
$this->template->matches = $this->database->table('match')->where('stage_list_id', $stageId)->order('id');
$this->template->render();
}
protected function createComponentMatchResult(){
return $this->matchResultFactory->create();
}
}
Editoval Etch (29. 11. 2014 17:47)
- Kcko
- Člen | 468
**Edit2: **
Tak jsem se posunul ještě dál, předchozí úprava sice funguje, ale nezdá
se mi, že by to úplně správně (třeba mě někdo opraví), a přesunul jsem
komponentu MatchResult do komponenty Result (pod tu totiž
hiearchicky patří)
tj.
<?
namespace App\FrontModule\Components;
use Nette\Application\UI,
Nette,
App;
class Results extends UI\Control
{
.
.
.
protected function createComponentMatchResult()
{
$control = new MatchResult();
return $control;
}
V šabloně poté můžu udělat něco jako
{foreach $matches as $match)
.
.
{?
$matchResult = $control['matchResult'];
$matchResult->evaluateResult($match); // tímto se zbavím vyhodnocení v renderu
}
// výsledek!
{$matchResult->renderInline()}
.
.
{/foreach}
Ted už mi tedy není jasné proč bych na tohle měl použít Multiplier a za další když jsem to trochu zkoumal, tak multiplier a komponenta v něm se identifikuje podle 1 jednoznačného identifikátoru. Co kdybych jich potřeboval víc?
- Kcko
- Člen | 468
@Etch Díky, 3/ je jasná, zatím si tam asi nechám ale to co jsem spatlal já. Na 4/ jsem už taky obdobně přišel.
Vrtá mi hlavou Multiplier.
Dejme tomu, že mám tento formulář:
protected function createComponentTestForm()
{
return new Nette\Application\UI\Multiplier(function ($itemId) {
$form = new Nette\Application\UI\Form;
$form->addText('zapas', 'Zápas')->setValue($itemId);
$form->addSubmit('send', 'Odeslat');
return $form;
});
}
V šabloně
{var $counter = 0}
{foreach $matches as $match}
{? $counter++}
{control testForm-$number}
{/foreach}
A já bych v té šabloně tomu formuláři chtěl předat nějaká data, v tomto případě $counter a $match (nejde mi ted o ty data, ale o způsob jak to tam dostat, aby o tom formulář veděl).
A Ještě hypoteticky, kdyby tohle nebyl formulář, ale třeba opět nějaký výpis zápasů a výsledků a potřeboval bych tu kontrolku nějak nastavit přes metodu, můžu to udělat stejně jako v předchozím postu?
{?
$matchResult = $control['matchResult'];
$matchResult->evaluateResult($match); // tímto se zbavím vyhodnocení v renderu
}
// výsledek!
{$matchResult->renderInline()}
Bylo by hezčí, tohle dělat někdě na úrovni Presenteru/Komponenty a ne v šabloně, ale tady to nejde a já potřebuji znát způsob jak dostat data ze šablony do komponenty (v tomto případě přes Multiplier).
Tohle je poslední dotaz.
- Etch
- Člen | 403
@Kcko :
ad tohle:
{foreach $matches as $match)
{?
$matchResult = $control['matchResult'];
$matchResult->evaluateResult($match); // tímto se zbavím vyhodnocení v renderu
}
// výsledek!
{$matchResult->renderInline()}
{/foreach}
Tohle není dobré rešení a napadají mě i hned dva důvody proč.
- Udělal si z metody nutné pro správný chod komponenty metodu nepovinnou.
Problém je, že když budu chtít použít danou komponentu, tak jako člověk který jí nezná udělám automaticky tohle:
{foreach $matches as $match)
{control matchResult:inline}
{/foreach}
protože mě vůbec nenapadne (teda samozřejmě mě napadne, že tý
komponentě nepředávám nic z čeho by to mohla vykreslit, ale teoreticky já
nemůžu vědět co je v té komponentě za magii), že bych předtím měl
volat ještě nějaké
$control['matchResult']->evaluateResult($match);
. Skončí to
tedy v lepším případě nějakou výjimkou, že nebyla zavolaná metoda
$control['matchResult']->evaluateResult($match);
a v horším
případě nějakou „chybou“ v šabloně typu Undefined index
nebo Undefined variable
, což mě donutí otevřít danou
komponentu a studovat její kód.
- Z pohledu počtu volání metody
evaluateResult($match)
je tento způsob výhodný jen na první pohled a ve „složitějších“ případech bude naopak kontraproduktivní.
Tohle je sice pravda:
{foreach $matches as $match)
{?
$matchResult = $control['matchResult'];
$matchResult->evaluateResult($match); // tímto se zbavím vyhodnocení v renderu
}
// výsledek!
{$matchResult->renderFull()}
{$matchResult->renderInline()}
{/foreach}
a výpočet v metodě proběhne jenom tolikrát kolik je zápasů, ale to je jenom „náhoda“ a je to příliš zjednodušený pohled na danou věc. Ve většině použití to bude takováto implementace spíše kontraproduktivní.
# Nějaká tabulka
{foreach $matches as $match)
{?
$matchResult = $control['matchResult'];
$matchResult->evaluateResult($match); // tímto se zbavím vyhodnocení v renderu
}
// výsledek!
{$matchResult->renderFull()}
{/foreach}
#Nějaký postraní box na stejné stránce
{foreach $matches as $match)
{?
$matchResult = $control['matchResult'];
$matchResult->evaluateResult($match); // tímto se zbavím vyhodnocení v renderu
}
// výsledek!
{$matchResult->renderInline()}
{/foreach}
Počet zápasů je 10. Pro výpis musí být metoda
evaluateResult($match)
volána 20×. Oproti tomu v implementaci ,
kterou jsem zde uvedl jako ukázku:
# Nějaká tabulka
{foreach $matches as $match}
{control matchResult:full, $match}
{/foreach}
#Nějaký postraní box na stejné stránce
{foreach $matches as $match}
{control matchResult:inline, $match}
{/foreach}
bude metoda evaluateResult($match)
volána jen 10×, je to
přehlednější na zápis, je to „blbu vzdorné“, protože se to dokáže
o sebe postarat samo a nespoléhá to na to, že uživatel (programátor) zná
střeva dané komponenty.
Prostě mi přijde, že situace ve kterém je potřeba vypsat v jednom cyklu N různých druhů zobrazení jedné komponenty není příliš častá.
Pokud jde o ten Multiplier tak tam by chtělo uvést na nějakém triviálním příkladu čeho přesně chceš dosáhnout.
Editoval Etch (29. 11. 2014 20:12)
- Kcko
- Člen | 468
@Etch
Super, díky moc za pěkný rozbor, vidím, že to co jsem jako poslední napsal, je blbost, i když to funguje. Vypadá to divně a není to moc přehledná komponenta (hlavně v použití v šabloně).
Tady
# Nějaká tabulka
{foreach $matches as $match}
{control matchResult:full, $match}
{/foreach}
#Nějaký postraní box na stejné stránce
{foreach $matches as $match}
{control matchResult:inline, $match}
{/foreach}
Tohle je fajn, doupravím to na tento způsob.
Můžu ještě požádat o informaci k tomu co jsem psal ohledně Multiplieru, zkouknul jsem i video J. Tvrdíka o komponentách, ale tam tohle neřeší. Tam sice nastaví interní ID článku, ale v presenteru když ho zná a ne ze šablony, můj post (Viz https://forum.nette.org/…-jak-nejlepe#… , začátek postu).
Třeba chci mít pod každým zápasem formulář a k němu do hidden pole docvaknout ID zápasu, ID souteze, ID sezóny a ještě něco a tyhle údaje budu znát jen ze šablony. (Popsal jsem to v tom prispevku).
Nebo znovu
protected function createComponentTestForm()
{
return new Nette\Application\UI\Multiplier(function ($matchId) {
$form = new Nette\Application\UI\Form;
$form->addHidden('zapas', 'Zápas')->setValue($matchId);
$form->addHidden('counter', 'Counter')->setValue(???);
$form->addHidden('competitionId', 'Competition')->setValue(???);
$form->addHidden('seasonId', 'Season')->setValue(???);
$form->addSubmit('send', 'Odeslat');
return $form;
});
}
V šabloně
{var $counter = 0}
{foreach $matches as $match}
{? $counter++}
{control testForm-$number} // jak sem dostanu ten counter a pak soutez a sezonu ktera se nachazi v $match
{/foreach}
Editoval Kcko (29. 11. 2014 20:26)
- Etch
- Člen | 403
Kcko napsal(a):
Třeba chci mít pod každým zápasem formulář a k němu do hidden pole docvaknout ID zápasu, ID souteze, ID sezóny a ještě něco a tyhle údaje budu znát jen ze šablony. (Popsal jsem to v tom prispevku).
To není pravda… Dané pole máš přístupné již v komponentě
Results
.
{var $counter = 0}
{foreach $matches as $match}
{? $counter++}
{control testForm-$number} // jak sem dostanu ten counter a pak soutez a sezonu ktera se nachazi v $match
{/foreach}
Místo $counteru se dá použít {$iterator->counter}. K čemu ten counter slouží??
Editoval Etch (29. 11. 2014 20:47)
- Etch
- Člen | 403
@Kcko
Jak jsem řekl záleží na situaci. Pokud se bude jednat o nějaká primitivní data (například idčka) můžeš například využít toho, že Multiplier vnoříš do sebe.
protected function createComponentTestForm(){
$presenter = $this;
return new UI\Multiplier(function ($matchId) use ($presenter) {
return new UI\Multiplier(function($counter) use ($presenter, $matchId) {
$form = new UI\Form;
$form->addHidden('zapas')->setValue($matchId);
$form->addHidden('counter')->setValue($counter);
$form->addSubmit('send', 'Odeslat');
$form->onSuccess[] = array($presenter, 'successMethod');
return $form;
});
});
}
a v šabloně zavoláš:
{foreach $matches as $match}
{control testForm-$match->id-$iterator->counter}
{/foreach}
Stejně tak si můžeš upravit samotný form:
class MyForm extends UI\Form{
public function render($data = array()){
$this->setDefaults($data);
parent::render();
}
}
(Upozorňuji, že tenhle „hack“ je zde pouze jako ukázka, že toho jde docílit hodně způsoby. Rozhodně to není nic co by se mělo používat!!!)
protected function createComponentTestForm(){
$presenter = $this;
return new UI\Multiplier(function($matchId) use ($presenter) {
$form = new MyForm();
$form->addHidden('zapas')->setValue($matchId);
$form->addHidden('counter');
$form->addSubmit('send', 'Odeslat');
$form->onSuccess[] = array($presenter, 'successMethod');
return $form;
});
}
{foreach $matches as $match}
{? $data['counter'] = $iterator->counter}
{control testForm-$match->id, $data}
{/foreach}
Způsobů jak toho docílit bude miliarda, ale mě osobně nepřijde vhodné, aby se takovéhle věci dělaly v šabloně.
Editoval Etch (30. 11. 2014 2:16)
- Kcko
- Člen | 468
@Etch – koukám a vidím, ten dvojitý multiplier už je ugly. Ted
jsem si zkoušel jednoduchý příklad s výpisem knih a nastavil jsem to tam
kde se to asi nastavovat má. Tenhle příklad je velmi
jednoduchý, v realném by autor byl z jiné tabulky (ref) a vadí mi tady
dvojí průchod. Jednou v šabloně, jednou v presenteru.
Jsem zvyklý z NDBT si vracet jen Selection a v šabloně pracovat
s aktivním view (tj. používat ref, related ..) a v tomto případě bych si
musel model upravit aby mi to vracel všechno najednou seskládané v nějakém
poli, aby mě ten dvojí průchod nemrzel.
Tj. něco jako
function getBooks()
{
$selection = $this->database->table('book');
$data = array();
foreach ($selection as $sel)
{
$data['baseData'][] = iterator_to_array($sel);
$data['autor'][] = $sel->author->name; // ref na jinou tabulku
// pak treba tagy -- Related --
a dalsi struktury
}
return $data;
}
Je to tak správně?
public function actionBook()
{
$this->template->books = $this->database->table('book');
foreach ($this->template->books as $book)
{
$this['bookForm-'.$book->id]->setDefaults($book);
}
}
protected function createComponentBookForm()
{
return new Nette\Application\UI\Multiplier(function ($id) {
$form = new Nette\Application\UI\Form;
$form->addText('id', 'Id')->setValue($id);
$form->addText('name', 'Name');
$form->addText('author', 'Author');
$form->addText('pages', 'Pages');
$form->addSubmit('send', 'Odeslat');
return $form;
});
}
- Etch
- Člen | 403
No jenže se mi opět tak nějak vnucuje myšlenka, kdy bych takovouhle funkčnost mohl reálně potřebovat.
Mám výpis knih a formulář pro „editaci“ každé knihy. V jakou chvíli nastane situace, že potřebuju pro 5O knih vypsat padesát editačních formulářu, které mi jsou stejně k ničemu, protože odeslat mohu reálně jen jeden z nich?
To už by snad bylo jednodušší udělat třeba tohle:
public function actionDefault($renderEditFormForBook = null){
if($renderEditFormForBook){
$book = $this->booksRepository->find($renderEditFormForBook);
$this['editForm']->setDefaults($book);
}
$this->template->books = $this->booksRepository->findAll();
$this->template->renderEditFormForBook = $renderEditFormForBook;
}
{foreach $books as $book}
{if $renderEditFormForBook == $book['id']}
{form editForm}
{input name}
{input author}
{input pages}
{input send}
{/form}
{else}
{$book['name']} - <a n:href="default, 'renderEditFormForBook' => $book['id']">Edit Book</a><br>
{/if}
{/foreach}
popřípadě to dát do nějakého signálu.
Opravdu mě teď nenapadá moc důvodů, proč pro běžnou editaci vytvářet X formulářů. Přijde mi, že ve většině případů, kdy je potřeba renderovat velké množství stejných formulářu, se jedná o formuláře například pro mazání záznamu.
Editoval Etch (30. 11. 2014 19:30)
- Oli
- Člen | 1215
Multiplier není primárně uečen na několik opakujících se formulářů tak aby jsi je odeslal všechny naráz. K tomu je určený Kdyby/Replicator.
Multiplier se hodí například pokud máš výpis článků a máš u každého článku komponentu (hodnocení/poznámky k článku/přidat do oblíbených/…). V takovém případě víš konkrétní ID článku až ve chvíli, kdy ten článek vypisuješ a potřebuješ ho předat komponentě. Zároveň potřebuješ tolik instancí komponenty, kolik máš vypsaných článků. Potom si jednou spočítáš data a pak už jen měníš metodu renderu.
- Kcko
- Člen | 468
@Oli Já jsem ale nepsal nikde nic o tom, že bych chtěl Multiplier používat na hromadné odesílání. Chtěl jsem mít záznamy a pod každým mít formulář s uloženýma datama a odesílat je po jednom a řešil sem jak tam docvaknout uložené $values.
@kzk_cz :))) ne. GW je napsaný sice po staru, ale s Nette bych si nepomohl, je tam hodně vyladěných SQL-ek a GW funguje bezvadně, do toho již hrabat nebudu. Ty si kdo? :)
- Oli
- Člen | 1215
@Kcko No ona to byla spíš reakce na @Etch
Mám výpis knih a formulář pro „editaci“ každé knihy. V jakou chvíli nastane situace, že potřebuju pro 5O knih vypsat padesát editačních formulářu, které mi jsou stejně k ničemu, protože odeslat mohu reálně jen jeden z nich?
Na to se právě multiplier nehodí, pokud to chceš poslat naráz všechno.
- Etch
- Člen | 403
@Oli
Replikátor to řeší na úrovni kontejneru a vygenerován bude prakticky pouze jeden formulář. Jenže právě @Kcko řešil generování X stejných formulářů, které chtěl odesílat po jednom viz:
Já jsem ale nepsal nikde nic o tom, že bych chtěl Multiplier používat na hromadné odesílání. Chtěl jsem mít záznamy a pod každým mít formulář s uloženýma datama a odesílat je po jednom a řešil sem jak tam docvaknout uložené $values.
Proto jsem se ptal v jakou chvíli nastane situace kdy je tohle potřeba, protože já teď rozhodně na žádnou nedokážu přijít. Narážel jsem tedy hlavně na to, že generovat X stejných formulářů, když reálně lze odeslat vždy jen jeden je podobné jako vytáhnout tabulku s miliardou záznamů z databáze a dělat „in-memory limit“ na 100 záznamů.
A už vůbec nemluvím o tom, že takto vygenerované formuláře budou zcela evidentně mást uživatele, protože to právě na první pohled může vypadat jako multi edit a úplně vidím jak by se ten člověk tvářil po redirectu, když by v domnění že se jedná o multi edit „najednou“ upravil 10 formulářů a kliknul na jeden ze submitů. :)
- Pavel Kravčík
- Člen | 1195
@Kcko: Kozel. Já už tam moc nechodím, ale Lahva by se měl lépe. :))
Jinak super vlákno, hodně mi to dalo. Hlavně ten příspěvek od @Etch je super!
- Kcko
- Člen | 468
@Etch moje ovečky si občas vymýšlejí věcí … viz http://files.rjwebdesign.cz/…--t---pn.png (co záznam to formulář + ajax, v administraci samozřejmě modul na hráče byl, ale tohle byla „rychlá“ editace …)
@kzk_cz tož vítaj mezi Nette greenhorny ;)
- Oli
- Člen | 1215
@Etch tak to s omlouvám, došlo k informačnímu šumu. Jsem to četl na mobilu a na počítači odpovídal až zpětně a asi ne uplně aktuálně :-)
Nicméně použil jsem to generování formuláře pomocí multiplikátoru. Bylo to u výpisu článků ke kterým si po kliknutí na ikonku mohl přihlášený uživatel přidat nějakou osobní poznámku. Tam jednoznačně chceš aby se ti otevřel jeden formulář u konkrétního článku. Ale je pravda, že to není zrovna nejčastější požadavek ;-)
@Kcko A nebylo by lepší to vykreslit jako jeden formulář s jedním odesílacím tlačítkem? Nevím jak často se tam mění víc „formulářů“ naráz…
Editoval Oli (1. 12. 2014 18:09)
- Etch
- Člen | 403
@Oli Jasně Multiplier je skvělej a najde řadu uplatnění. Úplně nejklasičtější použití jsou tlačítka smazat, které je potřeba ošetřit proti CSRF. Dovedu si představit jeho použití v mnoha situacích, ale podle mě by nikdy němělo UI s takto vytvořenými formuláři budit dojem, že se jedná o hromadou editaci. Vždycky samozřejmě záleží na konkrétní situaci, ale třeba generovat s každým requestem 50 formulářů, když daná stránka v 90% slouží jenom pro zobrazování dat a jen v 10% se něco opravdu updatuje a navíc to má tu vlastnost, že se to tváří jako něco co to není, mi přijde jako nesmysl. Třeba ten screenshot, co poslal @Kcko je toho krásný příklad.
@Kcko Tak to je přesně ten případ formuláře, který na první pohled vypadá, že můžu změnit všechno a přitom to tak není. :)
Tohle by mělo:
- být v jednom formuláři, aby byla jistota, že uživatel nepřijde o žádná editovaná data.
- nebo by se měl formulář zobrazit až na vyžádání. Třeba nějaké tlačítko.
- nebo by odesílání té hromady formulářů muselo být ajaxem po úspěšném odeslání nějakého formuláře by se nesměly formuláře překreslovat, ale musela by se překreslit jen nějaká část stránky se zprávou že se data povedlo nebo nepovedlo uložit a zároveň by tam mělo být nějak jasně specifikováno, které data byla opravdu uložena.
- nebo by tam šel udělat nějaký inline edit.
První varianta
Teoreticky neprůstřelná. Nevýhodou je, že se buď musí ověřovat, která data byla opravdu změněna a nebo se smířit s tím, že bude update proveden vždy na všech řádcích.
Uživatel nemůže přijít o data, bude to fungovat jak normálně tak ajaxem, je možný hromadný update. [funkční s JS i bez]
Druhá varianta
teoreticky také neprůstřelná. Nevýhodou je jedno kliknutí navíc, které je nutné k zobrazení formuláře.
Uživatel nemůže přijít o data, bude to fungovat jak normálně tak ajaxem, je možný jen update po jednom. [funkční s JS i bez]
Třetí varianta
by fungovala spíše z leknutí. Nesměly by se překreslovat formuláře, ale jen zobrazovat nějaká zpráva, aby byla jistota, že uživatel nepřijde o data.
Uživatel může přijít o data, bude to fungovat (spolehlivě) jen ajaxem, je možný update jen po jednom. Navíc by bylo ideálně potřeba, aby bylo vidět, které záznamy jsou změněny a neuloženy. (O data uživatel přijde pokud bude mít vypnutý javascript) [funkční s JS i bez s tím že uživatel přijde bez JS o data]
Čtvrtá varianta
by měla také fungovat bez větších problémů.
Uživatel nemůže přijít o data, bude to fungovat jak normálně tak ajaxem, pravděpodobně bude možný jen update po jednom. [funkční jen s JS]
Pojmem „přijít o data“ je myšleno to, že uživatel má možnost změnit i věci, které po submitu nebudou uloženy.
EDIT: Variant jak to udělat bude samozřejmě více. ;)
Editoval Etch (2. 12. 2014 2:11)
- Oli
- Člen | 1215
Přikláněl bych se k bodu 1 a 2.Záleží na použití. Pokud se jedná o výpis dat, která můžou být editována a budou editována stylem „jeden řádek za týden“ tak bych použil variantu 2+4 (inline editace po nějaké akci – jako adminer). Pokud se budou měnit data často tak jednoznačně variantu 1, jinak tě uživatelé roztrhají :-)
@Etch
trochu OT: Zrovna k tomu mazání bych nepoužíval formulář. Někde byla
o tom diskuse a je pravda, že formulář k tomu většinou není určenej.
Pokud se bavíme o formuláři, kterej má jen tlačítko smazat
.
Proti CSRF to ošetřuju pomocí Nextras/secured-link.
- Kcko
- Člen | 468
@Etch v jednom určitě ne, nevím jestli jsi se díval na ten screenshot, ale je X stovek záznamů vysypaných na jednu stránku, kdy je potřeba editovat záznamy z jedné tabulky (ta tabulka má cca 30 sloupečků) a bylo potřeba „rychle“ zeditovat asi 3–4. Samozřejmě se to ukládá ajaxově. Přirovnal bych to k editaci v excelu.
Takže co 1 záznam = 1 malý formulář + submit ⇒ odešle se ajaxem a uživatel přeskočí na další řádek a opět edituje.
- mpis
- Člen | 65
Kcko napsal(a):
Takže co 1 záznam = 1 malý formulář + submit ⇒ odešle se ajaxem a uživatel přeskočí na další řádek a opět edituje.
Tohle jde řešit snadno a rychle pomocí gridu s možností editovat
přímo v něm.
Je jich tady několik – addons.nette.org/
- Kcko
- Člen | 468
@mpis ano to určitě, ale tak chtěl jsem si vyzkoušet práci v surovém Nette ;-) a dobral jsem se k tomu, že tohle řešení je funkční https://forum.nette.org/…-jak-nejlepe#…