Yii Framework Forum: Nested set - Yii Framework Forum

Jump to content

  • (4 Pages)
  • +
  • 1
  • 2
  • 3
  • 4
  • You cannot start a new topic
  • You cannot reply to this topic

Nested set Nested set behavior for AR models Rate Topic: ***** 5 Votes

#21 User is offline   mech7 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 220
  • Joined: 26-March 09

Posted 27 July 2010 - 03:05 AM

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 :)
0

#22 User is offline   samdark 

  • Having fun
  • Yii
  • Group: Yii Dev Team
  • Posts: 3,745
  • Joined: 17-January 09
  • Location:Russia

Posted 27 July 2010 - 03:14 AM

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?
Yii 1.1 Application Development Cookbook

Enjoying Yii? Star us at github: 1.1 and 2.0.
0

#23 User is offline   mech7 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 220
  • Joined: 26-March 09

Posted 27 July 2010 - 03:28 AM

View Postsamdark, on 27 July 2010 - 03:14 AM, said:

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 :D 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 :)
0

#24 User is offline   mech7 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 220
  • Joined: 26-March 09

Posted 27 July 2010 - 03:56 AM

also.. if i try to do: $save = $model->moveAsFirst($root); it seems to corrupt my table putting left and right in the negative
0

#25 User is offline   samdark 

  • Having fun
  • Yii
  • Group: Yii Dev Team
  • Posts: 3,745
  • Joined: 17-January 09
  • Location:Russia

Posted 27 July 2010 - 04:14 AM

Hmm. Could you provide some code + DB values (4—5 nodes) before and after moving to root? It will help.
Yii 1.1 Application Development Cookbook

Enjoying Yii? Star us at github: 1.1 and 2.0.
0

#26 User is offline   Extak 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 4
  • Joined: 02-February 10
  • Location:Brno, Czech Republic

Posted 08 December 2010 - 05:10 AM

How can i create root node, when many roots mode is off?
0

#27 User is offline   Ben 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 273
  • Joined: 15-March 09

Posted 29 December 2010 - 01:10 PM

Yeah, some more examples would be great. In order to create a root node (guess I need one? Even in "single root" mode?), I tried:

$root = new Menu();
// $root->lft = 1;
// $root->rgt = 2;
// $root->level = 1;
$root->text = 'root';

$root->saveNode( false );


Both with the commented attributes set and unset. I feel this implementation detail should be hidden, I didn't even know if the counters start at zero or one until I read some comments in this thread, and maybe it is hidden and I don't have to care about it. Just still didn't find out.
In the end, this always throws an exception "Many roots mode is off". And since it's a new record and since I can't save the AR using CActiveRecord::save, I'm feeling kind of lost here.

In the end, I created the root node directly in the DB (lft:1, rgt:2, level: 1, root: null) and managed to insert a node using the following snippet of code:


$root = Menu::model()->roots()->find();

if ($root instanceof CActiveRecord)
{
  $help = new Menu();
  $help->text = 'Help';
  $appended = $help->appendTo( $root, false );
}


Maybe this helps others to get started.

Is there a chance to get some small code snippets for each of the provided API methods? For example it seems to me as if the saveNode method is useless in single root mode, but maybe I'm using it the wrong way.
Don't like ads in my sig...
1

#28 User is offline   Ben 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 273
  • Joined: 15-March 09

Posted 04 January 2011 - 03:38 PM

Another quick snippet:

  protected function createDefaultMenuItems()
  {
    $root = Menu::model()->roots()->find();
    if (!$root instanceof CActiveRecord)
      return;

    $help = new Menu();
    $help->text = 'Help';
    $help->enabled = true;
    $appended = $help->appendTo( $root, false );

    $about = new Menu();
    $about->text = 'About';
    $about->enabled = true;
    $about->appendTo( $help, false );

    // --- test data - START --------------------------------------------------

    $extensions = new Menu();
    $extensions->text = 'Extensions';
    $extensions->enabled = true;
    $extensions->insertBefore( $help, false );

    $install = new Menu();
    $install->text = 'Install';
    $install->enabled = false;
    $install->appendTo( $extensions, false );

    $uninstall = new Menu();
    $uninstall->text = 'Uninstall';
    $uninstall->enabled = false;
    $uninstall->insertAfter( $install, false );

    $separator = new Menu();
    $separator->text = '-';
    $separator->enabled = false;
    $separator->insertAfter( $uninstall, false );

    $hasChildren = new Menu();
    $hasChildren->text = 'Has children';
    $hasChildren->enabled = true;
    $hasChildren->insertAfter( $separator, false );

    $child1 = new Menu();
    $child1->text = 'Child 1';
    $child1->enabled = false;
    $child1->appendTo( $hasChildren, false );

    $child2 = new Menu();
    $child2->text = 'Child 2';
    $child2->enabled = false;
    $child2->insertAfter( $child1, false );

    // --- test data - END --------------------------------------------------

  }



In words:

  • Use "appendTo( parent, false )" for the first child you add to a parent
  • Use "insertAfter( sibling, false )" for subsequent children you want to add to a group



I tried to build up my groups using:

$child1->appendTo( $parent, false );
$child2->appendTo( $parent, false );


before, but every appendTo inserted the item as first child of parent which wasn't what I'd expected.
Don't like ads in my sig...
1

#29 User is offline   hk1 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 5
  • Joined: 09-January 11

Posted 10 January 2011 - 11:47 AM

I'm trying to understand what values are stored in the 'root' field and the 'level' field. I wouldn't need to know this except that I'm building a backoffice administration app that we'll most likely use to configure our categories. I want to make sure I use the correct logic so that this extension will work if and when I want to use it.
0

#30 User is offline   samdark 

  • Having fun
  • Yii
  • Group: Yii Dev Team
  • Posts: 3,745
  • Joined: 17-January 09
  • Location:Russia

Posted 10 January 2011 - 03:22 PM

Here is the test that will probably help you: http://code.google.c...ehaviorTest.php

Also there is an article: http://en.wikipedia....ested_set_model
Yii 1.1 Application Development Cookbook

Enjoying Yii? Star us at github: 1.1 and 2.0.
0

#31 User is offline   szako 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 56
  • Joined: 17-May 10

Posted 03 February 2011 - 06:33 AM

Hey

First of all, Thank you for implementing nested tree!

I'm facing a problem atm. The situation:

I'm making a webshop with nested categories, using the nested tree. When someone deletes a category, the program seeks for products assigned to that category and its subcategories, if found, the category must be not deleted. So I run the event beforeDelete. This works like a charm when the category is a leaf, but when it has subcategories the event doesn't get raised because of deleteAll() used in nestedtree's delete() method.

I think I will manually raise the event in the controller before deleting (if the category is not a leaf) and lean on the event's return. What do you think? Anyone had a similar case?
-------------
m(o_O)m
0

#32 User is offline   samdark 

  • Having fun
  • Yii
  • Group: Yii Dev Team
  • Posts: 3,745
  • Joined: 17-January 09
  • Location:Russia

Posted 03 February 2011 - 07:10 AM

Manually raising event is the proper solution. At least for now.
Yii 1.1 Application Development Cookbook

Enjoying Yii? Star us at github: 1.1 and 2.0.
0

#33 User is offline   szako 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 56
  • Joined: 17-May 10

Posted 03 February 2011 - 07:21 AM

Hey samdark, thanks for the reply!

The thing is done, tho I had to modify the extension.
I had to implement a new method into it:

        public function setIgnoreEvent($ignoreEvent)
        {
            $this->_ignoreEvent=$ignoreEvent;
        }


I did this because in the behavior beforeDelete() method checks for _ignoreEvent and if it is false, the event raising stops. What do you think? I think you should include this into the class.

So the code looks like:

                $toDelete = true;
                $category->tree->setIgnoreEvent(true);
                if (!$category->beforeDelete()) {
                    $toDelete = false;
                }
                $category->tree->setIgnoreEvent(false);

                if ($toDelete && $category->tree->deleteNode()) {
                    $this->messages[] = 'Sikeresen letöröltem a kategóriát.';
                } else {
                    $this->messages['hasError'] = 1;
                    $this->messages[] = 'Nem tudtam letörölni a kategóriát!';
                    foreach ($category->getErrors() as $field => $error) {
                        $this->messages[] = $error[0];
                    }
                }

-------------
m(o_O)m
0

#34 User is offline   samdark 

  • Having fun
  • Yii
  • Group: Yii Dev Team
  • Posts: 3,745
  • Joined: 17-January 09
  • Location:Russia

Posted 03 February 2011 - 08:07 AM

Maybe using the following will be less hacky:

if(!$node->isLeaf)
{
    foreach($node->descendants() as $d)
        if($d->thereAreProducts())
            break;
}

Yii 1.1 Application Development Cookbook

Enjoying Yii? Star us at github: 1.1 and 2.0.
0

#35 User is offline   szako 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 56
  • Joined: 17-May 10

Posted 03 February 2011 - 09:01 AM

I put the checking into one method:

        public function categoryHasProducts($mainCat)
        {            
            $command = Yii::app()->db->createCommand()
                        ->select('COUNT(id)')
                        ->from('{{product_mobiltelefon}}');
            
            if ($mainCat->isLeaf()) {    
                $command->where('category_id = '.$mainCat->id);
            } else {
                $subCategories = $mainCat->descendants()->findAll(array('index' => 'id', 'select' => 'id'));
                $command->where('category_id IN ('.implode(',', array_merge(array($mainCat->id), array_keys($subCategories))).')');
            }
            
            return $command->queryScalar();
        }


So I have to call it only once on the category. No problem, I'm gonna put the method into the new nestedtree releases as well.

Thanks!
-------------
m(o_O)m
0

#36 User is offline   samdark 

  • Having fun
  • Yii
  • Group: Yii Dev Team
  • Posts: 3,745
  • Joined: 17-January 09
  • Location:Russia

Posted 03 February 2011 - 10:09 AM

Probably it's better to put it into the model itself if it's used from controller.
Yii 1.1 Application Development Cookbook

Enjoying Yii? Star us at github: 1.1 and 2.0.
0

#37 User is offline   szako 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 56
  • Joined: 17-May 10

Posted 03 February 2011 - 11:12 AM

It is already in the (product) model. The event handler calls it:

        public static function handleProductCategoryDelete(&$event)
        {
            $productCount = self::categoryHasProducts($event->sender);

            if ((int)$productCount > 0) {
                $event->message = "A kategória tartalmaz {$productCount} db terméket, ezeket le kell először törölni.";
            }
        }

-------------
m(o_O)m
0

#38 User is offline   WallTearer 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 32
  • Joined: 05-November 10
  • Location:Kiev, Troeschina

Posted 04 March 2011 - 12:35 PM

Hey, thanks for the ext, it's very usefull, especially with ENestedSetBehavior2 improvement, that is suggested in comments to nestedsetbehavior extension page.

But I'd like to ask you, if there is any chance that you will add the support for `parent_id` field? Or I'll (and other people who we'll need it) have to add it manualy?
Thanks.
0

#39 User is offline   samdark 

  • Having fun
  • Yii
  • Group: Yii Dev Team
  • Posts: 3,745
  • Joined: 17-January 09
  • Location:Russia

Posted 04 March 2011 - 12:49 PM

Well, parent_id isn't really nested set related so don't know if it should be included.
Yii 1.1 Application Development Cookbook

Enjoying Yii? Star us at github: 1.1 and 2.0.
0

#40 User is offline   WallTearer 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 32
  • Joined: 05-November 10
  • Location:Kiev, Troeschina

Posted 04 March 2011 - 04:20 PM

Meanwhile, I solved this problem by extending model in this way:
<?php
class Communicate extends CActiveRecord
{
    // Will be storing parent id here
    public $parent_id;

    /**
     * Communicate with id of its parent.
     * Id of parent will be stored in ->parent_id
     */
    public function withParent()
    {
        $owner=$this->getOwner();
        $db=$owner->getDbConnection();
        $criteria = $owner->getDbCriteria();

        $criteria->select .= ', `parent`.ID as `parent_id`';

        $select =
            ' SELECT * from ' . $db->quoteColumnName($owner->tableName()) .
            ' WHERE ' . $db->quoteColumnName($owner->tableName()) . '.ROOT = ' . $this->ROOT .
            ' ORDER BY ' . $db->quoteColumnName($owner->tableName()) . '.LFT DESC';

        $criteria->join .= 
            'LEFT JOIN ('.$select.') `parent` ' .
            'ON (`parent`.LFT < `t`.LFT AND `parent`.RGT > `t`.RGT)'; 

        $criteria->group = '`t`.ID';

        return $owner;
    }

    /**
     * Need to populate extended data
     */
    public function populateRecord($attributes,$callAfterFind=true)
    {
        $record = parent::populateRecord($attributes, $callAfterFind);

        if ( $record )
        {
            //getting parent id if it's set
            if ( isset($attributes['parent_id']) )
                $record->parent_id = $attributes['parent_id'];
        }

        return $record;
    }

}

// And I'm triggering this like:
Communicate::model()->withParent()->findAll();

Maybe this will help somebody, cause getting parent ids of more than one item is not trivial.
Possibly, it's not the best solution, but works for me.

Anyway, thanks a lot for this extension, saved a lot of time.
0

Share this topic:


  • (4 Pages)
  • +
  • 1
  • 2
  • 3
  • 4
  • You cannot start a new topic
  • You cannot reply to this topic

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users