Opakované načítání dat z modelu v renderu při použití handle metody
- Neonode
- Člen | 9
Ahoj, nejdřív zjednodušeně co a jak teď mám.
V presenteru si do komponenty prostrčim selection.
protected function createComponentTable(): TableControl
{
return $this->tableControlFactory->create($this->facade->findAll());
}
V komponentě načtu data.
public function getData(): Selection
{
return $this->items->page($this->page, $this->per_page, $this->total);
}
A potom v render metodě té komponenty pošlu data do šablony.
public function render(): void
{
$this->template->items = $this->getData();
}
A nakonec to o co mi jde, handle metoda na otevření modal okna
public function handleModal(): void
{
$this->payload->modalId = 'table';
$this->payload->showModal = true;
$this->redrawControl('table_modal');
}
A všechno funguje dobře. Jen mi hrozně vadí, že jelikož je render metoda té komponenty volaná pokaždé, tedy i při handleModal(), tak i při otevření modal okna se mi znova načítaj data z DB.
Můžu nějak přeskočit překreslení a tim nenačítat data? Nebo mám mít načítání někde jinde, nějak jinak? Asi to můžu nějak oifovat, nebo použít cache, ale doufám existuje nějaké další řešení :)
Díky moc!
Editoval Neonode (8. 10. 2023 12:11)
- m.brecher
- Generous Backer | 873
@Neonode
V presenteru si do komponenty prostrčim selection.
Doporučuji dodržovat standardní MVC a využívat DI, kód je potom přehlednější. Místo předání Selection z presenteru do komponent factory předat pomocí DI celou modelovou třídu Facade rovnou do komponenty (jako službu) a v komponentě si z této třídy vytahat data jak potřebuješ.
Metodou create() komponent factory používej na předání parametrů z presenteru (typicky $id záznamu v databázi z requestu)
A všechno funguje dobře. Jen mi hrozně vadí, že jelikož je render metoda té komponenty volaná pokaždé, tedy i při handleModal(), tak i při otevření modal okna se mi znova načítaj data z DB.
Obvykle jeden sql dotaz navíc nijak výkonově ničemu nevadí, přeskočit vykreslení asi nepůjde. Co by se dalo udělat je cachování Selection v modelové třídě, ale otázkou je zdali potenciální potíže a práce navíc za to stojí.
- Neonode
- Člen | 9
Ahoj, díky moc za reakci.
Ta komponenta by ideálně měla být univerzální, rsp tak nějak si to
představuju. Takže komponenta co udělá tabulku s výpisem nějakých dat.
A co to je za data, by nemělo být definováno v té komponentě, ale měl
bych data do té komponenty předat ne? Vůbec se nehádám, jen si
představuju, že když budu mít pět stránek, na každé stejná tabulka –
jen jiná data, měla by ta komponenta s tabulkou jen vypsat data co
dostane.
Obvykle jeden sql dotaz navíc nijak výkonově ničemu nevadí, přeskočit vykreslení asi nepůjde. Co by se dalo udělat je cachování Selection v modelové třídě, ale otázkou je zdali potenciální potíže a práce navíc za to stojí.
Jasný, díky. Mě to vadí protože v kombinaci s tím, že při použití
page
pro paginaci, to vždy dělá i dotaz na počet stránek, **SELECT COUNT(*) **
. Takže je tam těch dotazů navíc víc.
Ale máš pravdu, že to není to nejdůležitější, jen mě zajímalo jak by
se to dalo elegantně vyřešit.
- mystik
- Člen | 313
Ted jak to ctu podruhe tobe jde o to ze v te komponente v tom modal ukazujes jen jeden radek takze nechces nacitat vsechna data, kdyz potrebujes jen jeden prvek pro presleni AJAXem? Tak je pak reseni v tom ze handle zjisti zda jde o AJAX a pokud ano tak nacte data jen pro ten jeden prvek (prida podminku na selection) a ulozi do property. A v render data nacitas jen pokud je nenacetl handle tj pokud ta property je null.
- m.brecher
- Generous Backer | 873
@Neonode
Ta komponenta by ideálně měla být univerzální
OK, pak je to v pořádku, přehlednější zápis by byl třeba takhle (to ale není tak důležité):
// místo:
protected function createComponentTable(): TableControl
{
return $this->tableControlFactory->create($this->facade->findAll());
}
//takhle:
protected function createComponentTable(): TableControl
{
$table = $this->tableControlFactory->create();
$table->setRows($this->facade->findAll());
return $table;
}
// s použitím pojmenovaných parametrů
protected function createComponentTable(): TableControl
{
return $this->tableControlFactory->create(
rows: $this->facade->findAll(),
);
}
Co se týče duplicity sql dotazů, není mě úplně jasné, jak ta komponenta má fungovat. Máš nějaký tabulkový výpis a po kliknutí na jeden řádek chceš otevřít ajaxem přes tu tabulku modální překryvné okno, ve kterém by byly data jednoho řádku ??
- mystik
- Člen | 313
Zhruba takhle nějak:
private array $rows;
public function getData(): Selection
{
return $this->items->page($this->page, $this->per_page, $this->total);
}
public function render(): void
{
$this->template->items = $this->rows ?? $this->getData();
}
public function handleModal(int $rowId): void
{
$this->payload->modalId = 'table';
$this->payload->showModal = true;
if($this->isAjax()) {
$this->rows = $this->items->where('id', $rowId);
}
$this->redrawControl('table_modal');
}
Editoval mystik (8. 10. 2023 14:20)
- Neonode
- Člen | 9
@mystik
Tak je pak reseni v tom ze handle zjisti zda jde o AJAX a pokud ano tak nacte data jen pro ten jeden prvek (prida podminku na selection) a ulozi do property. A v render data nacitas jen pokud je nenacetl handle tj pokud ta property je null.
To jsem zkoušel, ale on je problém v tom, že když v renderu třeba
s použitím if ($this->httpRequest->getQuery(‚do‘) ..... )
{ data nenačtu, pokud je to openModal, tak pak se komponenta
překreslí jako prázdná.
Takže mám query pro detail by id, query pro list, query pro COUNT – jen
tím, že otevřu modal pro editaci itemu.
private array $rows;
public function getData(): Selection
{
return $this->items->page($this->page, $this->per_page, $this->total);
}
public function render(): void
{
$this->template->items = $this->rows ?? $this->getData();
}
public function handleModal(int $rowId): void
{
$this->payload->modalId = 'table';
$this->payload->showModal = true;
if($this->isAjax()) {
$this->rows = $this->items->where('id', $rowId);
}
$this->redrawControl('table_modal');
}
Zkusim, díky moc :)
@mbrecher
Díky moc, zkusím setRows.
Co se týče duplicity sql dotazů, není mě úplně jasné, jak ta komponenta má fungovat. Máš nějaký tabulkový výpis a po kliknutí na jeden řádek chceš otevřít ajaxem přes tu tabulku modální překryvné okno, ve kterém by byly data jednoho řádku ??
Přesně, je tabulka s výpisem. Když kliknu na edit, otevře se modal
s formulářem a do formu se načtou default hodnoty.
A otevření modalu znamená render komponenty a to znamená že se znova
načtou i data do tabulky.
Ale jak říkáš, ono to vlastně ničemu nevadí, jen mě to trošku vnitřně
štve :)
Díky moc!
Editoval Neonode (8. 10. 2023 14:27)
- mystik
- Člen | 313
Pockat takze problem je ze se ti pri AJAXu prekresli cela komponenta kdyz by se ti mel prekreslit jen modal? Jak mas resene AJAX pozadavky? Dival ses co se ti vraci v AJAX response za snipetty k prekresleni?
Co ma delat ta podminka s httpRequest? Je to dost zvlastni kontstrukce.
Editoval mystik (8. 10. 2023 15:15)
- Neonode
- Člen | 9
@mystik
Hm, to bude nejspíš ten jedinej a hlavní problém, že mám špatně daný snippety v šabloně. A tak se mi překresluje komponenta s tabulkou, když zobrazuju modal okno.
No a je to proto, že mam v presenteru zapomenuté
$this->redrawControl();
Ach jo, díky moc!
Editoval Neonode (8. 10. 2023 15:44)
- m.brecher
- Generous Backer | 873
@Neonode
Už jsem přišel na to, proč se duplikuje sql dotaz pro celou tabulku. Ajax snippety v Nette totiž tímto způsobem pracují – viz dokumentace. Kdykoliv se v komponentě použije v handleru signálu
$this->redrawControl(snippet: 'mySnippet') ;
tak se vykoná komplet metoda render() komponenty a vykreslí se CELÁ šablona, jenom se do prohlížeče neposílá celá, ale požadovaný snippet.
Takže už v principu se sql pro tabulku vykoná duplicitně, přestože není pro vykreslení modálního okna s formulářem jednoho řádku potřeba.
Jde to celkem snadno vyřešit a to tak, že:
a) vytvoříme v komponentě proměnnou která bude řídit vykreslení
tabulky nebo modálního okna
b) v metodě render oifujem kód, kde se získávají data pro šablonu
tabulky
c) v šabloně oifujeme kód pro tabulku – bez dat by se vyhodila chyba
d) v šabloně oifujeme kód uvnitř snippetu, aby se ve výchozím stavu
vykreslil prázdný snippet, jinak nebude redraw fungovat
Ukázka otestovaného kódu, lze použít libovolnou tabulku – vypisují se všechny sloupce:
Presenter:
final class TestPresenter extends Presenter
{
public function __construct(private readonly MyTableModel $myTableModel)
{}
public function createComponentDataTable(): DataTable
{
return new DataTable(dataTableModel: $this->myTableModel);
}
}
Komponenta pro tabulku a editační panel (modální okno)
class DataTable extends Control
{
private string $view = 'tableView';
private int $id;
public function __construct(private TableModel $tableModel)
{}
public function handleOpenEditPanel(int $id)
{
$this->view = 'itemView';
$this->id = $id;
if($this->presenter->isAjax()){
$this->redrawControl(snippet: 'editPanelWrapper');
$this->redrawControl(snippet: 'editPanel');
}
}
public function handleCloseEditPanel()
{
if($this->presenter->isAjax()){
$this->redrawControl(snippet: 'editPanelWrapper');
$this->redrawControl(snippet: 'editPanel');
}
}
public function render()
{
match($this->view){
'tableView' => $this->renderTable(),
'itemView' => $this->renderForm(),
};
$this->template->view = $this->view;
$this->template->render(__DIR__.'/dataTable.latte');
}
private function renderTable()
{
$table = $this->tableModel->getAll();
$this->template->columns = $this->getTableColumns($table);
$this->template->rows = $table;
}
private function renderForm()
{
$currRow = $this->tableModel->getOne($this->id);
$this->template->currRow = $currRow;
if(!$this->presenter->isAjax()){
$this->renderTable();
}else{
$this->template->columns = $this->getRowColumns($currRow);
}
}
/* utils */
private function getRowColumns(ActiveRow $row): array
{
return array_keys($row->toArray() ?: []);
}
private function getTableColumns(Selection $table): array
{
return array_keys($table->fetch()->toArray() ?: []);
}
Šablona tabulky dataTable.latte:
<h2>Table</h2>
{if $view === 'tableView' || !$presenter->isAjax()}
<table class="table">
<tr class="heading">
{foreach $columns as $column}
<td>{$column}</td>
{/foreach}
<td></td>
</tr>
{foreach $rows as $row}
<tr>
{foreach $columns as $column}
<td>{$row->$column}</td>
{/foreach}
<td><a n:href="openEditPanel!, id: $row->id" class="text-link ajax">Editovat</a></td>
</tr>
{/foreach}
</table>
{/if}
{snippetArea 'editPanelWrapper'}
{include 'editPanel.latte', columns: $columns ?? [], row: $currRow ?? null}
{/snippetArea}
Šablona editačního panelu (modální okno) editPanel.latte
{snippet 'editPanel'}
{if $view === 'itemView'}
<h2>Edit panel</h2>
<p><a n:href="closeEditPanel!" class="text-link ajax">Zavřít panel</a></p>
<table class="table">
<tr class="heading">
<td>Sloupec</td>
<td>Hodnota</td>
</tr>
{foreach $columns as $column}
<tr>
<td>{$column}:</td>
<td>{$row->$column}</td>
</tr>
{/foreach}
</table>
{/if}
{/snippet}
Šablona akce presenteru default.latte:
{block 'content'}
{control 'dataTable'}
{/block}
{block 'head'}
{include parent}
<script src="https://unpkg.com/naja@2/dist/Naja.min.js"></script>
<script>
naja.initialize();
</script>
{/block}
Modelová třída:
class MyTableModel implements TableModel
{
public function __construct(private Explorer $database)
{}
public function getAll(): Selection
{
return $this->database->table('mytable');
}
public function getOne(int $id): ActiveRow|null
{
return $this->database->table('mytable')->get($id);
}
}
Pomocný interface pro modelovou třídu – vynutí existenci používaných metod:
interface TableModel
{
public function getAll(): Selection;
public function getOne(int $id): ActiveRow|null;
}
Editační panel se otevírá signálem openEditPanel a zavírá signálem closeEditPanel, aniž by se opakovaně odečítaly data pro tabulku.
Kód funguje i s vypnutým javascriptem.
Vypisuje libovolnou tabulku, v editačním panelu je výpis dat řádku, který lze nahradit editačním formulářem.
Editoval m.brecher (9. 10. 2023 3:09)