Using For Loop in Liquid to build accordion FAQ - shopify

I am pretty new to Liquid and coding in general. I found a tutorial that helped me build a custom section for my Shopify page that allows me to add questions and answers to a FAQ page.
My goal is to have different categories of questions. Using the following code, when clicking on a question (tab) in an instance other than the original, it opens the tab in the original section rather than the particular section that I am editing.
<div class="grid-item-100">
<div class="faq">
{%for block in section.blocks%}
<div class="tab">
<input type="checkbox" id="tab{{forloop.index}}" class="tab-toggle">
<label for="tab{{forloop.index}}" class="tab-label">{{block.settings.question}}</label>
<div class="tab-content">{{block.settings.answer}}</div>
</div>
{%endfor%}
I know that this is because of the forloop being used for the tab id's, I just don't know how to rewrite this for it to work specifically on the instance of the section that I want.
Here is the rest of the schema as well:
{% schema %}
{
"name": "FAQ-SECTION-2",
"settings": [],
"blocks" : [
{
"type":"text",
"name":"Question/Answer",
"settings":[
{
"id":"question",
"type":"text",
"label":"the question"
},
{
"id":"answer",
"type":"richtext",
"label":"answer"
}
]
}
]
}
{% endschema %}
Here is an image to help visualize the issue:

I recommend following this tutorial: https://easycodeguide.com/build-customizable-faqs-page-using-section-shopify.html
It allows for separators within it and then has separate cases for either it is looping through a separator or Q/A.

Related

Eleventy linking post author to author bio

I'm working on a site that essentially has posts, those posts have tags and also authors. there will only ever be one author per post.
I have the blogs working as intended as well as the tags, what I've hit a stumbling block with is relating an author key in my Frontmatter to their bio in contributors.json. Eventually this will be using Netlify CMS, so there will an "Add new author" form, then when an admin creates a new post, they can choose the author (some authors won't have access, they'll be guest contributors and just email the post over).
I can access both pages for my test authors and the correct posts are assigned, I just can't access the related data, when in the authors loop.
Anyway, my Frontmatter for authors.njk:
layout: base.njk
permalink: /authors/{{ author | slugify }}/
pagination:
data: collections.authorList
size: 1
alias: author
filter:
- all
- nav
- post
- posts
- tagList
eleventyComputed:
title: Posts written by {{ author }}
summary: "All posts written by “{{ author }}”"
Ideally, I'd need to access the data in the front matter, so I could use their actual name, as opposed to the key, in the page title etc.
A test post's Frontmatter:
title: "The fourteenth post"
summary: "A super useful post about something or other"
date: 2022-09-15
authors:
- jbloggs
tags:
- Android
- iOS
- MacOS
- Windows
In the above, my key is "jbloggs", I then have a global JSON file, like this:
[
{
"key": "jbloggs",
"name": "Joe Bloggs",
...
"img": {
"url": "/test.jpeg",
"alt": "Profile picture of Joe Bloggs"
}
},
...
]
In authors.njk, I have a card component which has a title, tags author etc and I'm correctly displaying all posts by jbloggs (I found I had to use set on a postAuthor variable and convert to JSON, as for some reason it was a JS object:
{% set postAuthor = null %}
{% for contributor in contributors %}
{% set postAuthor = contributor | toJson %}
{% if post.data.author == postAuthor.key %}
{%- set postAuthor = contributor[0] %}
{%- endif %}
{%- endfor %}
<h2>Showing {{ collections.authorList[author].length }} posts</h2>
<ul class="cards">
{%- for post in collections.authorList[author] | reverse -%}
<li class="card__item">
<article>
<h3 class="card__title">{{ post.data.title }}</h3>
...
<span class="card__author-name">
{{ postAuthor.name }}
...
</article>
</li>
{%- endfor %}
</ul>
Then finally, the .eleventy.js file creates a collection of authors, I tried using the same functionality as the tags, but it wouldn't display the posts and some Googling got me creating a collection in this way, which would be fine, if I didn't have related data.
function filterTagList(tags) {
return (tags || []).filter(tag => ["posts", "posts"].indexOf(tag) === -1);
}
eleventyConfig.addFilter("filterTagList", filterTagList)
eleventyConfig.addCollection("tagList", function(collection) {
let tagSet = new Set();
collection.getAll().forEach(item => {
(item.data.tags || []).forEach(tag => tagSet.add(tag));
});
return filterTagList([...tagSet].sort((a, b) => a.localeCompare(b, undefined, {sensitivity: 'base'})));
});
eleventyConfig.addCollection('authorList', collection => {
let authorSet = {};
collection.getAll().forEach(item => {
if (!item.data.authors) return;
item.data.authors.filter(
author => !['posts', 'all'].includes(author)
).forEach(
author => {
if (!authorSet[author]) { authorSet[author] = []; }
authorSet[author].push(item)
}
);
});
return authorSet;
});
What I think is happening, is the loop in the authors.njk file is converting my JSON to a js object, when it grabs the collection from config. As if I use {{ author.name }}, I get [object Object] hence why I ended up attempting to set to a new variable. I'm at that stage where I'm trying all sorts, to no avail, hence my ridiculous attempt with the new variable, so I know my I'm going about this in a complete noob type way.
What I need to do is:
Have access to the related JSON in the Frontmatter, so I can have a page title that is the persons full name, not the "key" I have set in Frontmatter
Query whether the Frontmatter key matches the JSON key, so I can access that data, to create an author bio at the top of the page and the authors/author urls are correctly generated on the post cards
But there is probably a much better way of doing this, that I am not seeing, so any help would be greatly appreciated.
Thank you
I managed to solve it, with a bit more Googling. I was over-complicating things with in my .eleventy.js file, but thanks to the chap who wrote this blog, I was able to refactor everything and got it working as I wanted.

Shopify text translation

My website is made by shopify and I use the dawn theme.
I've been trying for a few days to translate the word "Color" from inside the product pages into my language "Culoare". I looked for a solution on the internet, but I didn't find it. I don't want to translate the whole website because it is translated, I'm interested in translating only the variant picker metafield, the "Color" and "Size" options.
Thank you in advance.
You can put this code inside your main-product.liquid so that you can use the t filter for translation:
{% if option.name == 'Colors' %}
{% assign color_label = 'products.product.colors' %}
<legend class="form__label">{{ color_label | t }}</legend>
{% else %}}
<legend class="form__label">{{ option.name }}</legend>
{% endif %}
Then in your ro-RO.json add the reference to the translation:
"products": {
"product": {
"add_to_cart": "Adăugați în coș",
"colors": "Culoare"
}
}
It's the solution I came up with. Maybe there is a better solution.

vue v-model does not seem to be working in modal

I am pretty new to vue, and am trying to use it in a bootstrap modal. The relevant div in the modal is as follows.
<div class="form-group row">
<label for="priceQCField" class="col-sm-2 col-form-label">Price<span class="red"> *</span></label>
<input type="number" step="0.01" class="form-control col-sm-4" id="priceQCField" name="priceQCField" min="0" v-model="job.price">
</div>
I read some other questions about vue returning strings rather than numbers, so I have converted the job.price to a number inside my method to call the modal
showPriceJob: function (job) {
this.job = job;
this.job.price = parseFloat(this.job.price);
$('#mdlPriceJob').modal('show');
},
However, job.price refuses to appear in the input field either as a string or a number. I know it is available to the modal as I can see it using <span>{{job.price}}</span>.
Can anyone advise me please?
Additional - I think it is a display issue - if I change the input field, the entry in the <span> changes
2nd update - initial table
<tr class="light-grey" v-for="job in jobs" v-on:click="viewJob(job)">
<td>{{job.id}}</td>
<td>{{job.customerName}}</td>
<td>{{job.description}}</td>
<td v-bind:class="job.dueDate | dateColour">{{job.dueDate | dateOnly}}</td>
<td>£{{job.price}} {{job.isEstimate | priceEstimated}}</td>
<td>{{job.delivery}}</td>
</tr>
Upd.
According to your comments to my answer you are using v-for and you can't use this.job within your method. You should give us more code to see the whole picture.
Upd.2
You have showed more code but I didn't see any v-for so I am confused. You can try to use something like this if job is a property of appData.jobs:
showPriceJob: function (job) {
this.appData.jobs.job = Object.assign({}, job);
this.appData.jobs.job = parseFloat(this.appData.jobs.job.price);
$('#mdlPriceJob').modal('show');
},
But I'm not sure about this because I don't see where job is declared.
Upd.3
Oh! Wait! You have this code:
data: appData.jobs, but data should be in this format:
data: function(){
return {
appData: {
jobs: [],
},
}
},
Or show me what is your appData.jobs variable is.

Scope a single input in Vue built from JSON

I am building a simple quiz with vuejs. I've been working in https://codepen.io/jasonflaherty/pen/NBaJLO on this. I am able to get the correct responses to the checked input, however, it is not scoped to this.input and cascades to all radio buttons. I tried to use bind class with:
<span class="" v-bind:class="{ 'badge badge-success' : isCorrect, 'badge badge-danger' : isWrong, 'showthis' : showIt }">{{response.correct}}</span>
and am currently using:
v-if="isCorrect"
v-if="isWrong"
However, it is the same issue with the scope bound to the same input vs all of them.
What am I missing in vue to make this distinction?
You need to track at the question level rather than the global level.
Here are a couple simple changes you can make. Notice the input changes for value and v-model and also the conditions for the correct/wrong spans. This creates a property, selection on each question to track the currently selected answer for this specific question.
<li v-for="response in question.responses">
<label class="form-check-label">
<input class="form-check-input" type="radio"
:key="index"
:name="question.group"
:value="response.text"
v-model="question.selection">
{{response.text}}
<span v-if="question.selection === response.text && response.correct ==='Correct!' ">
Correct
</span>
<span v-else-if="question.selection === response.text && response.correct !=='Correct!' ">
Wrong
</span>
</label>
</li>
You could clean it up some more by altering your data model. There is no reason to save the correct text in the data. You can simply have the answer as a property and determine the textual response based on the selection.
var quizquestions = {
questions: [
{
text: "Subtract 219 from 500.",
group: "qone",
responses: [281, 719, 218, -219],
answer: 281, // correct answer
selection: null, // for v-model to track selected
},
]
};

Iterating through array in SendGrid email template

I'm trying to iterate through a collection and display information in a SendGrid template using Ruby on Rails.
recipient = SendGrid::Recipient.new("sergio#gmail.com")
recipient.add_substitution("username", user.github_id)
recipient.add_substitution("numbers", [1,2,3,4])
In gmail, this template arrives as:
sergiotapia
ARRAY(0x85b9d90)
The actual code for the template, copied from SendGrid's editor:
<html>
<head>
<title></title>
</head>
<body>
<div><%body%></div>
<div>username</div>
<div>numbers</div>
<p>This is a small example email.</p>
</body>
</html>
How can I iterate through a generic array or object in a SendGrid template? For this particular example, a user has many posts and I just want to show the title of the user's posts in a <li> element.
I'm just trying things out with a simple number array to see how it SendGrid works.
Update August 2018:
Sendgrid now offers iterators from the transactional email using handlebars, here's to the docs for more info:
https://sendgrid.com/docs/for-developers/sending-email/using-handlebars/#basic-iterator
Iterate example for the data:
{
"people":[{"name":"Bob"},{"name":"Sally"}]
}
Code:
{{#if people}}
<p>People:</p>
{{#each people}}
<p>{{this.name}}</p>
{{/each}}
{{/if}}
Result:
People:
Bob
Sally
{{#each data.products}}
{{name}}: {{price}} <br/>
{{/each}}
{"data":{"products": [{"name": "Tomato", "price": "5"}, {"name": "Banana", "price": "8"}]}}
Update
SendGrid now has support for dynamic templates!
You can read about it on their blog:
https://sendgrid.com/blog/how-to-use-sendgrids-dynamic-templates-for-your-transactional-emails/
Old answer:
Searching for this resulted the following GitHub issue. So it's not possible with SendGrid (yet?).
However there are other ways to do this. Using sendwithus you get access to a more powerful template editor that supports looping and iterating.
Simply set it up using your own SendGrid API key and you will be able to use the arrays in the sendwithus template which will send the mail using SendGrid.
Unfortunately the templates SendGrid provides are pretty minimal at this time. The templates don't support arrays as values and there are no conditional or looping controls, so you'd need to predetermine everything prior to building the template and template content. A more robust templating system is coming soon.
Here is a workaround Sendgrid is yet to update their template engine for this
Hello guys i need to do some iteration in my sendgrid mail and ran into this issue for now i had a temporal workaround which solved the problem. here is how i worked around it
created a txt file and loaded my html template there
Note areas i wanted to iterate on i replaced with a sendgrid variable e.g %list of items%
read the txt file into a string create a string builder and pass all iterated object into the %list of items% variable
then send the whole string content as message through sendgrid
public void sendSimpleMessage(String message,
String subject,
String toEmail,
String fromEmail){
Email from = new Email(fromEmail);
Email to = new Email(toEmail);
Content content = new Content("text/html", message);
Mail mail = new Mail(from, subject, to, content);
SendGrid sg = new SendGrid(sendgridApiKey);
Request request = new Request();
try {
request.method = Method.POST;
request.endpoint = "mail/send";
request.body = mail.build();
sg.api(request);
} catch (IOException ex) {
ex.printStackTrace();
}
}
hope it helps someone
https://github.com/sendgrid/sendgrid-nodejs/issues/221#issuecomment-361489007
"businessContributors" : [
{
"total" : {
"amount" : 11340,
"title" : "Dhama Ji Total"
},
"list" : {
"Dhama Ji Paid" : -296310,
"HDFC Account" : 0,
"Dhama Ji Received" : 307650
},
"title" : "Dhama Ji Owner Account"
},
{
"total" : {
"amount" : -1270,
"title" : "Rahul Total"
},
"list" : {
"Rahul Paid" : 243838,
"Rahul Received" : 242568
},
"title" : "Rahul Account"
},
]
in email template :-
<h4>Business Contributors </h4>
<ul>
{{#each businessContributors}}
<li> {{this.title}} <br>
{{#each this.list}}
{{#key}} = {{this}} <br>
{{/each}} </li>
<hr style="height: 2px; background-color: black;"><br>
<h2>{{this.total.title}}</h2><br>
<h2>{{this.total.amount}}</h2>
<br><br>
{{/each}}
</ul>