eleventy icon indicating copy to clipboard operation
eleventy copied to clipboard

Order of operation between 11tydata files and collections

Open cfjedimaster opened this issue 3 years ago • 9 comments

Describe the bug Imagine a directory, named posts, and inside there I'm using a data directory file to dynamically set the tags value based on front matter:

module.exports = {
	eleventyComputed: {
		permalink: data => {
			if(data.draft) return false;
		},
		tags: data => {
			if(!data.draft) return 'posts';
			return '';
		}
	}
}

Specifically the idea here is - if I included draft: true in front matter, I don't want to publish it, and if not, I want the post to be assigned to the tags value.

In my front end, I then tried to loop over it:

{% for post in collections.posts %}
a post: {{ post.url }}, title {{ post.data.title}}, tags: {{ post.data.tags }}<br/>
{% endfor %}

And nothing was output. But if I switch to all:

{% for post in collections.all %}
page: {{ post.url }}, title {{ post.data.title}}, tags: {{ post.data.tags }}<br/>
{% endfor %}

I correctly see my blog post and it correctly shows the right value for tags.

I then switched to using a custom collection in .eleventy.js:

eleventyConfig.addCollection("blogPosts", function(collectionApi) {
    return collectionApi.getFilteredByTag("posts");
});

But this also didn't work. I had to switch to this:

eleventyConfig.addCollection("blogPosts", function(collectionApi) {
	let initial = collectionApi.getFilteredByGlob("posts/*.md");
	return  initial.filter(i => {
		return i.data.tags && i.data.tags === 'posts';
	});
});

So I guess the collection stuff ran before the processing of the posts, but if collections are based on reading files and their tags, why wouldn't my posts.11tydata.js run properly there?

To Reproduce

See what I said above. Source may be found here: https://github.com/cfjedimaster/eleventy-demos/tree/master/eleventy_draft_test

Expected behavior I'd expect collections.tags to have the right results based on the custom logic in my directory data file.

Environment:

  • OS and Version: WSL, latest
  • Eleventy Version [via eleventy --version or npx @11ty/eleventy --version] 1.0.1

cfjedimaster avatar Aug 14 '22 17:08 cfjedimaster

FWIW, when using the draft: true pattern (or permalink: false), I like to also set eleventyExcludeFromCollections: true to prevent unpublished drafts from still loitering around in collections: https://www.11ty.dev/docs/collections/#how-to-exclude-content-from-collections

pdehaan avatar Aug 14 '22 19:08 pdehaan

Ah, and I think the other issue is that if you dynamically set tags data, it doesn't do the same auto-collection creation as it does if you set tags via front matter. Let me try cloning your repo and see if we can find a solution/workaround versus my regular unhelpful theories.

pdehaan avatar Aug 14 '22 19:08 pdehaan

Oh fascinating... I can reproduce the issue, but interestingly it might be a difference between string tags: 'posts' (:-1:) versus array tags: ['posts'] (👍) syntax.

For example, this worked for me:

module.exports = {
	eleventyComputed: {
		permalink: data => {
			if(data.draft) return false;
		},
		tags: data => {
-			if(!data.draft) return 'posts';
-			return '';
+			if(!data.draft) return ['posts'];
+			return [];
		}
	}
}

Although, then I had to modify your .eleventy.js config file to work w/ arrays (using i.data.tags?.includes("posts") as a weak array check w/ null/undefined tags[] support):

module.exports = function(eleventyConfig) {
	//this doesnt work
	eleventyConfig.addCollection("blogPosts2", function(collectionApi) {
		const res = collectionApi.getFilteredByTag("posts");
		console.log(`blogPosts2:`, res.length);
		return res;
	});

	eleventyConfig.addCollection("blogPosts", function(collectionApi) {
		let initial = collectionApi.getFilteredByGlob("posts/*.md");
		return  initial.filter(i => i.data.tags?.includes("posts"));
	});
};

But also, by switching to using the nicer array syntax (IIRC, Eleventy converts strings to arrays behind the scenes eventually anyways), your original "blogPosts" collection worked (renamed to "blogPosts2" to avoid naming conflicts).

TL;DR: Use array syntax when setting tags.

pdehaan avatar Aug 14 '22 20:08 pdehaan

So in theory, if I return the array and it works, I do not need the custom collection at all, right?

cfjedimaster avatar Aug 14 '22 20:08 cfjedimaster

Nope, so I tried your modified logic for tags, and collections.posts is still empty.

cfjedimaster avatar Aug 14 '22 20:08 cfjedimaster

Ah, to be clear, creating the custom collection in .eleventy.js DID work. This still feels half way fixed though - why is collections.posts still failing in index.liquid?

cfjedimaster avatar Aug 14 '22 20:08 cfjedimaster

"So in theory, if I return the array and it works, I do not need the custom collection at all, right?"

In my experience, you still need a custom collection in your .eleventy.js config file if you're setting the tags data dynamically. Collections are only auto-created if you set them via front matter. I don't know if that's a bug or gotcha or fact of life; I'm just in the workaround business(tm).

pdehaan avatar Aug 14 '22 21:08 pdehaan

And just because I still had the tab open and I'm bored on a Sunday, here's my proposed solution:

// posts/posts.11tydata.js
module.exports = {
	eleventyComputed: {
		permalink: data => (data.draft) ? false : data.permalink,
		eleventyExcludeFromCollections: (data) => data.draft,
	},
	tags: ["posts"]
};

Since we're hardcoding the tags: ["posts"] in the directory data file (but not as eleventyComputed data), all the posts should be automatically added to the collections.posts collection; BUT, the eleventyExcludeFromCollections will make sure any truthy draft: true posts aren't included (either in collections.posts or collections.all).

Since that explanation was hot garbage, here's my modified output from your /_site/index.html file:

<h2>collections.posts (1)</h2>
<ol>
  <li>a post: /posts/alpha/, title: alpha, tags: posts</li>
</ol>

<h2>collections.all (2)</h2>
<ol>
  <li>page: /, title: , tags: </li>
  <li>page: /posts/alpha/, title: alpha, tags: posts</li>
</ol>

And that also means I don't need any sneaky .eleventy.js .addCollection() tricks, behold:

// .eleventy.js
module.exports = function(eleventyConfig) {
};

pdehaan avatar Aug 15 '22 00:08 pdehaan

All very cool and thank you - going to edit my blog post with a link to this discussion. Still curious to hear though if this would be considered a bug. It feels like a bug to me. :)

cfjedimaster avatar Aug 15 '22 10:08 cfjedimaster