Closure table a výpis kategorií

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

Zdravím, už nějakou dobu se snažím to vyřešit, ale stále mě nenapadá řešení a tak se obracím zde. Chtěl bych pro hierarchii kategorií použít closure table, kde vycházím z článku Jana Voráčka. Bohužel nevím, jak můžu na hlavní stránce zobrazit všechny kategorie (tedy kořeny) a pak při jejich zobrazení ukázat jejich potomky (pouze o jednu úroveň méně), ale zároveň zobrazit i jejich předka. Mám toto:

Databázové dotazy:

    public function getCategories() {
        return $this->connection->query('
                    SELECT c.*, cc.depth FROM category c
                      JOIN category_closure cc
                        ON (c.category_id = cc.descendant)
                      WHERE (SELECT COUNT(*) FROM category_closure WHERE descendant = c.category_id)=1;
                    ');
    }

    public function AllCategorySubtree($id) {
        return $this->connection->query('
                    SELECT c.*, cc.depth FROM category c JOIN category_closure cc
ON (c.category_id= cc.descendant)
WHERE cc.ancestor = ?', $id);
    }

BasePresenter:

    public function beforeRender() {
        parent::beforeRender();
        $this->template->getCategories = $this->table->getCategories();
    }

CategoryPresenter:

    public function renderDefault($id, $titleCategory) {
        $this->template->subtree = $this->table->AllCategorySubtree($id);
    }

@layout

            <ul>
            {foreach $getCategories as $element}
                <li><a n:href="Category: $element->category_id, $element->name">{$element->name}</a></li>
                {block #cat}{/block}
             {/foreach}
            </ul>

Category:default

    {block cat}
        {foreach $subtree as $element}
            <ul>
                <li><a n:href="Category: $element->category_id, $element->name">{$element->name}</a></li>
            </ul>
        {/foreach}
    {/block}

Problém je ten, že rekurzívou při proklikání dostávám výpis namísto PC příslušenství → Tiskárny → Laserové pouze PC příslušenství → Laserové. Věděl by někdo jak toto vyřešit? Nebo prostě jiný způsob, jak můžu pomocí closure vypsat kategorie, ale zároveň vidět jeho předka a jeho potomka? Snad mě chápete.

BigCharlie
Člen | 285
+
0
-

zkus si přečíst ještě jeden článek, třeba ti pomůže.

castamir
Člen | 629
+
0
-

@BigCharlie drby se šíří rychle… Ten článek ještě neprošel korekturou (poněkud nestíhám) a blog ještě nemá nastavené styly. Ale já se k tomu časem prokoušu!

enumag
Člen | 2118
+
0
-

@castamir: Pořád jsi na tom líp než třeba já – stále ještě jsem blog ani nezprovoznil natož abych psal články. :-)

BigCharlie
Člen | 285
+
0
-

@castamir: jasné potvrzení toho, že „content is the king“ :-) Jinak jsem druhý, co na tvůj článek v diskusi odkazuje, odkaz jsem tu někde odchytil…

Draffix
Člen | 146
+
0
-

@BigCharlie No pokud myslíš, že první odkaz je tento, tak se mrkni na autora :-D Jinak jsem projel snad všechno co google vyplivl, tedy od těchto dvou blogů až po blog Billa Karwina, ale řešení jsem nenašel. Prostě bych potřeboval nějak pošťouchnout, jak mohu udělat klasické kategorické menu pomocí closure:

Hlavní kategorie č.1
	Subkategorie č.1
	Subkategorie č.1
		Subkategorie č.1.1.
Hlavní kategorie č.2
atd...
castamir
Člen | 629
+
0
-

@Draffix Někde jsem měl ještě lepší kód, ale nemůžu ho najít. Ale než ho najdu, hodím sem jiný funkční kód.

    public function getSubtree($node) {
        $tree = $this->db->query("
            SELECT c.*, cc2.ancestor, cc2.descendant, cc.depth
            FROM
                $this->category c
                JOIN $this->closure cc
                ON (c.id = cc.descendant)
                    JOIN $this->closure cc2
                    USING (descendant)
            WHERE cc.ancestor = $node AND cc2.depth = 1
            ORDER BY cc.depth, c.name
        ");
        return $this->parseSubTree($node, $tree);
    }

    private function parseSubTree($rootID, $nodes) {
        // to allow direct access by node ID
        $byID = array();

        // an array of parrents and their children
        $byParent = array();


        foreach ($nodes as $node) {
            if ($node["id"] != $rootID) {
                if (!isset($byParent[$node["ancestor"]])) {
                    $byParent[$node["ancestor"]] = array();
                }
                $byParent[$node["ancestor"]][] = $node["id"];
            }
            $byID[$node["id"]] = (array) $node;
        }

        // tree reconstruction
        $tree = array();
        foreach ($byParent[$rootID] as $nodeID) { // root direct children
            $tree[] = $this->parseChildren($nodeID, $byID, $byParent);
        }

        return $tree;
    }
    private function parseChildren($id, $nodes, $parents) {
        $tree = $nodes[$id];

        $tree["children"] = array();
        if (isset($parents[$id])) {
            foreach ($parents[$id] as $nodeID) {
                $tree["children"][] = $this->parseChildren($nodeID, $nodes, $parents);
            }
        }

        return $tree;
    }

dumpni si výsledek

Editoval castamir (11. 2. 2013 23:38)

castamir
Člen | 629
+
0
-

Výše zmíněné metody vrací pole, jehož prvky jsou přímí potomci kořene stromu. Pro všechny potomky (bez ohledu na hloubku) platí, že mají atribut children, jež obsahuje jejich přímé potomky.

příklad použití: presenter

    public function renderDefault() {
        $this->template->categories = $this->dir->getSubtree(1);
    }

šablona

<ul class="tree">
{block #categories}
{foreach $categories as $node}
    <li>
        <span">{$node["name"]}</span>
        <ul n:if="count($node['children'])">
        {include #categories, 'categories' => $node["children"]}
        </ul>
    </li>
{/foreach}
{/block}
</ul>

Editoval castamir (11. 2. 2013 23:44)

Draffix
Člen | 146
+
0
-

Funguje to super, ale lze to nějak udělat i tak, abych viděl vždy potomky jen dané kategorie včetně její nadřazenou kategorii? Teď mám kategorie takhle:

Výpis všech kořenových kategorií:

PC příslušenství
Reproduktory

A kliknu třeba na PC příslušenství:

	Klávesnice
		Bezdrátové
		Drátové
	Myši
		Laserové
		Optické
	Tiskárny
		Inkoustové
		Laserové

A já bych potřeboval něco ve stylu když někdo klikne třeba na PC příslušenství a pak na Klávesnice:

PC příslušenství
	Klávesnice
		Bezdrátové
		Drátové
	Myši
	Tiskárny
Reproduktory
castamir
Člen | 629
+
0
-

No, docela záleží na tom, kde to chceš použít. Pokud je dat málo, můžeš si vystačit s tím, že vypíšeš celý strom, který jen upravíš pomocí CSS stylů nebo javascriptu.

EDIT: něco na tento způsob

ul.tree ul { display: none }
ul.tree li:hover > ul { display: block }

edit: nebo můžeš použít něco předchystaného.

Editoval castamir (12. 2. 2013 1:14)

Draffix
Člen | 146
+
0
-

Jasně, to mě nenapadlo použít. Jedná se o relativně malý počet, takže by se to dalo použít. Jen si opět neumím (snad už naposled) poradit s tím výpisem. Jak jsem psal, vypisuje mi to teď takto:

Klávesnice
    Bezdrátové
    Drátové
Myši
    Laserové
    Optické
Tiskárny
    Inkoustové
    Laserové

A já bych tam potřeboval ještě „dodat“ ty kořenové kategorie (bude jich více) takto:

PC příslušenství (kořen)
Klávesnice
    Bezdrátové
    Drátové
Myši
    Laserové
    Optické
Tiskárny
    Inkoustové
    Laserové
Reproduktory (kořen)
Tašky (kořen)
atd..

Lze toto nějak vyřešit? Mimochodem děkuji za trpělivost se mnou.

castamir
Člen | 629
+
0
-

A co ti brání si nad těmito „kořeny“ udělat nový společný kořen?