reprezentace hierarchicky usporadanych dat – pouzit snippet?

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
wdolek
Člen | 331
+
0
-

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

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

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

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

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

hooo monstrozni :D diky, jak ti to pivo mam poslat? (5.3 je na mem notebucku, ja si s tim poradim)

wdolek
Člen | 331
+
0
-

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}">&laquo;</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}">&raquo;</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 <div id="snippet-galleryRecord-sub_1">...</div> za <tr ...>...</tr>… nicmene, to je to posledni – klidne to pak preklohnim do nejakych vykutalene pozicovanych div aha, druhy parametr snippetu {snippet jmeno element}

mozna je uplne nesmyslna myslenka mit snippet ve snippetu (?) … :(

… a jeste sem si uvedomil – v GalleryRecord nemuzu vyrobit odkaz {link Admin:WhateverAction} :P – dostanu error, ze komponenta Admin neexistuje. jak muzu vyrabet Admin linky z sablony komponenty? aha {plink ...}

Editoval wdolek (19. 5. 2010 12:35)

Mikulas Dite
Člen | 756
+
0
-

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

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

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

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)