11ty / eleventy

A simpler site generator. Transforms a directory of templates (of varying types) into HTML.
https://www.11ty.dev/
MIT License
16.92k stars 491 forks source link

Using getCollectionItem with front matter #1362

Open teknetia opened 4 years ago

teknetia commented 4 years ago

Hello!

I think I am approaching this correctly and I think I am right on the edge of understanding it correctly, however please do direct me to the more sensible option if it exists.

The scenario is that I have a folder with events in it and another with activities. Each event can have a bunch fo activities, and similarly an activity can be at multiple events (though for the same of simplicity let's assume one-to-one for now). The front matter references the other thing via the file slug/name and my own arbitrarily assigned name (as opposed to a tag).

Example event (events/event-name.md):

---
title: Event Name
activities: activity
tag: event
---
Description of the event

Example activity (activities/activity.md):

---
title: Activity
event: event-name
tag: activity
---
Description of the activity

In my template (using Liquid), I want to be able to grab info from the referenced location. For example, for the event page, something like this:

<main>
    <h1>{{ title }}</h1>
    {{ content }}
</main>
<hr>
<section>
    {{ assign eventActivity = collections.activities | getCollectionItem: activity }}
    <p>Activity to feature at this event: <a href="{{ eventActivity.url }}">{{ eventActivity.title }}</a></p>
</section>

But this does't work so I am fairly certain I am missing something super obvious. Or I am barking up the wrong tree entirely? I also tried using a custom collection that would filter based on the front matter but I couldn't figure out how to do it that way either. Basically all I want to do is pull info from another file based on what info is shown in the front matter which could be one related item or multiple (like you can have multiple tags).

Thanks in advance!

binyamin commented 4 years ago

Hi!

The getCollectionItem filter takes in a page object, not a string from the frontmatter. That's why your code couldn't find the correct page. Try using the where liquid filter (https://liquidjs.com/filters/where.html); that should work.

teknetia commented 4 years ago

Wow, I didn't even know Liquid had a where option and even less that I could use it with front matter, that is so fantastic!

That worked perfectly, thanks so much! :D A whole bunch of things that were hard in my head just got super simple.

teknetia commented 4 years ago

So turns out this isn't actually working (I only had one item in the activities collection when testing). This is still returning the whole collection so as soon as I add another, even if it doesn't include the thing I am checking with the where option, it prints out every item.

As an example (changed to use the where option):

<main>
    <h1>{{ title }}</h1>
    {{ content }}
</main>
<hr>
<section>
    {{ assign eventActivity = collections.activities | where: "activity", activity }}
    <p>Activity to feature at this event: <a href="{{ eventActivity.url }}">{{ eventActivity.title }}</a></p>
</section>

I am guessing I am using something incorrectly here because it isn't slimming the collection down to just the relevant item(s). Just for completeness, assuming the event including multiple activities, this also fails:

Example event (events/event-name.md):

---
title: Event Name
activities:
- activity1
- activity2
- activity3
tag: event
---
Description of the event
<section>
    {% for each in activity %}
        {{ assign eventActivity = collections.activities | where: "activity", each }}
        <p>Activity to feature at this event: <a href="{{ eventActivity.url }}">{{ eventActivity.title }}</a></p>
    {% endear %}
</section>
pdehaan commented 4 years ago

Re: LiquidJS where filter, Note that 11ty uses an outdated liquid module, so the filter might not exist in your 11ty unless you shim the latest liquid in.

https://github.com/11ty/eleventy/blob/ac77a7b8d3ff33e714daf8fcb0af03dcf557c650/package.json#L109

But I don’t think the where filter was added until liquidjs@8.1.0, per https://github.com/harttle/liquidjs/compare/v8.0.3...v8.1.0

binyamin commented 4 years ago

@pdehaan Good call. (See #1334)

@teknetia It's not as difficult to update liquid as you might think. On my site, it was just plug 'n play. I installed the latest Liquidjs version, and then added it in .eleventy.js. It gave me no complaints.

const {Liquid} = require("liquidjs");
eleventyConfig.setLibrary("liquid", new Liquid({
    extname: ".liquid",
    dynamicPartials: false,
    strict_filters: false
}))
teknetia commented 4 years ago

Ahhh, thanks for the help, I think that is running correctly, is there any easy way to confirm that 11ty is using the local Liquid version? I notice that since updating I am now getting 0 results for the where but that might be something to do with my code and I don't have a moment to check right now.

I'll have more of proper play this evening after work and see if I can make sense of it but if there are any likely gotchas I am keen to hear then! :D

pdehaan commented 4 years ago

@teknetia I cant find a specific "version" property in liquidjs, but you should be able to do a require on "liquidjs/package.json" and get the version property from there. Or, since the method signatures are different between liquidjs@6.4.3 (bundled in 11ty v0.11.0) and liquidjs@9.15.0 (latest on npm), that's a pretty quick check too.

const { Liquid } = require("liquidjs");

module.exports = (eleventyConfig) => {
  console.log(require("liquidjs/package.json").version);
  // 6.4.3  (if using bundled liquidjs)     --- Wrote 0 files in 0.02 seconds (v0.11.0)
  // 9.15.0 (if installing latest from npm) --- Wrote 0 files in 0.02 seconds (v0.11.0)

  const options = {
    extname: ".liquid",
    dynamicPartials: true,
    strictFilters: true,
    root: ["_includes"],
  };
  eleventyConfig.setLibrary("liquid", new Liquid(options));

  return {};
};

Ref https://github.com/11ty/11ty-website/pull/326 (a PR which updates the 11ty Liquid docs to be v9 compatible since there were breaking liquidjs API changes between 6.x and 9.x).


This seemed to work as a quick prototype of liquidjs where usage:

---
title: Some Activities
activities:
  - title: skating
    active: true
  - title: biking
    active: true
  - title: swimming
    active: false
---

{% assign eventActivity = activities | where: "active", true %}
{%- for activity in eventActivity %}
  <p>Activity = {{ activity.title }}</p>
{%- endfor %}

<!-- OUTPUT
  <p>Activity = skating</p>
  <p>Activity = biking</p>
-->
teknetia commented 4 years ago

Sorry it took me a bit, I got stuck with usual silly problems that eat all my time up :P

So I found using fileSlug as the where condition was the way to do it with the most reliability but I am not really sure this is the most efficient method to do it (it still comes back as an array, it would be nicer if it didn't need to so I didn't also need a {% for x in y %} to print out the info).

Example I threw in that works, subject the issue mentioned above:

{% for each in activity %}
    {% assign activity_info = collections.activities | where: 'fileSlug', each %}
    {% for info in activity_info %}
        {{ info.data.title }}
    {% endfor %}
{% endfor %}

I am not sure if there is maybe a way I can say "go get this specific thing" and then just use activity_info.title etc. I am also not sure if fileSlug could shoot me in the foot if two files in two folders in future have the same name, unsure if I can sensible use a different theory?

This also gets messy if I need to look one layer deeper (say the activity had an instructor that referenced another collection, it would then be each, for, each, for, etc. Would anyone suggest approaching this differently?

Thanks!