Dynamic Menu Builder for Bootstrap 3: Item and Link

Reza Lavaryan
This entry is part 2 of 2 in the series Dynamic Menu Builder for Bootstrap 3

Dynamic Menu Builder for Bootstrap 3

In part 1, we prototyped the end product and wrote the main Menu class, which serves as the menu manager – a container to hold all sub-units (items and links). In this part, we’ll build the remainder of the classes and demonstrate the menu builder’s usage.

Item

Represents our menu items as independent objects.

Create a new file called item.php and paste in the following code:

item.php

class Item {
	
	protected $manager;
	protected $id;
	protected $pid;
	protected $meta;
	protected $attributes = array();
	
	public    $link;
	
	//...
  • $manager stores a reference to the menu manager (Menu object). This makes us able to use menu manager methods within Item context.
  • $id stores the item’s id.
  • $pid stores item’s parent id if it has one otherwise it’ll be set to null.
  • $meta an array for storing extra data with each item.
  • $attributes an array of HTML attributes.
  • $link stores an instance of class Link.

__construct(manager, title, url, attributes, pid)

Initializes the attributes.

public function __construct($manager, $title, $url, $attributes = array(), $pid = 0)
{
	$this->manager     = $manager;
	$this->id          = $this->id();
	$this->pid         = $pid;
	$this->title       = $title;
	$this->attributes  = $attributes;
	
	// Create an object of type Link
	$this->link        = new Link($title, $url);
}

add(title, options)

Class Item has an add() method as well (just like the menu manager). In fact this method doesn’t create items on its own. It gets the arguments, adds a pid key to $options and calls add() of the menu manager.

public function add($title, $options)
{
	if( !is_array($options) ) {
			$options = array('url' => $options);
		}
		
	$options['pid'] = $this->id;
				
	return $this->manager->add( $title, $options );
}

This gives us the power to create sub items in a more semantic way rather than explicitly defining a pid:

	$menu = new Menu;
	
	$about = $menu->add('About', 'about');
	
	// We write it this way
	$about->add('What we do?', 'what-we-do');
	
	// instead of:
	// $menu->add('What we do?', array('url' => 'what-we-do', 'pid' => $about->get_id()));

id()

Generates a unique id for the Item. We use this identifier to refer to the item later.

protected function id()
{
	return $this->manager->length() + 1;
}

In fact id() calls length() of the menu manager and increments it by 1.

get_id()

We also need to create a getter method to return the id when needed:

public function get_id()
{
	return $this->id;
}

get_pid()

Items might have pid (parent’s id). pid value might be null or id of another item.

Items with pid set to null are the items at root level.

We need to create a getter to return Item’s pid as well:

public function get_pid()
{
	return $this->pid;
}

hasChildren()

Checks whether the item has any children or not.

public function hasChildren()
{
	return (count($this->manager->whereParent($this->id))) ? true : false;
}

It calls whereParent() via the manager.

children()

Fetches children of the item.

public function children()
{
	return $this->manager->whereParent($this->id);
}

attributes(key, value)

Gets or sets item’s attributes.

public function attributes()
{
	$args = func_get_args();

	if(is_array($args[0])) {
		$this->attributes = array_merge($this->attributes, $args[0]);
		return $this;
	}
	
	elseif(isset($args[0]) && isset($args[1])) {
		$this->attributes[$args[0]] = $args[1];
		return $this;
	} 
	
	elseif(isset($args[0])) {
		return isset($this->attributes[$args[0]]) ? $this->attributes[$args[0]] : null;
	}
	
	return $this->attributes;	
}

As you see attributes() returns different types of results according to the arguments given:

  • Sets attribute if both $key and $value given.
  • Sets array of attributes if $key is an array.
  • Gets attribute if only $key given.
  • Gets all attributes if no argument given.

meta($key, $value)

Meta stores extra data about the item. It can be any kind of data from placement order to required permissions.

public function meta()
{
	$args = func_get_args();

	if(is_array($args[0])) {
		$this->meta = array_merge($this->meta, $args[0]);
		return $this;
	}
	
	elseif(isset($args[0]) && isset($args[1])) {
		$this->meta[$args[0]] = $args[1];
		return $this;
	} 
	
	elseif(isset($args[0])) {
		return isset($this->meta[$args[0]]) ? $this->meta[$args[0]] : null;
	}
	
	return $this->meta;
}

meta() works exactly like attributes().

Now let’s move on to the Link class.

Class Link is a simple class consisting of several getter and setter methods.

Link has three attributes:

  • text link text
  • url link URL
  • attributes link attributes

link.php

class Link {
	
	public $text;
	public $url;
	public $attributes;

	//....

__construct(text, url, attributes)

When we create an object of type Link, the constructor method binds the arguments to the attributes listed above:

public function __construct($text, $url, $attributes = array())
{
	$this->text = $text;
	
	$this->url = $url;
	
	$this->attributes = $attributes;
}

string get_url()

Returns link url.

public function get_url()
{
	return $this->url;
}

string get_text()

Returns the link text

public function get_text()
{
	return $this->text;
}

You’ll encounter situations when you need to append or prepend some content to the anchor text like a caret sign for drop-downs or a graphical icon. To achieve this, we will create two simple functions that do just the thing for us.

append(content)

append adds content to the link text:

public function append($content)
{
	$this->text .= $content;
	
	return $this;
}

prepend(content)

prepend prepends content to the link:

public function prepend($content)
{
	$this->text = $content . $this->text;
	
	return $this;
}

attributes(key, value)

Like Items It would be fantastic if we could define HTML attributes for anchors.

public function attributes($key = null, $value = null)
{
	$args = func_get_args();

	if(is_array($args[0])) {
		$this->attributes = array_merge($this->attributes, $args[0]);
		return $this;
	}
	
	elseif(isset($args[0]) && isset($args[1])) {
		$this->attributes[$args[0]] = $args[1];
		return $this;
	} 
	
	elseif(isset($args[0])) {
		return isset($this->attributes[$args[0]]) ? $this->attributes[$args[0]] : null;
	}
	
	return $this->attributes;
}

I think this method is familiar to you since we’ve created it earlier.

With this, our Menu Builder is complete!

Usage

We usually create one PHP file per class definition, so, to use our menu builder we need to include each file at the beginning of our script.

Rather than including all the three files, I’m going to take advantage of class autoloading feature in PHP: __autoload(string $class). This feature helps us avoid writing a long list of includes at the beginning of each script.

__autoload() is automatically called in case you are trying to use a class or interface which hasn’t been defined yet.

__autoload receives the class name as argument.

This is how we’re going to use it:

function __autoload($class) {
	require_once(strtolower($class) . '.php');
}

Name this file autoload.php and include it in your script.

Please note that this is probably less than ideal. In a real project, your autoloading needs would be taken care of by Composer or the framework’s autoloader. You can see this on the Github link we provided – the project is fully developed there, and fine tuned for use with Laravel, among others.

Next, let’s create a menu to test our menu builder out:

<?php
require_once('autoload.php');

$menu = new Menu;

$about = $menu->add('About', 'about');

// since this item has sub items we append a caret icon to the hyperlink text
$about->link->append(' <span class="caret"></span>');

// we can attach HTML attributes to the hyper-link as well
$about->link->attributes(['class' => 'link-item', 'target' => '_blank']);

// Adding an attribute to the item wrapper itself
$about->attributes('data-model', 'info');

$about->add('Who we are?', array('url' => 'who-we-are',  'class' => 'navbar-item whoweare'));
$about->add('What we do?', array('url' => 'what-we-do',  'class' => 'navbar-item whatwedo'));

$about->add('Goals', array('url' => 'goals', 'display' => false));

$menu->add('Portfolio', 'portfolio');
$menu->add('Contact',   'contact');

// we're only going to hide items with `display` set to **false**

$menu->filter( function($item){
	if( $item->meta('display') === false) {
		return false;
	}
	return true;
});

// Now we can render the menu as various HTML entities:

echo $menu->asUl( attribute('class' => 'ausomw-ul') );

//OR

echo $menu->asOl( attribute('class' => 'ausomw-ol') );

// OR

echo $menu->asDiv( attribute('class' => 'ausomw-div') );

?>

Done!

Bootstrap 3 Navbar

The final step is to use our menu builder to create dynamic Bootstrap Navbars.

First of all, we need to create a function that populates our items in a Bootstrap friendly format because the existing render method doesn’t do this for us.

I name this function bootstrapItems() (I couldn’t really think of a better name, feel free to name it whatever you please).

You can put this function in any file you like as long as it is loaded at application startup. Alternatively you can extend the class Menu and add this method to the class. In this example, I place it in autoloader.php (as a helper function) to make sure it is always available to me.

function bootstrapItems($items) {
	
	// Starting from items at root level
	if( !is_array($items) ) {
		$items = $items->roots();
	}
	
	foreach( $items as $item ) {
	?>
		<li <?php if($item->hasChildren()): ?> class="dropdown" <?php endif ?>>
		<a href="<?php echo $item->link->get_url() ?>" <?php if($item->hasChildren()): ?> class="dropdown-toggle" data-toggle="dropdown" <?php endif ?>>
		 <?php echo $item->link->get_text() ?> <?php if($item->hasChildren()): ?> <b class="caret"></b> <?php endif ?></a>
		<?php if($item->hasChildren()): ?>
		<ul class="dropdown-menu">
		<?php bootstrapItems( $item->children() ) ?>
		</ul> 
		<?php endif ?>
		</li>
	<?php
	}
}

Since it’s just for educational purpose I didn’t use a template engine here as It’s beyond the scope of this tutorial. You can use the template engine of your choice to separate logic from presentation and make your code more readable.

Let’s see what BootstrapItems does behind the scenes.

First of all, it checks whether the given argument is an array or not. If it’s not, it fetches the items at root level and iterates over them. On each iteration it checks also if the current element has any children and if the element does have children, it will call itself passing the element’s children as a parameter. This process is repeated until it renders all the items to the deepest level.

Okay, now that we are able to generate the items in a Bootstrap friendly format, let’s register some items:

<?php
require_once('autoload.php');
// $menu #1
$main = new Menu;

$main->add('<span class="glyphicon glyphicon-home"></span>', '');
$about = $main->add('about', 'about');
   $about->add('Who we are?', 'who-we-are?');
   $about->add('What we do?', 'what-we-do?');
$main->add('Services', 'services');
$main->add('Portfolio', 'portfolio');
$main->add('Contact', 'contact');

// menu #2
$user = new Menu;

$user->add('login', 'login');
$profile = $user->add('Profile', 'profile');
  $profile->add('Account', 'account')
          ->link->prepend('<span class="glyphicon glyphicon-user"></span> ');
  
  $profile->add('Settings', 'settings')
          ->link->prepend('<span class="glyphicon glyphicon-cog"></span> ');
	
?>

And here’s our boilerplate code:

<nav class="navbar navbar-default" role="navigation">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">Sitepoint</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        <?php echo bootstrapItems($main); ?>
      </ul>
      <form class="navbar-form navbar-left" role="search">
        <div class="form-group">
          <input type="text" class="form-control" placeholder="Search">
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
      </form>
      <ul class="nav navbar-nav navbar-right">
         	<?php echo bootstrapItems($user); ?>
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>

Because we have two different menus, we call BootstrapItems two times in our Bootstrap template.

Don’t forget to have jquery and bootstrap CSS and JS files loaded in your page before testing out the result!

Conclusion

We implemented a Menu manager, Items and Links in three class definitions for the sake of flexbility. We also stored a reference to the manager along with each item. This reference allowed us to access the manager from within Item context.

You can use this menu builder in any form you like as long as you use the right methods.

If you’re using Laravel 4, you can get laravel-menu which is implemented based on the methods described in this tutorial while providing more features, otherwise, see the full code of our built Menu Builder: fleximenu.

Happy coding!

Dynamic Menu Builder for Bootstrap 3

<< Dynamic Menu Builder for Bootstrap 3: Menu Manager

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.