Browse Source

maxDepth for tree helper

euromark 13 years ago
parent
commit
557a529228
2 changed files with 180 additions and 167 deletions
  1. 0 1
      Test/Case/View/Helper/TreeHelperTest.php
  2. 180 166
      View/Helper/TreeHelper.php

+ 0 - 1
Test/Case/View/Helper/TreeHelperTest.php

@@ -3,7 +3,6 @@
 App::uses('TreeHelper', 'Tools.View/Helper');
 App::uses('MyCakeTestCase', 'Tools.TestSuite');
 App::uses('View', 'View');
-App::uses('Hash', 'Utility');
 
 class TreeHelperTest extends MyCakeTestCase {
 

+ 180 - 166
View/Helper/TreeHelper.php

@@ -1,105 +1,100 @@
 <?php
 
 /**
- * Tree Helper.
- *
- * Used the generate nested representations of hierarchial data
- *
- * PHP versions 4 and 5
+ * PHP versions 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
- * @modifiedBy $LastChangedBy: 2013-02-05 ms
  * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  */
 App::uses('AppHelper', 'View/Helper');
 
 /**
- * Tree helper
+ * TreeHelper class
  *
  * Helper to generate tree representations of MPTT or recursively nested data
  */
 class TreeHelper extends AppHelper {
 
-	/**
-	 * settings property
-	 *
-	 * @var array
-	 * @access private
-	 */
+/**
+ * Settings property
+ *
+ * @var array
+ */
 	protected $_settings = array();
 
-	/**
-	 * typeAttributes property
-	 *
-	 * @var array
-	 * @access private
-	 */
+/**
+ * typeAttributes property
+ *
+ * @var array
+ */
 	protected $_typeAttributes = array();
 
-	/**
-	 * typeAttributesNext property
-	 *
-	 * @var array
-	 * @access private
-	 */
+/**
+ * typeAttributesNext property
+ *
+ * @var array
+ */
 	protected $_typeAttributesNext = array();
 
-	/**
-	 * itemAttributes property
-	 *
-	 * @var array
-	 * @access private
-	 */
+/**
+ * itemAttributes property
+ *
+ * @var array
+ */
 	protected $_itemAttributes = array();
 
-	/**
-	 * helpers variable
-	 *
-	 * @var array
-	 * @access public
-	 */
+/**
+ * Helpers variable
+ *
+ * @var array
+ * @access public
+ */
 	public $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
-	 */
+/**
+ * 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 	find('threaded'); 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.
+ *  'maxDepth' => used to control the depth upto which to generate tree
+ *	'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 null (disabled) set the splitCount,
+ *		and optionally set the splitDepth to get parallel lists
+ *
+ * @param array $data data to loop on
+ * @param array $settings
+ * @return string html representation of the passed data
+ */
 	public function generate($data, $settings = array()) {
+		if (!$data) {
+			return '';
+		}
+
 		$this->_settings = array_merge(array(
 			'model' => null,
 			'alias' => 'name',
@@ -113,11 +108,13 @@ class TreeHelper extends AppHelper {
 			'left' => 'lft',
 			'right' => 'rght',
 			'depth' => 0,
+			'maxDepth' => 999,
 			'firstChild' => true,
 			'indent' => null,
 			'splitDepth' => false,
-			'splitCount' => 3,
-			), (array )$settings);
+			'splitCount' => null,
+			'totalNodes' => false
+		), (array)$settings);
 		if ($this->_settings['autoPath'] && !isset($this->_settings['autoPath'][2])) {
 			$this->_settings['autoPath'][2] = 'active';
 		}
@@ -142,6 +139,7 @@ class TreeHelper extends AppHelper {
 		}
 		$this->_settings['model'] = $model;
 
+		$this->_itemAttributes = $this->_typeAttributes = $this->_typeAttributesNext = array();
 		$stack = array();
 		if ($depth == 0) {
 			if ($class) {
@@ -153,18 +151,24 @@ class TreeHelper extends AppHelper {
 		}
 		$return = '';
 		$_addType = true;
-		foreach ($data as $i => $result) {
+		$this->_settings['totalNodes'] = count($data);
+		$keys = array_keys($data);
+
+		foreach ($data as $i => &$result) {
 			/* Allow 2d data arrays */
-			if ($model === '_NULL_') {
-				$_result = $result;
-				$result[$model] = $_result;
+			if ($model && isset($result[$model])) {
+				$row =& $result[$model];
+			} else {
+				$row =& $result;
 			}
 			/* BulletProof */
-			if (!isset($result[$model][$left]) && !isset($result['children'])) {
+			if (!isset($row[$left]) && !isset($result['children'])) {
 				$result['children'] = array();
 			}
 			/* Close open items as appropriate */
-			while ($stack && ($stack[count($stack) - 1] < $result[$model][$right])) {
+			// @codingStandardsIgnoreStart
+			while ($stack && ($stack[count($stack)-1] < $row[$right])) {
+				// @codingStandardsIgnoreEnd
 				array_pop($stack);
 				if ($indent) {
 					$whiteSpace = str_repeat("\t", count($stack));
@@ -180,33 +184,31 @@ class TreeHelper extends AppHelper {
 			/* Some useful vars */
 			$hasChildren = $firstChild = $lastChild = $hasVisibleChildren = false;
 			$numberOfDirectChildren = $numberOfTotalChildren = null;
+
 			if (isset($result['children'])) {
-				if ($result['children']) {
+				if ($result['children'] && $depth < $maxDepth) {
 					$hasChildren = $hasVisibleChildren = true;
 					$numberOfDirectChildren = count($result['children']);
 				}
-				$prevRow = prev($data);
-				if (!$prevRow) {
+				$key = array_search($i, $keys);
+				if ($key === 0) {
 					$firstChild = true;
 				}
-				next($data);
-				$nextRow = next($data);
-				if (!$nextRow) {
+				if ($key == count($keys) - 1) {
 					$lastChild = true;
 				}
-				prev($data);
-			} elseif (isset($result[$model][$left])) {
-				if ($result[$model][$left] != ($result[$model][$right] - 1)) {
+			} elseif (isset($row[$left])) {
+				if ($row[$left] != ($row[$right] - 1) && $depth < $maxDepth) {
 					$hasChildren = true;
-					$numberOfTotalChildren = ($result[$model][$right] - $result[$model][$left] - 1) / 2;
-					if (isset($data[$i + 1]) && $data[$i + 1][$model][$right] < $result[$model][$right]) {
+					$numberOfTotalChildren = ($row[$right] - $row[$left] - 1) / 2;
+					if (isset($data[$i + 1]) && $data[$i + 1][$model][$right] < $row[$right]) {
 						$hasVisibleChildren = true;
 					}
 				}
-				if (!isset($data[$i - 1]) || ($data[$i - 1][$model][$left] == ($result[$model][$left] - 1))) {
+				if (!isset($data[$i - 1]) || ($data[$i - 1][$model][$left] == ($row[$left] - 1))) {
 					$firstChild = true;
 				}
-				if (!isset($data[$i + 1]) || ($stack && $stack[count($stack) - 1] == ($result[$model][$right] + 1))) {
+				if (!isset($data[$i + 1]) || ($stack && $stack[count($stack) - 1] == ($row[$right] + 1))) {
 					$lastChild = true;
 				}
 			}
@@ -218,7 +220,8 @@ class TreeHelper extends AppHelper {
 				'numberOfTotalChildren' => $numberOfTotalChildren,
 				'firstChild' => $firstChild,
 				'lastChild' => $lastChild,
-				'hasVisibleChildren' => $hasVisibleChildren);
+				'hasVisibleChildren' => $hasVisibleChildren
+			);
 			$this->_settings = array_merge($this->_settings, $elementData);
 			/* Main Content */
 			if ($element) {
@@ -226,7 +229,7 @@ class TreeHelper extends AppHelper {
 			} elseif ($callback) {
 				list($content) = array_map($callback, array($elementData));
 			} else {
-				$content = $result[$model][$alias];
+				$content = $row[$alias];
 			}
 			if (!$content) {
 				continue;
@@ -265,7 +268,7 @@ class TreeHelper extends AppHelper {
 					}
 				} elseif ($numberOfTotalChildren) {
 					$_addType = true;
-					$stack[] = $result[$model][$right];
+					$stack[] = $row[$right];
 				}
 			} else {
 				if ($itemType) {
@@ -274,7 +277,6 @@ class TreeHelper extends AppHelper {
 				$return .= $this->_suffix();
 			}
 		}
-
 		/* Cleanup */
 		while ($stack) {
 			array_pop($stack);
@@ -289,6 +291,7 @@ class TreeHelper extends AppHelper {
 				$return .= '</' . $itemType . '>';
 			}
 		}
+
 		if ($return && $indent) {
 			$return .= "\r\n";
 		}
@@ -298,22 +301,21 @@ class TreeHelper extends AppHelper {
 				$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
-	 */
+/**
+ * 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
+ */
 	public function addItemAttribute($id = '', $key = '', $value = null) {
 		if (!is_null($value)) {
 			$this->_itemAttributes[$id][$key] = $value;
@@ -322,39 +324,39 @@ class TreeHelper extends AppHelper {
 		}
 	}
 
-	/**
-	 * 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)
-	 * $this->Tree->addTypeAttribute('style', 'display', 'hidden');
-	 * // give top level type (1) a class
-	 * $this->Tree->addTypeAttribute('class', 'hasHiddenGrandChildren', null, 'previous');
-	 *
-	 * @param string $id
-	 * @param string $key
-	 * @param mixed $value
-	 * @access public
-	 * @return void
-	 */
+/**
+ * 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
+ */
 	public function addTypeAttribute($id = '', $key = '', $value = null, $previousOrNext = 'next') {
-		$var = '__typeAttributes';
+		$var = '_typeAttributes';
 		$firstChild = isset($this->_settings['firstChild']) ? $this->_settings['firstChild'] : true;
 		if ($previousOrNext === 'next' && $firstChild) {
-			$var = '__typeAttributesNext';
+			$var = '_typeAttributesNext';
 		}
 		if (!is_null($value)) {
 			$this->{$var}[$id][$key] = $value;
@@ -363,27 +365,39 @@ class TreeHelper extends AppHelper {
 		}
 	}
 
-	/**
-	 * supressChildren method
-	 *
-	 * @return void
-	 */
+/**
+ * supressChildren method
+ *
+ * @return void
+ * @access public
+ */
 	public function supressChildren() {
 	}
 
-	/**
-	 * suffix method
-	 *
-	 * Used to close and reopen a ul/ol to allow easier listings
-	 *
-	 * @return void
-	 */
-	protected function _suffix() {
+/**
+ * suffix method
+ *
+ * Used to close and reopen a ul/ol to allow easier listings
+ *
+ * @return void
+ */
+	protected function _suffix($reset = false) {
 		static $_splitCount = 0;
 		static $_splitCounter = 0;
+
+		if ($reset) {
+			$_splitCount = 0;
+			$_splitCounter = 0;
+		}
 		extract($this->_settings);
-		if ($splitDepth) {
-			if ($depth == $splitDepth - 1) {
+		if ($splitDepth || $splitCount) {
+			if (!$splitDepth) {
+				$_splitCount = $totalNodes / $splitCount;
+				$rounded = (int)$_splitCount;
+				if ($rounded < $_splitCount) {
+					$_splitCount = $rounded + 1;
+				}
+			} elseif ($depth == $splitDepth - 1) {
 				$total = $numberOfDirectChildren ? $numberOfDirectChildren : $numberOfTotalChildren;
 				if ($total) {
 					$_splitCounter = 0;
@@ -394,25 +408,25 @@ class TreeHelper extends AppHelper {
 					}
 				}
 			}
-			if ($depth == $splitDepth) {
+			if (!$splitDepth || $depth == $splitDepth) {
 				$_splitCounter++;
-				if ($type && ($_splitCounter % $_splitCount) === 0) {
+				if ($type && ($_splitCounter % $_splitCount) === 0 && !$lastChild) {
+					unset ($this->_settings['callback']);
 					return '</' . $type . '><' . $type . '>';
 				}
 			}
 		}
-		return;
 	}
 
-	/**
-	 * attributes function
-	 *
-	 * Logic to apply styles to tags.
-	 *
-	 * @param mixed $rType
-	 * @param array $elementData
-	 * @return void
-	 */
+/**
+ * attributes function
+ *
+ * Logic to apply styles to tags.
+ *
+ * @param mixed $rType
+ * @param array $elementData
+ * @return void
+ */
 	protected function _attributes($rType, $elementData = array(), $clear = true) {
 		extract($this->_settings);
 		if ($rType == $type) {