Access existing collections inside a shortcode. (👍23) #3990
Replies: 14 comments
|
ps. eleventy rulez ok |
|
we have access to |
|
I have the same issue with regard to collection access in shortcodes. My current workaround is the following: Called with: It feel ugly, but it works. |
This comment has been hidden.
This comment has been hidden.
|
If I'd like to ensure this also adds access to filters, would I file a new bug? |
|
I'm hoping to get collection access inside of shortcakes in order to facilitate migration from Jekyll. In particular, Jekyll's Being able to access |
|
I have a potential hacky workaround, but I don't know that I really like it… I assume there's a dozen of ways this wouldn't work in the real world, but who knows… // .eleventy.js
module.exports = function (eleventyConfig) {
// Create a "semi-global" `$pages` array (which will be populated later via a custom "shadow" collection).
let $pages = [];
// I have no idea how the Jekyll `page_url` shortcode works internally, so this is a rough guess. 🤷
eleventyConfig.addShortcode("page_url", function (slug = "") {
// Try and find the specified page slug in our `$pages[]` array by matching the
// template's `filePathStem`.
const page = $pages.find((page) => page.filePathStem === slug);
// No match found, hard error (again, no idea how Jekyll works, but I liked the idea
// of failing fast-and-furious if we have bad links.
if (!page) {
throw new Error(`Unknown page slug: "${slug}"`);
}
// We had a match, so return the page's `url`.
return page.url;
});
// Create a custom "pages" collection, which basically copies/mirrors the items that
// are already tagged "pages".
// Seems silly, but we really are just using this to copy the collection into our semi-global
// `$pages[]` array for lookup by our custom shortcode.
eleventyConfig.addCollection("pages", function (collectionApi) {
$pages = [...collectionApi.getFilteredByTag("pages")];
return $pages;
});
return {
dir: {
input: "src",
output: "www",
},
};
};And I can use it like this: ---
# src/index.liquid
title: Page One
---
{% page_url "/nested/page-two" %}---
# src/nested/page-two.liquid
title: Page Two
---
HOME: {% page_url "/index" %}
{% comment %}
This page slug doesn't exist, so will throw a hard error.
BAD: {% page_url "beep boop" %}
{% endcomment %}// src/src.11tydata.js
module.exports = {
// All all the pages to a "pages" collection. Not sure why, although you did mention a "pages" collection.
tags: ["pages"]
};OUTPUT<!-- www/index.html -->
/nested/page-two/<!-- www/nested/page-two/index.html -->
HOME: /And for conversation's sake, here's the console output when using our very naughty > Executing task: npm run build <
> 11ty-813@1.0.0 build
> eleventy
[11ty] Problem writing Eleventy templates: (more in DEBUG output)
[11ty] > Having trouble rendering liquid template ./src/nested/page-two.liquid
`TemplateContentRenderError` was thrown
[11ty] > Unknown page slug: "beep boop", file:./src/nested/page-two.liquid, line:3, col:6
`RenderError` was thrown
[11ty] > Unknown page slug: "beep boop"
`Error` was thrown:
[11ty] Error: Unknown page slug: "beep boop"
at Object.<anonymous> (/private/tmp/11ty-813/.eleventy.js:7:13)
at Object.<anonymous> (/private/tmp/11ty-813/node_modules/@11ty/eleventy/src/BenchmarkGroup.js:32:26)
at Object.render (/private/tmp/11ty-813/node_modules/@11ty/eleventy/src/Engines/Liquid.js:152:25)
at async Template._render (/private/tmp/11ty-813/node_modules/@11ty/eleventy/src/TemplateContent.js:421:22)
at async Template.getTemplateMapContent (/private/tmp/11ty-813/node_modules/@11ty/eleventy/src/Template.js:1056:19)
at async TemplateMap.populateContentDataInMap (/private/tmp/11ty-813/node_modules/@11ty/eleventy/src/TemplateMap.js:461:39)
at async TemplateMap.cache (/private/tmp/11ty-813/node_modules/@11ty/eleventy/src/TemplateMap.js:360:5)
at async TemplateWriter._createTemplateMap (/private/tmp/11ty-813/node_modules/@11ty/eleventy/src/TemplateWriter.js:242:5)
at async TemplateWriter.generateTemplates (/private/tmp/11ty-813/node_modules/@11ty/eleventy/src/TemplateWriter.js:275:5)
at async TemplateWriter.write (/private/tmp/11ty-813/node_modules/@11ty/eleventy/src/TemplateWriter.js:321:23)
[11ty] Wrote 0 files in 0.02 seconds (v1.0.0)
The terminal process "/bin/zsh '-c', 'npm run build'" terminated with exit code: 1. |
|
I've got this working as a Liquid Custom Tag, which does seem to have access to collections: // Implement Jekyll's post_url tag
// Usage: {% post_url post-filename-without-extension %}
eleventyConfig.addLiquidTag("post_url", function (liquidEngine) {
return {
parse: function (tagToken, remainingTokens) {
this.str = tagToken.args; // post-filename-without-extension
},
render: async function (context) {
const postFilenameWithoutExtension = `./_posts/${this.str}`;
const posts = context.environments.collections.post;
const post = posts.find((p) =>
p.inputPath.startsWith(postFilenameWithoutExtension)
);
if (post === undefined) {
throw new Error(`${this.str} not found in posts collection.`);
} else {
return post.url;
}
},
};
}); |
|
Also, if you're using LiquidJS, you might be able to get access to collections in custom filters via eleventyConfig.addFilter("page_url", function (slug = "") {
const pages = this.context.environments.collections.pages;
const page = pages.find(p => p.filePathStem === slug);
if (page) {
return page.url;
}
throw new Error(`Unknown page slug: "${slug}"`);
});Actually, it looks like Nunjucks might have access to collections when using non-arrow functions as well via This hot mess might work for both LiquidJS and Nunjucks. I'd have to play some more to see if if I can get it working with JavaScript/Eleventy templates: eleventyConfig.addFilter("page_url", function (slug = "") {
const collections = this.ctx?.collections || // Nunjucks
this.context?.environments.collections || // LiquidJS
{}; // Default to an empty object.
// Use the custom `pages` collection, or else default to an empty array.
const pages = collections.pages || [];
const page = pages.find(p => p.filePathStem === slug);
if (page) {
// We found a matching slug/filePathStem, return the page's `url` property.
return page.url;
}
// ABORT! ABORT!
throw new Error(`Unknown page slug: "${slug}"`);
}); |
|
@sentience That's super cool! I was reading https://liquidjs.com/tutorials/register-filters-tags.html and wasn't sure if you want to use By running ---
title: Page One
var: one
---
{% post_url3 "one" %}
{% post_url3 var %}My slightly modified custom tag looks something like this: eleventyConfig.addLiquidTag("post_url", function (liquidEngine) {
return {
parse(tagToken, remainingTokens = []) {
this.str = tagToken.args;
},
async render(ctx) {
const slug = await liquidEngine.evalValue(this.str, ctx);
const postFilename = `./src/_posts/${slug}`;
const posts = ctx.environments.collections.post;
const post = posts.find(p => p.inputPath.startsWith(postFilename));
if (post) {
return post.url;
}
throw new Error(`${slug} not found in post collection.`);
},
};
}); |
|
@pdehaan To emulate Jekyll's |
|
I'm now encountering a need for this in eleventy.after. I was surprised that it wasn't available. |
I had this need for the first time, and it works! 🥳 Thanks a lot @pdehaan 🙏 |
Uh oh!
There was an error while loading. Please reload this page.
Describe the solution you'd like
I’d like the contents of existing
collectionsto be accessible from inside a shortcode.Additional context
I’m building a pattern library dealie, and I’d like to be able to compose “pages“ from individual “components.”
/_src/_components/hgroup/code.html
/_src/_pages/blogpost/code.html
{% set comps = collections.components %} <div class="copy"> {% useComponent "hgroup", comps %} <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit…</p> </div>/_src/.eleventy.js
Needing to include
{% set comps = collections.components %}on every composed page template and cobbling together a new, single-item collection inside the shortcode both feel a little iffy to me.Ideally, since I already have a collection containing all the components, I’d like to filter on that and return the result from the existing collection.
All reactions