Magento 2 - Store Locator with a Dynamic Route

This entry was posted on September 24, 2019 by Igor Stajić, SyncIt Group Team Leader.

Store Locator

What is a Store Locator?

Store Locator is an extension that helps you reach the exact location of a physical store more easily. In this article, we will go through the configuration and implementation of Magento 2 custom Store Locator. Moreover, we are going to review the class for creating a dynamic route mechanics and enabling an additional block on the checkout page.

Finding the nearest store and getting accurate directions to that store are extremely difficult and complicated tasks. To help you solve them, SyncIt Group Company has created a Store Locator extension that can be appropriately customized for the needs of your website.

Store Locator Features

Store Locator extension can bring a variety of huge improvements to shop owners and their customers. Some of the main features that add to the improvement are the following:

  • Store Locator map can be added to any web page, which is extremely convenient since there is no need to return to a specific page in order to find the store you are looking for.
  • It provides filters to help you find stores according to certain criteria. You can base your search on the distance of the store (search radius in kilometers or miles), name, location, ZIP code, etc. or you can choose one from the store list.

 

Store Locator Map

1. Store Locator Map, Source: https://rainierarms.com

 

  • Create and import unlimited store locations. Instead of adding one location at a time, you can simply import a csv or excel file filled with store locations and they will become visible on the Store Locator map.
  • It is optimized for mobile use. When you are on the move, which presumably is very often, you have access to the same extension with the same functionalities.
  • Set flexible store opening hours & days off. Use a very simple table to set location schedules, that is, to set the opening hours and days off based on the store's name and ID. This data is easily manageable - just pick the store, click “Edit” and change data in question.
  • CSV sample file is available for download. When you click on “Import Stores” in the Store Locator plugin, you instantly get the list of the column for importing. To download the file, simply click on the button “Download Sample File” located below the list.
  • Get the store’s details in 1 click. If you click on the location pin of the store, you will get all important data related to the store - Name, Address, ZIP code, Country, State, Website, Phone Number, Email Address, Work Time, Rating, Reviews, and Image Gallery of the store.
  • Upload images for any store location. Create customized markers in order for the store locations to stand out on the Store Locator map.
  • Multiple Store Views for Store Locator. Thanks to this useful functionality, you are able to create multiple store views from the admin panel. Different store views can display the same store for different countries but with different languages and currencies.
  • All store locations can be managed from the admin panel in the form of a grid. Apart from managing the basic store info (name, ID, country, etc.), you can enable or disable the status of the store and change its position.

 

Store Locator Dashboard

2. Store Locator Dashboard, Source: https://amasty.com

Adding a Dynamic Route in Magento 2

Creating a custom router can be very useful when you want to separate some business logic that can be applied on the same route (URL) without redirecting it to different routes inside other controllers.

A lot of Magento 2 extensions, like subscription and blog modules, allow merchants to configure the URL of the page in the admin panel within the Stores -> Configuration area. We have added this functionality by creating a custom Magento 2 Router class allowing the merchant to add a dynamic route in the Magento 2 admin panel.

Now, let’s focus on the steps that are necessary in order to allow a dynamic route that can be configured in the Stores -> Configuration section.

First, you need to add two necessary files for the custom module creation - registration.php and module.xml files.

File: app/code/Vendor/Module/registration.php

<?php
   \Magento\Framework\Component\ComponentRegistrar::register(
       \Magento\Framework\Component\ComponentRegistrar::MODULE,
       'SyncIt_StoreLocator',
       __DIR__
   );

 

File: app/code/Vendor/Module/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="SyncIt_StoreLocator" setup_version="0.0.1"/>
</config>

 

In system.xml file, you can allow the merchant to configure a custom URL from the admin panel.

File: app/code/Vendor/Module/etc/adminhtml/system.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
  <system>
     <section id="syncit_storelocator" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
        <label>Store Locator</label>
        <tab>syncit</tab>
        <resource>SyncIt_StoreLocator::storelocator</resource>
        <group id="general" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
           <label>General</label>
           <field id="route" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
              <label>Route</label>
           </field>
        </group>
     </section>
  </system>
</config>

 

In routes.xml file, you should define the default route.

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

 

Index Controller class will be used to handle the route.

<?php
namespace SyncIt\StoreLocator\Controller\Index;

use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;
use SyncIt\StoreLocator\Helper\Data as Helper;

class Index extends Action
{
   /**
    * @var PageFactory
    */
   protected $resultPageFactory;

   /**
    * @var Helper
    */
   protected $_helper;
   /**
    * Index constructor.
    * @param Context $context
    * @param PageFactory $resultPageFactory
    * @param Helper $helper
    */
   public function __construct(
       Context $context,
       PageFactory $resultPageFactory,
       Helper $helper)
   {
       parent::__construct($context);
       $this->resultPageFactory = $resultPageFactory;
       $this->_helper = $helper;
   }

   /**
    * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface|\Magento\Framework\View\Result\Page
    */
   public function execute()
   {
       $pageTitle = $this->_helper->getPageTitleStoreLocator();
       $page = $this->resultPageFactory->create();
       $page->getConfig()->getTitle()->set($pageTitle);
      
       // Add breadcrumb
       /** @var \Magento\Theme\Block\Html\Breadcrumbs */
       $breadcrumbs = $page->getLayout()->getBlock('breadcrumbs');
       $breadcrumbs->addCrumb(
           'home',
           [
               'label' => __('Home'),
               'title' => __('Home'),
               'link' => $this->_url->getUrl('')
           ]
       );
       $breadcrumbs->addCrumb(
           'SyncIt_StoreLocator',
           [
               'label' => $pageTitle,
               'title' => $pageTitle
           ]
       );

       return $page;
   }
}

 

Router class should be defined in di.xml file within the routerList argument.

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
   <type name="Magento\Framework\App\RouterList">
       <arguments>
           <argument name="routerList" xsi:type="array">
               <item name="storelocator" xsi:type="array">
                   <item name="class" xsi:type="string">SyncIt\StoreLocator\Controller\Router</item>
                   <item name="disable" xsi:type="boolean">false</item>
                   <item name="sortOrder" xsi:type="string">60</item>
               </item>
           </argument>
       </arguments>
   </type>
</config>

 

Add the Router class. This class should implement the interface, and subsequently contain a match() method.

The module path of the route is checked again in the route added in the admin. If it matches, the next step would be to set the module, controller and action names of the request to customroute, index and index, respectively.

This will allow the controller class defined above to handle the route.

<?php
namespace SyncIt\StoreLocator\Controller;

use Magento\Framework\App\ActionFactory;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\App\RouterInterface;
use Magento\Framework\DataObject;
use Magento\Framework\Event\ManagerInterface as EventManagerInterface;
use Magento\Framework\Url;
use SyncIt\StoreLocator\Helper\Data as CustomRouteHelper;

class Router implements RouterInterface
{
   /**
    * @var bool
    */
   private $dispatched = false;

   /**
    * @var ActionFactory
    */
   protected $actionFactory;

   /**
    * @var EventManagerInterface
    */
   protected $eventManager;

   /**
    * @var CustomRouteHelper
    */
   protected $helper;

   /**
    * Router constructor.
    *
    * @param ActionFactory $actionFactory
    * @param EventManagerInterface $eventManager
    * @param CustomRouteHelper $helper
    */
   public function __construct(
       ActionFactory $actionFactory,
       EventManagerInterface $eventManager,
       CustomRouteHelper $helper
   ) {
       $this->actionFactory = $actionFactory;
       $this->eventManager = $eventManager;
       $this->helper = $helper;
   }

   /**
    * @param RequestInterface $request
    * @return \Magento\Framework\App\ActionInterface|null
    */
   public function match(RequestInterface $request)
   {
       /** @var \Magento\Framework\App\Request\Http $request */
       if (!$this->dispatched) {
           $identifier = trim($request->getPathInfo(), '/');
           $this->eventManager->dispatch('core_controller_router_match_before', [
               'router' => $this,
               'condition' => new DataObject(['identifier' => $identifier, 'continue' => true])
           ]);

           $identifier = urldecode($identifier);
           $route = $this->helper->getModuleRoute();

           if ($route !== '' && strpos($identifier, $route) !== false) {
               $identifierPieces = explode("/", $identifier);

               if ($identifier == $route) {
                   $request->setModuleName('storelocator')
                       ->setControllerName('index')
                       ->setActionName('index');
                   $request->setAlias(Url::REWRITE_REQUEST_PATH_ALIAS, $identifier);
                   $this->dispatched = true;
                   return $this->actionFactory->create(
                       'Magento\Framework\App\Action\Forward',
                       ['request' => $request]
                   );
               }

               $city = '';
               if (isset($identifierPieces[1])) {
                   $city = $identifierPieces[1];
               }
               //This part of the code shows how to forward an additional parameter to the controller which is triggered from the custom route.
               $request->setModuleName('storelocator')
                   ->setControllerName('index')
                   ->setActionName('view')
                   ->setParam('city', $city);
               $request->setAlias(Url::REWRITE_REQUEST_PATH_ALIAS, $identifier);
               $this->dispatched = true;

               return $this->actionFactory->create(
                   'Magento\Framework\App\Action\Forward'
               );
           }

           return null;
       }
   }
}

 

Lastly, a customroute_index_index.xml layout file will be defined in order to render a template with some content.

<?xml version="1.0" encoding="UTF-8"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  layout="1column" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd">

 <body>
<referenceContainer name="content">
   <block class="SyncIt\StoreLocator\Block\Stores" name="store.list" template="SyncIt_StoreLocator::stores.phtml">
       <block class="Magento\Theme\Block\Html\Pager" name="store.list.pager"/>
   </block>
</referenceContainer>
<block class="SyncIt\StoreLocator\Block\Stores" name="map" template="SyncIt_StoreLocator::map.phtml">
   <block class="Magento\Theme\Block\Html\Pager" name="store.list.pager"/>
</block>

 </body>
</page>
<div class="content">
    <p> <?= echo __('Here  we can render out stores'); ?> </p>
</div>

 

And that’s it! Now, whichever route you define in the admin, the Controller class will handle it and render the content from the template defined in the layout file.

Conclusion

Every eCommerce store ought to have a customized Store Locator in order to improve store management. Moreover, you will ensure your customers’ trust thanks to this user-friendly extension. Don’t miss out on the opportunity to take advantage of it and enjoy the benefits it is sure to bring.

This entry was posted in Magento 2 and tagged Web Development, SyncIt Group, Magento 2, Web, Magento 2 Development, Magento 2 Extensions, eCommerce, Store Locator, Magento 2 Functionalities, Dynamic Route, Dynamic Route Development, Magento 2 Dynamic Route, Magento 2 Community on September 24, 2019 by Igor Stajić, SyncIt Group Team Leader .