Yii 1.1: Creating a database-driven hierarchical Structure combined with CMenu and superfish

25 followers

Note: Please also take a look at the newer EMenu extension. This works even better that the deprecated CDropDownMenu!

In this tutorial we will create a hierarchical Structure using the traditional adjacency list model. Yii's ActiveRecord paradigm makes it very easy to implement this structure via a join on itself. After this, we will use the new CMenu from yii 1.1 and implement it in conjunction with superfish, a jQuery plugin for creating menus.

The CDropDownMenu extension will help us to accomplish this task.

I will also add a tutorial using a Nested Set (using the wonderful nested set extension) soon.

At first, we create our SQL-Table containing our hierarchical data like this:

CREATE TABLE `Hierarchy` (
  `id` int(11) NOT NULL auto_increment,
  `sort` int(11) NOT NULL,
  `parent` int(11) default NULL,
  `title` varchar(255) default NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB

You may add additional Columns if you wish, and you can rename the table however you like. The 'parent' field contains the id of the direct parent, while 'sort' defines, where our row gets displayed in the menu.

After that, we generate the model and a C-R-U-D interface using Gii.

Once the model is ready, we should add two relation rules to it:

public function relations()
{
   return array(
      'getparent' => array(self::BELONGS_TO, 'Hierarchy', 'parent'),
      'childs' => array(self::HAS_MANY, 'Hierarchy', 'parent', 'order' => 'sort ASC'),
   );
}

This reads as: the 'parent' of a row belongs to the parent-column of the same table, while we can gather the childs of a row by a relation of ourself with the HAS_MANY relation. We always want our childs get ordered by the column 'sort's. You can add additional relations if you want.

To test our newly created model, we need to insert some random test data:

insert into Hierarchy (id, sort, parent, title) values(1, 0, 0, 'root');
insert into Hierarchy (id, sort, parent, title) values(2, 0, 1, 'First Entry');
insert into Hierarchy (id, sort, parent, title) values(3, 0, 1, 'Second Entry withoud Childs');
insert into Hierarchy (id, sort, parent, title) values(4, 0, 1, 'Third Entry');
insert into Hierarchy (id, sort, parent, title) values(5, 0, 3, 'Child of the third Entry');
insert into Hierarchy (id, sort, parent, title) values(6, 0, 5, 'Child of the Child of the third Entry');
insert into Hierarchy (id, sort, parent, title) values(7, 0, 1, 'Child of the first Entry');

Now, in our yii console or in the Application we can use

$model = Hierarchy::model()->findByPk(7);
$parent = $model->getparent;
echo $parent->title;
// returns 'First Entry'

to get the parent element, or

$model = Hierarchy::model()->findByPk(1);
print_r($model->childs);

to print out all existing models in our Database. As you may suggest, this output is not very nice. Since we want to use the CHtml module of yii 1.1, we need to format our Output in an array like this:

public function getListed() {
    $subitems = array();
    if($this->childs) foreach($this->childs as $child) {
        $subitems[] = $child->getListed();
    }
    $returnarray = array('label' => $this->title, 'url' => array('Hierarchy/view', 'id' => $this->id));
    if($subitems != array()) 
        $returnarray = array_merge($returnarray, array('items' => $subitems));
    return $returnarray;
}

We place this function in models/Hierarchy.php

This is a recursive function (note how the function calls itself) that gathers all subchilds of an element that are available in the Database. Of course, when we run this function on our root node, we get all non-orphan childs.

After that, we can use the CDropDownMenu-Widget to generate our Menu:

$model = Hierarchy::model()->findByPk(1);
$items[] = $model->getListed(); // note that the [] is important, otherwise CMenu will crash.
 
$this->widget('application.extensions.CDropDownMenu',array(
      'items'=>$items,
));

to render the menu with the content of our database.

Attention: please be sure to not use this Widget inside the div id="mainmenu" of standard-generated webapps because there seems to be some css inconsistency between yii's default css and superfish's default css. Maybe this will be fixed sometime.

Note the bunch of effects and options (like drop-shadow) you can configure with superfish in extensions/vendors/CDropDownMenu.js

After this, we want our Users to be able to easily move Menu entries around. To achieve this, we will use a Drop-Down List, in which we can choose the parent of our selected element. We write this code-snippet to views/Hierarchy/_form.php:

$data = Hierarchy::model()->findAll('parent=:parent', array('parent' => '0'));
 
foreach($data as $child) {
    $subchilds = $child->childs;
    foreach($subchilds as $subchild) {
        $subchild->title = $subchild->getparent->title . "|" . $subchild->title;
        $data = array_merge($data, $child->childs);
    }
}
 
$rootobj = new Hierarchy;
$rootobj->id = 0;
$rootobj->title = "root level";
$root = array($rootobj);
$data = array_merge($root, $data);
 
if(isset($model->id) && $model->id == 1) {
    echo "This is the root node and can't be moved.";
    $model->parent = 0; 
}
else {
    if(isset($_GET['hierarchyParent']))
        echo CHtml::DropDownList('Hierarchy[parent]', $_GET['HierarchyParent'], CHtml::listData($data, 'id', 'title'));
    else if($update)
        echo CHtml::DropDownList('Hierarchy[parent]', $model->parent, CHtml::listData($data, 'id', 'title'));
    else
        echo CHtml::DropDownList('Hierarchy[parent]', 1, CHtml::listData($data, 'id', 'title'));
}

Note the lines

if(isset($_GET['hierarchyParent']))
    echo CHtml::DropDownList('Hierarchy[parent]', $_GET['HierarchyParent'], CHtml::listData($data, 'id', 'title'));

With this lines we will be able to create a "add entry to this element"-Button like this:

if(!Yii::app()->User->isguest)
    echo CHtml::link("Add a new element", array('Hierarchy/create', 'hierarchyParent' => $model->id));

Place this lines somewhere at views/Hierarchy/view.php.

I hope my small tutorial was helpful for you. There are some points that can be made even better, for example someone could change the admin CGridView to be collapsable, and the elements could be moved around by drag & drop. In the next version of this Tutorial i will use the nestedset extension to achieve the Hierarchy Structure. Thank you for reading & trying, and don't hesitate to ask me when you have Questions.

Total 9 comments

#16721 report it
km994 at 2014/03/22 03:00pm
Illegal string offset 'label'

Hi I am trying this code by passing hardcoded variable .However it gives the error as "Illegal string offset 'label' ".

$title = 'Main' ;
$subitems = array(); $subitems[] = 'one'; $subitems[] = 'two';

$returnarray = array('label' => $title, 'url' => array('Hierarchy/view'));
$returnarray = array_merge($returnarray, array('items' => $subitems));
$items[] = $returnarray;

    $this->widget('ext.CDropDownMenu.CDropDownMenu',array(
        'items'=>$items,));

Any suggestions regarding the error please ?

#7344 report it
WitekS at 2012/03/15 03:55pm
Undefined variable: update

gives me an error

Undefined variable: update
#7183 report it
persia at 2012/03/01 04:13pm
Tree View

Hi,

your example works perfect but is it possible to make it work with the CtreeView ?

Thanks

#3972 report it
abhimir at 2011/05/24 09:26am
@mdomba

It works now, thanks. Had to make a few other changes, but the code works perfectly now.

#3965 report it
Maurizio Domba Cerin at 2011/05/24 03:58am
@abhimir

Seems this is an error...

If you put CDropDownMenu in the protected/extensions folder, try with

$this->widget('application.extensions.CDropDownMenu',...
#3964 report it
abhimir at 2011/05/24 03:51am
@mdomba

I have installed the CDropDownMenu... and added it in the main config file as

'import'=>array(
        'application.models.*',
        'application.components.*',
                'application.modules.user.models.*',
                'application.modules.user.components.*',
                'application.extensions.*'
                    ),

Maybe I am not initialising it correctly?

#3962 report it
Maurizio Domba Cerin at 2011/05/24 03:09am
@abhimir

Have you installed the CDropDownMenu extension? you have the link to this extension at the begining of the article...

#3959 report it
abhimir at 2011/05/23 08:56pm
Error

I get this error, while running the code

Alias "zii.widgets.CDropDownMenu" is invalid. Make sure it points to an existing PHP file.

Any ideas why?

#298 report it
Hoang Giang at 2010/07/15 11:27am
Display all menu items

Using loop to display all items:

$Hierarchy=Hierarchy::model()->findAll(array('condition'=>'parent = 0'));
        foreach ($Hierarchy as $Hierarchy){
            $models = Cat::model()->findBypk($Hierarchy->id);
            $items[] = $models->getListed();        
        }       
        if($items[0]['items'] != "0"){
            $items = array_merge(array('items'=>array('label'=>'Trang chủ', 'url'=>array('/site/index'))),
                                $items,array(array('label'=>'Liên hệ', 'url'=>array('/site/contact'))));    
 
        $this->widget('zii.widgets.CMenu',array(        
            'items'=>$items,    
        ));

Leave a comment

Please to leave your comment.

Write new article
  • Written by: thyseus
  • Category: Tutorials
  • Yii Version: 1.1
  • Votes: +15
  • Viewed: 41,470 times
  • Created on: Jan 16, 2010
  • Last updated: Oct 16, 2011
  • Tags: menu