Populating Select Boxes with Tree Paths
If you populate select boxes with a tree, and your tree has many nodes, which can be named the same, you are often left with an unusable select box. The following methods will help alleviate this problem by displaying the nodes in the select box as a path from the root of the tree.
To begin, your model must behave like a tree, so at the very least your model should look like:
Next we will add two functions that will do the work of finding each tree node's path.
Then in our controller we can do something like the following to get a list of tree paths in the same format as find('list').
And simply in our show_select.ctp view we write:
And we should get a select box filled with our category names as paths. So, for instance, we could get 'Electronics/Televisions/LCD' and 'Electronics/Monitors/LCD', whereas before we would get multiple LCD options.
Model Class:
Download code
<?php
class Category extends AppModel {
var $name = 'Category';
var $actsAs = array('Tree');
?>
Next we will add two functions that will do the work of finding each tree node's path.
Model Class:
Download code
<?php
class Category extends AppModel {
var $name = 'Category';
var $actsAs = array('Tree');
function setTreePath(&$data, $path='tree_path', $label='name') {
if (!is_array($data) || !in_array('Tree', $this->actsAs)) {
return $data;
}
if (is_array($data) && is_int(array_shift(array_keys($data)))) {
foreach ($data as $i=>$item) {
$this->_setTreePath($data[$i], $path, $label);
}
} else {
$this->_setTreePath($data, $path, $label);
}
}
function _setTreePath(&$data, $pathField, $label) {
$cats = $this->getpath($data[$this->name][$this->primaryKey]);
$path = array();
foreach ($cats as $cat) {
array_push($path, $cat[$this->name][$label]);
}
$data[$this->name][$pathField] = implode('/', $path);
}
}
?>
Then in our controller we can do something like the following to get a list of tree paths in the same format as find('list').
Controller Class:
Download code
<?php
function showSelect() {
$allCategories = $this->Category->find('all', array('fields'=>'id', 'name'));
$this->Category->setTreePath($allCategories);
$categories = array();
foreach ($allCategories as $cat) {
$categories[$cat['Category']['id']] = $cat['Category']['tree_path'];
}
$this->set(compact('categories'));
}
?>
And simply in our show_select.ctp view we write:
View Template:
Download code
<?php
echo $form->create('Category', array('action'=>'/not/an/action'));
echo $form->input('Category.Category');
echo $form->end('Submit');
?>
And we should get a select box filled with our category names as paths. So, for instance, we could get 'Electronics/Televisions/LCD' and 'Electronics/Monitors/LCD', whereas before we would get multiple LCD options.
Comments
Comment
1 Order field
By the by, instead of doing a find('all') and passing the result to a setTreePath, why not do a find('treePath') or similar?
Thanks for sharing
Comment
2 thanks
Hmm interesting. So to do that all I would do is override find() in the model, and if type=='treePath' then do my logic, else parent::find()? But, if it ain't broke, don't fix it.
Comment
3 Suggested Modifications
Model Class:
<?php
class Category extends AppModel {
// model stuffs
function find($type, $options = array()) {
if (in_array($type, $this->customFinds)) {
return $this->{'_find' . ucfirst($type)}($options);
} else {
return parent::find($type, $options);
}
}
function _findTreepath($options) {
// set some default options and get some from the parameters if set
$pathField = 'tree_path';
$labelField = 'name';
if (isset($options['pathField'])) {
$pathField = $options['pathField'];
unset($options['pathField']);
}
if (isset($options['labelField'])) {
$labelField = $options['labelField'];
unset($options['labelField']);
}
// find the specified rows and return something like find('list') does
$results = $this->find('all', $options);
$return = array();
foreach ($results as $i=>$result) {
$this->_setTreePath($result, $pathField, $labelField);
$return[$result[$this->name][$this->primaryKey]] = $result[$this->name][$pathField];
}
return $return;
}
function _setTreePath(&$data, $pathField, $label) {
$cats = $this->getpath($data[$this->name][$this->primaryKey]);
$path = array();
foreach ($cats as $cat) {
array_push($path, $cat[$this->name][$label]);
}
$data[$this->name][$pathField] = implode('/', $path);
}
}
?>
And then we can use it like the following to get an array of category ids and slugs.
Controller Class:
<?php$categories = $this->Category->find('treepath', array('pathField'=>'tree_path', 'labelField'=>'slug');
?>
Comment
4 cant edit
var $customFinds = array('treepath');