Happening now | Office Hours: Customizing Your Theme With Moeed | Ask your questions now!

FAQ Section on collection page

FAQ Section on collection page

ChrisW3
Trailblazer
284 1 20

Tried to add an FAQ section to our collection using the following code:

Sections/faq.liquid

 

{%- comment -%}
Section published at https://sections.design/blogs/shopify/faq-rich-snippets-section
Get the latest version: https://github.com/mirceapiturca/Sections/tree/master/FAQ
{%- endcomment -%}


{%- comment -%} ---------------- THE CSS ---------------- {%- endcomment -%}

{%- assign id = '#shopify-section-' | append: section.id -%}

{% style %}
  {{ id }} {
    background: {{ section.settings.background_color }};
    --panel-bg: {{ section.settings.panel_color }};
    --border-color: {{ section.settings.border_color }}; 
    --question-color: {{ section.settings.q_color }};
    --answer-color: {{ section.settings.a_color }};

    {%- assign min = section.settings.q_size_small -%}
    {%- assign max = section.settings.q_size_large -%}
    {%- assign min_rem = min | append: 'rem' -%}
    {%- assign max_rem = max | append: 'rem' -%}
    --title-font-size: clamp({{ min_rem }}, calc({{ min_rem }} + ({{ max }} - {{ min }}) * ((100vw - 25rem) / (64 - 25))), {{ max_rem }});
  }
{% endstyle %}

<style>
  .flex { display: flex }
  .items-center { align-items: center }
  .justify-between { justify-content: space-between }
  .w-full { width: 100% }
  .text-left { text-align: left }
  .m-0 { margin: 0 }
  .p-0 { padding: 0 }
  .p-4 { padding: 1rem }
  .overflow-hidden { overflow: hidden }
  .cursor-pointer { cursor: pointer }

  {{ id }} .faq-container {
    max-width: {{ section.settings.max_width }};
    margin: {{ section.settings.margin_top }}rem auto {{ section.settings.margin_bottom }}rem
  }
  
  {{ id }} .faq-title {
    border-bottom: 1px solid  var(--border-color);
    font-size: var(--title-font-size);
    color: var(--question-color);
  }
  
  {{ id }} .faq-panel {
    will-change: height;
    border-bottom: 1px solid var(--border-color);
    background-color: var(--panel-bg);
    color: var(--answer-color);
  }
  
  .faq-button {
    font: inherit;
    background: transparent;
    border: 0;
  }
  
  .faq-icon {
    width: clamp(12px, 0.65em, 20px);
    height: clamp(12px, 0.65em, 20px);
    min-width: clamp(12px, 0.65em, 20px);
    margin-left: 1rem;
  }
  
  .faq-icon-minus {
    transition: transform 240ms cubic-bezier(0.4, 0.0, 0.2, 1);
    transform-origin: 50% 50%;
  }
  
  .faq-button[aria-expanded="true"] .faq-icon-minus {
    transform: rotate(90deg);
  }
  
  .faq-panel * {
    color: inherit;
  }
  
  .faq-panel[data-is-animating] {
    display: block!important;
  }
</style>


{%- comment -%} ---------------- THE MARKUP ---------------- {%- endcomment -%}


<div class="faq-container">
{%- for block in section.blocks -%}
  {%- if block.settings.title != blank and block.settings.content != blank  -%}
  
    {%- if block.settings.checkbox_expanded == true -%}
      {%- assign expanded = 'true' -%}
      {%- assign hidden = '' -%}
    {%- else -%}
      {%- assign expanded = 'false' -%}
      {%- assign hidden = 'hidden' -%}
    {%- endif -%}
  
    <h2 class="faq-title m-0 p-0" data-faq-trigger="{{ block.id }}" {{ block.shopify_attributes }}>
      <button class="faq-button flex items-center justify-between w-full text-left m-0 p-4 cursor-pointer" data-faq-button="{{ block.id }}" aria-expanded="{{ expanded }}">
        <span>{{ block.settings.title }}</span>
        <svg class="faq-icon" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
          <path class="faq-icon-minus" fill="currentColor" d="M8 0v14H6V0z"></path>
          <path fill="currentColor" d="M0 6h14v2H0z"></path>
        </svg>
      </button>
    </h2>
  
    <div class="faq-panel rte overflow-hidden custom-bg" data-faq-panel="{{ block.id }}" {{ hidden }}>
      <div class="faq-wrap p-4">{{ block.settings.content }}</div>
    </div>
  
  {%- endif -%}
{%- endfor -%}
</div>


{%- comment -%} -------------- THE RICH SCHEMA ------------- {%- endcomment -%}

{%- if section.settings.enable_rich_schema -%}
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
  {%- for block in section.blocks -%}
  {%- if block.settings.title != blank and block.settings.content != blank  -%}
  {
    "@type": "Question",
    "name": {{ block.settings.title | json }},
    "acceptedAnswer": {
      "@type": "Answer",
      "text": {{ block.settings.content | strip_html | json }}
    }
  }{%- unless forloop.last -%},{%- endunless -%}
  {%- endif -%}
  {%- endfor -%}
  ]
}
</script>
{%- endif -%}


{%- comment -%} ---------------- THE CONFIG ---------------- {%- endcomment -%}

<script type="application/json" data-faq-config="{{ section.id }}" >
 {
   "sectionId": {{ section.id | json }},
   "blockIds": {{ section.blocks | map: 'id' | json }}
 }
</script>


{%- comment -%} ---------------- THE SETTINGS ---------------- {%- endcomment -%}

{% schema %}
{
  "name": "FAQ",
  "class": "sd-faq",
  "tag": "article",

  "settings": [
    {
      "type": "header",
      "content": "Rich schema"
    },
    {
      "type": "checkbox",
      "id": "enable_rich_schema",
      "default": true,
      "label": "Enable FAQ rich schema?"
    },
    {
      "type": "header",
      "content": "Dimensions"
    },
    {
      "type": "text",
      "id": "max_width",
      "label": "Max width",
      "default": "64rem"
    },
    {
      "type": "range",
      "id": "margin_top",
      "min": 0,
      "max": 10,
      "step": 0.1,
      "unit": "rem",
      "label": "Margin top",
      "default": 1
    },
    {
      "type": "range",
      "id": "margin_bottom",
      "min": 0,
      "max": 10,
      "step": 0.1,
      "unit": "rem",
      "label": "Margin bottom",
      "default": 1
    },
    {
      "type": "header",
      "content": "Colors"
    },
    {
      "type": "color",
      "id": "background_color",
      "label": "Background color",
      "default": "#ffffff"
    },
    {
      "type": "color",
      "id": "border_color",
      "label": "Border color",
      "default": "#eeeeee"
    },
    {
      "type": "color",
      "id": "panel_color",
      "label": "Panel color",
      "default": "#fdfdfd"
    },
    {
      "type": "header",
      "content": "Question"
    },
    {
      "type": "range",
      "id": "q_size_small",
      "min": 1,
      "max": 2,
      "step": 0.1,
      "unit": "rem",
      "label": "Small devices font size",
      "default": 1
    },
    {
      "type": "range",
      "id": "q_size_large",
      "min": 1,
      "max": 3,
      "step": 0.1,
      "unit": "rem",
      "label": "Large devices font size",
      "default": 1.4
    },
    {
      "type": "color",
      "id": "q_color",
      "label": "Question text color"
    },
    {
      "type": "header",
      "content": "Answer"
    },
    {
      "type": "color",
      "id": "a_color",
      "label": "Answer text color"
    }
  ],

  "blocks": [
    {
      "type": "faq",
      "name": "FAQ",
      "settings": [
        {
          "type": "checkbox",
          "id": "checkbox_expanded",
          "default": false,
          "label": "Expanded?"
        },
        {
          "type": "text",
          "id": "title",
          "label": "FAQ title",
          "default": "FAQ title"
        },
        {
          "type": "richtext",
          "id": "content",
          "label": "FAQ content",
          "default": "<p>FAQ content</p>"
        }
      ]
    }
  ],

  "presets": [
    {
      "name": "FAQ"
    }
  ]
}
{% endschema %}

{%- comment -%} ------------------ THE JS ----------------- {%- endcomment -%}

<script src="{{ 'faq.js' | asset_url }}" defer></script>


{%- comment -%} ---------------- THE NO-JS ---------------- {%- endcomment -%}

<noscript>
  <style>
    #shopify-section-{{ section.id }} [hidden] { display: block }
    .faq-icon { display: none }
  </style>
</noscript>


{%- comment -%} ---------------- THE EDITOR ------------------ {%- endcomment -%}

{%- if request.design_mode -%}
<script>
  (function FAQThemeEditor(SectionsDesign) {
    'use strict';
    
    document.addEventListener('shopify:section:load', sectionLoad);
    document.addEventListener('shopify:block:select', blockToggle);
    document.addEventListener('shopify:block:deselect', blockToggle);
    
    function sectionLoad(evt) {
      var sectionId = evt.detail.sectionId;
      var section =  SectionsDesign.faq[sectionId];
      
      if (!section) return;
      SectionsDesign.faq[sectionId] = section.init(sectionId);     
    }
    
    function blockToggle(evt) {
      var section = SectionsDesign.faq[evt.detail.sectionId];
      if (!section) return;
      
      var block = section.blocks[evt.detail.blockId];
      if (!block) return;
      
      evt.type === 'shopify:block:select' ? block.select() : block.deselect();
    }
      
  })(window.SectionsDesign = window.SectionsDesign || {});
</script>
{%- endif -%}

 

 assets/faq.js

 

(function FAQ(SectionsDesign) {
  'use strict';
  
  if (SectionsDesign.faq) return;
  SectionsDesign.faq = {};
  
  var support = getSupport();
 
  var init = function() {
    var configNodes = Array.from(document.querySelectorAll('[data-faq-config]'));
    
    configNodes.map(configNode => {
      var section = compose(publicAPI, setEvents, getBlocks, getConfig)(configNode);
      SectionsDesign.faq[section.id] = section;
    });
  }
  
  init();
 
  function publicAPI(config) {
    return {
      id: config.sectionId,
      config: config,
      blocks: zipObj(config.blockIds, config.blocks),
      init: init
    }
  }

  //**************************


  /**
   * Click event.
   * @param {Object} block Section block elements and methods.
   * @Return {Object} Section block elements and methods.
   */
  function blockEvents(block) {
    if (block.trigger.hasAttribute('data-faq-init')) return block;
    
    block.trigger.setAttribute('data-faq-init', true);
    block.trigger.addEventListener('click', triggerClick);
                                   
    function triggerClick() {
      toggle(block);
    }

    return block;
  }

  /**
   * Toggle block state.
   * @param {Object} block Section block elements and methods.
   * @Return {Object} Section block elements and methods.
   */
  function toggle(block) {
    block.collapsed ? expand(block) : collapse(block);
    return block;
  }
  
  /**
   * Expand block.
   * @param {Object} block Section block elements and methods.
   * @Return {Object} Section block elements and methods.
   */
  function expand(block) {
    block.button.setAttribute('aria-expanded', true);
    block.panel.removeAttribute('hidden');
    animate(block.panel, 'normal');
    return block;
  }
  
  /**
   * Collapse block.
   * @param {Object} block Section block elements and methods.
   * @Return {Object} Section block elements and methods.
   */
  function collapse(block) {
    block.button.setAttribute('aria-expanded', false);
    block.panel.setAttribute('hidden', '');
    animate(block.panel, 'reverse');
    return block;
  }
  
  /**
   * Collapse block.
   * @param {Object} block Section block elements and methods.
   * @Return {boolean} Collapsed block state.
   */
  function isCollapsed(block) {
    return Boolean(block.button.getAttribute('aria-expanded') === 'false');
  }

  /**
   * Collapse block.
   * @param {HTMLElement} element Block panel element to animate.
   * @param {String} direction Animation direction, normal or reverse.
   * @Return {undefined} Nothing to return.
   */
  function animate(element, direction) { 
    if (!support.WebAnimations) return;

    element.setAttribute('data-is-animating', true);

    element.animate([
      { height: 0 },
      { height: element.offsetHeight + 'px' }],

      { duration: 240,
        fill: 'both',
        easing: 'cubic-bezier(0.4, 0.0, 0.2, 1)',
        direction: direction
      }

    ).onfinish = function() {
      element.removeAttribute('data-is-animating');
      this.cancel();
    };
  }

  //**************************


  /**
   * Maps all block IDs to exposed block API.
   * @param {Object} config Section and section blocks IDs.
   * @Return {Object} config Section and section blocks IDs.
   */
  function getBlocks(config) {
    config.blocks = config.blockIds.map(block);
    return config;
  }

  /**
   * Create section block API.
   * @param {String} blockId Section Liquid block ID.
   * @Return {Object} block elements and methods.
   */
  function block(blockId) {
    return {
      trigger: document.querySelector('[data-faq-trigger="' + blockId + '"]'),
      button: document.querySelector('[data-faq-button="' + blockId + '"]'),
      panel: document.querySelector('[data-faq-panel="' + blockId + '"]'),

      get collapsed() { return isCollapsed(this) },
      select: function select() { return expand(this) },
      deselect: function deselect() { return collapse(this) }
    }
  }

  /**
   * Adds event listeners to block elements.
   * @param {Object} config Section and section blocks IDs.
   * @Return {Object} config Section and section blocks IDs.
   */
  function setEvents(config) {
    config.blocks.forEach(blockEvents);
    return config;
  }

  /**
   * Pass the Liquid assigned section variabiles.
   * @param {HTMLElement} element Configuration script node.
   * @Return {Object} Section and section blocks IDs.
   */
  function getConfig(node) {
    return JSON.parse(node.innerHTML);
  }

  /**
   * Feature detection.
   * @Return {Object} Browser support.
   */
  function getSupport() {
    return {
      WebAnimations: (typeof Element.prototype.animate === 'function')
    }
  }

  //**************************


  /**
   * Creates a new object out of a list of keys and a list of values.
   * Key/value pairing is truncated to the length of the shorter of the two lists.
   * @example
   *    zipObj(['a', 'b', 'c'], [1, 2, 3]); //=> {a: 1, b: 2, c: 3}
   * @param {Array} keys The array that will be properties on the output object.
   * @param {Array} values The list of values on the output object.
   * @Return {Object} The object made by pairing up same-indexed elements of `keys` and `values`.
   */
  function zipObj(keys, values) {
    return keys.reduce(
      function zipObj(acc, key, idx) {
        acc[key] = values[idx];
        return acc;
      }, {}
    )
  }

  /**
   * Performs right-to-left function composition.
   * The rightmost function may have any arity, the remaining functions must be unary.
   * @example
   *   function plus1(n) {return n + 1};
   *   function plus2(n) {return n + 2};
   *   compose(plus2,plus1)(1) => 4
   * @Return {Function} Composed function
   */
  function compose() {
    var funcs = Array.prototype.slice.call(arguments).reverse();
    return function() {
      return funcs.slice(1).reduce(function(res, fn) {
        return fn(res);
      }, funcs[0].apply(undefined, arguments));
    };
  }
  
})(window.SectionsDesign = window.SectionsDesign || {});

 

 

 

How do I get it to display on collection page?  I added the line {% include 'faq' %}  to collection template, but get the error Liquid error (sections/collection-template line 12): Could not find asset snippets/faq.liquid

Replies 6 (6)

Moeed
Shopify Partner
7682 2067 2546

Hey @ChrisW3 

 

The error occurs because you are using the {% include 'faq' %} syntax, which is designed to include snippet files. Instead of using {% include 'faq' %}, you should use the section inclusion syntax for a file located in the Sections folder.

{% section 'faq' %}

 

If I managed to help you then, don't forget to Like it and Mark it as Solution!

 

Best Regards,
Moeed

- Need a Shopify Specialist? Chat on WhatsApp

- Get a quick Shopify quote – Click here!

- Custom Design | Advanced Coding | Store Modifications


ChrisW3
Trailblazer
284 1 20

Now getting this

 

Liquid error (sections/collection-template line 131): Cannot render sections inside sections

Moeed
Shopify Partner
7682 2067 2546

I see, here's an alternate solution:

Use a Snippet Instead of a Section
Convert your FAQ section into a snippet. Snippets can be included within sections.

 

Steps:

1) Copy the code from Sections/faq.liquid.

2) Create a new snippet by going to Snippets > Add a new snippet and name it faq.

3) Paste the copied code into this new snippet file.

Now add this again where you added the {% section 'faq' %} code.

{% include 'faq' %}

 

If I managed to help you then, don't forget to Like it and Mark it as Solution!

 

Best Regards,
Moeed

- Need a Shopify Specialist? Chat on WhatsApp

- Get a quick Shopify quote – Click here!

- Custom Design | Advanced Coding | Store Modifications


ChrisW3
Trailblazer
284 1 20

Cant get this to work. 

 

Now getting this:

"Liquid syntax error (line 161): Unknown tag 'schema'"

Dan-From-Ryviu
Shopify Partner
11911 2339 2514

 {% include 'faq' %} will call a code in Snippets, not in Sections, and you cannot call a section into another section. Please try to check if your theme has a collapsible content and add to your Collections from Online Store > Themes > Customize > Collections instead of

 

- Helpful? Like & Accept solution!
- Ryviu - Product Reviews & QA app: Collect customer reviews, import reviews from AliExpress, Amazon, Etsy, Walmart, Dhgate and CSV.
- Lookfy Gallery: Lookbook Image - Gain customers with photo gallery, video & shoppable image.
- Reelfy‑Shoppable Videos+Reels: Create shoppable videos to engage customers and drive more sales.
- Enjoy 1 month of Shopify for $1. Sign up now.

ChrisW3
Trailblazer
284 1 20

I never got an answer to this