Cross browser PHP drop down menu generator powered by JQuery and CSS.

15 May

I needed to create a drop down menu that supported all the major browsers that was generated with dynamic content. So I decided to use the tried and true unordered HTML list approach. This approach can be easily controlled with pure CSS in browsers like Chrome and Firefox, however, other browsers such as Internet Explorer cause issues so JQuery is required to force compliance across multiple platforms and browsers.

First I created this neat little class that can be used to create an object model of MenuItems that can be later serialized into a HTML unordered list. This model is basically a linked list of MenuItems where the serialize function just iterates through each object and its children converting its properties into a string.

class MenuItem {

    private $name;
    private $url;
    private $level;
    private $items;

    public function __construct($item_name, $item_lvl, $item_url = "#") {
        $this->name = $item_name;
        $this->url = $item_url;
        $this->level = $item_lvl;
        $this->items = array();
    }

    public function add_item(MenuItem $menu_item) {
        $this->items[] = $menu_item;
    }

    public function serialize() {
	$html = "";

        if ($this->level == 0) {
            $html = "<ul class=\"menu\">\n";
        }

        if (!empty($this->items)) {
            $html .= "<li class=\"{$this->get_class()} menu_arrow\">";
        } else {
            $html .= "<li class=\"{$this->get_class()}\">";
        }

        $html .= "<a href=\"$this->url\">$this->name";

        if (!empty($this->items)) {
            $html .= "</a>\n<ul class=\"{$this->get_class()}_block\">\n";
            foreach ($this->items as $menu_item) {
                $html .= $menu_item->serialize();
            }
            $html .= "</ul>\n";
        } else {
            $html .= "</a>";
        }

        $html .= "</li>\n";

        if ($this->level == 0) {
            $html .= "</ul>\n";
        }

        return $html;
    }

    private function get_class() {
        switch ($this->level) {
            case 0:
                $class = "level_zero";
                break;
            case 1:
                $class = "level_one";
                break;
            case 2:
                $class = "level_two";
                break;
        }
        return $class;
    }
}

This class makes it very easy to dynamically generate a multi tiered menu. All you have to do is create your base items and add them to the MenuItem that they belong too.

$search_links = new MenuItem("Search", 1);
$search_links->add_item(
        new MenuItem("Google", 2, "http://www.google.com")
);
$search_links->add_item(
        new MenuItem("Yahoo", 2, "http://www.yahoo.com")
);

$social_links = new MenuItem("Social", 1);
$social_links->add_item(
        new MenuItem("Facebook", 2, "http://www.facebook.com")
);
$social_links->add_item(
        new MenuItem("Twitter", 2, "http://www.twitter.com")
);

$menu = new MenuItem("Links", 0);
$menu->add_item($search_links);
$menu->add_item($social_links);

echo $menu->serialize();

The code above generates the unordered list below which, as mentioned before, can be controlled with pure CSS in the newer standards compliant browsers.

<ul class="menu">
<li class="level_zero menu_arrow"><a href="#">Links</a>
<ul class="level_zero_block">
<li class="level_one menu_arrow"><a href="#">Search</a>
<ul class="level_one_block">
<li class="level_two"><a href="http://www.google.com">Google</a></li>
<li class="level_two"><a href="http://www.yahoo.com">Yahoo</a></li>
</ul>
</li>
<li class="level_one menu_arrow"><a href="#">Social</a>
<ul class="level_one_block">
<li class="level_two"><a href="http://www.facebook.com">Facebook</a></li>
<li class="level_two"><a href="http://www.twitter.com">Twitter</a></li>
</ul>
</li>
</ul>
</li>
</ul>

The list above is controlled with the tiniest amount of CSS below. Of course the nicer you wan’t your menu to look the more CSS you will need to throw in!

ul.menu li ul {
	display: none;
}

The JQuery code I used to control the above menu is larger then it needs to be in order to make the menu support most browsers and operating systems. Many of the hacks in the script is to force Internet Explorer 6/7 to conform to the likes of Chrome and Firefox. Only the min width value needs to be set here to get your menu pop outs positioned correctly.

/* IE does not support min-width so we can get it with js since we are 
 * already iterating through the items to display them.
 */
function setMinWidth(element) {
	if (element.width() < 150) {
		element.css('width', '150px')
	}
}

/* IE positoning is kinda random compared to FF, Chrome and Safari so
 * this is to force it to conform.
 */
function positionPopout(element, top) {
	element.css('display', 'block');
	element.css('top', top + "px");
	setMinWidth(element);
}

$(document).ready(function() {
	$('li.level_zero').hover(
		function() {
			var element = $('ul.level_zero_block', this);
			var top = $(this).offset().top + $(this).height();

			//Fix IE left
			element.css('left', ($(this).position().left + 2) + "px");
			positionPopout(element, top);
		},
		function() {
			$('ul.level_zero_block', this).css('display', 'none');
		}
	);

	$('li.level_one').hover(
		function() {
			var element = $('ul.level_one_block', this);
			var top = $(this).offset().top -  + $(this).height();

			positionPopout(element, top);
			setMinWidth(element);
		},
		function() {
			$('ul.level_one_block', this).css('display', 'none');
		}
	);

	$('li.level_two').hover(
		function() {
			var element = $('ul.level_two_block', this);
			var top = $(this).offset().top - ($(this).height() * 2);

			positionPopout(element, top);
			setMinWidth(element);
		},
		function() {
			$('ul.level_two_block', this).css('display', 'none');
		}
	);
});

This way of providing a drop down menu for your website is not a new idea, however I think using the MenuItem class is an elegant and easy way of dynamically generating this style of menu.

 
2 Comments

Posted in JQuery, PHP

 

Tags: ,

  1. Aleen Botello

    January 19, 2015 at 2:45 am

    Always an awesome submit after i check out this blog and various websites you possess. Value your insights.

     
  2. RosamondLogo Designer

    October 12, 2014 at 10:04 am

    just feel that this information is tremendously helpful. I’m sure the following quote will appeal to your interest. – “Failure is not the only punishment for laziness; there is also the success of others.” ~ Jules Renard (1864 – 1910)