feat: add site frontend with locations list, detail view, and SEF router (Phase 2)
Build the complete public-facing frontend for the store locator: - Site DisplayController routing to list and detail views - LocationsModel with search, city, and state filters (published only) - LocationModel for single location by ID - Locations list template with Schema.org LocalBusiness markup - Location detail template with address, contact, hours, map placeholder - SEF URL router with menu/standard/nomenu rules - Menu item types: "All Locations" list and "Location Detail" picker - Site language strings for views and menu items - Router wired into component extension class and service provider - .gitignore exception for source/packages/*/site/ Addresses #50 Authored-by: Moko Consulting
This commit is contained in:
@@ -114,6 +114,7 @@ build/
|
||||
dist/
|
||||
out/
|
||||
site/
|
||||
!source/packages/*/site/
|
||||
*.map
|
||||
*.css.map
|
||||
*.js.map
|
||||
|
||||
@@ -18,6 +18,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Published state filter and sort ordering support
|
||||
- Filter form XML (`filter_locations.xml`) with search tools bar
|
||||
- Language strings for filters, sort options, and save messages
|
||||
- Site frontend `DisplayController` routing to list and detail views
|
||||
- Site `LocationsModel` — published locations with search, city, and state filters
|
||||
- Site `LocationModel` — single location by ID (published only)
|
||||
- Site locations list view with Schema.org `LocalBusiness` markup and pagination
|
||||
- Site location detail view with address, contact, hours, and map placeholder
|
||||
- SEF URL router (`Service\Router`) with menu/standard/nomenu rules
|
||||
- Menu item types: "All Locations" list and "Location Detail" with location picker
|
||||
- Site language strings for frontend views and menu items
|
||||
- Router registered in service provider and component extension class
|
||||
|
||||
### Removed
|
||||
- Makefile (no longer used)
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Component\Router\RouterFactoryInterface;
|
||||
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
|
||||
use Joomla\CMS\Extension\ComponentInterface;
|
||||
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
|
||||
use Joomla\CMS\Extension\Service\Provider\MVCFactory;
|
||||
use Joomla\CMS\Extension\Service\Provider\RouterFactory;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
@@ -37,6 +39,7 @@ return new class implements ServiceProviderInterface
|
||||
{
|
||||
$container->registerServiceProvider(new MVCFactory('\\Moko\\Component\\MokoSuiteStoreLocator'));
|
||||
$container->registerServiceProvider(new ComponentDispatcherFactory('\\Moko\\Component\\MokoSuiteStoreLocator'));
|
||||
$container->registerServiceProvider(new RouterFactory('\\Moko\\Component\\MokoSuiteStoreLocator'));
|
||||
|
||||
$container->set(
|
||||
ComponentInterface::class,
|
||||
@@ -45,6 +48,7 @@ return new class implements ServiceProviderInterface
|
||||
$container->get(ComponentDispatcherFactoryInterface::class)
|
||||
);
|
||||
$component->setMVCFactory($container->get(MVCFactoryInterface::class));
|
||||
$component->setRouterFactory($container->get(RouterFactoryInterface::class));
|
||||
|
||||
return $component;
|
||||
}
|
||||
|
||||
+4
-2
@@ -10,6 +10,8 @@ namespace Moko\Component\MokoSuiteStoreLocator\Administrator\Extension;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Component\Router\RouterServiceInterface;
|
||||
use Joomla\CMS\Component\Router\RouterServiceTrait;
|
||||
use Joomla\CMS\Extension\MVCComponent;
|
||||
|
||||
/**
|
||||
@@ -17,7 +19,7 @@ use Joomla\CMS\Extension\MVCComponent;
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class MokoSuiteStoreLocatorComponent extends MVCComponent
|
||||
class MokoSuiteStoreLocatorComponent extends MVCComponent implements RouterServiceInterface
|
||||
{
|
||||
// TODO: Add boot(), getRouterRules(), or custom services as needed
|
||||
use RouterServiceTrait;
|
||||
}
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
; MokoSuiteStoreLocator - Site language strings
|
||||
; Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
; License: GNU General Public License version 3 or later; see LICENSE
|
||||
|
||||
COM_MOKOJOOMSTORELOCATOR="Store Locator"
|
||||
COM_MOKOJOOMSTORELOCATOR_LOCATIONS="Locations"
|
||||
COM_MOKOJOOMSTORELOCATOR_NO_LOCATIONS="No locations found."
|
||||
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELDSET_ADDRESS="Address"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELDSET_CONTACT="Contact Information"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_PHONE="Phone"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_WEBSITE="Website"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_HOURS="Business Hours"
|
||||
|
||||
COM_MOKOJOOMSTORELOCATOR_LOCATIONS_VIEW_DEFAULT_TITLE="All Locations"
|
||||
COM_MOKOJOOMSTORELOCATOR_LOCATIONS_VIEW_DEFAULT_DESC="Displays a list of all store locations."
|
||||
COM_MOKOJOOMSTORELOCATOR_LOCATION_VIEW_DEFAULT_TITLE="Location Detail"
|
||||
COM_MOKOJOOMSTORELOCATOR_LOCATION_VIEW_DEFAULT_DESC="Displays a single store location with full details."
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_LOCATION="Select Location"
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Site\Controller;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\Controller\BaseController;
|
||||
|
||||
/**
|
||||
* Default site controller.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class DisplayController extends BaseController
|
||||
{
|
||||
/**
|
||||
* The default view.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $default_view = 'locations';
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Site\Model;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\Model\ItemModel;
|
||||
use Joomla\Database\ParameterType;
|
||||
|
||||
/**
|
||||
* Single location model for the site frontend.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class LocationModel extends ItemModel
|
||||
{
|
||||
/**
|
||||
* The location item.
|
||||
*
|
||||
* @var object|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $_item = null;
|
||||
|
||||
/**
|
||||
* Get a single location item.
|
||||
*
|
||||
* @param integer $pk The item primary key. If null, uses the model state.
|
||||
*
|
||||
* @return object|null The location object or null if not found.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function getItem($pk = null)
|
||||
{
|
||||
$pk = $pk ?: (int) $this->getState('location.id');
|
||||
|
||||
if ($this->_item === null)
|
||||
{
|
||||
$this->_item = [];
|
||||
}
|
||||
|
||||
if (!isset($this->_item[$pk]))
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true);
|
||||
|
||||
$query->select('a.*')
|
||||
->from($db->quoteName('#__mokosuitestorelocator_locations', 'a'))
|
||||
->where($db->quoteName('a.id') . ' = :pk')
|
||||
->where($db->quoteName('a.published') . ' = 1')
|
||||
->bind(':pk', $pk, ParameterType::INTEGER);
|
||||
|
||||
$db->setQuery($query);
|
||||
$this->_item[$pk] = $db->loadObject();
|
||||
}
|
||||
|
||||
return $this->_item[$pk] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the model state.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function populateState()
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
|
||||
$id = $app->input->getInt('id', 0);
|
||||
$this->setState('location.id', $id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Site\Model;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\Model\ListModel;
|
||||
use Joomla\Database\ParameterType;
|
||||
use Joomla\Database\QueryInterface;
|
||||
|
||||
/**
|
||||
* Locations list model for the site frontend.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class LocationsModel extends ListModel
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $config Configuration settings.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct($config = [])
|
||||
{
|
||||
if (empty($config['filter_fields']))
|
||||
{
|
||||
$config['filter_fields'] = [
|
||||
'id', 'a.id',
|
||||
'title', 'a.title',
|
||||
'city', 'a.city',
|
||||
'state', 'a.state',
|
||||
'ordering', 'a.ordering',
|
||||
];
|
||||
}
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the model state.
|
||||
*
|
||||
* @param string $ordering Default ordering column.
|
||||
* @param string $direction Default ordering direction.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function populateState($ordering = 'a.ordering', $direction = 'ASC')
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
|
||||
$search = $app->input->getString('search', '');
|
||||
$this->setState('filter.search', $search);
|
||||
|
||||
$city = $app->input->getString('city', '');
|
||||
$this->setState('filter.city', $city);
|
||||
|
||||
$state = $app->input->getString('state', '');
|
||||
$this->setState('filter.state', $state);
|
||||
|
||||
parent::populateState($ordering, $direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the query for the locations list.
|
||||
*
|
||||
* @return QueryInterface
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function getListQuery(): QueryInterface
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true);
|
||||
|
||||
$query->select('a.*')
|
||||
->from($db->quoteName('#__mokosuitestorelocator_locations', 'a'))
|
||||
->where($db->quoteName('a.published') . ' = 1');
|
||||
|
||||
// Search filter
|
||||
$search = $this->getState('filter.search');
|
||||
|
||||
if (!empty($search))
|
||||
{
|
||||
$search = '%' . trim($search) . '%';
|
||||
$query->where(
|
||||
'(' . $db->quoteName('a.title') . ' LIKE :search'
|
||||
. ' OR ' . $db->quoteName('a.city') . ' LIKE :search2'
|
||||
. ' OR ' . $db->quoteName('a.state') . ' LIKE :search3'
|
||||
. ' OR ' . $db->quoteName('a.address') . ' LIKE :search4)'
|
||||
)
|
||||
->bind(':search', $search)
|
||||
->bind(':search2', $search)
|
||||
->bind(':search3', $search)
|
||||
->bind(':search4', $search);
|
||||
}
|
||||
|
||||
// City filter
|
||||
$city = $this->getState('filter.city');
|
||||
|
||||
if (!empty($city))
|
||||
{
|
||||
$query->where($db->quoteName('a.city') . ' = :city')
|
||||
->bind(':city', $city);
|
||||
}
|
||||
|
||||
// State filter
|
||||
$state = $this->getState('filter.state');
|
||||
|
||||
if (!empty($state))
|
||||
{
|
||||
$query->where($db->quoteName('a.state') . ' = :state')
|
||||
->bind(':state', $state);
|
||||
}
|
||||
|
||||
// Ordering
|
||||
$orderCol = $this->state->get('list.ordering', 'a.ordering');
|
||||
$orderDir = $this->state->get('list.direction', 'ASC');
|
||||
$query->order($db->escape($orderCol) . ' ' . $db->escape($orderDir));
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Site\Service;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Application\SiteApplication;
|
||||
use Joomla\CMS\Component\Router\RouterView;
|
||||
use Joomla\CMS\Component\Router\RouterViewConfiguration;
|
||||
use Joomla\CMS\Component\Router\Rules\MenuRules;
|
||||
use Joomla\CMS\Component\Router\Rules\NomenuRules;
|
||||
use Joomla\CMS\Component\Router\Rules\StandardRules;
|
||||
use Joomla\CMS\Menu\AbstractMenu;
|
||||
|
||||
/**
|
||||
* SEF URL router for the store locator component.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Router extends RouterView
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param SiteApplication $app The application object.
|
||||
* @param AbstractMenu $menu The menu object.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct(SiteApplication $app, AbstractMenu $menu)
|
||||
{
|
||||
$locations = new RouterViewConfiguration('locations');
|
||||
$this->registerView($locations);
|
||||
|
||||
$location = new RouterViewConfiguration('location');
|
||||
$location->setKey('id')->setParent($locations);
|
||||
$this->registerView($location);
|
||||
|
||||
parent::__construct($app, $menu);
|
||||
|
||||
$this->attachRule(new MenuRules($this));
|
||||
$this->attachRule(new StandardRules($this));
|
||||
$this->attachRule(new NomenuRules($this));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Site\View\Location;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
|
||||
/**
|
||||
* Single location detail view for the site frontend.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
/**
|
||||
* @var object|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $item;
|
||||
|
||||
/**
|
||||
* Display the view.
|
||||
*
|
||||
* @param string $tpl The template name.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
$this->item = $this->get('Item');
|
||||
|
||||
if ($this->item === null)
|
||||
{
|
||||
throw new \Exception('Location not found', 404);
|
||||
}
|
||||
|
||||
parent::display($tpl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Site\View\Locations;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
|
||||
/**
|
||||
* Locations list view for the site frontend.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $items;
|
||||
|
||||
/**
|
||||
* @var \Joomla\CMS\Pagination\Pagination
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $pagination;
|
||||
|
||||
/**
|
||||
* @var \Joomla\Registry\Registry
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Display the view.
|
||||
*
|
||||
* @param string $tpl The template name.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
$this->items = $this->get('Items');
|
||||
$this->pagination = $this->get('Pagination');
|
||||
$this->state = $this->get('State');
|
||||
|
||||
parent::display($tpl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
/** @var \Moko\Component\MokoSuiteStoreLocator\Site\View\Location\HtmlView $this */
|
||||
|
||||
$item = $this->item;
|
||||
?>
|
||||
<div class="com-mokosuitestorelocator-location" itemscope itemtype="https://schema.org/LocalBusiness">
|
||||
<h2 itemprop="name"><?php echo $this->escape($item->title); ?></h2>
|
||||
|
||||
<?php if ($item->image) : ?>
|
||||
<div class="com-mokosuitestorelocator-location__image">
|
||||
<img src="<?php echo $this->escape($item->image); ?>"
|
||||
alt="<?php echo $this->escape($item->title); ?>"
|
||||
itemprop="image">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($item->description) : ?>
|
||||
<div class="com-mokosuitestorelocator-location__description" itemprop="description">
|
||||
<?php echo $item->description; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="com-mokosuitestorelocator-location__details">
|
||||
<div class="com-mokosuitestorelocator-location__address" itemprop="address" itemscope itemtype="https://schema.org/PostalAddress">
|
||||
<h3><?php echo Text::_('COM_MOKOJOOMSTORELOCATOR_FIELDSET_ADDRESS'); ?></h3>
|
||||
<?php if ($item->address) : ?>
|
||||
<span itemprop="streetAddress"><?php echo $this->escape($item->address); ?></span><br>
|
||||
<?php endif; ?>
|
||||
<?php if ($item->city) : ?>
|
||||
<span itemprop="addressLocality"><?php echo $this->escape($item->city); ?></span>,
|
||||
<?php endif; ?>
|
||||
<?php if ($item->state) : ?>
|
||||
<span itemprop="addressRegion"><?php echo $this->escape($item->state); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ($item->postcode) : ?>
|
||||
<span itemprop="postalCode"><?php echo $this->escape($item->postcode); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ($item->country) : ?>
|
||||
<br><span itemprop="addressCountry"><?php echo $this->escape($item->country); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="com-mokosuitestorelocator-location__contact">
|
||||
<h3><?php echo Text::_('COM_MOKOJOOMSTORELOCATOR_FIELDSET_CONTACT'); ?></h3>
|
||||
<?php if ($item->phone) : ?>
|
||||
<div>
|
||||
<strong><?php echo Text::_('COM_MOKOJOOMSTORELOCATOR_FIELD_PHONE'); ?>:</strong>
|
||||
<a href="tel:<?php echo $this->escape($item->phone); ?>" itemprop="telephone">
|
||||
<?php echo $this->escape($item->phone); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($item->email) : ?>
|
||||
<div>
|
||||
<strong><?php echo Text::_('JGLOBAL_EMAIL'); ?>:</strong>
|
||||
<a href="mailto:<?php echo $this->escape($item->email); ?>" itemprop="email">
|
||||
<?php echo $this->escape($item->email); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($item->website) : ?>
|
||||
<div>
|
||||
<strong><?php echo Text::_('COM_MOKOJOOMSTORELOCATOR_FIELD_WEBSITE'); ?>:</strong>
|
||||
<a href="<?php echo $this->escape($item->website); ?>" itemprop="url" target="_blank" rel="noopener">
|
||||
<?php echo $this->escape($item->website); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($item->hours) : ?>
|
||||
<div class="com-mokosuitestorelocator-location__hours">
|
||||
<h3><?php echo Text::_('COM_MOKOJOOMSTORELOCATOR_FIELD_HOURS'); ?></h3>
|
||||
<div itemprop="openingHours">
|
||||
<?php echo nl2br($this->escape($item->hours)); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($item->latitude && $item->longitude) : ?>
|
||||
<meta itemprop="latitude" content="<?php echo $this->escape($item->latitude); ?>">
|
||||
<meta itemprop="longitude" content="<?php echo $this->escape($item->longitude); ?>">
|
||||
<div class="com-mokosuitestorelocator-location__map"
|
||||
data-lat="<?php echo $this->escape($item->latitude); ?>"
|
||||
data-lng="<?php echo $this->escape($item->longitude); ?>"
|
||||
data-title="<?php echo $this->escape($item->title); ?>"
|
||||
style="height: 300px;">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<metadata>
|
||||
<layout title="COM_MOKOJOOMSTORELOCATOR_LOCATION_VIEW_DEFAULT_TITLE"
|
||||
option="COM_MOKOJOOMSTORELOCATOR_LOCATION_VIEW_DEFAULT_DESC">
|
||||
<message>
|
||||
<![CDATA[COM_MOKOJOOMSTORELOCATOR_LOCATION_VIEW_DEFAULT_DESC]]>
|
||||
</message>
|
||||
</layout>
|
||||
<fields name="request">
|
||||
<fieldset name="request">
|
||||
<field
|
||||
name="id"
|
||||
type="sql"
|
||||
label="COM_MOKOJOOMSTORELOCATOR_FIELD_LOCATION"
|
||||
query="SELECT id, title FROM #__mokosuitestorelocator_locations WHERE published = 1 ORDER BY title"
|
||||
key_field="id"
|
||||
value_field="title"
|
||||
required="true"
|
||||
/>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</metadata>
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
/** @var \Moko\Component\MokoSuiteStoreLocator\Site\View\Locations\HtmlView $this */
|
||||
?>
|
||||
<div class="com-mokosuitestorelocator-locations">
|
||||
<h2><?php echo Text::_('COM_MOKOJOOMSTORELOCATOR_LOCATIONS'); ?></h2>
|
||||
|
||||
<?php if (empty($this->items)) : ?>
|
||||
<p><?php echo Text::_('COM_MOKOJOOMSTORELOCATOR_NO_LOCATIONS'); ?></p>
|
||||
<?php else : ?>
|
||||
<div class="com-mokosuitestorelocator-locations__list">
|
||||
<?php foreach ($this->items as $item) : ?>
|
||||
<div class="com-mokosuitestorelocator-location-card" itemscope itemtype="https://schema.org/LocalBusiness">
|
||||
<?php if ($item->image) : ?>
|
||||
<div class="com-mokosuitestorelocator-location-card__image">
|
||||
<img src="<?php echo $this->escape($item->image); ?>"
|
||||
alt="<?php echo $this->escape($item->title); ?>"
|
||||
itemprop="image" loading="lazy">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="com-mokosuitestorelocator-location-card__body">
|
||||
<h3 itemprop="name">
|
||||
<a href="<?php echo Route::_('index.php?option=com_mokosuitestorelocator&view=location&id=' . (int) $item->id); ?>">
|
||||
<?php echo $this->escape($item->title); ?>
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<div itemprop="address" itemscope itemtype="https://schema.org/PostalAddress">
|
||||
<?php if ($item->address) : ?>
|
||||
<span itemprop="streetAddress"><?php echo $this->escape($item->address); ?></span><br>
|
||||
<?php endif; ?>
|
||||
<?php if ($item->city) : ?>
|
||||
<span itemprop="addressLocality"><?php echo $this->escape($item->city); ?></span>,
|
||||
<?php endif; ?>
|
||||
<?php if ($item->state) : ?>
|
||||
<span itemprop="addressRegion"><?php echo $this->escape($item->state); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ($item->postcode) : ?>
|
||||
<span itemprop="postalCode"><?php echo $this->escape($item->postcode); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($item->phone) : ?>
|
||||
<div class="com-mokosuitestorelocator-location-card__phone">
|
||||
<a href="tel:<?php echo $this->escape($item->phone); ?>" itemprop="telephone">
|
||||
<?php echo $this->escape($item->phone); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php echo $this->pagination->getListFooter(); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<metadata>
|
||||
<layout title="COM_MOKOJOOMSTORELOCATOR_LOCATIONS_VIEW_DEFAULT_TITLE"
|
||||
option="COM_MOKOJOOMSTORELOCATOR_LOCATIONS_VIEW_DEFAULT_DESC">
|
||||
<message>
|
||||
<![CDATA[COM_MOKOJOOMSTORELOCATOR_LOCATIONS_VIEW_DEFAULT_DESC]]>
|
||||
</message>
|
||||
</layout>
|
||||
</metadata>
|
||||
Reference in New Issue
Block a user