Loaders and loading state

When a LokiComponent is busy with the AJAX call (storing its value in Magento and/or updating HTML elements on the page), the Alpine component allows you to hook into this loading state in various ways.

The Alpine property loading

Each LokiComponent ships with an Alpine property loading which is false by default, but which is set to true while the POST request is running. Elsewhere, the property loading is watched for further functionality. However, you could already show a simple loading text, based on this:

<span x-show="loading">Loading ...</span>

A fancier example could be the following:

<div x-cloak x-show="loading" class="<?= $css('inline-block mr-3 w-4 animate-spin fill-white', 'icon') ?>">
    <?= $imageOutput->get('Yireo_LokiFieldComponents::images/spinner.svg') ?>
</div>

Setting the aria-busy attribute

A single LokiComponent update could also involve a refresh of other HTML attributes on the page. While making the AJAX call, each target is resolved into the corresponding HTML element and the aria-busy HTML attribute is set. This also counts for the component itself.

If the HTML element contains a child element that shows some kind of loading image, then this child element could be hidden by default but shown again via CSS based upon this aria-busy attribute. A lot of LokiCheckout components use this logic as follows:

<div class="<?= $css('relative') ?>">
    <?= $blockRenderer->html($block,'loki-field-components.utils.loader-overlay') ?>
    ...
</div>

Here the <div> element equals the root of the Alpine component, where aria-busy is set. Next, the block loki-field-components.utils.loader-overlay is used to display an image loader. By default, this block has the display:none CSS rule set (via the Tailwind class hidden).

With the corresponding CSS the loader overlay is shown conditionally:

 [aria-busy] > .loader-overlay {
    display: flex;
}

Setting the loading property in other Alpine components

The post() method tries to map each target name into another LokiComponent on the page. And if that LokiComponent is found, the loading property of that LokiComponent is toggled as well.

This causes the possibility that a single component update triggers multiple loaders on the page.

Delayed loader with showLoader

Above it was already explained how a loader overlay was shown. With many field components, a smaller loader image is shown within the field input itself:

<div x-cloak x-show="showLoader" class="<?= $css('field-loader flex absolute inset-y-0 right-0 pt-1 pr-2', 'loader') ?>">
      <?= $blockRenderer->html($block, 'loki-field-components.utils.loader-small') ?>
</div>

Apart from different Tailwind classes, it is the same logic. Except for that a different Alpine property showLoader is used. While the loading property is set once the AJAX call is initiated, the showLoader property toggles only after a slight delay. This delay is set via the property showLoaderTimeout. (Note that you can change any property of a LokiComponent via the getJsData() method of its ViewModel and with that, the XML layout.)

The reason for this is an esthetic reason: In a production environment, often component updates are fast and take little more than 300ms. If every single change would popup that loader for just a split second, it will confuse users. Because of this, the delayed showLoader approach allows for showing the loader only, if the user really needs to be informed of the fact that the update is not instant.

The default showLoaderTimeout is 700ms, which should make for a visually appealing updating experience.

The CSS class loading for fields

A complementary way of styling the loading state of fields is by using the CSS class loading. This is added to all components that are derived from the LokiFieldComponent component data (or actually derived from the LokiFieldComponentType component type).

A simplified version of such a component might look like the following:

<div x-data="LokiFieldComponent" x-title="FooBar">
    <script ref="initialData" type="text/x-loki-init">{}</script>
    <input x-model="value" @change="submit" x-ref="field" class="foobar" />
</div>

Here, the <div> element serves as the root of an Alpine component FooBar which uses the LokiFieldComponent component data. The <input> element is connected to the Alpine property value via x-model. And every time the value is changed, it is posted to the server by using submit().

Now note the x-ref. Within the component logic, this is used by a watcher on the Alpine property loading. Whenever the loading value is true, the CSS class loading is added. Whenever the loading value is false, the CSS class loading is removed again.

Additionally, the aria-busy attribute and disabled attribute are also added to the field.

The resulting HTML while loading might look like the following:

<div x-data="LokiFieldComponent" x-title="FooBar" aria-busy>
    <script ref="initialData" type="text/x-loki-init">{}</script>
    <input x-model="value" @change="submit" x-ref="field" class="foobar loading" aria-busy disabled />
</div>
Last modified: April 30, 2025