Welcome Guest, Not a member yet? Register   Sign In
Nested Set library (aka MPTT, aka Hierarchy DB Trees)
#11

[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?
#12

[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 Smile
#13

[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;
    }
#14

[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;
    }
#15

[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.
#16

[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.
#17

[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.
#18

[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 Smile
#19

[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).
#20

[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? Smile

Thanks!




Theme © iAndrew 2016 - Forum software by © MyBB