/wildflower/views/helpers/tree.php
PHP | 393 lines | 229 code | 0 blank | 164 comment | 65 complexity | 82b8c302f2679f0b971e1f4b6d0d36f2 MD5 | raw file
Possible License(s): LGPL-2.1
- <?php
- /* SVN FILE: $Id: tree.php 112 2008-06-19 11:57:33Z 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: 112 $
- * @modifiedBy $LastChangedBy: AD7six $
- * @lastModified $Date: 2008-06-19 13:57:33 +0200 (Thu, 19 Jun 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,
- 'splitDepth' => false,
- 'splitCount' => 3,
- ), (array)$settings);
- if ($this->__settings['autoPath'] && !isset($this->__settings['autoPath'][2])) {
- $this->__settings['autoPath'][2] = 'active';
- }
- extract($this->__settings);
- $view =& ClassRegistry:: getObject('view');
- if ($model === null) {
- $model = Inflector::classify($view->params['models'][0]);
- }
- $stack = array();
- if ($depth == 0) {
- if ($class) {
- $this->addTypeAttribute('class', $class, null, 'previous');
- }
- if ($id) {
- $this->addTypeAttribute('id', $id, null, 'previous');
- }
- }
- $return = '';
- $__addType = true;
- foreach ($data as $i => $result) {
- /* Allow 2d data arrays */
- if (!$model) {
- $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);
- $return .= "\r\n" . str_repeat("\t",count($stack) + 1) . '</' . $type . '>';
- $return .= '</' . $itemType . '>';
- }
- /* Some useful vars */
- $hasChildren = $firstChild = $lastChild = $hasVisibleChildren = false;
- $numberOfDirectChildren = $numberOfTotalChildren = 0;
- 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;
- }
- /* Prefix */
- if ($__addType) {
- $typeAttributes = $this->__attributes($type, array('data' => $elementData));
- $return .= "\r\n" . str_repeat("\t",count($stack)) . '<' . $type . $typeAttributes . '>';
- }
- $itemAttributes = $this->__attributes($itemType, $elementData);
- $return .= "\r\n" . str_repeat("\t",count($stack) + 1) . '<' . $itemType . $itemAttributes . '>';
- $return .= $content;
- /* Suffix */
- $__addType = false;
- if ($hasChildren) {
- if ($numberOfDirectChildren) {
- $settings['depth'] = $depth + 1;
- $return .= $this->__suffix();
- $return .= $this->generate($result['children'], $settings);
- $return .= '</' . $itemType . '>';
- } elseif ($numberOfTotalChildren) {
- $__addType = true;
- $stack[] = $result[$model][$right];
- }
- } else {
- $return .= '</' . $itemType . '>';
- $return .= $this->__suffix();
- }
- }
- /* Cleanup */
- while ($stack) {
- array_pop($stack);
- $return .= "\r\n" . str_repeat("\t",count($stack) + 1) . '</' . $type . '>';
- $return .= '</' . $itemType . '>';
- }
- $return .= "\r\n" . '</' . $type . '>' . "\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;
- }
- }
- /**
- * 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 (($__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 '';
- }
- }
- ?>