I can not paginate after filtering

hello, this is my first post. I have been working on a website and I am filtering images with tagcloud based on the category. The problem is that I need to load many images on the same page so I tried to use pagination. The problem is that I cant fina a way to paginate after the filtering because I am filtering on the grandparent of the page the image is in. Its a little tricky but here is my code

			<?php
				$tecnicas = tagcloud($page->children(), array('field'=>'tecnica',
																											'sort'    => 'name',
																											'sortdir' => 'asc'));
				$estilos = tagcloud($page->children(), array('field'=>'estilo',
																											'sort'    => 'name',
																											'sortdir' => 'asc'));
				$rangos_mx = tagcloud($page->children(), array('field'=>'rango_precio_mx',
																												'sort'    => 'name',
																												'sortdir' => 'asc'));
			 ?>

			<div class="obras__filter__navbar">
					<form class="obras__filter__list" action="" method="post">
						<select name="tecnicas">
							<option value="val1">Técnica</option>
							<?php foreach($tecnicas as $tecnica): ?>
								<option value="<?= $tecnica->name()?>">
									<?= $tecnica->name()?>
								</option>
							<?php endforeach ?>
						</select>
						<select name="estilos">
							<option value="val2">Estilo</option>
							<?php foreach($estilos as $estilo): ?>
								<option value="<?php echo $estilo->name()?>">
									<?= $estilo->name()?>
								</option>
							<?php endforeach ?>
						</select>
						<select name="rangos_mx">
							<option value="val3">Precio MXN</option>
							<?php foreach($rangos_mx as $rango_mx): ?>
								<option value="<?php echo $rango_mx->name()?>">
									<?= $rango_mx->name()?>
								</option>
							<?php endforeach ?>
						</select>
						<div class="obras_btn">
							<button type="submit" id="submit" name="submit" value="Submit" class="btn">Filtrar</button>
						</div>
					</form>
			</div>
			<?php $autores = $page->children()->paginate(4)?>
			<?php var_dump($autores); ?>
			<div class="obras__container">
				<?php
					$items = 0;
					foreach($autores as $autor):
			  ?>
			    <?php
						$obras = $autor->children()->flip();

				    if($_POST){

							// 1 FILTRO
							if ($_POST['tecnicas'] == 'val1' && $_POST['rangos_mx'] == 'val3') {
								$obras = $autor->children()->filterBy('estilo', '*=', $_POST['estilos']);  // In here i do 
                    // the filtering of the images. 
								foreach ($obras as $obra) {
									$autores = $obra->parent()->title(); // what I want here is to save all the autors //of  each $obra without reapeats 
								}
								$autores = $autores->paginate(4);
								var_dump($autores); 
							}
							elseif ($_POST['estilos'] == 'val2' && $_POST['rangos_mx'] == 'val3') {
								$obras = $autor->children()->filterBy('tecnica', '*=', $_POST['tecnicas']);
							}
							elseif ($_POST['tecnicas'] == 'val1' && $_POST['estilos'] == 'val2') {
								$obras = $autor->children()->filterBy('rango_precio_mx', '*=', $_POST['rangos_mx']);
							}

					<?php
						$items ++;
						endforeach
					?>

As I understand it your trying to change the page content based on the selection of a dropdown? I think you would be better off using ajax or a front end solution like list.js. Your current solution looks like it causes a page reload, which is not great from the users point of view. Its late and i cant quite follow your code, but it seems a little overboard.

Are you using a controller at all? If not it might better to move the logic into a controller and do the hard work there with filtering and pagination.

Edit: Actually i think this is basically what you want - https://getkirby.com/docs/cookbook/filtering#using-filters-with-forms-e-g-select-fields

I think it would be useful if you first define what you are trying to do here. There are a lot of if statements in your code that don’t seem to make much sense.

You seem to have a structure like this

– parent page --> autores. --> obras

And you want to show what exactly in the parent page? All obras of all authors if nothing is selected? Filtered obras if something is selected? What needs to be paginated here, the authors or the obras?

@jimbobrjames has already pointed you to the filtering compendium, but your use case seems to be a bit different.

Actually, I think you would better approach this differently.

First get all obras. Then filter if filters are applied.

<?php

$obras = $page->grandchildren()->visible();

Then do the same filtering stuff as described in the filtering compendium

//if filter is set
$obras = $obras->filter(function($child) {
// filter code here
}

Finally group the obras by author (using a group method with a callback), then paginate.

All this would go into a controller.

this is a screenshot of the site:

I have the main page which has autors as children pages, and each autor has paintings as their children.
Because of the design, I first do a for loop for each autor, and then when I have each autor I do the filtering on each painting of that autor by their tag. The problem is, I am filtering the paintings by tag and gets me a lot of paintings, many of which have the same autors. I need this for filtering the paintings, but the problem is, there are a lot of autors and images to load so I want to paginate. What I am trying yo do is to get the autors that had any paintings after the filtering. I know I should be using a controller but right now I just want to get the logic to work.

And thank you for answering!!

Can you tranlsate the dropdown filters for me? What do they mean in english? Are they like types of work, as in oils, sketch, watercolor etc? Why is there three?

Sorry, this is the english version:

Each of that is a tag that each painting page has.
I want to filter by 3 different categories, style, price and technique. Each of those has their options as tags. The filtering I did was to filter by every possible combination of the 3 categories, because I want to be able to filter all the images by say tecnique, or by teccnique and style and so on. I dont know if it makes sense ?

Ok, that makes a little more sense. What does the blueprint look like?

Yes, it makes sense, but you can do this all in one, without all the different if statements. the first value should always. be empty, though. Have a look at the example in the filtering compendium.

the blueprint of each painting is something like this:

fields:
  title:
    label: Nombre de la obra
    type:  text
    width: 1/2

  obra_image:
    label: Imagen
    type:  image
    width: 1/2

  precio_us:
    label: Precio EUA
    type: text
    width: 1/2

  dimensiones:
    label: Dimensiones
    type: text
    width: 1/2

  tecnica:
    label: Técnica
    type: tags
    width: 1/2


  estilo:
    label: Estilo
    type: tags

  rango_precio_us:
    label: Rango de precio USA
    type: tags
    width: 1/2

With my suggestion above to start with the works instead of with the authors, you could still get the same design, only with much cleaner code.

Your controller would look like this:

<?php


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

  $obras = $page->grandchildren();

  $tecnicas = tagcloud($page->children(), array(
    'field'=>'tecnica',
    'sort'    => 'name',
    'sortdir' => 'asc')
  );
  $estilos = tagcloud($page->children(), array(
    'field'=>'estilo',
    'sort'    => 'name',
    'sortdir' => 'asc')
  );
  $rangos_mx = tagcloud($page->children(), array(
    'field'=>'rango_precio_mx',
    'sort'    => 'name',
    'sortdir' => 'asc')
  );

  if(r::is('POST') && $data = get()) {
      $keys = array('technica', 'estilo', 'rango_price_US');
      $obras = $obras->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;

      });
  }

  // we apply grouping and pagination here, so that it applies to both the unfiltered and the filtered collections
  // lets group the works by author; we use the uid here to make it easy to later find the page again 
  // then we paginate, so we get 4 groups (= 4 authors and their works) per page.
  $groups = $obras->group(function($child) {
    return $child->parent()->uid();
  })->paginate(4);
 
  // finally, let's create a pagination object for the navigation
  $pagination = $groups->pagination();

  return [
    'groups'   => $groups,
    'tecnicas' => $tecnicas,
    'estilos' => $estilos,
    'rangos_mx' => $rangos_mx,
    'pagination' => $pagination
  ];

};

In your template:


<div class="obras__filter__navbar">
    <form class="obras__filter__list" action="" method="post">
      <select name="tecnicas">
        <option value="">Técnica</option>
        <?php foreach($tecnicas as $tecnica): ?>
          <option value="<?= $tecnica->name()?>">
            <?= $tecnica->name()?>
          </option>
        <?php endforeach ?>
      </select>
      <select name="estilos">
        <option value="">Estilo</option>
        <?php foreach($estilos as $estilo): ?>
          <option value="<?php echo $estilo->name()?>">
            <?= $estilo->name()?>
          </option>
        <?php endforeach ?>
      </select>
      <select name="rangos_mx">
        <option value="">Precio MXN</option>
        <?php foreach($rangos_mx as $rango_mx): ?>
          <option value="<?php echo $rango_mx->name()?>">
            <?= $rango_mx->name()?>
          </option>
        <?php endforeach ?>
      </select>
      <div class="obras_btn">
        <button type="submit" id="submit" name="submit" value="Submit" class="btn">Filtrar</button>
      </div>
    </form>
  </div>


<?php

// loops through the groups
foreach($groups as $author => $group):
  // find the author page for each work
  if($author = $page->children()->findBy('uid', $author)): ?>
    <!-- output all the author information your want -->
    <h2><?= $author->title() ?></h2>
  <?php endif ?>
  <!-- loop through the group of works -->
  <?php foreach($group as $item): ?>
     <!-- output all the information about each work -->
    <?= $item->title(); ?>
    <!-- and the image etc. -->
    <?php endforeach; ?>  
<?php endforeach; ?>

The value of the first select option should each be empty.

Thank you for this!
This worked, but I still have a problem, with this code , On the main page I have 6 pages of pagination and if I filter from page 1 say by technique it shows me around 3 pages of pagination. But if I click the second page of the result it shows the second page of all pages. The only way to access the second or third page of a filter result is by making the filtering in the second or third page when all the pictures are. This is the code I have for the pagination nav:

Everything else is the same as the code you did with minor variable changes.

And you are not redefining the $pagination variable anywhere? It is only defined once in the controller, nowhere else?

No, I double checked the témplate and the variable is only declared on the controller exactly how you did it and nowhere else

Just realised there is some stuff missing from your form and also from the controller, maybe you could post your final code (template + controller)?

<?php
return function($site, $pages, $page) {

  $obras = $page->grandchildren();

  $tecnica = tagcloud($page->children(), array(
    'field'=>'tecnica',
    'sort'    => 'name',
    'sortdir' => 'asc')
  );
  $estilo = tagcloud($page->children(), array(
    'field'=>'estilo',
    'sort'    => 'name',
    'sortdir' => 'asc')
  );
  $rango_precio_mx = tagcloud($page->children(), array(
    'field'=>'rango_precio_mx',
    'sort'    => 'name',
    'sortdir' => 'asc')
  );

  if(r::is('POST') && $data = get()) {
      $keys = array('tecnica', 'estilo', 'rango_precio_mx');
      $obras = $obras->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;

      });
  }

  // we apply grouping and pagination here, so that it applies to both the unfiltered and the filtered collections
  // lets group the works by author; we use the uid here to make it easy to later find the page again
  // then we paginate, so we get 4 groups (= 4 authors and their works) per page.
  $groups = $obras->group(function($child) {
    return $child->parent()->uid();
  })->paginate(5);

  // finally, let's create a pagination object for the navigation
  $pagination = $groups->pagination();

  return [
    'groups'   => $groups,
    'tecnica' => $tecnica,
    'estilo' => $estilo,
    'rango_precio_mx' => $rango_precio_mx,
    'pagination' => $pagination
  ];

};

And here is the template:

   <div class="obras__filter__navbar">
	    <form class="obras__filter__list" action="" method="post">
	      <select name="tecnica">
	        <option value="">Técnica</option>
	        <?php foreach($tecnica as $tecnica): ?>
	          <option value="<?= $tecnica->name()?>">
	            <?= $tecnica->name()?>
	          </option>
	        <?php endforeach ?>
	      </select>
	      <select name="estilo">
	        <option value="">Estilo</option>
	        <?php foreach($estilo as $estilo): ?>
	          <option value="<?= $estilo->name()?>">
	            <?= $estilo->name()?>
	          </option>
	        <?php endforeach ?>
	      </select>
	      <select name="rango_precio_mx">
	        <option value="">Precio MXN</option>
	        <?php foreach($rango_precio_mx as $rango_mx): ?>
	          <option value="<?= $rango_mx->name()?>">
	            <?= $rango_mx->name()?>
	          </option>
	        <?php endforeach ?>
	      </select>
	      <div class="obras_btn">
	        <button type="submit" id="submit" name="submit" value="Submit" class="btn">Filtrar</button>
	      </div>
	    </form>
  </div>

>` <?php foreach($groups as $author => $group):`
> 		  if($author = $page->children()->findBy('uid', $author)): ?>
> 				<div class="obras__container">
> 					<div class="row row--mod">
> 						<!-- AUTOR -->
> 						<div class="col-lg-4 col-sm-12 col--end">
> 								<div class="obras__autor__info">
> 									<div class="row size--mod">
> 										<div class="col-6">
> 											<div class="obras__autor__image">
> 												<?php if ($img1=$author->autor_image()->toFile()): ?>
> 													<img src="<?= $img1->url() ?>" alt="<? ?$author->title()->html()>">
> 												<?php endif; ?>
> 											</div>
> 										</div>
> 										<div class="col-6">
> 											<div class="obras__autor__abstract">
> 												<h2><?= $author->title()->html() ?></h2>
> 												<?= $author->abstract()->kirbytext() ?>
> 											</div>
> 										</div>
> 									</div>
> 								</div>
> 						</div>
> 
> 					 <!-- loop through the group of works -->
> 					 <!-- OBRAS -->
> 					 <div class="col-lg-8 col-sm-12 col--end">
> 							 <div class="obras__carousel__container">
> 								<!-- SLIDER -->
> 									<div class="obras-carousel">
> 										<?php foreach($group as $item): ?>
> 	 										 <div class="carousel-cell margin--cell">
> 	 												 <a href="<?= $item->url()?>">
> 	 													 <div class="obra__item__image">
> 	 															<?php if ($img2=$item->obra_image()->toFile()): ?>
> 	 																<img src="<?= $img2->url() ?>" alt="<?= $item->title()->html()?>">
 															<?php endif; ?>
 														</div>
 												 </a>
 												 <div class="obra__item__text">
 													 <p>
 														 <?= $item->dimensiones()->html() ?>
 													 </p>
 												 </div>
 										 </div>
								 <?php endforeach; ?>
							 </div>
					 </div>
				 </div>

				</div>
			</div>
	 <?php endif ?>
	<?php	endforeach;
	?>

	</section>

<nav class="navigation_paginate">
  <ul>
    <?php foreach($pagination->range(10) as $r): ?>
    	<li>
				<a<?php if($pagination->page() == $r) echo ' class="active"' ?>
					href="<?= $pagination->pageURL($r) ?>">
					<?= $r ?>
				</a>
		 </li>
    <?php endforeach ?>
  </ul>
</nav>

In the. controller, we have to add the $data variable:

<?php
return function($site, $pages, $page) {

  $obras = $page->grandchildren();

  $data = '';

  $tecnica = tagcloud($page->children(), array(
    'field'=>'tecnica',
    'sort'    => 'name',
    'sortdir' => 'asc')
  );
  $estilo = tagcloud($page->children(), array(
    'field'=>'estilo',
    'sort'    => 'name',
    'sortdir' => 'asc')
  );
  $rango_precio_mx = tagcloud($page->children(), array(
    'field'=>'rango_precio_mx',
    'sort'    => 'name',
    'sortdir' => 'asc')
  );

  if(r::is('POST') && $data = get()) {
      $keys = array('tecnica', 'estilo', 'rango_precio_mx');
      $obras = $obras->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;

      });
  }

  // we apply grouping and pagination here, so that it applies to both the unfiltered and the filtered collections
  // lets group the works by author; we use the uid here to make it easy to later find the page again
  // then we paginate, so we get 4 groups (= 4 authors and their works) per page.
  $groups = $obras->group(function($child) {
    return $child->parent()->uid();
  })->paginate(5);

  // finally, let's create a pagination object for the navigation
  $pagination = $groups->pagination();

  return [
    'groups'   => $groups,
    'tecnica' => $tecnica,
    'estilo' => $estilo,
    'rango_precio_mx' => $rango_precio_mx,
    'pagination' => $pagination,
    'data' =>  $data
  ];

};

And in the form, the. options have to be changed:

<div class="obras__filter__navbar">
	    <form class="obras__filter__list" action="" method="post">
	      <select name="tecnica">
	        <option value=""  selected>Técnica</option>
	        <?php foreach($tecnica as $tecnica): ?>
          <option<?php e(isset($data['tecnica']) && $data['tecnica'] == $tecnica->name(), ' selected') ?> value="<?= $tecnica->name() ?>">
            <?= $tecnica->name() ?>
          </option>

	        <?php endforeach ?>
	      </select>
	      <select name="estilo">
	        <option value="" selected>Estilo</option>
	        <?php foreach($estilo as $estilo): ?>
          <option<?php e(isset($data['estilo']) && $data['estilo'] == $estilo->name(), ' selected') ?> value="<?= $estilo->name() ?>">
            <?= $estilo->name() ?>
          </option>          
	          <option value="<?= $estilo->name()?>">
	            <?= $estilo->name()?>
	          </option>
	        <?php endforeach ?>
	      </select>
	      <select name="rango_precio_mx">
	        <option value="">Precio MXN</option>
	        <?php foreach($rango_precio_mx as $rango_mx): ?>
          <option<?php e(isset($data['rango_mx']) && $data['rango_mx'] == $rango_mx->name(), ' selected') ?> value="<?= $rango_mx->name() ?>">
            <?= $rango_mx->name() ?>
          </option>              
	        <?php endforeach ?>
	      </select>
	      <div class="obras_btn">
	        <button type="submit" id="submit" name="submit" value="Submit" class="btn">Filtrar</button>
	      </div>
	    </form>
  </div>

With these changes, the pagination should then also work as expected.

Thank you again for your answer!
I did that but the navigation is still working the same, I really dont know why.

Hm, the action attribute is missing in your forms,

action="<?= $page->url() ?>"

but I don’t know if that makes a difference. If not, feel free to upload your project somewhere and send me a PM with the link.

1 Like