gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
Open up base.html.twig
and move the {% block layout %}
to be around everything. So, put the start just inside the body
tag... and the end just before the closing body
tag:
<html> | |
... lines 3 - 16 | |
<body> | |
{% block layout %} | |
<nav class="navbar navbar-expand-lg navbar-light bg-light"> | |
... lines 20 - 45 | |
</nav> | |
{% block body %}{% endblock %} | |
<div class="container mt-5"> | |
... lines 51 - 60 | |
</div> | |
{% endblock %} | |
</body> | |
</html> |
If we refresh the homepage now... it's destroyed! The top nav
and footer
are gone. Why did I do this? Because I love chaos! Kidding - I did it because it gives us the power, inside layouts, to design totally custom pages: even pages without the traditional navigation
and footer
, maybe like a temporary landing page for a promotion.
But let's be honest, 99% of the time, we will want the nav
and footer
. No problem, head back over to base.html.twig
. Remember: adding blocks give us more flexibility. So, above the navigation, add a new block called navigation
, with {% endblock %}
after. Then, down here, another called footer
... and {%
endblock %}:
<html> | |
... lines 3 - 16 | |
<body> | |
{% block layout %} | |
{% block navigation %} | |
<nav class="navbar navbar-expand-lg navbar-light bg-light"> | |
... lines 21 - 46 | |
</nav> | |
{% endblock %} | |
{% block body %}{% endblock %} | |
{% block footer %} | |
<div class="container mt-5"> | |
... lines 54 - 63 | |
</div> | |
{% endblock %} | |
{% endblock %} | |
</body> | |
</html> |
I bet you know what I'll do next. In the layout admin, we can now add a Twig block to the top that renders navigation
... then one down here on the bottom. It doesn't need to be in this last zone... but it makes sense there. Render footer
.
Let's try it! Hit "Publish and continue editing" and... refresh. We are back!
Let's create a second layout, this time for the /recipes
page. If you look at RecipeController
, you'll see that I already did all the work to query for the recipes, and pass them into this template:
... lines 1 - 12 | |
class RecipeController extends AbstractController | |
{ | |
'/recipes/{page<\d+>}', name: 'app_recipes') ( | |
public function recipes(RecipeRepository $recipeRepository, int $page = 1): Response | |
{ | |
$queryBuilder = $recipeRepository->createQueryBuilderOrderedByNewest(); | |
$adapter = new QueryAdapter($queryBuilder); | |
/** @var Recipe[]|Pagerfanta $pagerfanta */ | |
$pagerfanta = Pagerfanta::createForCurrentPageWithMaxPerPage($adapter, $page, 4); | |
return $this->render('recipes/list.html.twig', [ | |
'pager' => $pagerfanta, | |
]); | |
} | |
... lines 27 - 34 | |
} |
And in that template, we loop over and render each one, with pagination:
... lines 1 - 4 | |
{% block body %} | |
<div class="hero-wrapper"> | |
<h1>Doggone Good Recipes</h1> | |
<p>Recipes your pup will love!</p> | |
</div> | |
... lines 10 - 31 | |
{% endblock %} |
And so, I definitely want to include all of this custom work in the new layout.
Back in the admin area, I'll hit "Publish layout" as an easy way to get back to the layout list. Then hit new layout, I'll choose my favorite layout 2 and call it "Recipes List Layout". To start, add a new block called "Full View"... and drag it anywhere onto the page, whoops! There we go.
What is this "Full View". It's nothing special, in fact, it's kind of redundant! It's nothing more than a "Twig block" that renders the block called body
. So, yes, we could have just as easily done this by using the normal Twig block and typing in body
.
Publish this layout... then go to "Layout Mappings". Add a new one... and this time I'll link it first... to "Recipes List Layout". Then click "Details". Like last time, we could map this via the route name. But to see something different, use "Path Info", which, again, is just a fancy word for the URL, but without any query parameters. Make it match /recipes
... "Save Changes" and... sweet!
When we try the page... it looks awesome! Except, whoops, I forgot the nav and footer! Adding those two blocks to "Recipe List Layout" is easy. But what if, later we decide that every page should render both the navigation block on top as well as a dynamic banner, maybe for a sale that we're having. If that happened, we would need to edit every layout to manually add that new banner.
Fortunately, there's a better way to handle repeated layout elements like this.
Hit "Discard" to get back to the layouts list, then click "Shared layouts" and "New shared layout". As usual, the layout type doesn't matter much, so I'll use my normal one... and call it "Nav & Footer Layout".
This is not going to be a real layout that's linked to any pages. Nope, it's just going to be a layout that we steal pieces from. Up in the top zone, create a Twig Block that renders navigation
... and I'll even label it "Top Nav" to make it more clear. Then in any other zone - you can put it at the bottom, but you don't have to, add another twig block that renders footer
and is labeled Footer.
Cool! Hit "Publish layout". Now we have one shared layout. Again, these are not meant to be mapped to pages: they're meant for us to use in other real layouts.
Check it out: edit "Recipe List Layout". On the bottom left of the screen, hiding behind the web debug toolbar - I'll close that temporarily - there's a button to link a zone with a shared layout zone. Click that, then select the top zone... called the "Header" zone, though that name isn't important.
Now, we can find a shared zone from a shared layout... and we only have one. Hit "Select Zone" and... that's it! The top zone in our layout will now equal whatever block or blocks are in the top zone of that shared layout. If we added more stuff to that zone in the shared layout, it would automatically show up here.
Do that one more time: select the last zone so that the footer definitely shows up at the bottom, select the shared zone and... done!
Publish that, move over, refresh and... the full page is back! Let's quickly repeat that for the "Homepage Layout". Oh, but this is tricky because I put all of my blocks inside this top zone. Mostly, these zones don't matter, but in this case, to avoid overwriting all of this, I'll drag everything except for the navigation twig block down here. We can fix the order later.
And now, set the top zone to use the one from the shared layout. Yup! It replaced what we had there before. Below, also link the bottom zone with the shared layout.
Perfect! Let's check the order of our blocks... which is kind of the beauty of layouts. If I don't like the order of what's on my page, I can always change it! That's better. Publish the layout, head back to the homepage on the frontend and... beautiful!
Next: let's make our recipe list page more flexible by allowing this top h1
area to be built and customized from inside layouts... instead of it being hardcoded in the template.
Hey edin!
This is tricky stuff! When I saw your well-researched comment, I was thinking "he's right! How did I mess that up?".
But actually, the text is correct. But... you are ALSO correct :D. Here's what happens internally:
1) You're right that the template that is rendered for the full_view.html.twig
looks like this:
{% block content %}
{{ twig_content|raw }}
{% endblock %}
But this doesn't mean that this reads from your block called content
. It means that it prints the twig_content
variable (we'll talk about where this comes from in a minute) into a block called content
. Then, because this extends block.html.twig
- that template. - https://github.com/netgen-layouts/layouts-core/blob/master/bundles/LayoutsBundle/Resources/views/nglayouts/themes/standard/block/block.html.twig#L9-L19 - just takes the content
block and renders it out.
So this is all a really fancy way of basically just saying {{ twig_content }}
outside of any block.
2) So where does twig_content
come from? First, each block has a "handler" class. The one for full view is this - https://github.com/netgen-layouts/layouts-standard/blob/master/lib/Block/BlockDefinition/Handler/Twig/FullViewHandler.php - nothing too important there, except that it implements TwigBlockDefinitionHandlerInterface
.
3) When the blocks are rendering an event is fired. One of the listeners is this class: https://github.com/netgen-layouts/layouts-core/blob/master/bundles/LayoutsBundle/EventListener/BlockView/GetTwigBlockContentListener.php - notice this checks to see if the block handler implements TwigBlockDefinitionHandlerInterface
. If it does, it takes whatever the current template is that's rendering (it's an object, stored in that twig_template
parameter), loops over $blockDefinition->getTwigBlockNames($block)
and returns the contents of the first matching block. These contents become the twig_content
variable rendered in the template.
4) So then, which block does $blockDefinition->getTwigBlockNames($block)
return? Well, if you look back at FullViewHandler
:
class FullViewHandler ... {
public function __construct(array $twigBlockNames)
{
$this->twigBlockNames = $twigBlockNames;
}
public function getTwigBlockNames(Block $block): array
{
return $this->twigBlockNames;
}
}
That doesn't tell us. It just says that the block names are injected via the constructor. Where is this service configured? Well, the argument is configured here: https://github.com/netgen-layouts/layouts-standard/blob/14af051a3f0bdc1f2be89014169a8b84148f1225/bundle/Resources/config/services/block_definitions.yaml#L21
So in the end, EITHER the content
block or body
block would be used (though the fact that you see a block called content
inside ful_view.html.twig
is coincidental and not part of this process). If you had both a content
and body
block, content
would win, so I suppose your answer is slightly more correct than mine, though I think I'll keep the text as it is :).
Cheers!
Oh wow, thank you for the detailed explanation. I feel a bit guilty not doing this myself.
I am going to dig into this matter more myself when the full tutorial gets released.
For now, I am not able to render anything besides content
Twig block in my template.
I probably missed something as I am using my own playground project for this, but as I said, I will investigate this further with the tutorial project.
Thank you.
Hey edin!
No worries - I love to dig in, and your conclusion seemed logical to me too originally! Weird that only content
Twig block is being used - let me know what you find out when you have a chance to investigate further.
Cheers!
// composer.json
{
"require": {
"php": ">=8.1.0",
"ext-ctype": "*",
"ext-iconv": "*",
"babdev/pagerfanta-bundle": "^3.7", // v3.7.0
"doctrine/doctrine-bundle": "^2.7", // 2.7.0
"doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
"doctrine/orm": "^2.13", // 2.13.3
"easycorp/easyadmin-bundle": "^4.4", // v4.4.1
"netgen/layouts-contentful": "^1.3", // 1.3.2
"netgen/layouts-standard": "^1.3", // 1.3.1
"pagerfanta/doctrine-orm-adapter": "^3.6",
"sensio/framework-extra-bundle": "^6.2", // v6.2.8
"stof/doctrine-extensions-bundle": "^1.7", // v1.7.0
"symfony/console": "5.4.*", // v5.4.14
"symfony/dotenv": "5.4.*", // v5.4.5
"symfony/flex": "^1.17|^2", // v2.2.3
"symfony/framework-bundle": "5.4.*", // v5.4.14
"symfony/monolog-bundle": "^3.0", // v3.8.0
"symfony/proxy-manager-bridge": "5.4.*", // v5.4.6
"symfony/runtime": "5.4.*", // v5.4.11
"symfony/security-bundle": "5.4.*", // v5.4.11
"symfony/twig-bundle": "5.4.*", // v5.4.8
"symfony/ux-live-component": "^2.x-dev", // 2.x-dev
"symfony/ux-twig-component": "^2.x-dev", // 2.x-dev
"symfony/validator": "5.4.*", // v5.4.14
"symfony/webpack-encore-bundle": "^1.15", // v1.16.0
"symfony/yaml": "5.4.*", // v5.4.14
"twig/extra-bundle": "^2.12|^3.0", // v3.4.0
"twig/twig": "^2.12|^3.0" // v3.4.3
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
"symfony/debug-bundle": "5.4.*", // v5.4.11
"symfony/maker-bundle": "^1.47", // v1.47.0
"symfony/stopwatch": "5.4.*", // v5.4.13
"symfony/web-profiler-bundle": "5.4.*", // v5.4.14
"zenstruck/foundry": "^1.22" // v1.22.1
}
}
Not sure if you made a mistake or not, but you are referring to a
body
twig block when using the Full View block, but this is not correct, it should be content. In the text:The full view twig template (in my version at least, file:
@NetgenLayoutsStandard/nglayouts/themes/standard/block/full_view.html.twig
) shows: