• Stars
    star
    221
  • Rank 179,773 (Top 4 %)
  • Language
    PHP
  • License
    MIT License
  • Created about 3 years ago
  • Updated 8 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Laravel breadcrumbs right out of a fairy tale

Gretel from the story 'Hansel and Gretel' holding bread behind her back

Gretel

Laravel breadcrumbs right out of a fairy tale.

Gretel is a Laravel package for adding route-based breadcrumbs to your application.

Installation

composer require glhd/gretel

Usage

Defining Breadcrumbs

Gretel adds a new Route macro that you can use when defining your routes:

Single Breadcrumb

In the simplest case, chain the breadcrumb() function onto your existing route to define a breadcrumb:

Route::get('/', HomeController::class)
  ->name('home')
  ->breadcrumb('Home');

Homepage Example

If you need to dynamically control the title, pass in a closure instead:

Route::get('/dashboard', DashboardController::class)
  ->name('dashboard')
  ->breadcrumb(fn() => Auth::user()->name.'’s dashboard');

Dashboard Example

Nested Breadcrumb

Breadcrumbs aren't very useful unless you string them together. Gretel handles nested breadcrumbs by pointing to a previously-defined parent breadcrumb:

Route::get('/users', [UserController::class, 'index'])
  ->name('users.index')
  ->breadcrumb('Users');
  
Route::get('/users/{user}', [UserController::class, 'show'])
  ->name('users.show')
  ->breadcrumb(fn(User $user) => $user->name, 'users.index');

Route::get('/users/{user}/edit', [UserController::class, 'edit'])
  ->name('users.edit')
  ->breadcrumb('Edit', 'users.show');

Nested Route Example

Here, you can see that our users.show route references users.index as its parent. This way, when you render breadcrumbs for users.show it will also show the breadcrumb for users.index.

Gretel assumes that the parameters in nested routes can be safely used for their parent routes. In this example, users.edit will render the users.show breadcrumb using the User value that was resolved for the edit action. In the vast majority of cases, this is exactly what you want. If not, you can override this behavior (see below).

Parent Shorthand

Often, a child route will reference a parent with the same name prefix. In our above example, users.show references users.index and users.edit references users.show. In this case, you can use the parent shorthand:

Route::get('/admin/users/{user}/notes/create', [NotesController::class, 'create'])
  ->name('admin.users.notes.create')
  ->breadcrumb('Add Note', '.index'); // shorthand for "admin.users.notes.index"

This is particularly useful for large apps that have many deeply nested routes.

Shallow Nested Routes

If your nested routes do not contain the route parameters necessary for the parent route, you will need to provide the values to Gretel. You can do this using a third callback:

Route::get('/companies/{company}', [CompanyController::class, 'show'])
  ->name('companies.show')
  ->breadcrumb(fn(Company $company) => $company->name);

Route::get('/users/{user}', [UserController::class, 'show'])
  ->name('users.show')
  ->breadcrumb(fn(User $user) => $user->name, 'companies.show', fn(User $user) => $user->company);

Shallow Nested Example

Resource Routes

You can also define breadcrumbs for resource controllers. The index(), create(), show(), and edit() methods behave exactly like the regular breadcrumb helper except that they automatically set up the parent for you if you don’t provide one.

Route::resource('users', UserController::class)
  ->breadcrumbs(function(ResourceBreadcrumbs $breadcrumbs) {
    $breadcrumbs
      ->index('Users')
      ->create('New User')
      ->show(fn(User $user) => $user->name)
      ->edit('Edit');
  });

If you prefer, you can also use an array syntax for simple resource routes:

Route::resource('users', UserController::class)
  ->breadcrumbs([
    'index' => 'Users',
    'create' => 'New User',
    'show' => fn(User $user) => $user->name,
    'edit' => 'Edit',
  ]);

Vendor Routes

Sometimes you want to register breadcrumbs for routes that are defined in 3rd-party packages. In this case, you can use the Gretel facade directly. The API is exactly the same as the Route::breadcrumb() method, except that you must pass the route name as the first parameter:

Gretel::breadcrumb(
  'teams.show', // Route name
  fn(Team $team) => $team->name, // Title callback
  'profile.show', // Parent
);

Displaying Breadcrumbs

You can display the breadcrumbs for the current route with the <x-breadcrumbs /> Blade component. The Blade component accepts a few optional attributes:

Attribute
framework Render to match a UI framework ("tailwind" by default)
view Render a custom view (supersedes the framework attribute)
jsonld Render as a JSON-LD <script> tag

Supported Frameworks

Gretel supports most common CSS frameworks. We've taken the CSS framework's documented markup and added additional aria- tags where appropriate for better accessibility. Currently supported frameworks:

Tailwind use "tailwind" (default)

Tailwind theme

Materialize use "materialize"

Materialize theme

Bootstrap 5 use "bootstrap5"

Bootstrap 5 theme

Bulma use "bulma"

Bulma theme

Semantic UI use "semantic-ui"

Semantic UI theme

Primer use "primer"

Primer theme

Foundation 6 use "foundation6"

Foundation 6 theme

UIKit use "uikit"

UIKit theme

Older Frameworks

Older versions of some frameworks are also available:

You'll typically want to include the <x-breadcrumbs /> tag somewhere in your application layout (maybe twice if you're using JSON-LD):

layouts/app.blade.php:

<!DOCTYPE html>
<html>
<head>
    <title>{{ $title }}</title>
    <x-breadcrumbs jsonld />
</head>
<body>
<div class="container mx-auto">
    <x-breadcrumbs framework="tailwind" />
    ...
</div>
</body>
</html>

Custom Breadcrumb View

You can render a custom view either by publishing the gretel.php config file via php artisan vendor:publish or by passing a view attribute to the Blade component:

<x-breadcrumbs view="app.breadcrumbs" />

Using Breadcrumbs in Some Other Way

If you need to use the breadcrumbs in some other way—maybe for rending via a client-side framework that Gretel doesn’t already integrate with—you can always just get the current breadcrumbs as a Collection or array off the route:

Route::breadcrumbs()->toCollection();  // Collection of `Breadcrumb` objects
Route::breadcrumbs()->toArray();       // Array of `Breadcrumb` objects
Route::breadcrumbs()->jsonSerialize(); // Array of breadcrumb arrays (title, url, is_current_page)
Route::breadcrumbs()->toJson();        // JSON string of breadcrumbs matching jsonSerialize

For example, our Inertia.js integration could easily be implemented as:

Inertia::share('breadcrumbs', function(Request $request) {
    return $request->route()->breadcrumbs()->jsonSerialize();
});
Accessibility

If you choose to render your own view, please be sure to follow the current WAI-ARIA accessibility best practices. Gretel provides some helpers to make this easier:

@unless ($breadcrumbs->isEmpty())
  <!-- Wrap your breadcrumbs in a <nav> element with an aria-label attribute -->
  <nav aria-label="Breadcrumb">
    <!-- Use an <ol> (ordered list) for the breadcrumb items -->
    <ol>
      @foreach ($breadcrumbs as $breadcrumb)
        <!-- You can use $activeClass() or $inactiveClass() to conditionally apply classes -->
        <li class="{{ $activeClass('active-breadcrumb') }}">
          <!-- Use $ariaCurrent() to apply aria-current="page" to the active breadcrumb -->
            <a href="{{ $breadcrumb->url }}" {{ $ariaCurrent() }}>
              {{ $breadcrumb->title }}
            </a>
        </li>
      @endforeach
    </ol>
  </nav>
@endunless

Caching Breadcrumbs

Because Gretel breadcrumbs are registered alongside your routes, you need to cache your breadcrumbs if you cache your routes. You can do so with the two commands:

# Cache breadcrumbs
php artisan breadcrumbs:cache

# Clear cached breadcrumbs
php artisan breadcrumbs:clear

Please note that you must cache your breadcrumbs before you cache your routes.

Handling Errors

Sometimes you'll mis-configure a breadcrumb or forget to define one. You can register handlers on the Gretel facade to handle these cases:

// Log or report a missing breadcrumb (will always receive a MissingBreadcrumbException instance)
Gretel::handleMissingBreadcrumbs(function(MissingBreadcrumbException $exception) {
  Log::warning($exception->getMessage());
});

// Throw an exception locally if there's a missing breadcrumb
Gretel::throwOnMissingBreadcrumbs(! App::environment('production'));

// Log or report a mis-configured breadcrumb (i.e. a parent route that doesn't exist).
// This handler will pick up any other exception that is triggered while trying to configure
// or render your breadcrumbs, so the type is unknown.
Gretel::handleMisconfiguredBreadcrumbs(function(Throwable $exception) {
  Log::warning($exception->getMessage());
});

// Throw an exception locally if there's a mis-configured breadcrumb
Gretel::throwOnMisconfiguredBreadcrumbs(! App::environment('production'));

Integration With Third Party Packages

Gretel automatically shares your breadcrumbs with Inertia.js if you have that package installed. You don't need to do anything to enable this integration. (If you do not want this behavior for some reason, you can disable it by publishing the Gretel config.)

Your breadcrumbs will be available in your client code as breadcrumbs and look something like:

const breadcrumbs = [
  {
    title: 'Home',
    url: 'https://www.yourapp.com',
    is_current_page: false,
  }, {
    title: 'Users',
    url: 'https://www.yourapp.com/users',
    is_current_page: false,
  }, {
    title: 'Add a User',
    url: 'https://www.yourapp.com/users/create',
    is_current_page: true,
  }
];

You can then render the breadcrumbs in the client however you see fit. Be sure to review the custom breadcrumbs section for information about how to ensure that your client-side breadcrumbs are fully accessible.

More Repositories

1

aire

Modern form builder for Laravel
PHP
535
star
2

laravel-dumper

Upgrade dd() with Laravel-specific improvements
PHP
387
star
3

alpine-wizard

Multi-step wizard helpers for Alpine.js
JavaScript
145
star
4

conveyor-belt

All the underlying mechanics necessary to write artisan commands that process lots of data efficiently
PHP
133
star
5

tailwindcss-plugins

Plugins & helpers for tailwindcss
JavaScript
85
star
6

bits

PHP
73
star
7

dawn

Experimental next-gen browser testing for Laravel
PHP
72
star
8

laravel-addressing

Laravel package providing addressing functionality
PHP
62
star
9

linen

Lightweight spreadsheets for Laravel
PHP
50
star
10

special

PHP
17
star
11

hooks

PHP
14
star
12

tinker-gui

Laravel Tinker GUI
TypeScript
12
star
13

linearavel

PHP
9
star
14

laravel-timezone-mapper

Map latitude/longitude to a timezone without making external API calls
PHP
8
star
15

wp-bootstrap-widgets

Bootstrap Components as WordPress Widgets
PHP
6
star
16

aire-bootstrap

Bootstrap theme for Aire
PHP
6
star
17

laravel-dusk-browserstack

Easily run Laravel Dusk tests on BrowserStack
PHP
6
star
18

ansipants

PHP
6
star
19

laralint

Framework for linting Laravel projects
PHP
6
star
20

laravel-package-template

Template for Laravel packages
PHP
4
star
21

laravel-prismoquent

Prismoquent lets you access Prismic.io using the same methods you're used to in Eloquent.
PHP
2
star
22

medusa

Headless content management
JavaScript
2
star
23

ansible-interactive

Interactive CLI for ansible plays
JavaScript
2
star
24

fs-of-holding

A filesystem that has every file in it
PHP
1
star
25

aire-livewire

1
star
26

omniponent

JavaScript
1
star
27

suspend

PHP
1
star
28

aire-docs

Blade
1
star
29

vbulletin-bbcode-parser

Parser package for vBulletin bbCode
PHP
1
star
30

verbs-github-webhooks

A collection of Verbs events to use with GitHub webhooks
PHP
1
star
31

accounts-payable

Help collect tax documents and pay independent contractors
PHP
1
star