Filter children and grandChildren with select fields

Hello,

I’m trying to adapt this solution to my needs.

My structure looks like this:

All projects

   - Projects A
         -Subproject 1 
         -Subproject 2  
   - Projects B
         -Subproject 1 
         -Subproject 2  
   - Projects C
         -Subproject 1 
         -Subproject 2  

In the All Projects template I would like to filter projects and sub-projects.

template: allprojects.php

 <form id="filters" method="post" style="margin-top:20em;">
<select name="child" onchange="this.form.submit()">
  <option selected value="">Select a child</option>   
  <?php foreach($child as $item): ?>     
  <?php if($item == "") continue ?>     
  <option<?php e(isset($data['child']) && $data['child'] == $item, ' selected') ?> value="<?php echo $item ?>"><?php 
   echo $item ?></option>
  <?php endforeach ?>
</select>
<select name="grandchild" onchange="this.form.submit()">
  <option selected value="">Select a grandchild</option>
  <?php foreach($grandchild as $item): ?>
  <?php if ($item == "") continue; ?>
  <option<?php e(isset($data['grandchild']) && $data['grandchild'] == $item, ' selected') ?> value="<?php echo $item 
    ?>"><?php echo $item ?></option>
  <?php endforeach ?>
 </select>
</form>


<?php if($allprojects->count()): ?>
<ul>
<?php foreach($allprojects->children()->limit(5) as $project): ?>
<li>      
  <h3><?= $project->parent()->title()->html() ?> <?= $project->title()->html() ?></h3>
  <a href="<?= $project->url() ?>"><?= $project->parent()->title()->html() ?> - <?= $project->title()->html() ?></a>
</li>
<?php endforeach ?>
</ul>
<?php else: ?>
<p>No result :( </p>
<?php endif ?>

and the controller: allprojects.php:

<?php

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

$child = $page->children()->pluck('title', null, true);
$grandchild = $page->grandChildren()->pluck('title', null, true);  
$keys = array('child', 'grandchild');

  // return all children if nothing is selected
   //  $allprojects = $page->children()->visible(); 

 // if there is a post request, filter the projects collection
  if(r::is('POST') && $data = get()) {
$allprojects = $page->children()->visible()->filter(function($child) use($keys, $data) {

  // loop through the post request
  foreach($data as $key => $value) {

    // only act if the value is not empty and the key is valid
    if($value && in_array($key, $keys)) {

      // return false if the child page's category and value don't match
      if(!$match = $child->$key() == $value) {
        return false;
      }
    }
  }

  // otherwise return the child page
  return $child;

});
}
return compact('allprojects', 'child', 'grandchild', 'data');
};

Select field shows corrects datas, but result gives No results :frowning:

Thanks for help.

1 Like

Two problems: Your keys are child and grandchild, but you want to filter by title. What you end up with, though, if you replace $key in this bit:

$child->$key() == $value

is $page->child() or $page->grandChild(), so that doesn’t make sense. Also, you should probably use $page->index()instead of $page->children()` to filter the complete collection.

What is your use case for using these two select fields, anyway? What if the user picks a combination that doesn’t match?

The web site is about boats and some services/cruises.

Their are 4 boats, wich propose 4 ou 5 same or différents cruises. Client want users to be able to select or a boat first and then a cruise, or a cruise first and then the boat :frowning:

Maybe organizing pages and folders in an other way would be better ?

I began first with the classic projects/project structure, each for cruises and boats, but it makes website not very clear and easy to navigate.

What do you think ?

I see. No, I think you can work with the current structure, but I think you then would have to filter your select options as well.

For example, if the user selects Boat 1, then the second select should be automatically reflect that and only show the options that are available for that boat. And the other way round, of course, if the user selects a cruise first.

hmm it seems hard for me. But if you have a starting point for me I will try this way.

btw, I’m not really afraid about the fact that a combination does not match nothing. But you are certainly right, I will check it as soon as I will make it work…

I think if you think it through logically (like: what should happen when user does x etc.), then you can code it. Try to formulate what you want to do in plain language first, then code that. Always check your results with some dumps.

I just thought about this again this morning, and think that you current structure is probably hard to maintain, because you end up with a duplicate content and would have to make changes in each of those pages (ok, could be automated with a hook, but nonetheless). Unless the cruises have to be modified for each boat specifically. If not, I’d reference the cruises for each boat with a checkboxes, multi-select or similar field that allows multiple options.

The filtering would have to work differently but shouldn’t be any more complicated than currently.

And as regards the navigation, that doesn’t necessarily have to follow the internal folder structure. Routing to the rescue…

Happy to know you think about my project in the morning ^^

Yes, that’s what I did in the other way, I created a page with all cruises and checkboxes to fetch correspondants boats that are in an other page.
It quite works… select from cruises works fine, it show all cruises and correspondant boats, but select from boats does not return cruises if more than one boat is selected :frowning:

I go on my invistations…

Me again…

here is the new structure:

Cruises:

projects & project templates

  • all cruises
    • cruise 1
    • Cruise 2
    • (…)

Boats:
Boats & Boat templates

  • all boats
    • boats 1
    • boats 2
    • (…)

blueprint cruise:

  boat:
    label: Boat
    type: checkboxes
    options: query
    query:
      page: boats
      fetch: children

  cruiseType:
    label: Cruise Type
    type: select
    options:
      Type 1: Type 1
      Type 2: Type 2
      Type 3: Type 3
      Type 4: Type 4

projects controller:

<?php

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

  $boat = $page->children()->pluck('boat', ',', true);
  $cruiseType = $page->children()->pluck('cruiseType', null, true);  
  $keys = array('boat', 'cruiseType');

  // return all children if nothing is selected
  $projects = $page->children()->visible(); 

  // if there is a post request, filter the projects collection
  if(r::is('POST') && $data = get()) {
    $projects = $page->children()->visible()->filter(function($child) use($keys, $data) {

      // loop through the post request
      foreach($data as $key => $value) {

        // only act if the value is not empty and the key is valid
        if($value && in_array($key, $keys)) {

          // return false if the child page's category and value don't match
          if(!$match = $child->$key() == $value) {
            return false;
          }
        }
      }

      // otherwise return the child page
      return $child;

    });
  }
  return compact('projects', 'boat', 'cruiseType', 'data');
};

projects template:

<form id="filters" method="post" style="margin-top:10em;">
  <!-- give the select the name of the category to filter by -->

  <select name="boat" onchange="this.form.submit()">
    <option selected value="">Select a boat</option>   
    <?php foreach($boat as $item): ?>     
    <?php if($item == "") continue ?>     
    <option<?php e(isset($data['boat']) && $data['boat'] == $item, ' selected') ?> value="<?php echo $item ?>"><?php echo $item ?></option>
    <?php endforeach ?>
  </select>

  <select name="cruiseType" onchange="this.form.submit()">
    <option selected value="">Select a cruiseType</option>
    <?php foreach($cruiseType as $item): ?>
    <?php if ($item == "") continue; ?>
    <option<?php e(isset($data['cruiseType']) && $data['cruiseType'] == $item, ' selected') ?> value="<?php echo $item ?>"><?php echo $item ?></option>
    <?php endforeach ?>
  </select>

</form>


<?php if($projects->count()): ?>
<ul>
  <?php foreach($projects as $project): ?>
  <li>     
    <strong><?= $project->title()->html() ?></strong><br/>
    <?php foreach($project->boat()->split() as $item): ?>
    <span><?= $item ?> /</span>    
    <?php endforeach ?><br>
    <a href="<?= $project->url() ?>"><i>More infos</i></a>
    <br/><br/>
  </li>
  <?php endforeach ?>
</ul>
<?php else: ?>
<p>Il n' aucun résultat :( </p>
<?php endif ?>

This gives me a good result when cruises are selected. Boats are correctly shown etc.
It also gives a good result with boats, if only one boats is selected. If more than one boat is selected in a cruise, result is empty for the boat.

If in controller I use
$boat = $page->children()->pluck('boat', null, false); instead of
$boat = $page->children()->pluck('boat', ',', true);

results are corrects, when boats are selected, but all are show in the select field :frowning:

The problem is this line.

It works if there’s only one boat in the field. But it can’t work if there are more boats. You’d have to split the field value up and then check if the value you get from. the form is in the array instead of a string comparison.

if(! $match = in_array($value, $child->{$key}()->split(',')))

You just saved my week-end :wink:
Again a big thanks, I would’not be able to write it alone even if now I read it, it is logic.