Nested set

How can I create a root nodes?




$root=new Root;

$root->title='Mobile Phones';

$root->saveNode();

$root=new Root;

$root->title='Cars';

$root->saveNode();



where is the Root Class?

Can you give a example? TKS~ :lol:

I think


$root=new Root

is a typo in the doc. It should be


$root=new Category

I was test use


$root=new Category

Is error~

thank you~~~ phtamas ::)

Could you please help me get this clear — how exactly can I display the whole tree via CTreeView widget? What is the right way to get the whole tree, when three has many roots?

E.g I have list of categories, and I need to display the whole categories tree via CTreeView?

May I suggest that you take a look at ejNestedTreeActions?

http://www.yiiframework.com/forum/index.php?/topic/9434-extension-ejnestedtreeactions/page__pid__112890__st__20#entry112890

Be sure to grab it from Github, and then apply the fix for the latest nested set as written in the topic I just linked to.

Oh, I foud someone else’s code and it was buggu, but turns out it just needs a little fix and it now works!

So, this should be added to NestedSetBehavior:




public function getTreeViewData($rootNodeId = null, $isReturnRootNode = true){

        $keyField = 'id';


        if ($rootNodeId == null)

            $rawTree = $this->getTreeWithoutManyRoots();

        else

            $rawTree = $this->getTreeWithManyRoots($rootNodeId);


        // Init variables needed for the array conversion

        $tree = array();

        $node =& $tree;

        $position = array();

        $lastitem = '';

        $depth = 1;


        foreach($rawTree as $rawItem){

            // If its a deeper item, then make it subitems of the current item

            if ($rawItem->getLevelValue() > $depth)

            {

                $position[] =& $node; //$lastitem;

                $depth = $rawItem->getLevelValue();

                $node =& $node[$lastitem]['children'];

            }

            // If its less deep item, then return to a level up

            else

            {

                while ($rawItem->getLevelValue() < $depth)

                {

                    end($position);

                    $node =& $position[key($position)];

                    array_pop($position);

                    $depth = $node[key($node)]['node']->getLevelValue();

                }

            }


            // Add the item to the final array

            $node[$rawItem->$keyField]['node'] = $rawItem;

            $node[$rawItem->$keyField]['id'] = (int) 'node'.$rawItem->owner->id;

            $node[$rawItem->$keyField]['text'] = (string) $rawItem->owner->name;

            // save the last items' name

            $lastitem = $rawItem->$keyField;

        }

        // we don't care about the root node

        if (!$isReturnRootNode){

            reset($tree);

            $tree = $tree[key($tree)]['children'];

            //array_shift($tree);

        }


        return $tree;

    }




    protected function getTreeWithoutManyRoots(){

        $owner=$this->getOwner();

        return $owner->findAll(array('order'=>$this->hasManyRoots

                           ?$this->rootAttribute . ', ' . $this->leftAttribute

                           :$this->leftAttribute));

    }




    protected function getTreeWithManyRoots($rootId){

        $owner=$this->getOwner();

        return $owner->findAll(

            array(

                 'condition' => $this->rootAttribute.'=:rootId',

                 'order'=>$this->leftAttribute,

                 'params' => array(':rootId'=>$rootId)

            ));

    }




    protected function hasChildNodes(){

        return $this->getLeftValue() != ($this->getRightValue() - 1);

    }


    protected function getLeftValue(){

        $fieldName = $this->leftAttribute;

        $owner=$this->getOwner();


        return $owner->$fieldName;

    }


    protected function getRightValue(){

        $fieldName = $this->rightAttribute;

        $owner=$this->getOwner();


        return $owner->$fieldName;

    }




    protected function getLevelValue(){

        $fieldName = $this->levelAttribute;

        $owner=$this->getOwner();


        return $owner->$fieldName;

    }




    protected function getRootValue(){

        $fieldName = $this->rootAttribute;

        $owner=$this->getOwner();


        return $owner->$fieldName;

    }

I have just made simple method to get paths for node(s) as strings. Result is like: [PrimaryKey] => "Root • Node 1 • Node2 • Node 3" Maybe someone will found it useful.




/**

	 * Build the path to node(s)

	 * @param string $column name of model attribute column

	 * @param string $option accept all|leafs|branches|this

	 * @param bool $inclRoot include root to part

	 * @param string $separator path separator

	 * @return array list of paths

	 */

	public function paths($column, $option = 'this', $inclRoot = TRUE, $separator = ' • ') {

		$result = NULL;

		if ($option == 'this') {

			$path = NULL;

			foreach ($this->ancestors()->findAll(array('order' => $this->leftAttribute)) as $node) {

				$path[] = $node->attributes[$column];

			}

			if ($inclRoot) {

				$result[$node->primaryKey] = implode($separator, $path);

			} else {

				$result[$node->primaryKey] = implode($separator, array_slice($path, 1, count($path)));

			}

		} elseif (in_array ($option, array('all','branches','leafs'))) {

			$nodes = $this->findAll(array('order' => $this->leftAttribute));

			$actualLevel = 1;

			$aPath = array();

			foreach ($nodes as $k => $node) {

				$include = FALSE;

				if ($option == 'all') {

					$include = TRUE;

				} elseif ($option == 'branches' && !$node->isLeaf()) {

					$include = TRUE;

				} elseif ($option == 'leafs' && $node->isLeaf()) {

					$include = TRUE;

				}


				if ($node->{$this->levelAttribute} < $actualLevel) {

					$path = array_slice($path, 0, $node->{$this->levelAttribute} - 1);

				}

				$path[$node->{$this->levelAttribute}] = $node[$column];

				$actualLevel = $node->{$this->levelAttribute};


				if ($include) {

					if ($inclRoot) {

						$result[$node->primaryKey] = implode($separator, $path);

					} else {

						$result[$node->primaryKey] = implode($separator, array_slice($path, 1, count($path)));

					}

				}

			}

		}

		return $result;

	}



really useful thing!

have tried to find swapNodes($node1,$node2) method, but unluckly :)

write to your to-do list, if you’re planning to update you great-behavior!

Also will be nice to have copy of node function. thx.

Hello,

can someone provide an sql statement thats fetches multiple trees with its hierachical data?

Cars

  • Electro

– automatic

– manual

  • Gas

Building

  • Test1

  • Test2

In need the complete structure, correctly sorted within the hierarchy. I just found sql statements for getting only one tree but I need all the trees.

thanks in advance :)

Anyone got a solution?

I tried the following but did not solve it, as I dont know how to merge the trees :(




    	$roots=Category::model()->roots()->findAll();

    	foreach($roots as $root) {

        	$tree = Category::model()->findAll(

                	array(

                    	'condition'=>'root='.$root->id,

                    	'order'=>'lft'

                	)

        	);

    	}  


  	// I need to merge all the trees, but don't know how


  	$this->renderPartial(

            	'//partials/category/_gridViewCategory',

            	array(

                	'tree'=>$tree,

            	)

    	);



Hi,

I was thinking of using this extension but I have few questions about concepts made here. I am not very familiar with nested set using left-right-parent attributes (I did use pattern with extra table containing all relations), so my questions may be silly or my concepts are wrong. In such case simply point me such cases :)

  1. Why not using events (beforeSave in this case) to apply specific functionalities and instead providing additional functions like ‘saveNode’? I like extensions that do not need any modifications to standard approach when it is not really needed - this is the reason I ask about it. Is there any reason for this extra interface methods?

  2. Why there is additional attribute ‘root’ that is used in multi-root scenario? why can’t you just get roots by ‘id_parent = NULL’ or ‘level = 0’?

ping…

  1. Yes, there is. It wasn’t possible to proceed with standard API.

  2. What is id_parent? If 10 trees are stored how to get tree #8?

  1. could you be more specific? What is default ‘save’ used for now?

  2. if I find elements with level=0 or id_parent=null I can easily sort them by ‘lft’ and get 8th element of such list, or I can find interesting root with more specific query (by other columns). Consider such data:




node  , lft, rgh, level, id_parent

root 1,   1,   2,     0,      null

root 2,   3,   4,     0,      null

root 3,   5,   6,     0,      null

root 4,   7,   8,     0,      null

root 5,   9,  10,     0,      null



Say you had a tree like this with its accompanying representation in the database.




   R

N1   N2


lft, rgh, level

1,   6,     0     

2,   3,     0      

4,   5,     0     



If I want to insert a child node under N1, then all the rows in the database have to be updated.

However, say you changed the database so the tree was represented like this:




lft, rgh, level

100,   600,     0     

200,   300,     0      

400,   500,     0     



Now if I want to insert under N1, you don’t have to alter the root or N2.

So, would it be possible to introduce "gaps" like this to reduce the number of records that need to be touched when altering the tree structure?

Can someone tell me is there any way to arrange roots ?

For example, I have model pages that uses multiple trees forstructure:

=============================

HOME (ROOT)

NEWS (ROOT)

ABOUT (ROOT)

PORTFOLIO (ROOT)

— WEBSITES (LEAF)

— APPLICATIONS (LEAF)

SERVICES (ROOT)

=============================

And I would like to put "About" page at the end, and page "Services" above "News" page ?

=============================

ROOT (ROOT)

— HOME (LEAF)

— NEWS (LEAF)

— ABOUT (LEAF)

— PORTFOLIO (LEAF)

— --- WEBSITES (LEAF)

— --- APPLICATIONS (LEAF)

— SERVICES (LEAF)

=============================

I am using this at the moment, but I don’t like to have page “Root” that don’t get any use.

Thanks ;)

Since the moveNode method disallows target to be a root node, I’d guess this is not supported. :(

However, when loading pages, you can easily exclude the root:




$root  = Page::model()->findByPk( $rootId );

$pages = $root->descendants()->findAll();



Thank you Ben for reply! I’ve been doing that already ;)

I just wanted to know if there is a way to not have a "root" page in the table at all.

Thanks anyway ;)

Hi,

Similar to the curiosity raised in this comment on this thread, I’d also be happy to know why there is the ‘root’ and ‘level’ columns in the schema. AFAIK, they are not strictly needed in nested set implementation. Is that for performance enhancement?

I’ve looked at the couple of resources linked in the mentioned comment: the first is a 404 now and the second doesn’t answer the question as well (though I’ve skimmed it).

Thanks!

Boaz.