Component definitions

Within the Loki architecture, we are not just simply referring to Alpine components. Instead, we talk about components, component data definitions, component types and component partials.

Because Loki focuses heavily upon the reusability of code, how Alpine components come to being depends upon 4 things:

  • Components are regular Alpine components, managed by Alpine.js, including two-way binding with the HTML document;
  • Components are based upon component data: The thing that you refer to with x-data. With Loki, component data is always defined with Alpine.data() and the definitions are placed preferably in the XML layout container loki-components (which places them at the bottom of the page).
  • Component data is constructed via regular JavaScript objects. With Loki, those objects are separated again from the component data definition itself, as a means to reuse JavaScript objects in multiple component data definitions.
  • Last but not least, such a JavaScript object could be just a plain object, but it could also be composed of other objects using ES6 destructuring (...foobar). Whenever this happens, the destructured object that is imported into the regular object is referred to as a component partial. Component partials are comparable to mixins or traits.

An example: LokiCheckoutSteps

As an example, there is a checkout component named LokiCheckoutSteps (which resembles the component property id and the x-title attribute. It is initiated with x-data="LokiFieldComponent" which shows that there is a component data definition LokiFieldComponent somewhere on the page as well.

<div id="loki-checkout-steps" x-data="LokiFieldComponent" x-title="LokiCheckoutSteps">
    <script x-ref="initialData" type="text/x-loki-init">
        {"id": "LokiCheckoutSteps", "name": "LokiFieldComponent", /* ... */}
    </script>
</div>

The general data definition LokiFieldComponent is used in numerous components and defined with the following:

document.addEventListener('alpine:init', () => {
    Alpine.data('LokiFieldComponent', () => ({
        ...LokiFieldComponentType,
    }));
});

As you can see, the data definition of LokiFieldComponent fully depends upon an object LokiFieldComponentType (which we call a component type). This object looks like the following):

const LokiFieldComponentType = {
    ...LokiComponentType,
    fieldName: '',
    fieldValue: null,
    /* ... */
}

It is a plain object that uses ES6 destructuring to borrow properties from a more generic LokiComponentType and then add its own properties on top of this. The LokiComponentType is also known as a component type.

In the case, there is no usage of component partials here.

Component types vs component partials

An example for a component partial could look like the following:

const LokiExampleComponent = {
    ...LokiComponentType,
    ...LokiTabComponentPartial,
}

In this case, a custom component LokiExampleComponent is created based on top of the component type LokiComponentType and the component partial LokiTabComponentPartial. As you can see, using a component type and a component partial is the same.

The difference between a component type and a component partial is that with the import of a component type (...LokiComponentType) you already have a full-blown component, while the import of a component type (...LokiTabComponentPartial) you only get specific behaviour.

Existing component partials

At this moment, the following component partials exist:

  • LokiTabComponentPartial for managing tabs;
  • LokiModalComponentPartial for managing modal popups;
Last modified: April 30, 2025