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) . ''; $return .= ''; } /* 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 .= ''; } elseif ($numberOfTotalChildren) { $__addType = true; $stack[] = $result[$model][$right]; } } else { $return .= ''; $return .= $this->__suffix(); } } /* Cleanup */ while ($stack) { array_pop($stack); $return .= "\r\n" . str_repeat("\t",count($stack) + 1) . ''; $return .= ''; } $return .= "\r\n" . '' . "\r\n"; return $return; } /** * addItemAttribute function * * Called to modify the attributes of the next to be processed * Note that the content of a 'node' is processed before generating its wrapping 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 to be processed * Note that the content of a 'node' is processed before generating its wrapping tag (if appropriate) * An 'interesting' case is that of a first child with children. To generate the output *
    (1) *
  • XYZ (3) *
      (2) *
    • ABC... * ... *
    * ... * 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 . '>'; } } } 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 ''; } } ?>