Giter Site home page Giter Site logo

yii2-nested-sets's Introduction

Nested Sets Behavior for Yii 2

Build Status Code Coverage Packagist Version Total Downloads

A modern nested sets behavior for the Yii framework utilizing the Modified Preorder Tree Traversal algorithm.

Installation

The preferred way to install this extension is through composer.

Either run

$ composer require creocoder/yii2-nested-sets

or add

"creocoder/yii2-nested-sets": "0.9.*"

to the require section of your composer.json file.

Migrations

Run the following command

$ yii migrate/create create_menu_table

Open the /path/to/migrations/m_xxxxxx_xxxxxx_create_menu_table.php file, inside the up() method add the following

$this->createTable('{{%menu}}', [
    'id' => $this->primaryKey(),
    //'tree' => $this->integer()->notNull(),
    'lft' => $this->integer()->notNull(),
    'rgt' => $this->integer()->notNull(),
    'depth' => $this->integer()->notNull(),
    'name' => $this->string()->notNull(),
]);

To use multiple tree mode uncomment tree field.

Configuring

Configure model as follows

use creocoder\nestedsets\NestedSetsBehavior;

class Menu extends \yii\db\ActiveRecord
{
    public function behaviors() {
        return [
            'tree' => [
                'class' => NestedSetsBehavior::className(),
                // 'treeAttribute' => 'tree',
                // 'leftAttribute' => 'lft',
                // 'rightAttribute' => 'rgt',
                // 'depthAttribute' => 'depth',
            ],
        ];
    }

    public function transactions()
    {
        return [
            self::SCENARIO_DEFAULT => self::OP_ALL,
        ];
    }

    public static function find()
    {
        return new MenuQuery(get_called_class());
    }
}

To use multiple tree mode uncomment treeAttribute array key inside behaviors() method.

Configure query class as follows

use creocoder\nestedsets\NestedSetsQueryBehavior;

class MenuQuery extends \yii\db\ActiveQuery
{
    public function behaviors() {
        return [
            NestedSetsQueryBehavior::className(),
        ];
    }
}

Usage

Making a root node

To make a root node

$countries = new Menu(['name' => 'Countries']);
$countries->makeRoot();

The tree will look like this

- Countries

Prepending a node as the first child of another node

To prepend a node as the first child of another node

$russia = new Menu(['name' => 'Russia']);
$russia->prependTo($countries);

The tree will look like this

- Countries
    - Russia

Appending a node as the last child of another node

To append a node as the last child of another node

$australia = new Menu(['name' => 'Australia']);
$australia->appendTo($countries);

The tree will look like this

- Countries
    - Russia
    - Australia

Inserting a node before another node

To insert a node before another node

$newZeeland = new Menu(['name' => 'New Zeeland']);
$newZeeland->insertBefore($australia);

The tree will look like this

- Countries
    - Russia
    - New Zeeland
    - Australia

Inserting a node after another node

To insert a node after another node

$unitedStates = new Menu(['name' => 'United States']);
$unitedStates->insertAfter($australia);

The tree will look like this

- Countries
    - Russia
    - New Zeeland
    - Australia
    - United States

Getting the root nodes

To get all the root nodes

$roots = Menu::find()->roots()->all();

Getting the leaves nodes

To get all the leaves nodes

$leaves = Menu::find()->leaves()->all();

To get all the leaves of a node

$countries = Menu::findOne(['name' => 'Countries']);
$leaves = $countries->leaves()->all();

Getting children of a node

To get all the children of a node

$countries = Menu::findOne(['name' => 'Countries']);
$children = $countries->children()->all();

To get the first level children of a node

$countries = Menu::findOne(['name' => 'Countries']);
$children = $countries->children(1)->all();

Getting parents of a node

To get all the parents of a node

$countries = Menu::findOne(['name' => 'Countries']);
$parents = $countries->parents()->all();

To get the first parent of a node

$countries = Menu::findOne(['name' => 'Countries']);
$parent = $countries->parents(1)->one();

Donating

Support this project and others by creocoder via gratipay.

Support via Gratipay

yii2-nested-sets's People

Contributors

bethrezen avatar creocoder avatar mihai-p avatar nepster-web avatar zhuravljov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

yii2-nested-sets's Issues

Building tree method

Hi, it will be good to add method to build "tree" array with output something like this:

<? $data = [
    ['title' => 'Node 1'],
    ['title' => 'Node 2', 'children' => [
        ['title' => 'Node 2.1'],
        ['title' => 'Node 2.2']
    ]]
];

Does anybody have ready to use snippet? Thanks

Условия при выборке parents и children

Доброго времени!
Скажите, при выборке родительских или дочерних нод как нибудь можно использовать where() ?
Дело в том, что бывает нужно указывать некоторые условия.
Например в случае хлебных крошек для вложенных страниц приходится делать так:

// treeAttribute = false
$breadcrumbs = [];

if($this->parent_id > 0) {// parent_id хранится просто для удобства
    $parents = $this->parents()->all();
    unset($parents[0]);
    foreach($parents as $parent) {
        $breadcrumbs[] = [
            'label' => $parent->title,
            'url' => ['/page/page/view', 'slug' => $parent->slug]
        ];
    }
}

$breadcrumbs[] = $this->title;

return $breadcrumbs;

Спасибо.

Possible memory leak

$_cached values could be cleared only in destructor method, meanwhile behavior might already detached and owner property will be nulled, so get_class($this->owner) values will be wrong. Also, maybe, it is sensible to clear $_cached values with behavior detaching .

Creating a custom tree array

Implementing this is very easy, although using it at a practical level I'm finding difficult.

How would I create a custom nested plain array such as the following:

[
    {
        'id'        => 123,
        'name'      => 'Example',
        'value'     => 'Testing',
        'children'  => [
            {
                'id'        => 123,
                'name'      => 'Example',
                'value'     => 'Testing',
                'children'  => [],
            },
            {
                'id'        => 123,
                'name'      => 'Example',
                'value'     => 'Testing',
                'children'  => [],
            },  
        ]
    },
    {
        'id'        => 123,
        'name'      => 'Example',
        'value'     => 'Testing',
        'children'  => [
            {
                'id'        => 123,
                'name'      => 'Example',
                'value'     => 'Testing',
                'children'  => [            
                {
                    'id'        => 123,
                    'name'      => 'Example',
                    'value'     => 'Testing',
                    'children'  => [],
                }
            },

        ]
    },

];    

External roots (FK to another table)

В мультируте есть такой код

$this->owner->setAttribute($this->treeAttribute, $this->owner->getPrimaryKey());

Который автоматически меняет значение treeAttribute, что делает невозможным использование поля treeAttribute в связи по foreign key с другой таблицей.
Может быть есть возможность разрешить использовать своё значение treeAttribute при создании root?

Fix readme

Problem

Since composer change default stability option this no longer works:
php composer.phar require creocoder/yii2-nested-set-behavior "*"

or add

"creocoder/yii2-nested-set-behavior": "*"

How to fix

php composer.phar require creocoder/yii2-nested-set-behavior "@dev"

or add

"creocoder/yii2-nested-set-behavior": "@dev"

How about allowing to modify the id attribute?

Hey mate, I see you're going a nice way allowing people to modify specific column names. How about allowing to modify and configure the id attribute? For example, I would love to change it to id_xxxxxx to match the rest of the schema.

Cheers,
P.

error in documentation with behavior configuration

Hi. Thank you for the extension. Just want to say you have an error in the documentation. When configuring behavior with parameters we should use array and place it in array of behaviors. So instead of

    public function behaviors() {
        return [
            'class' => NestedSetsBehavior::className(),
            // 'treeAttribute' => 'tree',
            // 'leftAttribute' => 'lft',
            // 'rightAttribute' => 'rgt',
            // 'depthAttribute' => 'depth',
        ];
    }

there should be this

    public function behaviors() {
        return [
            [
                'class' => NestedSetsBehavior::className(),
                // 'treeAttribute' => 'tree',
                // 'leftAttribute' => 'lft',
                // 'rightAttribute' => 'rgt',
                // 'depthAttribute' => 'depth',
            ]
        ];
    }

How to temporarily detach this behaviour?

Hello,
I need to temporarliy disable the behaviour.
In yii1 I used this on the model where the behaviour is attached to:

$model->detachBehavior('nestedSetBehavior');

But this is not working in the updated version. Or is this a yii2 issue?

Thanks for any hints... (again)
gb5256

Error methods(roots, title, parent)

Calling unknown method: yii\db\ActiveQuery::roots()
Getting unknown property: yii\db\ActiveQuery::title
Calling unknown method: yii\db\ActiveQuery::parent()

Configure query class as follows?

Hello,
I am just switching to yii2 from yii1, so it might be that I have a general problem of understanding.
You write "Configure query class as follows". But I do not have a query class.
I am using the advanced yii2 template.
Can you give me a hint where that has to be put?
Thanks,
gb5256

Modification methods wrong overwrite properties in the current model.

moveAsLast and moveAsFirst methods working as expected but after initialization they incorrectly overwrite 'lft' and 'rgt' properties in the current model.
And when I update my model (saveNode()), correct property in the table overwrites whith this wrong data.
There is problem with method correctCachedOnMoveNode(), when I turn it of , methods start working properly.

Installation fail - Invalid version string "LICENSE.md"

I am having an issue for installing this behavior through composer.

$ composer require creocoder/yii2-nested-set-behavior "*"

  [UnexpectedValueException]
  Could not parse version constraint LICENSE.md: Invalid version string "LICE
  NSE.md"

On Many Roots tree, the nested set seem not work

Try the following sample data:

-- Root1
-- -- Branch1
-- -- -- Leaf1
-- Root2

Now move Root2 to Leaf1 then move it back to root once again.

This time, using Tree::find()->roots()->all() will only list Root1 as the root node.

Also the descendants() and ancestors() not working any more.

Any suggestion on fixing this?

Валится структура

Приветствую. Есть ли какая информация как изменить родителя ветви?
Если использовать $category->appendTo($root) то спонтанно валится структура (иногда да иногда нет), и ветвь исчезает. В базе есть но уровень не тот.

Как правильно добавить новую ветвь к указанному родителю и как изменить существующую с переносом на другого родителя?

Update

Are you going to update this? It doens't work at all

add support for deleting a root node

I got this error with 0.9.0

Method "dmstr\modules\pages\models\Tree::delete" is not supported for deleting root node.
Stack trace:
#0 [internal function]: creocoder\nestedsets\NestedSetsBehavior-&gt;beforeDelete(Object(yii\base\ModelEvent))
#1 /app/vendor/yiisoft/yii2/base/Component.php(538): call_user_func(Array, Object(yii\base\ModelEvent))
....

Is there a special reason why this is denied?

Widget

Здравствуйте. Было бы здорово, если бы вы добавили старый пример в доки:

$categories = Category::find()->addOrderBy('lft')->all();
$level = 0;

foreach ($categories as $n => $category)
{
    if ($category->level == $level) {
        echo Html::endTag('li') . "\n";
    } elseif ($category->level > $level) {
        echo Html::beginTag('ul') . "\n";
    } else {
        echo Html::endTag('li') . "\n";

        for ($i = $level - $category->level; $i; $i--) {
            echo Html::endTag('ul') . "\n";
            echo Html::endTag('li') . "\n";
        }
    }

    echo Html::beginTag('li');
    echo Html::encode($category->title);
    $level = $category->level;
}

for ($i = $level; $i; $i--) {
    echo Html::endTag('li') . "\n";
    echo Html::endTag('ul') . "\n";
}

Cant Save My Root Node

$root = new NestedMenuTree();
$root->title = Sanitizer::getSanitizedUrlValue($model->title);
if($root->saveNode()){
VarDumper::dump($root->attributes,10,true);
}else{
VarDumper::dump($root->getErrors(),10,true);
};

Thats my try result:

[
'root' => [
0 => 'Root cannot be blank.'
]
'lft' => [
0 => 'Lft cannot be blank.'
]
'rgt' => [
0 => 'Rgt cannot be blank.'
]
'level' => [
0 => 'Level cannot be blank.'
]
]

? i missunderstand anything ? your behavior will create it or not ?

Is Separate Query Behavior Really Necessary?

I'm just wondering why a separate Query behavior is needed when you could simply add those functions to the main AR behavior and just use that without having to extend ActiveQuery and add a second behavior?
e.g move roots function to NestedSetsBehavior (would be similar for leaves() function too) ...

    /**
     * Gets root node(s).
     * @return ActiveQuery.
     */
    public function roots()
    {
        $query = $this->owner->find();

        $query->andWhere([$this->leftAttribute => 1])
              ->addOrderBy([$this->owner->primaryKey()[0] => SORT_ASC]);

        return $query;
    }

Am I wrong in thinking this?

Upgrading from yii1 to yii2 nested set behaviour

Hello,
I have an app that I move into yii2. I am already using nested set (for yii1).
In your old version, tree was called root, and also my table is set up this way.
I do not want to change the table, as lots of other things need the column root.
I thought, that changing this would do the trick:

public function behaviors() {
    return [
        'class' => NestedSetsBehavior::className(),
              'treeAttribute' => 'root', \\ this is 'tree' by your default
            // 'leftAttribute' => 'lft',
            // 'rightAttribute' => 'rgt',
             'depthAttribute' => 'level',
    ];
}

But this throws an error: Class root does not exist

Do I have to change the attribute name some where else?
thanks again,
gb5256

Can not create a node when the target node is root.

How to make sibling between a root models?

$model = new Category();
$model->name = 'new category';
$after = $this->findModel(1);
$model->insertAfter($after);

it will throw

throw new Exception('Can not create a node when the target node is root.');

Add some tags

Could you add some tags to your projects? I will be very greatful. After the one of your latest commits I had to change my code, becouse there was no compatibilty.

P.S. thanks for yours project. It's great.

How to move nodes?

In Yii 1.x nested sets behavior there was methods for moving nodes inside tree, ex. moveAsLast(), moveAsFirst(). How to implement these methods in your Yii 2.x behavior?

Multiple addition to $_cached.

Owner added to $_cached first time after attaching behavior (ensureBevaviors calls $behavior->attach) and second time by processing aterFind event. Is it correct? There is another issue #19 - may be it's caused by second cache correction?

Possible `NestedSet::correctCachedOnMoveBetweenTrees()` bug

Была такая иерархия:

- Радио(id=3,lft=1, rgt=2, lvl=1, root=3)
- Авто (id=4,lft=1, rgt=2, lvl=1, root=4)

Хочу такую:

- Авто (id=4,lft=1, rgt=4, lvl=1, root=4)
- - Радио(id=3,lft=2, rgt=3, lvl=2, root=4)

Но получаю вообще:

- Авто (id=4,lft=1, rgt=6, lvl=1, root=4)
- - Радио(id=3,lft=4, rgt=5, lvl=2, root=4)

После обновления страницы всё норм становится. Перемещаю методом $model->moveAsLast($parent).
Кусок кода из контроллера

$save = $model->load(Yii::$app->request->post()) && $model->saveNode() !== false;

if ( ($parent = Yii::$app->request->post('Category', ['parent'=>false])['parent']) || $parent!==false ) {
    $transaction = Yii::$app->getDb()->beginTransaction();
    if ($save && $parent > 0) {
        $parent = $this->findModel($parent);
        $save = $model->moveAsLast($parent); //Здесь эта странность
    } elseif (!$model->isRoot()) {
        $save = $model->isNewRecord ? $model->makeRoot() : $model->moveAsRoot();
    }
    if ($save)
        $transaction->commit();
    else
        $transaction->rollBack();
}

appendTo does not properly update lft/rgt

I use the following code to generate a categories tree out of a nested array. The code is updated to work with the latest version of nesteset. However when it runs all the lft/rgt columns appear to be wrong. With the prev. version (before the major update - renaming of classes, etc) this was not the case.

Is this a known bug?

(example code)

    /**
     * @param $tree
     * @param null $parentNode
     * @throws Exception
     * @throws \yii\base\ErrorException
     */
    protected function createTree($tree, $parentNode = null)
    {
        foreach($tree as $node) {
            $c = new Category();
            $c->name = $node['name'];

            if ($parentNode)
                $success = $c->appendTo($parentNode);
            else
                $success = $c->makeRoot();

            if (!$success)
                throw new Exception("Could not save node!\n".VarDumper::dumpAsString($c->errors));

            if (isset($node['items'])) {
                $this->createTree($node['items'], $c);
            }
        }
    }

    /**
     * This is a console action used to initialise the menu for the first time.
     * @throws Exception
     */
    public function actionReset()
    {
        $categoriesTree = [
            [
                'name' => 'Categories Demo',
                'url_key' => null,
                'items' => [
                    [
                        'name' => 'Eat and Drink',
                        'items' => [
                            [
                                'name' => 'Cafe',
                            ],
                            [
                                'name' => 'Bar',
                            ],
                            [
                                'name' => 'Restaurant',
                            ],
                            [
                                'name' => 'Fast Food',
                            ],
                        ],
                    ],
                    [
                        'name' => 'Sport',
                        'items' => [
                            [
                                'name' => 'Sportbar',
                            ],
                            [
                                'name' => 'Fitness',
                            ],
                            [
                                'name' => 'Bowling',
                            ],
                        ],
                    ],
                    [
                        'name' => 'Games',
                        'items' => [
                            [
                                'name' => 'Internet Cafe',
                            ],
                            [
                                'name' => 'Video Game Centre',
                            ],
                        ],
                    ],
                ],
            ],
        ];

        Category::deleteAll();
        $this->createTree($categoriesTree);
    }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.