How to display infinite depth expandable categories using php and javascript

2007-09-30

Build a simple infinite depth category system for your site, subcategories expand using javascript without refreshing the page.

Let's start by showing the database structure and data


CREATE TABLE `categories` (

  `id` int(11) NOT NULL auto_increment,

  `name` varchar(100) NOT NULL,

  `parent` int(11) NOT NULL,

  PRIMARY KEY  (`id`)

) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=13 ;



-- 

-- Dumping data for table `categories`

-- 



INSERT INTO `categories` (`id`, `name`, `parent`) VALUES 

(1, 'Web development', 0),

(2, 'Application development', 0),

(3, 'Linux', 0),

(4, 'Misc', 0),

(5, 'Php', 1),

(6, 'Mysql', 1),

(7, 'Javascript', 1),

(8, 'CSS', 1),

(9, 'C plus plus', 2),

(10, 'wxWidgets', 2),

(11, 'Tutorials', 3),

(12, 'My thoughts', 4);

We are using a single table to hold together categories and subcategories. Each subcategory can have a parent to show which category it belongs to, and they can be linked recursively

In this example, we have only 1 level subcategories, but you can add how many levels you would like to. For example category 'Web development' has 4 subcategories: Php, Mysql, Javascript and CSS

Using data from this table we will generate a nested html unordered list using the php code below


<?php

//connect to database

$link = mysqli_connect('localhost','root','');

mysqli_select_db($link,'your_database_name');





//get all rows

$query = mysqli_query($link,'SELECT * FROM categories');

while ( $row = mysqli_fetch_assoc($query) )

{

	$menu_array[$row['id']] = array('name' => $row['name'],'parent' => $row['parent']);

}



//recursive function that prints categories as a nested html unorderd list

function generate_menu($parent)

{

	$has_childs = false;

	//this prevents printing 'ul' if we don't have subcategories for this category



	global $menu_array;

	//use global array variable instead of a local variable to lower stack memory requierment



	foreach($menu_array as $key => $value)

	{

		if ($value['parent'] == $parent) 

		{	

			//if this is the first child print '<ul>'			

			if ($has_childs === false)

			{

				//don't print '<ul>' multiple times				

				$has_childs = true;

				echo '<ul>';

			}

			echo '<li><a href="/category/' . $value['name'] . '/">' . $value['name'] . '</a>';

			generate_menu($key);

			//call function again to generate nested list for subcategories belonging to this category

			echo '</li>';

		}

	}

	if ($has_childs === true) echo '</ul>';

}





//generate menu starting with parent categories (that have a 0 parent)

generate_menu(0);

And the resulting html unordered list looks like this


<ul id="categories">

	<li>Web development

		<ul>

			<li><a href="/category/Php/">Php</a></li>

			<li><a href="/category/Mysql/">Mysql</a>

			</li><li><a href="/category/Javascript/">Javascript</a></li>

			<li><a href="/category/CSS/">CSS</a></li>

		</ul>

	</li>

	<li>Application development

		<ul>

			<li><a href="/category/C-plus-plus/">C plus plus</a></li

			><li><a href="/category/wxWidgets/">wxWidgets</a></li>

		</ul>

	</li>

	<li>Linux

		<ul>

			<li><a href="/category/Tutorials/">Tutorials</a></li>

		</ul>

	</li>

	<li>Misc

		<ul>

			<li><a href="/category/My-thoughts/">My thoughts</a></li>

		</ul>

	</li>

</ul>

Now there are to ways of displaying your category menu. One is by using only css like this


#categories

{

	list-style-position:inside;

	list-style-image: url(/examples/categories/bullet.png);

	font-size:10px;

	font-family:Tahoma,Verdana,Arial;

	margin:0px;

	padding:0px;

}

And the other is by using javascript to make your categories expandable. This is useful if you have lots of subcategories


<ul id="categories">

	<li>Web development

		<ul>

			<li><a href="/category/Php/">Php</a></li>

			<li><a href="/category/Mysql/">Mysql</a>

			</li><li><a href="/category/Javascript/">Javascript</a></li>

			<li><a href="/category/CSS/">CSS</a></li>

		</ul>

	</li>

	<li>Application development

		<ul>

			<li><a href="/category/C-plus-plus/">C plus plus</a></li

			><li><a href="/category/wxWidgets/">wxWidgets</a></li>

		</ul>

	</li>

	<li>Linux

		<ul>

			<li><a href="/category/Tutorials/">Tutorials</a></li>

		</ul>

	</li>

	<li>Misc

		<ul>

			<li><a href="/category/My-thoughts/">My thoughts</a></li>

		</ul>

	</li>

</ul>

<script>menu_initiate();</script>

Download all files for this example, including javascript from here

Icons from sweetie.sublink.ca, javascript code (slightly improved) from javascript.internet.com

Update:Rik Moncur sent me a menu implementation based on this code, you can download the code from here dynamicmenu.zip, thanks Rik

Share this with the world

Related

Comments

Jason

The css version doesn't seem to function as per your example i.e. the first item in the menu has href link but should be just text?

Hope that makes sense - great tutorial though :-)

Regards

Jason

Posted on 2007-10-05 23:02:58
CodeAssembly

Yes, your observation is correct, I removed the links from top categories, because I don't have those categories on my website, and I don't like putting # on my links.
Thanks.

Posted on 2007-11-19 15:53:44
kasp3r

Thank you very much for your article. It helped me a lot!

Posted on 2007-11-19 15:52:58
Schalk

Hi,

I have been looking for something like this for ages, thank you so much! I just have one problem, and I'm sure it would just require a little tweaking of some sorts...When the initial parent level (0) is generated, the only 0 level links that is displayed is 'Web Development' with 'PHP" underneath and that's it! Do you know why this is happening, or even better how I can fix it?

Many Thanks in advance Schalk

Posted on 2007-11-09 15:39:34
Jason

Hi

Having the same problem as Schalk above - works perfectly on one host but not on another?

Is there a specific PHP module that has to be enabled/installed for this to work?

Regards

Jason

Posted on 2007-11-26 14:32:42
Stefan

Hi,

I discovered a problem when using the script in
combination with prototype.

The fix is simple ...

file:expand.js line:12

-- OLD --
for(i in cookieA)

-- NEW --
for(i=0;i<cookieA.length;i++)


Prototype adds some generic methods so using the "for in"-iteration causes errors.


cheers

Stefan

Posted on 2008-03-21 20:02:43
Eric

Hi,

I'm having the same problems as Schalk & Jason whereby only "Web Development" & "Php" are printing. It stops there.

Can anyone else get this to work?

Thanks

Posted on 2008-03-21 20:02:43
Mk

Hi. This code is great! But one problem: I copied the code straight from this page, and it works fine, except for one thing. I don't want the category names to be linked. I only want the subcategories to be linked. In the example displayed on this page, the category names are not linked, and this is how I want it, but when I copied the code, it makes them links. How do I fix this?

Posted on 2008-03-21 20:02:43
Ken

Hi, It looks like this script has a bug. I also has problem listing all of the categories. It starts nicely by listing Web development og followed by Php, but then it stops.

It really seems like the loop does stops when the last sub categori does not contain a new sub. Because if you add a new sub cat. it will be listed.

Ok? Some ideas?

Posted on 2008-03-05 14:58:50
MacGoerk

Actually i think this has something to do with the use of a global var.

If you change the function to

generate_menu($parent, $menu_array) {

remove the line "global $menu_array;"

change the inner recursive call to generate_menu($key, $menu_array)

and the initial call to the recursive function to generate_menu(0, $menu_array) where $menu_array is your mysql result set

it will work!
As php SHOULD make use of pointers and call_by_reference, this will indeed result in a slightly higher memory usage. If it isn't this is just ... going to be exponential large if you have a deeply nested structure. However I'll try this way :)

Also, change the mysqli_whatever calls to mysql_whatever, as mysqli is deprecated.

HTH
Mac

Posted on 2008-05-01 21:22:23
titel

Hi,

I was reading your article today and this is exactly what I need for the project I'm working on.

One think though, is there any way that the script is aware of the hierarchy when it displaying the URL as well?

I'll jut give you an example. Say we have
Web development - /web-development
- - Javascript - /web-development/javascript
- - - - AJAX - /web-development/javascript/ajax

Again, very good article and thanks a lot,
titel

Posted on 2008-06-13 22:17:10
Ashish

This is such a wonderful script....It really helps me to show the category tree like site map.
Thanx a lot

Posted on 2008-06-26 15:51:54
Terry

Hi, I have downloaded the menu version and am wondering how to implement an image arrow bullet for the parent categories to show there are children while recursive function sorts down the levels. Can't seem to make it work any thoughts??
thanks Terry

Posted on 2008-08-21 23:33:14
rocbar

Very good script, but the article could be a little more in detail explaining the variable $has_childs = false; in relationship to the $menu_array or the if ($value['parent'] == $parent). This would be helpfull for a newbee like me.

Posted on 2008-08-24 06:02:45
Daniel

If you want to display every folder with parent of 0 just replace

if ($has_childs === false)
{
//don't print '<ul>' multiple times
$has_childs = true;
echo '<ul>';
}
With

if ($has_childs === false)
{
//don't print '<ul>' multiple times
$has_childs = true;
echo '<ul id="categories">';
}


And Replace
<ul id="categories">
<li>Nested menu categories
<?php
generate_menu(0);
?>
</li></ul>

With

<?php
generate_menu(0);
?>

Posted on 2009-01-05 03:55:09
Locnav

i tried the script but it doesn't show the plus and minus images.
does it matter if i'm running the server on windows?

Posted on 2009-04-24 04:10:28
bobik

HI, i`m problem with this code in Mozilla, Opera and Explorer 6 and down version. This code is corect in Expolrer 7. Where is error. Please help me.

Posted on 2009-06-04 15:05:30
kevin

Hi,

here is a little mod I made to the script. For those you might be interested..

function generate_menu($parent){

$output = ''; // We will use this instead of echoing values straight into our function

$has_childs = false;

global $show_menu;

foreach($show_menu as $key => $value){
if($value['parent_id'] == $parent){
if($has_childs === false){
$has_childs = true;
}
if($value['parent_id'] == '0'){
$output .= '<div id='.$value['class_id'].$value['class'].'>';
$output .= '<div id='.$value['class_id'].$value['style_id'].'>'.$value['title'].'</div>';
$output .= '<ul id='.$value['class_id'].$value['list_style'].'>';
}else{
$output .= '<li><a href="">'.$value['title'].'</a>';
}
$output .= generate_menu($key);

$output .= '</li>';
}
}
if($has_childs === true){
$output .= '</ul>';
$output .='</div>';
}
return $output;
}


Also, this one will make your category name a Normal text instead of a link..

What i also did is add some fields in the Mysql Table and store my CSS class and ID, then pass them into the array and put then as variables in my HTML elements..

Posted on 2009-07-29 23:23:15
Kevin

Hi, Nice script, but is it possible to return the <ul><li> etc etc instead of echoing it directly in the function?

Please give some info on this!

Posted on 2009-07-29 19:26:02
yan

i could not expand the folder ....why?

Posted on 2009-08-13 11:40:15
John phpALD

This code will show all main categories.
It will also include the main category in the link when a subcategory has been selected.
and it will take care of the white space in the link when a category name has more then one word.

category name is cars and trucks then the link will be:
/cars-and-trucks/

if there is a subcategory in cars and trucks like new trucks
then the link will be:
/cars-and-trucks/new-trucks/


function generate_menu($parent)
{
$has_childs = false;
global $menu_array;
foreach($menu_array as $key => $value)
{
if ($value['parent'] == $parent)
{
if ($has_childs === false)
{
$has_childs = true;
echo '<ul id="categories">';
}
$cat = str_replace(" ", "-", $value['name']);
if ($value['parent'] == 0){
$cat = str_replace(" ", "-", $value['name']);
}
if ($value['parent'] > 0){
$ca = str_replace(" ", "-", $value['name']);
$selp = "SELECT * FROM `categories` WHERE `id` = '$parent'";
$queryp=mysql_query($selp);
while($rowp = mysql_fetch_array($queryp)){
$parentca = str_replace(" ", "-", $rowp['1']);
}
$cat = $parentca . '/' . $ca;
}
echo '<li><a href="/' . $cat . '/">' . $value['name'] . '</a>';
generate_menu($key);
echo '</li>';
}
}
if ($has_childs === true) echo '</ul>';
}

Posted on 2009-10-10 19:15:55
Vassilis

Hello, nice script. I have already used this script without problems in the past.

Now I'm using the script in a CMS as a menu and I have alternative cookies there. The problem is that every time I click to the menu all the categories that are already expanded, collapse immediately. I tried to change a lot of things to the script but with no luck.

Can you please help me? Thanks a lot.

Posted on 2009-11-18 03:55:54
Tester

Nice !!! Dos anybody know how to display the menu horizontaly?

Posted on 2009-12-20 19:06:26
copied

Hello your code has been stolen and is being sold here: http://codecanyon.net/item/manage-categories-of-hierarchic-content/

Posted on 2010-04-16 09:51:52
copied

sorry link is: http://codecanyon.net/item/manage-categories-of-hierarchic-content/77295

Posted on 2010-04-16 09:52:42
Koray

How can i integrate this menu ?

http://www.smartmenus.org/samples6/style/simple-style-1/

i integrate with this;

if ($has_childs === false)
{
//don't print '<ul>' multiple times
$has_childs = true;
echo '<ul id="Menu1" class="MM">';
}


But every <ul> echo <ul id="Menu1" class="MM">

how can i do main <ul> like <ul id="Menu1" class="MM"> and submenu <ul> no css, thank your answers..

Posted on 2010-03-28 01:48:58
Vassilis

Hello, nice script. I have already used this script without problems in the past.

Now I'm using the script in a CMS as a menu and I have alternative cookies there. The problem is that every time I click to the menu all the categories that are already expanded, collapse immediately. I tried to change a lot of things to the script but with no luck.

Can you please help me? Thanks a lot.

Posted on 2009-11-18 14:38:14
cobb

mate!

I've been looking for something like this for ages.

You've made it so simple to understand. Thanks

Posted on 2010-05-12 21:02:25
matt

how dow you remove link from top categories or on all <ul> and link only all <li> like how you did in this example?

Thanks

Posted on 2010-07-06 17:01:15
Ivan

Thanks, very nice
How can I modify this to get a "select" list with indented "option"s?

Posted on 2010-08-22 16:03:45
Nike

hi, Nice worked. how to worked with <select> and <option> ?
thanks

Posted on 2010-12-26 22:45:36
Ossama Khayat

Thanks for the code. I customized it a bit to generate an array and return it so I can use it in a dropdown list.
Here it is:
protected static function recurse($parent, $list, $level = 0)
{
$output = array();
$has_children = false;
foreach($list as $key => $value)
{
if ($value['parent'] == $parent)
{
if ($has_children === false)
{
$has_children = true;
$level++;
}
$output[$key] = str_repeat('&larr; ', $level-1).$value['name'];
$output = $output+self::recurse($key, $list, $level);
}
}
return $output;
}

Posted on 2011-12-04 09:45:44
nik

You are a life saver!
One Question: is this being done with the adjacency model ?

Posted on 2012-05-02 07:26:26
nik

Problem with IE8 . all the categories are expanded

Posted on 2012-05-11 10:49:36
nik

Disregard my previous comment. THere is no problem wit IE8.

Posted on 2012-05-13 20:09:00
sagar

How can i force this code to expand maximum 4 level depth. not more than that......?

Posted on 2012-09-05 09:52:26
Donna Cerca Uomo Milano

@sagar : you can validate with php when adding a new category to the database: check to see if the tree has more than 4 subcategories ... error or die() :-)

Posted on 2013-07-17 15:38:03

Make yourself heard

Categories

Subscribe

All Posts

All Comments

© Copyright CodeAssembly

All code is licensed under LGPL, unless otherwise noted

littlebubu