Parent post: Online Book Catalog Application Tutorial
It is time to provide a little search box that visitors can enter keywords to make a search in the Online Book Catalog.
Developing an engaging web site requires a search functionality to provide visitors to find out what they are looking for quickly. I will show you how to build a basic search functionality in this post of the Online Book Catalog Tutorial series.
I will start with placing the search text box at the top right which looks to be a good spot for this purpose. I will update the catalog index view template to implement this.
<div class="row">
<div class="col-sm-6">
<h1>Book Listing</h1>
</div>
<div class="col-sm-6 pull-right">
<h4>Make a search in the catalog</h4>
<form method="get">
<div class="input-group pull-right">
<input name="search_keyword" type="text" class="form-control" placeholder="Search...">
<span class="input-group-btn">
<button class="btn btn-default" type="submit">
<span class="glyphicon glyphicon-search"></span>
</button>
</span>
</div>
</form>
</div>
</div>
<hr>
<div class="row">
// ... refer to the previous post in the series for this section
</div>
<div>
<?= $this->paginationControl(
$this->books,
'Sliding',
'partial/paginator',
[]
);
?>
</div>
Fig1 shows the new look with the Search Box at the top right corner of the page.
I set up the form
element in the code to make the request method GET
, so when visitor clicks on the search button which is a submit
button element, the keyword will be added to the URL as the form will be submitted
as a GET request and will be captured in the associated controller action in the next step.
Just like the page
value previously obtained from the URL query, I will get the search keyword by using the controller's
$this->params()->fromQuery()
method and pass it to the view template with searchKeyword
parameter as shown in the updated action method. By passing the variable, the view template will use the keyword between pages and the pagination control will be also able to paginate by using the same keyword.
<?php
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Application\Model\Book\BookMapper;
class CatalogController extends AbstractActionController
{
// ... refer to the previous post in the series for this section
public function indexAction()
{
// Getting requested page number from the query
$pageNumber = (int) $this->params()->fromQuery('page', 1);
// Set total item count per page for pagination
$count = 3;
// Getting search keyword if any
$searchKeyword = (string) $this->params()->fromQuery('search_keyword', false);
$books = $this->bookMapper->fetchAll($pageNumber, $count);
return new ViewModel([
'books' => $books,
'searchKeyword' => $searchKeyword,
]);
}
}
Modified view template using the passed $searchKeyword
variable:
<div class="row">
<div class="col-sm-6">
<h1>Book Listing</h1>
</div>
<div class="col-sm-6 pull-right">
<h4>Make a search in the catalog</h4>
<form method="get">
<div class="input-group pull-right">
<input name="search_keyword" type="text" class="form-control" placeholder="Search..." value="<?= $this->searchKeyword ?>">
<span class="input-group-btn">
<button class="btn btn-default" type="submit">
<span class="glyphicon glyphicon-search"></span>
</button>
</span>
</div>
</form>
</div>
</div>
<hr>
<div class="row">
// ... refer to the previous post in the series for this section
</div>
<div>
<?= $this->paginationControl(
$this->books,
'Sliding',
'partial/paginator',
['searchKeyword' => $this->searchKeyword]
);
?>
</div>
Note that how I passed searchKeyword
to the paginator in the last array parameter so it will be injected into the pagination control object to build the page links by also placing the keyword in the URL.
Following shows the updated paginator.phtml
template that I created as partial template in the previous post of this tutorial series. Note that how search_keyword
query parameter is populated with the passed searchKeyword
variable.
<?php if ($this->pageCount): ?>
<ul class="pagination pagination-sm">
<?php if (!isset($this->previous)): ?>
<li class="disabled">
<a>«</a>
<a>‹</a>
</li>
<?php else: ?>
<li>
<a href="<?= $this->url($this->route, [],
['query' => [
'page' => $this->first,
'search_keyword' => $this->searchKeyword
]]); ?>">«</a>
<a href="<?= $this->url($this->route, [],
['query' => [
'page' => $this->previous,
'search_keyword' => $this->searchKeyword
]]); ?>">‹</a>
</li>
<?php endif; ?>
<!-- Numbered page links -->
<?php foreach ($this->pagesInRange as $page): ?>
<?php if ($page == $this->current): ?>
<li class="active">
<a><?= $page ?></a>
</li>
<?php else: ?>
<li>
<a href="<?= $this->url($this->route, [],
['query' => [
'page' => $page,
'search_keyword' => $this->searchKeyword
]]); ?>">
<?= $page; ?>
</a>
</li>
<?php endif; ?>
<?php endforeach; ?>
<?php if (!isset($this->next)): ?>
<li class="disabled">
<a>›</a>
<a>»</a>
</li>
<?php else: ?>
<li>
<a href="<?= $this->url($this->route, [],
['query' => [
'page' => $this->next,
'search_keyword' => $this->searchKeyword
]]); ?>">›</a>
<a href="<?= $this->url($this->route, [],
['query' => [
'page' => $this->last,
'search_keyword' => $this->searchKeyword
]]); ?>">»</a>
</li>
<?php endif; ?>
</ul>
<?php endif; ?>
It looks that our model is about to return filtered results but it won't happen itself unless BookMapper
is informed about this request and return filtered results instead of returning the entire table content as it is currently doing.
I am going to achieve this by passing the $searchKeyword
variable to the mapper's fetchAll()
method to query database by using this value to search in the title
field. I will use where
method of the Zend\Db\Sql\Select
element.
<?php
namespace Application\Model\Book;
use Zend\Db\TableGateway\TableGateway;
use Zend\Db\Sql\Select;
class BookMapper
{
// ... refer to the previous post in the series for this section
/**
* FetchAll for Book Model
*
* @param int $pageNumber
* @param int|null $count
* @param string|null $keyword
* @return \Zend\Paginator\Paginator \Application\Model\Book\BookEntity[]
*/
public function fetchAll($pageNumber = 1, $count = null, $keyword = null)
{
$select = new Select($this->tableGateway->table);
$select->order('publish_date DESC');
if (!is_null($keyword)) {
$select->where
->like('title', sprintf('%%%s%%', $keyword));
}
$rowset = $this->tableGateway->selectWith($select);
return $rowset;
}
}
And the final edit will be done in the controller action. I will make the $bookMapper->fetchAll()
call by also passing the $searchKeyword
parameter this time.
// ..
$books = $this->bookMapper->fetchAll($pageNumber, $count, $searchKeyword);
// ..
Finally Fig2 shows how it works when I make a search for php
keyword. I will also see how pagination works great when my catalog grows up with more book entries.
I didn't add any new file in this post, but simply changed the existing controller, mapper, and view templates to provide simple search functionality.
This could be improved to make the search against more fields other than only the "title" field, make it searching for more keywords, and ideally search keyword can be stored in a session variable to not pass it with the URL but along with the session.