Skip to content

View Components

View Components (or just Components) allow you to create custom HTML elements to use within your views.

Self-Closing Tag Components

At their most basic, components serve as dynamic templates that allow you to reduce the typing in your application. This can help boil longer, complex sections down to a single HTML tag. This is especially useful with CSS utility frameworks like TailWind, or when using the utilities in Bootstrap 5, etc. Using components in these situations allows you to keep the style info in one place where making changes to one file changes every instance of the view throughout the application.

To create a component, simply create a new view file within the theme's Component directory, or the application's, app/Components directory.

A simple avatar image might look something like this:

// app/Components/avatar.php
<img
  src="<?= $src ?? '' ?>"
  class="rounded-circle shadow-4"
  style="width: <?= $width ?? '150px' ?>;"
  alt="<?= $alt ?? '' ?>"
/> 

When using the component within a view, you would insert a tag with x- prepended to the filename:

<x-avatar src="<?= $user->avatarUrl() ?>" alt="<?= $user->name ?>" />

Any attributes provided when you insert the component like this are made available as variables within the component view. In this case, the $src and $alt attributes are passed to the component view, resulting in the following output:

<img
  src="http://example.com/avatars/foo.jpg"
  class="rounded-circle shadow-4"
  style="width: 150px"
  alt="John Smith"
/> 

Components With Opening and Closing Tags

You can include the content within the opening and closing tags by inserting the reserved $slot variable:

<x-green-button>Click Me!</x-green-button>
// app/Components/green-button.php
<button class="btn btn-success">
    <?= $slot ?>
</button>

Controlled Components

Finally, you can create a class to add additional logic to the output for both self-closing tags and tags with content. The file must be in the same directory as the component view, and should have a name that is the PascalCase version of the filename, with 'Component' added to the end of it. Any attributes that you set on the custom element and any content within element will be passed to the class of the controlled component to be, optionally, processed by the component class and/or rendered by the comonent view file. The data you pass will be available within the class as it's $data property (the tag content – as $data['slot'] property).

A famous-qoutes component would have a view called famous-quotes.php and a controlling class called FamousQuotesComponent.php. The class must extend Bonfire\View\Component. The only requirement is that you implement a method called render().

Below are example files of a usable famous-quotes component, that can be included in any of these three ways:

<x-famous-quotes />
<x-famous-quotes seconds="30" />
<x-famous-quotes seconds="30">Famous Quotes</x-famous-quotes>

It's controlling class file FamousQuotesComponent.php:

namespace App\Components;

/**
 * This example controlled component allows to retrieve a random famous quote
 * from the ZenQuotes API and cache it for a specified number of seconds.
 * Number of seconds can be passed as a parameter to the component.
 * If you want to add a title to the component, you can pass it as a $slot variable.
 */
class FamousQuotesComponent extends \Bonfire\View\Component
{
    protected $defaultNoOfSeconds         = 600;
    protected string $famousQuotesAPINode = 'https://zenquotes.io/api/random';

    public function render(): string
    {
        return $this->renderView($this->view, $this->getFamousQuote());
    }

    public function getFamousQuote(): array
    {
        helper('cache');

        if (isset($this->data['seconds']) && is_numeric($this->data['seconds'])) {
            $this->data['seconds'] = (int) round($this->data['seconds'], 0);
        } else {
            $this->data['seconds'] = $this->defaultNoOfSeconds;
        }

        $cacheKey = 'famous_quote';

        if ($cachedQuote = cache($cacheKey)) {
            return $cachedQuote;
        }

        $response  = file_get_contents($this->famousQuotesAPINode);
        $quoteData = json_decode($response, true);

        if (isset($quoteData[0])) {
            $this->data['quote'] = [
                'text'   => $quoteData[0]['q'],
                'author' => $quoteData[0]['a'],
            ];

            cache()->save($cacheKey, $this->data, $this->data['seconds']);
        } else {
            $this->data['quote'] = [
                'text'   => 'Sucking at something is the first step to becoming sorta good at something.',
                'author' => 'Jake (from "Adventure Time")',
            ];
        }

        return $this->data;
    }
}

And it's view file famous-quotes.php:

<div class="card mb-3">
    <div class="card-header">
        <?= esc($slot ?? '') ?>
    </div>
    <div class="card-body mt-3">
        <p class="card-text text-center"><?= esc($quote['text']) ?></p>
        <footer class="blockquote-footer text-center"><em><?= esc($quote['author']) ?></em></footer>
    </div>
    <div class="card-footer" style="font-size:10px;">Cached for <?= esc($seconds) ?> seconds</div>
</div>