Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tags only applying to home page when using dynamic permalink #2178

Closed
mark-reason opened this issue Jan 18, 2022 · 15 comments
Closed

Tags only applying to home page when using dynamic permalink #2178

mark-reason opened this issue Jan 18, 2022 · 15 comments

Comments

@mark-reason
Copy link

Hi, I'm using a dynamic permalink (generated from data from an API endpoint) but when I add tags to the page template's front matter and then output a tag page then only thing returned is the home page. Please see my code below.

pages/index.html (this outputs all my pages)

---js
{
  layout: "layouts/base.html",
  pagination: {
    data: "pages",
    alias: "pg",
    size: 1
  },
  tags: ['post'],
  eleventyComputed: {
    permalink: (data) => {
      return data.pg.systemProperties.url;
    },
  }
}
---

<h1>{{ pg.systemProperties.name }}</h1>

pages/tags.html (as per https://www.11ty.dev/docs/quicktips/tag-pages/)

---
pagination:
  data: collections
  size: 1
  alias: tag
permalink: /{{ tag }}/index.html
---

<h1>Tag: {{ tag }}</h1>

<ol>
  {% set taglist = collections[ tag ] %}
  {% for post in taglist | reverse %}
  <li><a href="{{ post.url | url }}">{{ post.data.title }}</a></li>
  {% endfor %}
</ol>

So if I go to /post it should output all of my pages but currently it just outputs the homepage.

Cheers
Mark

@pdehaan
Copy link
Contributor

pdehaan commented Jan 18, 2022

I think this might be hard for me to reproduce locally unless you can send me a sample of what the pages data looks like.

But my guts tell me it's likely https://www.11ty.dev/docs/pagination/#add-all-pagination-pages-to-collections

{
  layout: "layouts/base.html",
  pagination: {
    data: "pages",
    alias: "pg",
    size: 1,
+   addAllPagesToCollections: true
  },
  tags: ['post'],
  eleventyComputed: {
    permalink: (data) => {
      return data.pg.systemProperties.url;
    },
  }
}

@mark-reason
Copy link
Author

@pdehaan unfortunately that didn't seem to have any effect.

Sample pages JSON here:

[
  {
    "content": {
      "body": ""
    },
    "systemProperties": {
      "id": 1069,
      "name": "Home",
      "createDate": "2021-06-09T12:17:48.38Z",
      "updateDate": "2022-01-07T10:23:59.34Z",
      "contentTypeAlias": "homePage",
      "url": "/",
      "urlSegment": "home"
    }
  },
  {
    "content": {
      "body": ""
    },
    "systemProperties": {
      "id": 1070,
      "name": "Page 1",
      "createDate": "2021-06-09T12:17:48.38Z",
      "updateDate": "2022-01-07T10:23:59.34Z",
      "contentTypeAlias": "contentPage",
      "url": "/page-1",
      "urlSegment": "page-1"
    }
  },
  {
    "content": {
      "body": ""
    },
    "systemProperties": {
      "id": 1071,
      "name": "Page 2",
      "createDate": "2021-06-09T12:17:48.38Z",
      "updateDate": "2022-01-07T10:23:59.34Z",
      "contentTypeAlias": "contentPage",
      "url": "/page-2",
      "urlSegment": "page-2"
    }
  },
  {
    "content": {
      "body": ""
    },
    "systemProperties": {
      "id": 1072,
      "name": "Page 3",
      "createDate": "2021-06-09T12:17:48.38Z",
      "updateDate": "2022-01-07T10:23:59.34Z",
      "contentTypeAlias": "contentPage",
      "url": "/page-3",
      "urlSegment": "page-3"
    }
  }
]

@pdehaan
Copy link
Contributor

pdehaan commented Jan 21, 2022

@mark-reason Works for me in my sample sandbox: https://github.com/pdehaan/11ty-2178

INPUT

---
pagination:
  data: collections
  size: 1
  alias: tag
permalink: "/{{ tag | slug }}/index.html"
---

<h1>Tag: {{ tag }}</h1>

<ol>
  {% set taglist = collections[ tag ] %}
  {% for post in taglist | reverse %}
  <li><a href="{{ post.url | url }}">{{ post.data.pg.systemProperties.name }}</a></li>
  {% endfor %}
</ol>

Here's my [generated] "post" archive page:

<h1>Tag: post</h1>

<ol>
  <li><a href="/page-3">Page 3</a></li>
  <li><a href="/page-2">Page 2</a></li>
  <li><a href="/page-1">Page 1</a></li>
  <li><a href="/">Home</a></li>
</ol>

Although the one weird bit in your sample "pages.json" data from your CMS is that the URLs don't always have a trailing slash, which is creating a "_site/page-1" file for me, instead of my preferred "_site/page-1/index.html" format. Not sure if you have control over those URLs, or if it's something you'll have to try and fix from the Eleventy side:

      "systemProperties": {
        "id": 1070,
        "name": "Page 1",
        "createDate": "2021-06-09T12:17:48.38Z",
        "updateDate": "2022-01-07T10:23:59.34Z",
        "contentTypeAlias": "contentPage",
        "url": "/page-1", // <<------- This right here.
        "urlSegment": "page-1"
      }

Here's what my current output directory looks like:

tree www
www
├── all/
│   └── index.html
├── index.html
├── page-1
├── page-2
├── page-3
└── post/
    └── index.html

2 directories, 6 files

@pdehaan
Copy link
Contributor

pdehaan commented Jan 21, 2022

And I added a few tweaks in a new [pending] PR, if you're curious: pdehaan/11ty-2178#1

Basically I excluded the "collections.all" tag archive pagination, because it somehow felt wrong.
Also, I "fixed" the "page-1" file issue by appending a trailing / in your eleventyComputed.permalink method:

  eleventyComputed: {
    permalink(data) {
      let permalink = data.pg.systemProperties.url;
      if (!permalink.endsWith("/")) {
        permalink += "/";
      }
      return permalink;
    },
  }

I don't really know your site/config, so that might break other things, but it worked for me, so #YOLO.

So now my new output directory looks like this:

tree www
www/
├── index.html
├── page-1/
│   └── index.html
├── page-2/
│   └── index.html
├── page-3/
│   └── index.html
└── post/
    └── index.html

4 directories, 5 files

@mark-reason
Copy link
Author

Hi @pdehaan

Thanks for looking at this, I got this to work by adding addAllPagesToCollections: true into the pagination object (I didn't do this the first time as I misread your example) so thank you for that.

The next step I'm trying to do is then populate the tags via the API by adding a content.categories property but this doesn't seem to work, any idea why that may be?

---js
{
  layout: "layouts/base.html",
  pagination: {
    data: "pages",
    alias: "pg",
    size: 1,
    addAllPagesToCollections: true
  },
  eleventyComputed: {
    permalink: (data) => {
      return data.pg.systemProperties.url;
    },
    tags: (data) => data.pg.content.categories
  }
}
---

<h1>{{ pg.systemProperties.name }}</h1>

{{ pg.content.body | safe }}
[
  {
    "content": {
      "body": "Homepage",
      "categories": ["Category 1", "Category 3"]
    },
    "systemProperties": {
      "id": 1069,
      "name": "Home",
      "createDate": "2021-06-09T12:17:48.38Z",
      "updateDate": "2022-01-07T10:23:59.34Z",
      "contentTypeAlias": "homePage",
      "url": "/",
      "urlSegment": "home"
    }
  },
  {
    "content": {
      "body": "page one",
      "categories": ["Category 2"]
    },
    "systemProperties": {
      "id": 1070,
      "name": "Page 1",
      "createDate": "2021-06-09T12:17:48.38Z",
      "updateDate": "2022-01-07T10:23:59.34Z",
      "contentTypeAlias": "contentPage",
      "url": "/page-1",
      "urlSegment": "page-1"
    }
  },
  {
    "content": {
      "body": "Page 2"
    },
    "systemProperties": {
      "id": 1071,
      "name": "Page 2",
      "createDate": "2021-06-09T12:17:48.38Z",
      "updateDate": "2022-01-07T10:23:59.34Z",
      "contentTypeAlias": "contentPage",
      "url": "/page-2",
      "urlSegment": "page-2"
    }
  },
  {
    "content": {
      "body": "Page number 3",
      "categories": ["Category 1"]
    },
    "systemProperties": {
      "id": 1072,
      "name": "Page 3",
      "createDate": "2021-06-09T12:17:48.38Z",
      "updateDate": "2022-01-07T10:23:59.34Z",
      "contentTypeAlias": "contentPage",
      "url": "/page-3",
      "urlSegment": "page-3"
    }
  }
]

Cheers
Mark

@pdehaan
Copy link
Contributor

pdehaan commented Jan 25, 2022

@mark-reason Good news, I think that's correct!
Less good news, I don't think that dynamically set tags (via eleventyComputed automatically create collections like they do w/ hard coded tags).
I think we have a few examples/workarounds buried in the GitHub issues, but my memory says that something like your solution above works, but then you need to loop over all the unique tags from your dynamic pages data set and then do something like pseudocode:

// Somewhere in .eleventy.js
// Loop over your dynamic data from ./src/_data/pages.js and generate a unique set of categories/tags.
const dynamicTags = [...];
for (const tag of dynamicTags) {
  eleventyComputed.addCollection(tag, function (collectionApi) {
    return collectionApi.getFilteredByTag(tag);
  });
}

But that might help unblock you if you're trying to create archive pages for your dynamic pages/tags.

@pdehaan
Copy link
Contributor

pdehaan commented Jan 25, 2022

This is proving to be difficult to find in the issues, so it's also possible that I'm lying.
Closest I can find is #1225
Or I think we got a bit clever in #1805

@pdehaan
Copy link
Contributor

pdehaan commented Jan 26, 2022

Yep, this seemed to work for me. With the caveat that I'm using your sample file (saved as a local JSON file in my local ./src/_data/pages.json file). This might get more complex if you're fetching the data async.

const pages = require("./src/_data/pages.json");

module.exports = (eleventyConfig) => {
  eleventyConfig.addFilter("inspect", require("node:util").inspect);

  const dynamicTags = pages.reduce((set, page = {}) => {
    // Default to an empty array in case `page.content.categories` is `undefined` or `null`.
    for (const cat of page.content?.categories || []) {
      set.add(cat);
    }
    return set;
  }, new Set());

  for (const tag of dynamicTags) {
    // Create a custom Eleventy collection based on our dynamically set tags.
    eleventyConfig.addCollection(tag, function (collectionApi) {
      return collectionApi.getFilteredByTag(tag);
    });
  }

  return {
    dir: {
      input: "src",
      output: "www",
    },
  };
};

OUTPUT

"Page 2" had no categories, so doesn't appear in our category/tag archive below.

tree www/

www/
├── category-1/index.html # "Page 3" and "Home"
├── category-2/index.html # "Page 1"
├── category-3/index.html # "Home"
├── index.html
├── page-1/index.html
├── page-2/index.html
├── page-3/index.html
└── post/index.html

7 directories, 8 files

@mark-reason
Copy link
Author

Amazing @pdehaan, I've managed to get that working with the remote data also like this:

const fetch = require("node-fetch");

module.exports = (eleventyConfig) => {

  fetch(ENDPOINT)
    .then(function (response) {
      response.json().then((pages) => {
        const dynamicTags = pages.reduce((set, page = {}) => {
          // Default to an empty array in case `page.content.categories` is `undefined` or `null`.
          for (const cat of page.content?.categories || []) {
            set.add(cat);
          }
          return set;
        }, new Set());

        for (const tag of dynamicTags) {
          // Create a custom Eleventy collection based on our dynamically set tags.
          eleventyConfig.addCollection(tag, function (collectionApi) {
            return collectionApi.getFilteredByTag(tag);
          });
        }
      });
    });

  return {
    dir: {
      input: "src",
      output: "www",
    },
  };
};

One last question I've got and I was wondering if it is possible is regarding the tags page itself, that obviously uses the pagination set to 1 to generate all the different single tags pages, is there anyway to then paginate the posts (the output of collections[ tag ]) so it could then act as a standard paginated listing page rather than outputs all of the posts at once? I'm aware that if you know the collection name beforehand then you can use for example 'collections.post' and the normal pagination but that isn't an option here.

Cheers
Mark

@pdehaan
Copy link
Contributor

pdehaan commented Jan 26, 2022

@mark-reason Fantastic! As long as there aren't any timing issues between trying to run an async promise loop to create those tag collections, that looks like a good solution.
Worst case it might build/parse the config before the dynamic collections are created and your archive pages may not always build consistently. But I can't think of a good workaround for that unless you run a prebuild script which fetches the remote async data and stores it in a static JSON file in your /src/_data/ directory. 🤷

Feel free to close the issue and open a new issue/discussion if you run into other issues.

@pdehaan
Copy link
Contributor

pdehaan commented Jan 26, 2022

One last question I've got and I was wondering if it is possible is regarding the tags page itself, that obviously uses the pagination set to 1 to generate all the different single tags pages, is there anyway to then paginate the posts (the output of collections[ tag ]) so it could then act as a standard paginated listing page rather than outputs all of the posts at once? I'm aware that if you know the collection name beforehand then you can use for example 'collections.post' and the normal pagination but that isn't an option here.

Ah, I missed the last question...
I think I'm a bit confused. Are you saying you want to have a dynamic archive page of collections[tag] with all the posts in a single page?
Or are you saying you want to loop over dynamic collections[tag] and break those into paginated 10-per-page as well?

@pdehaan
Copy link
Contributor

pdehaan commented Jan 27, 2022

Ah, I think my local version is already doing tag-based archive pages via your code snippet above:

---
pagination:
  data: collections
  size: 1
  alias: tag
  filter:
    - all
permalink: "/{{ tag | slug }}/index.html"
---

<h1>Tag: {{ tag }}</h1>

<ol>
  {% set taglist = collections[ tag ] %}
  {% for post in taglist | reverse %}
    <li><a href="{{ post.url | url }}">{{ post.data.pg.systemProperties.name }}</a></li>
  {% endfor %}
</ol>

But if you're looking for double pagination, this might issue/thread be useful:

@mark-reason
Copy link
Author

Double pagination looks to be working!

const fetch = require("node-fetch");
const lodashChunk = require("lodash.chunk");

module.exports = (eleventyConfig) => {
  fetch(`${process.env.BACKEND_API_URL}/umbraco/api/SiteMap/GetAllPages`)
    .then(function (response) {
      response.json().then((pages) => {
        const dynamicTags = pages.reduce((set, page = {}) => {
          // Default to an empty array in case `page.content.categories` is `undefined` or `null`.
          for (const cat of page.content?.categories || []) {
            set.add(cat);
          }
          return set;
        }, new Set());

        eleventyConfig.addCollection("pagedPostsByTag", function (collection) {
          let paginationSize = 3;
          let tagMap = [];
          let tagArray = [...dynamicTags];

          for (const tag of tagArray) {
            let tagItems = collection.getFilteredByTag(tag);
            let pagedItems = lodashChunk(tagItems, paginationSize);
            console.log(tagItems);
            for (
              let pageNumber = 0, max = pagedItems.length;
              pageNumber < max;
              pageNumber++
            ) {
              tagMap.push({
                tagName: tag,
                pageNumber: pageNumber,
                pageData: pagedItems[pageNumber],
              });
            }
          }

          return tagMap;
        });
      });
    });

  return {
    dir: {
      input: "src",
      output: "www",
    },
  };
}
---js
pagination: {
  data: "collections.pagedPostsByTag",
  alias: "tag",
  size: 1,
},
permalink: "/{{ tag.tagName }}/{% if tag.pageNumber %}{{ tag.pageNumber + 1 }}/{% endif %}",
---

<h1>Tag: {{ tag.tagName }}</h1>

<ol>
  {% for post in tag.pageData %}
    <li><a href="{{ post.url | url }}">{{ post.data.title }}</a></li>
  {% endfor %}
</ol>

Thanks for all your help @pdehaan!

@mark-reason
Copy link
Author

Hi @pdehaan

There's something else I'm trying with this and that's allowing a user to sort the paginated collection by date (and maybe other data in the future) via a filter on the front end. Would the best way to do this be by outputting the normal collection and then outputting another one with the posts then reversed? So you would have /category-1 and /category-1-reversed?

Cheers
Mark

@mark-reason mark-reason reopened this Jan 31, 2022
@mark-reason
Copy link
Author

Achieved the above by sorting the collection like so:

const fetch = require("node-fetch");
const lodashChunk = require("lodash.chunk");

module.exports = (eleventyConfig) => {
  fetch(`${process.env.BACKEND_API_URL}/umbraco/api/SiteMap/GetAllPages`)
    .then(function (response) {
      response.json().then((pages) => {
        const dynamicTags = pages.reduce((set, page = {}) => {
          // Default to an empty array in case `page.content.categories` is `undefined` or `null`.
          for (const cat of page.content?.categories || []) {
            set.add(cat);
          }
          return set;
        }, new Set());

        eleventyConfig.addCollection("pagedPostsByTag", function (collection) {
          let paginationSize = 3;
          let tagMap = [];
          let tagArray = [...dynamicTags];

          for (const tag of tagArray) {
            let tagItems = collection.getFilteredByTag(tag);
            const pagedItems = lodashChunk(tagItems, paginationSize);
            const tagItemsSortNew = tagItems.reverse();
            const pagedItemsSortNew = lodashChunk(tagItemsSortNew, paginationSize);

            for (
              let pageNumber = 0, max = pagedItems.length;
              pageNumber < max;
              pageNumber++
            ) {
              tagMap.push({
                tagName: tag,
                pageNumber: pageNumber,
                pageData: pagedItems[pageNumber],
              });
            }
          }

         for (
              let pageNumber = 0, max = pagedItemsSortNew.length;
              pageNumber < max;
              pageNumber++
            ) {
              tagMap.push({
                tagName: `${tag}-sort-newest`,
                pageNumber: pageNumber,
                pageData: pagedItemsSortNew[pageNumber],
              });
            }
          }

          return tagMap;
        });
      });
    });

  return {
    dir: {
      input: "src",
      output: "www",
    },
  };
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants