liquid icon indicating copy to clipboard operation
liquid copied to clipboard

Introduce a new concept to conditionally render block of content

Open bakura10 opened this issue 4 years ago • 1 comments

Hi :),

This is a proposal for a new Liquid feature that aimed to simplify code for complex templates. With increased complexity of themes, and new features such as dynamic metafield sources, writing DOM that stays clean requires more and more boilerplate code that does not scale well and can be error-prone.

Problem

Imagine you have a heading and subheading setting. In order to avoid generating useless elements in the DOM, one common approach is to wrap them around a condition:

{% if section.settings.title != blank or section.settings.subheading != blank %}
  <div class="section-header">
    {% if section.settings.title %}
       <h2>{{ section.settings.title }}</h2>
    {% endif %}

    {% if section.settings.subheading %}
       <h2>{{ section.settings.subheading }}</h2>
    {% endif %}
  </div>
{% endif %}

Another approach is to capture the content and then output it if not empty:

{% capture header %}
  {% if section.settings.title %}
     <h2>{{ section.settings.title }}</h2>
  {% endif %}

  {% if section.settings.subheading %}
     <h2>{{ section.settings.subheading }}</h2>
  {% endif %}
{% endcapture %}

{% if header != blank %}
  <div class="section-header">
    {{ header }}
  </div>
{% endif %}

None of those approach are optimal.

However, with the addition of dynamic source, it can be extremely complex to do that accurately. For instance, imagine a section with three "testimonial" block, each bound with a metafield.

In the past we could use the number of blocks to know if we should render things or not. However, now that we have metafields, one product may have one testimonial while another one may have three. This therefore becomes extremely complex to generate clean HTML.

Solution

I wish Liquid would have a native way to handle this specific use case. I think the Twig engine has a pretty nice abstraction around that: https://twig.symfony.com/doc/3.x/functions/block.html

Here is how it may look in Liquid (I have replaced the "block" word to "part", as block has specific meaning in context of Shopify). Here is how the previous example could be rewrite:

{% if part('header') != blank %}
  <div class="section-header">
    {% part 'header' %}
      {% if section.settings.title %}
       <h2>{{ section.settings.title }}</h2>
      {% endif %}

      {% if section.settings.subheading %}
         <h2>{{ section.settings.subheading }}</h2>
      {% endif %}
    {% endpart %}
  </div>
{% endif %}

With metafield, here is how it would block:

{% if part('products') != blank %}
   {% part 'products' %}
     {% for block in section.blocks %}
        {% assign product = block.settings.product %}
        {% if product != empty %}
          {% render 'product', product: product %}
        {% endif %}
     {% endfor %}
  {% endpart %}
{% endif %}

The language could have a built-in syntax for loops to make the syntax more concise:

{% for block in section.blocks as "products"%} // Would render into a "products" part

The main benefit would be to move the display logic outside of the parent, and allow to remove a lot of complex conditions to check before a specific element should be rendered or not.

This is something that is becoming increasingly important with metafields.

To show a slightly more complex example, here is a section that would show how this new syntax would help writing a more concise, metafields friendly section, that would render no useless DOM content:

{% if section.blocks.size > 0 %}
  {% if part('testimonials') !== blank %}
    <div class="section">
      {% if part('header') !== blank %}
        <div class="section-header">
          {% part 'header' %}
            {% if section.settings.title != blank %}
              <h2>{{ section.settings.title }}</h2>
            {% endif %}
            
            {% if section.settings.subtitle != blank %}
              <h3>{{ section.settings.subtitle }}</h3>
            {% endif %}
            
            {{ section.settings.content }}
          {% endpart %}
        </div>
      {% endif %}
    
      // Either this syntactic sugar, or by wrapping it with a part
      {% for block in section.blocks as 'testimonials' %}
        {% if block.settings.testimonial %}
          <p>{{ block.settings.testimonial }}</p>
        {% endif %}
      {% endfor %}
    </div>
  {%- endif -%}
{% else %}
  // Show on-boarding section experience
{% endif %}

bakura10 avatar Oct 03 '21 07:10 bakura10

I am pinging @Thibaut @tobi and @dylanahsmith as I have been asked to to have some feedback about this :)

bakura10 avatar Oct 04 '21 14:10 bakura10