Watchfield in Joomla Form: WT CDEK field dependencies
Watchfield in WT CDEK for Joomla covers field bindings, XML, AJAX and Script Options. This reference explains its purpose, parameters and XML examples for Joomla Form.
Watchfield in WT CDEK 1.4.0 connects an existing Joomla Form field to one or more parent fields. When sources change, it retrieves data and notifies the field renderer. Tariffinfo and Deliverypoints use this mechanism.
Set watchfield on the dependent field. The library does not provide type="watchfield". The PHP helper Webtolk\Cdekapi\Helper\Watchfield prepares configuration; WebAsset wtcdek.watchfield observes sources; each field's renderer displays a card or updates a selector. showon controls visibility; watchfield controls value dependencies and data loading.
One source: tariff and details card
<form addfieldprefix="Webtolk\Cdekapi\Fields">
<fieldset name="tariffs">
<field name="tariff_code" type="Tarifflist" label="Tariff" />
<field name="tariff_info" type="Tariffinfo"
label="Tariff details" watchfield="tariff_code" />
</fieldset>
</form>
watchfield="tariff_code" observes the XML field named tariff_code in the current group. The shared handler turns a scalar binding into {"value":"tariff_code"}. Tariffinfo understands the value key; AJAX sends this source as a POST parameter named value.
Several sources: city and postal code
<form addfieldprefix="Webtolk\Cdekapi\Fields">
<fieldset name="address">
<field name="city" type="text" label="City" />
<field name="postal_code" type="text" label="Postal code" />
<field name="point" type="Deliverypoints" label="CDEK pickup point"
watchfield='{"city":"city","zip":"postal_code"}' />
</fieldset>
</form>
Map keys are consumer parameters; map values are XML source field names. Deliverypoints allows city, zip and fias_guid; zip is sent to the server as postal_code. For example, watchfield='{"fias_guid":"settlement_fias"}' watches the settlement_fias field, which must exist in the form. Deliverypoints does not support scalar watchfield.
Use a named JSON object with nonempty string keys and field names. A JSON array such as ["city","zip"] is invalid. Single quotes around the XML attribute let you copy JSON with regular double quotes.
Groups, forms and repeatable rows
An unqualified source name resolves within the current Joomla Form group. address.city references city in fields name="address". For a nested group, use e.g. settings.address.city: the last segment is the field name; preceding segments identify the group. A fieldset name alone does not create a data group.
Supply an XML name, not an HTML ID or jform[params][city]. The helper obtains the full input name from Form, so arbitrary control prefixes work. In the browser, lookup stays within the nearest HTML form.
Native repeatable subforms bind source names to the current row index through data-base-name and data-group. Added target containers are discovered automatically; removed targets release subscriptions and abort requests. After custom markup insertion, call Joomla.Watchfield.scan() or dispatch Joomla's joomla:updated event.
Update sequence
- PHP resolves source fields through
Form::getField()and adds JSON attributesdata-watchfieldanddata-watchfield-optionsto the target container. - After DOM readiness, the handler binds sources and performs the initial data load.
inputandchangecreate a new snapshot. Leading/trailing whitespace is trimmed; internal spaces remain.- A changed snapshot immediately emits
watchfield:invalidate. For AJAX, input uses a 250 ms debounce; change updates without that delay. Script Options have no 250 ms debounce. - The previous request is aborted; a late response cannot replace newer data. The same snapshot does not trigger another successful load.
- The renderer receives data or an error state and updates its output.
Radio returns the selected value; multiple selects and checkbox groups return arrays. The built-in CDEK examples use scalar address values and tariff codes.
Changing a text input programmatically
Assigning .value does not generate a browser event. Dispatch change after selecting a city in your own autocomplete. Use your source's actual full HTML name:
const city = document.querySelector('input[name="jform[city]"]');
if (city) {
city.value = 'Саратов';
city.dispatchEvent(new Event('change', { bubbles: true }));
}
Input covers typing, paste and deletion. For a text field, change normally occurs on blur after editing. When updating several sources programmatically, set their values before dispatching notification events.
Provider attributes
| XML attribute | Meaning |
|---|---|
watchfield-data-source |
scriptoptions: page data; onpage: alias; ajax: POST request. |
watchfield-data-options |
Literal Joomla Script Options key, e.g. shop.cdek. |
watchfield-data-path |
Dotted path within the returned data; empty means the whole object. Numeric segments address arrays: data.0.options. |
watchfield-data-url |
AJAX endpoint on the same origin: scheme, host and port must match. |
Tariffinfo defaults to scriptoptions, the wtcdek.tariffs key and an empty path. Its catalog is prepared server-side through the API/cache. Deliverypoints defaults to ajax, the standard com_ajax URL and data.0.options. Switching source modes without an explicit path resets the path to an empty string. When changing only the URL in the same mode, set the path appropriate to your response format.
Page data: Script Options
Add the catalog in your extension PHP code before the document is output:
use Joomla\CMS\Factory;
Factory::getApplication()->getDocument()->addScriptOptions('shop.cdek', [
'catalog' => [
'136' => [
'tariff_code' => '136',
'tariff_name' => 'Example tariff',
'delivery_mode_name' => 'Warehouse to warehouse',
],
],
]);
Then use this form:
<form addfieldprefix="Webtolk\Cdekapi\Fields">
<fieldset name="tariffs">
<field name="tariff_code" type="list" label="Tariff" default="136">
<option value="">Select a tariff</option>
<option value="136">Example tariff</option>
</field>
<field name="tariff_info" type="Tariffinfo" label="Details"
watchfield="tariff_code"
watchfield-data-source="scriptoptions"
watchfield-data-options="shop.cdek"
watchfield-data-path="catalog" />
</fieldset>
</form>
watchfield-data-options="shop.cdek" calls Joomla.getOptions('shop.cdek'). Dots in the options key do not create a path. watchfield-data-path="catalog" selects the catalog property inside that object. Missing Tariffinfo card properties display as empty. Tariffinfo still prepares its standard catalog through the API/cache during rendering, even with a custom client-side source.
Pickup points from page data
use Joomla\CMS\Factory;
Factory::getApplication()->getDocument()->addScriptOptions('shop.delivery', [
'points' => [
['value' => 'SAR141', 'text' => 'Example pickup point'],
],
]);
<field name="point" type="Deliverypoints"
addfieldprefix="Webtolk\Cdekapi\Fields"
label="CDEK pickup point" city="Саратов"
watchfield-data-source="scriptoptions"
watchfield-data-options="shop.delivery"
watchfield-data-path="points" />
This is demonstration data, not current information about a real point. Deliverypoints expects an array of objects with string value codes and string text labels. Script Options reads the specified array without filtering by city or point type. Apply such filters when preparing your data. The city attribute explicitly selects fixed-address mode, so watchfield is not required here.
A custom AJAX endpoint
<form addfieldprefix="Webtolk\Cdekapi\Fields">
<fieldset name="tariffs">
<field name="tariff_code" type="Tarifflist" label="Tariff" />
<field name="tariff_info" type="Tariffinfo" label="Details"
watchfield='{"tariff_code":"tariff_code"}'
watchfield-data-source="ajax"
watchfield-data-url="index.php?option=com_ajax&group=system&plugin=yourplugin&format=json"
watchfield-data-path="data.0.catalog" />
</fieldset>
</form>
yourplugin is your system plugin name: replace it and implement the handler. WT CDEK does not provide an endpoint with this placeholder name. The named map sends tariff_code; a scalar binding would send value.
Example JSON response for data.0.catalog:
{
"success": true,
"data": [
{
"catalog": {
"136": {
"tariff_code": "136",
"tariff_name": "Example tariff",
"delivery_mode_name": "Warehouse to warehouse"
}
}
}
]
}
The handler uses POST with same-origin credentials, source snapshots, static field parameters and the current token from Joomla.getOptions('csrf.token'). The body is form-urlencoded; arrays use name[]. Your endpoint must validate the CSRF token, access permissions and input values. Do not put a token in XML or source code.
Default Deliverypoints parameters are action=deliverypoints, city, postal_code, fias_guid, type, is_reception and is_handout. For a custom provider, specify its URL and path to the options array; standard com_ajax uses data.0.options. The client sends configured parameters and watched values, not the entire Joomla Form.
An HTTP error, success: false, invalid JSON or a missing data path produces an error state. Default Deliverypoints AJAX is skipped when city, postal_code and fias_guid are all empty; an empty event is emitted instead. Script Options requires no network request.
Events and your own renderer
| Event | When emitted | Additional detail |
|---|---|---|
watchfield:invalidate |
Source snapshot changed, before loading/debounce. | initial: false |
watchfield:loading |
Before AJAX. Not emitted for Script Options. | — |
watchfield:data |
Data loaded and path applied. | data |
watchfield:error |
Source, transport or data error. | message |
watchfield:empty |
All AJAX requiredAny values are empty. | — |
Events bubble from the target container. Normal state events include values, the source snapshot, and initial. A JSON-attribute parse error before state creation includes only message. Initial means the first load attempt, not necessarily the first successful response. Each field renderer validates its data format.
To build a custom consumer, use the helper with the owning Joomla Form instance in $form:
use Joomla\CMS\Factory;
use Webtolk\Cdekapi\Helper\Watchfield;
// $form is the owning Joomla Form instance.
$document = Factory::getApplication()->getDocument();
$wa = $document->getWebAssetManager();
$wa->getRegistry()->addRegistryFile('media/lib_wtcdek/joomla.assets.json');
$wa->useScript('wtcdek.watchfield');
$document->addScriptOptions('shop.cdek', ['catalog' => []]);
$sources = Watchfield::resolve($form, '{"city":"city"}', 'address');
$attributes = Watchfield::attributes($sources, [
'source' => 'scriptoptions',
'options' => 'shop.cdek',
'path' => 'catalog',
]);
// Render $attributes on your .my-cdek-field container.
Render $attributes on your .my-cdek-field container. Register its renderer as a WebAsset depending on wtcdek.watchfield, so event listeners exist before initial loading:
document.addEventListener('watchfield:data', (event) => {
if (!event.target.matches('.my-cdek-field')) return;
const { values, data, initial } = event.detail;
// Render data for this container; values contains the source snapshot.
});
Version 1.4.0 boundaries
scan()discovers new containers and rebinds source nodes. It does not reread changed data attributes on an existing registered container and is not an unconditional refresh. Replace the target container to apply different configuration.- Changing Script Options with unchanged source values does not guarantee rendering: snapshots are deduplicated. Provide your own refresh/new container for dynamic datasets.
- A scan after an error can retry the same address. In 1.4.0 that retry may clear the saved pickup point; automatic selection preservation on retry is not guaranteed.
- Deliverypoints emits
wtcdek:options-updatedafter programmatically replacing options. Watchfield listens to input/change, so a longer dependency chain through an automatically cleared point needs separate integration. Notify downstream consumers with change when the value actually changes, and avoid cycles. - Old layout overrides using
data-watch-field/data-tariffsmust adopt the current standard layout contract. Existing Tariffinfo XML watchfield and the tariff_field alias remain supported.