Introduction
The core concept of the Hyvä Checkout is to reduce the amount of JavaScript used, combined with maximizing server-side rendering. This approach results in excellent performance and reduced complexity. However, this doesn't mean we can or should entirely eliminate JavaScript. AlpineJS is a good example of this principle in action — a framework designed to use JavaScript only when necessary and always keep it to a minimum. This concept also applies to the Hyvä Checkout Frontend API. This API primarily supports and enhances the backend, where Magewire handles most of the heavy lifting.
Why use it?
The Frontend API supports the backend, serving as a toolkit to enable specific elements, primarily based on UX and browser functionality. It offers validation capabilities, navigation options, and handles tasks such as placing the order. We chose this additional layer specifically to allow developers to perform certain frontend tasks before any server-related actions, considering that Magewire relies mainly on XHR calls.
Beyond being a toolkit, it also provides a structure designed to ensure that any additions by developers are always placed correctly without needing to worry about architectural decisions. We have developed a specific folder, file, and layout structure to make adding new features as simple as possible.
Example scenario
A good example of our reliance on the Frontend API is our Evaluation API. With this, we have consciously extended the Magewire concept, where the frontend supports the backend. This means that for the Evaluation API, most of the instructions originate from the backend. The backend can send various "Evaluation Results" back to the frontend during page rendering or when updating a component. These instructions are essentially data that the frontend needs to act upon.
Thanks to the power of Magewire, we can listen to the data coming from the backend and process it in various ways. For example, we can wait for a specific action because, at the server level, we already know before rendering, that a payment method hasn't been selected yet. We provide instructions to the frontend in advance, theoretically stating that as long as there is no interaction with the component, an error message should always be displayed if the user tries to navigate or place the order.
Under the hood
Initially, new concepts can seem overwhelming. We understand this and aim to guide you through it by explaining and illustrating how the above example is structured.
We begin with the basic concept of the Evaluation API, where we provide developers with various options to create specific instructions in PHP. These instructions are automatically hydrated and sent to the frontend during page rendering or when processing an update request.
In the example below, we build validation instructions for the frontend. This specific validation is a written JavaScript function. We also attach a "Failure Result" to the validation instruction. The advantage of this approach is that everything is constructed via the backend, making it easier to modify using tools like Magento Plugins.
<?php
class ExampleComponent extends \Magewirephp\Magewire\Component implements \Hyva\Checkout\Model\Magewire\Component\EvaluationInterface
{
public function evaluateCompletion(\Hyva\Checkout\Model\Magewire\Component\EvaluationResultFactory $resultFactory): \Hyva\Checkout\Model\Magewire\Component\Evaluation\EvaluationResult
{
return $resultFactory->createValidation('validateExampleComponent')
->withFailureResult(
$resultFactory->createErrorMessageEvent()
->withCustomEvent('payment:method:error')
->withMessage('Value should be 1234')
->withVisibilityDuration(5000)
);
}
}
For more details about the Evaluation API, please refer to the documentation available here
The above "instructions" result in the following XHR response payload:
{
"example-component-name": {
"arguments": {
"name": "validateExampleComponent",
"detail": {
"component": {
"id": "example-component-name"
}
},
"stack": {
"position": 500
},
"results": {
"failure": {
"arguments": {
"event": "payment:method:error",
"detail": {
"component": {
"id": "example-component-name"
},
"message": {
"text": "Value should be 1234",
"type": "error",
"duration": 5000
}
},
"dispatch": false,
"blocking": {
"result": false,
"cause": null
}
},
"dispatch": true,
"result": false,
"type": "event",
"id": "example-component-name",
"hash": "..."
}
}
},
"dispatch": false,
"result": true,
"type": "validation",
"id": "example-component-name",
"hash": "..."
}
}
The frontend now needs to act on these instructions. The hyvaCheckout.evaluation
namespace provides the ability to
process a result via the process()
method based on the result "type." In this case, the checkout will automatically
handle this result.
More details can be found in Hyva_Checkout::page/js/api/V1/init-evaluation.phtml
.
In this same file, you will find multiple registrations for different types of evaluation result processors, including the one for validation. The callback is uniquely written for each processor and tailored to the possible instructions coming from the backend.
For validation instructions, it first checks if a validator callback can be found in hyvaCheckout.evaluation.validators
based on the given name validateExampleComponent
.
For more details about the Validation API, please refer to the documentation available here
If the validator callback exists, it can be added to the validation stack via hyvaCheckout.validation.register()
.
All validation callbacks are automatically executed when the user attempts to navigate forward or place the order.
When a validation fails, the process is halted, and optionally, the "failure" instruction will be executed.
In this case, that too is an Evaluation result, which can be fed into the process()
function. Since the "dispatch" flag
for that result is set to true, the failure — such as an error message — will be immediately executed and displayed to the user.
<!-- File: My_Example::page/js/hyva-checkout/api/v1/evaluation/validate-example-component.phtml -->
<script>
window.addEventListener('checkout:init:evaluation', () => {
hyvaCheckout.evaluation.registerValidator('validateExampleComponent', (element, component) => {
const field = element.querySelector('#secret')
if (field) {
// API fetch request simulation which takes approximately 2.5 seconds.
return new Promise((resolve, reject) => setTimeout(() => {
if (field.value === '1234') {
resolve(true)
}
reject()
}, 2500))
}
return false
})
})
</script>
As previously mentioned, the failure addition is optional. It is also possible to trigger specific error messages directly from the validateExampleComponent
callback.