Multiple Level nested section blocks

Solved

I'm trying to build a section managed part of a product page, where the merchant can input a bunch of FAQ question/answer items based on product type. The mockup has 3 different categories, each with their own list of FAQs.

 

I'm a bit stuck on how to make the number of FAQ's under each section be dynamically added (ex, Formula could have 9 FAQs, but Porridge could have 2).

 

Currently, the below code just adds a bunch of buttons, and returns an "invalid JSON tag in 'schema'.

 

It seems like section blocks only go 1 level deep from my experience. Is there a way to create a multi level section? Any suggestions on how to rework this?

 

[screenshot of shopify section][https://imgur.com/a/LcWEXZ6]

[mockup][https://imgur.com/a/BABFRwK]

 

<div class="faqs">
	{% for block in section.blocks %}
	{%- assign titleIndex = 0 -%}	
		
		{% if block.type == "faqHeading" %}
		  <h2 class="title faq--heading">{{ block.settings.faqHeading }}</h2>
		{% endif %}

		<div data-tab-component>
		  <div role="tablist" aria-label="Tabbed content">
			{% if block.type == "faqSection" %}
			    <button role="tab" 
			            aria-selected="true" 
			            aria-controls="tab1-content" 
			            id="tab1"
			            class="tab__button">
			      {{ block.settings.faqSection }}
			    </button>
		    {% endif %}
		</div>
	  
	  {%- assign contentIndex = 0 -%}
			
			{% if block.type == "faqSection" %}	  	
				{%- assign contentIndex = 0 | plus: 1 -%}	
				<section id="faq__tab-content tab{{ contentIndex }}-content" 
			        	role="tabpanel" 
			        	aria-labelledby="tab{{ contentIndex }}" 
			        	tabindex="0"
			        	aria-hidden="true">

			        	{%- assign faqIndex = 0 -%}
			        	{% for block in section.blocks %}
				        	{% if block.type == "FAQQandA" %}
					        	<dl class="faqAccordion">
					        		{% for block in section.blocks %}
					        			{%- assign faqIndex = 0 | plus: 1 -%}
						        		<dt><button type="button" aria-controls="panel-01" aria-expanded="true">{{ block.settings.faqQuestion }}</button></dt>
								        	
							            <dd id="panel-0{{ faqIndex }}" aria-hidden="false">
							              {{ block.settings.faqAnswer }}
							            </dd>
						            {% endfor %}
					            </dl>
				        	{% endif %}
			        	{% endfor %}
				</section>
			{% endif %}
	{% endfor %}
</div>

{% schema %}
{
	"blocks": [
		{
			"name": "FAQ Heading",
			"type": "FAQHeading",
			"limit": 1,
			"settings": [
				{
					"type": "text",
					"id": "faqHeading",
					"label": "FAQ Title",
					"default": "FAQs"
				}
			]
		},
		{
		  "name": "FAQ Section ",
		  "type": "faqSection",
		  "settings" : [
			    {
			    	"type": "text",
			  		"id": "faqSection",
			    	"label": "FAQ Product Type"
					"blocks": [
					    {
							"type": "text",
							"name": "Question"
							"settings": [
								{
									"type": "text",
									"label": "Enter Question",
									"id": "faqQuestion",
									"default": "FAQ question content goes here"
								},
								{
									"type": "textarea",
									"label": "Enter Answer",
									"id": "faqAnswer",
									"default": "Lorem ipsum dolor amit icit"
								}
							]
					    } 
				    ]
			    }
			]
		}
	]
}
{% endschema %}

 

 

Problem Solved? ✔️Accept the solution so you can help others.
Confused? Busy? ? I can help! Email: hello@arqdesign.studio.
Buy me a coffee? ☕ paypal.me/arqdesignstudio
0 Likes

Success.

Answering my own question in case anyone needs an answer!

 

Short answer: no, sections can only go 1 level. Shopify doesn't support multi-level sections.

 

Long answer: You can't associate sections to other sections. For this use case however, making a dropdown list of 2 options, I was able to pass the selected option from that dropdown into the FAQ to make it associated to the correct category. It's an extra step and is important to note that this would be a nightmare for the merchant to manage if this approach was used for multiple products. Only 3 products are here and less the 30 FAQ's, so the tradeoff was acceptable in this scenario.

 

So, [section 2] - name the category:

category Names

[text area] A

[text area] B

[text area] C

 

Section 2 - FAQ

assign to a category

[DROPDOWN LIST. Options: A, B C]

[FAQ Question]

[FAQ Answer]

 

And the loop would look for the category name in an if statement, and then return the code.

 

 

<div class="faqs" itemscope itemtype="https://schema.org/FAQPage">
	{% for block in section.blocks %}
		{% if block.type == "FAQHeading" %}
		  <h1 class="title faq--heading text-center">{{ block.settings.faqHeading }}</h1>
		{% endif %}
	{% endfor %}

	<div data-tab-component>
		<div class="faq__tabList" role="tablist" aria-label="Tabbed sections">
			{% for block in section.blocks %}
				{% if block.type == "faqCategories" %}
				    <button role="tab" 
				            aria-selected="true" 
				            aria-controls="tab1-content" 
				            id="tab1"
				            class="tab__button">
				      {{ block.settings.faqProductType1 }}
				    </button>
				    <button role="tab" 
				    		aria-selected="false" 
				            aria-controls="tab2-content" 
				            id="tab2"
				            class="tab__button">
				      {{ block.settings.faqProductType2 }}
				    </button>
				    <button role="tab" 
				    		aria-selected="false" 
				            aria-controls="tab3-content" 
				            id="tab3"
				            class="tab__button">
				      {{ block.settings.faqProductType3 }}
				    </button>
				{% endif %}
			{% endfor %}
		</div>

				
				
				
		<section 
			class="faq__tabPanel"
			id="tab1-content" 
	    	role="tabpanel" 
	    	aria-labelledby="tab1" 
	    	tabindex="0">
				
		    <div data-tab-component>
				<div role="tablist" aria-label="Tabbed sections">
					<dl class="faqAccordion">
						{% for block in section.blocks %}	       
			   				{%- assign type = block.settings.productType -%} 
							
							{%- if type contains "CategoryA"  -%}
								 
					            <dt 
					            	itemscope 
					            	itemprop="mainEntity" 
					            	itemtype="https://schema.org/Question">

						            <button 
											itemprop="name"
						            		role="tab" 
								            aria-selected="true" 
								            aria-controls="faq{{ block.id }}-content" 
								            id="faq{{ block.id }}"
								            class="accordion_heading faq-list">
							        		{{ block.settings.faqQuestion }}
					        		</button>
				        		</dt>
				        		<dd 
									itemscope 
									itemprop="acceptedAnswer" 
									itemtype="https://schema.org/Answer"
				        			class="success_stories_data"
					            	role="tabpanel" 
					            	id="faq{{ block.id }}-content"
							    	aria-labelledby="faq{{ block.id }}" 
							     	tabindex="0"
							     	aria-hidden="true">
							    	<p itemprop="text">
							    		{{ block.settings.faqAnswer }}
							    	</p>
							   </dd>

			  				{%- endif -%}
			            {% endfor %}
		        	</dl>
	            </div>
	        </div>

		</section>

		<section id="tab2-content" 
	       role="tabpanel" 
	       aria-labelledby="tab2" 
	       tabindex="0"
	       aria-hidden="true">
			<div data-tab-component>
				<div role="tablist" aria-label="Tabbed sections">
					<dl class="faqAccordion">
						{% for block in section.blocks %}	       
			   				{%- assign type = block.settings.productType -%} 
							
							{%- if type contains "CategoryB"  -%}
								 
					            <dt 
					            	itemscope 
					            	itemprop="mainEntity" 
					            	itemtype="https://schema.org/Question">

						            <button 
											itemprop="name"
						            		role="tab" 
								            aria-selected="true" 
								            aria-controls="faq{{ block.id }}-content" 
								            id="faq{{ block.id }}"
								            class="accordion_heading faq-list">
							        		{{ block.settings.faqQuestion }}
					        		</button>
				        		</dt>
				        		<dd 
									itemscope 
									itemprop="acceptedAnswer" 
									itemtype="https://schema.org/Answer"
				        			class="success_stories_data"
					            	role="tabpanel" 
					            	id="faq{{ block.id }}-content"
							    	aria-labelledby="faq{{ block.id }}" 
							     	tabindex="0"
							     	aria-hidden="true">
							    	<p itemprop="text">
							    		{{ block.settings.faqAnswer }}
							    	</p>
							   </dd>

			  				{%- endif -%}
			            {% endfor %}
		        	</dl>
	            </div>
	        </div>
		</section>
		<section id="tab3-content" 
	       role="tabpanel" 
	       aria-labelledby="tab3" 
	       tabindex="0"
	       aria-hidden="true">
			<div data-tab-component>
				<div role="tablist" aria-label="Tabbed sections">
					<dl class="faqAccordion">
						{% for block in section.blocks %}	       
			   				{%- assign type = block.settings.productType -%} 
							
							{%- if type contains "CategoryC"  -%}
								 
					            <dt 
					            	itemscope 
					            	itemprop="mainEntity" 
					            	itemtype="https://schema.org/Question">

						            <button 
											itemprop="name"
						            		role="tab" 
								            aria-selected="true" 
								            aria-controls="faq{{ block.id }}-content" 
								            id="faq{{ block.id }}"
								            class="accordion_heading faq-list">
							        		{{ block.settings.faqQuestion }}
					        		</button>
				        		</dt>
				        		<dd 
									itemscope 
									itemprop="acceptedAnswer" 
									itemtype="https://schema.org/Answer"
				        			class="success_stories_data"
					            	role="tabpanel" 
					            	id="faq{{ block.id }}-content"
							    	aria-labelledby="faq{{ block.id }}" 
							     	tabindex="0"
							     	aria-hidden="true">
							    	<p itemprop="text">
							    		{{ block.settings.faqAnswer }}
							    	</p>
							   </dd>

			  				{%- endif -%}
			            {% endfor %}
		        	</dl>
	            </div>
	        </div>
		</section>
	</div>
</div>

{% schema %}
{
	"blocks": [
		{
			"name": "FAQ Heading",
			"type": "FAQHeading",
			"limit": 1,
			"settings": [
				{
					"type": "text",
					"id": "faqHeading",
					"label": "FAQ Title",
					"default": "FAQs"
				}
			]
		},
		{
			"name": "FAQ Categories",
			"type": "faqCategories",
			"settings": [
				{
					"type": "text",
					"label": "faqProductType1",
					"id": "faqProductType1",
					"default": "Formula"
				},
				{
					"type": "text",
					"label": "faqProductType2",
					"id": "faqProductType2",
					"default": "Porridge"
				},
				{
					"type": "text",
					"label": "faqProductType3",
					"id": "faqProductType3",
					"default": "Snack Puffs"
				}
			]
		},
		{
			"name": "FAQ Q and A",
			"type": "faqQandA",
			"settings": [
				{
					"type": "select",
					"id": "productType",
					"options": [
				      { "value": "CategoryA", "label": "CategoryA"},
				      { "value": "CategoryB", "label": "CategoryB"},
				      { "value": "CategoryC", "label": "CategoryC"}
					],
					"label": "Select Product"					
				},
	            {
	                "type": "text",
	                "label": "Enter Question",
	                "id": "faqQuestion",
	                "default": "FAQ question content goes here"
	            },
	            {
	                "type": "textarea",
	                "label": "Enter Answer",
	                "id": "faqAnswer",
	                "default": "Lorem ipsum dolor amit icit"
	            }
			]
		}
	]
}
{% endschema %}

I'm sure there's a clever way to nest another loop so you would only have to write the output HTML for the tabs and accordion groups once which would make this more maintainable.

 

This is also a WCAG AA accessible accordion structure. Here's the Vanilla JS.

{% javascript %}
/**
 * @function Tabs()
 *
 * @param args {object} Settings for controlling the functionality of the component
 * @returns bindEventListeners {function} Event listeners for the component
 */

function Tabs(args) {
  // Scope-safe constructors
  if (!(this instanceof Tabs)) { 
    return new Tabs();
  }
  
  /**
   * Default component settings
   *
   * @param container {string} Classname for container of the entire component
   * @param trigger {string} Element that toggles content
   * @param content {string} Classname for the content
   */
  var defaults = {
    container: '[data-tab-component]',
    trigger: '[role="tab"]',
    content: '[role="tabpanel"]'
  };

  // If there are no settings overrides
  var settings = (typeof args !== 'undefined') ? args : defaults;

  /**
   * @function toggle()
   *
   * Handles the displaying/hiding of content
   *
   * @returns null
   */
  var toggle = function() {
    var parent = this.closest(settings.container),
        target = this.getAttribute('aria-controls'),
        content = document.getElementById(target),
        toggles = parent.querySelectorAll(settings.trigger),
        all_content = parent.querySelectorAll(settings.content);

    // Update visibility
    for (var i = 0, len = toggles.length; i < len; i++) {
      toggles[i].setAttribute('aria-selected', 'false');
      all_content[i].setAttribute('aria-hidden', 'true');
    }
    
    this.setAttribute('aria-selected', 'true');
    content.setAttribute('aria-hidden', 'false');
  };

  /**
   * @function bindEventListeners()
   *
   * Attach event listeners
   *
   * @returns null
   */
  var bindEventListeners = function() {
    var trigger = document.querySelectorAll(settings.trigger);
    
    //
    // TODO
    // Use event delgation to add event handlers
    //
    for (var i = 0, len = trigger.length; i < len; i++) {
      trigger[i].addEventListener('click', function(event) {
        toggle.call(this);
      });
      
      trigger[i].addEventListener('keydown', function(event) {
        if (event.which == 13) {
          toggle.call(this);
        }
      });
    };
  };

  return bindEventListeners();
}

// Create an instance of component
window.onload = function() {
  var tabs = new Tabs();
};
{% endjavascript %}

 

Problem Solved? ✔️Accept the solution so you can help others.
Confused? Busy? ? I can help! Email: hello@arqdesign.studio.
Buy me a coffee? ☕ paypal.me/arqdesignstudio
0 Likes