Skip to content

Commit

Permalink
Add breadcrumbs
Browse files Browse the repository at this point in the history
MGatner committed Apr 23, 2021

Verified

This commit was signed with the committer’s verified signature.
MGatner MGatner
1 parent 11ad4b7 commit 0074dca
Showing 7 changed files with 377 additions and 0 deletions.
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -130,3 +130,44 @@ class MainMenuFilter extends MenusFilter
}
}
```

## Packaged Menus

`Menus` comes with some pre-made menus which can be used immediately or built on to create
your own variants. All menus are in the `Tatter\Menus\Menus` namespace and extend the `Menu`
class so can be used with the Filter or as any other menu you would make.

### Breadcrumbs

The `BreadcrumbsMenu` is a special menu, using horizontal-style navigation links for nested
content. This menu comes pre-styled for [Bootstrap](https://getbootstrap.com/docs/4.3/components/breadcrumb/)
and defaults to the segments retrieved from the framework's `IncomingRequest::$uri`, but
you may provide your own using the static methods `set`, `get`, `push`, and `pop`. Additionally,
`BreadcrumbsMenu::discover()` will attempt to create a default menu. All these methods use
the `Breadcrumb` class, a simple wrapper for the URL and display value.
For example:
```
use Tatter\Menus\Breadcrumb;
use Tatter\Menus\Menus\BreadcrumbsMenu;
class Users extends Controller
{
public function show(int $userId)
{
// Get the User
$user = model('UserModel')->find($userId);
// Start with the default breadcrumbs
BreadcrumbsMenu::discover();
// Pop off the numeric last segment
BreadcrumbsMenu::pop();
// Replace it with the user's name
BreadcrumbsMenu::push(new Breadcrumb(current_url(), $user->name));
return view('users/show', ['user' => $user]);
}
}
```
... if you have the filter in place the rest is handled for you.
44 changes: 44 additions & 0 deletions src/Breadcrumb.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php namespace Tatter\Menus;

use CodeIgniter\HTTP\URI;

/**
* Breadcrumb Class
*
* Represents a single item
* in a breadcrumb menu.
*/
class Breadcrumb
{
/**
* The URL.
*
* @var string
*/
public $url;

/**
* The display value.
*
* @var string
*/
public $display;

/**
* @param string $url
* @param string|null $display
*/
public function __construct(string $url, string $display = null)
{
// If no display was passed then make a best guess
if (empty($display))
{
$uri = new URI($url);
$segments = $uri->getSegments();
$display = ucfirst(end($segments) ?: lang('Menus.home'));
}

$this->url = $url;
$this->display = $display;
}
}
5 changes: 5 additions & 0 deletions src/Language/en/Menus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

return [
'home' => 'Home',
];
130 changes: 130 additions & 0 deletions src/Menus/BreadcrumbsMenu.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php namespace Tatter\Menus\Menus;

use Config\Services;
use CodeIgniter\HTTP\URI;
use Tatter\Menus\Breadcrumb;
use Tatter\Menus\Menu;
use Tatter\Menus\Traits\BreadcrumbsStyle;

/**
* Breadcrumbs Menu
*
* Generates a BootStrap-style series
* of links for nested content.
*
* This class assumes some very basic
* yet specific application structure
* and should often be used only as
* a guide for implementing your own.
*/
class BreadcrumbsMenu extends Menu
{
use BreadcrumbsStyle;

/**
* Array of the Breadcrumbs.
*
* @var Breadcrumb[]|null
*/
protected static $breadcrumbs;

/**
* Sets the crumbs based on the
* current/provided URI segments.
*
* @param URI|null $uri
*
* @return Breadcrumb[] The discovered crumbs
*/
public static function discover(URI $uri = null): array
{
$uri = $uri ?? Services::request()->uri;

// Always start with the base URL
$breadcrumbs = [
new Breadcrumb(base_url(), lang('Menus.home')),
];

// Add each segment
foreach ($uri->getSegments() as $segment)
{
$breadcrumbs[] = new Breadcrumb(site_url($segment));
}
self::set($breadcrumbs);

return self::get();
}

/**
* Returns the currently-configured crumbs.
*
* @return Breadcrumb[]|null
*/
public static function get(): ?array
{
return self::$breadcrumbs;
}

/**
* Sets the crumbs used to build the Menu.
*
* @param Breadcrumb[]|null $breadcrumbs
*/
public static function set(?array $breadcrumbs)
{
self::$breadcrumbs = $breadcrumbs;
}

/**
* Removes and returns the last crumb.
*
* @return Breadcrumb
*/
public static function pop(): ?Breadcrumb
{
return is_null(self::$breadcrumbs) ? null : array_pop(self::$breadcrumbs);
}

/**
* Adds a new Breadcrumb to the Menu.
*
* @param Breadcrumb $breadcrumb
*
* @return int New number of items in the Menu
*/
public static function push(Breadcrumb $breadcrumb): int
{
self::$breadcrumbs = self::$breadcrumbs ?? [];

return array_push(self::$breadcrumbs, $breadcrumb);
}

//--------------------------------------------------------------------

/**
* Builds the Menu and returns the
* rendered HTML string.
*
* @return string
*/
public function __toString(): string
{
// If no breadcrumbs are set then initiate discovery
if (is_null(self::$breadcrumbs))
{
self::discover();
}

// Use the last item without a link
$last = self::pop();

foreach (self::$breadcrumbs as $breadcrumb)
{
$this->builder->link($breadcrumb->url, $breadcrumb->display);
}

return $this->builder
->html($last->display, ['class' => 'breadcrumb-item active'])
->render();
}
}
26 changes: 26 additions & 0 deletions src/Traits/BreadcrumbsStyle.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php namespace Tatter\Menus\Traits;

use Spatie\Menu\Link;

/**
* Breadcrumbs Style Trait
*
* Applies CSS classes & styles
* to make a Bootstrap-style
* breadcrumbs nav Menu.
*
* @mixin \Tatter\Menus\Menu
*/
trait BreadcrumbsStyle
{
protected function applyBreadcrumbsStyle(): void
{
$this->builder
->addClass('breadcrumb')
->setWrapperTag('ol')
->wrap('nav', ['aria-label' => 'breadcrumb'])
->registerFilter(function (Link $link) {
$link->addParentClass('breadcrumb-item');
});
}
}
21 changes: 21 additions & 0 deletions tests/breadcrumbs/BreadcrumbTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php namespace Tests\Support;

use Tatter\Menus\Breadcrumb;
use Tests\Support\MenusTestCase;

class BreadcrumbTest extends MenusTestCase
{
public function testUsesProvidedDisplay()
{
$breadcrumb = new Breadcrumb(site_url('fruit'), 'banana');

$this->assertSame('banana', $breadcrumb->display);
}

public function testGuessesDisplay()
{
$breadcrumb = new Breadcrumb(site_url('fruit'));

$this->assertSame('Fruit', $breadcrumb->display);
}
}
110 changes: 110 additions & 0 deletions tests/breadcrumbs/BreadcrumbsMenuTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php namespace Tests\Support;

use Tatter\Menus\Breadcrumb;
use Tatter\Menus\Menus\BreadcrumbsMenu;
use Tests\Support\MenusTestCase;

class BreadcrumbsMenuTest extends MenusTestCase
{
/**
* @var BreadcrumbsMenu
*/
private $menu;

/**
* Initializes the Breadcrumbs menu.
*/
protected function setUp(): void
{
parent::setUp();

$this->menu = new BreadcrumbsMenu();
}

/**
* Removes any Breadcrumbs.
*/
protected function tearDown(): void
{
parent::tearDown();

BreadcrumbsMenu::set(null);
}

public function testDiscovery()
{
$expected = [
new Breadcrumb('http://example.com', 'Home'),
new Breadcrumb('http://example.com/current', 'Current'),
];

$result = BreadcrumbsMenu::discover();

$this->assertEquals($expected, $result);
}

public function testDefaultUsesDiscovery()
{
$expected = '<nav aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="http://example.com">Home</a></li><li class="breadcrumb-item active">Current</li></ol></nav>';
$result = (string) $this->menu;

$this->assertSame($expected, $result);
}

public function testGet()
{
$breadcrumbs = BreadcrumbsMenu::discover();

$result = BreadcrumbsMenu::get();

$this->assertSame($breadcrumbs, $result);
}

public function testSetNull()
{
BreadcrumbsMenu::discover();
BreadcrumbsMenu::set(null);

$result = BreadcrumbsMenu::get();

$this->assertNull($result);
}

public function testPopNull()
{
$result = BreadcrumbsMenu::pop();

$this->assertNull($result);
}

public function testPop()
{
$breadcrumbs = BreadcrumbsMenu::discover();

$result = BreadcrumbsMenu::pop();

$this->assertSame($breadcrumbs[1], $result);
}

public function testPushNull()
{
$breadcrumb = new Breadcrumb('food');

$result = BreadcrumbsMenu::push($breadcrumb);
$this->assertSame(1, $result);

$this->assertSame($breadcrumb, BreadcrumbsMenu::pop());
}

public function testPush()
{
$breadcrumbs = BreadcrumbsMenu::discover();

$breadcrumb = new Breadcrumb('food');

$result = BreadcrumbsMenu::push($breadcrumb);
$this->assertSame(3, $result);

$this->assertSame($breadcrumb, BreadcrumbsMenu::pop());
}
}

0 comments on commit 0074dca

Please sign in to comment.