Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Block Views & View Types

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Let's override one other template completely. Go into the Individual Skill Layout. We're using a Contentful entry here, which is a "Referenced asset"... and it's rendering as this image tag. Cool!

Block "View Types" / Templates

This is a great example of how a single Block type - for example the "Contentful Entry Field" block type - can have multiple View types, which basically means "multiple templates". Each of these different View types will be rendered by a different template. We actually see this with a lot of different Block types - even the Grid Block type. I'll add one down here temporarily. It has a View type that allows you to switch between List and Grid. Yup, the List and Grid blocks are actually both the same Block type internally: they just have a different View type, meaning each is rendered by a different template. Go ahead and delete that.

Anyway, every Block type can have one or more View types. And I actually want to dive a little deeper into this concept of "views". Find your terminal and run:

php ./bin/console debug:config netgen_layouts view

I'm debugging the configuration that could live under the view key below the netgen_layouts key:

netgen_layouts:
... lines 2 - 12
view:
... lines 14 - 36

When you run this, you see a ton of config. Notice that there are several root keys, like parameter_view, layout_view, and a few others. But there are actually only two that we care about: block_view, which we'll talk about now, and item_view, which controls how the items in a List or Grid render. We actually saw this one earlier when we customized how our Recipe "item" rendered inside a List or Grid. We'll talk even more about those soon.

The Block View Config

Anyways, to zoom in on the block views, run that same command, but add .block_view

php ./bin/console debug:config netgen_layouts view.block_view

Block views, very simply, control how entire block types are rendered. For example, we can see how the "Title block" renders... or the "Text block", or how the "List block" renders.

This block_view config can have several keys below it, like default, app, and ajax. And we know what those mean. default means these are used on the frontend, app means they're used in the admin section and ajax, which is not as common, is used on the frontend for AJAX calls. So to override the frontend template for a block, we really mean that we want to override its block "view" under the default key.

Let's... zoom in one more time by adding .default:

php ./bin/console debug:config netgen_layouts view.block_view.default

The "match" config

These are all the block views that will be used on the frontend. The trickiest thing about these are the match part.

When you define a "block view", it's pretty common to define the template that should be used when two things match. Search for "list\grid": this is a great example. This has two match items: block\definition is set to list because, technically the "Block type" for both the List and Grid blocks is called list. The second match condition is block\view_type set to grid.

Together these mean that if a block is being rendered whose block\definition is list and whose block\view_type is grid, use this.

By the way, both of these things can be seen very clearly from the web debug toolbar. Go to the homepage, click on the Layouts web debug toolbar, and go to "Rendered blocks". Down here... look at this! You can see "Block definition: List", "View type: grid"! And then it points to the template that was rendered. In this case, it's referring to this Grid right here.

So then... why is the Title block rendered by title.html.twig? We can see that in the config. Search for "title"... here we go. This says: if the block\definition is title and the block\view_type is title, use this template. This is an example of a Block type that only has one View type. So, in practice, this is the view that's used for all title blocks.

Find & Overriding the Contentful Field Assets View

Ok, let's remember our original goal: to override the template that renders this image. We know that this is a "Contentful entry field" and it has a View type of "Referenced assets". So... we can find that in here!

Search for "assets" and... there it is! So if block\definition is contentful_entry_field and the block\view_type is assets, this is the template! This means that if we want to override just the assets View type of the Contentful entry, that's the template we need to override.

And yes, we could have very easily found this by going to the web debug toolbar and finding the template there. But now we understand a bit more about how blocks are rendered and how each block can have multiple views so that we can choose how they're rendered. Later, we'll add an extra "view type" to an existing block.

Okay, so let's get to work. The path starts with the normal nglayouts/themes/standard/, then we need block/, followed by this path. So inside of our block/ directory, create a new sub-directory called contentful_entry_field/. And inside of that, a new assets.html.twig. For now, I'll just say ASSET:

Ok! Spin over to the frontend and... yes! It instantly sees it! We're now in control!

Making the Template Fancier

Like before, we probably don't want to override the entire template. Instead, open the core template - assets.html.twig - so we can steal, um borrow from it. Temporarily, copy the whole thing, paste:

{% extends '@nglayouts/block/block.html.twig' %}
{% block content %}
{% set field_identifier = block.parameter('field_identifier').value %}
{% set field = block.dynamicParameter('field') %}
{{ dump() }}
{% block contentful_entry_field %}
{% if field is not empty %}
{% if field.type is constant('TYPE_OBJECT', field) or field.type is constant('TYPE_ASSET', field) %}
<div class="field field-{{ field.type }} field-{{ field_identifier }}">
<img src="{{ field.value.file.url }}?h={{ block.parameter('height').value }}&w={{ block.parameter('width').value }}" width="{{ block.parameter('width').value }}" height="{{ block.parameter('height').value }}" />
</div>
{% elseif field.type is constant('TYPE_ASSETS', field) %}
<div class="field field-{{ field.type }} field-{{ field_identifier }}">
{% for asset in field.value %}
<img src="{{ asset.file.url }}?h={{ block.parameter('height').value }}&w={{ block.parameter('width').value }}" width="{{ block.parameter('width').value }}" height="{{ block.parameter('height').value }}" />
{% endfor %}
</div>
{% else %}
{{ 'contentful.field_not_compatible'|trans({'%field_identifier%': field_identifier}, 'contentful') }}
{% endif %}
{% endif %}
{% endblock %}
{% endblock %}

And... yep! That works.

Contentful is fairly advanced... and you can see that this supports fields that hold a single image as well as multiple images. You can keep this as flexible as you want, but you can also make it your own. I'm going to drastically simplify this template... and replace it with a very simple image. For the src, I'll paste in some code:

{% extends '@nglayouts/block/block.html.twig' %}
{% block content %}
{% set field = block.dynamicParameter('field') %}
{{ dump() }}
<img src="{{ field.value.file.url }}?h={{ block.parameter('height').value }}&w={{ block.parameter('width').value }}" />
{% endblock %}

All of the fancy Twig parts of this code were in the template before. This also shows off a Contentful superpower where you can control the image size. Calling block.parameter() allows us to read the options from the layouts admin, where we earlier configured this block to have a width and height of 200.

Let's see what it looks like! Refresh. Yeah! It looks like it worked!

Choosing to Render or Not Render Complex Options

But I do want to want give one small warning about customize templates: make sure you don't lose flexibility that you need. For example, we know that we can add extra CSS classes to any block via the admin.

If we did that right now, it would not work because... we're simply not rendering those classes! And, that might be fine. But if you do want to support that, you'll need to make sure to add it. In this case we can say class="{{ css_class }}", which is one of the variables we saw earlier. And while we're here, let's also add an alt attribute set to field.value.title:

... lines 1 - 2
{% block content %}
... line 4
<img class="{{ css_class }}" src="{{ field.value.file.url }}?h={{ block.parameter('height').value }}&w={{ block.parameter('width').value }}" alt="{{ field.value.title }}" />
{% endblock %}

When we try this... I love it! There's the alt attribute and there's our class, including some core classes that Layouts always adds to that variable.

Okay, we just talked about block views: how templates are configured for entire blocks. Next, let's talk about item views: how we customize the template that's used when rendering an item inside of a Grid or List. We'll use this to style our skill items.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// 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
    }
}
userVoice