Jump to content

Creating filtering system with AND logic instead of OR logic


Ksenia
 Share

Recommended Posts

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.

1019123271_ScreenRecording2021-10-27at19_55_30.gif.6af89f1ab6cd022f9a2cde799f0d3814.gif

 

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. 

IMG_1771.thumb.png.1d650b27cccaa61e9771832e9b1b5eef.png

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

  • Like 1
Link to comment
Share on other sites

Welcome to the PW forums!

A URL segment is suitable for a single tag but not for multiple tags. The simplest way to submit multiple tags is via checkboxes in a form. If you want a different appearance (e.g. something that looks more like buttons instead of checkboxes) then once you have the basic form working you could hide the checkboxes with CSS and show some buttons that check/uncheck the hidden checkboxes using JavaScript.

This is a generic example but you can adapt it to your scenario...

At the top of your template file:

// Get your tag pages
$tags = $pages->find("template=tag");

// Get the array of tags that the user has submitted via the form
$selected_tags_raw = $sanitizer->array($input->get('tags'));
// Sanitize the tags against the valid tags
$selected_tags = $sanitizer->options($selected_tags_raw, $tags->explode('name'));

// Build up a selector string
$selector = "template=document";
// Add the selected tags to the selector string - these will use AND logic
foreach($selected_tags as $selected_tag) $selector .= ", tags=$selected_tag";
// Find the matching pages and later output them wherever you need
$matches = $pages->find($selector);

And later in the template file markup where you want the form to appear:

<form action="./" method="get">
	<?php foreach($tags as $tag): ?>
		<?php $checked = in_array($tag->name, $selected_tags) ? ' checked' : '' ?>
		<label><input type="checkbox" name="tags[]" value="<?= $tag->name ?>"<?= $checked ?>> <?= $tag->title ?></label><br>
	<?php endforeach; ?>
	<button>Submit</button>
</form>

 

Link to comment
Share on other sites

11 hours ago, Robin S said:

Welcome to the PW forums!

A URL segment is suitable for a single tag but not for multiple tags. The simplest way to submit multiple tags is via checkboxes in a form. If you want a different appearance (e.g. something that looks more like buttons instead of checkboxes) then once you have the basic form working you could hide the checkboxes with CSS and show some buttons that check/uncheck the hidden checkboxes using JavaScript.

This is a generic example but you can adapt it to your scenario...

At the top of your template file:

// Get your tag pages
$tags = $pages->find("template=tag");

// Get the array of tags that the user has submitted via the form
$selected_tags_raw = $sanitizer->array($input->get('tags'));
// Sanitize the tags against the valid tags
$selected_tags = $sanitizer->options($selected_tags_raw, $tags->explode('name'));

// Build up a selector string
$selector = "template=document";
// Add the selected tags to the selector string - these will use AND logic
foreach($selected_tags as $selected_tag) $selector .= ", tags=$selected_tag";
// Find the matching pages and later output them wherever you need
$matches = $pages->find($selector);

And later in the template file markup where you want the form to appear:

<form action="./" method="get">
	<?php foreach($tags as $tag): ?>
		<?php $checked = in_array($tag->name, $selected_tags) ? ' checked' : '' ?>
		<label><input type="checkbox" name="tags[]" value="<?= $tag->name ?>"<?= $checked ?>> <?= $tag->title ?></label><br>
	<?php endforeach; ?>
	<button>Submit</button>
</form>

 

Thank you so much! You saved me ahah

I implemented it and it works well! I also made it so it includes different Page Reference fields!

296140878_ScreenRecording2021-10-28at15_00_51.gif.5cc931b1f52bcf684c82fcb98e5e0d22.gif

 

But another challenge for me is to use the same logic but for the Repeater fields. Each Repeater includes two Page reference fields. And I can't seem to even first print out the info I need from them (to build a selector). The structure I have looks like this:

1678502749_Screenshot2021-10-28at14_49_04.thumb.png.f5f28604af21a9a5583b5daf7387990c.png

And I tried to use the code from the documentation, but it only works with simpler fields like text. Otherwise gives the id of the right page, but refuses to access the name/title of it. 

  <h1>Relations</h1>
  <?php
  foreach($page->relation_document_technique as $relation_document_technique) {
      echo "Other name: {$relation_document_technique->other_name}<p>"; //gives me text
      echo "Technique:{$relation_document_technique->relation_document_technique_select}<p>"; //gives id of the right page
      echo "Type of relation: {$relation_document_technique->relation_document_technique_type->title} </p>"; //gives nothing
  }
?>

1206087687_Screenshot2021-10-28at15_11_56.png.0650af582d4c4371fa2abdcf5ba1f11e.png

Maybe there is a simple mistake I'm making, but I would be super grateful if you see the solution for filtering based on Repeater structure!

Have a neat day!

Kseniia

  • Like 1
Link to comment
Share on other sites

2 hours ago, Ksenia said:

Thank you so much! You saved me ahah

I implemented it and it works well! I also made it so it includes different Page Reference fields!

296140878_ScreenRecording2021-10-28at15_00_51.gif.5cc931b1f52bcf684c82fcb98e5e0d22.gif

 

But another challenge for me is to use the same logic but for the Repeater fields. Each Repeater includes two Page reference fields. And I can't seem to even first print out the info I need from them (to build a selector). The structure I have looks like this:

1678502749_Screenshot2021-10-28at14_49_04.thumb.png.f5f28604af21a9a5583b5daf7387990c.png

And I tried to use the code from the documentation, but it only works with simpler fields like text. Otherwise gives the id of the right page, but refuses to access the name/title of it. 

  <h1>Relations</h1>
  <?php
  foreach($page->relation_document_technique as $relation_document_technique) {
      echo "Other name: {$relation_document_technique->other_name}<p>"; //gives me text
      echo "Technique:{$relation_document_technique->relation_document_technique_select}<p>"; //gives id of the right page
      echo "Type of relation: {$relation_document_technique->relation_document_technique_type->title} </p>"; //gives nothing
  }
?>

1206087687_Screenshot2021-10-28at15_11_56.png.0650af582d4c4371fa2abdcf5ba1f11e.png

Maybe there is a simple mistake I'm making, but I would be super grateful if you see the solution for filtering based on Repeater structure!

Have a neat day!

Kseniia

Update:

I have figured out that those were arrays of pages, so now I can get the values I need with this:

<h1>Relations</h1>
<?php
foreach($page->relation_document_technique as $relation_document_technique) {
 $arraySelect = $relation_document_technique->relation_document_technique_select;
 $arrayType = $relation_document_technique->relation_document_technique_type;
   echo "<p>Technique:</p>";
    foreach($arraySelect as $item) {
        echo " <li><a href='$item->url'>$item->title</a></li>";
    }
    echo "<p>Type of relation:</p>";
    foreach($arrayType as $item) {
        echo " <li>$item->title</li>";
    }
}
?>

But still am lost at how to turn this into a filter checkbox!

Link to comment
Share on other sites

1 hour ago, Ksenia said:

Update:

I have figured out that those were arrays of pages, so now I can get the values I need with this:

<h1>Relations</h1>
<?php
foreach($page->relation_document_technique as $relation_document_technique) {
 $arraySelect = $relation_document_technique->relation_document_technique_select;
 $arrayType = $relation_document_technique->relation_document_technique_type;
   echo "<p>Technique:</p>";
    foreach($arraySelect as $item) {
        echo " <li><a href='$item->url'>$item->title</a></li>";
    }
    echo "<p>Type of relation:</p>";
    foreach($arrayType as $item) {
        echo " <li>$item->title</li>";
    }
}
?>

But still am lost at how to turn this into a filter checkbox!

Ok sorry for hectic updating, I did figure it out! Turned out to be quite simple!

//Techniques
$techniques = $pages->find("template=Technique");
$selected_techniques_raw = $sanitizer->array($input->get('techniques'));
$selected_techniques = $sanitizer->options($selected_techniques_raw, $techniques->explode('name'));
foreach($selected_techniques as  $selected_technique) $selector .= ", relation_document_technique.relation_document_technique_select= $selected_technique";

Thanks a lot for your help!

  • Like 1
Link to comment
Share on other sites

On 10/28/2021 at 12:35 AM, Robin S said:

Welcome to the PW forums!

A URL segment is suitable for a single tag but not for multiple tags. The simplest way to submit multiple tags is via checkboxes in a form. If you want a different appearance (e.g. something that looks more like buttons instead of checkboxes) then once you have the basic form working you could hide the checkboxes with CSS and show some buttons that check/uncheck the hidden checkboxes using JavaScript.

This is a generic example but you can adapt it to your scenario...

At the top of your template file:

// Get your tag pages
$tags = $pages->find("template=tag");

// Get the array of tags that the user has submitted via the form
$selected_tags_raw = $sanitizer->array($input->get('tags'));
// Sanitize the tags against the valid tags
$selected_tags = $sanitizer->options($selected_tags_raw, $tags->explode('name'));

// Build up a selector string
$selector = "template=document";
// Add the selected tags to the selector string - these will use AND logic
foreach($selected_tags as $selected_tag) $selector .= ", tags=$selected_tag";
// Find the matching pages and later output them wherever you need
$matches = $pages->find($selector);

And later in the template file markup where you want the form to appear:

<form action="./" method="get">
	<?php foreach($tags as $tag): ?>
		<?php $checked = in_array($tag->name, $selected_tags) ? ' checked' : '' ?>
		<label><input type="checkbox" name="tags[]" value="<?= $tag->name ?>"<?= $checked ?>> <?= $tag->title ?></label><br>
	<?php endforeach; ?>
	<button>Submit</button>
</form>

 

Hello Robin! 

You really helped me out the last time in this topic and I was wondering if you could help me figure out a much more complex filter using the logic you suggested?

So, basically, what I need to do is: 

- filter one type of pages (Organisations) based on a field in another page type (Individuals) which has connection to (Techniques) and (Organisaions)

How it looks:

Individual has page reference field (a repeater, but it doesn't matter in the context, hence I only need one field in this repeater) called "Techniques" and a page reference field "Organisation". My filter is by Technique.

25356009_Screenshot2021-11-02at23_22_43.thumb.png.938fa9ef57e90190686884f57950ff3b.png

What I am doing is:

1)  filtering out all individuals who have selected technique in their technique_relation field;

2) looping through them to get the Organisations they are connected to in their organisation_relation field;

3) (where I fail) trying to match titles of Organisations from action (2) with titles of the list of Organisations I am filtering to make them appear in my matches. My issue is that I overwrite the variable with titles and not loop through it, so it works, but only for the last title. 

296225523_Screenshot2021-11-02at23_28_42.thumb.png.d414dbe42e13d36d0a9aacbe128db15b.png

My code: 

// Build up a selector string
$selector_org = "template=Organisation";
//making global var to not get error if there are no matches
$org_name = $page->title;
//create second selector
$selector_ind = "template=Individual";
// get my Techniques
$techniques = $pages->find("template=Technique");
//Get the array of techniques that the user has submitted via the form
$selected_techniques_raw = $sanitizer->array($input->get('techniques_ind_org'));
// Sanitize the techniques against the valid tags
$selected_techniques = $sanitizer->options($selected_techniques_raw, $techniques->explode('name'));
// Add the selected techniques to the selector string 
foreach ($selected_techniques as $selected_technique) {
    //I create another selector to pick the pages from Individuals who have selected technique picked in the field
    $selector_ind .= ", relation_individual_technique.relation_individual_technique_select= $selected_technique";
    //find matching Individuals
    $matches_ind = $pages->find($selector_ind);
    //start looping through them
    foreach ($matches_ind as $match_ind) {
        //log Individuals
        echo " <li><a href='$match_ind->url'>$match_ind->title</a></li>";
        //for each Individual I get the organisations they are related to
        // (because i need to end up with the list of Organisations in the end)
        foreach ($match_ind->relation_individual_organisation as $relation_individual_organisation) {
            //get array of selected Organisaions
            $array_orgs_ind = $relation_individual_organisation->relation_individual_organisation_select;
            //loop through hem to get the names
            foreach ($array_orgs_ind as $item_select) {
                //log names
                echo " <a href='$item_select->url'>$item_select->title</a>";
                //try to create a variable for the selector to only pick pages with same titles from my list
                $org_name = $item_select->title;
            }
        }
    }
    //try to use the selector, but ofc var re-writes itself and works only for the last name
    $selector_org .= ", title= $org_name";
}

 

If you can see something obvious I would really appreciate help!

I am kind of stuck on that now...

 

Best,

Ksenia

Link to comment
Share on other sites

@Ksenia, I read your post a few times but I'm not sure I understand what you're trying to do.

A couple of things that might help...

1. The space after the equals sign in the selector here is wrong:

$selector_ind .= ", relation_individual_technique.relation_individual_technique_select= $selected_technique";

It should be:

$selector_ind .= ", relation_individual_technique.relation_individual_technique_select=$selected_technique";

2. If you're trying to get a PageArray of organisations, and those organisations are referenced in a Page Reference field in individual pages then you can do something like this:

// A new empty PageArray that will hold the organisations selected for all the individuals
$organisations = new PageArray();
// The $individuals PageArray has been created elsewhere in your code
foreach($individuals as $individual) {
	// Add to $organisations from the "organisations" Page Reference field
	$organisations->add($individual->organisations);
}
// Now do something with $organisations

 

Link to comment
Share on other sites

10 hours ago, Robin S said:

@Ksenia, I read your post a few times but I'm not sure I understand what you're trying to do.

A couple of things that might help...

1. The space after the equals sign in the selector here is wrong:

$selector_ind .= ", relation_individual_technique.relation_individual_technique_select= $selected_technique";

It should be:

$selector_ind .= ", relation_individual_technique.relation_individual_technique_select=$selected_technique";

2. If you're trying to get a PageArray of organisations, and those organisations are referenced in a Page Reference field in individual pages then you can do something like this:

// A new empty PageArray that will hold the organisations selected for all the individuals
$organisations = new PageArray();
// The $individuals PageArray has been created elsewhere in your code
foreach($individuals as $individual) {
	// Add to $organisations from the "organisations" Page Reference field
	$organisations->add($individual->organisations);
}
// Now do something with $organisations

 

Hello! Thanks for the response!  As to what I am trying to do: it is convoluted because of the way pages are logged. But I tried to draw it, maybe it helps:

IMG_1881.thumb.png.c6f4d68482c9b04cda4875564aa537a5.png

I tried to implement your approach but now it only works if there is only one organisation inside an array! If there are more, it shows nothing. 

1078078879_Screenshot2021-11-03at14_25_55.thumb.png.e3aa1924af9c4183f96c714242970c6e.png496800919_Screenshot2021-11-03at14_27_13.thumb.png.32b3e2f76e335305f1a50bc211f3daf9.png

How I implemented it:

// Build up a selector string
$selector_org = "template=Organisation";
$selector_ind = "template=Individual";
$techniques = $pages->find("template=Technique");
$selected_techniques_raw = $sanitizer->array($input->get('techniques_ind_org'));
$selected_techniques = $sanitizer->options($selected_techniques_raw, $techniques->explode('name'));
// A new empty PageArray that will hold the organisations selected for all the individuals
$organisations = new PageArray();
foreach ($selected_techniques as $selected_technique) {
    // The $individuals PageArray has been created elsewhere in your code
    $selector_ind .= ", relation_individual_technique.relation_individual_technique_select=$selected_technique";
    $matches_ind = $pages->find($selector_ind);
    foreach ($matches_ind as $match_ind) {
        echo " <li><a href='$match_ind->url'>$match_ind->title</a></li>";
        foreach ($match_ind->relation_individual_organisation as $relation_individual_organisation) {
            $organisations->add($relation_individual_organisation->relation_individual_organisation_select);
                 foreach ($organisations as $organisation) {
                     echo " <li><a href='$organisation->url'>$organisation->title</a></li>";
                 }
            }
        // Now do something with $organisations
        foreach ($organisations as $organisation) {
            $selector_org .= ", title=$organisation->title";
        }
    }
}

 

Link to comment
Share on other sites

11 hours ago, Ksenia said:

I tried to implement your approach but now it only works if there is only one organisation inside an array! If there are more, it shows nothing.

I think you must have some other bug in your code. When doing $some_pagearray->add() this is WireArray::add() and the argument can be a single item or a WireArray of items. So there shouldn't be any problem adding multiple pages to the PageArray like this.

My two "basic_page" pages with colours in a "multiple" Page Reference field:

2021-11-04_142614.png.b1eff4f4f5e4e0dc91e4b3106f3f431c.png

2021-11-04_142625.png.cc97a4ba832b0a90dfb6a8a8d529dc32.png

And you can see the result:

2021-11-04_142804.png.2a72694577918c31f8b46006f9c3df3b.png

By default a Page can only exist once in a PageArray so you'll notice that the "Orange" page is not duplicated in the PageArray despite being selected on both pages.

The order of colours looks a bit random but you can apply whatever sort you want by doing something like...

$colours->sort('title');

...or...

$colours->sort('sort');

...before the PageArray is output in your template.

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

×
×
  • Create New...