Any idea how to influence the order in a dynamically generated category-/ tag-cloud?

I there. I started with kirby a week ago and am pretty happy with all the options kirby offers.

At the moment I stuck at a problem. I have a dynamically generated category- and tag-list in my navigation. For this I created a file in my site/controller-dolswe, which is going through all my articles and analyzes the categories and tags.

<?php

  return function ( $site, $pages, $page ) {

    $articles = $page->children();

    if($page->template() == 'item'){
      $articles = $page->parent()->children();
    }
    
    $categories = $articles->pluck( 'categories', ',', true );
    $tags = $articles->pluck( 'tags', ',', true );

    if ( $category = urldecode( param( 'category' ) ) ) {
      $articles = $articles->filterBy( 'categories', $category, ',' )->sortBy( 'date' )->flip();
    } else {
      $articles = $articles->sortBy( 'date' )->flip();
    }

    if ( $tag = urldecode( param( 'tag' ) ) ) {
      $articles = $articles->filterBy( 'tags', $tag, ',' )->flip();
    } else {
      $articles = $articles->sortBy( 'date' )->flip();
    }

    return compact( 'articles', 'categories', 'category', 'tags', 'tag' );

  };`

In my navigation bar snippet I loaded all existing categories and tags and it works like a charme. Here is a fragment of this snippet:

`<div class="categories" id="kategorien">
      <?php foreach ( $categories as $category ) : ?>
          <a href="<?php echo url($page->url(), ['params' => ['category' => $category]]) ?>" class="category<?php if ( $category == urlencode( param( 'category' ) ) ) : ?> active<?php endif; ?>" rel="category tag">
            <?= $category ?>
          </a>
      <?php endforeach ?>
    </div>

    <div class="tags" id="materials">
      <?php foreach ( $tags as $tag ) : ?>
          <a href="<?php echo url($page->url(), ['params' => ['tag' => $tag]]) ?>" class="tag<?php if ( $tag == urlencode( param( 'tag' ) ) ) : ?> active<?php endif; ?>" rel="category tag">
            <?= $tag ?>
          </a>
      <?php endforeach ?>
    </div>`

I really like the concept of loading all categories and tags dynamically. My colleague asked me, if there is an option to influence the sorting of the tags manually. Unfortunately I have no idea how to achieve a manually sorted order without hardcoding my links into the template. Do you have an idea or know a good alternative solution for this?

Thank you very much for your help. :bowing_man:

Hey, welcome to our community!

I assume the tags can be freely applied without any limitations?

The only way I can currently think of in this case is to create a structure field that you populate with all available tags whenever a page is saved (i.e. using a page.update hook).

Then the items inside the structure could be manually reordered. But that would mean unless you reorder the tags regularly, new ones would be added to the beginning or end of the array.

What is the purpose of a manual sort order as opposed to sorting alphabetically or by most used?

1 Like

Hi there, adding to this thread. I have a tags field on the portfolio site I’m building, and I’ve included a tag “recent” so that the artist can mark projects that were … made recently. But I don’t want “recent” to be mixed in with the rest of the tags, but to go at the end. Any tips on moving the one tag “Recent” to the end?

Here’s my current tag list code:

<?php foreach($tags as $tag): ?>
        <li <?php echo $tag === param('tag') || ($tag === 'all' && is_null(param('tag'))) ? 'class="active"' : '';  ?> >
          <a href="<?= url($page->url(), ['params' => ['tag' => $tag]]) ?>"><?= html($tag) ?></a>
        </li>
<?php endforeach ?>

Thanks!

  $tags = $page->tags()->split(',');
  $key = array_search('recent', $tags, true);
  unset($tags[$key]);
  $tags[] = 'recent';

Hey thanks, so I incorporated the code like so, but doesn’t seem like I did it right. Pardon my patchy knowledge of PHP, here…

<?php foreach($tags as $tag): $tags = $page->tags()->split(','); $key = array_search('recent', $tags, true); unset($tags[$key]); $tags[] = 'recent';?>
            <li <?php echo $tag === param('tag') || ($tag === 'all' && is_null(param('tag'))) ? 'class="active"' : '';  ?> >
              <a href="<?= url($page->url(), ['params' => ['tag' => $tag]]) ?>"><?= html($tag) ?></a>
            </li>
      <?php endforeach ?>

Well, you have to place the code where you define your tags variable, i.e. the part that we don’t see here, and therefore before the loop.

I actually don’t have any area on the page (currently) that defines the tag variable.

Just added this above the loop:

<?php
 $tags = $page->tags()->split(',');
  $key = array_search('recent', $tags, true);
  unset($tags[$key]);
  $tags[] = 'recent';

?>

And it now on the front end, it only shows the recent tag. I want to do the opposite, print everything BUT the recent tag. So I can manually put it at the end. If that makes sense. I want the tag list to read like so (irrespective of if we add new tags down the road):

All Book Website Poster Identity Recent

Well, the problem is that I don’t know where your $tags variable is defined. If that doesn’t happen in the template, then likely in the controller.

Maybe the variable doesn’t contain the page tags, but all tags of multiple children. But you need to provide this information, anything else is just guess work.

Are you using a theme, or why don’t you know where the variable is defined?

Ah yes! Okay here is the code from the controller:

return function($page) {

  // fetch the basic set of pages
  $articles = $page->children()->listed();

  // fetch all tags
  $tags = $articles->pluck('tags', ',', true);

  // add the tag filter
  if($tag = param('tag')) {
    $articles = $articles->filterBy('tags', $tag, ',');
  }

  // apply pagination
  $articles   = $articles->paginate();
  $pagination = $articles->pagination();

  return compact('articles', 'tags', 'tag', 'pagination');

};

Hi there, on a portfolio site, I’ve got all the projects sorted by tags. I have read through some other posts on the forum about this, but none seem to quite solve my question. I added a tag “recent”.

I would like to do two things:
— order the tags instead of alphabetically, → by most frequently used.
— force the tag “recent” to be last, no matter what. Like so:
All, Book, Identity, Poster, Mural, Animation, Recent

For now, here is how I’m pulling the tags in my archive.php template page:

      <ul class="tags">
            <?php foreach($tags as $tag => $frequency): ?>
        <li <?php echo $tag === param('tag') || ($tag === 'all' && is_null(param('tag'))) ? 'class="active"' : '';   ?> >
          <a href="<?= url($page->url(), ['params' => ['tag' => $tag]]) ?>"><?= html($tag) ?></a>
        </li>
        <?php endforeach ?>
      </ul>

This is my archive.php controller:

return function($page) {

  // fetch the basic set of pages
  $articles = $page->children()->listed();

  // fetch all tags
  $tags = $articles->pluck('tags', ',', true);

// flip the array to prevent duplicate keys
$tags = array_flip($tags);

// assign the number of albums with that tag to the value
array_walk($tags, function(&$value, $key) use($articles) {
  return $value = $articles->filterBy('tags', $key, ',')->count();

});
// reverse sort the array
arsort($tags);

  // add the tag filter
  if($tag = param('tag')) {
    $articles = $articles->filterBy('tags', $tag, ',');
  }

  // apply pagination
  $articles   = $articles->listed();

  return compact('articles', 'tags', 'tag');

};

Any ideas? Thank you!

This thread had the same question (apart from the recent part):

I thought I had already answered that recent question last week?

You just have to place this below where you define the tags (apart from the line that defines the tags variable.

Hey, thanks. So when I placed that code in my archive.php controller, what it did in the front end was this:
now only the tag “recent” shows up, and none of the others. This is how I incorporated it into the archive.php controller, is this incorrect?

return function($page) {

  // fetch the basic set of pages
  $articles = $page->children()->listed();

  // fetch all tags
  $tags = $articles->pluck('tags', ',', true);

$tags = $page->tags()->split(','); $key = array_search('recent', $tags, true); unset($tags[$key]); $tags[] = 'recent';

// flip the array to prevent duplicate keys
$tags = array_flip($tags);

// assign the number of albums with that tag to the value
array_walk($tags, function(&$value, $key) use($articles) {
  return $value = $articles->filterBy('tags', $key, ',')->count();

});
// reverse sort the array
arsort($tags);

  // add the tag filter
  if($tag = param('tag')) {
    $articles = $articles->filterBy('tags', $tag, ',');
  }

  // apply pagination
  $articles   = $articles->listed();

  return compact('articles', 'tags', 'tag');

};

This needs to be removed.

Removed that, now “recent” isn’t forced to the end. Perhaps the flip is conflicting with that line?

return function($page) {

  // fetch the basic set of pages
  $articles = $page->children()->listed();

  // fetch all tags
  $tags = $articles->pluck('tags', ',', true); 

$key = array_search('recent', $tags, true); unset($tags[$key]); $tags[] = 'recent';

// flip the array to prevent duplicate keys
$tags = array_flip($tags);

// assign the number of albums with that tag to the value
array_walk($tags, function(&$value, $key) use($articles) {
  return $value = $articles->filterBy('tags', $key, ',')->count();

});
// reverse sort the array
arsort($tags);

  // add the tag filter
  if($tag = param('tag')) {
    $articles = $articles->filterBy('tags', $tag, ',');
  }

  // apply pagination
  $articles   = $articles->listed();

  return compact('articles', 'tags', 'tag');

};

Definitely, because it flips the order again.

When I remove that line, it just prints out the numbers 1 through 10.

My end goal is to have the tags ordered by frequency of use, but with the exception of “Recent” which I want to ALWAYS go last. Don’t think I’m there yet with the above code if I remove the flip, which was part of the code that is arranging the tag list by frequency, right?

If I remove “arsort($tags);” then recent is pushed to the end but it is no longer ordering the tags by frequency.

You have to combine the two approaches from the two posts, otherwise it won’t work. You can’t get the frequency if you only have unique tags.

Ah ok. I actually don’t know how to write PHP, I always just copy paste from pre-existing, and make minor adjustments. How would I do combine these two without breaking the code?

Might anyone be able to help combine those two pieces of code?
With the end goal of: having the tags ordered by frequency of use, with the exception of “recent” which I want to ALWAYS go last.

My PHP knowledge isn’t good enough. I’ve tried a bunch of things, all break the page.

Got it, thanks!