Browse Source

allow treehelper element/callback to hide unrelated tree records

euromark 13 years ago
parent
commit
a0eac869c8
2 changed files with 244 additions and 31 deletions
  1. 165 3
      Test/Case/View/Helper/TreeHelperTest.php
  2. 79 28
      View/Helper/TreeHelper.php

+ 165 - 3
Test/Case/View/Helper/TreeHelperTest.php

@@ -10,6 +10,20 @@ class TreeHelperTest extends MyCakeTestCase {
 
 
 	public $Model;
 	public $Model;
 
 
+	/**
+	 * Initial Tree
+	 *
+	 * - One
+	 * -- One-SubA
+	 * - Two
+	 * -- Two-SubA
+	 * --- Two-SubA-1
+	 * ---- Two-SubA-1-1
+	 * - Three
+	 * - Four
+	 * -- Four-SubA
+	 *
+	 */
 	public function setUp() {
 	public function setUp() {
 		parent::setUp();
 		parent::setUp();
 
 
@@ -87,6 +101,84 @@ TEXT;
 		$this->assertTrue(substr_count($output, '<li>') === substr_count($output, '</li>'));
 		$this->assertTrue(substr_count($output, '<li>') === substr_count($output, '</li>'));
 	}
 	}
 
 
+	//TODO: beautify debug output
+	public function testGenerateWithFindAll() {
+		$tree = $this->Model->find('all', array('order' => array('lft' => 'ASC')));
+
+		$output = $this->Tree->generate($tree);
+		//debug($output); return;
+		$expected = <<<TEXT
+
+<ul>
+	<li>One
+	<ul>
+		<li>One-SubA</li>
+	</ul>
+	</li>
+	<li>Two
+	<ul>
+		<li>Two-SubA
+		<ul>
+			<li>Two-SubA-1
+			<ul>
+				<li>Two-SubA-1-1</li>
+			</ul>
+			</li>
+		</ul>
+		</li>
+	</ul>
+	</li>
+	<li>Three</li>
+	<li>Four
+	<ul>
+		<li>Four-SubA</li>
+	</ul>
+	</li>
+</ul>
+
+TEXT;
+		$output = str_replace(array("\t", "\r", "\n"), '', $output);
+		$expected = str_replace(array("\t", "\r", "\n"), '', $expected);
+		$this->assertTextEquals($expected, $output);
+	}
+
+	public function testGenerateWithDepth() {
+		$tree = $this->Model->find('threaded');
+
+		$output = $this->Tree->generate($tree, array('depth' => 1));
+		$expected = <<<TEXT
+
+	<ul>
+		<li>One
+		<ul>
+			<li>One-SubA</li>
+		</ul>
+		</li>
+		<li>Two
+		<ul>
+			<li>Two-SubA
+			<ul>
+				<li>Two-SubA-1
+				<ul>
+					<li>Two-SubA-1-1</li>
+				</ul>
+				</li>
+			</ul>
+			</li>
+		</ul>
+		</li>
+		<li>Three</li>
+		<li>Four
+		<ul>
+			<li>Four-SubA</li>
+		</ul>
+		</li>
+	</ul>
+
+TEXT;
+		$this->assertTextEquals($expected, $output);
+	}
+
 	public function testGenerateWithSettings() {
 	public function testGenerateWithSettings() {
 		$tree = $this->Model->find('threaded');
 		$tree = $this->Model->find('threaded');
 
 
@@ -159,10 +251,10 @@ TEXT;
 
 
 	public function testGenerateWithAutoPath() {
 	public function testGenerateWithAutoPath() {
 		$tree = $this->Model->find('threaded');
 		$tree = $this->Model->find('threaded');
-		debug($tree);
+		//debug($tree);
 
 
 		$output = $this->Tree->generate($tree, array('autoPath' => array(7, 10))); // Two-SubA-1
 		$output = $this->Tree->generate($tree, array('autoPath' => array(7, 10))); // Two-SubA-1
-		debug($output);
+		//debug($output);
 		$expected = <<<TEXT
 		$expected = <<<TEXT
 
 
 <ul>
 <ul>
@@ -196,7 +288,7 @@ TEXT;
 		$this->assertTextEquals($expected, $output);
 		$this->assertTextEquals($expected, $output);
 
 
 		$output = $this->Tree->generate($tree, array('autoPath' => array(8, 9))); // Two-SubA-1-1
 		$output = $this->Tree->generate($tree, array('autoPath' => array(8, 9))); // Two-SubA-1-1
-		debug($output);
+		//debug($output);
 		$expected = <<<TEXT
 		$expected = <<<TEXT
 
 
 <ul>
 <ul>
@@ -230,6 +322,76 @@ TEXT;
 		$this->assertTextEquals($expected, $output);
 		$this->assertTextEquals($expected, $output);
 	}
 	}
 
 
+	/**
+	 *
+	 * - One
+	 * -- One-SubA
+	 * - Two
+	 * -- Two-SubA
+	 * --- Two-SubA-1
+	 * ---- Two-SubA-1-1
+	 * -- Two-SubB
+	 * -- Two-SubC
+	 * - Three
+	 * - Four
+	 * -- Four-SubA
+	 */
+	public function testGenerateWithAutoPathAndHideUnrelated() {
+		$data = array(
+			array('name' => 'Two-SubB', 'parent_id' => 2),
+			array('name' => 'Two-SubC', 'parent_id' => 2),
+		);
+		foreach ($data as $row) {
+			$this->Model->create();
+			$this->Model->save($row);
+		}
+
+		$tree = $this->Model->find('threaded');
+		$id = 6;
+		$path = $this->Model->getPath($id);
+	 	//$this->_hideUnrelated($tree, $path);
+
+		$output = $this->Tree->generate($tree, array('autoPath' => array(6, 11), 'hideUnrelated' => true, 'treePath' => $path, 'callback'=>array($this, '_myCallback'))); // Two-SubA
+		//debug($output);
+
+		$expected = <<<TEXT
+
+<ul>
+	<li>One</li>
+	<li class="active">Two (active)
+	<ul>
+		<li class="active">Two-SubA (active)
+		<ul>
+			<li>Two-SubA-1
+			<ul>
+				<li>Two-SubA-1-1</li>
+			</ul>
+			</li>
+		</ul>
+		</li>
+		<li>Two-SubB</li>
+		<li>Two-SubC</li>
+	</ul>
+	</li>
+	<li>Three</li>
+	<li>Four</li>
+</ul>
+
+TEXT;
+		$output = str_replace(array("\t", "\r", "\n"), '', $output);
+		$expected = str_replace(array("\t", "\r", "\n"), '', $expected);
+		debug($output);
+		debug($expected);
+		$this->assertTextEquals($expected, $output);
+	}
+
+	public function _myCallback($data) {
+		if (!empty($data['data']['AfterTree']['hide'])) {
+			return;
+		}
+		return $data['data']['AfterTree']['name'] . ($data['activePathElement'] ? ' (active)' : '');
+	}
+
 	public function testGenerateProductive() {
 	public function testGenerateProductive() {
 		$tree = $this->Model->find('threaded');
 		$tree = $this->Model->find('threaded');
 
 

+ 79 - 28
View/Helper/TreeHelper.php

@@ -52,7 +52,6 @@ class TreeHelper extends AppHelper {
  * Helpers variable
  * Helpers variable
  *
  *
  * @var array
  * @var array
- * @access public
  */
  */
 	public $helpers = array('Html');
 	public $helpers = array('Html');
 
 
@@ -75,6 +74,8 @@ class TreeHelper extends AppHelper {
  *	'element' => path to an element to render to get node contents.
  *	'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'
  *	'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.
  *	'autoPath' => array($left, $right [$classToAdd = 'active']) if set any item in the path will have the class $classToAdd added. MPTT only.
+ *  'hideUnrelated' => if unrelated (not children, not siblings) should be hidden, needs 'treePath'
+ *  'treePath' => treePath to insert into callback/element
  *	'left' => name of the 'lft' field if not lft. only applies to MPTT data
  *	'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
  *	'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.
  *	'depth' => used internally when running recursively, can be used to override the depth in either mode.
@@ -105,6 +106,8 @@ class TreeHelper extends AppHelper {
 			'element' => false,
 			'element' => false,
 			'callback' => false,
 			'callback' => false,
 			'autoPath' => false,
 			'autoPath' => false,
+			'hideUnrelated' => false,
+			'treePath' => array(),
 			'left' => 'lft',
 			'left' => 'lft',
 			'right' => 'rght',
 			'right' => 'rght',
 			'depth' => 0,
 			'depth' => 0,
@@ -151,6 +154,10 @@ class TreeHelper extends AppHelper {
 		$this->_settings['totalNodes'] = count($data);
 		$this->_settings['totalNodes'] = count($data);
 		$keys = array_keys($data);
 		$keys = array_keys($data);
 
 
+		if ($hideUnrelated) {
+			$this->_markUnrelatedAsHidden($data, $treePath);
+		}
+
 		foreach ($data as $i => &$result) {
 		foreach ($data as $i => &$result) {
 			/* Allow 2d data arrays */
 			/* Allow 2d data arrays */
 			if ($model && isset($result[$model])) {
 			if ($model && isset($result[$model])) {
@@ -158,10 +165,7 @@ class TreeHelper extends AppHelper {
 			} else {
 			} else {
 				$row =& $result;
 				$row =& $result;
 			}
 			}
-			/* BulletProof */
-			if (!isset($row[$left]) && !isset($result['children'])) {
-				$result['children'] = array();
-			}
+
 			/* Close open items as appropriate */
 			/* Close open items as appropriate */
 			// @codingStandardsIgnoreStart
 			// @codingStandardsIgnoreStart
 			while ($stack && ($stack[count($stack)-1] < $row[$right])) {
 			while ($stack && ($stack[count($stack)-1] < $row[$right])) {
@@ -208,7 +212,21 @@ class TreeHelper extends AppHelper {
 				if (!isset($data[$i + 1]) || ($stack && $stack[count($stack) - 1] == ($row[$right] + 1))) {
 				if (!isset($data[$i + 1]) || ($stack && $stack[count($stack) - 1] == ($row[$right] + 1))) {
 					$lastChild = true;
 					$lastChild = true;
 				}
 				}
+			} else {
+				throw new CakeException('Invalid Tree Structure');
 			}
 			}
+
+			$activePathElement = null;
+			if ($autoPath) {
+				if ($result[$model][$left] <= $autoPath[0] && $result[$model][$right] >= $autoPath[1]) {
+					$activePathElement = true;
+				} elseif (isset($autoPath[3]) && $result[$model][$left] == $autoPath[0]) {
+					$activePathElement = $autoPath[3];
+				} else {
+					$activePathElement = false;
+				}
+			}
+
 			$elementData = array(
 			$elementData = array(
 				'data' => $result,
 				'data' => $result,
 				'depth' => $depth ? $depth : count($stack),
 				'depth' => $depth ? $depth : count($stack),
@@ -217,7 +235,8 @@ class TreeHelper extends AppHelper {
 				'numberOfTotalChildren' => $numberOfTotalChildren,
 				'numberOfTotalChildren' => $numberOfTotalChildren,
 				'firstChild' => $firstChild,
 				'firstChild' => $firstChild,
 				'lastChild' => $lastChild,
 				'lastChild' => $lastChild,
-				'hasVisibleChildren' => $hasVisibleChildren
+				'hasVisibleChildren' => $hasVisibleChildren,
+				'activePathElement' => $activePathElement,
 			);
 			);
 			$this->_settings = array_merge($this->_settings, $elementData);
 			$this->_settings = array_merge($this->_settings, $elementData);
 			/* Main Content */
 			/* Main Content */
@@ -316,7 +335,6 @@ class TreeHelper extends AppHelper {
  * @param string $id
  * @param string $id
  * @param string $key
  * @param string $key
  * @param mixed $value
  * @param mixed $value
- * @access public
  * @return void
  * @return void
  */
  */
 	public function addItemAttribute($id = '', $key = '', $value = null) {
 	public function addItemAttribute($id = '', $key = '', $value = null) {
@@ -352,7 +370,6 @@ class TreeHelper extends AppHelper {
  * @param string $id
  * @param string $id
  * @param string $key
  * @param string $key
  * @param mixed $value
  * @param mixed $value
- * @access public
  * @return void
  * @return void
  */
  */
 	public function addTypeAttribute($id = '', $key = '', $value = null, $previousOrNext = 'next') {
 	public function addTypeAttribute($id = '', $key = '', $value = null, $previousOrNext = 'next') {
@@ -372,7 +389,6 @@ class TreeHelper extends AppHelper {
  * supressChildren method
  * supressChildren method
  *
  *
  * @return void
  * @return void
- * @access public
  */
  */
 	public function supressChildren() {
 	public function supressChildren() {
 	}
 	}
@@ -445,32 +461,67 @@ class TreeHelper extends AppHelper {
 				$this->_itemAttributes = array();
 				$this->_itemAttributes = array();
 			}
 			}
 		}
 		}
-		if ($autoPath && $rType == $itemType) {
-			if ($this->_settings['data'][$model][$left] <= $autoPath[0] && $this->_settings['data'][$model][$right] >= $autoPath[1]) {
+		if ($rType == $itemType && $elementData['activePathElement']) {
+			if ($elementData['activePathElement'] === true) {
 				$attributes['class'][] = $autoPath[2];
 				$attributes['class'][] = $autoPath[2];
-			} elseif (isset($autoPath[3]) && $this->_settings['data'][$model][$left] == $autoPath[0]) {
-				$attributes['class'][] = $autoPath[3];
+			} else {
+				$attributes['class'][] = $elementData['activePathElement'];
 			}
 			}
 		}
 		}
-		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 . ';';
+		if (!$attributes) {
+			return '';
+		}
+		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);
+			$attributes[$type] = $type . '="' . implode(' ', $attributes[$type]) . '"';
+		}
+		return ' ' . implode(' ', $attributes);
+	}
+
+	/**
+	 * Mark unrelated records as hidden using `'hide' => 1`
+	 * In the callback or element you can then return early in this case
+	 *
+	 * @param array $tree
+	 * @param array $treePath
+	 * @param int $level
+	 * @return void
+	 */
+	protected function _markUnrelatedAsHidden(&$tree, $path, $level = 0) {
+		extract($this->_settings);
+		$siblingIsActive = false;
+		foreach ($tree as $key => &$subTree) {
+			if (!isset($subTree['children'])) {
+				throw new CakeException('Only workes with threaded (nested children) results');
+			}
+
+			if (!empty($path[$level]) && $subTree[$model]['id'] == $path[$level][$model]['id']) {
+				$subTree[$model]['show'] = 1;
+				$siblingIsActive = true;
+			}
+			if (!empty($subTree[$model]['show']) || !empty($subTree[$model]['parent_show'])) {
+				foreach ($subTree['children'] as &$v) {
+					$v[$model]['parent_show'] = 1;
+				}
+			}
+		}
+		foreach ($tree as $key => &$subTree) {
+			if (!$siblingIsActive && !isset($subTree[$model]['parent_show'])) {
+				$subTree[$model]['hide'] = 1;
+			}
+			$this->_markUnrelatedAsHidden($subTree['children'], $path, $level + 1);
 		}
 		}
-		return '';
 	}
 	}
 
 
 }
 }