Skip to content

Commit

Permalink
Merge pull request #9 from tattersoftware/breadcrumbs
Browse files Browse the repository at this point in the history
Breadcrumbs
  • Loading branch information
MGatner authored Apr 23, 2021
2 parents 9a67e8e + 0074dca commit 55ff886
Show file tree
Hide file tree
Showing 10 changed files with 382 additions and 6 deletions.
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
4 changes: 2 additions & 2 deletions src/Traits/Bootstrap.php → src/Traits/BootstrapStyle.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
*
* @mixin \Tatter\Menus\Menu
*/
trait Bootstrap
trait BootstrapStyle
{
protected function applyBootstrap(): void
protected function applyBootstrapStyle(): void
{
$this->builder->registerFilter(function (Link $link) {
$link->addParentClass('nav-item');
Expand Down
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());
}
}
Loading

0 comments on commit 55ff886

Please sign in to comment.