And finally (for today
); very minor, but I found it made more sense in my code to have the level starting at zero rather one. This way when using foreach($nodes as $level=>$node) at the top of the tree $level == $node->level.
And finally (for today
); very minor, but I found it made more sense in my code to have the level starting at zero rather one. This way when using foreach($nodes as $level=>$node) at the top of the tree $level == $node->level.
There is a new version in SVN. Will be released as archive soon.
changelog
— Unit tests added (creocoder)
— Incorrect usage of save() and delete() instead of behavior’s saveNode() and deleteNode() is now detected (creocoder)
Yeti
Please try to reproduce your issue with DB error.
getDescendants looks like presentational method. I think it should not be included into behavior itself. Maybe a widget will fit.
Level starting at zero isn’t better in any way plus most implementations are using level started at one so it will be easier to migrate when needed.
to reproduce just remove the change above then call the saveNode() method with $manyRoots true.
Tracing through what is going on is:
The schema supplied declares root as NOT NULL.
the makeRoot() method does not (as supplied) set root to any value before inserting the record, i.e. root for the record is NULL when it is saved.
To prevent the error root needs to be set to something prior to saving the record (zero made sense to me for a new root node and the behavior I think is the right place to do it), or the schema needs to allow root to be NULL; of the two setting root to a value for me seems the better solution.
We might be getting into semantics; getDescendants returns the data in a particular format which of itself it is not presentational. Though as the example shows the view may become simpler; kind of depends what you are doing in the view I guess.
Whether it goes in your behavior is of course your call; for those that want to use it perhaps a widget or a class extending ENestedSetBehavior.
Said it was minor ![]()
Fixed SQL schema.
hey, guys!
first of all, Samdark, thanks for this extension!
I have a little doubt: how do you create a CRUD for this kind of table?
I mean, when you crud the model, all you get is a form where you have to manually fill the root, left, right and level fields…this is not that easy imho and i’m sure you guys have solved this more elegantly
could you please share your experience on how to crud this model?
any help is very appreciated
![]()
regards!
scoob.junior
Creocoder implemented it. I’m just tested it and wrote documentation.
To manage tree (taxonomy) I’m personally using this one: http://www.yiiframework.com/forum/index.php?/topic/9434-extension-ejnestedtreeactions/
thank you very much, Samdark, i’m testing this right now…facing some troubles in rendering but have already posted in the EJNestedTreeActions forum topic
regards!!
![]()
I am trying somoething very simple like…
$root=Categories::model()->roots()->findByPk(1);
$cat = new Categories();
$cat->name = 'Cool';
$root->append($cat);
$root->save();
// Does nothing too <img src='http://www.yiiframework.com/forum/public/style_emoticons/default/sad.gif' class='bbc_emoticon' alt=':(' />
$cat->save();
Or…
$cat = new Categories();
$cat->name = 'Cool';
$cat->appendTo($root);
$cat->save();
But having no luck nothing get’s saved
Could you please add some documentation how to actually add / modify / delete nodes ![]()
Also please add to the documentation what values a root needs left: 1, right:0, root: (primary id) Else it would throw some sql errors…
Ah sorry… I had validation in my model on lft / rgt / lvl… But the behavior runs after validation.
And I notice the root needst left = 1 / rgt = 2 else it will move the root to child ![]()
Any reason why there is not a addRoot method ?
Also it be nice to have some convience method to have the tree return in an multi dimensional associative array… As far as I can see now descendants()->findAll() just returns all records in a flat array.
Also how to get the the path? Like I select a category but want to get the entire path all the way up, usefull for breadcrumbs and filtering in categories.
like…
SELECT parent.name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.name = 'FLASH'
ORDER BY parent.lft;
Also I see now that my first error was because validation is called before the makeRoot function… maybe better to call this after the makeRoot so the model can still have validation rules. And not have to call validate = false
Ok i needed this badly so i added my own function… but if anybody can show how to do with activerecord plz do ![]()
/**
* Returns the path of the node
* @return array
*/
public function getPath()
{
$owner = $this->getOwner();
$alias = $owner->getTableAlias();
$pk = $owner->primaryKey;
$tbl_select = "";
$total = count($owner->tableSchema->columns);
$i = 0;
foreach($owner->tableSchema->columns as $column => $value){
$i++;
$tbl_select .= "parent.".$column;
if($i < $total){
$tbl_select .= ", ";
}
}
$sql = "SELECT $tbl_select
FROM ".$owner->tableSchema->name." AS node,
".$owner->tableSchema->name." AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.".$owner->tableSchema->primaryKey." = ".$pk."
AND parent.".$this->level." > 0 ";
if($this->hasManyRoots){
$sql .= "AND parent.".$this->root." = ".$owner->{$this->root}." ";
}
$sql .= " ORDER BY parent.lft;";
$command = Yii::app()->db->createCommand($sql);
$result = $command->queryAll();
return $result;
}
One more thing… I think tables now are not being locked when making modifications?
Or is the transaction enough?
Transactions should be enough but if you’ll get a problem with data, let me know.
Btw how to move the node to a new parent? I only see there is moveBefore / moveAsFirst and append does not seem to work ![]()
And why if I want to use the move methods there are restrictions that the node cannot be a descendant? If I want to move a child to top cannot ?
One more thing how to use the delete function? If just get the model instance and delete it will not be called… and will not remove the descendants. Shouldnt it use the beforeDelete event?
Because if i change it too:
/**
* Deletes node and it's descendants.
* @return boolean whether the deletion is successful.
*/
public function beforeDelete($event) {
parent::beforeDelete($event);
$owner=$this->getOwner();
if($owner->getIsNewRecord())
throw new CDbException(Yii::t('yiiext','The node cannot be deleted because it is new.'));
$transaction=$owner->getDbConnection()->beginTransaction();
try
{
$root=$this->hasManyRoots ? $owner->{$this->root} : null;
if($owner->isLeaf())
$result=$owner->delete();
else
{
$condition=$this->left.'>='.$owner->{$this->left}.' AND '.
$this->right.'<='.$owner->{$this->right};
if($root!==null)
$condition.=' AND '.$this->root.'='.$root;
$result=$owner->deleteAll($condition)>0;
}
if($result)
{
$first=$owner->{$this->right}+1;
$delta=$owner->{$this->left}-$owner->{$this->right}-1;
$this->shiftLeftRight($first,$delta,$root);
$transaction->commit();
return true;
}
}
catch(Exception $e)
{
$transaction->rollBack();
}
return false;
}
It will work ![]()
Latest version supports both moving to the same root and moving to another root. moveBefore / moveAsFirst should work fine.
For structure like this:
R
N1
N2
N3
You can’t move N1 to N2 or N3.
What error have you got?
Ok yes i see now it works
the naming confused me… i though this method id only to move and existing child to the first position ![]()
But it would be nicer if a parent could turn into a child… for example if I have a drag and drop tree it can go any direction ![]()
also… if i try to do: $save = $model->moveAsFirst($root); it seems to corrupt my table putting left and right in the negative
Hmm. Could you provide some code + DB values (4—5 nodes) before and after moving to root? It will help.
How can i create root node, when many roots mode is off?