eleventy icon indicating copy to clipboard operation
eleventy copied to clipboard

Navigate through specific tag in double layered pagination

Open kaviars opened this issue 5 years ago • 7 comments

Trying to make blog for some creative texts, that is in style of a book with visible one page at a time.

Chapters, the tagged content, generates using @zachleat code 11ty/eleventy#332 (comment).

The problem To get the pagination navigation am using https://www.11ty.dev/docs/pagination/nav/#put-it-all-together, but this loops through all tags (f.ex., pagination.href.first leads to mysite.com/tag1/ but pagination.href.last to mysite.com/tag3/9) . How to make the navigation to loop through a certain tag?

Git repository https://github.com/ritms/11ty-simple-book

kaviars avatar Apr 25 '20 22:04 kaviars

that is in style of a book with visible one page at a time

Like in print stylesheets? In that case @maxboeck blogged about a résumé builder which might serve as inspiration. It inspired Zach at least.

Ryuno-Ki avatar May 03 '20 21:05 Ryuno-Ki

Navigating through tagged content is the issue.

For next item in tag list my take <li>{% if pagination.href.next and pagination.tag.tagName == pagination.tag.tagName.next %}<a href="{{ pagination.href.next }}">Next</a> {% else %}Next{% endif %}</li> iterates through all tags.

kaviars avatar May 04 '20 09:05 kaviars

Hi @kaviars, I got in the same situation woking on multi-level pagination or as you call it, double layered pagination (I also came up with deep pagination, but you know what I mean). So I thought I'd share my solution to this particular problem here. The solution I'm presenting is based on @zachleat code seen here #332. As you experiment, using @zachleat code as is with the built in pagination navigation will encompass all of the pages which is normal behavior since it's using a pagination of size: 1. So I decided to integrate collection specific navigation as properties. It worked well for first, previous, next and last properties, but in the end I had to retort to an extra collection for page listing (I'm more than open for improvements suggestions). Here is what it looks like:

.eleventy.js

const pagedTagsCollection = require('./_src/_includes/collections/pagedTags');

module.exports = function(config) {
    config.addCollection('posts', collection => collection.getFilteredByGlob('_src/posts/*.md'));
    config.addCollection('pagedTags', collection => {
        return pagedTagsCollection(collection);
    });
    config.addCollection('pagedTagsListing', collection => {
        return pagedTagsCollection(collection).reduce((accumulatorObject, currentItem) => {
            const tagNameProp = currentItem.tagName;
            if(!accumulatorObject[tagNameProp]) accumulatorObject[tagNameProp] = [];
            accumulatorObject[tagNameProp].push(currentItem);
            return accumulatorObject;
        }, {});
    });
}

collections/pagedTags.js

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

module.exports = collection => {
    const postsCollection = collection.getFilteredByGlob('_src/posts/*.md');
    let tagSet = new Set();
    postsCollection.forEach(templateObjet => {
        if('tags' in templateObjet.data) {
            const tagsProperty = templateObjet.data.tags;
            if(Array.isArray(tagsProperty)) {
                tagsProperty.forEach(tag => tagSet.add(tag));
            } else if(typeof tagsProperty === 'string') {
                tagSet.add(tagsProperty);
            }
        }
    });
    const pagedTags = [];
    let pagedCollectionMaxIndex;
    [...tagSet].forEach(tag => {
        const tagCollection = collection.getFilteredByTag(tag);
        const pagedCollection = lodashChunk(tagCollection, 4);
        pagedCollection.forEach((templateObjectsArray, index) => {
            pagedCollectionMaxIndex = index;
            pagedTags.push({
                tagName: tag,
                path: `/tags/${tag}/${index ? (index + 1) + '/' : ''}`,
                pageNumber: index,
                templateObjets: templateObjectsArray
            });
        });
    });
    const pagedCollectionLength = ++pagedCollectionMaxIndex;
    const groupedByTagName = lodashChunk(pagedTags, pagedCollectionLength);
    groupedByTagName.forEach(group => {
        group.forEach((pageObject, index, source) => {
            pageObject.first = source[0].path;
            pageObject.last = source[source.length - 1].path;
            if(source[index - 1]) pageObject.previous = source[index - 1].path;
            if(source[index + 1]) pageObject.next = source[index + 1].path;
        });
    });
    return pagedTags;
}

Using it in a template tags.njk:

---
layout: base-layout.njk
pagination:
  data: collections.pagedTags
  size: 1
  alias: tag
  addAllPagesToCollections: true
permalink: tags/{{ tag.tagName }}/{{ (tag.pageNumber + 1) + "/" if tag.pageNumber }}
eleventyComputed:
  title: "{{ tag.tagName }}-page-{{ tag.pageNumber }}"
---
{% include 'partials/tags-header.njk' %}
<hr>
<main>
  <section class="post-listing">
    {% for post in tag.templateObjets %}
    <div class="listing-card">
      <h2>{{ post.data.title }} on {{ tag.tagName }}</h2>
      <a href="{{ post.url }}">Read more!</a>
    </div>
    {% endfor %}
  </section>
  <nav aria-labelledby="page-navigation">
    <p id="page-navigation"><big><b>Tagged page navigation</b></big></p>
    <ul>
      <li>{% if page.url !== tag.first %}<a href="{{ tag.first }}">First</a>{% else %}First{% endif %}</li>
      <li>{% if tag.previous %}<a href="{{ tag.previous }}">Previous</a>{% else %}Previous{% endif %}</li>
      {% for item in collections.pagedTagsListing[tag.tagName] %}
        <li><a href="{{ item.path }}"{% if page.url == item.path %} aria-current="page"{% endif %}>Page {{ item.pageNumber + 1 }}</a></li>
      {% endfor %}
      <li>{% if tag.next %}<a href="{{ tag.next }}">Next</a>{% else %}Next{% endif %}</li>
      <li>{% if page.url !== tag.last %}<a href="{{ tag.last }}">Last</a>{% else %}Last{% endif %}</li>
    </ul>
  </nav>
  <a href="/">Back to Home page</a>
</main>

I also created a live demo hosted on Netlify! I will probably use this solution in production until double layered pagination becomes a feature (or if it does). I hope this answers some of your questions. Have a good day!

solution-loisir avatar Dec 07 '20 15:12 solution-loisir

I forgot, this is what the data looks like:

pagedTags

[
  {
    tagName: 'JavaScript',
    path: '/tags/JavaScript/',
    pageNumber: 0,
    templateObjets: [ [Object], [Object], [Object], [Object] ],
    first: '/tags/JavaScript/',
    last: '/tags/JavaScript/3/',
    next: '/tags/JavaScript/2/'
  },
  {
    tagName: 'JavaScript',
    path: '/tags/JavaScript/2/',
    pageNumber: 1,
    templateObjets: [ [Object], [Object], [Object], [Object] ],
    first: '/tags/JavaScript/',
    last: '/tags/JavaScript/3/',
    previous: '/tags/JavaScript/',
    next: '/tags/JavaScript/3/'
  },
  {
    tagName: 'JavaScript',
    path: '/tags/JavaScript/3/',
    pageNumber: 2,
    templateObjets: [ [Object], [Object] ],
    first: '/tags/JavaScript/',
    last: '/tags/JavaScript/3/',
    previous: '/tags/JavaScript/2/'
  },
  {
    tagName: 'GO',
    path: '/tags/GO/',
    pageNumber: 0,
    templateObjets: [ [Object], [Object], [Object], [Object] ],
    first: '/tags/GO/',
    last: '/tags/GO/3/',
    next: '/tags/GO/2/'
  },
  {
    tagName: 'GO',
    path: '/tags/GO/2/',
    pageNumber: 1,
    templateObjets: [ [Object], [Object], [Object], [Object] ],
    first: '/tags/GO/',
    last: '/tags/GO/3/',
    previous: '/tags/GO/',
    next: '/tags/GO/3/'
  },
  {
    tagName: 'GO',
    path: '/tags/GO/3/',
    pageNumber: 2,
    templateObjets: [ [Object], [Object] ],
    first: '/tags/GO/',
    last: '/tags/GO/3/',
    previous: '/tags/GO/2/'
  },
  {
    tagName: 'Rust',
    path: '/tags/Rust/',
    pageNumber: 0,
    templateObjets: [ [Object], [Object], [Object], [Object] ],
    first: '/tags/Rust/',
    last: '/tags/Rust/3/',
    next: '/tags/Rust/2/'
  },
  {
    tagName: 'Rust',
    path: '/tags/Rust/2/',
    pageNumber: 1,
    templateObjets: [ [Object], [Object], [Object], [Object] ],
    first: '/tags/Rust/',
    last: '/tags/Rust/3/',
    previous: '/tags/Rust/',
    next: '/tags/Rust/3/'
  },
  {
    tagName: 'Rust',
    path: '/tags/Rust/3/',
    pageNumber: 2,
    templateObjets: [ [Object], [Object] ],
    first: '/tags/Rust/',
    last: '/tags/Rust/3/',
    previous: '/tags/Rust/2/'
  },
  {
    tagName: 'PHP',
    path: '/tags/PHP/',
    pageNumber: 0,
    templateObjets: [ [Object], [Object], [Object], [Object] ],
    first: '/tags/PHP/',
    last: '/tags/PHP/3/',
    next: '/tags/PHP/2/'
  },
  {
    tagName: 'PHP',
    path: '/tags/PHP/2/',
    pageNumber: 1,
    templateObjets: [ [Object], [Object], [Object], [Object] ],
    first: '/tags/PHP/',
    last: '/tags/PHP/3/',
    previous: '/tags/PHP/',
    next: '/tags/PHP/3/'
  },
  {
    tagName: 'PHP',
    path: '/tags/PHP/3/',
    pageNumber: 2,
    templateObjets: [ [Object], [Object] ],
    first: '/tags/PHP/',
    last: '/tags/PHP/3/',
    previous: '/tags/PHP/2/'
  }
]

pagedTagsListing

{
  JavaScript: [
    {
      tagName: 'JavaScript',
      path: '/tags/JavaScript/',
      pageNumber: 0,
      templateObjets: [Array],
      first: '/tags/JavaScript/',
      last: '/tags/JavaScript/3/',
      next: '/tags/JavaScript/2/'
    },
    {
      tagName: 'JavaScript',
      path: '/tags/JavaScript/2/',
      pageNumber: 1,
      templateObjets: [Array],
      first: '/tags/JavaScript/',
      last: '/tags/JavaScript/3/',
      previous: '/tags/JavaScript/',
      next: '/tags/JavaScript/3/'
    },
    {
      tagName: 'JavaScript',
      path: '/tags/JavaScript/3/',
      pageNumber: 2,
      templateObjets: [Array],
      first: '/tags/JavaScript/',
      last: '/tags/JavaScript/3/',
      previous: '/tags/JavaScript/2/'
    }
  ],
  GO: [
    {
      tagName: 'GO',
      path: '/tags/GO/',
      pageNumber: 0,
      templateObjets: [Array],
      first: '/tags/GO/',
      last: '/tags/GO/3/',
      next: '/tags/GO/2/'
    },
    {
      tagName: 'GO',
      path: '/tags/GO/2/',
      pageNumber: 1,
      templateObjets: [Array],
      first: '/tags/GO/',
      last: '/tags/GO/3/',
      previous: '/tags/GO/',
      next: '/tags/GO/3/'
    },
    {
      tagName: 'GO',
      path: '/tags/GO/3/',
      pageNumber: 2,
      templateObjets: [Array],
      first: '/tags/GO/',
      last: '/tags/GO/3/',
      previous: '/tags/GO/2/'
    }
  ],
  Rust: [
    {
      tagName: 'Rust',
      path: '/tags/Rust/',
      pageNumber: 0,
      templateObjets: [Array],
      first: '/tags/Rust/',
      last: '/tags/Rust/3/',
      next: '/tags/Rust/2/'
    },
    {
      tagName: 'Rust',
      path: '/tags/Rust/2/',
      pageNumber: 1,
      templateObjets: [Array],
      first: '/tags/Rust/',
      last: '/tags/Rust/3/',
      previous: '/tags/Rust/',
      next: '/tags/Rust/3/'
    },
    {
      tagName: 'Rust',
      path: '/tags/Rust/3/',
      pageNumber: 2,
      templateObjets: [Array],
      first: '/tags/Rust/',
      last: '/tags/Rust/3/',
      previous: '/tags/Rust/2/'
    }
  ],
  PHP: [
    {
      tagName: 'PHP',
      path: '/tags/PHP/',
      pageNumber: 0,
      templateObjets: [Array],
      first: '/tags/PHP/',
      last: '/tags/PHP/3/',
      next: '/tags/PHP/2/'
    }
  ]
}

solution-loisir avatar Dec 07 '20 15:12 solution-loisir

As a note, I would have rather merge the two collections into one returning an object with pagedTags under a pages key. But for some reasons pagination through collections.pagedTagsObject.pages returned an error (although it works in for loops).

solution-loisir avatar Dec 08 '20 01:12 solution-loisir

I just figured out a simpler way to list pages without the need of an extra collection like this:

{% for item in collections.pagedTags %}
    {% if tag.tagName === item.tagName %}
        <li><a href="{{ item.path }}"{% if page.url == item.path %} aria-current="page"{% endif %}>Page {{ item.pageNumber + 1 }}</a></li>
    {% endif %}
 {% endfor %}

That way I can implement navigation solely with pagedTags collection and get rid of pagedTagsListing altogether!

solution-loisir avatar Dec 12 '20 23:12 solution-loisir

For anyone who comes across this in the future, I found this article to be really helpful in implementing double-layered pagination: https://www.webstoemp.com/blog/basic-custom-taxonomies-with-eleventy/. Works like a charm!

AleksandrHovhannisyan avatar Jun 27 '21 20:06 AleksandrHovhannisyan

#3987 is the new home base for double layer pagination!

zachleat avatar Nov 12 '25 17:11 zachleat