Saturday, November 14, 2009

How to Create a Category Tree with CakePHP 'Tree' behavior

Okay! Let's try to create a category tree using CakePHP (This is something like parent category -> child category type records).

SQL:

CREATE TABLE categories (
id INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT,
parent_id INTEGER(10) DEFAULT NULL,
lft INTEGER(10) DEFAULT NULL,
rght INTEGER(10) DEFAULT NULL,
name VARCHAR(255) DEFAULT '',
PRIMARY KEY  (id)
);

Now insert some record:
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(1, 'Tutorials', NULL, 1, 8);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(2, 'PHP', 1, 2, 5);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(3, 'MySQL', 1, 6, 7);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(4, 'CakePHP', 2, 3, 4);

Now I'll create a model for this category.
1. Create a new file.
2. Copy-paste the following code:

<?php
   class Category extends AppModel {  
var $name = 'Category';
   var $actsAs = array('Tree');
}
?>

3. Save the files as app/models/category.php.

Note:
The variable $actsAs tells Cake to attach 'Tree' behavior to this model, i.e.,  Cake will generate a Tree data structure for category model. I think it is also a good time to introduce you with another fascinating feature of CakePHP - 'Behaviors'. CakePHP has built-in behaviors, like - behaviors for tree structures, translated content, access control list interaction etc., which you can attach with any model. As you might know - 'add', 'edit', 'delete' options for these type of data structures need special care. Cake takes care of it once you have specified the applicable 'behavior' in the model. Behaviors are attached with models using $actsAs variable. In this case, I have specified $actsAs = array('Tree'). This will enforce 'Tree' behavior on Category model. Simple.
To learn more about 'Behaviors', please refer to CakePHP online book.

Now I'll create CategoriesController
Step:
1. Create a new file.
2. Copy-paste the following code.

<?php
class CategoriesController extends AppController {
            var $name = 'Categories';  
             
function index() {
                  $categories = $this->Category->generatetreelist(null, null, null, '&nbsp;&nbsp;&nbsp;');
                  $this->set(compact('categories'));    
                  }
 }
?>
3. Save that file as categories_controller.php under 'app/controllers' folder.

Note: generatetreelist() method generates a tree-type views for our Categories. There are lots of options you can use with this method. For a complete guidelines on options for this method, please refer to CakePHP book.
compact(); function is used to pass variables to your views in CakePHP. Compact() method detects the variables having the same name (in this case 'categories') in your Controller and splits them as an array() of $key => value pairs. Now $this->set() is used to set those values for using them in your view file.

Now I'll create a view for our index() function.
file: '/app/views/categories/index.php'

<?php
echo $html->link("Add Category",array('action'=>'add'));
echo "<ul>";
  foreach($categories as $key=>$value){
  echo "<li>$value</li>";
  }
  echo "</ul>";
?>


Now point your browser to:
http://caketest.local/categories

And you should see following structure:

> Tutorials    
   > PHP    
> CakePHP    
> MySQL

To Add a new category to the list:
1. Open categories_controller.php (found under '/app/controllers')
2. Copy-paste the following function:


function add() {


if (!empty($this -> data) ) {
$this->Category->save($this -> data);
$this->Session->setFlash('A new category has been added');
$this->redirect(array('action' => 'index'));
} else {
$parents[0] = "[Top]";
$categories = $this->Category->generatetreelist(null,null,null," - ");
if($categories) {
foreach ($categories as $key=>$value)
$parents[$key] = $value;
}
$this->set(compact('parents'));
}


}

3. Save this file.

Now we need to create a view file for this add() method (to display the add category form).

1. Create a new file.
2. Copy-paste the following code:


<h1>Add a new category</h1>
<?php
echo $form->create('Category');
echo $form->input('parent_id',array('label'=>'Parent'));
echo $form->input('name',array('label'=>'Name'));
echo $form->end('Add');
?>

3. Save the file as '/app/views/categories/add.ctp'

Now point your browser to this location:
http://caketest.local/categories/add

You should be able to add a new category.

EDIT Category
To edit category, I'll specify a controller action. To do so:
1. Open categories_controller.php (found under '/app/controllers').
2. Copy-paste the following function:


function edit($id=null) {
if (!empty($this->data)) {
if($this->Category->save($this->data)==false)
$this->Session->setFlash('Error saving Node.');
$this->redirect(array('action'=>'index'));
} else {
if($id==null) die("No ID received");
$this->data = $this->Category->read(null, $id);
$parents[0] = "[ Top ]";
$categories = $this->Category->generatetreelist(null,null,null," - ");
if($categories) 
foreach ($categories as $key=>$value)
$parents[$key] = $value;
$this->set(compact('parents'));
}
}


3. Save that function.

Now, I'll write the view file (the form to edit a category). To do so:
1. Create a new file.
2. Copy paste the following code:


<?php
echo $html->link('Back',array('action'=>'index'));
echo $form->create('Category');
  echo $form->hidden('id');
  echo $form->input('name');
  echo $form->input('parent_id', array('selected'=>$this->data['Category']['parent_id']));
  echo $form->end('Update');
?>

3. Now save the file as '/app/views/categories/edit.ctp'.

Hold on!
There is one more thing we should do. We need to show the link to edit record. To do show:
1. Open '/app/views/categories/index.ctp' file.
2. Replace the existing code with this one:

<?php
echo $html->link("Add Category",array('action'=>'add'));
echo "<ul>";
  foreach($categories as $key=>$value){
$edit = $html->link("Edit", array('action'=>'edit', $key));
 echo "<li>$value &nbsp;[$edit]</li>";
  }
  echo "</ul>";
?>

3. Now save the file.

Now point your browser to:
http://caketest.local/categories/
You should be able to see the 'Edit' option against each category name.

Delete Category
CakePHP Format:
removeFromTree($id=null, $delete=false)

Using this method will either delete [to delete, set ($delete=true)] or move a node but retain its sub-tree, which will be re-parented one level higher.

Steps:
1. Open '/app/controllers/categories_controller.php'
2. Copy paste the following code:









function delete($id=null) {
  if($id==null)
  die("No ID received");
  $this->Category->id=$id;
  if($this->Category->removeFromTree($id,true)==false)
     $this->Session->setFlash('The Category could not be deleted.');
   $this->Session->setFlash('Category has been deleted.');
   $this->redirect(array('action'=>'index'));
}

3. Now save that file.

Next, I'll display the option to 'delete' a category.
Step:
1. Open '/app/views/categories/index.ctp
2. Replace the existing code with this one:

<?php
echo $html->link("Add Category",array('action'=>'add'));
echo "<ul>";
  foreach($categories as $key=>$value){
$edit = $html->link("Edit", array('action'=>'edit', $key));
  $delete = $html->link("Delete", array('action'=>'delete', $key));
  echo "<li>$value &nbsp;[$edit]&nbsp;[$delete]</li>";
  }
  echo "</ul>";
?>
3. Save the file.

Done
Point your browser to:
http://caketest.local/categories
Here is a screenshot of what you should see:


[ACKNOWLEDGEMENT]
The code above is mostly based on
Bram Borggreve's beautiful website - Tree Behavior
I express my sincere gratitude to Bram for his wonderful contribution.
Further, to learn more about 'Tree' behavior, please visit: CakePHP Book.





Here is the COMPLETE script files:

1. categories_controller.php ('to be saved under '/app/controllers')


<?php
  class CategoriesController extends AppController {
 var $name = 'Categories';
 function index() {
  $categories = $this->Category->generatetreelist(null, null, null, '&nbsp;&nbsp;&nbsp;');
  // debug ($this->data); die; 
  $this->set(compact('categories')); 
  
  }
  
  function add() {
  
  if (!empty($this -> data) ) {
  $this->Category->save($this -> data);
  $this->Session->setFlash('A new category has been added');
  $this->redirect(array('action' => 'index'));
  } else {
  $parents[0] = "[ Top ]";
  $categories = $this->Category->generatetreelist(null,null,null," - ");
  if($categories) {
  foreach ($categories as $key=>$value)
  $parents[$key] = $value;
  }
  $this->set(compact('parents'));
  }
  
  }

  function edit($id=null) {
  if (!empty($this->data)) {
  if($this->Category->save($this->data)==false)
  $this->Session->setFlash('Error saving Category.');
  $this->redirect(array('action'=>'index'));
  } else {
  if($id==null) die("No ID received");
  $this->data = $this->Category->read(null, $id);
  $parents[0] = "[ Top ]";
  $categories = $this->Category->generatetreelist(null,null,null," - ");
  if($categories) 
  foreach ($categories as $key=>$value)
  $parents[$key] = $value;
  $this->set(compact('parents'));
  }
  }
 function delete($id=null) {
  if($id==null)
  die("No ID received");
  $this->Category->id=$id;
  if($this->Category->removeFromTree($id,true)==false)
  $this->Session->setFlash('The Category could not be deleted.');
  $this->Session->setFlash('Category has been deleted.');
  $this->redirect(array('action'=>'index'));
  }


}
  ?>

2. Category Model (file: '/app/models/category.php')

<?php
class Category extends AppModel { 
var $name = 'Category';
var $actsAs = array('Tree'); 

?> 

3. Views for Category Files
(a)File: '/app/views/categories/index.php'

<?php
echo $html->link("Add Category",array('action'=>'add'));
echo "<ul>";
  foreach($categories as $key=>$value){
$edit = $html->link("Edit", array('action'=>'edit', $key));
  $delete = $html->link("Delete", array('action'=>'delete', $key));
  echo "<li>$value &nbsp;[$edit]&nbsp;[$delete]</li>";
  }
  echo "</ul>";
?>



(b) File: '/app/views/categories/add.php'





<h1>Add a new category</h1>
<?php
echo $form->create('Category');
echo $form->input('parent_id',array('label'=>'Parent'));
echo $form->input('name',array('label'=>'Name'));
echo $form->end('Add');
?>


(c) File: 'app/views/categories/edit.php'

<h1>Add a new category</h1>
<?php
echo $form->create('Category');
echo $form->input('parent_id',array('label'=>'Parent'));
echo $form->input('name',array('label'=>'Name'));
echo $form->end('Add');
?>



Thanks for reading the entry.

16 comments:

Unknown said...

Hi Nilz,

I say you uses parts of my tutorial, thanks for the acknowledgement! :)

Regards, Bram

Siddhartha said...

Thanks for your visit.
Your tutorial was really great and original. My tutorial is mostly based on your work. I have merely changed few variable names. Thanks again for your good words.

Unknown said...

That helped ! Thank you for that great example ;)

Siddhartha said...

Thanks.

Unknown said...

Really Helpful stuff...
Thanks 4 brief and well organized Description.

Siddhartha said...

Thanks Vyas.

Muthu said...

Its really helpful, and learned a lot

Muthu said...

Really fine and I learned a lot.
Thanks

Siddhartha said...

Thanks...

Anonymous said...

I have read your blog and i got a very useful and knowledgeable information from your blog.its really a very nice article.You have done a great job .
Thank you so much

Cake PHP developmentfor sharing.

Siddhartha said...

Thanks @USEO!

Anonymous said...

Several years have passed, and this tutorial is still useful. Thanks !

Anonymous said...

Several years have passed, and this tutorial is still useful. Thanks !

Anonymous said...

Several years have passed, and this tutorial is still useful. Thanks !

Anonymous said...

Several years have passed, and this tutorial is still useful. Thanks !

PC_Link said...

Am very grateful for this post. as old has it is its still applicable and relevance.

is there a way to show the tree in the form of organizational structure. that is
A

B C D

E F G H I J K L M