Create a custom link browser
In this tutorial we create a custom link browser and the associated backend link handler.
We create a new tab in the link browser window in the TYPO3 backend:
Tip
If you want to link to a record in a custom table, configure the RecordLinkBrowser. You do not need a custom link browser in that scenario.
We introduce a custom link format to store
the links in this format: t3://
.
This enables us to edit an existing link in the link browser or to change parts of the GitHub URI programmatically later.
And we render the new link in the frontend automatically.
Step by step to a custom link browser
1. Register the custom link browser tab in page TSconfig
TCEMAIN.linkHandler {
github {
handler = T3docs\Examples\LinkHandler\GitHubLinkHandler
label = LLL:EXT:examples/Resources/Private/Language/locallang_browse_links.xlf:github
displayAfter = haiku
scanBefore = url
configuration {
project = TYPO3-Documentation/TYPO3CMS-Reference-CoreApi
action = issues
}
}
}
The following options are of note here:
handler
- The backend link handler that we create in step 2.
configuration
- Some configuration, available to the backend link handler. This information is not available in the frontend. Therefore in the frontend rendering of the link the information must be stored in another way. In this example we hardcoded it. But you could also make it available by TypoScript Setup or as part of the link that is saved.
For a complete list of available option see Link handler configuration.
2. Create a link browser tab
To create a link browser tab we implement the interface
\TYPO3\
.
All backend link handlers provided by the Core extend the abstract class
\TYPO3\
. However, this class is
marked as @internal
and therefore can be changed by the Core Team at any time.
You have the choice of implementing the Link
yourself by
having a look at the Abstract
for best practices or to extend
the Abstract
. In the latter case your code might break on
updates though.
In this tutorial, we implement the Link
directly, as it is
best practice not to rely on internal classes.
You can find the complete class in the extension EXT:examples on GitHub: GitHubLinkHandler.
We will explain some of the important methods below:
Initialization and dependencies
use TYPO3\CMS\Backend\Controller\AbstractLinkBrowserController;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\View\ViewInterface;
class GitHubLinkHandler implements LinkHandlerInterface
{
protected array $linkAttributes = ['target', 'title', 'class', 'params', 'rel'];
protected array $linkParts = [];
protected ViewInterface $view;
protected array $configuration;
public function __construct(
// The page renderer is needed to register the JavaScript
private readonly PageRenderer $pageRenderer,
) {}
public function initialize(AbstractLinkBrowserController $linkBrowser, $identifier, array $configuration)
{
$this->configuration = $configuration;
}
public function setView(ViewInterface $view): void
{
$this->view = $view;
}
}
For technical reasons, not all dependencies needed by the backend link handler can be acquired by Dependency injection. Therefore the following two methods are called by Core classes once the dependencies are available:
Link
takes care of setting the
\TYPO3\
, the identifier and
the configuration information. In this example we only need the configuration,
the other parameters might be needed in different scenarios.
Abstract
Link Browser Controller $link Browser - Is the surrounding class calling the link handler. This class stores configuration information of the complete link browser window.
string $identifier
- Contains the key of the page TSconfig configuration of the link browser tab this instance renders.
array $configuration
- Contains the page TSconfig configuration as array of the link browser tab this instance renders.
The method set
is called by the Abstract
once the view is available and contains the necessary information to render
the link browser window.
Note
set
is not part of the Link
and its call is an implementation detail that might be
changed in the future.
Enable dependency injection
Backend link handlers are called internally in the TYPO3 Core by
General
. Therefore dependency injection needs
to be enabled by marking the class as public in the extension's
Configuration/
. As we keep internal states in the link
handler class (for example $link
) it cannot be a singleton and must
be marked as shared: false
:
Render the link browser tab
The method Link
is called when the tab should
be rendered. It registers the required JavaScript in the page renderer, assigns
variables to the view and returns the rendered HTML.
use Psr\Http\Message\ServerRequestInterface;
class GitHubLinkHandler implements LinkHandlerInterface
{
public function render(ServerRequestInterface $request): string
{
$this->pageRenderer->loadJavaScriptModule('@t3docs/examples/github_link_handler.js');
$this->view->assign('project', $this->configuration['project']);
$this->view->assign('action', $this->configuration['action']);
$this->view->assign('linkParts', $this->linkParts);
$this->view->assign('issue', $this->linkParts['issue'] ?? '');
return $this->view->render('LinkBrowser/GitHub');
}
}
Set the link via JavaScript
When the button in the rendered form is clicked to set a link, a custom JavaScript class interprets the form data and creates the link to be stored:
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
import LinkBrowser
from "@typo3/backend/link-browser.js";
/**
* Module: @t3docs/examples/github_link_handler.js
* Github issue link interaction
*/
class GitHubLinkHandler {
constructor() {
var form_el = document.getElementById("lgithubform");
form_el.addEventListener("submit", function(event) {
event.preventDefault();
var value = document.getElementById('lgithub').value;
if (value === 't3://github?issue=') {
return;
}
if (value.indexOf('t3://github?issue=') === 0) {
value = value.substring(18);
}
LinkBrowser.finalizeFunction('t3://github?issue=' + value);
});
}
}
export default new GitHubLinkHandler();
It is important that the JavaScript function calls
Link
. Otherwise no link will be set.
If not done yet, the JavaScript has to be registered in the file
EXT:
. Otherwise it
will not be found by $page
.
<?php
return [
'dependencies' => ['backend'],
'imports' => [
'@t3docs/examples/' => 'EXT:examples/Resources/Public/JavaScript/',
],
];
As our JavaScript class depends on classes provided by the backend system extension,
backend
has to be added as dependency. See also
Loading ES6.
Can we handle this link?
The method Link
is called when the
user edits an existing link in the link browser. All backend link handlers will
be called and can decide if they can handle that link. If so, they should store
the provided information to be used in rendering (for example, to fill an input
field with the old value).
class GitHubLinkHandler implements LinkHandlerInterface
{
public function canHandleLink(array $linkParts): bool
{
if ($linkParts['type'] !== 'github') {
return false;
}
$this->linkParts = $linkParts['url'] ?? [];
return true;
}
}
Format current URL
The function Link
is used to preview
what the link will look like in the backend, for example, in the upper part of
the link browser window.
Attention
Link
is not used to render the
link in the frontend.
3. Introduce the custom link format
You can find the complete class in the extension EXT:examples on GitHub: GitHubLinkHandling.
Our backend link handler implementation from step 1
saves the link in the custom format t3://
via JavaScript.
This format is only an arbitrary string until we tell TYPO3 how to handle links
of the new format by a second class which implements the
\TYPO3\
.
Note
There are two interfaces with very similar names and very different
functionality involved here. The
\TYPO3\
renders a tab in
the link browser window in the backend. Its implementing class is commonly
called a "(backend) link handler". Classes implementing the interface
\TYPO3\
handle the
introduced link format. Such a class is called a "(core) link handler".
/**
* Resolves GitHub Links
*/
class GitHubLinkHandling implements LinkHandlingInterface
{
protected $baseUrn = 't3://github';
public function asString(array $parameters): string
{
$githubIssue = (int)$parameters['issue'];
return $this->baseUrn . '?issue=' . $githubIssue;
}
public function resolveHandlerData(array $data): array
{
return [
'issue' => (int)$data['issue'],
];
}
}
The method Link
creates a string
representation from the parameter array.
Link
receives
the string representation of the link and creates the parameter array from it.
For convenience the parameters are already parsed and stored as key-value pairs
in an array for you. You can perform further processing here if needed.
4. Render the custom link format in the frontend
The link builder, a class extending the abstract class
\TYPO3\
is called whenever
a link is rendered in the frontend, for example via
TypoScript .typolink
, by the
\TYPO3\
function or by the \TYPO3\
.
use TYPO3\CMS\Frontend\Typolink\LinkResult;
use TYPO3\CMS\Frontend\Typolink\LinkResultInterface;
use TYPO3\CMS\Frontend\Typolink\UnableToLinkException;
/**
* Builds a TypoLink to a Github issue
*/
class GithubLinkBuilder extends AbstractTypolinkBuilder
{
private const TYPE_GITHUB = 'github';
public function build(
array &$linkDetails,
string $linkText,
string $target,
array $conf
): LinkResultInterface {
$issueId = (int)$linkDetails['issue'];
if ($issueId < 1) {
throw new UnableToLinkException(
'"' . $issueId . '" is not a valid GitHub issue number.',
// Use the Unix timestamp of the time of creation of this message
1665304602,
null,
$linkText
);
}
$url = 'https://github.com/TYPO3-Documentation/TYPO3CMS-Reference-CoreApi/issues/' . $issueId;
return (new LinkResult(self::TYPE_GITHUB, $url))
->withTarget($target)
->withLinkConfiguration($conf)
->withLinkText($linkText);
}
}
The link builder must be registered in ext_
, so that
the correct link builder for the new type can be determined by the calling API:
$GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder']['github'] =
\T3docs\Examples\LinkHandler\GithubLinkBuilder::class;
The function Abstract
is called with the link
configuration and data from the typolink function. If the link can be rendered,
it returns a new \TYPO3\
. The
actual rendering of the link depends on the context the link is rendered in
(for example HTML or JSON).
If the link cannot be built it should throw a
\TYPO3\
.
Attention
The configuration from the :ref:`page TSconfig
configuration <tutorial_backend_link_handler-tsconfig>` (step 1) is not available in the frontend. Therefore the information which repository to use must be stored in another way. In this example we hardcoded it. But you could also make it available by TypoScript setup or as part of the link format that is saved.