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 app/Plugins/HelloWorld/Views/settings.twig maps to '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 App\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/5.5/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 App\Plugins\HelloWorld\Listeners;

class FooterHook
{
    /**
     * Inject data into the view.
     *
     * @return void
     */
    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 Twitter integration we only want to modify the reply options if the user is currently viewing a Twitter channel ticket.


    // 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('Twitter::ticket.forms.reply_options')->render();
        });
    });

Available Hooks

Frontend

Event Name Area
General
frontend.head Inside <head> Add any necessary JS/CSS here that needs to be loaded before the rest of the page loads.
frontend.body_start Below <body>
frontend.header_start Start of header
frontend.header_end End of header
frontend.wrapper_start Start of main area
frontend.content_start Start of left content area This includes the main title and breadcrumb.
frontend.content_inner_start Start of left content inner area This is the section within the white box.
frontend.content_inner_end End of left content inner area This is the section within the white box.
frontend.content_end End of left content area This includes the main title and breadcrumb.
frontend.sidebar_start Start of sidebar
frontend.navigation End of navigation The navigation is used both in the sidebar and also in mobile view, use the same HTML format as existing links for it to work in both cases.
frontend.sidebar_end End of sidebar
frontend.wrapper_end End of main area
frontend.footer Page footer
frontend.body_end Above </body> Add any JS/CSS 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 Inside <head> Add any necessary JS/CSS here that needs to be loaded before the rest of the page loads.
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 JS/CSS 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 here.
operator.ticket_tab_content Ticket view tab content Add a tab (the tab content) to the ticket view here.
operator.ticket_reply_options Ticket view reply options Add additional reply options here, will show at top of list.

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 Twitter integration overrides the ticket message view with a Twitter specific message view which has additional functionality such as retweet and like buttons:


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

Next Section: Scheduled Tasks