Jump to content

Simple categories filtering


onjegolders
 Share

Recommended Posts

I've been reading up on a few posts on categories but I'm struggling to get them working.

I saw a video from PW1

and the example seems nice and simple:

Output the categories, click one and the list of entries gets filtered.

I tried to replicate that but no joy. I can't seem to understand how Ryan can output $page->categories from his page when really the categories are surely part of children()?

Is it really possible to filter categories of events or news this simply? I've seen other examples in the forums which look much more complex (urlSegments and the like).

I currently have my category page set up as a child of home, and categories as its children.

Before seeing that video, I had managed to output the categories like this:

<div id="events_index_side">

<?php if ($pages->find("template=category")) { ?>

<div class="events_index_side_box">

<h5 class="page_header">By category</h5>

<ul>
<?php $categories = $pages->find("template=category");
foreach ($categories as $category) { ?>

<li><a href="<?php echo $page->url . $category->name; ?>"><?php echo $category->title; ?></a></li>

<?php }
?>
</ul>

</div><!-- /.events_index_side_box -->

<?php } ?>

but using this method, I'm not quite sure how I can get the page to reload with just those categorised entries?

Would appreciate if anyone could give me a shove in the right direction. Thanks.

Link to comment
Share on other sites

if ($pages->find("template=category"))

The above will never resolve to false, so doesn't do anything. That's because find() returns a PageArray(). Think of it like a bottle that might have beer in it. An empty beer bottle is still a beer bottle. So you have to check if there's anything in it:

$categories = $pages->find("template=category"); 
if(count($categories)) {
   // there are categories
   foreach($categories) {
       // output category
   }
}

I suggest using a separate 'category' template to handle the display of a given category (rather than trying to do it all in your 'categories' template). Here's the code you might do it with in your category template. This assumes the categorized entries have a field called "categories"

$entries = $pages->find("categories=$page"); 
if(count($entries)) {
   echo "<ul>";
   foreach($entries as $entry) {
       echo "<li><a href='{$entry->url}'>{$entry->title}</a></li>";
   } 
   echo "</ul>";
} else {
   echo "<p>No entries in this category.</p>";
}
  • Like 1
Link to comment
Share on other sites

The above will never resolve to false, so doesn't do anything. That's because find() returns a PageArray(). Think of it like a bottle that might have beer in it. An empty beer bottle is still a beer bottle. So you have to check if there's anything in it:

$categories = $pages->find("template=category");
if(count($categories)) {
// there are categories
foreach($categories) {
	// output category
}
}

I suggest using a separate 'category' template to handle the display of a given category (rather than trying to do it all in your 'categories' template). Here's the code you might do it with in your category template. This assumes the categorized entries have a field called "categories"

$entries = $pages->find("categories=$page");
if(count($entries)) {
echo "<ul>";
foreach($entries as $entry) {
	echo "<li><a href='{$entry->url}'>{$entry->title}</a></li>";
}
echo "</ul>";
} else {
echo "<p>No entries in this category.</p>";
}

Thanks Ryan, that makes it clearer, though I'm still unsure what I should be doing on my events "index" page.

On the left I display all the events and in the sidebar I ideally wanted to list the categories and then on click, filter the entries (I suppose at events/category_name).

Sorry for being slow, just not quite sure how to link this up.

On your video example, you only seemed to have a small bit of code in one template which did all the magic!

Link to comment
Share on other sites

On the left I display all the events and in the sidebar I ideally wanted to list the categories and then on click, filter the entries (I suppose at events/category_name).

If you want your events index to list all the categories, you could just do thi:

$categories = $pages->find("template=category, sort=name");

foreach($categories as $c) echo "<p><a href='{$c->url}'>{$c->title}</a></p>";

If you want to use URL segments to filter the listing of events by category, you could use URL segments with a URL structure like /events/category/cheese/. Then your events index could look for it like this:

if($input->urlSegment1 == 'category' && $input->urlSegment2) {
   $name = $sanitizer->pageName($input->urlSegment2);
   $category = $pages->get("template=category, name=$name"); 
   if($category->id) {
       $events = $pages->find("template=event, categories=$category"); 
       foreach($events as $event) echo "<p><a href='{$event->url}'>{$event->title}</a></p>"; 
   }
}
  • Like 2
Link to comment
Share on other sites

Thanks, have spent this morning trying to get this to work but I'm just not managing it unfortunately.

I've gone down the include route so that now, my event index page looks like this:

<?php include("./header.inc"); ?>

<?php if ($input->urlSegment1 != "") {
include ("./categories.inc");
} else { ?>

<div id="events_index_wrap" class="bg_gradient">

<div id="events_index_main">

<h3 class="page_header">Upcoming Events</h3>

<?php
$entries = $pages->find("template=event, sort=date");
$count = 0;

foreach ($entries as $event) {
$count++;
$class = "event_box";
if (0 == $count % 2) { $class .= " event_box_second"; }
?>

<div class="<?php echo $class; ?>">

<div class="event_box_text">
<h2><a href="<?php echo $event->url; ?>"><?php echo $event->title; ?></a></h2>
<h5><?php echo $event->date; ?></h5>
</div><!-- /.event_box_text -->
<div class="clear"></div>

<?php if ($event->images) {
$img = $event->images->first()->size(220,165);
?>
<a href="<?php echo $event->url; ?>"><img src="<?php echo $img->url; ?>" width="<?php echo $img->width; ?>" height="<?php echo $img->height; ?>" alt="<?php echo $entry->title; ?>" class="med_frame" /></a>
<?php } ?>

</div><!-- /.event_box -->

<?php }
?>

</div><!-- /#events_index_main -->

<div id="events_index_side">


<div class="events_index_side_box">

<?php if (count($pages->find("template=category"))) {
$category = $pages->find("template=category");
?>
<ul>
<?php foreach ($category as $cat) { ?>
<li><a href="<?php echo $page->url . $cat->name; ?>"><?php echo $cat->name; ?></a></li>
<?php } ?>
<?php } ?>

</div><!-- /.events_index_side_box -->

</div><!-- /#events_index_side -->

<div class="clear"></div>

</div><!-- /#events_index_wrap -->

<?php } ?>

<?php include("./footer.inc"); ?>

So on the category list, I tack on the category name to the url and if there's a URL segment it includes the categories include, which looks like:

<div id="events_index_wrap" class="bg_gradient">

<div id="events_index_main">

<h3 class="page_header">Upcoming Events - <?php echo $input->urlSegment1; ?></h3>

<?php
$name = $sanitizer->pageName($input->urlSegment1);
echo "<h1>$name</h1>";

$entries = $pages->find("template=event, categories=$name, sort=date");
$count = 0;

foreach ($entries as $event) {
$count++;
$class = "event_box";
if (0 == $count % 2) { $class .= " event_box_second"; }
?>

<div class="<?php echo $class; ?>">

<div class="event_box_text">
<h2><a href="<?php echo $event->url; ?>"><?php echo $event->title; ?></a></h2>
<h5><?php echo $event->date; ?></h5>

</div><!-- /.event_box_text -->
<div class="clear"></div>

<?php if ($event->images) {
$img = $event->images->first()->size(220,165);
?>
<a href="<?php echo $event->url; ?>"><img src="<?php echo $img->url; ?>" width="<?php echo $img->width; ?>" height="<?php echo $img->height; ?>" alt="<?php echo $entry->title; ?>" class="med_frame" /></a>
<?php } ?>

<?php $categories = $event->categories; foreach ($categories as $cg) { ?>
<h6><?php echo $cg->name; ?></h6>
<?php } ?>

</div><!-- /.event_box -->

<?php }
?>

</div><!-- /#events_index_main -->

<div id="events_index_side">


<div class="events_index_side_box">

<?php if (count($pages->find("template=category"))) {
$category = $pages->find("template=category");
?>
<ul>
<?php foreach ($category as $cat) { ?>
<li><a href="<?php echo $page->url . $cat->name; ?>"><?php echo $cat->name; ?></a></li>
<?php } ?>
<?php } ?>

</div><!-- /.events_index_side_box -->

</div><!-- /#events_index_side -->

<div class="clear"></div>

</div><!-- /#events_index_wrap -->

My issue is that I'm pretty sure that the category name is matching the urlSegment but when I choose a category the list comes up empty. I've thought of possible reasons (lower/upper case categories?), I've also tried to pass a variable to the current category but not to sure how to get that working before looping through the entries...

If anyone has any idea, that would be much appreciated, thanks.

Link to comment
Share on other sites

This is one of those moments when I question what I'm doing with my life! Spend 7 hours today trying to figure out this one problem (sometimes I really think I would pay for commercial support ;))

Seems I should have been setting a $category variable and checking for name=$name (so essentially was missing a step) I must have spent about a million hours trying to figure that out...

One thing I can't seem to figure out though is how to only show links to categories that have been used once or more. Currently it is showing all categories, even if they're empty. The tiny logical part of my brain is saying that I should be referencing events and not categories

Something like:

$cats = $pages->find("template=event")->categories;

But can't seem to figure it out

Link to comment
Share on other sites

If you are displaying categories and want to exclude any that don't have any events attached, then you could do this:

$categories = $pages->find("template=category"); 
foreach($categories as $category) {
   $numEvents = $pages->count("template=event, categories=$category"); 
   if($numEvents > 0) {
       // display category
   } else {
       // don't display category
   }
}
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Similar Content

    • By Ksenia
      Hello! 
      I am a new user of PW and I really need some guidence. :---)
      I use PW to design a front-end for the researchers I intern with. They logged all their database with PW (but before only used admin backend).
      I use Page Reference field mostly to create filters. I have followed this tutorial (very grateful for it!) and it does work as you can see at the screen recording of the test-website.

       
      But I cannot figure out how to change the inner workings of it to combine the tags user presses like Russia&&esoterism to see only the items that have both of those tags. 
      What is even more complex: I will have multiple filters working in the same manner (page reference field) and I need all of them to also be using && logic. 

      With possibility of clearing the history and showing all documents again. 
      My tree structure is sth like this:
      -Documents 
                 -document
                 -document
                 -document
      -Tags 
                 -tag
                 -tag
                 -tag
      -Types
                 -type
                 -type
                 -type
      etc
      This is my code right now.
      <div class="row"> <ul class="nav"> <h3> Filter by tag</h3> <li class="nav-item"> <a class="nav-link" href="/Documents/">Show all</a> </li> <?php $docTags = $pages->get("/Tags/")->children; foreach ($docTags as $docTag): ?> <li class="nav-item"> <a class="nav-link" href='<?php echo "/documents/{$docTag->name}/"; ?>' ?> <?php echo $docTag->title; ?> </a> </li> <?php endforeach; ?> </ul> </div> <div class="row py-5"> <?php // only 1 URL segment should be allowed if ($input->urlSegment2) { throw new Wire404Exception(); } // create a string that will be our selector $selector = "template=Document"; // get URL segment 1 $segment1 = $input->urlSegment1; // if there is a URL segment 1 if ($segment1) { // get cat type page $catType = $pages->findOne("parent=/Tags/, name=$segment1"); // if cat type page exists if ($catType->id) { // add this to the selector $selector .= ", tags=$segment1"; } else { // invalid URL segment 1 throw new Wire404Exception(); } } // find the pages based on our selector $docPages = $pages->find($selector); foreach ( $docPages as $docPage): ?> <div class="col-md-4 pb-3"> <div class="card"> <?php // if the page object has a featured image if ( $docPage->featuredImage): // https://processwire.com/api/fieldtypes/images/ // set some default image options $options = array('quality' => 80, 'cropping' => 'center'); // create a new image on the fly 800px wide $img = $docPage->featuredImage->width(400, $options); // get the url to the image $imgUrl = $img->url; // get the description field $imgDesc = $img->description; ?> <a href="<?php echo $docPage->url; ?>"> <img src="<?php echo $imgUrl; ?>" alt="<?php echo $imgDesc; ?>" class="img-fluid card-img-top" /> </a> <?php endif; ?> <div class="card-body"> <h4 class="card-title"> <a href="<?php echo $docPage->url; ?>"><?php echo $docPage->title; ?></a> </h4> </div> </div> </div> <?php endforeach; ?> </div> I would really appreciate any links to resources/tutorials or even general explanation about which direction to take! 
      Thanks in advance! This forum has already helped me a lot!
      Best,
      Ksenia
    • By MarkE
      I have a selector:
      $pageArray = $pages->find("template=basic-page, length.magnitude>0, include=all"); I would like an array of each of the 'length' fields. However, the field is an object and $pageArray->each('length') returns the formatted values; I want the unformatted objects.
      I thought perhaps I could use a callable like:
      $pageArray = $pages->find("template=basic-page, length.magnitude>0, include=all"); $lengths = $pageArray->each(function($p) { $length = $p->getUnformatted('length'); return $length; }); but it seems that only strings can be returned and those are concatenated.
      So it looks like I have to use a foreach() loop - which works, but I was expecting some neater shorthand. Any thoughts?
    • By Webrocker
      Hi,
      I updated to the currently latest master (3.0.184) and now a lister (listerPro 1.1.3) preset shows the info cited in the title.
      According to this closed issue this is supposed to work <del>in 3.0.184</del>
      https://github.com/processwire/processwire-issues/issues/1428

      or is this a lister(pro) issue?

    • By sebr
      Hi
      I'm using Processwire 3.0.180.
      When I edit a page in admin, Tracy Debugger count between 50 and 100 pages loaded, depending on the templates. All pages open in less than a second.
      But when I edited pages from a certain template, this pages opened in more than 40 seconds and Tracy Debugger count more than 14000 pages loaded.
      When I look in the pages loaded list, I find a very large amount of lines that look like this:
      1247 /sc_admin/repeaters/for-field-182/for-page-1246/ Page 1248 /sc_admin/repeaters/for-field-158/for-page-1246/ Page 1249 /sc_admin/repeaters/for-field-159/for-page-1246/ Page 1250 /sc_admin/repeaters/for-field-169/for-page-1246/ Page 1251 /sc_admin/repeaters/for-field-170/for-page-1246/ Page 1252 /sc_admin/repeaters/for-field-180/for-page-1246/ Page 1254 /sc_admin/repeaters/for-field-137/for-page-1253/ Page 1256 /sc_admin/repeaters/for-field-182/for-page-1255/ Page 1258 /sc_admin/repeaters/for-field-170/for-page-1195/ Page 1261 /sc_admin/repeaters/for-field-182/for-page-1132/ Page 1262 /sc_admin/repeaters/for-field-159/for-page-1132/ Page 1263 /sc_admin/repeaters/for-field-169/for-page-1132/ Page 1264 /sc_admin/repeaters/for-field-170/for-page-1132/ Page 1265 /sc_admin/repeaters/for-field-180/for-page-1132/ Page Or other like :
      10222 /sc_admin/repeaters/for-field-138/for-page-5710/1623420295-8678-1/ RepeaterMatrixPage 10232 /sc_admin/repeaters/for-field-138/for-page-5710/1623420316-7346-1/ RepeaterMatrixPage 10242 /sc_admin/repeaters/for-field-138/for-page-5708/1623482402-7605-1/ RepeaterMatrixPage 10256 /sc_admin/repeaters/for-field-138/for-page-10254/1624257169-0968-1/ RepeaterMatrixPage 10266 /sc_admin/repeaters/for-field-138/for-page-10254/1624257191-2937-1/ RepeaterMatrixPage 10278 /sc_admin/repeaters/for-field-138/for-page-10254/1624257284-3093-1/ RepeaterMatrixPage 10289 /sc_admin/repeaters/for-field-138/for-page-10254/1624258816-4716-1/ RepeaterMatrixPage 10302 /sc_admin/repeaters/for-field-138/for-page-10254/1624259001-0062-1/ RepeaterMatrixPage 10312 /sc_admin/repeaters/for-field-138/for-page-10254/1624259056-5808-1/ RepeaterMatrixPage 10326 /sc_admin/repeaters/for-field-138/for-page-10254/1624259204-4337-1/ RepeaterMatrixPage 10336 /sc_admin/repeaters/for-field-138/for-page-10254/1624259240-545-1/ RepeaterMatrixPage 10347 /sc_admin/repeaters/for-field-138/for-page-10254/1624259280-7777-1/ RepeaterMatrixPage 10357 /sc_admin/repeaters/for-field-138/for-page-10254/1624259299-317-1/ RepeaterMatrixPage 10369 /sc_admin/repeaters/for-field-138/for-page-6006/1624259684-8705-1/ RepeaterMatrixPage 10381 /sc_admin/repeaters/for-field-138/for-page-10379/1624259765-4187-1/ RepeaterMatrixPage More than 10000...
      This template doesn't contain repeaterMatrix.
      How can I understand what's happened ?
      Thanks for your help
    • By Guy Incognito
      We have a client's Processwire site where the Pageimage::size() method has stopped working. It's creating small white PNGs in the assets folder instead of the resized image as usual.
      Their hosting was recently upgraded and switched to PHP 7.4 and I'm wondering if a they've removed a PHP package or changed a server config to cause this.
      Does anyone know if the image resizing functionality requires specific PHP modules I need to check for?
      I've checked the folder permissions on the server - this all looks fine.
      TIA,
      J
×
×
  • Create New...