4

Originally at Include a template that sets variables (global template variables).

I have a number of templates and layouts but every layout includes a global header in which the meta data is displayed.

That data always comes from the entry you are viewing and currently I'm setting the variables the global header needs in each template, such as /news/index.html:

{% extends "_listing_layout" %}

{% set metaTitle = (entry.metaTitle) ? entry.metaTitle : entry.title %}
{% set metaDescription = entry.metaDescription %}
{% set metaKeywords = entry.metaKeywords %}
{% set metaRobots = entry.metaRobots %}
{% set openGraphImage = entry.openGraphImage.first() %}

{% if openGraphImage %}

    {% set ogCrop = {
        mode: 'crop',
        width: 1200,
        height: 630,
        quality: 75,
        position: 'center'
    } %}

    {% set openGraphImage = openGraphImage.getUrl(ogCrop) %}

{% endif %}

{% set listingSection = "newsArticles" %}

{% block content %}

    <h1>{{ entry.title }}</h1>

    {% include '_partials/_isotope.html' %}

{% endblock %}

The layout is super simple:

{% include '_partials/_global_header.html' %}

    {% block content %}{% endblock %}

{% if craft.request.firstSegment %}

    {% include '_partials/_global_footer.html' %}

{% else %}

    {% include '_partials/_home_footer.html' %}

{% endif %}

And then the global header partial:

<!DOCTYPE html>
<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8" lang="en"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9" lang="en"> <![endif]-->
<!--[if gt IE 8]><!--> <html lang="en-gb" dir="ltr" class="no-js"> <!--<![endif]-->

    <head>

        {# meta #}
        <meta charset="utf-8">
        <title>{% if metaTitle is defined %}{{ metaTitle }} - {% endif %}{{ siteName }}</title>

        {# seo #}
        <meta name="description" content="{% if metaDescription is defined %}{{ metaDescription }}{% endif %}">
        <meta name="keywords" content="{% if metaKeywords is defined %}{{ metaKeywords }}{% endif %}">
        <link rel="canonical" href="{{ siteUrl }}">
        <meta name="robots" content="{% if metaRobots is defined %}{{ metaRobots }}{% else %}index,follow{% endif %}">

        {# open graph #}
        <meta property="og:site_name" content="{{ siteName }}">
        <meta property="og:type" content="website">
        <meta property="og:title" content="{% if metaTitle is defined %}{{ metaTitle }} - {% endif %}{{ siteName }}">
        <meta property="og:url" content="{{ siteUrl }}">
        <meta property="og:image" content="{% if openGraphImage is defined %}{{ openGraphImage }}{% else %}{{ siteUrl }}images/og-default.jpg{% endif %}">
        <meta property="og:description" content="{% if metaDescription is defined %}{{ metaDescription }}{% endif %}">

...etc

How can I keep this variable code DRY? I've tried putting direct in the header partial and layouts, but that doesn't work. And putting it in an included partial within each template throws a template error on account of the fact the template extends a layout and can't have a body.

4

There are two basic issues with using includes in this scenario, both of which you have discovered.

The first is that included templates do not have access to variables defined in master templates. You would have to specifically pass the variables to the template in the include statement using the with clause.

The second is that extended templates cannot have a body outside of a declaration block, and includes are by definition expected to return html, and thus throw an error.

A better solution would be to use hierarchical extensions vs includes, placing all shared content together in the parent template. All variables in child templates should bubble up through the template hierarchy and be available to the parent templates. Something like the following for example (I've left out much of the content for brevity).

In _layout.html:

<!DOCTYPE html>
<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8" lang="en"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9" lang="en"> <![endif]-->
<!--[if gt IE 8]><!--> <html lang="en-gb" dir="ltr" class="no-js"> <!--<![endif]-->

<head>
    {# meta #}
    <meta charset="utf-8">
    <title>{% if metaTitle is defined %}{{ metaTitle }} - {% endif %}{{ siteName }}</title>

    {# set seo #}
    <meta name="description" content="{% if metaDescription is defined %}{{ metaDescription }}{% endif %}">
    <meta name="keywords" content="{% if metaKeywords is defined %}{{ metaKeywords }}{% endif %}">
    <link rel="canonical" href="{{ siteUrl }}">
    <meta name="robots" content="{% if metaRobots is defined %}{{ metaRobots }}{% else %}index,follow{% endif %}">

    {# set open graph #}
    <meta property="og:site_name" content="{{ siteName }}">
    <meta property="og:type" content="website">
    <meta property="og:title" content="{% if metaTitle is defined %}{{ metaTitle }} - {% endif %}{{ siteName }}">
    <meta property="og:url" content="{{ siteUrl }}">
    <meta property="og:image" content="{% if openGraphImage is defined %}{{ openGraphImage }}{% else %}{{ siteUrl }}images/og-default.jpg{% endif %}">
    <meta property="og:description" content="{% if metaDescription is defined %}{{ metaDescription }}{% endif %}">

    ...etc

    {# set default site meta variables, if any #}

    {% block head %}{% endblock %}

</head>    

<body>

    {% block content %}{% endblock %}

    {% block footer %}{% endblock %}

</body>

</html>

In _home-layout.html:

{% extends "_layout" %}

{% block head %}
    {# additional home head content #}
{% endblock %}

{% block content %}{% endblock %}

{% block footer %}
    {# home footer content #}
{% endblock %}

In _page-layout.html:

{% extends "_layout" %}

{% block head %}
    {# additional page head content #}
{% endblock %}

{% block content %}{% endblock %}

{% block footer %}
    {# page footer content #}
{% endblock %}

In your index.html template:

{% extends "_home-layout" %}

{# set home meta variables, etc #}

{% block content %}
    {# home body content #}
{% endblock %}

In your page.html template:

{% extends "_page-layout" %}

{# set page meta variables, etc #}

{% block content %}
    {# page body content #}
{% endblock %}

In this way you can group shared content in the parent template and specific content further down the template hierarchy. How best to structure this hierarchy to keep you code 'dry' will be heavily dependent on the needs of your project so I won't even begin to guess.

Keep in mind it is also possible to define blocks of html in variables using conditionals and then place those variables into the block declarations.

{% if craft.request.firstSegment %}

    {% set footer %}
        {# page footer content #}
    {% endset %}

{% else %}

    {% set footer %}
        {# home footer content #}
    {% endset %}

{% endif %}

{% block footer %}
    {{ footer }}
{% endblock %}
| improve this answer | |
  • Thanks, but I don't see how that helps. You would still have to repeat the block of code that sets each meta variable in each template. If you look at my question you can see there are 10-20 lines of code there - it's this snippet of code I'm trying to make DRY. In ExpressionEngine that would be a snippet, but I don't see how we can tackle that in Craft. – Russ Back Dec 29 '14 at 22:06
  • Put it in an intermediary template, that is shared by all sub templates. That was the point. You should only ever have to define something once if done correctly. In my example I have the meta info set in the child template because I thought this is unique, but if it's not then move it to the parent template. – Douglas McDonald Dec 29 '14 at 23:43
2

What you're trying to do is how I used to do it; for your specific case, I'd suggest trying out SEOmatic, which will keep all of this out of your templates, but still give you the control you want. It will also give you a ton of meta and functionality that you're missing.

Now in terms of solving the generalized case of how to do this via templates, some pretty good examples have already been given. I'd also suggest looking into the Twig Embed functionality as well.

| improve this answer | |
1

I'm doing a very similar thing. This is the approach I got to work:

_base_template.html:

{# set default vars here ... this could also be put in an include file to share across different base templates #}
{% set title_default = title_default ? title_default : 'This is the default title' %}
{% set meta_desc_default = meta_desc_default ? meta_desc_default : 'This is the default meta description' %}
{% set meta_keywords_default = meta_keywords_default ? meta_keywords_default : 'These,are,the,default,meta,keywords' %}

{# page_info is set from the CMS ... #}
<title>{{ page_info.page_title | default(title_default) ~ " | #{company_name}" }}</title>

<body>
{% block content %}
{% endblock content %}
{# etc. #}
</body>

child_page.html:

{% extends "_base_template.html" %}

{# only set the ones we need to override here #}
{% set title_default = 'This is the child page title.' %}

{% block content %}
{# etc. #}
{% endblock content %}

The key, for me, was in using the conditionals when setting the default variables.

What appears to happen, for me, is that it processes the set blocks in the child template first, and then the ones in the base template. So without the conditionals, the defaults in the base template end up overriding the page-specific ones I set in the child page.

The conditionals allow me to only set the defaults if they're not already "overridden" in the child page (so to speak).

They could also be written in non-ternary form, which might make them easier to read, but take up more lines:

{% if not title_default %}{% set title_default = 'This is the default title!!' %}{% endif %}

or:

{% if title_default is not defined %}{% set title_default = 'This is the default title!!' %}{% endif %}

It's more work than using, say, PHP, but it's better than repeating all that logic on every child page.

At some point, one may question whether it's better to move something like this up to the PHP level (or whatever one is using), more on the level of the "controller" than the "view". But it still might make sense to do this on the view level. And in some instances, maybe one doesn't have access further up the chain, before Perch gets called.

For the record, I'm using Twig 1.14.0 with a home-grown CMS, without CraftCMS.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.