This package is used across various TYPO3 Symfony Applications to ensure a streamlined visual experience and reduced maintenance.
- Symfony Template Bundle
- Installation
- Configuration
- Page Template
- EMail Template
- Utilities
- Twig Extensions
- Twig Tags
- JavaScript Libraries
composer require t3g/symfony-template-bundle
Ensure that symfony default scripts are present in your composer.json
file.
{
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
}
}
The bundle comes with a sensible default configuration, which is listed below. You can define these options if you need to change them:
# config/packages/template.yaml
template:
application:
# Example: Intercept
name: 'Template Bundle'
# Example: intercept
platformkey: ~
copyright:
author: TYPO3 GmbH
url: https://typo3.com
routes:
# Example: app_index
home: app_index
privacy: https://typo3.com/privacy-policy
legal: https://typo3.com/legal-notice
feedback: https://support.typo3.com
menu:
# Example: App\Menu\MenuBuilder
class: T3G\Bundle\TemplateBundle\Menu\MenuBuilder
assets:
# Example: app
encore_entrypoint: ~
email:
legal_footer: |
TYPO3 GmbH, Emanuel-Leutze-StraĂźe 11, DE-40547 DĂĽsseldorf, Germany
Phone: +49 (0)211 20 54 36 0, Web: www.typo3.com, Email: [email protected]
Court of registration: Amtsgericht DĂĽsseldorf HRB 77950
CEO: Daniel Fau, (CTO & Procuration) Frank Nägler
Supervisory Board: Olivier Dobberkau, Ric van Westhreenen, Stefan Busemann
theme:
# Example: md
navbar_breakpoint: lg
use_logo: false
background: light
bin/console config:dump-reference template
Configuration will be available within the templates and can be accessed through template
variable.
Examples:
<title>{% block title %}{% endblock %} - {{ template.application.name }}</title>
<a class="navbar-brand" href="{{ path(template.application.routes.home) }}">
<i class="fab fa-typo3 text-primary"></i>
<strong>{{ template.application.name }}</strong>
</a>
composer require symfony/webpack-encore-bundle
To enable your encore entrypoint simply configure the key within the yaml configuration.
# config/packages/template.yaml
template:
application:
assets:
encore_entrypoint: app
# config/packages/template.yml
template:
application:
menu:
class: App\Menu\MenuBuilder
<?php
namespace App\Menu;
use T3G\Bundle\TemplateBundle\Menu\MenuBuilder as TemplateMenuBuider;
class MenuBuilder extends TemplateMenuBuider
{
public function mainDefault(array $options)
{
$menu = parent::mainDefault($options);
$menu->addChild(
'home',
[
'route' => 'app_index',
'label' => 'Home',
'extras' => [
'icon' => 'home',
],
]
);
return $menu;
}
}
Available methods to override mainDefault
, mainProfile
and mainFooter
.
$menu->addChild($this->getDivider());
Extend Default Layout
{% extends '@Template/layout.html.twig' %}
{% block title %}Home{% endblock %}
<title>Home - {{ template.application.name }}</title>
This block will only be rendered obove the body block if defined in the template.
{% block headline %}Super Headline{% endblock %}
<div class="page-wrapper">
<!-- HEADER -->
<!-- FLASHMESSAGES -->
<main class="page-main">
<div class="content-section content-section-small bg-primary text-white">
<div class="container">
<h1 class="h2">Super Headline</h1>
</div>
</div>
<!-- BODY BLOCK -->
</main>
<!-- FOOTER -->
</div>
{% block body %}
<div class="content-section">
<div class="container">
BODY CONTENT
</div>
</div>
{% endblock %}
<div class="page-wrapper">
<!-- HEADER -->
<!-- FLASHMESSAGES -->
<main class="page-main">
<!-- HEADLINE -->
<div class="content-section">
<div class="container">
BODY CONTENT
</div>
</div>
</main>
<!-- FOOTER -->
</div>
{% block footer %}
<div class="content-section">
<div class="container">
FOOTER CONTENT
</div>
</div>
{% endblock %}
<div class="page-wrapper">
<!-- HEADER -->
<!-- FLASHMESSAGES -->
<!-- MAIN HEADLINE AND BODY -->
<footer class="page-footer">
<div class="content-section">
<div class="container">
FOOTER CONTENT
</div>
</div>
<!-- COPYRIGHT AND FOOTER MENU -->
</footer>
</div>
Stylesheet block will be rendered after the base
and encore
css before the closing </head>
.
JavaScript block will be rendered after base
and encore
javascript before the closing </body>
.
Extend Default Layout
{% extends '@Template/email.html.twig' %}
{% block email_subject %}Reset Your Password{% endblock %}
{% block email_plaintext %}
Forgot your password? Let's get you a new one.
We got a request to change the password for the account with the username {{ user.username }}.
You can reset your password by accessing {{ confirmationUrl }}
If you don't want to reset your password, you can ignore this email.
{% endblock %}
{% block email_html_preview %}
You requested a password reset for your account.
{% endblock %}
{% block email_html_body %}
<h3>Forgot your password?<br>Let's get you a new one.</h3>
<p>We got a request to change the password for the account with the username <strong style="color: #222222;">{{ user.username }}</strong>.</p>
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="buttonBlock" style="min-width:100%;">
<tbody class="buttonBlockOuter">
<tr>
<td style="padding-top: 36px; padding-right:18px; padding-bottom:36px; padding-left:18px;" valign="top" align="center" class="buttonBlockInner">
<table border="0" cellpadding="0" cellspacing="0" class="buttonContentContainer" style="border-collapse: separate !important;border-radius: 3px;background-color: #ff8700;">
<tbody>
<tr>
<td align="center" valign="middle" class="buttonContent" style="font-family: "Helvetica Neue", Helvetica, Arial, Verdana, sans-serif; font-size: 16px; padding: 18px;">
<a class="button " title="Reset Your Password" href="{{ confirmationUrl }}" rel="noopener noreferrer" target="_blank" style="font-weight: bold;letter-spacing: -0.5px;line-height: 100%;text-align: center;text-decoration: none;color: #FFFFFF;">Reset Your Password</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<p style="font-size: 12px;">Or reset your password using this link: {{ confirmationUrl }}</p>
<p style="font-size: 12px;">If you don't want to reset your password, you can ignore this email.</p>
{% endblock %}
<?php
use T3G\Bundle\TemplateBundle\Utility\AvatarUtility;
echo AvatarUtility::getAvatar('[email protected]', 32);
// <img src="https://...avatar.png" class="avatar" height="32" width="32">
<?php
use T3G\Bundle\TemplateBundle\Utility\AvatarUtility;
echo AvatarUtility::getAvatarUrl('[email protected]', 32);
// https://...avatar.png
Twig filter to automaticly convert urls, emails and phone numbers to links.
{{ value|autolink }}
or
{% apply autolink %}
<p>
Phone: +49 (0)211 205436-0<br>
Web: www.typo3.com<br>
Email: [email protected]
</p>
{% endapply %}
<p>
Phone: <a href="tel:+492112054360">+49 (0)211 205436-0</a><br>
Web: <a href="https://www.typo3.com" target="_blank" rel="noopener">www.typo3.com</a><br>
Email: <a href="mailto:[email protected]">[email protected]</a>
</p>
Twig function to display avatars.
{{ avatar('[email protected]', 32) }}
<img src="https://...avatar.png" class="avatar" height="32" width="32">
Twig function to display typo3 icons.
{{ icon('actions-heart', 'auto') }}
<span class="icon icon-size-auto icon-state-default">
<span class="icon-markup">
<svg role="img" class="icon-color"><use xlink:href="/bundles/template/icons/sprites/actions.svg#actions-heart"></use></svg>
</span>
</span>
Twig function to render markdown as HTML.
{{ value|markdown }}
A function to check if a function is available within the current Twig Environment.
{% if template_function_exist('relativetime') %}
The relativetime function is available.
{% endif %}
Twig always checks for all functions with a template, also within a condition. This function is a wrapper around the original function call that is only executed if the function actually exists.
# Template will still work if `encore_entry_link_tags` is not defined, function just returns null.
{{ template_function_call('encore_entry_link_tags', template.application.assets.encore_entrypoint) }}
# Template will fail if `encore_entry_link_tags` is not registered.
{{ encore_entry_link_tags(template.application.assets.encore_entrypoint) }}
Converts a unix timestamp to datetime object.
{{ to_datetime(timestamp) }}
Returns a localized string representing this date.
{{ localdate(datetimeObject) }}
Returns a localized string representing this datetime.
{{ localdatetime(datetimeObject) }}
Returns a string representation of a time relative to now, such as "in two days". Rounds down by default.
Option | Type | Default |
---|---|---|
units | array | [] |
For possible values see luxon documentation.
{{ relativetime(datetimeObject) }}
With specific time units.
{{ relativetime(datetimeObject, ['minutes', 'seconds']) }}
Returns a string representing the difference between a date and now() such as "1 year and 11 months".
{{ timediff(datetimeObject) }}
Option | Type | Default |
---|---|---|
id | string | |
layout | embedded/default | default |
size | small/default | default |
color | default/primary/secondary/tertiary/quaternary/light/dark/white | default |
indent | bool/left/right | false |
rulerBefore | bool | false |
rulerAfter | bool | false |
center | bool | false |
backgroundImage | string | |
backgroundImageFade | bool | true |
backgroundImageBlur | bool | false |
backgroundImageParallax | bool | false |
backgroundImageGrayscale | bool | false |
backgroundImageSepia | bool | false |
height | auto/small/medium/max | auto |
innerWidth | small/medium/large/full/default | default |
title | string | |
titleSize | int | 2 |
titleAnchor | bool | true |
{% frame with options %}Inner Content{% endframe %}
{% frame with { id: 'identifier', color: 'primary', center: true } %}
<p class="lead">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
{% endframe %}
<div id="identifier" class="frame frame-size-default frame-background-primary frame-no-backgroundimage frame-space-before-none frame-space-after-none">
<div class="frame-container">
<div class="frame-inner text-center">
<p class="lead">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
</div>
</div>
</div>
{% expand %}Inner Content{% endexpand %}
{% expand %}
<p class="lead">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
{% endexpand %}
<div class="expander">
<input class="expander-checkbox" type="checkbox" id="expander-6267e5f76fa06">
<label for="expander-6267e5f76fa06" class="expander-toggle">
Show more
</label>
<div class="expander-content">
<div class="expander-content-inner">
<p class="lead">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
</div>
</div>
</div>
The library can be enabled and configured through the attribute data-datepicker
, pass the configuration as json encoded string.
Example HTML
<input
type='text'
name='date'
placeholder='Select Date...'
data-datepicker='{}'
class="form-control"
>
<input
type='text'
name='date'
placeholder='Select Datetime...'
data-datetimepicker='{}'
class="form-control"
>
Example FormBuilder
$builder->add('date', DateType::class, [
'widget' => 'single_text',
'attr' => [
'placeholder' => 'Select Date...',
'data-datepicker' => json_encode(
[],
JSON_THROW_ON_ERROR
),
],
'html5' => false
]);
$builder->add('date', DateTimeType::class, [
'widget' => 'single_text',
'attr' => [
'placeholder' => 'Select Datetime...',
'data-datetimepicker' => json_encode(
[],
JSON_THROW_ON_ERROR
),
],
'html5' => false
]);
Source: https://flatpickr.js.org/
The library can be enabled and configured through the attribute data-choicesjs
, pass the configuration as json encoded string.
Example HTML
<select
name='country'
data-choicesjs='{"maxItemCount":1}'
>
<option value="">Please select a country</option>
...
<option value="DE" selected="selected">Germany</option>
<option value="GH">Ghana</option><option value="GI">Gibraltar</option>
<option value="GL">Greenland</option><option value="GD">Grenada</option>
...
</select>
Example FormBuilder
$builder->add('country', ChoiceType::class, [
'attr' => [
'data-choicesjs' => json_encode(
[
'maxItemCount' => 1,
],
JSON_THROW_ON_ERROR
),
],
'choices' => $countryChoices,
'empty_data' => null,
'placeholder' => 'Please select a country',
]);
Source: https://github.com/jshjohnson/Choices
The library can be enabled and configured through the attribute data-taginput
, pass the configuration as json encoded string.
Example FormBuilder
$builder->add('usernames', TextType::class, [
'attr' => [
'data-taginput' => json_encode([], JSON_THROW_ON_ERROR),
],
]);
$usernames = array_map(
function($item) { return $item['value']; },
json_decode($form->getData()['usernames'], true)
);
Source: https://github.com/yairEO/tagify