1

I have a range of products on a product collection page. Each product has a matrix block (ageRange) with a group of checkboxes (values 1-10).

I am currently trying to get loop through all products, get the matrix block values and push them into a new array, with the amount of times the value appears!

{% set myArray2 = [] %}
{% for product in craft.commerce.products({relatedTo: category}) %}
  {% for block in product.ageRange %}
    {% for option in block.options %}
      {% set myArray2 = myArray2|merge({ ('key'): option }) %}
    {% endfor %}
  {% endfor %}
{% endfor %}

{{dump(myArray2)}

This seems like a very bad approach. Even if I can get this working, it is clunky and no doubt un-performant.

Essentially I am looking for a way to attached fields to products, and easily get the total amount of times that that field is used.

Have I overlooked something simpler? if not, is twig the right place to do this?

Desired output would be:

  • 1 (6)
  • 2 (3)
  • 3 (3)
  • 4 (6)
  • 5 (0) // i'd hide this
  • 6 (1)
1

Ok, so It's totally possible to do this utilising the array|merge feature of twig. Firstly, we do a for loop over all our products (or whatever), loop over our matrix block(s), loop of the fields(s) and spit those values into a new array. We should end up with every value applied to every product in a big array:

{% set ageArray = [] %} // array for all your values
{% for product in craft.commerce.products.relatedTo(category) %}
  {% for block in product.ageRange %}  
    {% for option in block.options %} 
      {% set ageArray = ageArray|merge({ (loop.index): option.value }) %} 
    {% endfor %}
  {% endfor %}

for me, I had multiple values set from 1-10 on all products, so ended up with something like: [2,3,4,5,6,3,4,5,6,7,1,2,3,4] etc etc


Now we sort and loop over this new array, counting how many times we come across the same number. The original value, as well as the amount of times we count it, gets spat into another array:

{% set counter = 1 %}
{% set ageArray = [] %}
{% for age in ageArray|sort %}
  {% if currentAge is defined and currentAge != age %}
    {% set counter = 1 %}
  {% endif %}
  {% if currentAge is defined and currentAge == age %}
    {% set counter = counter + 1 %}
  {% endif %}
  {% set newArray = newArray|merge({ ('age'~age): {'age': age, 'counter': counter} }) %} 
  // ('age'~age) has to be string key, integer wont overwrite
  {% set currentAge = age %}
{% endfor %}

CurrentAge is basically the last iteration's value, so we do some checks against the current value and either reset the counter or increment. If it is the first time we see this number - the counter should be 1. If it matches, we add 1 on!

We then merge these values into the final array, overwriting any with the same key. Finally, we update currentAge with the actual new value, ready for the next iteration.


finally, you can just use this new array:

<ul>
  {% for reducedAge in newArray %}
    <li>
      <a href="{{ craft.request.getUrl()}}?ageRange={{reducedAge.age}}">
         {{ reducedAge.age }} ({{reducedAge.counter}})
      </a>
    </li>
  {% endfor %}
</ul>

Twig really does not seem like a clever place for this logic. However, this will provide a nice fallback for an ElementApi / JS solution. I'm surprised it wasn't easier, I'd have though alot of people need this sort of functionality!

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.