1

I have a page with a featured entry at the top and then remaining entries showing below. On the first

{% set entries = craft.entries({
  section: 'articles',
  limit: 1,
  search: 'featuredOnHomepage:1'
}) %}
{% for entry in entries %}
  entry print out
  {% set homeFeature = entry.id %}
{% endfor}

Later in the template I do

    {% set entries = craft.entries({
        section:        'articles',
        order:          'postDate',
        limit:          4
    }).id('not ' ~ homeFeature) 
    %}
    {% for entry in entries %}
      <div class="summary-box">
        <h2><a href="{{ entry.url }}">{{ entry.title }}</a></h2>    
        {% set asset = entry.articleImage.first() %}
        {% if asset %}
          <a href="{{ entry.url }}"><img src="{{ asset.getUrl('summaryImage') }}" /></a>
        {% endif %}
      </div>
    {% endfor %} 

But it is still including the feature id. If I hardcode in the entry id it works fine. I have tried to print the {{ homeFeature }} variable in the second part and I get nothing yet in the first part it prints out the correct ID. I am assuming the variables don't persist through the different loops. Is there a way to make them persist or write the code differently?

Further Info

I have tried the solution from Edit 2 and still having an issue. Perhaps a little more info. There is a template _layout as is common. The template I am accessing is called index.html and is the homepage for the site. It includes

{% extends _layout %}
{% block headercontent %}
  code that prints out featured article
{% endblock %}
{% block content %}
  code that prints out remainder of articles minus feature
{% endblock %}

Trying to implement your Edit 2 solution I tried just declaring the variable in index.html outside of the blocks. That didn't work so I tried declaring it in _layout before the block calls. That also didn't work so then I tried in _layout {% set homeFeature = "foo" %} and in index.html {% set homeFeature = "bar" %} Then I added a print out of the {{ homeFeature }} variable within both blocks. Within the headercontent block i get 13 (the entry's id) and within the content block i get "foo" which I find very odd. Here is the full code minus some html if it helps.

_layout

 {% set homeFeature = "foo" %}
 <header>
 some other html
 {% block headercontent %}
 {% endblock %}
 </header>
 {% block content %}
 {% endblock %}

index.html

{% extends "_layout" %}
{% set homeFeature = "bar" %}
{% block headercontent %}
  <div id="home-featured">
    {% set entries = craft.entries({
      section: 'articles',
      limit: 1,
      search: 'featuredOnHomepage:1'
    }) %}
    {% for entry in entries %}
      {% set homeFeature = entry.id %}
      <h1>The feature id is: {{ homeFeature }}</h1>
      <h4>Featured</h4>
      <h2><a href="/articles/{entry.url}">{{ entry.title }}</a></h2>
      {{ entry.articleSummary }}
      <p>FILED IN: 
      {% set categories = craft.categories({
        relatedTo: { sourceElement: entry, field: "typeCategory, mainCategory" },
        limit:     null
      }) %}
      {% for category in categories %}
        <a href="{{ category.url }}">{{ category.title }}{% if not loop.last %}, {% endif %}</a>
      {% endfor %}
      </p>
    {% endfor %}
  </div>
{% endblock %}

{% block content %}
  <main id="content" role="main">
    <div class="green-block">
      <div id="home-stamp" class="wrapper">
        <img src="/css/gfx/home-map.gif" />
        {{ entry.body }}
      </div>
    </div>
    <div class="wrapper">
      {% set entries = craft.entries({
        section:        'articles',
        order:          'postDate',
        limit:          4,
        id: 'not '~homeFeature
      }) 
      %}
      {% for entry in entries %}
        <h1>The feature id is: {{ homeFeature }}</h1>
        <div class="summary-box">
          <h2><a href="{{ entry.url }}">{{ entry.title }}</a></h2>  
          {% set asset = entry.articleImage.first() %}
          {% if asset %}
            <a href="{{ entry.url }}"><img src="{{ asset.getUrl('summaryImage') }}" /></a>
          {% endif %}
        </div>
      {% endfor %} 
    </div>
  </main>
{% endblock %}
3
  • 1
    Your question has been answered correctly, just wanted to add some advice – you don't actually need to use the search parameter when you're filtering your craft.entries query on custom fields. {% set entries = craft.entries({ featuredOnHomepage: '1' }) %} works nicely and is probably more performant. Also, the whole "homeFeature" part of your code could be simplified to a one-liner: {% set homeFeature = craft.entries({ section: 'articles', limit: 1, featuredOnHomepage: '1' }).ids()|first %} Commented Oct 24, 2016 at 20:50
  • Appreciate the advice. I am new to craft and find the one liners more difficult to write and understand. The syntax is very different to what I am used to in ExpressionEngine and I find the long form feels more familiar. Working towards using what you have shown.
    – Phil
    Commented Oct 24, 2016 at 20:55
  • 1
    Sure. It doesn't necessarily need to be all on one line, though. The main point is to avoid the additional for loop, which you don't really need with a single entry. Basically, the example in my comment above is the exact same thing as you're currently doing; except for the .ids() method appended (which will return an array of IDs from the craft.entries query preceding it) and finally the |first filter which will return the first value from that array (or NULL if the array is empty – i.e., when there are no results). Commented Oct 24, 2016 at 21:25

2 Answers 2

3

A quick fix would be to set homeFeature outside the for loop and than the one inside the loop would change the one outside, you could than access it from other parts of your template.

{% set homeFeature = "" %}

{% set entries = craft.entries({
    section: 'articles',
    limit: 1,
    search: 'featuredOnHomepage:1'
}) %}

{% for entry in entries %}
    {% set homeFeature = entry.id %}
{% endfor}

Edit: here it is at the twig docs - http://twig.sensiolabs.org/doc/tags/set.html - "Note that loops are scoped in Twig; therefore a variable declared inside a for loop is not accessible outside the loop itself. ... If you want to access the variable, just declare it before the loop"

Edit 2 : For the id part you just need to make sure that the homeFeature is declared outside any block scope. after that you could use this later in the template

{% set entries = craft.entries({
    section:        'articles',
    order:          'postDate',
    limit:          4,        
    id:             'not '~homeFeature
}) %} 

Edit 3: Given the extra information, it seems that you are over complicating the task, and scopes can get tricky. So this is what i would do.

_layout.html set the featured article globally, why not?

{% set homeFeature = craft.entries({ 
    section: 'articles', 
    limit: 1,
    featuredOnHomepage: '1'
    })|first %}        
<header>
    some other html
    {% block headercontent %}
    {% endblock %}
</header>
{% block content %}

{% endblock %}

index.html

{% extends "_layout" %}

{% block headercontent %}
    <div id="home-featured">
        <h1>The feature id is: {{ homeFeature.id }}</h1>
        <h4>Featured</h4>
        <h2><a href="/articles/{entry.url}">{{ homeFeature.title }}</a></h2>
        {{ homeFeature.articleSummary }}
        <p>FILED IN: 
        {% set categories = craft.categories({
            relatedTo: { sourceElement: homeFeature, field: "typeCategory, mainCategory" },
            limit:     null
        }) %}
        {% for category in categories %}
            <a href="{{ category.url }}">{{ category.title }}{% if not loop.last %}, {% endif %}</a>
        {% endfor %}
        </p>
        {% endfor %}
    </div>
{% endblock %}

{% block content %}
<main id="content" role="main">
    <div class="green-block">
    <div id="home-stamp" class="wrapper">
        <img src="/css/gfx/home-map.gif" />
        {{ entry.body }}
    </div>
    </div>
    <div class="wrapper">
    {% set entries = craft.entries({
        section:        'articles',
        order:          'postDate',
        limit:          4,
        id: 'not '~homeFeature.id
    }) 
    %}
    {% for entry in entries %}
        <h1>The feature id is: {{ entry.id }}</h1>
        <div class="summary-box">
        <h2><a href="{{ entry.url }}">{{ entry.title }}</a></h2>  
        {% set asset = entry.articleImage.first() %}
        {% if asset %}
            <a href="{{ entry.url }}"><img src="{{ asset.getUrl('summaryImage') }}" /></a>
        {% endif %}
        </div>
    {% endfor %} 
    </div>
</main>
{% endblock %}
5
  • still doesn't print out {{ homeFeature }} within the second loop and still doesn't exclude it from the list of entries
    – Phil
    Commented Oct 24, 2016 at 20:47
  • just noticed another complication though. each loop is within a different block. perhaps that is the issue.
    – Phil
    Commented Oct 24, 2016 at 20:51
  • just edited the original answer to tackle the other points of the question. Hope this solves the problem. In sum : declare {% set homeFeature = "" %} outside any block scopes first, so it can be accessed from within the whole template.
    – Gui Rams
    Commented Oct 24, 2016 at 21:05
  • This provided enough info for me to fix the issue. Declaring the variable outside of the for loop works within the block but declaring it outside of the block does not make it available in other blocks. Because this is the first featured article though and not just a random one I just made the call again to pick the first featured id in the second block. Would be nice to know if there is some way to work it just so you could pass a random value if that was ever needed.
    – Phil
    Commented Oct 24, 2016 at 23:09
  • just added what i think would be a better solution
    – Gui Rams
    Commented Oct 24, 2016 at 23:12
3

You're dealing with scope issues – both related to for loops and in regards to extending templates (blocks).

First, any variable set inside a for loop will not be available outside that loop.

The solution is to declare the variable outside the loop:

{% set homeFeature = null %}
{% for entry in entries %}
    {% set homeFeature = entry.id %}
{% endfor %}

<p>The home feature's ID is {{ homeFeature }}</p>

For {% block %} tags, the same applies – any variable set inside a block is not available outside that block. Much like with for loops, you can declare a variable outside a block, and it will be available inside the block. However, unlike with for loops if you change the value of that variable inside a block, the updated variable will not be available to other blocks in the same template. In other words, this doesn't work:

{% extends '_layout' %}

{% set homeFeature = null %}

{% block headerContent %}
    {% set homeFeature = 'foo' %}
{% endblock %}

{% block content %}
    {{ homeFeature }} {# Still a `null` value! #}
{% endblock %}

There are a lot of potential workarounds to your specific issue, though.

First, since you can set a variable outside the blocks, something like this would work – simply execute the craft.entries query for the featured entry outside the blocks, and store the result in a variable that can be accessed inside the blocks:

{% extends '_layout' %}

{% set homeFeature = craft.entries({
    section: 'articles',
    limit: 1,
    featuredOnHomepage: '1'
}).first() %}

{% block headercontent %}
    <div id="home-featured">
        {% if homeFeature %}<h1>The feature id is: {{ homeFeature.id }}</h1>{% endif %}
    </div>
{% endblock %}

{% block content %}
    {% set entries = craft.entries({
        section:        'articles',
        order:          'postDate',
        limit:          4,
        id: homeFeature ? 'not '~homeFeature.id : null
    }) %}
    ...
{% endblock %}

The drawback with the above, is that it's going to be difficult to {% cache %} the craft.entries query outside the blocks. For a single, basic query this probably doesn't matter much in the scheme of things, though.

Another approach is to simply query for the homeFeature entry inside each {% block %}. You can even make things a bit more DRY by storing the actual query parameters in a variable outside the blocks:

{% extends '_layout' %}

{% set homeFeatureQuery = {
    section: 'articles',
    limit: 1,
    featuredOnHomepage: '1'
} %}

{% block headercontent %}
    <div id="home-featured">
        {% set homeFeature = craft.entries(homeFeatureQuery).first() %}
        {% if homeFeature %}<h1>The feature id is: {{ homeFeature.id }}</h1>{% endif %}
    </div>
{% endblock %}

{% block content %}
    {% set homeFeature = craft.entries(homeFeatureQuery).first() %}
    {% set entries = craft.entries({
        section:        'articles',
        order:          'postDate',
        limit:          4,
        id: homeFeature ? 'not '~homeFeature.id : null
    }) %}
    ...
{% endblock %}

Of course, the drawback with this approach is that you need to do two separate queries – but at least everything can be cached.

1
  • That's more or less the solution I came up with but I really appreciate your explaination. Makes it very clear where the different scopes come from and why it outputted what it did in testing.
    – Phil
    Commented Oct 24, 2016 at 23:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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