Skip to content

Commit 5d44bcd

Browse files
committed
[ADD] Nested Menu (unlimited levels).
1 parent 9097768 commit 5d44bcd

File tree

3 files changed

+114
-2
lines changed

3 files changed

+114
-2
lines changed

docs/pages/use-in-blade.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,53 @@ Output in the Blade template
120120
@endif
121121
```
122122

123+
### Multi-level menu (tree)
124+
125+
If you need a nested menu (unlimited levels), get all menu items in one query and then convert the collection to a tree.
126+
127+
Data preparation in BaseController.php
128+
```php
129+
use Seiger\sLang\Models\sLangContent;
130+
131+
...
132+
133+
public function globalElements()
134+
{
135+
$this->data['mainMenu'] = sLangContent::withTVs(['menu_main'])
136+
->where('hidemenu', 0)
137+
->whereTv('menu_main', 1)
138+
->orderBy('parent_id')
139+
->orderBy('menuindex')
140+
->active()
141+
->get()
142+
->toTreeParent(0); // return only parent=0 nodes, with nested children
143+
}
144+
```
145+
146+
Recursive output in Blade (example with partial)
147+
148+
`views/partials/menu-tree.blade.php`
149+
```php
150+
<ul>
151+
@foreach($items as $item)
152+
<li class="{% raw %}{{$item->id == evo()->documentObject['id'] ? 'active' : ''}}{% endraw %}">
153+
<a href="{% raw %}{{$item->full_link}}{% endraw %}">{% raw %}{{$item->menutitle}}{% endraw %}</a>
154+
155+
@if($item->children && $item->children->isNotEmpty())
156+
@include('partials.menu-tree', ['items' => $item->children])
157+
@endif
158+
</li>
159+
@endforeach
160+
</ul>
161+
```
162+
163+
Call it from your layout/template:
164+
```php
165+
@if($mainMenu && $mainMenu->isNotEmpty())
166+
@include('partials.menu-tree', ['items' => $mainMenu])
167+
@endif
168+
```
169+
123170
## TV variables
124171

125172
The `withTVs()` scope makes it easy to retrieve TV parameters associated with a resource. For example, the **tv_image** parameter.

src/Models/sLangContent.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use Illuminate\Database\Eloquent;
55
use Illuminate\Database\Eloquent\Builder;
66
use Illuminate\Support\Facades\DB;
7+
use Seiger\sLang\Support\TreeCollection;
78

89
class sLangContent extends Eloquent\Model
910
{
@@ -87,7 +88,7 @@ public function scopeActive($query)
8788
/**
8889
* Performs a search on the query based on the search term.
8990
*
90-
* @return \Illuminate\Database\Query\Builder|null The modified query builder instance or null if no search term is provided
91+
* @return \Illuminate\Support\HigherOrderWhenProxy|sLangContent The modified query builder instance or null if no search term is provided
9192
*/
9293
public function scopeSearch()
9394
{
@@ -270,6 +271,10 @@ protected function applyContentSelects(Builder $query): Builder
270271
}
271272

272273
return $query->addSelect(
274+
'site_content.parent as parent_id',
275+
'site_content.menuindex as menuindex',
276+
'site_content.hidemenu as hidemenu',
277+
'site_content.isfolder as isfolder',
273278
'site_content.pagetitle as pagetitle_orig',
274279
'site_content.longtitle as longtitle_orig',
275280
'site_content.description as description_orig',
@@ -403,4 +408,15 @@ protected static function booted(): void
403408
$builder->where($model->getTable() . '.lang', '=', $locale);
404409
});
405410
}
406-
}
411+
412+
/**
413+
* Create a new Eloquent Collection instance.
414+
*
415+
* @param array $models
416+
* @return \Illuminate\Database\Eloquent\Collection
417+
*/
418+
public function newCollection(array $models = [])
419+
{
420+
return new TreeCollection($models);
421+
}
422+
}

src/Support/TreeCollection.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php namespace Seiger\sLang\Support;
2+
3+
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
4+
use Illuminate\Database\Eloquent\Model;
5+
6+
class TreeCollection extends EloquentCollection
7+
{
8+
public const CHILDREN_RELATION_NAME = 'children';
9+
10+
public function toTree(): static
11+
{
12+
return $this->toTreeParent(null);
13+
}
14+
15+
public function toTreeParent($parent = null): static
16+
{
17+
/** @var array<int|string, Model> $byId */
18+
$byId = [];
19+
$tops = [];
20+
21+
foreach ($this->items as $item) {
22+
if ($item instanceof Model) {
23+
$item->setRelation(static::CHILDREN_RELATION_NAME, new static());
24+
$byId[$item->getKey()] = $item;
25+
}
26+
}
27+
28+
foreach ($this->items as $item) {
29+
if (!$item instanceof Model) {
30+
continue;
31+
}
32+
33+
$parentId = $item->getAttribute('parent_id');
34+
35+
if ($parentId !== null && array_key_exists($parentId, $byId)) {
36+
/** @var Model $parentItem */
37+
$parentItem = $byId[$parentId];
38+
$parentItem->getRelation(static::CHILDREN_RELATION_NAME)->push($item);
39+
continue;
40+
}
41+
42+
if ($parent === null || (string)$parentId === (string)$parent) {
43+
$tops[] = $item;
44+
}
45+
}
46+
47+
return new static($tops);
48+
}
49+
}

0 commit comments

Comments
 (0)