Closed renestalder closed 3 years ago
related issue #1110
I'm not sure what the docs mean, since computing layouts works fine for me. The advanced order of operations doc should be very useful here, but I can't wrap my head around it. What solutions have you tried already?
Computed didn't work for me, as expected (from what I read in the documentation).
I'm using liquid for my layouts and what worked so far is using liquid's built in layout
tag.
eleventyConfig.setLiquidOptions({
dynamicPartials: true,
});
---
pagination:
data: myjavascriptdatafile
size: 1
alias: data
addAllPagesToCollections: true
permalink: "/{{ data.id }}/"
tags: "data"
eleventyComputed:
title: "{{ data.title}}"
computedLayout: "layouts/{{data.template}}"
---
{% layout computedLayout %}
This does not exactly behave the same way as 11ty's layout
, but it somehow does the job. I just don't find it as elegant as using Eleventy's layout feature.
Note: As mentioned, I tried assigning to eleventyComputed.layout, but that doesn't make a difference. Eleventy doesn't pick it up, like it is described in the documentation.
It took me about a week to figure out why this was not working:
---
pagination:
data: cms.content.pages
size: 1
alias: item
addAllPagesToCollections: true
permalink: "{{ item.slug }}/index.html"
tags:
- cms
- pages
eleventyComputed:
layout: "{{ item.layout }}"
---
{{ item.content }}
These restrictions may be relaxed over time.
This would be super useful! Please do allow modifying the layout
programmatically in eleventyComputed
.
Probably not very helpful, but you can use dynamic layout w/ Nunjucks {% extends %}
as a nifty workaround.
Also see the caveat in the docs link above, re:
FEATURE | SYNTAX |
---|---|
Extends | {% extends 'base.njk' %} looks in _includes/base.njk . Does not process front matter in the include file. |
Extends (Relative Path) | Relative paths use ./ (template’s directory) or ../ (template’s parent directory). Example: {% extends './base.njk' %} looks for base.njk in the template’s current directory. Does not process front matter in the include file. |
---
pagination:
data: pages
size: 1
alias: item
permalink: "page/{{ item.title | slug }}/"
tags:
- cms
- pages
eleventyComputed:
title: "{{ item.title }}"
---
{% extends item.layout %}
{% block content %}
<h1 data-slug="{{ item.title | slug }}">{{ item.title }}</h1>
<p>layout={{ item.layout }}</p>
{% endblock %}
And my nifty global "src/_data/pages.js" data file is just:
module.exports = [
{
title: "Page One",
layout: "layouts/one.njk"
},
{
title: "Page Two",
layout: "layouts/two.njk"
}
];
Since you're using {% extends %}
I don't think you could use layout aliasing here though. Or at least I haven't tried in a long time to see if there is a hacky way to use aliasing outside of the expected layout
front matter usage.
But this would probably restrict you to using (a) Nunjucks templating, and (b) blocks.
Ah, so using .addLayoutAlias()
just adds it to a .layoutAliases
object in the Eleventy config object. So we can just create a custom filter to abuse that knowledge.
// .eleventy.js snippet...
eleventyConfig.addLayoutAlias("one", "layouts/one.njk");
eleventyConfig.addLayoutAlias("two", "layouts/two.njk");
eleventyConfig.addFilter("layout_alias", name => eleventyConfig.layoutAliases[name]);
{# make sure we pass our dynamic `item.layout` through the custom `layout_alias` filter. #}
{% extends item.layout | layout_alias %}
And now our global data file can use the aliases instead of specific .njk paths:
module.exports = [
{
title: "Page One",
layout: "one"
},
{
title: "Page Two",
layout: "two"
}
];
I'm 💯 not saying this is at all a good idea or will work in any future version since it's hacky. But it can be fun to look into Eleventy internals. That disclaimer out of the way, if this did break in the future, it seems like a pretty easy fix with a custom object somewhere (global data file, right in the custom filter) for dynamic layout aliasess.
Worth noting that you can use data files to set the layout
in the data cascade, but you just won’t have access to anything else in the data cascade when you’re doing it.
For example a _data/layout.js
file (for the entire project) or a template/directory data file https://www.11ty.dev/docs/data-template-dir/
Moving this to the enhancement queue
This repository is now using lodash style issue management for enhancements. This means enhancement issues will now be closed instead of leaving them open.
View the enhancement backlog here. Don’t forget to upvote the top comment with 👍!
Worth noting that you can use data files to set the
layout
in the data cascade, but you just won’t have access to anything else in the data cascade when you’re doing it.
@zachleat to clarify, that means in a template data file, I would not be able to set layout
to item.layout
from this example -- is that correct?
I’m not saying that will necessarily solve your problem (that’s why this is in the queue) but I do want to mention it in case it sparks something you hadn’t thought of. Note that in JavaScript data files you can require other JavaScript data files, so I believe could do this (though this is likely not the most efficient implementation, just a brainstorm):
_data/
cms.js
src/
myTemplate.11ty.js
myTemplate.11ty.js:
const cms = require("../_data/cms");
module.exports.data = async function() {
let cmsData = await cms(); // assuming your data file exports is an async function
return {
layout: "" // return something from cmsData.content.pages
};
};
Nice! I will take a look at that approach. Thanks for the explanation.
I'm a bit lost re. the status of this issue 😭 I'm an 11ty newbie so I'm not sure I understand @zachleat's suggestion to "set the layout in the data cascade" - I believe this is what I'm already doing/have tried. I've structured my build to be headless ready. I'm using JSON Server to fake a rest API returning my content which I'll later switch out with a headless CMS (TBD). The data should dictate the layout/template to be used to display the content.
// http://localhost:3000/pages
[
{
"id": 1,
"meta": {
"title": "Home"
},
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis dictum maximus ante a ultrices.",
"url": "/",
"layout": "layouts/home.njk"
},
{
"id": 2,
"meta": {
"title": "About"
},
"body": "Curabitur ultricies quam ut semper gravida. Integer vitae gravida dui.",
"url": "/about/",
"layout": "layouts/about.njk"
}
]
// _data/pages.js
const fetch = require('node-fetch');
module.exports = async () => {
return fetch('http://localhost:3000/pages').then((res) => res.json());
};
page.njk
---
eleventyComputed:
title: "{{ _page.meta.title }}"
layout: "{{ _page.layout }}" # Doesn't work #2
layout: "{{ _page.layout }}" # Doesn't work #1
pagination:
data: pages
size: 1
alias: _page
addAllPagesToCollections: true
permalink: "{{ _page.url }}"
tags:
- page
---
{{ _page.body }}
The workaround suggestions all involve templating engine trickery (@renestalder @paulshryock) but come with caveats/cons.
Could anyone lend me a hand please/clarify where things are at? Appreciate your time reading and any help/suggestions 🤗
@michael-bullimore-uk I believe what @zachleat and the docs are saying is that you can set the layout
property directly in the data cascade, but it cannot be set dynamically, per the INFO callout on https://www.11ty.dev/docs/data-computed/:
"Therefore Computed Data cannot be used to modify the special data properties used to configure templates (e.g.
layout
,pagination
,tags
, etc.)."
I was able to get your snippets working, but I did had to resort to using Nunjucks {% block %}
and {% extends %}
tags if I wanted to extend a dynamic layout
from an API/data file.
{%- extends layout -%}
{%- block content %}
<h2>{{ title }}</h2>
<article>{{ _page.body | safe }}</article>
{%- endblock %}
Which might not be the solution you want, but it seems to work.
Morning @pdehaan! Many thanks for the quick reply. I completely missed the blurb in the "INFO" box (derp), but that does suggest the layout logic can only be done by the templating engine. Thank you 🤗
As of the current computed data documentation, I quote:
What's a current solution to dynamically set the layout based on the data I got from an external API? Seems quite common to me that a user in a CMS can choose the template for a specific page or that the CMS is already responding with the template information as statically defined in the CMS.