Plugin Development: Extending Templates

Previous Section: Model Events


Plugins give you the option to inject data in to and completely override existing templates.

Contents

Example cases where this is useful:

Understanding View Names

View names are constructed based on the folder structure where the directory separator (/) is replaced by a period (.).

For example, the following template file path resources/templates/operator/integration/ticket/ticket.twig maps to 'operator.integration.ticket.ticket'. The resources/templates is stripped away because this is the base path to the templates directory.

In the subsequent sections of this page, wildcards can be used for path components that we don't know. For example, 'operator.*.ticket.ticket' would cover the ticket view in all templates.

The above naming convention applies to views stored under the resources/templates/ folder. For any views within a plugin's Views folder, the view name is prepended by the plugin namespace. For example the file addons/Plugins/HelloWorld/Views/settings.twig maps to 'Plugins#HelloWorld::settings'.

Modifying View Data

It's possible to access and modifying existing view data (variables) using view composers. View composers are callbacks that are called just before the view is rendered and shown to the user. They can be define using closures or classes, as demonstrated below:


    // Using class based composers...
    \View::composer('operator.default.ticket.ticket', 'App\Plugin\HelloWorld\Listeners\TicketComposer');

    // Using Closure based composers...
    \View::composer('operator.default.ticket.ticket', function ($view) {
        // Fetch ticket model.
        $ticket = $view->ticket;

        ...
    });

The class based composer should be defined as below - the compose() function will be called automatically:

Listeners/TicketComposer.php

<?php
namespace Addons\Plugins\HelloWorld\Listeners;

use Illuminate\View\View;

class TicketComposer
{
    /**
     * Bind data to the view.
     *
     * @param  View  $view
     * @return void
     */
    public function compose(View $view)
    {
        // Fetch ticket model.
        $ticket = $view->ticket;

        ...
    }
}

For more information please read: https://laravel.com/docs/6.x/views#view-composers.

View Hooks

A view hook is an event that is fired when that line in the template file is being rendered. We can use event listeners to inject HTML into the view without having to actually modify the template itself.

These hooks are declared with the View.fireHook function call in our default template files. A number of hook points have been provided by default, as listed below, but it's also possible to define your own in custom templates or your own plugin template files if needed.


    {{ View.fireHook('frontend.footer') }}

We then just need to listen for the event and return escaped HTML. The example below will include the HTML in the footer of the template on every frontend page.


    // Using class based hooks...
    \View::hook('frontend.footer', 'App\Plugin\HelloWorld\Listeners\FooterHook');

    // Using Closure based hooks...
    \View::hook('frontend.footer', function () {
        return '<script>...</script>';
    });

The class based hook should be defined as below - the hook() function will be called automatically:

Listeners/FooterHook.php

<?php
namespace Addons\Plugins\HelloWorld\Listeners;

class FooterHook
{
    /**
     * Inject data into the view.
     *
     * @return string
     */
    public function hook()
    {
        return '<script>...</script>';
    }
}

Combining with View Composers

Combining template hooks with view composers allows us to access view data and create context aware template hooks. For example, in our X (formerly Twitter) channel we only want to modify the reply options if the user is currently viewing a Twitter channel ticket. Note that channels use a Channels# prefix for the namespace rather than plugins which are Plugins#.


    // In operator ticket view only
    \View::composer('operator.*.ticket.ticket', function ($view) {
        if ($view->ticket->channel_id != 5) {
            return;
        }

        \View::hook('operator.ticket_reply_options', function () {
            return \TemplateView::other('Channels#Twitter::ticket.forms.reply_options')->render();
        });
    });

Available Hooks

Frontend

Event Name Area
General
frontend.head_start Start of <head> If any CSS or other assets need to be loaded before our own assets (common CSS for example), generally frontend.head_end below should be used instead.
frontend.head_end End of <head> Add any necessary CSS and other assets here that needs to be loaded before the rest of the page loads. JS and jQuery should be added in frontend.body_end.
frontend.body_start Below <body>
frontend.header_start Start of header
frontend.navigation End of navigation The navigation is the links shown under the user's name when logged in or the full navigation in mobile view. Use the same HTML format as existing links for it to work in both cases.
frontend.header_end End of header
frontend.wrapper_start Start of main area This includes the main title and breadcrumb.
frontend.content_start Start of main content area
frontend.content_inner_start Start of main content inner area This is the section within the white box.
frontend.content_inner_end End of main content inner area This is the section within the white box.
frontend.content_end End of main content area
frontend.sidebar_start Start of sidebar The sidebar is only shown on certain pages.
frontend.sidebar_end End of sidebar The sidebar is only shown on certain pages.
frontend.wrapper_end End of main area
frontend.footer Page footer
frontend.body_end Above </body> Add any CSS, JS, jQuery and other assets here that can be ran after the rest of the page loads.
Dashboard
frontend.dashboard Help desk section links Add custom frontend sections here.

Operator

Event Name Area
General
operator.head_start Start of <head> If any CSS or other assets need to be loaded before our own assets (common CSS for example), generally operator.head_end below should be used instead.
operator.head_end End of <head> Add any necessary CSS and other assets here that needs to be loaded before the rest of the page loads. JS and jQuery should be added in operator.body_end.
operator.body_start Below <body>
operator.header_start Start of header
operator.navigation Header navigation The navigation is used both in the header and also in mobile view, use the same HTML format as existing links/dropdowns for it to work in both cases.
operator.header_end End of header
operator.wrapper_start Start of main area This includes the sidebar if present.
operator.content_start Start of right content area This starts outside the padded inner content area.
operator.content_inner_start Start of left content inner area
operator.content_inner_end End of left content inner area
operator.content_end End of left content area This ends outside the padded inner content area.
operator.wrapper_end End of main area This includes the sidebar if present.
operator.footer Page footer
operator.body_end Above </body> Add any CSS, JS, jQuery and other assets here that can be ran after the rest of the page loads.
operator.sidebar_start Start of sidebar Only visible on certain pages.
operator.sidebar_end End of sidebar Only visible on certain pages.
Ticket
operator.ticket_messages Ticket view messages Above or below the add reply form depending on the order selected in personal or system settings.
operator.ticket_tab Ticket view tab Add a tab (the link) to the ticket view.
operator.ticket_tab_content Ticket view tab content Add a tab (the tab content) to the ticket view.
operator.ticket_reply_options Ticket view reply options Add additional reply options here, will show at top of list.
operator.sidebar_ticket Ticket view sidebar Add sections to the sidebar, below the ticket details section.
User
operator.user_tab User tab Add a tab (the link) to the user pages, such as user profile.
operator.user_tab_content User tab content Add a tab (the tab content) to the user pages, such as user profile.

Overriding Views

Overriding views will replace the current view with the named view and render that instead. This is achieved using view composer events and simply overriding the path to the template using the setPath function on the View object. The path can either be an absolute path or the name of the view.

In the example below, our X (formerly Twitter) channel overrides the ticket message view with an X specific message view which has additional functionality such as retweet and like buttons. Note that channels use a Channels# prefix for the namespace rather than plugins which are Plugins#.


    \View::composer('operator.default.ticket.message', function ($view) {
        $view->setPath('Channels#Twitter::ticket.message');
    });

Next Section: Scheduled Tasks