aem-core-forms-components
aem-core-forms-components copied to clipboard
AEM Core Forms Components do not have the same events available as the Foundation Components
Expected Behaviour
We are investigating on migrating from AEM Foundation Forms Components to AEM Core Forms Components and we expect that we can listen to the same events on the guideBridge, namely guideBridge.on("submitStart") to take actions before the submit action and guideBridge.on("validationComplete") to take correct actions when the validation is complete
Actual Behaviour
Both events are no longer available in the AEM Core Forms Components
AEM Version (mention the exact version in case of cloud SDK)
AEM 6.5.19
AEM Forms Version
1.1.32
Sample Code that illustrates the problem
guideBridge.connect(function() {
// It's executed before submit
guideBridge.on('submitStart', (submitEvent) => {
// do things like showing a loading shield
});
//check if there are any validation errors
guideBridge.on('validationComplete', (validationEvent, payload) => {
if (payload.newText.length > 0) {
const error = {
errorList: payload.newText,
};
sendPostMessage('validation-failure', error);
} else {
sendPostMessage('validation-success', validationEvent.type);
}
});
});
@royteeuwen
We have made certain GuideBridge events accessible for external applications to interact with in core components. However, not all events are exposed, as we advise against incorporating GuideBridge in core components due to their compatibility with headless use-cases.
The core components offer enhanced flexibility due to their modular design, leveraging a shared SDK that is also utilized in headless use-cases. The core component is inherently event-driven, providing a comprehensive set of events that can be accessed by subscribing to the form model (for use-cases where event is not exposed in GuideBridge) as demonstrated below.
const onSubmitStart = (event) => {
console.log(event.detail.fieldId)
};
const onValidationComplete = (event) => {
const x = event.payload[0].id;
// do something with the invalid field
};
const initialGbEvents = (guideBridge) => {
guideBridge.getFormModel().subscribe((action) => {
onSubmitStart(action);
}, 'submit');
guideBridge.getFormModel().subscribe((action) => {
onValidationComplete(action);
}, 'validationComplete');
};
if(window.guideBridge !== undefined){
bridge = window.guideBridge;
initialGbEvents(bridge);
} else {
window.addEventListener("bridgeInitializeStart", (event)=>{
bridge = event.detail.guideBridge;
initialGbEvents(bridge);
});
}
The APIs exposed in the formModel returned by the getFormModel function are listed under the "methods" section (like visit, getState) on this page: https://opensource.adobe.com/aem-forms-af-runtime/js-docs/interfaces/FormModel.html.
For example, You can use the following code to submit a form,
guideBridge.getFormModel().dispatch({type: 'submit'})
For more details on the submit event, please visit: Submit Event Documentation.
@rismehta Thank you for your previous knowledgeable responses. I am wondering if the example given is one that leverages the shared sdk that you mention. Or is this not a recommended method. I am referring to "against incorporating GuideBridge in core components due to their compatibility with headless use-cases." and specifically this code?
const onSubmitStart = (event) => {
console.log(event.detail.fieldId)
};
const onValidationComplete = (event) => {
const x = event.payload[0].id;
// do something with the invalid field
};
const initialGbEvents = (guideBridge) => {
guideBridge.getFormModel().subscribe((action) => {
onSubmitStart(action);
}, 'submit');
guideBridge.getFormModel().subscribe((action) => {
onValidationComplete(action);
}, 'validationComplete');
};
if(window.guideBridge !== undefined){
bridge = window.guideBridge;
initialGbEvents(bridge);
} else {
window.addEventListener("bridgeInitializeStart", (event)=>{
bridge = event.detail.guideBridge;
initialGbEvents(bridge);
});
}
If using guidebridge is not recommended how does one get a list of Validation errors in a proper way?
. I am referring to "against incorporating GuideBridge in core components due to their compatibility with headless use-cases." and specifically this code?
@fran-boop Apologies for the delayed response. If there are web applications that need to interact with adaptive forms, you can still use the GuideBridge APIs for those use cases, which is why I've shared the samples above.
If your logic is contained within a form, you can reuse it for headless as well. It's recommended to utilize the visual rule editor or custom functions.
You can link the custom function above in the form initialization using the rule editor.
@rismehta Thanks for the response! I think that I understand what you are saying. Since these requests are called from within the framework a headless application would be able to access the guidebridge instance much like it would any custom scripts referenced in this way. Is there a way to access the form model at runtime to better explore some of these classes that are provided? For example on a foundation form I might put a guidebridge.resolveNode(); into the console for a specific field. Then I would be able to see the object that relates to that form field. Also another unrelated question but if someone wanted to register a validator on a custom component from within the component view js how would one do that?
EDIT: Seeing I can use guidebridge.getmodel()
EDIT: For clarification on the second question around validation. How does for example the emailinput register its validator. I don't see it in the js?
For example on a foundation form I might put a guidebridge.resolveNode(); into the console for a specific field. Then I would be able to see the object that relates to that form field.
You can use guideBridge.getFormModel().resolveQualifiedName to handle this use case.
For clarification on the second question around validation. How does for example the emailinput register its validator. I don't see it in the js?
You can use a custom function to validate any field within the form. For example, you can define a rule that triggers when a field is changed, invoking a function via the Visual Rule Editor. This custom function can then mark the field as valid or invalid. Here's an example of how it can be implemented: https://github.com/adobe/aem-core-forms-components/blob/4c6f9b44c0641d372e4285c73e6dcd2413eb19ce/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/clientlib-it-custom-function/js/functions.js#L252-L262
@rismehta I see that method in the documentation and it seems promising. I was hoping that you would be able to do something similar on the actual component level.
Another question I have about this method is: If you have a pattern defined in the authoring as well, will these two conflict since you are setting the property of valid to true? What if the pattern validation is false?
I guess for me, my main question is how is the validation system designed in the runtime?
I imagined that from within a component that extends from the baseField, you would be able to do something similar (set the component as invalid). For example, on the blur events that are assigned in the constructor of most fields, my thought would be to have a validation there and then set the validation of the model there. I see that those fields are read only but maybe I need a helper method to set that property?
Thank you in advance for any guidance and I would love to catch you for a 30min convo if possible.
Another question I have about this method is: If you have a pattern defined in the authoring as well, will these two conflict since you are setting the property of valid to true? What if the pattern validation is false?
You would typically do this if the out-of-the-box authoring validation constructs don’t meet your use case. Combining both can lead to conflicts
For example, on the blur events that are assigned in the constructor of most fields, my thought would be to have a validation there and then set the validation of the model there.
Yes, that’s also an option. It largely depends on the use case. For instance, if this pattern is consistent across all your forms, it’s better to create a single component, modify its view layer, and handle the logic there.
Generally, form runtime engine offer several hook points, and your choice should be based on the specific use case. If it’s unique to one form, you could use the visual rule editor I mentioned earlier.
However, if it’s a pattern that applies to all forms—where validation needs to trigger on certain events for all fields—you can customize the component’s view layer by creating a custom component.
I see that those fields are read only but maybe I need a helper method to set that property?
Could you tell me which fields are read-only? You can dynamically set the field's valid property, as I mentioned earlier, even through the component code.
I guess for me, my main question is how is the validation system designed in the runtime?
The core library manages all form constraints (you can review the documentation here) and triggers validity event based on constraint evaluation. The view layer then responds to these events. Here’s the relevant code: FormFieldBase.js.
You can refer to this wiki for a basic overview of the form runtime, specifically related to core components, https://github.com/adobe/aem-core-forms-components/wiki/Forms-Runtime-Infrastructure
If you could provide the exact use case, we can share a working sample tailored to it.
@rismehta That sounds great all I need to see is how from within a class that is extending from FormFieldBase.js you can set the component as invalid :)
/**
* Updates the HTML state based on the validity state of the field.
* @param {Object} validity - The validity state.
* @param {Object} state - The state object.
*/
updateValidity(validity, state) {
// todo: handle the type of validity if required later
const valid = validity.valid;
if (this.errorDiv) {
this.element.setAttribute(Constants.DATA_ATTRIBUTE_VALID, valid);
this.widget.setAttribute(Constants.ARIA_INVALID, !valid);
this.updateValidationMessage(state.validationMessage, state);
}
}
Where you linked I saw when reading through the code and it looks like this is grabbing the valid state from the model and setting the html. Not the other way around. That is where I become a bit confused. I figured you would set the state of the model and then this would be the function that you can use to ensure that the state of the markup is correct.
I was thinking you would be able to do something like below. But I can't figure out how to do so.
this.widget.addEventListener('blur', (e) => {
this._model.value = this.country.value + " " + e.target.value;
this._model.valid = customValidationFunction();
this.setInactive();
});
@rismehta That sounds great all I need to see is how from within a class that is extending from FormFieldBase.js you can set the component as invalid :)
/** * Updates the HTML state based on the validity state of the field. * @param {Object} validity - The validity state. * @param {Object} state - The state object. */ updateValidity(validity, state) { // todo: handle the type of validity if required later const valid = validity.valid; if (this.errorDiv) { this.element.setAttribute(Constants.DATA_ATTRIBUTE_VALID, valid); this.widget.setAttribute(Constants.ARIA_INVALID, !valid); this.updateValidationMessage(state.validationMessage, state); } } Where you linked I saw when reading through the code and it looks like this is grabbing the valid state from the model and setting the html. Not the other way around. That is where I become a bit confused. I figured you would set the state of the model and then this would be the function that you can use to ensure that the state of the markup is correct.
I was thinking you would be able to do something like below. But I can't figure out how to do so.
this.widget.addEventListener('blur', (e) => { this._model.value = this.country.value + " " + e.target.value; this._model.valid = customValidationFunction(); this.setInactive(); });
@fran-boop You can leverage validationExpression for this use case, which would be a custom function tailored to your specific logic. This validation expression can be linked to the component through the rule editor and saved as a cq:template, allowing it to be reused across all your forms.
An example of validation expression via custom function can be seen here, https://github.com/adobe/aem-core-forms-components/blob/4c6f9b44c0641d372e4285c73e6dcd2413eb19ce/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/ruleeditor/basic/.content.xml#L73
@rismehta I still feel as though it would make more sense to be able to add constraints directly through the component itself. Anyone could potentially remove that rule. Directly in the component interfacing with the constraints just seems more robust to me. Especially if I wanted to create a more advanced component containing multiple configurable fields. For example something like an address component. It feels like a regression back to something I would do in foundation components. I want all of my logic contained in one place and to choose what an author can/cannot configure.
I still feel as though it would make more sense to be able to add constraints directly through the component itself. Anyone could potentially remove that rule.
Agreed, the rule can be removed through authoring. If the use case assumes that authors might remove it, you can implement this logic within the component's view layer, as you previously suggested.
It feels like a regression back to something I would do in foundation components. I want all of my logic contained in one place and to choose what an author can/cannot configure.
This can still be achieved using core components as well. While the Rule Editor makes such customizations easier, modifying the component itself for this logic feels a bit excessive to me. However, if your goal is to restrict authors, creating a custom component would be a suitable approach.
How would one set the validation from within the component js?
How would one set the validation from within the component js?
You can reuse the same code you wrote for v1, but tailor it specifically for the component here. For instance, if you'd like this behavior for the TextInput component, you can implement it at this location: https://github.com/adobe/aem-core-forms-components/blob/master/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/clientlibs/site/js/textinputview.js#L74
this._model.valid = customValidationFunction();
this._model.errorMessage = <something>
Update: was able to get this working! Thank you.
When doing this._model.valid = customValidationFunction(); it does not allow for the setting of the member of the model?