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 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
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
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