reprezentace hierarchicky usporadanych dat – pouzit snippet?
- wdolek
- Člen | 331
hloubam a lamu si hlavu, jak nejlepe reprezentovat v administracni casti webu hierarchicky usporadane data.
zaznam galerie obsahuje odkaz na rodicovskou galerii, poradi/prioritu, jmeno, … a mozna by se jeste neco naslo. strom galerii definuji nejen odkazem na rodice, ale i pomoci cisel levy/pravy ( http://articles.sitepoint.com/…a-database/2 ).
zpusob, jakym chci data zobrazit, je nasledujici: na uvodni strance jsou vypsany pouze korenove galerie (parent=NULL). u galerie, ktera ma nejake pod-galerie je tlacitko, po jehoz zmacknuti se vyroluje (nejaky jQuery efekt – nepodstatne) pod aktualnim radkem dalsi tabulka se seznamem primych potomku teto galerie (pekne dynamicky AJAXem)… vlastne ta sama tabulka (plus nejak graficky odsazena), jen s tim, ze se nevypisuji galerie, ktere maji parent=NULL, ale parent=<id>
vzhledem k tomu, ze chci vlastne pouzivat stejny blok (tabulku) jen se zmenou „vstupnich“ dat, jsem si rikal, ze bych mohl vyrobit nejaky snippet.
jenze… se snippety sem nikdy nic nedelal a nikdy ani zadny nesplodil :) a vlastne si ani nejsem jistej, jestli je to spravny napad. z clanku o snippetech v dokumentaci zas tak uplne moudrej nejsem – nezda se mi, ze by to bylo vhodne. (a datagrid ukazku jsem prilis intelektualne nepozrel)
tedy bych vyzvedel od moudrejsich a zkusenejsich, co vy na to? nejaky tip? rada? tutorial? odkaz? nebo by to slo uplne jinak (treba ze by se to naprgalo samo)?
predem diky za kazde pismenko :)
- srigi
- Nette Blogger | 558
Dolezite je vediet, co ti „nested set“ vrati. Vacsina stromovych struktur je v PHP reprezentovana ako rekurzivne pole, nested set nie. Ked ziskas cely strom/pod strom, v PHP budes mat cca toto
Vsimni si, ze je tam polozka level. A teraz je len na tebe, ci si strom zostavis na strane PHP (najlepsie ako UL-LI zoznam), alebo na strane klienta cez JS. Kvoli maximalnej pouzitelnosti je samozrejme najlepsie zostavit UL-LI HTML kod. Ak kces mat najskor dostupnu iba prvu uroven, urobis to celkom jednoducho, ze pomocou CSS skryjes vnorene ULka a potom ich zobrazis po nejakom kliknuti niekam :)
#tree ul ul { dislpay:none }
No a este ostava kod co ti z toho tam hore :) spravi UL-LI HTML kod. Netreba vymyslat vymyslene, ja to mam uz spravene, tu je kod (vyzera ako „prasarna“ ale ver mi, aj v Symfony to robia ludia takto, takze to prasarna nie je).
<ul{if !empty($class)} class="{$class}"{/if}>
{? $temp = 1; }
{foreach $tree as $node}
{if $node->level > $temp}
<ul>
{? $temp = $node->level;}
{elseif ($node->level == $temp) && (!$iterator->isFirst())}
</li>
{elseif $node->level < $temp}
</li></ul></li>
{? $temp = $node->level;}
{/if}
<li{if $node->getActive()} class="active"{/if}><a href="{plink Page:default 'lang'=>$lang, 'url'=>$node->getUrl($lang)}">{$node->getName($lang)}</a>
{/foreach}
</li>
</ul>
este pridam render metodu komponenty
class MenuControl extends Control
{
public function render($menu, $class = '')
{
$tree = ModelPages::getPagesTree($menu);
$template = $this->template;
$template->setFile(__DIR__ . '/MenuControl.phtml');
$this->template->class = $class;
$this->template->tree = $tree;
$template->render();
}
}
V mieste kde kces vykreslit strom zavolas v sablone
{widget menu 1,'sf-menu'}
enjoy
Editoval srigi (15. 5. 2010 5:31)
- wdolek
- Člen | 331
srigi: diky za kus kodu :) koukam na to, ale ja mam potiz jeste s tim, ze korenovych zaznamu mohou byt stovky – a tedy musim mit moznost nejakeho strankovani.
jiste bych to mohl udelat tak, ze nejprve ziskat N korenovych zaznamu a pak pro kazdy z nich nacist kompletni strukturu, – a tu pak jen v HTML poschovavat a na vyzadani ji zobrazovat.
samozrejme – to by bylo velice proste reseni :) , jenze abych pravdu rekl, nejde mi za kazdou cenu JEN docilit sveho, snippety (nebo podobna dynamicka ajaxova kouzla) bych se k tomu rad naucil.
… nemuze nejaky handler vratit kus HTMLka (vyrenderovanou komponentu) na ajaxovy pozadavek ze stranky? (asi je hloupe se ptat jestli muze – spis bych se mel ptat „jak“)
mimochodem, tech „5:27“, to je stredoevropsky cas? ;D
- srigi
- Nette Blogger | 558
Zajtra by som sa na to pozrel (dnes uz nie, bol som na Barcampe, preto ten skory cas – musel som stihnut bus), dufam ze pojdu stranky Jakuba Vrany, kde su algoritmy na traverz. okolo stromu. Len prezradim, ze bezny nested set umozuje iba jeden strom v jednej tabulke (jeden root s level 1). Doctrine ma feature, ze dokaze v jednej tabulke mat aj viac stromov, ale v praxi (a v tvojom pripade) to imo nebude treba.
- srigi
- Nette Blogger | 558
wdolek >> Mam u teba velke pivo :D zdrojaky
Demo nie je, lebo to je naprogramovane v PHP 5.3 a ja nemam taky hosting. Mozno ale postaci mala video ukazka.
edit: este dump databazy
INSERT INTO `galleries` (`id`, `lft`, `rgt`, `level`, `name`) VALUES
(1, 1, 22, 1, '_root'),
(2, 2, 15, 2, 'Procesory'),
(3, 3, 8, 3, 'Intel'),
(4, 4, 5, 4, 'Pentium IV'),
(5, 6, 7, 4, 'Core 2 Duo'),
(6, 9, 14, 3, 'AMD'),
(7, 10, 11, 4, 'Duron'),
(8, 12, 13, 4, 'Athlon'),
(9, 16, 21, 2, 'Pamäte'),
(10, 17, 18, 3, 'DDR'),
(11, 19, 20, 3, 'DDR2');
Editoval srigi (17. 5. 2010 0:01)
- wdolek
- Člen | 331
hmm, tak asi nekde delam neco spatne… asi sem si to spatne vylozil. snazil sem se radnou dobu koukat do toho zdrojaku a vydedukovat, jak to cele funguje… pak sem cely ten zdrojak obslehl s tim, ze jsem tam naklohnil sve galerie… ale… nejak se nic nedeje.
ajaxovy dotaz odejde, vrati se 200. zkusil sem si dumpnout, co akce provede, a provede presne to co pozaduji. problem je tedy az pri zobrazeni (invalidaci) snippetu, ktera se neprovede… nebo se mozna neprovede jeste neco jineho – spinner totiz po dokoncenem dotazu nezmizi (coz by mel, co jsem tak koukal do zdrojaku).
GalleryRecord
:
use Nette\Application\Control;
use Foo\Models\Content\Gallery;
/**
* Gallery record control
*/
class GalleryRecord extends Control {
/**
* @var \Foo\Models\Content\Gallery
*/
private $gallery = NULL;
/**
* @var array of \Foo\Models\Content\Gallery
*/
private $childGalleries = NULL;
/**
* @param \Foo\Models\Content\Gallery $gallery
* @return void
*/
public function render($gallery) {
if (!($gallery instanceof Gallery)) {
exit;
}
$this->gallery = $gallery;
// template
$template = $this->template;
$template->setFile(__DIR__ . '/GalleryRecord.phtml');
// properties
$template->gallery = $gallery;
$template->galleryId = $gallery->getId();
$template->childGalleries = $this->childGalleries;
// render
$template->render();
}
/**
* @return void
*/
public function handleSubTree($parentGalleryId) {
$galls = Gallery::find(NULL, NULL, array(Gallery::FIELD_ID_PARENT => $parentGalleryId));
// wrap to array
if (!is_null($galls) && !is_array($galls)) {
$galls = array(
$galls->getId() => $galls
);
}
$this->childGalleries = $galls;
if ($this->presenter->isAjax()) {
$this->invalidateControl('sub_' . $parentGalleryId);
}
}
/**
* @return \GalleryRecord
*/
public function createComponentGalleryRecord() {
return new self;
}
}
GalleryRecord.phtml
:
<tr id="g_{$gallery->getId()}">
<td>{$gallery->getId()}</td>
<td>
{if $gallery->hasChild()}
<a href="{link subTree! $gallery->getId()}" class="icon jump ajax"></a>
{/if}
</td>
<td>{$gallery->getName()}</td>
<td>{$gallery->getOrder()}</td>
<td><input type="checkbox" name="public[{$gallery->getId()}]" disabled="disabled" {if $gallery->isPublic()}checked="checked"{/if} /></td>
<td>{$gallery->getTime()|date:'Y-m-d H:i'}</td>
<td>
<a href="" class="icon edit">editace</a>
<a href="" class="icon remove" onclick="return window.confirm('Smazat zázanm?');">smazat</a>
</td>
</tr>
{snippet sub_$galleryId}
@{if $childGalleries}
@{foreach $childGalleries as $g}
@{widget galleryRecord $g}
@{/foreach}
@{/if}
{/snippet}
galleries.phtml
:
@{block #content}
@{if !empty($galleries) || !empty($filter)}
<table>
<colgroup class="id"></colgroup>
<colgroup class="sub"></colgroup>
<colgroup span="4"></colgroup>
<colgroup class="dupleAction"></colgroup>
<thead>
<tr>
<th>#ID</th>
<th></th>
<th>Jméno</th>
<th>Pořadí</th>
<th>Veřejná</th>
<th>Vytvořeno</th>
<th>Akce</th>
</tr>
</thead>
<tfoot class="paging" n:if="!empty($mediaItems)">
<tr>
<td colspan="7">
{if !$paging->isFirst()}
<a href="{link Admin:Galleries 'page' => ($paging->getPage() - 1), 'filter' => $filter}">«</a>
{/if}
{for $i = 1; $i <= $paging->getPageCount(); $i++}
{if $paging->getPage() == $i}
<span>{$i}</span>
{else}
<a href="{link Admin:Galleries 'page' => $i, 'filter' => $filter}">{$i}</a>
{/if}
{/for}
{if !$paging->isLast()}
<a href="{link Admin:Galleries 'page' => ($paging->getPage() + 1), 'filter' => $filter}">»</a>
{/if}
</td>
</tr>
</tfoot>
<tbody>
@{if !empty($galleries)}
@{foreach $galleries as $g}
@{widget galleryRecord $g}
@{/foreach}
@{else}
<tr>
<td colspan="7">Kritériu neodpovídá žádný záznam...</td>
</tr>
@{/if}
</tbody>
</table>
<script type="text/javascript">
// <![CDATA[
$('a.ajax').live('click', function(event) {
event.preventDefault();
// create and show spinner
var $spinner = $(document.createElement('div'));
$spinner.addClass('ajax-spinner');
$spinner.appendTo(document.body);
$spinner.show();
// ajax
$.get(this.href);
});
// ]]>
</script>
@{else}
<p>V databázi se nenachází žádná <i>galerie</i>.</p>
@{/if}
<div class="action">
<a href="{link Admin:GalleryForm}" class="icon new">nová galerie</a>
</div>
@{/block}
… taky by me tuze zajimalo, jestli muzu nahradit
aha, druhy parametr
snippetu <div id="snippet-galleryRecord-sub_1">...</div>
za
<tr ...>...</tr>
… nicmene, to je to posledni –
klidne to pak preklohnim do nejakych vykutalene pozicovanych
div
{snippet jmeno element}
mozna je uplne nesmyslna myslenka mit snippet ve snippetu (?) … :(
… a jeste sem si uvedomil – v
aha GalleryRecord
nemuzu
vyrobit odkaz {link Admin:WhateverAction}
:P – dostanu error, ze
komponenta Admin
neexistuje. jak muzu vyrabet Admin
linky z sablony komponenty?{plink ...}
Editoval wdolek (19. 5. 2010 12:35)
- Mikulas Dite
- Člen | 756
Mrkni do toho jsonu co se vrátí – jestli tam je opravdu to co chceš, tak se z nějakého důvodu nepustí callback, který se stará o přepsání stránky (a potažmo schování animačky) a jestli to tam není, tak špatně invaliduješ.
- wdolek
- Člen | 331
v json odpovedi skutecne nic neni :( (respektive tam dostanu jen hodnotu persisentni promenne „language“)
inu.. co znamena spravne invalidovat? mam spravne zavinace? muzu invalidovat snippet, ktery vykresluje jiny snippet?
odpoved serveru: {"state":{"language":"cs"}}
Editoval wdolek (19. 5. 2010 17:14)
- wdolek
- Člen | 331
opet sem si chvili hral a dosel sem k zaveru, ze
$this->invalidateControl(...)
nedela co ma. a ja nevim proc.
cvicne jsem si zkusil nahradit invalidaci primym dosazenim do payload
...
if ($this->presenter->isAjax()) {
//$this->invalidateControl('sub_' . $parentGalleryId);
$this->presenter->payload->snippets['sub_' . $parentGalleryId] = '<td>Snippet content!</td>';
}
...
dale jsem pozmenil jquery.nette.js
...
updateSnippet: function (id, html) {
$('[id$=' + id + ']').html(html);
$('.ajax-spinner').hide();
},
...
s temito upravami, ktere samozrejme vubec nenapomahaji funkcnosti webu, to
„funguje“. v JSON odpovedi mam pole, ktere obsahuje HTML kod snippetu,
ktery se ma zmenit.
obsah snippetu se zmeni, a spinner zmizi (puvodni kod predpokladal, ze spinner
je potomkem snippetu, ja vsak spinner vyrabim dynamicky a lepim ho k
document.body
).
je mi ale zahadou, proc mi nejde invalidateControl
. po zavolani
jsem zkusil isControlInvalid(...)
a samozrejme byla invalidni.
nemuze byt nejaky problem ve jmenu snippetu? – v sablone davam snippetu
jmeno sub_$galleryId
, s tim samym jmenem pak pracuji v handleru.
v HTML je ale snippet pojmenovan
takto: snippet-galleryRecord-sub_1
Editoval wdolek (19. 5. 2010 17:16)
- wdolek
- Člen | 331
facepalm … ja trouba zapomnel pridat zavinac k
{include #content}
v sablone layoutu :) … takze ted uz to frci
(az na ty tr
, takze to prepisu do div
, to nebude
tak sasit)
… umm, tak se musim opravit – funguje to pekne, ale pouze pro prvni uroven. dalsi urovne stromu uz nejdou (snippet ve snippetu). :( nejake moudro od nekoho?
Editoval wdolek (20. 5. 2010 14:09)