Search - Show Results from a Filter on a Page Only

I have an Items page that has a custom controller to filter an item to only show those details for the one item (like a product detail view).

How do I make the search only show results for those “detail pages”?

<?php

return function ($page) {
    $filter       = get('filter');
    $items        = $page->items()->toStructure();
    $selectedItem = $filter ? $items->findBy('itemnumber', $filter) : null;


 return [
        'filter'       => $filter,
        'items'        => $items,
        'selectedItem' => $selectedItem
    ];
};

Page Template

<?php snippet('head') ?>

<?php snippet('header') ?>

<div id="content_container">
	<div class="grid-x grid-margin-x">
		<?php if ($filter && $selectedItem): ?>
			<div id="page_title" class="cell">
				<?= $page->meta_title() ?> - Item#<?= $selectedItem->itemnumber() ?>
			</div>
		<?php else: ?>
			<div id="page_title" class="cell">
				<?= $page->meta_title() ?>
			</div>
		<?php endif ?>

		<div id="page_content" class="cell">
			<div class="grid-x grid-margin-x">
			<?php if ($filter && $selectedItem): ?>
				<div id="selected_item" class="cell">
					<div id="item_image">
						<a href="<?= $selectedItem->image()->toFile()->url() ?>" data-lightbox="<?= $selectedItem->itemnumber() ?>">
							<?= $selectedItem->image()->toFile() ?>
						</a>
						<span><i class="fa-solid fa-magnifying-glass-plus"></i> Click on image to enlarge.</span>
					</div>
					<div id="item_details">
						<div id="item_number">
							Item# <?= $selectedItem->itemnumber() ?>
						</div>
						<div id="item_full_description">
							<?= $selectedItem->fulldescription() ?>
						</div>
						<div id="item_price">
							Price: $<?= $selectedItem->price() ?>
						</div>
					</div>
				</div>

			<?php else: ?>

				<div id="page_sidebar" class="cell medium-3">
					<h2 id="submenu_header">
						<center><span><i class="fa-solid fa-rectangle-list fa-xl"></i></span> Categories</center>
					</h2>
					<?php snippet('submenu') ?>
				</div>
				<div class="cell medium-9">
					<?php if ($page->alert()->isNotEmpty()): ?>
						<div id="page_alert">
							<?= $page->alert()->kt() ?>
						</div>
					<?php endif ?>

					<table id="items_listing">
						<tbody>
						<?php foreach ($items as $item): ?>
						<!-- show the list in the table here -->
							<tr class="list-item-row">
								<td class="list-item">
									<a href="?filter=<?= $item->itemnumber() ?>"><span><i class="fa-solid fa-magnifying-glass-plus"></i></span><?= $item->image()->tofile() ?></a>
									<span class="list-item-number"><?= $item->itemnumber() ?></span>
									<?php if ($item->newitemdate()->isNotEmpty()): ?>
										<div class="list-item-date">
											New <?= $item->newitemdate()->toDate('m-d-y') ?>
										</div>
									<?php endif ?>
								</td>
								<td class="list-item-short-desc">
									<?= $item->shortdescription() ?>
									<span class="list-item-price">$<?= $item->price() ?></span>
								</td>
							</tr>
								
						<?php endforeach ?>
						</tbody>
					</table>

					<div class="button-group align-center">
						<a class="brand-button" href="/table-of-contents">Table of Contents</a>

						<?php if ($page->hasPrevListed()): ?>
							<a class="brand-button" href="<?= $page->prevListed()->url() ?>">Previous Section</a>
							<?php endif ?>

							<?php if ($page->hasNextListed()): ?>
							<a class="brand-button" href="<?= $page->nextListed()->url() ?>">Next Section</a>
						<?php endif ?>
					</div>
				</div>
			<?php endif ?>
			</div>
		</div>
	</div>
</div>

<?php snippet('footer') ?>

Could you please describe what exactly is not working as expected?

Currently the site search only will show the content pages based on “title|text”. I need it to show the results of the filtered items only.

Example: /search?q=4437

I need it to search only for $selectedItem->itemnumber()

Don’t see how this search query relates to your code above?

That is true. I was just showing the filtered page code to see what is needed for the search controller to function the way that I need.

Current Controller (how to modify?)

<?php

return function ($site) {

  $query   = get('q');
  $results = $site->search($query, 'title|text|itemnumber');

  return [
    'query'   => $query,
    'results' => $results,
  ];

};

itemnumber is not a field, but a subfield of the structure field, so limiting to itemnumber doesn’t make sense here. If you only want to get a particular structure field item, site search is not really the right choice, or you would have to search in the parent field, then if the query string is such a number and that query returns a result, filter the structure of the given page by that itemnumber, or something like that.

How would you search in the parent field? Is it at the $site or $page level?

I assume it would be at the $page level, if so then I would need a function for each page with the list of items in the structured field.

It might make more sense to use subpages instead of the structure field. What was your reason for the structure field instead of subpages?

The reason was so I could filter results to show a dynamic detail page without have to create double the amount of pages.

It also makes it easier for the client to only enter in an item once.

The maybe create virtual pages from the structure items.

Okay. Resource to explain how to do that?

We have a complete guide with multiple examples, for virtual children from the structure field, you would have to adapt this a little.

The principle is the same, you would create a model for the category page and the structure field would fill the virtual pages.

The model would be within the route within config correct? Can you help me with an example I can modify?

No, route and model are two different things.

Use this example, but instead of reading from the .csv, get the structure field instead:

Example with structure here in the first post: Page model for virtual children pages on the fly

I have this as the model but it is giving a 404 error.

<?php

use Kirby\Cms\Page;

class ItemsPage extends Page
{

    public function children()
    {
        $itemdetail = [];

        foreach ($this->items()->toStructure() as $item) {
            $itemdetail[] = [
                'slug'     => $this->slug() . $item->itemnumber(),
                'template' => 'item',
                'model'    => 'item',
                'content'  => [
                    'title'  => $this->title() . '(' . $item->itemnumber() . ')',
                ]
            ];
        }

        return Pages::factory($itemdetail, $this);
    }

}

Just to make sure the model name is correct, so the page with the structure field uses the items template?

   public function children()
    {
        if ($this->children instanceof Pages) {
            return $this->children;
        }

        $itemdetail = [];

        foreach ($this->items()->toStructure() as $item) {
            $itemdetail[] = [
                'slug'     => $item->itemnumber(),
                'template' => 'item',
                'model'    => 'item',
                'content'  => [
                    'title'  => $this->title() . ' (' . $item->itemnumber() . ')',
                ]
            ];
        }

        return $this->children = Pages::factory($itemdetail, $this);
    }

The page with the structure field uses the template ‘sidemenuproducts.php’.

Then you have to prepend this to Page and not Items