Plugin Development: Extending Templates
Previous Section: Model Events
Plugins give you the option to inject data in to and completely override existing templates.
Example cases where this is useful:
- Showing user's products/services as options in a custom field (our WHMCS Information plugin) - uses a View Composer
- Showing user's billing details in the ticket view (our WHMCS Information plugin) - uses a View Hook
- Showing social media details about the user (our X and Facebook channels) - makes use of Overriding Views
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:
<?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:
<?php
namespace Addons\Plugins\HelloWorld\Listeners;
class FooterHook
{
/**
* Inject data into the view.
*
* @return string
*/
public function hook()
{
return '<script>...</script>';
}
}
e
function is a shorthand version of running htmlspecialchars
.
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');
});