Eleventy linking post author to author bio - static-site

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.

Related

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.

Disable checkout if all products do not share same tag Shopify

I have a custom checkout experience in my Shopify store that I only want to allow if all products in the cart contain the tag "test"
This is the function I currently have, which seems to only work with a single item in the cart.
function productTags() {
{%- assign tagEnabled = false -%}
return {
{%- for item in cart.items -%}
{%- if item.product.tags contains 'test' -%}
"{{ item.product.tags }}": {{ item.quantity | json }}
{%- assign tagEnabled = true -%}
{%- endif -%}
{%- endfor -%}
};
}
this line ( "{{ item.product.tags }}": {{ item.quantity | json }}) is only there for display in the console when testing this. I can remove that if necessary.
How can I expand this to look for all item tags in the cart, and only assign the tagEnabled variable to true if all of them have the same tag?
Thanks in advance!
It looks like you are trying to mix Liquid with Javascript, which can make coding confusing. I would recommend splitting your code into two parts: one where you collect your Liquid variables and assign them to Javascript variables (and I see you are using the json filter to do that already, which is awesome - that filter guarantees that your variable output will be in a Javascript-legal format), and the second part where you write your Javascript code without any Liquid brackets getting in the way. (This is especially helpful if you are using a code editor with any syntax or error highlighting)
Let me know if the following helps you get the information you need:
// The Liquid filter 'map' lets you drill into nested object properties. We can use this to get an array of tag arrays.
// The JSON Liquid filter will turn any Liquid variable into a legal Javascript variable.
const itemProductTags = {{ cart.items | map: 'product'| map: 'tags' | json }}
// Check on our variables
console.log('All product tag arrays', itemProductTags)
console.log('Results of tagCheck()', tagCheck(itemProductTags, 'test') )
function tagCheck(tagsList, targetTag){
// Checks to see if the targetTag appears in all, some or none of the tag arrays.
// tagsList will be an array of arrays
var targetInAny = false;
var targetInAll = true;
for(var i=0; i<tagsList.length; i++){
var tags = tagsList[i];
if(tags.indexOf(targetTag) != -1){
// Found the tag - this targetTag appears in at least one product-tag list
targetInAny = true;
}
else{
// Did not find the tag - this targetTag is NOT in every product-tag list
targetInAll = false;
}
}
// Returns an object specifying if we found the target in any, all or none of the lists
return { targetInAny: targetInAny, targetInAll: targetInAll, targetInNone: !targetInAny }
}

Using For Loop in Liquid to build accordion FAQ

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.

How to add external Json as variables to shopify

In my online shop I want to show some more data about each product. I have managed to do this by adding an extra nodes to settings_data.json file. Like this:
{
"current": {
...
...
"7887193478": { //Product ID
"has-badge" => true,
"show-image" => false
...
},
"7887193479": { //Product ID
"has-badge" => true,
"show-image" => true
...
},
}
}
It is working and I am getting the data and showing it successfully.
The good thing about this solution is the page load is very fast.
But the problem is when some one updates the theme setting it overrides the settings_data.json file. Is there any way that I can import this extra json settings separably into my Shopify?
Please do not advise me with Metadata App. Metadata app is very slow and I am not interested to use it.
You can create a snippet products_data.liquid (or any other name you want) that stores the information you need, that's separate from the theme settings file, and won't be overwritten. Here's an example:
{% assign "7887193478-has-badge" = true %}
{% assign "7887193478-show-image" = false %}
{% assign "7887193479-has-badge" = true %}
{% assign "7887193479-show-image" = true %}
...
Then you'll want to include it somewhere in there theme.liquid file, above where you'd be using it:
{% include "products_data" %}
The downside is that it's not as clean in terms of data entry.

in SQL, or Django ORM, what's the conventional way to have an ordered one-to-many?

Say I wanted to have a project, and one-to-many with to-do items, and wanted to re-order the to-do items arbitrarily?
In the past, I've added a numbered order field, and when someone wants to change the order, had to update all the items with their new order numbers. This is probably the worst approach, since it's not atomic & required several updates.
I notice Django has a multi-valued CommaSeparatedIntegerField which could contain the order by storing the ordered keys to the items in the to-do items table right in one field of the project table.
I've pondered a dewey decimal system where if I wanted to take item 3 and put it between 1 and 2 I would change it's order number to 1.5.
Something tells me there's an easier option that I'm missing though...
How would you give order to a one-to-many relationship?
I hate this problem ... and I run into it all the time.
For my most recent Django site we had a Newsletter which contained N Articles and, of course, order was important. I assigned the default order as ascending Article.id, but this failed if Articles were entered in something other than "correct" order.
On the Newsletter change_form.html page I added a little bit of jQuery magic using the Interface plugin (http://interface.eyecon.ro/). I show the titles of the associated Articles and the user can drag them around as they like. There is an onChange handler that recomputes the Article.id's in article_order field.
Enjoy,
Peter
For app=content, model=Newsletter, the following is in
templates/admin/content/newslettter/change_form.html
{% extends 'admin/change_form.html' %}
{% block form_top %}{% endblock %}
{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="/media/js/jquery.js"></script>
<script type="text/javascript" src="/media/js/interface.js"></script>
<script>
$(document).ready(
function () {
$('ol.articles').Sortable(
{
accept : 'sortableitem',
helperclass : 'sorthelper',
activeclass : 'sortableactive',
hoverclass : 'sortablehover',
opacity: 0.8,
fx: 200,
axis: 'vertically',
opacity: 0.4,
revert: true,
trim: 'art_',
onchange:
function(list){
var arts = list[0].o[list[0].id];
var vals = new Array();
var a;
for (a in arts) {
vals[a] = arts[a].replace(/article./, '');
}
$('#id_article_order').attr('value', vals.join(','));
}
});
}
);
</script>
{% endblock %}
{% block after_related_objects %}
{% if original.articles %}
<style>
.sortableitem {
cursor:move;
width: 300px;
list-style-type: none;
}
</style>
<h4>Associated Articles</h4>
<ol class="articles" id="article_list">
{% for art in original.articles %}
<li id="article.{{art.id}}" class="sortableitem">{{art.title}}</li>
{% endfor %}
</ol>
{% endif %}
{% endblock %}
"added a numbered order field" - good.
"update all the items with their new order numbers" - avoidable.
Use numbers with gaps.
Floating point. That way, someone can insert "1.1" between 1 and 2. I find that this works nicely, as most people can understand how the sequencing works. And you don't have to worry too much about how much space to leave -- there's lots and lots of space between each number.
On the initial load, number the articles by the 100 or 1000 or something with space between each one. In this case, you have to guess how many digits to leave for reordering.
A comma-separated position. Initially, they're all (1,0), (2,0), (3,0), etc. But when you want to rearrange things, you might have to introduce (2,1) and (2,2) that go after (2,0) but before (3.0).
This looks kind of complicated, but some people like this kind of complexity. It's essentially the same as floating-point, except the single number is replace by a (whole-number, implicit-fraction) tuple. And this extends to handle hierarchies.
I have had this problem with two projects I've worked on in the last little while. For my example solution I have a "Form" that has many "Variables" assigned to it and the order of the variables on the form needs to be sortable. So I have implemented the following:
models.py
class Form(models.Model):
FormName = models.CharField(verbose_name="Form Name:", max_length=40)
VariableOrder = models.CommaSeparatedIntegerField(default="[]", editable=False)
def __unicode__(self):
return "%s" % (self.FormName)
class Variable(models.Model):
FormID = models.ForeignKey(Form, default=0, editable=False, related_name="Variable")
VarName = models.CharField(max_length=32, verbose_name="Name of variable in the database:")
def __unicode__(self):
return "%s" % self.VarName
The key from above is the VariableOrder CommaSeparatedIntegerField is where we are going to store the order of the Variables on the Form, and we are going to be using it as a python list, which is why the default is [].
For the template I render my Variables in an that we are going to make drag and drop sortable (the list elements I actually use have a ton more CSS related styling and information about the Variable).
<ul id="sortable">
{% for Variable in VarList %}
<li id="{{ Variable.id }}">{{ Variable }}</li>
{% endfor %}
</ul>
Now we are going to make the list drag and drop for the changing of order. For this to work you need to have the AJAX CSRF snippet from Django site in the head
$(function() {
$("#sortable" ).sortable({
placeholder: "ui-state-highlight",
update: function(event, ui){
$.ajax({
type:"POST",
url:"{% url builder.views.variableorder %}",
data: {Order: JSON.stringify($('#sortable').sortable('toArray')) },
success: function(data){
// Do stuff here - I don't do anything.
}
});
}
});
$( "#sortable" ).disableSelection();
});
The important part above is that "update" calls the function every time there is a position change of any of the variables, which sends the AJAX. toArray on sortable along with the JSON stringify gets us sending the top to bottom id's of each variable, which is used by the view as follows. Note: I keep the active Form object as a session variable, but in another case you would just need to call the Form object you were wanting to change the order of.
def variableorder(request):
if request.is_ajax():
Order = request.POST['Order']
updateOrder = request.session['FormID']
updateOrder.VariableOrder = newOrder
updateOrder.save()
request.session['FormID'] = Form.objects.get(id=updateOrder.id)
return HttpResponse("Order changed.")
else:
pass
The key of all of this is that you can use this CommaSeparatedIntegerField as a list by evaluating the string. For example:
Adding a Variable:
aForm = Form.objects.get(id=1)
currentOrder = aForm.VariableOrder
currentOrder = eval(currentOrder)
newVar = Variable(stuff in here)
newVar.save()
currentOrder.append(newVar.id)
aForm.VariableOrder = currentOrder
aForm.save()
Removing a Variable:
aForm = Form.objects.get(id=1)
currentOrder = aForm.VariableOrder
currentOrder = eval(currentOrder)
# Variable ID that we want to delete = 3
currentOrder.remove(3)
aForm.VariableOrder = currentOrder
aForm.save()
Rendering the Variables in Order:
aForm = Form.objects.get(id=1)
currentOrder = aForm.VariableOrder
currentOrder = eval(currentOrder)
VarList = []
for i in currentOrder:
VarList.append(Variable.objects.get(id=i))
This is a rough first draft of what I am going to use, but it is working well for me. The obvious first improvement being the evaluation to python list being a method in the class. eg.
def getVarOrder(self):
return eval(self.VariableOrder)
and then just call Form.getVarOrder() when want to manipulate the list. In any case hopefully this helps out.
JD
I've run into this so many times that I've settled on managing these dynamically in the BL or UI, and then just persisting the ordering to a purpose-built column once the user is happy. SQL is just intentially designed not to handle orderings, and it always fights back.
This is a late answer to the question, but I just wanted to chime in and point out that B-Trees are a great data structure for this sort of thing, especially if your access patterns don't require you to retrieve the entire list at once.
http://en.wikipedia.org/wiki/B-tree