eleventy icon indicating copy to clipboard operation
eleventy copied to clipboard

Dynamic collection generation based on dynamic tags

Open designbuedchen opened this issue 5 years ago • 18 comments

Hi there!

I tried several hours to get into this but I cannot get it working. I have global data file where I fetch some data from an api. In this api I receive posts and every single post has an array of tags already baked in.

I tried to include those already given tags into my collection, to generate new collections out of it.

But entering this into my front matter does not work?!

---
pagination:
    data: posts
    size: 1
    alias: post
permalink: "posts/{{ post.slug }}/"
tags: {{ post.tags }}
---

Is there a way to dynamically chain the tags from my api to the tags from the collection?

designbuedchen avatar Mar 29 '20 18:03 designbuedchen

I tried exactly the same things today. In fact I also tried to do it for the site title as well to push it to my template. Would love to understand how to get this done.

sachinsancheti1 avatar Apr 01 '20 17:04 sachinsancheti1

I tried exactly the same things today. In fact I also tried to do it for the site title as well to push it to my template. Would love to understand how to get this done.

It is a little bit frustrating that no one of the pros answers this simple question. This would really open a lot of doors for me right now...

designbuedchen avatar Apr 03 '20 16:04 designbuedchen

Hi @designbuedchen, please keep in mind, that this community consits of volunteers and is a comparatively small one. Chances are, nobody ran into your use case yet or hasn't checked in this issues list for a while.

Ryuno-Ki avatar Apr 03 '20 17:04 Ryuno-Ki

I think I found a work around for this problem. I was unable to figure out the tags part but I definitely could get around other parts of the content like the meta 'title' and 'description'. On the generate.md page I worked with a block like this

{% extends '_includes/layouts/page.njk' %}
{% block ctitle %}{{sampledata.title}}{% endblock %}

and in the page.njk file I added a block to the header section like this. <title>{% block ctitle %}{% endblock %}</title> . This worked beautifully I must say.

this ofcourse then completely ignore the {{title}} property that I would otherwise normally pass in the front matter. On adding a code like the following: <title>{% block ctitle %}{% endblock %}{{title}}</title> I would be thrown a title like [object Object] which makes no sense to me but that is how eleventy processed it.

I hope this throws some light and hope we all could figure out a way for this.

sachinsancheti1 avatar Apr 03 '20 17:04 sachinsancheti1

[object Object] is the stringified version of a JavaScript object. (You can check with console.log(({}).toString())). I wonder, whether you could use the dump pipe as in <title>{% block ctitle %}{% endblock %}{{ title | dump }}</title>

Ryuno-Ki avatar Apr 03 '20 18:04 Ryuno-Ki

Step 1: A) In the as-is state, I checked with this on the chrome browser. > console.log(({}).toString()) VM414:1 [object Object]

Step 2: B) I tried <title>{% block ctitle %}{% endblock %}{{ title | dump }}</title>. The [object Object] issue was sorted out with this. Interestingly, quotes surrounding the variable for pages which had the title in the front matter. Something like this <title>&quot;foo&quot;</title>

So, I used the nunjuncks filter to replace filter to replace the quotes with nothing. Thus my final output is

<title>{% block ctitle %}{% endblock %}{{title | dump | replace('"','')}}</title>

Fortunately, my issue on the meta tags I sorted for now.

There are 2 problems which have risen from this situation:

  1. The collection does not know how to read the page title. A sample filecheck file I have done is attached.
  2. The layout variable that is used commonly for sitemap.xml and checking collections which have a layout, has missed on the front-matter that the page is infact is made from a layout.

Though whatever I have posted does not necessarily answer @designbuedchen

Attached is a sample code which I used to check if the collections took up the title and found missing. page-check.txt

sachinsancheti1 avatar Apr 04 '20 07:04 sachinsancheti1

My generate.md file header looks like this now:

---
pagination:
  data: brands
  size: 1
  alias: brand
  addAllPagesToCollections: true
permalink: 'brandss/{{prop.websiteLink}}/'
headerHeight: 70vh
tags:
  - brand
carousel: true
---

{% extends 'layouts/page.njk' %}

sachinsancheti1 avatar Apr 04 '20 07:04 sachinsancheti1

Step 1: A) In the as-is state, I checked with this on the chrome browser. > console.log(({}).toString()) VM414:1 [object Object]

See, same output like you observed before

Step 2: B) I tried <title>{% block ctitle %}{% endblock %}{{ title | dump }}</title>. The [object Object] issue was sorted out with this. Interestingly, quotes surrounding the variable for pages which had the title in the front matter. Something like this <title>&quot;foo&quot;</title>

Hm, then I'm surprised to find this output. I'd expected something like {&quot;foo&quot;: &quot;bar&quot;} (that is, an object instead of a string).

Sample code is

<?xml version="1.0" encoding="UTF-8"?>
{%- for page in collections.all %}
<page>
  <layout>{{page.data.layout}}</layout>
  <url>{{ page.url }}</url>
  <title>{{ page.data.title }}</title>
  </page>
{%- endfor %}

Ryuno-Ki avatar Apr 04 '20 11:04 Ryuno-Ki

Thanks. I followed the sitemap.xml code. This is one of them which was generated. Sample output here below which had some pages which had empty titles and layouts.

<page>
  <layout></layout>
  <url>/brand/dawson-bungalow/</url>
  <title></title>
  </page>
<page>

{&quot;foo&quot;: &quot;bar&quot;} is what it did. You are right.

sachinsancheti1 avatar Apr 04 '20 13:04 sachinsancheti1

Thank you for the support anyway @Ryuno-Ki . The primary objective of having the rest of the website populate correctly has been achieved for me.

sachinsancheti1 avatar Apr 04 '20 13:04 sachinsancheti1

Okay, what happens with <title>{% block ctitle %}{% endblock %}{{ title.foo }}</title> (or whatever you have instead of „foo”?

Ryuno-Ki avatar Apr 04 '20 17:04 Ryuno-Ki

@Ryuno-Ki So what I have done is: A) Pages which are paginated use the block code for obtaining the title. The title.foo gives me nothing anyway as I have not added a title in the front matter of these pages like in post https://github.com/11ty/eleventy/issues/1059#issuecomment-608988030

B) Pages which are non-paginated (built from an MD file) use the title.foo part while block remains empty, something like this

---
title: All Brands for Sale
layout: page
eleventyNavigation:
  key: brands
  parent: main
---

It is like either or basis.

sachinsancheti1 avatar Apr 05 '20 06:04 sachinsancheti1

Hi @designbuedchen , I have figured out a way to handle your issue (and mine as well). To avoid limitations of any functionality in the front-matter or any other part, I created a node module that handles it for me. I am 15 days old to node.js so please do ignore if I have done anything that is considered inefficient. Mustache templates did not really work out for me as I am still too new to it. I have added a public repo for the same to demonstrate how I worked it out. I used a similar technique on converting some json files earlier to a Hugo blog site. The main code is the templateMaker folder.

https://github.com/sachinsancheti1/json-yaml-front-matter

The node module created here has helped me solve my issues of creating pages. I hope this helps you too.

At this point I can only wish there was an easier way to add titles and tags dynamically from a data source using the pagination feature.

sachinsancheti1 avatar Apr 05 '20 11:04 sachinsancheti1

I am 15 days old to node.js so please do ignore if I have done anything that is considered inefficient.

Welcome to the JavaScript community then :-) Would you be willing to accept PRs on your repo?

Ryuno-Ki avatar Apr 05 '20 13:04 Ryuno-Ki

Yes sure. Please feel free. It's open to all.

sachinsancheti1 avatar Apr 05 '20 14:04 sachinsancheti1

I'd like to bring this back around to the original discussion about dynamic tags. If one wanted to pull in, say, posts from an API and bring in the tags for each post, is it possible to generate collections from those tags?

skoontastic avatar Apr 08 '20 23:04 skoontastic

Also eager to hear if dynamic tags are possible. I tried to do this using a javascript template and code similar to what's possible with permalink:

function (data) {
    return data.item.tags
  }

Unfortunately this didn't work, and I was returned an error similar to entry.data.tags is not iterable

toufali avatar Aug 28 '20 22:08 toufali

I guess I had a similar issue... ended with something like below

assuming that posts is a list of posts

[
  { title: 'first post', tags: ['tag1', 'tag2'] },
  { title: 'second post', tags: ['tag1', 'tag2'] },
  { title: 'third post', tags: ['tag1', 'tag3'] }
]

1. Creating page per post

---
pagination:
  data: posts
  size: 1
  alias: page
  addAllPagesToCollections: true
permalink: "blog/{{ page.title | slug }}/"
layout: layout.njk
tags: post
---

<h1>
  {{ page.title }}
</h1>

will create

  • blog/first-page/index.html
  • blog/second-page/index.html
  • blog/third-page/index.html

2. Creating posts list

---
pagination:
  data: posts
  size: 2
permalink: blog/{% if pagination.pageNumber %}{{ pagination.pageNumber + 1 }}/{% endif %}
layout: layout.njk
---

<ol>
{% for page in pagination.items %}
  <li>
    {{ page.title }}
  </li>
{% endfor %}
</ol>

will create

  • blog/index.html
  • blog/2/index.html

3. Creating post list per tag

Basic on this issue https://github.com/11ty/eleventy/issues/332 I added new collection tags

eleventy.js

require('dotenv').config()
const lodashChunk = require('lodash.chunk')

module.exports = eleventyConfig => {

  eleventyConfig.addCollection('tags', function (collection) {

    const posts = collection.getFilteredByTag('post')
      .map(item => item.data.page)
    const tags = posts
      .map(item => item.tags)
      .flat()
      .filter(Boolean)
    const uniqueTags = [...new Set(tags)]
    const pageSize = 2

    return uniqueTags.map(tag => {
      const postsWithTag = posts.filter(post => post.tags.includes(tag))

      return lodashChunk(postsWithTag, pageSize)
        .map((item, index) => ({
          tagName: tag,
          pageNumber: index,
          pageData: item
        }))
    }).flat()
  })
}

and

---
pagination:
  data: collections.tags
  size: 1
  alias: tag
permalink: /blog/{{ tag.tagName | slug }}/{% if tag.pageNumber %}{{ tag.pageNumber + 1 }}/{% endif %}
---

<ol>
{% for page in tag.pageData %}
  <li>
    {{ page.title }}
  </li>
{% endfor %}
</ol>

will create

  • blog/tag1/index.html
  • blog/tag1/2/index.html
  • blog/tag2/index.html
  • blog/tag3/index.html

lukasz-galka avatar Jan 06 '21 16:01 lukasz-galka