Rekordlista-szerű tömbök fa struktúrába rendezése - Fa menüstruktúra

Gyakori feladat olyan menüket készíteni, amik fa struktúrában épülnek fel és akár egy javascript program készít belőle lenyíló menüt. Vagy akár csak egy oldaltérképre is gondolhatunk. A következő megoldásom főként azon alapszik, amikor egy adatbázisban tárolunk valamilyen adatokat. Legyen az többszintű kategória rendszer vagy menüszerkezet. Ilyenkor többnyire van egy egyedi azonosító. Amire az alárendelt rekordok hivatkoznak egy másik mezőben. Emellett persze egyéb adatokat tartalmaz a rekord. Legalább egy megjelenítendő mező is van, ami például a menü szövege. De lehet hozzá leírás is rendelve. Különböző listák lehetnek, de ami közös bennük, az a felépítés módja. Azon túl más lehet a megjelenítés. Az egyiket például egyszerű rendezetlen listában kell megjeleníteni, a másikat talán már táblázatban. A harmadikat pedig csak tabulátorokat használva. Ráadásul ha már html megjelenítésről beszélünk, ott képbe jön a css is. HTML attribútumok, osztályok. Célszerűen, lehetőleg elérhetővé téve minden elemhez egy hivatkozást, amivel egyedien lehet szükség esetén formázni akár a második menüelem 3. gyerekének 1 gyerekét. Stb... Erre mutatok egy lehetséges, ám nem mindenre kiterjedő megoldást.

Példa

Felhasznált tömb

$array = array(
        array(
                'id' => 1,
                'name' => 'Első menü',
                'description' => 'Ez az első menü',
                'url' => 'elso',
                'parent' => 0
        ),
        array(
                'id' => 2,
                'name' => 'Első menü - Almenü 1',
                'description' => 'Ez a főmenübe tartozó első almenü',
                'url' => 'elso/almenu1',
                'parent' => 1
        ),
        array(
                'id' => 3,
                'name' => 'Első menü - Almenü 2',
                'description' => 'Második almenü a főmenüben',
                'url' => 'elso/almenu2',
                'parent' => 1
        ),
        array(
                'id' => 4,
                'name' => 'Második menü',
                'description' => 'Ez egy új menü kategória',
                'url' => 'masodik',
                'parent' => 0
        ),
        array(
                'id' => 5,
                'name' => 'Második menü - Almenü 1',
                'description' => 'Második menübe való első almenü',
                'url' => 'masodik/almenu1',
                'parent' => 4
        ),
        array(
                'id' => 6,
                'name' => 'Második almenü - Almenü 1 - Al almenü 1',
                'description' => 'Ez már harmadik szint',
                'url' => 'masodik/almenu1/alalmenu1',
                'parent' => 5
        ),
        array(
                'id' => 7,
                'name' => 'Második almenü - Almenü 1 - Al almenü 2',
                'description' => '',
                'url' => 'masodik/almenu1/alalmenu2',
                'parent' => 5
        ),
        array(
                'id' => 9,
                'name' => 'Ismeretlen menü',
                'description' => 'Menü, aminek nincs meg a szülője',
                'url' => 'ismeretlen',
                'parent' => 8
        ),
        array(
                'id' => 14,
                'name' => 'Ismeretlen menü - Almenü 1',
                'description' => 'Itt nincs',
                'url' => 'ismeretlen/almenu1',
                'parent' => 9
        )
);

PHP és HTML vegyesen, sorban

<?php
$tree = new MenuTree($array, 'mainmenu');
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
        <head>
                <title></title>
                <meta http-equiv="content-type" content="text/html; charset=utf-8" />
                <link rel="stylesheet" type="text/css" href="style.css" />
        </head>
        <body>
                <div id="container">

                <?php echo $tree; ?>

                </div>
        </body>
</html>

style.css

#mainmenu .menuitem.level-1.title {
        font-weight: bold;
}
#mainmenu .menuitem.level-2.menuitem-1.title {
        color: red;
}
.menu a.title {
        text-decoration: none;
}

Az eredmény

Forrás

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
        <head>
                <title></title>
                <meta http-equiv="content-type" content="text/html; charset=utf-8" />
                <link rel="stylesheet" type="text/css" href="style.css" />
        </head>
        <body>
                <div id="container">
                <ul class="menu level-0 menu-0" id="mainmenu"><li class="mainmenu menuitem level-1 menuitem-1 menuitem-path-1" id="mainmenu-item-1" ><a title="Ez az első menü" href="elso" class="mainmenu menuitem level-1 menuitem-1 title">Első menü</a><ul class="menu level-1 menu-1" id="mainmenu-1"><li class="mainmenu menuitem level-2 menuitem-1 menuitem-path-1-1" id="mainmenu-item-2" ><a title="Ez a főmenübe tartozó első almenü" href="elso/almenu1" class="mainmenu menuitem level-2 menuitem-1 title">Első menü - Almenü 1</a></li><li class="mainmenu menuitem level-2 menuitem-2 menuitem-path-1-2" id="mainmenu-item-3" ><a title="Második almenü a főmenüben" href="elso/almenu2" class="mainmenu menuitem level-2 menuitem-2 title">Első menü - Almenü 2</a></li></ul></li><li class="mainmenu menuitem level-1 menuitem-2 menuitem-path-2" id="mainmenu-item-4" ><a title="Ez egy új menü kategória" href="masodik" class="mainmenu menuitem level-1 menuitem-2 title">Második menü</a><ul class="menu level-1 menu-2" id="mainmenu-4"><li class="mainmenu menuitem level-2 menuitem-1 menuitem-path-2-1" id="mainmenu-item-5" ><a title="Második menübe való első almenü" href="masodik/almenu1" class="mainmenu menuitem level-2 menuitem-1 title">Második menü - Almenü 1</a><ul class="menu level-2 menu-1" id="mainmenu-5"><li class="mainmenu menuitem level-3 menuitem-1 menuitem-path-2-1-1" id="mainmenu-item-6" ><a title="Ez már harmadik szint" href="masodik/almenu1/alalmenu1" class="mainmenu menuitem level-3 menuitem-1 title">Második almenü - Almenü 1 - Al almenü 1</a></li><li class="mainmenu menuitem level-3 menuitem-2 menuitem-path-2-1-2" id="mainmenu-item-7" ><a title="" href="masodik/almenu1/alalmenu2" class="mainmenu menuitem level-3 menuitem-2 title">Második almenü - Almenü 1 - Al almenü 2</a></li></ul></li></ul></li></ul>          </div>
        </body>
</html>

Megjelenés

Megoldás

Opciók

Konstruktorban átadhatók opciók tömbként, amiket a lista generáláskor tovább ad a generáló metódus szükség szerint módosítva a gyermek listáknak.

Név Leírás Alapértelmezett
i Egy ciklusváltozó annak jelölésére, hogy adott listában adott elem hányadik. 1
pathi Egy tömb, ami tartalmazza sorra azon „i” indexeket, amin keresztül az elemhez eljuthatunk. array()
level Annak száma, hogy adott menü hányadik szintű listában szerepel. 0
parentIndex Az egyedi azonosító neve. id
refToParent A szülő egyedi azonosítójára hivatkozó mező neve. parent
parentValue A szülő egyedi azonosítójának értéke 0
titleIndex A megjelenítendő mező neve. Egyedi megjelenítésnél figyelmen kívül is hagyható. title

Tree osztály

Ez az osztály tartalmazza a lista felépítéséhez szükséges metódusokat. Valamint olyan, a megjelenítést szabályzó metódusokat, amiket egy gyermekosztályban felül lehet írni. Így egyedi, saját megjelenítést adni a listának.

Metódus Leírás Argumentumok
__construct Konstruktor
  • $array: Kétdimenziós tömb. Mint egy adatbázisból lekérd rekordlista.
  • $options: Opciók
asArray Visszaadja a fa struktúrát tömbként.
asString() Visszaadja a fát stringként
getChildsAsString Visszaadja a gyerekek listáját stringként.
  • $node: A node tömbje.
getChildsStartString Visszatérés: Egy node gyerekeinek string alakban listázásánál mi kerüljön a lista elé
  • $node: A node tömbje.
getChildsEndString Visszatérés: Egy node gyerekeinek string alakban listázásánál mi kerüljön a lista után
  • $node: A node tömbje.
getItemAsString Visszaad egy lista elemet stringként
  • $node: A node tömbje.
getItemStartString Visszaadja, mi kerüljön egy listaelem elé.
  • $node: A node tömbje.
getItemEndString Visszaadja, mi kerüljön egy listaelem után.
  • $node: A node tömbje.

A node alatt a következő struktúrájú tömb értendő:

array(
   'item' => //Aktuális lista elem tömbje,
   'childs' => //Újabb node,
   'level' => //szint száma,
   'i' => //elem sorszáma,
   'pathi' => //útvonal tömbje
)

MenuTree osztály

Ez az osztály felülírja a Tree osztály alapbeállításait. Csak azokat, amikben különbözik, vagy speciálisabb működésre van szüksége. Így például az átadott $node argumentumokat felhasználva egyedi html attribútumokat lehet rendelni a listák címkéihez. Ezt meg is teszi a MenuTree osztály.

Metódus Leírás Argumentumok
__construct Konstruktor
  • $array: Kétdimenziós tömb. Mint egy adatbázisból lekérd rekordlista.
  • $id: ID, amit felhasznál a html címkék osztályaiban és ID-kben azonosításra.
getChildsStartString Visszatérés: Egy node gyerekeinek string alakban listázásánál mi kerüljön a lista elé
  • $node: A node tömbje.
getItemAsString Visszaad egy lista elemet stringként
  • $node: A node tömbje.
getItemStartString Visszaadja, mi kerüljön egy listaelem elé.
  • $node: A node tömbje.

A többi metódus nem változik a Tree osztályhoz képest.

Forráskódok

Tree osztály

/**
 * Fa struktúra
 */

class Tree {

        /**
         * Eredeti tömb
         *
         * @var array
         */

        private $_items = array();

        /**
         * A létrehozott fa tömbje
         *
         * @var array
         */

        private $_tree = null;

        /**
         * A fa string reprezentációja
         *
         * @var string
         */

        private $_treeAsString = null;

        /**
         * Opciók a fa és annak string alakjának létrehozásához.
         *
         * @var array
         */

        private $_options = array(
                'parentIndex' => 'id',
                'refToParent' => 'parent',
                'parentValue' => 0,
                'titleIndex' => 'title',
                'level' => 0,
                'i' => 1,
                'pathi' => array()
        );

        /**
         *
         * @param array $array Kétdimenziós tömb. Mint egy adatbázisból lekért rekordlista.
         * @param array $options Opciók
         */

        public function __construct(array $array, array $options = array()) {
                $this->_items = $array;
                $this->_options = $options + $this->_options;
        }

        /**
         * Lista string kezdése
         *
         * Egy node gyerekeinek string alakban listázásánál mi kerüljön a lista elé.
         *
         * @param array $node Node
         * @return string
         */

        public function getChildsStartString(array $node) {
                return '<ul>';
        }

        /**
         * Lista  befejezése
         *
         * Egy node gyerekeinek string alakban listázásánál mi kerüljön a lista után.
         *
         * @param array $node Node
         * @return string
         */

        public function getChildsEndString(array $node) {
                return '</ul>';
        }

        /**
         * Egy lista elem megjelenítése
         *
         * @param array $node
         * @return string
         */

        public function getItemAsString(array $node) {
                $item = &$node['item'];
                return isset($item[$this->_options['titleIndex']]) ? $item[$this->_options['titleIndex']] : $item[$this->_options['parentIndex']];
        }

        /**
         * Mi kerüljön egy listaelem elé.
         *
         * @param array $node Node
         * @return string
         */

        public function getItemStartString(array $node) {
                return '<li>';
        }

        /**
         * Mi kerüljön egy listaelem végére
         *
         * @param array $node Node
         * @return string
         */

        public function getItemEndString(array $node) {
                return '</li>';
        }

        /**
         * Egy lista megjelenítése
         *
         * @param array $node Node
         * @return string
         */

        public function getChildsAsString(array $node) {
                return sprintf('%s%s%s%s', $this->getItemStartString($node),
                                $this->getItemAsString($node), $node['childs']
                                        ? $this->_asString($node)
                                        : '', $this->getItemEndString($node));
        }

        /**
         * A teljes fa lekérése tömbként
         *
         * @return array
         */

        public function asArray() {
                if (is_null($this->_tree)) {
                        $this->_tree = $this->_build($this->_items, $this->_options);
                }
                return $this->_tree;
        }

        /**
         *
         * @param array $node
         * @return string
         */

        private function _asString(array $node) {
                $ret = $this->getChildsStartString($node);

                foreach ($node['childs'] as &$item) {
                        $ret .= $this->getChildsAsString($item);
                }

                $ret .= $this->getChildsEndString($node);

                return $ret;
        }

        /**
         * Teljes fa lekérése stringként.
         *
         * @return string
         */

        public function asString() {
                if (is_null($this->_tree)) {
                        $this->_tree = $this->_build($this->_items, $this->_options);
                }

                if (is_null($this->_treeAsString)) {
                        $this->_treeAsString = $this->_asString($this->_tree);
                }

                return $this->_treeAsString;
        }

        /**
         * Teljes fa generálása
         *
         * @param array $items Eredeti tömb, amiből a fa generálandó
         * @param array $options Opciók
         * @return array
         */

        private function _build(array $items, array $options = array()) {
                return array(
                        'item' => null,
                        'childs' => $this->_buildChilds($items, array('level' => $options['level'] + 1) + $options),
                        'level' => $options['level'],
                        'i' => 0,
                        'pathi' => array()
                );
        }

        /**
         * A gyerekek generálása
         *
         * @param array $items Eredeti tömb
         * @param array $options Opciók
         * @return array
         */

        private function _buildChilds(array $items, array $options = array()) {
                $options += $this->_options;

                $tree = array();

                foreach ($items as $key => $item) {
                        if (isset($item[$options['refToParent']]) and isset($item[$options['parentIndex']]) and
                                        $item[$options['refToParent']] == $options['parentValue']) {
                                $tree[] = array(
                                        'item' => $item,
                                        'childs' => $this->_buildChilds(&$items, array(
                                                'parentValue' => $item[$options['parentIndex']],
                                                'level' => $options['level'] + 1,
                                                'pathi' => array_merge($options['pathi'], array($options['i'])),
                                                'i' => 1
                                                        ) + $options),
                                        'level' => $options['level'],
                                        'pathi' => array_merge($options['pathi'], array($options['i'])),
                                        'i' => $options['i']++
                                );

                                unset($items[$key]);
                        }
                }

                return $tree;
        }

        /**
         * Magic method: Stringgé konvertálás
         *
         * @return string
         */

        public function __toString() {
                return $this->asString();
        }

}

MenuTree osztály

/**
 * Menü fa struktúrában
 */

class MenuTree extends Tree {

        /**
         * Menü id-je
         *
         * @var string
         */

        private $id;

        /**
         *
         * @param array $items Menü rekordlista szerint
         * @param string $id Menü id-je (Olyan, ami bekerülhet html id -nek is)
         */

        public function __construct(array $items, $id) {
                parent::__construct($items, array('titleIndex' => 'name'));
                $this->id = $id;
        }

        /**
         * A menü elem elé kerülő string
         *
         * UL lista item ( LI ) nyitása osztályokkal, id-vel
         *
         * @param array $node
         * @return string
         */

        public function getItemStartString(array $node) {
                return '<li class="' . $this->id . ' menuitem level-' . $node['level']
                                . ' menuitem-' . $node['i'] . ' menuitem-path-'
                                . implode('-', $node['pathi']) . '" id="' . $this->id
                                . '-item-' . $node['item']['id'] . '" >';
        }

        /**
         * Menü elem lista kezdése
         *
         * UL lista kezdés osztályokkal, id-vel.
         *
         * @param array $node
         * @return string
         */

        public function getChildsStartString(array $node) {
                return '<ul class="menu level-' . $node['level'] . ' menu-' . $node['i'] . '" id="' . $this->id . ($node['level'] ? '-' . $node['item']['id'] : '') . '">';
        }

        /**
         * Menü elem megjelenítése
         *
         * A menü elemre link kerül osztályokkal, id-vel.
         *
         * @param array $node
         * @return string
         */

        public function getItemAsString(array $node) {
                $item = &$node['item'];
                return '<a title="'.htmlspecialchars($item['description']).'" href="' . $item['url'] . '" class="' . $this->id . ' menuitem level-' . $node['level'] . ' menuitem-' . $node['i'] . ' title">' . htmlspecialchars($item['name']) . '</a>';
        }

}

Kategóriák: 
Megosztás/Mentés