1

I have a section, where i display a list of entries from a structure. The section is an overview page, where the entries are listed with a picture and title.

If the user clicks on an image, a detail (entry)- page is shown, where it is possible to jump right away to the next entry, instead of going back to the overview. works fine with:

 {% set params = craft.entries.section('kelims') %}
 {% set prevEntry = entry.getPrev(params) %}
 {% set nextEntry = entry.getNext(params) %}

there is also a filter on the overview page, where the user can choose from multiple categories and display the entries, based on checkboxes. each checkbox represents a category and depending on what checkbox is clicked, the entry gets a class to hide or show it. so entries are shown without page-reload. each entry has multiple categories.

now if a user uses theses filters and the clicks a detail page, and then wants to get to the next entry (with the {% set prevEntry = entry.getPrev(params) %} it does not work anymore. because the param does not reflect the new classes to hide or show the entry.

is there a way to alter the param settings? or how to i get this working?

More explanation for the setup: i have 4 Category Groups with several categories. The use can click multiple and they filter by AND. So there are catPlace, catTime, catSize, catType

EDIT: After googling and asking, this should be the theory for the solution, but i dont know how to put into code:

with craft.app.request.getQueryParam i fetch the ids of the different category-groups and append them to the url of the item-link, like ?caPlace=72&catTime=123, and use this on the Detailpage for the getPrev()/getNext()

EDIT 2 The Section Overview Page Looks like this:

{% set categoriesObject = craft.categories.group('catObject').all() %}
{% set categoriesSize = craft.categories.group('catSize').all() %}
{% set categoriesProvenienz = craft.categories.group('catProvenienz').all() %}
{% set categoriesYear = craft.categories.group('catYear').all() %}

<div class="content-grid filter-wrap result" id="filter-wrap">
{# {% set kelims = entry.kelimsEntrieField.all() %} #}
{% set kelims = craft.entries.section('kelims') %}

{% for entry in kelims %}

{# Fetch all of the categories related to this entry #}
{% set categories = craft.categories.relatedTo(entry).ids() %}

    
    {% set favIdStringClass = categories|join(' ') %}
    {% set favIdStringData = categories|join(',') %}
    {% set catGroupId = entry.FilterCatObject %}
    {% set kelimImage = entry.kelimImage.one().getUrl('kelimthumb')|default('https://dummyimage.com/600x600/d1d1d1/d1d1d1.png&text=Sample') %}
    
        <div class="result__item" data-entry-id="{{ entry.id }}">
            <a href="{{entry.url}}" class="content-grid__box filter-item result__link {{favIdStringClass}}">
            
                <div 
                style="background-image: url( {{kelimImage}} );"
                loading="lazy"
                class="bgimage lazy"
                >
                    <h2 class="content-grid__box-title">{{ entry.title }}</h2>
                </div>
            </a>
        </div>
    {% endfor %}
    
</div>

and the script for the Filtering:

function change() {
  const catObject = document.querySelectorAll(".catObject input[type='checkbox']");
  const catSize = document.querySelectorAll(".catSize input[type='checkbox']");
  const catProvenienz = document.querySelectorAll(".catProvenienz input[type='checkbox']");
  const catYear = document.querySelectorAll(".catYear input[type='checkbox']");
  const filters = {
    Cobject: getClassOfCheckedCheckboxes(catObject),
    Csize: getClassOfCheckedCheckboxes(catSize),
    Cprovenienz: getClassOfCheckedCheckboxes(catProvenienz),
    Cyear: getClassOfCheckedCheckboxes(catYear),
  };
  
  filterResults(filters);
}

function getClassOfCheckedCheckboxes(checkboxes) {
  const classes = [];
  
  if (checkboxes && checkboxes.length > 0) {
    for (let i = 0; i < checkboxes.length; i++) {
      const cb = checkboxes[i];
      
      if (cb.checked) {
        classes.push(cb.getAttribute('data-value'));
      }
    }
  }
  
  return classes;
}

function filterResults(filters) {
  const rElems = document.querySelectorAll('.result a');
  const hiddenElems = [];
  
  if (!rElems || rElems.length <= 0) {
    return;
  }
  
  for (var i = 0; i < rElems.length; i++) {
    const el = rElems[i];

    if (filters.Cobject.length > 0) {
      var isHidden = true;

      for (var j = 0; j < filters.Cobject.length; j++) {
        var filter = filters.Cobject[j];

        if (el.classList.contains(filter)) {
          isHidden = false;
          break;
        }
      }

      if (isHidden) {
        hiddenElems.push(el);
      }
    }

    if (filters.Csize.length > 0) {
      var isHidden = true;

      for (var j = 0; j < filters.Csize.length; j++) {
        var filter = filters.Csize[j];

        if (el.classList.contains(filter)) {
          isHidden = false;
          break;
        }
      }

      if (isHidden) {
        hiddenElems.push(el);
      }
    }
    

    if (filters.Cprovenienz.length > 0) {
      var isHidden = true;

      for (var j = 0; j < filters.Cprovenienz.length; j++) {
        var filter = filters.Cprovenienz[j];

        if (el.classList.contains(filter)) {
          isHidden = false;
          break;
        }
      }

      if (isHidden) {
        hiddenElems.push(el);
      }
    }


    // Year
    if (filters.Cyear.length > 0) {
      var isHidden = true;

      for (var j = 0; j < filters.Cyear.length; j++) {
        var filter = filters.Cyear[j];

        if (el.classList.contains(filter)) {
          isHidden = false;
          break;
        }
      }

      if (isHidden) {
        hiddenElems.push(el);
      }
    }
  }

  for (var i = 0; i < rElems.length; i++) {
    // rElems[i].style.display = "block";
    rElems[i].classList.remove('hide');
    rElems[i].classList.add('block');
  }

  if (hiddenElems.length <= 0) {
    return;
  }

  for (var i = 0; i < hiddenElems.length; i++) {
    // hiddenElems[i].style.display = "none";
    hiddenElems[i].classList.add('hide');
    hiddenElems[i].classList.remove('block');
  }
}
1

Your entry detail page needs to be aware of the filters applied on the overview page in order for this to work. I see a couple of ways to do this:

  1. Whenever the filters change, use JS to make an AJAX call that passes the selected filters to a custom controller action. This controller then saves the filters in the session, then the entry detail page can use it the filters as parameters for getNext() / getPrevious(). This is a bit flimsy, since it can easily break and might result in unexpected behaviour if the user visits other pages in the meantime.
  2. Whenever a filter changes, dynamically modify the links to the entries on your overview page with JS to include the current filters as GET parameters. Then the entry page can read those parameters from the request to get the next / previous entry. This requires you to replicate the filtering logic in your backend template and make sure the JS-based filtering works exactly like the backend query.
  3. Same as 2, but instead of passing parameters, pass the ID of the previous / next entry directly.

I'd say the most robust solution is #2. The easiest would be #3.

Sample code for approach #3

For this to work, you'd need to adjust your overview template so the entries list includes the entries' IDs:

<ul class="entry-list">
    {% for item in craft.entries().section('kelims').all() %}
        <li class="entry-list__item" data-entry-id="{{ item.id }}">
            <a href="{{ entry.url }}" class="entry-list__link">
                {{- item.title -}}
            </a>
        </li>
    {% endfor %}
</ul>

Then whenever the filters changes, after you have filtered the entries with JS, add the ID of the previous / next entry to the URL. Quick and might contain errors, but you get the idea:

const refreshEntryLinks = () => {
    const entryList = document.querySelector('.entry-list');
    const visibleEntries = Array.from(entryList.querySelectorAll('.entry-list__item'))
        // filter out hidden entries (this assumes elements are hidden with the `hidden` attribute)
        .filter(entry => !entry.hidden);
    visibleEntries.forEach((entry, index) => {
        const link = entry.querySelector('.entry-list__link')
        const prevIndex = index > 0 ? index - 1 : null;
        const nextIndex = index < visibleEntries.length - 1 ? index + 1 : null;
        const addParams = new URLSearchParams;
        if (null !== prevIndex) {
            const prevId = visibleEntries[prevIndex].dataset.entryId;
            addParams.append('prevId', prevId);
        }
        if (null !== nextIndex) {
            const nextId = visibleEntries[nextIndex].dataset.entryId;
            addParams.append('nextId', nextId);
        }
        const modifiedUrl = new URL(link.href);
        modifiedUrl.search = addParams;
        link.href = modifiedUrl;
    });
}

Finally, you can use the prevId and nextId parameters added to the URL in the detail page to display links to those entries:

{% set prevId = craft.request.getQuery('prevId') %}
{% set nextId = craft.request.getQuery('nextId') %}
{% set prevEntry = prevId ? craft.entries.section('kelims').id(prevId).one() : null %}
{% set nextEntry = nextId ? craft.entries.section('kelims').id(nextId).one() : null %}
11
  • Thanks! it does not get/find the prevID or nextID. Maybe the filter you applied !entry.hidden does not work? What exactly do you mean by the hidden attribute? I have set a class of "hide" or "block, after unchecking the checkbox.
    – Henrik
    May 28 at 8:40
  • I edited my question as well. hope this is understandable.
    – Henrik
    May 28 at 8:42
  • @Henrik In your JS-based filter, how are you hiding the entries that don't match the selected filters? The code sample above assumes you're using the hidden Attribute. If you're using a class or setting display: none; manually, you need to adjust the condition in the filter callback. For example: .filter(entry => !entry.classList.contains('is-hidden')).
    – MoritzLost
    May 28 at 8:48
  • @Henrik In regards to your edit: Your proposed solution is approach #2 in my answer. The JS code would be similar to my sample code posted here. Only instead of adding the IDs of the previous / next entries, you would be adding the selected category IDs to the URL so you can filter entries in the backend based on that. I could try to write some sample code for that as well, but you'd need to post your overview template and preferrably the JS filtering code for that so I don't have to make guesses regarding your HTML structure :)
    – MoritzLost
    May 28 at 8:51
  • thanks, i adjusted the code with the correct class, but it still gives me the Variable "prevEntry" does not exist.. Totally understand, i will try to post my code as readable as possible. The code is quite old now, i got it from another question asked here, and it just worked well for me. Although i only understand half of it, i am the "learning by doing" js-guy, which makes it sometimes quite hard :).
    – Henrik
    May 28 at 9:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.