Skip to main content

ZoneField Field Type

ZoneField extends Joomla's ListField to render a <select> populated with enabled zones from #__j2commerce_zones. Its distinguishing feature is the optional country_field attribute: when provided, the field injects inline JavaScript that reloads the zone list via AJAX whenever the linked country dropdown changes. This makes it the standard building block for country/state or country/province address pairs throughout J2Commerce.

Class Reference​

PropertyValue
ClassZoneField
NamespaceJ2Commerce\Component\J2commerce\Administrator\Field
Fileadministrator/components/com_j2commerce/src/Field/ZoneField.php
ExtendsJoomla\CMS\Form\Field\ListField
Field type tokenZone
Since6.0.7

Database Query​

Initial Load (No Country Linked or No Country Selected)​

All enabled zones are returned:

SELECT j2commerce_zone_id AS value, zone_name AS text
FROM #__j2commerce_zones
WHERE enabled = 1
ORDER BY zone_name ASC

Initial Load (Country Field Set and Country Already Selected)​

The query adds a country_id filter so only the zones for that country are shown on first render:

SELECT j2commerce_zone_id AS value, zone_name AS text
FROM #__j2commerce_zones
WHERE enabled = 1
AND country_id = :country_id
ORDER BY zone_name ASC

AJAX Endpoint​

When the country dropdown changes, the JavaScript calls:

index.php?option=com_j2commerce&task=ajax.getZones&country_id={id}&zone_id={currentZoneId}

The response is an HTML <option> fragment that replaces the zone select's innerHTML.

XML Usage​

Basic Usage (No Linking)​

A standalone zone dropdown showing all enabled zones:

<field
name="zone_id"
type="Zone"
label="COM_MYPLUGIN_FIELD_ZONE_LABEL"
description="COM_MYPLUGIN_FIELD_ZONE_DESC"
/>

Linked to a Country Field (With AJAX)​

Pair with a CountryField using the country_field attribute. The value of country_field must match the name attribute of the country field in the same form:

<field
name="country_id"
type="Country"
label="COM_MYPLUGIN_FIELD_COUNTRY_LABEL"
required="true"
/>

<field
name="zone_id"
type="Zone"
country_field="country_id"
label="COM_MYPLUGIN_FIELD_ZONE_LABEL"
description="COM_MYPLUGIN_FIELD_ZONE_DESC"
/>

The country_field attribute tells ZoneField two things:

  1. On initial render: filter zones to match the form's current country value (when one is already saved).
  2. On country change: inject JavaScript that calls the AJAX endpoint and replaces the zone options.

Supported XML Attributes​

AttributeTypeDescription
namestringForm field name
labelstringLanguage key for the field label
descriptionstringLanguage key for the tooltip
country_fieldstringName of the sibling country field to link to. Enables AJAX dynamic filtering.
defaultstring/integerDefault selected zone ID
requiredbooleanWhether a value is required
classstringAdditional CSS classes on the <select> element
multiplebooleanAllow multi-select (disables AJAX linking)

AJAX Behavior​

When country_field is set, ZoneField appends an inline <script> block after the standard select markup. The script:

  1. Finds the country <select> by its computed ID ({formControl}_{group}{countryField}).
  2. Finds the zone <select> by its own field ID.
  3. On DOMContentLoaded, if a country is already selected, immediately fetches zones for that country and pre-selects the saved zone ID.
  4. On country change, sets the zone select to a loading state (disabled), fetches new zones, and re-enables it.

ID Computation​

The country field element ID is built as:

{formControl}_{group}{countryField}

Where formControl defaults to jform (standard for component forms) and group is the field group prefix if any. For a top-level field in a standard plugin params form, the country field ID would be jform_country_id.

JavaScript Pattern (Inline)​

document.addEventListener('DOMContentLoaded', function() {
const countrySelect = document.getElementById('jform_country_id');
const zoneSelect = document.getElementById('jform_zone_id');

async function loadZones(countryId, selectedZoneId = 0) {
zoneSelect.innerHTML = '<option value="">Loading...</option>';
zoneSelect.disabled = true;

if (!countryId || countryId === '0') {
zoneSelect.innerHTML = '<option value="">Select Zone</option>';
zoneSelect.disabled = false;
return;
}

const url = 'index.php?option=com_j2commerce&task=ajax.getZones'
+ '&country_id=' + countryId + '&zone_id=' + selectedZoneId;
const response = await fetch(url);
zoneSelect.innerHTML = await response.text();
zoneSelect.disabled = false;
}

countrySelect.addEventListener('change', function() {
loadZones(this.value, 0);
});

if (countrySelect.value && countrySelect.value !== '0') {
loadZones(countrySelect.value, zoneSelect.value || 0);
}
});

Usage in Plugin Forms​

<?xml version="1.0" encoding="utf-8"?>
<form addfieldprefix="J2Commerce\Component\J2commerce\Administrator\Field">

<fieldset name="basic">

<field
name="country_id"
type="Country"
label="COM_MYPLUGIN_FIELD_COUNTRY_LABEL"
default="0">
<option value="0">JOPTION_SELECT_COUNTRY</option>
</field>

<field
name="zone_id"
type="Zone"
country_field="country_id"
label="COM_MYPLUGIN_FIELD_ZONE_LABEL"
default="0">
<option value="0">JOPTION_SELECT_REGION</option>
</field>

</fieldset>

</form>

In a Plugin Manifest <config> Block​

<config>
<fields name="params" addfieldprefix="J2Commerce\Component\J2commerce\Administrator\Field">
<fieldset name="basic">
<field name="country_id" type="Country" label="COM_MYPLUGIN_FIELD_COUNTRY_LABEL" />
<field name="zone_id" type="Zone" country_field="country_id"
label="COM_MYPLUGIN_FIELD_ZONE_LABEL" />
</fieldset>
</fields>
</config>

Source Code​

// File: administrator/components/com_j2commerce/src/Field/ZoneField.php

declare(strict_types=1);

namespace J2Commerce\Component\J2commerce\Administrator\Field;

use Joomla\CMS\Factory;
use Joomla\CMS\Form\Field\ListField;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\Database\DatabaseInterface;
use Joomla\Database\ParameterType;

class ZoneField extends ListField
{
protected $type = 'Zone';

protected $countryField;

public function setup(\SimpleXMLElement $element, $value, $group = null): bool
{
$result = parent::setup($element, $value, $group);

if ($result) {
$this->countryField = (string) $this->element['country_field'];
}

return $result;
}

protected function getInput(): string
{
$html = parent::getInput();

if (!empty($this->countryField)) {
$html .= $this->getAjaxScript();
}

return $html;
}

public function getOptions(): array
{
$options = parent::getOptions();

$db = Factory::getContainer()->get(DatabaseInterface::class);
$query = $db->getQuery(true)
->select([
$db->quoteName('j2commerce_zone_id', 'value'),
$db->quoteName('zone_name', 'text')
])
->from($db->quoteName('#__j2commerce_zones'))
->where($db->quoteName('enabled') . ' = 1')
->order($db->quoteName('zone_name') . ' ASC');

// Server-side filter on initial render if a country is already selected
if (!empty($this->countryField) && $this->form) {
$countryValue = $this->form->getValue($this->countryField);

if (!empty($countryValue) && is_numeric($countryValue)) {
$countryId = (int) $countryValue;
$query->where($db->quoteName('country_id') . ' = :country_id')
->bind(':country_id', $countryId, ParameterType::INTEGER);
}
}

$db->setQuery($query);

foreach ($db->loadObjectList() as $zone) {
$options[] = HTMLHelper::_('select.option', $zone->value, $zone->text);
}

return $options;
}
}

Notes​

  • When country_field is omitted, the field behaves as a plain list of all enabled zones with no AJAX behavior.
  • The AJAX endpoint ajax.getZones is provided by the J2Commerce component's AJAX controller and returns <option> HTML fragments.
  • If your form uses a non-standard formControl or field groups, verify the computed country field element ID matches what getAjaxScript() produces.
  • ZoneField stores the integer j2commerce_zone_id as its value.
  • CountryField — Country dropdown, typically paired with ZoneField
  • GeoZoneField — Geo-zone dropdown for tax/shipping rules