Search AutoComplete: Magento 2 Module Add All Category for Search

This article is all about how we can develop a Search AutoComplete extension in Magento 2. This module also provides the options to enable category search facilities.

When you install this module this automatically created a div box on the left side of the Magento 2 catalog search box in the header content and then you can select a category from the dropdown and search the product based on category.

Wishusucess Search AutoComplete

 

Wishusucess Search AutoComplete Features:

  • Easy to install/manage
  • Get all the category list
  • Enable/disable to search from categories
  • You can set the number of categories to display
  • Define the search delay period

Wishusucess Search AutoComplete

 

Step 1:

app/code/Wishusucess/SearchAutocomplete/registration.php

<?php
/*
Author: Wishusucess
Developer: Hemant Singh
Website URL: http://www.wishusucess.com/
*/
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Wishusucess_SearchAutocomplete',
__DIR__
);

 

Step 2:

app/code/Wishusucess/SearchAutocomplete/etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Wishusucess_SearchAutocomplete" setup_version="1.0.0">
</module>
</config>

 

Step 3:

app/code/Wishusucess/SearchAutocomplete/frontend/routes.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="standard">
<route id="searchautocomplete" frontName="searchautocomplete">
<module name="Wishusucess_SearchAutocomplete" />
</route>
</router>
</config>

 

Step 4: Magento 2 Search AutoComplete Controller

 

app/code/Wishusucess/SearchAutocomplete/Controller/Index/Index.php

<?php
/*
Author: Wishusucess
Developer: Hemant Singh
Website URL: http://www.wishusucess.com/
*/
namespace Wishusucess\SearchAutocomplete\Controller\Index;

class Index extends \Magento\Framework\App\Action\Action
{
protected $_resultJsonFactory; 
protected $_productCollectionFactory;
protected $_reviewFactory;
protected $_storeManager;
protected $_imageBuilder;
protected $_productVisibility;
protected $_categoryFactory;
protected $_priceHelper;

public function __construct(
\Magento\Framework\App\Action\Context $context,
\Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
\Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory,
\Magento\Review\Model\ReviewFactory $reviewFactory,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Catalog\Block\Product\ImageBuilder $imageBuilder,
\Magento\Catalog\Model\Product\Visibility $productVisibility,
\Magento\Catalog\Model\CategoryFactory $categoryFactory,
\Magento\Framework\Pricing\Helper\Data $priceHelper)
{
$this->_resultJsonFactory = $resultJsonFactory;
$this->_productCollectionFactory = $productCollectionFactory;
$this->_reviewFactory = $reviewFactory;
$this->_storeManager = $storeManager;
$this->_imageBuilder = $imageBuilder;
$this->_productVisibility = $productVisibility;
$this->_categoryFactory = $categoryFactory;
$this->_priceHelper = $priceHelper;
parent::__construct($context);
}
public function execute()
{
$postMessage = $this->getRequest()->getPost();

$query = preg_replace('/[^A-Za-z0-9\ \_\'\-]/', '', $postMessage['query']);
$category = preg_replace('/[^a-z0-9]/', '', $postMessage['category']);


if($category=='all'){
$collection = $this->_productCollectionFactory->create()
->addAttributeToSelect('*')
->addAttributeToFilter('name', array('like'=>'%'.$query.'%'));
}else{
$collection = $this->getProductCollection($category);
$collection->addAttributeToFilter('name', array('like'=>'%'.$query.'%'));
}
$collection->setVisibility($this->_productVisibility->getVisibleInSiteIds());

$collection ->setPageSize(5)
->setCurPage(1);

$productList = [];
$i = 1;

foreach ($collection as $product) {
$productList[$i]['name'] = str_ireplace($query,'<b>'.$query.'</b>',$product->getName());
$productList[$i]['price'] = $this->_priceHelper->currency(number_format($product->getFinalPrice(),2),true,false);
$productList[$i]['url'] = $product->getProductUrl();
$productList[$i]['thumbnail'] = $this->getImage($product, 'category_page_list')->getImageUrl();
$this->_reviewFactory->create()->getEntitySummary($product, $this->_storeManager->getStore()->getId());
$productList[$i]['rating'] = $product->getRatingSummary()->getRatingSummary();
$i++;
}

if($collection->getSize() > 0){
return $this->_resultJsonFactory->create()->setData($productList);
}else{
return $this->_resultJsonFactory->create()->setData([]);
}
}
public function getImage($product, $imageId)
{
return $this->_imageBuilder->setProduct($product)
->setImageId($imageId)
->create();
}
public function getCategory($categoryId)
{
$category = $this->_categoryFactory->create()->load($categoryId);
return $category;
}
public function getProductCollection($categoryId)
{
return $this->getCategory($categoryId)->getProductCollection()->addAttributeToSelect('*'); 
}
}

 

Step 5: Create Block for Search AutoComplete

app/code/Wishusucess/SearchAutocomplete/Block/SearchAutocomplete.php

<?php
/*
Author: Wishusucess
Developer: Hemant Singh
Website URL: http://www.wishusucess.com/
*/
namespace Wishusucess\SearchAutocomplete\Block;
class SearchAutocomplete extends \Magento\Framework\View\Element\Template
{
protected $_categoryCollectionFactory;
protected $_storeManager;
protected $_store;
protected $_categoryRepository;

public function __construct(
\Magento\Backend\Block\Template\Context $context, 
\Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $categoryCollectionFactory,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Store\Model\Store $store,
\Magento\Catalog\Model\CategoryRepository $categoryRepository,
array $data = []
)
{
$this->_categoryCollectionFactory = $categoryCollectionFactory;
$this->_storeManager = $storeManager;
$this->_store = $store;
$this->_categoryRepository = $categoryRepository;
parent::__construct($context, $data);
}

public function getCategoryCollection($isActive = true, $level = false, $sortBy = false, $pageSize = false)
{
$collection = $this->_categoryCollectionFactory->create();
$collection->addAttributeToSelect('*');
if ($isActive) {
$collection->addIsActiveFilter();
}
if ($level) {
$collection->addLevelFilter($level);
}
if ($sortBy) {
$collection->addOrderField($sortBy);
}
if ($pageSize) {
$collection->setPageSize($pageSize); 
}
$collection->setOrder('position', 'ASC');
return $collection;
}
public function getAjaxUrl(){
return $this->_storeManager->getStore()->getUrl('searchautocomplete/index/index');
}
public function getMediaUrl(){
return $this ->_storeManager->getStore()->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA );
}
public function getRootCategoryId()
{
return $this->_store->getStoreRootCategoryId();
}
public function getCategories(){
return $this->_categoryRepository->get(2)->getChildrenCategories();
}
}

 

Step 6:

app/code/Wishusucess/SearchAutocomplete/view/frontend/layout/default.xml

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd" >
<body>
<referenceContainer name="header-wrapper">
<referenceBlock name="top.search" remove="true"/>
<block class="Magento\Framework\View\Element\Template" name="top.search" as="" template="Magento_Search::form.mini.phtml" />
<block class="Wishusucess\SearchAutocomplete\Block\SearchAutocomplete" name="search.autocomplete" after="logo" template="search/autocomplete.phtml" />
</referenceContainer>
</body>
</page>

 

Step 7:

app/code/Wishusucess/SearchAutocomplete/templates/search/autocomplete.phtml

<?php
/*
Author: Wishusucess
Developer: Hemant Singh
Website URL: http://www.wishusucess.com/
*/
// @codingStandardsIgnoreFile
?>
<?php
/** @var $block \Magento\Framework\View\Element\Template */
/** @var $helper \Magento\Search\Helper\Data */
$helper = $this->helper(\Magento\Search\Helper\Data::class);
?>
<div class="block block-search">
<div class="block block-title"><strong><?= /* @escapeNotVerified */ __('Search') ?></strong></div>
<div class="block block-content">
<form class="form minisearch" id="search_mini_form" action="<?= /* @escapeNotVerified */ $helper->getResultUrl() ?>" method="get">
<div class="field search">
<label class="label" for="search" data-role="minisearch-label">
<span><?= /* @escapeNotVerified */ __('Search') ?></span>
</label>
<div class="control">
<div id="searchCategories">
<select>
<option value="all">All Categories</option>
<?php 
foreach($this->getCategories() as $category){
?>
<option value="<?php echo $category->getId(); ?>"><?php echo $category->getName(); ?></option>
<?php 
}
?>
</select>
</div>
<input id="search"
type="text"
name="<?= /* @escapeNotVerified */ $helper->getQueryParamName() ?>"
value="<?= /* @escapeNotVerified */ $helper->getEscapedQueryText() ?>"
placeholder="<?= /* @escapeNotVerified */ __('Search entire store here...') ?>"
class="input-text"
maxlength="<?= /* @escapeNotVerified */ $helper->getMaxQueryLength() ?>"
autocomplete="off"/>
</div>
</div>
<div class="actions">
<button type="submit"
title="<?= $block->escapeHtml(__('Search')) ?>"
class="action search">
<span><?= /* @escapeNotVerified */ __('Search') ?></span>
</button>
</div>
</form>
</div>
</div>

<script type="text/javascript">
require(['jquery', 'domReady!'], function($){
var searchControlElement = $('#search_mini_form .field.search .control').first();
searchControlElement.append('<div id="searchAutocomplete" style="display:none;"><ul></ul><button type="submit"><?= /* @escapeNotVerified */ __('View All Results') ?></button></div>');
$('#search_autocomplete').remove();
$('#search').unbind();
$(window).click(function() {
$('#searchAutocomplete').hide();
});
$('.block.block-search').click(function(event){
event.stopPropagation();
});
$('#search').bind('input', function(){
var searchInput = $(this);
var searchInputValue = searchInput.val().replace("/^[a-zA-Z0-9 _-]+$/i", "");
if(searchInputValue.length >=3){
url = "<?php echo $this->getAjaxUrl(); ?>";
$.ajax({
type: "POST",
url: url,
data: {query: searchInputValue, category: $('#searchCategories select').first().val()},
success: function(data){
console.log(data);
var searchResultHtml = '';
var size = Object.keys(data).length;
for(let i=1; i<=size; i++){
searchResultHtml += '<li data-url="'+data[i]['url']+'">';
searchResultHtml += '<div class="sa-image"><img src="'+data[i]['thumbnail']+'"></div>';
searchResultHtml += '<div class="sa-prop">';
searchResultHtml += '<div class="sa-title"><p>'+data[i]['name']+'</p></div>';
if(data[i]['rating']){
searchResultHtml += '<div class="sa-rating"><div class="rating-summary"><div class="rating-result" title="'+data[i]['rating']+'%"><span style="width:'+data[i]['rating']+'%"><span>'+data[i]['rating']+'%</span></span></div></div></div>';
}
searchResultHtml += '<div class="sa-price"><p>'+data[i]['price']+'<p></div>';
searchResultHtml += '</div>';
searchResultHtml += '</li>';
$('#searchAutocomplete ul').first().html(searchResultHtml);
}
$( "#searchAutocomplete ul li" ).on( "click", function() {
window.location = $(this).data('url');
});
if(size > 0){
$('#searchAutocomplete').show();
}else{
$('#searchAutocomplete').hide();
}
},
error: function (data) {
console.log(data);
}
});
}else{
$('#searchAutocomplete').hide();
}
});
$("#search").on("focus", function(){
var searchInput = $(this);
var searchInputValue = searchInput.val().replace("/^[a-zA-Z0-9 _-]+$/i", "");
if(searchInputValue.length >=3){
url = "<?php echo $this->getAjaxUrl(); ?>";
$.ajax({
type: "POST",
url: url,
data: {query: searchInputValue, category: $('#searchCategories select').first().val()},
success: function(data){
console.log(data);
var searchResultHtml = '';
var size = Object.keys(data).length;
for(let i=1; i<=size; i++){
searchResultHtml += '<li data-url="'+data[i]['url']+'">';
searchResultHtml += '<div class="sa-image"><img src="'+data[i]['thumbnail']+'"></div>';
searchResultHtml += '<div class="sa-prop">';
searchResultHtml += '<div class="sa-title"><p>'+data[i]['name']+'</p></div>';
if(data[i]['rating']){
searchResultHtml += '<div class="sa-rating"><div class="rating-summary"><div class="rating-result" title="'+data[i]['rating']+'%"><span style="width:'+data[i]['rating']+'%"><span>'+data[i]['rating']+'%</span></span></div></div></div>';
}
searchResultHtml += '<div class="sa-price"><p>'+data[i]['price']+'<p></div>';
searchResultHtml += '</div>';
searchResultHtml += '</li>';
$('#searchAutocomplete ul').first().html(searchResultHtml);
}
if(size > 0){
$('#searchAutocomplete').show();
}else{
$('#searchAutocomplete').hide();
}
$( "#searchAutocomplete ul li" ).on( "click", function() {
window.location = $(this).data('url');
});
},
error: function (data) {
console.log(data);
}
});
}else{
$('#searchAutocomplete').hide();
}
});
$("#searchCategories select").first().on("change", function(){
var searchInput = $('#search');
var searchInputValue = searchInput.val().replace("/^[a-zA-Z0-9 _-]+$/i", "");
if(searchInputValue.length >=3){
url = "<?php echo $this->getAjaxUrl(); ?>";
$.ajax({
type: "POST",
url: url,
data: {query: searchInputValue, category: $('#searchCategories select').first().val()},
success: function(data){
console.log(data);
var searchResultHtml = '';
var size = Object.keys(data).length;
for(let i=1; i<=size; i++){
searchResultHtml += '<li data-url="'+data[i]['url']+'">';
searchResultHtml += '<div class="sa-image"><img src="'+data[i]['thumbnail']+'"></div>';
searchResultHtml += '<div class="sa-prop">';
searchResultHtml += '<div class="sa-title"><p>'+data[i]['name']+'</p></div>';
if(data[i]['rating']){
searchResultHtml += '<div class="sa-rating"><div class="rating-summary"><div class="rating-result" title="'+data[i]['rating']+'%"><span style="width:'+data[i]['rating']+'%"><span>'+data[i]['rating']+'%</span></span></div></div></div>';
}
searchResultHtml += '<div class="sa-price"><p>'+data[i]['price']+'<p></div>';
searchResultHtml += '</div>';
searchResultHtml += '</li>';
$('#searchAutocomplete ul').first().html(searchResultHtml);
}
if(size > 0){
$('#searchAutocomplete').show();
}else{
$('#searchAutocomplete').hide();
}
$( "#searchAutocomplete ul li" ).on( "click", function() {
window.location = $(this).data('url');
});
},
error: function (data) {
console.log(data);
}
});
}else{
$('#searchAutocomplete').hide();
}
});
});
</script>
<style type="text/css">
#searchCategories {
display: inline-block;
float: left;
width: 30%;
}
#search {
display: inline-block;
position: relative;
left: unset;
width: 70%;
float: right;
}
.block-search {

}
#searchAutocomplete {
position: absolute;
top: 32px;
border: 1px solid #cccccc;
background-color: white;
width: 100%;
}
#searchAutocomplete button {
margin: 10px;
}
#searchAutocomplete ul {
margin: 0;
padding: 5px;
list-style: none;
}
#searchAutocomplete ul li {
padding: 4px;
border-bottom: 1px solid #cccccc;
margin: 0;
cursor: pointer;
}
#searchAutocomplete ul li:hover, #searchAutocomplete ul li:last-child:hover {
border: 1px solid #cccccc;
}
#searchAutocomplete ul li:last-child {
border-bottom: unset;
}
#searchAutocomplete .sa-image {
display: inline-block;
max-width: 30%;
}
#searchAutocomplete .sa-rating {
margin-top: 10px;
}
#searchAutocomplete .sa-prop {
display: inline-block;
width: 70%;
float: right;
}
#searchAutocomplete .sa-title {
margin-top: 10px;
}
#searchAutocomplete .sa-price {
margin-top: 10px;
padding: 10px;
}
@media all and (min-width: 768px), print {
.block-search {
width: 400px;
padding-left: 0;
}
}
</style>

 

Now, Run Following Command:

php bin/magento setup:upgrade

php bin/magento setup:di:compile

php bin/magento setup:static-content:deplpy -f

php bin/magento cache:clean

 

Search AutoComplete Wishusucess

Download Link

This Extension available on GitHub

 

Related Post:

Wishusucess AdminMenu: How to Create Magento 2 Admin Menu Module

HelloWorld Extension: How to Create Hello World Module

 

Recommended Post:

Magento 2.4 Installation Guide: How to Install Magento 2.4.2

Magento Store: Best 36 Magento Websites Example in The World