question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Support loop filtering

See original GitHub issue

Jinja supports pre-filtering an array to be looped over, like so: {% for i in [1, 2, 3] if i > 1 %}

Nunjucks currently doesn’t support this.

It can be emulated by nesting the {% if %} within the for loop, but this workaround doesn’t interact correctly with {% else %} blocks on for loops (which I’ve implemented in #259). If you use a non-empty list in a {% for %} loop, but after applying filtering the list is empty, the {% else %} clause should be run. Getting this behavior without filtering built into the for loop itself is clunky and verbose.

I’m willing to work up a PR for this, but I’d like to see action on #259 first, since the implementations will interact (and almost certainly conflict if done independently).

Issue Analytics

  • State:open
  • Created 9 years ago
  • Reactions:10
  • Comments:13

github_iconTop GitHub Comments

5reactions
trevorrcommented, Oct 12, 2018

To extend @AmeliaBR’s suggestion, I used bind to pass additional arguments to the filter function:

env.addGlobal('propEquals', (k, v, o) => o[k] === v);

Remember that the first argument to bind is the this value, so you need a placeholder value such as null:

{% for myItem in myList.filter(propEquals.bind(null, 'myPropName', 'myPropValue')) %}
3reactions
AmeliaBRcommented, Jul 21, 2018

For cases when your array-filtering criteria can’t be defined by a pre-existing boolean attribute on list items, myList | selectattr(attrName) won’t work.

This is the solution I came to:

To filter a list in Nunjucks, you can use the usual JavaScript Array.prototype.filter method. That requires a function that returns boolean (or at least, truthy/falsy) values when it is passed each list item.

You need to pass the function, by reference, into the JS filter function.

{% for i in myList.filter(testFunction) %}
  {# run for each item in the list that passes the test function #}
{% else %}
  {# run if no items in the list pass the test function #}
{% endfor %}

But how do you define the testFunction?

The simplest option is to define a global function in your environment configuration. So this would be the “greater than one” test from the original issue:

env.addGlobal("testFunction", function(i){ return i>1; } );
// or, in arrow notation
env.addGlobal("testFunction", (i)=>(i>1) );

In this case, the filtering test is a simple global function, that doesn’t depend on the current template context.

If your test needs data from the context, Array.prototype.filter also allows you to define a this object (which is shared by all list items) as a second parameter. So this compares if the target value on the list items equals the path value from the current template context:

{% for i in myList.filter(testFunction, path) %}
...
env.addGlobal("testFunction", function(i){ return i.target === this; } );

Alternatively, this is the approach I ended up using. It’s not as clean as the this approach, but it’s more flexible, because I don’t need to encapsulate all the relevant context into a single object:

  • I define a test function in my template context as a macro. The macro resolves to an empty string or the literal string ‘false’ if I want the test to fail. (Be careful of extra whitespace here!!!) This macro does the same test as the previous example, if it is defined in the template context with the path value:

    {% macro targetMatches(item) %}{{ (item.target === path) or '' }}{% endmacro %}
    {# or #}
    {% macro targetMatches(item) %}{{ (item.target === path) }}{% endmacro %}
    
  • I define the following custom Nunjucks filter function in my configuration environment. It converts the macro result into a boolean that can be used as a JavaScript filter, and uses it to filter a list:

    let filterByMacro = function(list, macro) {
      return list.filter((item)=>{
        let result = macro(item);
        return result && result.val && (result.val !== 'false');
          // result of a macro is a SafeString object with its text value in the .val property
      });
    
  • I then filter (in the JavaScript sense) the list, by passing it to my custom filter (in the Nunjucks sense), referring to my custom macro by name as a parameter:

    {%- set filtered = myList | filterByMacro(targetMatches) %}
    {% for i in filtered | sort %}
    ...
    {# or skip the intermediary variable: #}
    {% for i in myList | filterByMacro(targetMatches) | sort %}
    ...
    

It’s a little messy & difficult to follow, but it gets the job done.

Of course, a nicer built-in way to do any of this would be very helpful.

PS, For anyone else using Metalsmith, you can set custom global functions or filter functions using the engineOptions parameter to metalsmith-layouts or metalsmith-in-place. The details for that options object are in the Readme for jstransformers-nunjucks

Read more comments on GitHub >

github_iconTop Results From Across the Web

Loop Filter - an overview | ScienceDirect Topics
The loop filter of a ΔΣ ADC provides the noise-shaping. In tunable bandpass ΔΣ ADCs, the resonance frequencies of the loop filter are...
Read more >
Adaptive Loop Filter - Medium
Adaptive Loop Filter (ALF) is one of in-loop filtering techniques from VVC. The main aim of VVC is to improve compression efficiency of...
Read more >
VVC In-Loop Filters | IEEE Journals & Magazine
Adaptive loop filter and cross-component adaptive loop filter are adaptive filters enabling to enhance the reconstructed signal, using for ...
Read more >
Filters and Sorting - dotloop support
Filters and sorting are two powerful functions on the My Loops page that allow you to see only the loops you want to...
Read more >
Fast Loop Filters - what are they and why are they used?
The sample is taken off from the side of the fast loop filter housing ... For further help or assistance with fast loop...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found