4

I have a structure that goes up to 4 levels deep. Each page has a field for a 'hero' image. If a child page does not have this field filled out I would like to traverse up the structure and find the closest parent entry with this field set. Basically some 'sections' would have a default hero image added to the top level parent while others may have a specific image for each child page.

6

You can get your entry's parent entry with getParent(). Use this entry method in a conditional, if your entry has no hero image set.

You can nest the code to climb up all of your structure's levels:

{# Check if entry has hero image #}
{% if entry.hero.first() %}
    {{ entry.hero.first().url }}
{% else %}

    {# Check the parent entry #}
    {% if entry.getParent().hero.first() %}
        {{ entry.getParent().hero.first().url }}
    {% else %}

        {# Check the entry 2 levels above #}
        {% if entry.getParent().getParent().hero.first() %}
            {{ entry.getParent().getParent().hero.first().url }}
        {% else %}

            {# ... Oh no, we're not finished yet :( #}

        {% endif %}

    {% endif %}

{% endif %}

Yes, that's not good code. That's why I recommend to use the same technique in a macro, that you can call recursively:

{% macro showHero(thisRecursionEntryModel) %}

    {% import _self as self %}

    {# Check if entry has hero image #}
    {% if thisRecursionEntryModel.hero.first() %}
        {{ thisRecursionEntryModel.hero.first().url }}
    {% else %}

        {# Check the entry's ancestors recursively #}
        {% if thisRecursionEntryModel.getParent() %}
            {{ self.showHero(thisRecursionEntryModel.getParent()) }}
        {% endif %}

    {% endif %}

{% endmacro %}

{% import _self as self %}
{{ self.showHero(entry) }}

You can also import the macro from a file, if you want to use it in more than one template file or just like your code to be modular:

{% import '_includes/macros.html' as macros %}
{{ macros.showHero(entry) }}
  • Thanks for the edit, @Brandon. I wrote this not with assets in mind and simply added .url at the end. – carlcs Oct 24 '14 at 12:11
  • Perfect, thank you. I tried something along these lines but had syntax issues. @Brandon, thanks for clarifying my title as well. – Lance Oct 24 '14 at 14:33
5

Another way to do this would be to assemble a list of the entry IDs that you want to look for a hero image in, in order, and then finding the first hero image related to one of those entries.

To create that list of entry IDs, we start by creating an array of the current entry’s ID, and merge it with an array of the entry’s ancestors’ IDs in reverse order:

{% set ancestorIds = entry.getAncestors().ids() %}
{% set entryIds = [ entry.id ]|merge( ancestorIds|reverse ) %}

Now that we have our list of entry IDs that could have the hero image, next up we need to find the first one that actually has one. We can do that with craft.entries and passing :notempty: to the relational field param:

{% set heroEntry = craft.entries({
    id: entryIds,
    fixedOrder: true,
    hero: ':notempty:',
}).first() %}

There’s a chance that no entry was found, so before you start interacting with heroEntry you should make sure that first() actually returned something.

{% if heroEntry %}
    <img src="{{ heroEntry.hero.first().url }}">
{% endif %}
  • Nice one Brandon, I like it more than my solution (even though I was a little proud of that recursive macro)! :) – carlcs Oct 24 '14 at 15:59
  • @carlcs Hah, your solution is great too! Just thought it was worth mentioning the alternative. Performance-wise it's probably a toss-up if the hero image is never more than 3 levels away, though this one will be a little more scalable if it could be a few levels deeper. – Brandon Kelly Oct 24 '14 at 16:02

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.