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

Double-layered pagination? #332

Closed
zellwk opened this issue Dec 6, 2018 · 20 comments
Closed

Double-layered pagination? #332

zellwk opened this issue Dec 6, 2018 · 20 comments

Comments

@zellwk
Copy link

zellwk commented Dec 6, 2018

I'm creating a blog. Each post are tagged. I've created each tag with the zero maintenance tag pages tip.

But each tag can potentially contain lots of posts. Can Eleventy create paginated tag pages?

EG:

  1. /tags/tag1/
  2. /tags/tag1/page2
  3. /tags/tag1/page3
  4. /tags/tag2/
  5. /tags/tag2/page2
  6. /tags/tag3/

And so on.

@zellwk zellwk changed the title Double-layed pagination? Double-layered pagination? Dec 6, 2018
@jevets
Copy link

jevets commented Dec 6, 2018

I don't think there's a straightforward way. There are a couple issues about this but I don't think a solution was found, without some custom modules/preprocessing.
#294
#308

_includes files can't use pagination in their own frontmatter.

But there's this idea from the docs (I haven't tried this approach for anything):
https://www.11ty.io/docs/permalinks/#ignore-the-output-directory

Basically, let Eleventy compile a file and output it to the _includes directory.
Then your other file will include the compiled file.

edit: This statement, specifically:

Writes to _includes/index.html even though the output directory is _site. This is useful for writing child templates to the _includes directory for re-use in your other templates.

If you give this a go and if it works, please let us know how it goes.

@edwardhorsford
Copy link
Contributor

Another idea might be to add a custom collection that outputs the data you want. Essentially recreate all the tag page collections, but one entry per page, rather than one per tag.

@zachleat
Copy link
Member

zachleat commented Dec 7, 2018

This is a fascinating idea—I’ve been mulling it over since you posted it.

I think you can do this, but you would have to flatten your custom collection to a single layer to do it.

Use custom collections: https://www.11ty.io/docs/collections/#advanced%3A-custom-filtering-and-sorting.

Here’s how it would work:

// note that this uses the lodash.chunk method, so you’ll have to require that
eleventyConfig.addCollection("doublePagination", function(collection) {
	// Get unique list of tags
	let tagSet = new Set();
	collection.getAllSorted().map(function(item) {
		if( "tags" in item.data ) {
			let tags = item.data.tags;

			// optionally filter things out before you iterate over?
			for (let tag of tags) {
				tagSet.add(tag);
			}

		}
	});

	// Get each item that matches the tag
	let paginationSize = 3;
	let tagMap = [];
	let tagArray = [...tagSet];
	for( let tagName of tagArray) {
		let tagItems = collection.getFilteredByTag(tagName);
		let pagedItems = lodashChunk(tagItems, paginationSize);
		// console.log( tagName, tagItems.length, pagedItems.length );
		for( let pageNumber = 0, max = pagedItems.length; pageNumber < max; pageNumber++) {
			tagMap.push({
				tagName: tagName,
				pageNumber: pageNumber,
				pageData: pagedItems[pageNumber]
			});
		}
	}

	/* return data looks like:
		[{
			tagName: "tag1",
			pageNumber: 0
			pageData: [] // array of items
		},{
			tagName: "tag1",
			pageNumber: 1
			pageData: [] // array of items
		},{
			tagName: "tag1",
			pageNumber: 2
			pageData: [] // array of items
		},{
			tagName: "tag2",
			pageNumber: 0
			pageData: [] // array of items
		}]
	 */
	//console.log( tagMap );
	return tagMap;
});

and then in your template it might look like this:

---
pagination:
  data: collections.doublePagination
  size: 1
  alias: tag
permalink: /tags/{{ tag.tagName }}/{% if tag.pageNumber %}{{ tag.pageNumber + 1 }}/{% endif %}
---

{% for post in tag.pageData %}
  Iterate over the items.
{% endfor %}

Does that make sense?

@zachleat
Copy link
Member

zachleat commented Dec 7, 2018

Seems to work!

image

@zachleat zachleat added education waiting-to-close Issue that is probably resolved, waiting on OP confirmation. labels Dec 7, 2018
@edwardhorsford
Copy link
Contributor

This is really cool!

@zellwk
Copy link
Author

zellwk commented Dec 9, 2018

Looks great! Lemme test it out as soon as I can :D

@zachleat zachleat removed the waiting-to-close Issue that is probably resolved, waiting on OP confirmation. label Jan 8, 2019
@zachleat
Copy link
Member

zachleat commented Jan 8, 2019

This is an automated message to let you know that a helpful response was posted to your issue and for the health of the repository issue tracker the issue will be closed. This is to help alleviate issues hanging open waiting for a response from the original poster.

If the response works to solve your problem—great! But if you’re still having problems, do not let the issue’s closing deter you if you have additional questions! Post another comment and I will reopen the issue. Thanks!

@hidegh
Copy link

hidegh commented Feb 19, 2021

The vredeburg theme uses very similar approach! You can check it out there!

@TheReyzer
Copy link

Hi, I'm using your code @zachleat to paginate my tags, but it takes all collections and I just need to paginate my collection "store" that have some tags like "Adventure", "RPG"... I already see the filter API but I don't know how to use that. Can someone help me, please?

@pdehaan
Copy link
Contributor

pdehaan commented Sep 6, 2022

@TheReyzer I think the secret would be to maybe
replace collection.getAllSorted() with something like collection.getFilteredByTag("store").

eleventyConfig.addCollection("doublePagination", function(collection) {
	// Get unique list of tags
	let tagSet = new Set();
-   collection.getAllSorted().map(function(item) {
+   collection.getFilteredByTag("store").map(function(item) {

@TheReyzer
Copy link

I've tried but it keeps paging between categories, ideally, it would be paging between items in each category, not the categories themselves. anyway thanks, but I'm giving up on 11ty unfortunately I can't continue.

@TheReyzer
Copy link

I finally make it work right for my case. But I need make it reverse, I use | reverse filter, and just reverse the post on page not the array, like this:

With | reverse:
page 1:
  3
  2
  1
page 2:
  6
  5
  4

But I need this result:
page 1:
  6
  5
  4
page 2:
  3
  2
  1

The code:

module.exports = function (eleventyConfig) {
   eleventyConfig.addPassthroughCopy("images");
   eleventyConfig.addPassthroughCopy("admin");
   eleventyConfig.addPassthroughCopy('css');

   var lodashChunk = require('lodash.chunk');

   // note that this uses the lodash.chunk method, so you’ll have to require that
   eleventyConfig.addCollection("doublePagination", function(collection) {
      // Get unique list of tags
      //let tagSet = new Set();
      var tagSet = new Set(collection.getAllSorted().flatMap((post) => post.data.tags || []));
       
      // Get each item that matches the tag
      let paginationSize = 3;
      let tagMap = [];
      let tagArray = [...tagSet];
      for( let tagName of tagArray) {
         let tagItems = collection.getFilteredByTag(tagName);
         let pagedItems = lodashChunk(tagItems, paginationSize);
          //console.log( tagName, tagItems.length, pagedItems.length );
         for( let pageNumber = 0, max = pagedItems.length; pageNumber < max; pageNumber++) {
            
            tagMap.push({
               tagName: tagName,
               pageNumber: pageNumber,
               pageSize: pagedItems.length,
               pageData: pagedItems[pageNumber]
            });
         }
      }
      /* return data looks like:
         [{
            tagName: "tag1",
            pageNumber: 0
            pageData: [] // array of items
         },{
            tagName: "tag1",
            pageNumber: 1
            pageData: [] // array of items
         },{
            tagName: "tag1",
            pageNumber: 2
            pageData: [] // array of items
         },{
            tagName: "tag2",
            pageNumber: 0
            pageData: [] // array of items
         }]
      */
      //console.log(tagMap);
      return tagMap;
   });
}

Can someone help me, please?

@pdehaan
Copy link
Contributor

pdehaan commented Sep 15, 2022

@TheReyzer Does something like let pagedItems = lodashChunk(tagItems.reverse(), paginationSize); work?

@TheReyzer
Copy link

@TheReyzer Does something like let pagedItems = lodashChunk(tagItems.reverse(), paginationSize); work?

Absolutely, thank you so much! Help a lot.

@m4rrc0
Copy link

m4rrc0 commented Mar 26, 2023

For reference: I feel like the following comment might be useful to some people landing here.

Originally posted by @therealshark in #136 (comment)

BlogTemplate.js

class BlogTemplate {
  language;
  data() {
    return {
      pagination: {
        data: `posts-${this.language}`,
        size: 1
      },
      permalink: props => {
        return `/${this.language}/blog${
          props.pagination.pageNumber === 0
            ? ''
            : `/${props.pagination.pageNumber + 1}`
        }/index.html`;
      }
    };
  }
  render(props) {
    // final template...
  }
}

export default BlogTemplate;

BlogTemplateEn.11ty.js

import BlogTemplate from './BlogTemplate';
class BlogTemplateEn extends BlogTemplate {
  language = 'EN';
}

That way you don't need to duplicate code.

@m4rrc0
Copy link

m4rrc0 commented Mar 26, 2023

I am relatively new to Eleventy, so maybe I am being naïve in the way I approach this problem but...
Why can't we 'just' return arrays from JS templates @zachleat ?

I see 2 options:

  1. Being able to return arrays from the data and the render methods.
    a. If we return arrays from both, they must have the same size. Indexes are used to associate the data to the rendered output.
    b. Alternatively, we can return an array from one method and an object from the other. In this case, the object is associated with each array entry (for example, having one render template for many different data objects)
  2. Being able to export an array of classes.

In any case, we still make the build fail if writing to the same output twice so it is up to the us to avoid colliding premalinks.

@adliymeri
Copy link

Does @zachleat code work for any key in the front matter, let's say "category", or is it only for "tags" key?

@j9t
Copy link

j9t commented Dec 10, 2023

Also just in case anyone is still following this, it seems that this doesn’t play well with filterTagList—i.e., tags previously ignored would now end up with paginated pages in the output.

Hacked around to make it work but kept running run into issues, like tags filtered being ignored in the output altogether, which is the other extreme. Need to go of this unsolved for the time being, but if this happens to ring a bell to anyone, any feedback appreciated!


Edit: Not the most elegant solution (it would be nicer to hook this up to “filterTagList”), but it turned out to be fairly easy to do by adding something like tagSet.delete("TAGTOBEEXCLUDED");.

@zachleat
Copy link
Member

I noted the above example used the collections API but here’s another way to do it in Eleventy 3.0 with the pagination before callback: https://github.com/zachleat-cc/demo-cloudcannon-i18n/blob/d986f5943cd3daa29ba2391624dfeb5f032eee66/src/songs.liquid#L8-L37

@TheReyzer
Copy link

This looks like a better way to do it, great Zach!

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

10 participants