Tree Helper 1

By Andy Dawson (AD7six)
A helper to generate nested ULs OLs, DIVs or whatever from tree data.

Works best with the TreeBehavior, but not a requirement.

Example usage 1:

Download code <?php
//controller code:
$stuff $this->MyTreeBehaviorModel->find('all'
     array(
'fields' => array('whatever''lft''rght'), 'order' => 'lft ASC'));
$this->set('stuff'$stuff);
//view code
echo $tree->generate($stuff);

Example usage 2:

Download code <?php
//controller code:
$stuff $this->NoTreeBehaviorModel->findAllThreaded();
$this->set('stuff'$stuff);
//view code
echo $tree->generate($stuff);

Example usage 3:

Download code <?php
//controller code:
$id 1;
$showMeChildren true;
$stuff $this->MyTreeBehaviorModel->children($id$showMeChildren);
$this->set('stuff'$stuff);
//view code
echo $tree->generate($stuff);

Example usage 4:

Download code <?php
//controller code:
$stuff $this->MainModel->MyTreeBehaviorModel->find('all'
     array(
'fields' => array('whatever''lft''rght'), 'order' => 'lft ASC'));
$this->set('stuff'$stuff);
//view code
echo $tree->generate($stuff, array('model' => 'MyTreeBehaviorModel'));

Example usage 5:

Download code <?php
//controller code:
$stuff $this->MyTreeBehaviorModel->find('all'
     array(
'fields' => array('whatever''lft''rght'), 'order' => 'lft ASC'));
$this->set('stuff'$stuff);
//view code
echo $tree->generate($stuff, array('type' => 'ol')); // generate an ol not a ul

Example usage 6:

Download code <?php
//controller code:
$stuff $this->MyTreeBehaviorModel->find('all'
     array(
'fields' => array('whatever''lft''rght'), 'order' => 'lft ASC'));
//view code
// pass the data for each node to /views/elements/thisone.ctp and put the results in the rendered UL
echo $tree->generate($stuff, array('element' => 'thisone'));
The contents of "thisone.ctp" can be as simple or as complex as you like. e.g.:
Download code
<?php
extract 
($data);
echo 
$html->link($myModel['name'], array('action' => 'view'$myModel['id']));
or *new* as of revision 84 you can also do the following:
Download code
<?php
extract 
($data);
if (
some logic) {
     
$tree->addItemAttribute('class''highlight'); // highlight this li
} else {
     
$tree->addTypeAttribute('style''display''none'); // hide this ul completely
}
echo 
$html->link($myModel['name'], array('action' => 'view'$myModel['id']));

Helper Class:

Download code <?php 
/* SVN FILE: $Id: tree.php 205 2008-08-13 14:13:32Z ad7six $ */
/**
 * Tree Helper.
 *
 * Used the generate nested representations of hierarchial data
 *
 * PHP versions 4 and 5
 *
 * Copyright (c) 2008, Andy Dawson
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @copyright            Copyright (c) 2008, Andy Dawson
 * @link                 www.ad7six.com
 * @package              cake-base
 * @subpackage           cake-base.app.views.helpers
 * @since                v 1.0
 * @version              $Revision: 205 $
 * @modifiedBy           $LastChangedBy: ad7six $
 * @lastModified         $Date: 2008-08-13 16:13:32 +0200 (Wed, 13 Aug 2008) $
 * @license              http://www.opensource.org/licenses/mit-license.php The MIT License
 */
/**
 * Tree helper
 *
 * Helper to generate tree representations of MPTT or recursively nested data
 */
class TreeHelper extends AppHelper {
/**
 * name property
 *
 * @var string 'Tree'
 * @access public
 */
    
var $name 'Tree';
/**
 * settings property
 *
 * @var array
 * @access private
 */
    
var $__settings = array();
/**
 * typeAttributes property
 *
 * @var array
 * @access private
 */
    
var $__typeAttributes = array();
/**
 * typeAttributesNext property
 *
 * @var array
 * @access private
 */
    
var $__typeAttributesNext = array();
/**
 * itemAttributes property
 *
 * @var array
 * @access private
 */
    
var $__itemAttributes = array();
/**
 * helpers variable
 *
 * @var array
 * @access public
 */
    
var $helpers = array ('Html');
/**
 * Tree generation method.
 *
 * Accepts the results of
 *     find('all', array('fields' => array('lft', 'rght', 'whatever'), 'order' => 'lft ASC'));
 *     children(); // if you have the tree behavior of course!
 * or     findAllThreaded(); and generates a tree structure of the data.
 *
 * Settings (2nd parameter):
 *    'model' => name of the model (key) to look for in the data array. defaults to the first model for the current
 * controller. If set to false 2d arrays will be allowed/expected.
 *    'alias' => the array key to output for a simple ul (not used if element or callback is specified)
 *    'type' => type of output defaults to ul
 *    'itemType => type of item output default to li
 *    'id' => id for top level 'type'
 *    'class' => class for top level 'type'
 *    'element' => path to an element to render to get node contents.
 *    'callback' => callback to use to get node contents. e.g. array(&$anObject, 'methodName') or 'floatingMethod'
 *    'autoPath' =>  array($left, $right [$classToAdd = 'active']) if set any item in the path will have the class $classToAdd added. MPTT only.
 *    'left' => name of the 'lft' field if not lft. only applies to MPTT data
 *    'right' => name of the 'rght' field if not lft. only applies to MPTT data
 *    'depth' => used internally when running recursively, can be used to override the depth in either mode.
 *    'firstChild' => used internally when running recursively.
 *    'splitDepth' => if multiple "parallel" types are required, instead of one big type, nominate the depth to do so here
 *        example: useful if you have 30 items to display, and you'd prefer they appeared in the source as 3 lists of 10 to be able to
 *        style/float them.
 *    'splitCount' => the number of "parallel" types. defaults to 3
 *
 * @param array $data data to loop on
 * @param array $settings
 * @return string html representation of the passed data
 * @access public
 */
    
function generate ($data$settings = array ()) {
        
$this->__settings array_merge(array(
                
'model' => null,
                
'alias' => 'name',
                
'type' => 'ul',
                
'itemType' => 'li',
                
'id' => false,
                
'class' => false,
                
'element' => false,
                
'callback' => false,
                
'autoPath' => false,
                
'left' => 'lft',
                
'right' => 'rght',
                
'depth' => 0,
                
'firstChild' => true,
                
'indent' => null,
                
'splitDepth' => false,
                
'splitCount' => 3,
            ), (array)
$settings);
        if (
$this->__settings['autoPath'] && !isset($this->__settings['autoPath'][2])) {
            
$this->__settings['autoPath'][2] = 'active';
        }
        
extract($this->__settings);
        if (
$indent === null && Configure::read()) {
            
$indent true;
        }
        
$view =& ClassRegistry:: getObject('view');
        if (
$model === null) {
            
$model Inflector::classify($view->params['models'][0]);
        }
        if (!
$model) {
            
$model '_NULL_';
        }
        
$stack = array();
        if (
$depth == 0) {
            if (
$class) {
                
$this->addTypeAttribute('class'$classnull'previous');
            }
            if (
$id) {
                
$this->addTypeAttribute('id'$idnull'previous');
            }
        }
        
$return '';
        if (
$indent) {
            
$return "\r\n";
        }
        
$__addType true;
        foreach (
$data as $i => $result) {
            
/* Allow 2d data arrays */
            
if ($model == '_NULL_') {
                
$_result $result;
                
$result[$model] = $_result;
            }
            
/* BulletProof */
            
if (!isset($result[$model][$left]) && !isset($result['children'])) {
                
$result['children'] = array();
            }
            
/* Close open items as appropriate */
            
while ($stack && ($stack[count($stack)-1] < $result[$model][$right])) {
                
array_pop($stack);
                if (
$indent) {
                    
$whiteSpace str_repeat("\t",count($stack));
                    
$return .= "\r\n" $whiteSpace "\t";
                }
                if (
$type) {
                    
$return .= '</' $type '>';
                }
                if (
$itemType) {
                    
$return .= '</' $itemType '>';
                }
            }
            
/* Some useful vars */
            
$hasChildren $firstChild $lastChild $hasVisibleChildren false;
            
$numberOfDirectChildren $numberOfTotalChildren null;
            if (isset(
$result['children'])) {
                if (
$result['children']) {
                    
$hasChildren $hasVisibleChildren true;
                    
$numberOfDirectChildren count($result['children']);
                }
                
$prevRow prev($data);
                if (!
$prevRow) {
                    
$firstChild true;
                }
                
next($data);
                
$nextRow next($data);
                if (!
$nextRow) {
                    
$lastChild true;
                }
                
prev($data);
            } elseif (isset(
$result[$model][$left])) {
                if (
$result[$model][$left] != ($result[$model][$right] - 1)) {
                    
$hasChildren true;
                    
$numberOfTotalChildren = ($result[$model][$right] - $result[$model][$left] - 1) / 2;
                    if (isset(
$data[$i 1]) && $data[$i 1][$model][$right] < $result[$model][$right]) {
                        
$hasVisibleChildren true;
                    }
                }
                if (!isset(
$data[$i 1]) || ($data[$i 1][$model][$left] == ($result[$model][$left] - 1))) {
                    
$firstChild true;
                }
                if (!isset(
$data[$i 1]) || ($stack && $stack[count($stack) - 1] == ($result[$model][$right] + 1))) {
                    
$lastChild true;
                }
            }
            
$elementData = array(
                
'data' => $result,
                
'depth' => $depth?$depth:count($stack),
                
'hasChildren' => $hasChildren,
                
'numberOfDirectChildren' => $numberOfDirectChildren,
                
'numberOfTotalChildren' => $numberOfTotalChildren,
                
'firstChild' => $firstChild,
                
'lastChild' => $lastChild,
                
'hasVisibleChildren' => $hasVisibleChildren
            
);
            
$this->__settings array_merge($this->__settings$elementData);
            
/* Main Content */
            
if ($element) {
                
$content $view->element($element,$elementData);
            } elseif (
$callback) {
                list(
$content) = array_map($callback, array($elementData));
            } else {
                
$content $result[$model][$alias];
            }
            if (!
$content) {
                continue;
            }
            
$whiteSpace str_repeat("\t"$depth);
            if (
$indent && strpos($content"\r\n"1)) {
                
$content str_replace("\r\n""\n" $whiteSpace "\t"$content);
            }
            
/* Prefix */
            
if ($__addType) {
                if (
$indent) {
                    
$return .= "\r\n" $whiteSpace;
                }
                if (
$type) {
                    
$typeAttributes $this->__attributes($type, array('data' => $elementData));
                    
$return .= '<' $type .  $typeAttributes '>';
                }
            }
            if (
$indent) {
                
$return .= "\r\n" $whiteSpace "\t";
            }
            if (
$itemType) {
                
$itemAttributes $this->__attributes($itemType$elementData);
                
$return .= '<' $itemType $itemAttributes '>';
            }
            
$return .= $content;
            
/* Suffix */
            
$__addType false;
            if (
$hasVisibleChildren) {
                if (
$numberOfDirectChildren) {
                    
$settings['depth'] = $depth 1;
                    
$return .= $this->__suffix();
                    
$return .= $this->generate($result['children'], $settings);
                    if (
$itemType) {
                        
$return .= '</' $itemType '>';
                    }
                } elseif (
$numberOfTotalChildren) {
                    
$__addType true;
                    
$stack[] = $result[$model][$right];
                }
            } else {
                if (
$itemType) {
                    
$return .= '</' $itemType '>';
                }
                
$return .= $this->__suffix();
            }
        }
        
/* Cleanup */
        
while ($stack) {
            
array_pop($stack);
            if (
$indent) {
                
$whiteSpace str_repeat("\t",count($stack));
                
$return .= "\r\n" $whiteSpace "\t";
            }
            if (
$type) {
                
$return .= '</' $type '>';
            }
            if (
$itemType) {
                
$return .= '</' $itemType '>';
            }
        }
        if (
$indent) {
            
$return .= "\r\n";
        }
        if (
$type) {
            
$return .= '</' $type '>';
            if (
$indent) {
                
$return .= "\r\n";
            }
        }
        return 
$return;
    }
/**
 * addItemAttribute function
 *
 * Called to modify the attributes of the next <item> to be processed
 * Note that the content of a 'node' is processed before generating its wrapping <item> tag
 *
 * @param string $id
 * @param string $key
 * @param mixed $value
 * @access public
 * @return void
 */
    
function addItemAttribute($id ''$key ''$value null) {
        if (!
is_null($value)) {
            
$this->__itemAttributes[$id][$key] = $value;
        } elseif (!(isset(
$this->__itemAttributes[$id]) && in_array($key$this->__itemAttributes[$id]))) {
            
$this->__itemAttributes[$id][] = $key;
        }
    }
/**
 * addTypeAttribute function
 *
 * Called to modify the attributes of the next <type> to be processed
 * Note that the content of a 'node' is processed before generating its wrapping <type> tag (if appropriate)
 * An 'interesting' case is that of a first child with children. To generate the output
 * <ul> (1)
 *      <li>XYZ (3)
 *              <ul> (2)
 *                      <li>ABC...
 *                      ...
 *              </ul>
 *              ...
 * The processing order is indicated by the numbers in brackets.
 * attributes are allways applied to the next type (2) to be generated
 * to set properties of the holding type - pass 'previous' for the 4th param
 * i.e.
 * // Hide children (2)
 * $tree->addTypeAttribute('style', 'display', 'hidden');
 * // give top level type (1) a class
 * $tree->addTypeAttribute('class', 'hasHiddenGrandChildren', null, 'previous');
 *
 * @param string $id
 * @param string $key
 * @param mixed $value
 * @access public
 * @return void
 */
    
function addTypeAttribute($id ''$key ''$value null$previousOrNext 'next') {
        
$var '__typeAttributes';
        
$firstChild = isset($this->__settings['firstChild'])?$this->__settings['firstChild']:true;
        if (
$previousOrNext == 'next' && $firstChild) {
            
$var '__typeAttributesNext';
        }
        if (!
is_null($value)) {
            
$this->{$var}[$id][$key] = $value;
        } elseif (!(isset(
$this->{$var}[$id]) && in_array($key$this->{$var}[$id]))) {
            
$this->{$var}[$id][] = $key;
        }
    }

/**
 * supressChildren method
 *
 * @return void
 * @access public
 */
    
function supressChildren() {
    }
/**
 * suffix method
 *
 * Used to close and reopen a ul/ol to allow easier listings
 *
 * @access private
 * @return void
 */
    
function __suffix() {
        static 
$__splitCount 0;
        static 
$__splitCounter 0;
        
extract($this->__settings);
        if (
$splitDepth) {
            if (
$depth == $splitDepth -1) {
                
$total $numberOfDirectChildren?$numberOfDirectChildren:$numberOfTotalChildren;
                if (
$total) {
                    
$__splitCounter 0;
                    
$__splitCount $total $splitCount;
                    
$rounded = (int)$__splitCount;
                    if (
$rounded $__splitCount) {
                        
$__splitCount $rounded 1;
                    }
                }
            }
            if (
$depth == $splitDepth) {
                
$__splitCounter++;
                if (
$type && ($__splitCounter $__splitCount) == 0) {
                    return 
'</' $type '><' $type '>';
                }
            }
        }
        return;
    }
/**
 * attributes function
 *
 * Logic to apply styles to tags.
 *
 * @param mixed $rType
 * @param array $elementData
 * @access private
 * @return void
 */
    
function __attributes($rType$elementData = array(), $clear true) {
        
extract($this->__settings);
        if (
$rType == $type) {
            
$attributes $this->__typeAttributes;
            if (
$clear) {
                
$this->__typeAttributes $this->__typeAttributesNext;
                
$this->__typeAttributesNext = array();
            }
        } else {
            
$attributes $this->__itemAttributes;
            
$this->__itemAttributes = array();
            if (
$clear) {
                
$this->__itemAttributes = array();
            }
        }
        if (
$autoPath && $depth) {
            if (
$this->__settings['data'][$model][$left] < $autoPath[0] && $this->__settings['data'][$model][$right] > $autoPath[1]) {
                
$attributes['class'][] = $autoPath[2];
            } elseif (isset(
$autoPath[3]) && $this->__settings['data'][$model][$left] == $autoPath[0]) {
                
$attributes['class'][] = $autoPath[3];
            }
        }
        if (
$attributes) {
            foreach (
$attributes as $type => $values) {
                foreach (
$values as $key => $val) {
                    if (
is_array($val)) {
                        
$attributes[$type][$key] = '';
                        foreach (
$val as $vKey => $v) {
                            
$attributes[$type][$key][$vKey] .= $vKey ':' $v;
                        }
                        
$attributes[$type][$key] = implode(';'$attributes[$type][$key]);
                    }
                    if (
is_string($key)) {
                        
$attributes[$type][$key] = $key ':' $val ';';
                    }
                }
                
$attributes[$type] = $type '="' implode(' '$attributes[$type]) . '"';
            }
            return 
' ' implode(' '$attributes);
        }
        return 
'';
    }
}
?>

Variables available in your element

If you choose to nominate an element to be used to render the contents of each node, the following variables are automatically available inside the element:
Download code
<?php
$data 
// the row of data passed to the helper
$depth // depth in the current tree 1 = first item
$hasChildren // whether the current row has children or not
$hasVisibleChildren // whether the current row has Visible children or not. Only relavent for MPTT tree data
$numberOfDirectChildren // only avaliable with recursive data
$numberOfTotalChildren // only available with MPTT tree data
$firstChild // whether the current row is the first of it's siblings or not
$lastChild // whether the current row is the last of it's siblings or not

 

Comments 609

CakePHP Team Comments Author Comments
 

Bug

1 Incorrect variable name line 91

Fantastic work ! By far the most reliable technique to display a mptt tree. A little correction though :

Line 91 :

Controller Class:

<?php 
$numberOfChildren 
$numberOfTotalChildren 0;
?>

Should be :

Controller Class:

<?php 
$numberOfDirectChildren 
$numberOfTotalChildren 0;
?>

Also, don't forget to pass the name of the model if you use this helper globally (like a navigation menu in the default layout). I failed to remember that and made my Apache crash !
Posted Mar 12, 2008 by Pierre Emmanuel Fringant
 

Comment

2 Corrected

Hi Pierre,

Thanks for pointing that out - now corrected in the article.
Posted Mar 12, 2008 by Andy Dawson
 

Question

3 Remove empty UL elements

This is a great helper. However, i was wondering if it is possible to remove the empty UL elements that are added after each node without a child.

eg)

View Template:

<ul id="nestList" >
    <li><a href="somelink">Windows</A>
<ul>
    <li><a href="someOtherLink">File System</A>
<ul>
</ul>
</li>
</ul>
</li>
</ul>

I'm very new to Cake and not a very experienced programmer, having looked at the helper code i cannot see how to remove the empty UL's. I ask as i have some collapsible menu javascript code which does not work properly with the empty UL elements after all childless nodes. Im currently passing in data using findAllThreaded(), Any help would be greatly appreciated.
Posted Apr 3, 2008 by Simon Wass
 

Question

4 urls

I have this working but i dont understand how give to nodes the url to go :?
Posted Apr 4, 2008 by minskog
 

Comment

5 re urls

I have this working but i dont understand how give to nodes the url to go :?
You need to create an element and place it in views/elements.
your tree generation in the view should look similar to this.

View Template:

echo $tree->generate($threadedlist, array('element'=>'elementName', 'model'=>'Category'));
Then in the element, you simply echo whatever you would like to appear in the node, the $tree-generate makes an array variable called '$data' available to the element which the node element it is about to print to the screen. Check the helper code above, there is also a lot of other useful information in the $data array which can be used to make your output conditional or to tailor it to your needs. So my element looks similar to this

View Template:


$return = '<A HREF="/categories/view_cat/'.$data['Category']['id'].'">';
$return .= $data['Category'][$lang.'_description']."</A>";

I think you can use the $html->link method in the element also but i chose this way. remember, the element is called for every node in the list so you only need to echo the url or whatever information for that single line in the list.

Hope this helps
Posted Apr 10, 2008 by Simon Wass
 

Question

6 first child and last child

Is there a way to know if current rendered element is first child or last child within it's depth? Currently it seems it's only possible to know if it's the very first and very last child of the tree?
Posted Apr 21, 2008 by Kim Biesbjerg
 

Comment

7 Found a solution

I added an extra check in each of the if-statements setting $firstChild and $lastChild to true.

I added this to add firstChild and lastChild classes to html elements for all depths/levels allowing for greater CSS styling flexibility.



if (!isset($data[$i - 1]) || ($data[$i - 1][$model][$left] == ($result[$model][$right] - 1)) || $data[$i - 1][$model][$left] + 1 == $result[$model][$left]) {
    $firstChild = true;
}
if ($stack && $stack[count($stack) - 1] == ($result[$model][$right] + 1) || !isset($data[$i + 1])) {
    $lastChild = true;
}

Posted Apr 30, 2008 by Kim Biesbjerg
 

Question

8 Possible Enhancement and Question

Awesome helper! Thanks for sharing it. Has made outputting tree data a whole lot easier.

An enhancement (that I don't have the skills to implement myself and I'm sure could be added easily by those with skills):

1) User selectable class name for each li ... maybe one of the vars in $data. User provides $liclass = 'Model.field' as a setting. Can be usefull for styling/css control purposes.

Also a question:

I might be reading the code wrong but I can't find out how to 'turn off' the output of the ul/ol and li's ie to control the output manually using the element setting. Any hints?
Posted May 12, 2008 by Scott
 

Bug

9 A small typo

Nice piece of code Andy.
Am I wrong or there is a typo (a $lft instead of $left) ?

if (!isset($data[$i - 1]) || ($data[$i - 1][$model][$left] == ($result[$model][$lft] - 1))) {
Posted May 14, 2008 by Luigi Gualtieri
 

Comment

10 Tree Helper updates v1 70

I've updated this helper mainly cleaning the code up but also putting in place the initial stub to be able to set class/styles/whatever to your ul/li elements.

Re Empty ULs
That shouldn't be a problem now

Re Urls
Use an element - it's the easiest solution if you are not just wanting an inanimate list of names. I wouldn't advocate hand writing your html links though ;)

Re First child and last child
The internal logic of the behavior now relies on the 'helpful vars' so these variables should always be accurate now (I await being proven wrong).

Re Possible enhancments
This still isn't possible but please take a look at the _attributes method. At the moment it is necessary to edit the helper to put your logic in there to set attribute values - your patches on how to structure the code so it's not necessary are welcome.

Re turning off uls/lis
Assuming the purpose of doing so is to be able to put the ul/li inside your element - you can't. if you tried to do so you'd end up with an invalid tree as soon as you rendered an element that had a child - your patches on how to prove me wrong are welcome.

Re typos
probably ;)
Posted May 22, 2008 by Andy Dawson
 

Comment

11 Updated. Rev 89

It's now possible to manipulate your ul/li classes/ids/attributes and also possible to use a callback (ala rss helper) to get your content
Posted Jun 16, 2008 by Andy Dawson
 

Bug

12 Trees that dont have name fields

Just a warning to everyone, if your table doesn't have a field `name` make sure that you modify your generate a little bit.

For me I had a title instead of name. When I used this:

<?php echo $tree->generate($pages); ?>

I get Notice (8): Undefined index: name [APP/views/helpers/tree.php, line 210]
But after you pass an alias in the options array you get lovely output. I am now using


<?php echo $tree->generate($pages, array('alias' => 'title')); ?>

Great helper, keep up the good work!

Sincerely,
~Andrew Allen
Posted Jul 9, 2008 by Andrew Allen
 

Comment

13 A little change

It's a great work! Thank you!

Your check of last/first child don't work. I don't know why :(
Here is some changes:


                /*$prevRow = prev($data);
                if (!$prevRow) {
                    $firstChild = true;
                }
                next($data);
                $nextRow = next($data);
                if (!$nextRow) {
                    $lastChild = true;
                }
                prev($data);*/
                if (!isset($data[$i - 1])) {
                    $firstChild = true;
                }
                if (!isset($data[$i + 1])) {
                    $lastChild = true;
                }
Posted Aug 3, 2008 by arbitr
 

Comment

14 Bug Example usage 4

Please note there is a bug in the example usage 4.
echo $tree->generate($stuff, array('Model' => 'MyTreeBehaviorModel')); Should be:
echo $tree->generate($stuff, array('model' => 'MyTreeBehaviorModel')); Notice the options-array-key (model) should lowercase! Toke me a while before noticing :). (In the future I should read the code (comments) before using a helper/component...)
Posted Aug 14, 2008 by Ruud
 

Bug

15 a possible bug when using in error templates

Hello, I have noticed a possible bug when trying to render an error message.

I have a layout, that includes $tree->generate() function. But when I try to use this layout for error messages I cannot access any helpers in my menu element (the one that is rendered for each menu item).

I guess it must be something with variable context because in the template called on error "$this" in the element refers to a "View" class and not to the Tree helper itself.

Let me know if you need more info on this.

regards
danielz
Posted Aug 31, 2008 by DanielZ
 

Comment

16 methods in element (example) have no effect

Hi,

I am using the helper and everything looks great. I use the tree-behavior to get childs and generate the tree. Until here everything works!

When I try to use the methods: addTypeAttribute and/or addItemAttribute in the element (exactly like example 6). Nothing happens...

It's not really clear why it won't work. Could use some help please, been pulling my last hairs for the last couple of hours.

I am using: 1.2.0.7692 RC3

Thanks in advanced!

Adriaan
Posted Nov 6, 2008 by adriaan
 

Question

17 Element requests action, view requests element (for each node)

For some reason this scenario throws problems when trying to access a member function of $tree

In the view

echo $this->element('user_menu');

In the element (user_menu)

echo $this->requestAction('users/user_menu', array('return'));

In the view (users/user_menu)

echo $tree->generate($data, array('model' => 'UserMenu', 'element' => 'user_menu_node'));

And, finally!, in the element (user_menu_node)

... some logic ...
$tree->addItemAttribute('class', $data['UserMenu']['class']);
... some more ...

This setup generates an error (undefined variable tree), have i inadvertently skipped a step?
Posted Nov 12, 2008 by Dan Walton
 

Comment

18 Making tree available to the node element

With reference to the above comment; For now, and I'm not certain this is the best way, i've modified the $elementData array to include a reference of $this, ala:

(tree.php line ~211)

$elementData = array(
    'tree' => $this,
    'data' => $result,
    'depth' => $depth?$depth:count($stack),
    ...
);
Posted Nov 12, 2008 by Dan Walton
 

Comment

19 methods in element (example) have no effect

same problems like adriaan here. any news?

-> problem solved, i've used this wrong code in my view:

App::import('Helper', 'Tree');
$tree = new TreeHelper();
Posted Feb 12, 2009 by Max Frisch
 

Comment

20 Generic Element View

If you have more Tree Helper in your system, you need generic element. This code works for me:

View Template:


// view myModel
echo $tree->generate($stuff, array('element' => 'actions_tree','model' => 'myModel'));

View Template:


// element actions_tree.ctp
$model = array_keys($data);
echo $html->link($data[$model[0]]['nome'], array('action' => 'view', $data[$model[0]]['id']));

Andy, thanks for your helper! :)
Posted Aug 12, 2009 by Bruno PorKaria
 

Bug

21 Using autoAuth without default model

At line 134

Controller Class:

<?php 
        
if ($model === null) {
            
$model Inflector::classify($view->params['models'][0]);
        }
?>

Should be

Controller Class:

<?php 
        
if ($model === null) {
            
$model Inflector::classify($view->params['models'][0]);
            
$this->__settings['model'] = $model;
        }
?>

Otherwise a bug occur at line 428 : Notice (8): Undefined index: [APP/views/helpers/tree.php, line 428]
Posted Sep 11, 2009 by Fred
 

Comment

22 __attributes function question

I was just browsing the code for this helper and noticed this

            $this->__itemAttributes = array();
            if ($clear) {
                $this->__itemAttributes = array();
            }

This seems to be pointless


            if ($clear) {
                $this->__itemAttributes = array();
            }

is rendered useless by the line just before it


            $this->__itemAttributes = array();

Also, what is the purpose of the $clear attribute? It is not listed in the function description, nor do any of the calls to it from the class use it.

Am I missing something?
Posted Sep 22, 2009 by Dan Bemowski
 

Comment

23 Use a database field value as an id or class attribute

I have made a slight modification to the code for this helper to allow you to use a database field value as the id or class attribute for an element.

To do this I created 2 new settings, elementIdField and elementClassField.

        $this->__settings = array_merge(array(
                'model' => null,
                                ...

                'elementIdField' => false,
                'elementClassField' => false,
            ), (array)$settings);

These can be set to a database field name that defines the id or class name.

$tree->generate($test_for_layout, array('class'=>'nav', 'elementClassField'=>'class', 'element'=>'menu/menu', 'model'=>'Menu'));

I added the following code to the __attributes function that would then generate the id/class names. I added this just before the line "if ($autoPath && $depth) {"

    function __attributes($rType, $elementData = array(), $clear = true) {
        ...[begin added code]...

        if ($elementIdField && $depth && $rType == $itemType) {
            $attributes['id'][] = $this->__settings['data'][$model][$elementIdField];
        }
        if ($elementClassField && $depth && $rType == $itemType) {
            $attributes['class'][] = $this->__settings['data'][$model][$elementClassField];
        }
    }

Here is a sample output:

<ul class="nav">
    <li>Items

    <ul>
        <li class="parent">List items

        <ul>
            <li class="parent">By location

            <ul>
                <li class="link">For my location</li>
                <li class="parent">locations A - K

                <ul>

                    <li class="tablelist">item list</li>

Let me know if this is useful.
Posted Sep 22, 2009 by Dan Bemowski
 

Bug

24 $firstChild asn $lastChild fix

When tree has non-numeric indexes both methods (Andy's and arbitr's) are wrong.

FIX:
lines 153-154:
$__addType = true;
foreach ($data as $i => $result) {

change to:
$__addType = true;
$iteration = 0;
$iterations = count($data);
foreach ($data as $i => $result) {

AND

lines 188-197:
$prevRow = prev($data);
if (!$prevRow) {
$firstChild = true;
}
next($data);
$nextRow = next($data);
if (!$nextRow) {
$lastChild = true;
}
prev($data);

change to:
/*$prevRow = prev($data);
if (!$prevRow) {
$firstChild = true;
}
next($data);
$nextRow = next($data);
if (!$nextRow) {
$lastChild = true;
}
prev($data);*/
$firstChild = $iteration == 0;
$lastChild = $iteration == $iterations - 1;

AND

lines 278-281:
}
}
/* Cleanup */

change to:
}
++$iteration;
}
/* Cleanup */
Posted Jan 14, 2010 by Tomasz Wójcik
 

Question

25 Can the treenode add a link?

I use the helper succeed. But I am wondering if the treenode or leaf can add link? That makes the helper more flexible.
Posted Feb 1, 2010 by Kevin