Below I have implemented a class that will generate HTML to build the structure for a navigation menu.
define('BASE_URL', 'http://localhost/site');
class Navigation
{
private $doc;
private $xpath;
private $rootNode;
/**
* Initialize navigation.
*/
public function __construct($menu)
{
$this->doc = new DOMDocument();
$this->doc->appendChild($this->buildMenu($menu));
$this->xpath = new DOMXpath($this->doc);
}
/**
* Un-mark selected menu item.
*/
public function deselect()
{
$liNodeList = $this->xpath->query("//li[contains(@class, 'selected')]");
if ($liNodeList->length) {
$liNode = $liNodeList->item(0);
$classAttr = explode(' ', $liNode->getAttribute('class'));
$key = array_search('selected', $classAttr);
unset($classAttr[$key]);
$liNode->setAttribute('class', implode(' ', $classAttr));
}
}
/**
* Mark first occurrence of menu item containing link with URI as selected.
* @param $uri string
*/
public function select($uri)
{
$liNodeList = $this->xpath->query("//li[a[contains(@href, '" . $uri . "')]]");
if ($liNodeList->length) {
$liNode = $liNodeList->item(0);
$classAttr = explode(' ', $liNode->getAttribute('class'));
$classAttr[] = 'selected';
$liNode->setAttribute('class', trim(implode(' ', $classAttr)));
}
}
/**
* Build menu.
* @param $menu array
* @param $depth int
*/
private function buildMenu($menu, $depth = 0)
{
$divNode = $this->createNav();
$ulNode = $this->doc->createElement('ul');
// set first nav as root
if (!$depth) {
$divNode->setAttribute('id', 'primary-nav');
$this->rootNode = $divNode;
}
// add menu items
foreach ($menu as $index => $menuItem) {
$ulNode->appendChild($this->buildMenuItem($menuItem, $depth, $index));
}
$divNode->appendChild($ulNode);
return $divNode;
}
/**
* Build menu.
* @param $menu array
* @param $depth int
* @param $index int
*/
private function buildMenuItem($menuItem, $depth, $index)
{
$liNode = $this->doc->createElement('li');
$aNode = $this->doc->createElement('a');
// add class attribute
$classAttr = array();
if (isset($menuItem['class'])) {
$classAttr[] = $menuItem['class'];
}
if (strpos($_SERVER['REQUEST_URI'], $menuItem['url']) !== false) {
$classAttr[] = 'selected';
}
if ($classAttr) {
$liNode->setAttribute('class', implode(' ', $classAttr));
}
// add target attribute
if (isset($menuItem['target'])) {
$liNode->setAttribute('target', $menuItem['target']);
}
// set absolute URL
if (strpos($menuItem['url'], 'http') === false) {
$menuItem['url'] = BASE_URL . '/' . $menuItem['url'];
}
$aNode->setAttribute('href', $menuItem['url']);
// add link
$aNode->nodeValue = $menuItem['text'];
$liNode->appendChild($aNode);
// add menu items
if (isset($menuItem['menu'])) {
$liNode->appendChild($this->buildMenu($menuItem['menu'], $depth + 1));
}
return $liNode;
}
/**
* Get menu.
*/
public function getMenu()
{
return $this->doc->saveHTML();
}
/**
* Create nav element that will wrap list.
*/
private function createNav()
{
$divNode = $this->doc->createElement('div');
$divNode->setAttribute('class', 'nav');
return $divNode;
}
/**
* Check if node element is nav element i.e. has 'nav' class name
* @param $node DOMElement
*/
private function isNav(DOMElement $node)
{
return array_search('nav', explode(' ', $node->getAttribute('class'))) !== false;
}
}
This is how it would be used:
$menu = array(
array(
"text" => "Nav Item 1",
"url" => "page1.php"
),
array(
"text" => "Nav Item 2",
"url" => "page2.php",
"menu" => array(
array(
"text" => "Nav Item 2.1",
"url" => "#",
"menu" => array(
array(
"text" => "Nav Item 2.1.1",
"url" => "http://www.google.com",
"target" => "_blank"
),
array(
"text" => "Nav Item 2.1.2",
"url" => "page2-1-2.php",
"class" => "page2-1-2"
)
),
),
array(
"text" => "Nav Item 2.2",
"url" => "page2-2.php"
)
),
"class" => "nav-item-2"
)
);
$nav = new Navigation($menu);
$nav->select('page2-1-2.php');
echo $nav->getMenu();
This will generate:
<div class="nav" id="primary-nav">
<ul>
<li><a href="http://localhost/site/page1.php">Nav Item 1</a></li>
<li class="nav-item-2">
<a href="http://localhost/site/page2.php">Nav Item 2</a>
<div class="nav">
<ul>
<li>
<a href="http://localhost/site/#">Nav Item 2.1</a>
<div class="nav">
<ul>
<li target="_blank"><a href="http://www.google.com">Nav Item 2.1.1</a></li>
<li class="page2-1-2"><a href="http://localhost/site/page2-1-2.php">Nav Item 2.1.2</a></li>
</ul>
</div>
</li>
<li><a href="http://localhost/site/page2-2.php">Nav Item 2.2</a></li>
</ul>
</div>
</li>
</ul>
</div>
I am aware of Pear HTML_Menu but I wanted to create my own implementation. Initially, I was building the HTML using string concatenation, but I felt it had some limitations. So I switched to building the HTML using a tree structure which gives me more control on manipulating elements.
Anyways, any suggestions on how to improve this?