Porto

Dynamically Create Menu With PHP DOM

Dynamically Create Menu With PHP DOM

By Kevin Waterson

Contents

  1. Abstract
  2. Getting Started
  3. Creating the Root Node
  4. Adding Menu Items
  5. Adding Hypertext Links
  6. Adding Sub Menus
  7. Credits

Abstract

Programatically createing menus using php is a task many programmers will need to do over and over again in developing applications and web sites. Most PHP coders will have their own menu generation class that will take an array or an item and add it to a HTML un-ordered list or other tag and the styling is handled with CSS. This has proven to be a worthy solution to menu creation, however, PHP already has all the tools and classes built in to create a menu using DOM. It is both powerful and extensible and further eliminates for yet another class in your tree.

Getting Started

Most menu systems revolve around an un-ordered list, and the formatting is handled with CSS. This type of menu allows the menu itself to be easily changed by simply altering the style sheet. The menu created in this tutorial will follow the same principles, however, the method of creating the menu may surprise you.

To begin with, a simple class is required to create the menu root and items within the menu. As the PHP DOM class is being used, this can be extended to make very simple when it comes time to create an add menu items.

Looking at the white board, the functionality required for the menu generation class is quite simple.

  • Create root node
  • Create list items withing the root node
  • Create sub menus, or sub nodes
  • Ability to create hypertext links for menu items
  • Valid HTML
When creating a menu item, it is not always safe to assume that it will be a hypertext link. A menu items can be, and often is, a simple javascript mouseover event. To this end, a seperate function will be needed to seperate the creation of an item, and making that item a hypertext link.

Creating the Root Node

The root node will be created in the constructor to ensure when the class is instantiated, that the root node will always be present. The constructor will also be used to format the HTML so that it is easily legible by mere humans. As the class extends the PHP DOM class, the functionality for all this is built in and no external code is needed.


<?php

class menuGen extends DOMDocument
{
    
/**
    * @var  string  $menu
    * @access       private
    */
    
public $menu;

    
/**
    *
    * Constructor, Calls parent and sets root node
    *
    * @access    public
    * @param    string    $version
    * @param    string    $encoding
    *
    */
    
public function __construct$version='1.0'$encoding='iso-8859-1' )
    {
        
parent::__construct$version$encoding);

        
/*** format the created XML ***/
        
$this->formatOutput true;

        
$this->menu $this->appendChildparent::createElement('ul'));
    }

    
/**
    *
    * Return a string representation of the menu
    *
    * @access    public
    * @return    string
    *
    */
    
public function __toString()
    {
        return 
html_entity_decode$this->saveHTML() );
    }
// end of class

With the basis of the class in place, it is now a simple matter of instantiating an instace of the class to produce the root node.


<?php
    
// a new list object
    
$list = new menuGen('1.0','iso-8859-15');

    
// set an attribute
    
$list->menu->setAttribute'class''css_class' );

    
// echo the list
    
echo $list

Stepping through the above code, a new list object is created. As the class extends the built in PHP DOM class, all the methods available in the DOM class are now available to the object which means the DOM::setAttribute method can be used to add the CSS class, or an ID, or any other attribute.

As the class utilizes the PHP magic method __toString() it is possible to echo the HTML list. The __toString() method, in turn, uses the DOM::saveHTML() method to give a HTML string representation of the object. The resulting HTML looks like this.

<ul class="css_class"></ul>

So, not very useful as it is. What is required is a method to add some items to the list.

Adding Menu Items

Now that the root node can be created, the class needs a method to create the list item elements. Once again, the built in DOM class provides a solution. By over riding the built in DOM::createElement method, the list items can be produced in a single call. The class will now look like this.


<?php

class menuGen extends DOMDocument
{
    
/**
    * @var  string  $menu
    * @access       private
    */
    
public $menu;

    
/**
    *
    * Constructor, Calls parent and sets root node
    *
    * @access    public
    * @param    string    $version
    * @param    string    $encoding
    *
    */
    
public function __construct$version='1.0'$encoding='iso-8859-1' )
    {
        
parent::__construct$version$encoding);

        
/*** format the created XML ***/
        
$this->formatOutput true;

        
$this->menu $this->appendChildparent::createElement('ul'));
    }

    
/**
    *
    * Over ride the parent createElement method
    *
    * @access    public
    * @param    string    $value
    * @return    object    domElement
    *
    */
    
public function createElement$value=null )
    {
        return 
parent::createElement'li'$value );
    }

    
/**
    *
    * Return a string representation of the menu
    *
    * @access    public
    * @return    string
    *
    */
    
public function __toString()
    {
        return 
html_entity_decode$this->saveHTML() );
    }
// end of class

?>

The newly created createElement method, over rides the parent DOM::createElement method so that it takes only a single arguement. This is because the elements will always be a list item <li> and the value is the only variable required. With this in place, the code to implement it is simple. The below example shows two elements added to the root node.


<?php

// a new menu instance
$list = new menuGen('1.0','iso-8859-15');

// add and attribute
$list->menu->setAttribute'class''css_class' );

// create a list element
$item $list->createElement(  'First' );

// add an id attribute node
$item->setAttributeNode( new DOMAttr'id''first_id' ) );

// append the first element node to the root node
$list->menu->appendChild$item );

// add a second list item element
$item $list->createElement(  'Second' );

// set an id attribute node
$item->setAttributeNode( new DOMAttr'id''second_id' ) );

// append the first element node to the root node
$list->menu->appendChild$item );

// display the list
echo $list;

?>

In the code above, the same instantiation process takes place as in the first example. An item is then created from the using the new createElement() method and then, to show how an attribute is added to a list item, the setAttributeNode() method is used, The item, is then appended to the parent list as a child node. The process is then repeated a second time to add a second element. The resulting HTML code looks like this.

<ul class="css_class">
<li id="first_id">First</li>
<li id="second_id">Second</li>
</ul>

This is fine and the list is taking shape, but there needs to be something to click on for it to work. Here there are two options. A java script onclick event, or, a hypertext link. Both methods can be expressed as an attribute node, and set in the same way as the id tag is set with the setAttributeNode() method.

In this following example, an onclick event is added to the second menu item from above.

The class definition remains the same, and the only change needed is to the userland code.


<?php
// a new menu instance
$list = new menuGen('1.0','iso-8859-15');

// add and attribute
$list->menu->setAttribute'class''css_class' );

// create a list element
$item $list->createElement(  'First' );

// add an id attribute node
$item->setAttributeNode( new DOMAttr'id''first_id' ) );
$item->setAttributeNode( new DOMAttr'onclick''alert("A Message")' ) );

// append the first element node to the root node
$list->menu->appendChild$item );

// add a second list item element
$item $list->createElement(  'Second' );

// set an id attribute node
$item->setAttributeNode( new DOMAttr'id''second_id' ) );
$item->setAttributeNode( new DOMAttr'onclick''alert("Another Message")' ) );
// append the first element node to the root node
$list->menu->appendChild$item );

// display the list
echo $list;

The HTML produced from the above code looks like this.

<ul class="css_class">
<li id="first_id" onclick='alert("A Message")'>First</li>
<li id="second_id" onclick='alert("Another Message")'>Second</li>
</ul>

Adding Hypertext Links

Adding a hypertext link is much the same as with other attribute nodes, with the exception that the node name is a given, it will always be an anchor tag, and will always have a href. With this in mind, the menu generation class can be ustilised to add the new links. Here the class added to a little more to provide a method for adding links.


<?php

class menuGen extends DOMDocument
{
    
/**
    * @var  string  $menu
    * @access       private
    */
    
public $menu;

    
/**
    *
    * Constructor, Calls parent and sets root node
    *
    * @access    public
    * @param    string    $version
    * @param    string    $encoding
    *
    */
    
public function __construct$version='1.0'$encoding='iso-8859-1' )
    {
        
parent::__construct$version$encoding);

        
/*** format the created XML ***/
        
$this->formatOutput true;

        
$this->menu $this->appendChildparent::createElement('ul'));
    }

    
/**
    *
    * Over ride the parent createElement method
    *
    * @access    public
    * @param    string    $value
    * @return    object    domElement
    *
    */
    
public function createElement$value=null )
    {
        return 
parent::createElement'li'$value );
    }

    
/**
    *
    * Create a hypertext link
    *
    * @access    public
    * @param    string    $name
    * @param    string    $url
    * @return    object    DomElement
    *
    */
    
public function createLink$name$url )
    {
        
$link parent::createElement'a'$name );
        
$link->setAttributeNode( new DOMAttr'href'$url ) );
        return 
$link;
    }

    
/**
    *
    * Return a string representation of the menu
    *
    * @access    public
    * @return    string
    *
    */
    
public function __toString()
    {
        return 
html_entity_decode$this->saveHTML() );
    }
// end of class

With the new createLink() method in place, creating links is now as simple as calling the class method, and appending it to the list item object.


<?php
// a new menu instance
$list = new menuGen('1.0','iso-8859-15');

// add and attribute
$list->menu->setAttribute'class''css_class' );

// create a list element without any arguement
$item $list->createElement( );

// add an id attribute node
$item->setAttributeNode( new DOMAttr'id''first_id' ) );

// append the first element node to the root node
$list->menu->appendChild$item );

// create the link
$link $list->createLink('Link One''/link1.php' );

// append the link to the item
$item->appendChild$link );

// add a second list item element without an arguement
$item $list->createElement( );

// set an id attribute node
$item->setAttributeNode( new DOMAttr'id''second_id' ) );

// append the first element node to the root node
$list->menu->appendChild$item );

// create the link
$link $list->createLink('Link Two''/link2.php' );

// append the link to the item
$item->appendChild$link );

// display the list
echo $list;

Not in the above userland code, that no arguement is passed to the createElement method. This is because, the text will be the hypertext link created with createLink(). The HTML code generated now looks like this.

<ul class="css_class">
<li id="first_id"><a href="/link1.php">Link One</a>
</li>
<li id="second_id"><a href="/link2.php">Link Two</a>
</li>
</ul>

Adding Sub Menus

The above menu structure works fine, and with a little CSS added can be made to behave in many ways, horizontally, vertically, hovers, etc. However, most menu\'s have more than a single level of depth. The menu class needs no changing to create new levels, it is ready to go, and the built in PHP DOM functions do the rest.

The following code demonstrates how the menu, sub menus, and sub sub menus may be created.


<?php
    
// a new menuGenb instance
    
$list = new menuGen('1.0','iso-8859-15');
    
$list->menu->setAttribute'class''css_class' );

    
// create the first item
    
$item $list->createElement(  'First' );
    
$item->setAttributeNode( new DOMAttr'id''first_id' ) );
    
$list->menu->appendChild$item );

    
// create the second item
    
$item2 $list->createElement'Second' );
    
$item2->setAttributeNode( new DOMAttr'id''second_id' ) );
    
$list->menu->appendChild$item2 );

    
// the value for item three will be a sub menu
    
$item3 $list->createElement'Third' );
    
$item3->setAttributeNode( new DOMAttr'id''third_id' ) );
    
$list->menu->appendChild$item3 );

    
// a sub node
    
$sub = new menuGen;
    
$sub_item1 $sub->createElement'' );
    
$sub_item1->setAttributeNode( new DOMAttr'id''sub_item1_id' ) );
    
$sub->menu->appendChild$sub_item1 );
    
$link $sub->createLink('Link''/index.php' );
    
$sub_item1->appendChild$link );

    
$sub_item2 $sub->createElement'Sub Two' );
    
$sub_item2->setAttributeNode( new DOMAttr'id''sub_item2_id' ) );
    
$sub_item2->setAttributeNode( new DOMAttr'onclick''alert("A Message")' ) );
    
$sub->menu->appendChild$sub_item2 );

    
// append the sub node to the item3 element
    
$item3->appendChild($list->createTextNode$sub ) );

    
// the value for item three will be a sub menu
    
$item4 $list->createElement'Fourth' );
    
$item4->setAttributeNode( new DOMAttr'id''fourth_id' ) );
    
$list->menu->appendChild$item4 );

    
// a sub node
    
$sub = new menuGen;
    
$sub_item1 $sub->createElement'sub_one' );
    
$sub_item1->setAttributeNode( new DOMAttr'id''sub_item_id' ) );
    
$sub->menu->appendChild$sub_item1 );

    
// a sub sub node for the fourth menu item
    
$sub_sub = new menuGen;
    
$sub_sub_item1 $sub_sub->createElement'Fourth Sub Sub One' );
    
$sub_sub_item1->setAttributeNode( new DOMAttr'id''fourth_sub_sub_item1_id' ) );
    
$sub_sub->menu->appendChild$sub_sub_item1 );

    
$sub_sub_item2 $sub_sub->createElement'Fourth Sub Sub Two' );
    
$sub_sub_item2->setAttributeNode( new DOMAttr'id''fourth_sub_sub_item2_id' ) );
    
$sub_sub->menu->appendChild$sub_sub_item2 );

    
// append the sub node to the item4 element
    
$sub_item1->appendChild$sub->createTextNode$sub_sub ) );

    
// a second sub item for the fourth level
    
$sub_item2 $sub->createElement'sub two' );
    
$sub_item2->setAttributeNode( new DOMAttr'id''fourth_sub_item2_id' ) );
    
$sub->menu->appendChild$sub_item2 );

    
// append the sub node to the item4 element
    
$item4->appendChild($list->createTextNode$sub ) );

    echo 
$list;

The resulting HTML code now looks like this.

<ul class="css_class">
<li id="first_id">First<li>
<li id="second_id">Second</li>
<li id="third_id">Third<ul>
<li id="sub_item1_id"><a href="/index.php">Link</a>
</li>
<li id="sub_item2_id" onclick='alert("A Message")'>Sub Two</li>
</ul>
</li>
<li id="fourth_id">Fourth<ul>
<li id="sub_item_id">sub_one<ul>
<li id="fourth_sub_sub_item1_id">Fourth Sub Sub One</li>
<li id="fourth_sub_sub_item2_id">Fourth Sub Sub Two</li>
</ul>

</li>
<li id="fourth_sub_item2_id">sub two</li>
</ul>

</li>
</ul>

Credits

Special thanks to Capt. James Cook for discovering Australia