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(
'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
$tree = new MenuTree($array, 'mainmenu');
?>
<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; ?>
</body>
</html>
style.css
font-weight: bold;
}
#mainmenu .menuitem.level-2.menuitem-1.title {
color: red;
}
.menu a.title {
text-decoration: none;
}
Az eredmény
Forrás
<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 |
|
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. |
|
getChildsStartString | Visszatérés: Egy node gyerekeinek string alakban listázásánál mi kerüljön a lista elé |
|
getChildsEndString | Visszatérés: Egy node gyerekeinek string alakban listázásánál mi kerüljön a lista után |
|
getItemAsString | Visszaad egy lista elemet stringként |
|
getItemStartString | Visszaadja, mi kerüljön egy listaelem elé. |
|
getItemEndString | Visszaadja, mi kerüljön egy listaelem után. |
|
A node alatt a következő struktúrájú tömb értendő:
'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 |
|
getChildsStartString | Visszatérés: Egy node gyerekeinek string alakban listázásánál mi kerüljön a lista elé |
|
getItemAsString | Visszaad egy lista elemet stringként |
|
getItemStartString | Visszaadja, mi kerüljön egy listaelem elé. |
|
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>';
}
}