Navigate into a collection with hasNext/hasPrev

Hi,

I implemented a navigation in my portfolio with hasNextVisible() and hasPrevVisible() and it works well (even with keyboard, you can test it here)

Now I wonder if there is a way to do the same thing inside a collection of page, like tags, because if I click on a project after seeing it in a tag page, the next one is not the next in this filtered collection but the next child of global parent.

I wondered out of curiosity if there was a native simple way to remember the “real” next in collection that I miss or if I have to use session and complicated tricks to do it (in this case, I will not implement it)

Tell me if you have some leads !

Thanks.

Just an idea, not tested:

You could use an URL parameter to pass the tag to the project page. Instead of using the standard page methods, you could create your own custom page methods https://getkirby.com/docs/developer-guide/objects/page, that use the information from the parameter to determine the next/prev pages (custom page methods will be available in the upcoming 2.3 release).

Edit: That would in fact work if you copy the code from the protected methods _prev() and _next() into your own page methods. Then you can pass a filtered collection to those methods like this.

// the referer is passed from the projects overview page
<?php $referer = param('referer'); ?>
<nav class="nextprev cf" role="navigation">

      <?php if($prev = $page->_prev($page->siblings()->filterBy('tags', $referer, ','))): ?>
      <a class="prev" href="<?php echo $prev->url() . "/referer:" . $referer ?>">&larr; previous</a>
      <?php endif ?>
      <?php if($next = $page->_next($page->siblings()->filterBy('tags', $referer, ','))): ?>
      <a class="next" href="<?php echo $next->url() . "/referer:" . $referer ?>">next &rarr;</a>
      <?php endif ?>
    </nav>

Note that the above is just a rough outline to illustrate the idea using the native protected _prev() and _next() methods (by hacking the core to make them public for testing). Also, you would have to check if the referer exists etc.

OK I understand the idea. I will look the possibilities and see if it’s worth the extra work.

Thanks for your insight

Just as an update: I needed the same functionality for a project and it works fine; all that is needed is to copy the code into custom page methods:

page::$methods['getNext'] = function($page, Children $siblings, $sort = array(), $visibility = false) {
  if($sort) $siblings = call(array($siblings, 'sortBy'), $sort);
  $index = $siblings->indexOf($page);
  if($index === false) return null;
  if($visibility) {
    $siblings = $siblings->offset($index+1);
    $siblings = $siblings->{$visibility}();
    return $siblings->first();
  } else {
    return $siblings->nth($index + 1);
  }
};

page::$methods['getPrev'] = function($page, Children $siblings, $sort = array(), $visibility = false) {
  if($sort) $siblings = call(array($siblings, 'sortBy'), $sort);
  $index = $siblings->indexOf($page);
  if($index === false or $index === 0) return null;
  if($visibility) {
    $siblings = $siblings->limit($index);
    $siblings = $siblings->{$visibility}();
    return $siblings->last();
  } else {
    return $siblings->nth($index - 1);
  }
};

Then you can use those methods in your next/prev snippet:

<nav>
  <ul class="prevNext">
    <?php if($prev = $page->getPrev($collection)): ?>
      <li class="previous"><a href="<?= $prev->url() ?>">prev</a></li>
    <?php endif ?>
    <?php if($next = $page->getNext($collection)): ?>
      <li class="next"><a href="<?=$next->url()?>">next</a></li>
    <?php endif ?>
  </ul>
</nav>

You only have to add a way to preserve the original collection, by passing a parameter or storing the values in a session or whatever.

1 Like

Thank you for the follow up ! I will look at it but I think that unfortunately it’s waaaay beyond my developer skills. I will try to implement it one day tough. Thanks for the detailed example.

thanks! worked great.

is there a way to get the current count of the $collection?

$page->num()

obviously returns the wrong number.

num() returns the sorting number of a page, you can get the index, i.e. the number within the collection, like in this example:

<?php
$collection = $page->siblings();
$index  = $collection->indexOf($page);
1 Like

This is the solution I am looking, only then to work with tags instead of siblings of a collection. How would this change the methode?

Or is the idea that the current $collection is an array with the order of projects that is currently viewed?

In the methode I don’t see anything done with the collection information. Just trying to make sense of this :slight_smile:

Just for reference my situation is as follow:

  • I have a list of projects shown on the showcase.php
  • The order of the list is sorted by year, within the year sorted by modification date
  • these projects can be filtered with tags

Showcase

$category  = param('category');
if(isset($category)) {
	// If there is a parameter set then only load projects with that parameter
	$projects = page('projects')->children()->filterBy('category', $category);
} else {
	// Load all projects
	$projects = page('projects')->children();
}

<ul class="showcase grid gutter-1">
  <?php foreach($projects->sortBy('year', 'desc', 'modified', 'desc') as $project): ?>
    <li class="showcase-item column <?= $project->type() ?>">
        <a href="<?= $project->url() ?>" class="showcase-link">
          <?php if($image = $project->images()->sortBy('sort', 'asc')->first()): $thumb = $image->crop(400, 400, 60); ?>
            <img src="<?= $thumb->url() ?>" alt="Thumbnail for <?= $project->title()->html() ?>" class="showcase-image" />
          <?php endif ?>
        </a>
    </li>
  <?php endforeach ?>
</ul>

The above methods are intended to replace the standard prev/next methods like they are used in the prevnext.php snippet of the starterkit. Instead of just fetching the next page, you would pass the collection to the method so that not only the sort order but also the filtering is taken into consideration when trying to get the next page.

The thing is that you have to pass the information about the collection down to the single page, so the $collectionvariable would be something like this

$referer = param('referer'); ?>
$collection = $page->siblings()->sortBy('whatever')->filterBy('tags' $referer, ',');

where $referer fetches a referer parameter that is sent from the page the user comes from (the parent page with the collection).

Is this what you are looking for or are you talking about pagination?

Ah yes, I understand now what is happening here. In this case you can load $collection (or another variable) with the list you want to navigate through with ‘next’ and ‘previous’. But the right order should already be in $collection.

Thanks for helping out and writing the custom page method. This is something that can be used in so many situations.

Thank you very much!

1 Like

Trying to use this solution as I made a custom collection with

$collection = pages(array('home', 'events', 'contact'));

and what I need is to navigate with a scrollTo effect to each item and subitem of the custom collection in one page.

I added the Custom Page Method code you posted inside site/plugins/page-method.php as is (eg without the first line being <?php), and add the navigation syntax in the template.

The output I’m having is the code in page-method.php being printed at the top of the page.

If I add <?php to the beginning of page-method.php I get this error

Argument 2 passed to Kirby::{closure}() must be an instance of Children, instance of Pages given, called in /website/kirby/vendor/getkirby/toolkit/helpers.php on line 282

What am I overseeing?

The method expects an object of class Children, you are passing an object of type Pages. If you change your code so that you get a Children object, it should work:

$collection = $site->pages()->find('home', 'projects', 'contact');

Of course, if you omit the opening PHP tag from your code, it is rendered as plain text instead of being executed, so you can’t just leave the tag out.

Sweet, thank you.

Working but every link is printing only the project page url.

I think I should flatten my collection, as then I need to print out each subpage once we reach the project page.

EDIT

Oh now I see why I get always the Project page, the first and last page both direct respectively to the next and previous element. LOL, ok more green tea, it’s Sat :smiley: