[eluser]tobben[/eluser]
Hi Dirkpostma.
I have been thinking about the same thing, to switch it back to a model.
And add some extra query methods, also serve along a extension class to deal with recursion and general generation of navigation lists, dropdowns, breadcrumbs etc. Here I have already started.
I think this library is a good place to start.. but it has to be further developed.
Would you do the honour of making it into a model again?
[eluser]dirkpostma[/eluser]
There are also some other issues, e.g.: setNodeAsFirstChild does not check whether the target is a child of the node to move. If that's the case, the tree is broken at the current time. I will add a method "isDescendant" to check whether this is the case and if so, either return false or first move the target node to the [node to move]'s parent to make sure the tree won't be broken.
Here some code I created:
Code: /**
* Test to determine whether a node (A) is an ancestor ([grand]*parent) of some other node (B)
* @param array $a The node to use as the initial focus of enquiry
* @param array $b The node prototype to use for the comparison
* @return boolean
*/
public function isAncestor($a, $b) {
// Node A is an ancestor of node B if A.lft < B.lft AND A.rgt > B.rgt
return (($a[$this->left_column_name]<$b[$this->left_column_name]) and ($a[$this->right_column_name]>$b[$this->right_column_name]));
}
/**
* Test to determine whether a node (A) is a descedant ([grand]*child) of some other node (B)
* @param array $a The node to use as the initial focus of enquiry
* @param array $b The node prototype to use for the comparison
* @return boolean
*/
public function isDescendant($a, $b) {
// Node A is a descendant of node B if A.lft > B.lft AND A.rgt < B.rgt
return (($a[$this->left_column_name]>$b[$this->left_column_name]) and ($a[$this->right_column_name]<$b[$this->right_column_name]));
}
And, some method names are not intuitive. E.g. the code of method "getAncestor" actually performs a "getParent". I renamed that:
Code: /**
* Returns the node that represents the parent of the given node
* @param array $currNode The node to use as the initial focus of enquiry
* @return array $resultNode the node returned
*/
public function getParent($currNode) {
return $this->getNodeWhere($this->primary_key_column_name . ' = "' . $currNode[$this->parent_column_name] . '"');
}
I'm not promising anything, but IF I adapt the class and is suitable for sharing, I certainly will
[eluser]tobben[/eluser]
UPDATED POST
Also one thing might come in handy.. exspecially for making breadcrumbs
Code: /**
* Find the path of a given node
* @param array $node The node to start with
* @param boolean $includeSelf Wheter or not to include given node in result
* @param boolean $returnAsArray Wheter or not to return array or unordered list
* @return array or unordered list
*/
public function getPath($node, $includeSelf=FALSE, $returnAsArray=FALSE) {
if(emtpy($node)) return FALSE;
$leftcol = $this->left_column_name;
$rightcol = $this->right_column_name;
$leftval = (int) $node[$leftcol];
$rightval = (int) $node[$rightcol];
if($includeSelf)
{
$this->db->where($leftcol . ' <= ' . $leftval . ' AND ' . $rightcol . ' >= ' . $rightval);
}
else
{
$this->db->where($leftcol . ' < ' . $leftval . ' AND ' . $rightcol . ' > ' . $rightval);
}
$this->db->order_by($leftcol);
$query = $this->db->get($this->table_name);
if($query->num_rows() > 0)
{
if($returnAsArray)
{
return $query->result_array();
}
else
{
return $this->buildCrumbs($query->result_array());
}
}
return FALSE;
}
Also the generation of the list if not array is returned
Code: function buildCrumbs($crumbData)
{
$retVal = '';
$retVal = '<ul>';
foreach ($crumbData as $itemId)
{
$retVal .= '<li>' . anchor(
$itemId['id'],
$itemId['title'],
array('title' => $itemId['title'])
);
$retVal .= '</li>';
}
$retVal .= '</ul>';
return $retVal;
}
[eluser]tobben[/eluser]
Also getSubTree (maybe added AsList to the method name)
Could certainly be further developed..... but its a start
Maybe the list generation could have some settings for id/class names.. max depth etc.
Code: /**
* Renders the tree starting from given node
* @param array $node The node to start with
* @return string Unordered HTML list of the tree
*/
public function getSubTree($node) {
if(empty($node)) return FALSE;
$tree_handle = $this->getTreePreorder($node);
$menuData = array(
'items' => array(),
'parents' => array()
);
foreach ($tree_handle['result_array'] as $menuItem)
{
$menuData['items'][$menuItem['id']] = $menuItem;
$menuData['parents'][$menuItem['parent_id']][] = $menuItem['id'];
}
return $this->buildMenu($node['parent_id'], $menuData);
}
function buildMenu($parentId, $menuData, $depth=0)
{
$retVal = '';
if (isset($menuData['parents'][$parentId]))
{
$retVal = '<ul>';
foreach ($menuData['parents'][$parentId] as $itemId)
{
$retVal .= '<li class="depth-'.$depth.'">' . anchor(
$menuData['items'][$itemId]['id'],
$menuData['items'][$itemId]['title'],
array('title' => $menuData['items'][$itemId]['title'])
);
$retVal .= $this->buildMenu($itemId, $menuData, $depth+1);
$retVal .= '</li>';
}
$retVal .= '</ul>';
}
return $retVal;
}
[eluser]nilsm[/eluser]
This is a fantastic contribution / discussion.
It would be great if someone could upload a version with all the fixes in place.
Many thanks in advance.
[eluser]creativeinsight[/eluser]
I had just spent a week programming the nested sets in CI with out finding this, now i have i'd like to ask ifthunders code is based on every thing being encapsulated by a top node so every thing lies within the hierachy.
I ask becouse i had been writting mine based on the top level being seperate nodes
eg
1-2
3-6 parent to 4-5
7-8
where i belive this cod eis based on everything laying between
eg
1
2-3
4 7 parent to 5-6
8-9
10
This is from reading through teh code i just want some one to confirm my conclusion who knows.
[eluser]Jon L[/eluser]
I don't know if Thunder has updated his original code, but when I started making changes (for the code reflected in this thread), Thunder's base script had assumed everything must exist within a single master node, to function.
Such as:
1{
2-3
4-5
6{
7-8
}9
}10
My code made changes to make multiple master (or, root) nodes possible:
1{
2-3
}4
5{
6{
7-8
}9
}10
All nodes, regardless, always occupy *at least* 2 spaces, such as 1-2, 3-4, 5-6, etc, as they always need a lft and rgt value (otherwise the system breaks down).
Hope that answers your question.
[eluser]creativeinsight[/eluser]
thanks for that, i had got my head around the nested sets and had wrtitten my own upto the moving nodes as siblings functionality(yeah i'd done most the work)
But i could not make out weather your code allwoed multiple top level nodes.
i'm using the code in backendpro, did not find it in there till i found this thread.
Thanks for the reply
[eluser]WanWizard[/eluser]
I thought about a similar implementation, but decided against it.
Instead, I have implemented multiple roots by introducing a tree_id, which is part of the WHERE clause in all queries. In parent tables, I only store the tree_id, all tree roots start with 1, and the queries never have to deal with records not part of the selected tree (so things like counting nodes is very strait forward).
[eluser]CroNiX[/eluser]
I'm a bit confused. In Joe Celmo's original article (linked to in the first post), there is a parent_id, left_id and right_id. In this library, there is also an extra id, primary_key_column_name which it says would be the auto id column, which in Joe's example is the parent_id. Why is this extra ID necessary and what is it used for? In this library, if primary_key_column is equivalent to Joe's parent_key, what is the parent_column_name field used for? Why do you need more than just the parent_id, left_id and right_id?
Maybe if someone could provide some simple sample sql table to test I could better understand how its working and the differences. Please?
Thanks!
|