avo
avo copied to clipboard
Using native Avo components in Resource Tools
Context
First of all, great work! Avo has been evolving faster than ever! I don't actually have an issue, it's more of a comment/suggestion about the recent nested fields feature Avo 2.12.
We've had this problem and ended up solving it with a custom page, but now we will probably move it to a Resource Tool, since the form object is already available there.
In any case, styling cohesion was important for us, and given that you use ViewComponents I tried rendering those. Had to learn the innards of Avo (with the hydrating process etc), and finally reached a solution that works. I think It might be useful for other people or maybe even future Avo versions. Here are some examples on how this was achieved:
System configuration
Avo version: Rails version: Ruby version: License type (Community or Pro):
Screenshots
This is inside a custom tool page. Notice that the field styles use the Avo Edit components. Everything works, the Stimulus controllers, etc.
Initial implementation to make these fields work. Very verbose, have to build the EditComponent, the field and hydrate both so we can later highlight the fields for errors, etc.
<%= render(Avo::Fields::SelectField::EditComponent.new(
field: Avo::Fields::SelectField.new(
:vat_rate_id,
name: "VAT Rate",
prompt: t('avo.choose_an_option'),
options: VatRate.all.map do |vr|
[number_to_percentage(vr.value, precision: 2), vr.id]
end
).hydrate(model: form.object),
resource: SubmissionResource.new,
form: form))
%>
With some helpers, this was later refactored into something more rails like;
<%= form.fields_for :product do |pf| %>
<%= avo_select_field_tag(:category_id,
form: pf,
name: "Category",
required: true,
options: Category.categories.leaves.all.map {|s| [s.full_name_path, s.id]},
html: {
edit: {
input: {
data: {
"submission-creator-form-target": "categorySelect",
"action": "change->submission-creator-form#changeCategory"
}
}
}
}) %>
<%= avo_date_field_tag(:buy_date, form: form, help: t("...")) %>
<%= avo_date_field_tag(:pick_up_date, form: form, help: t("...")) %>
<%= avo_text_field_tag(:name, form: vf, name: "...", help: t("...")) %>
<%= avo_textarea_field_tag(:description,
form: vf, name: "New Description",
help: t("variant.description.help"), rows: 4) %>
<%= avo_select_field_tag(:weight_interval_id,
form: vf,
name: "Product Weight",
prompt: t('avo.choose_an_option'),
options: WeightInterval.all.map {|wi| [wi.presentation, wi.id]}) %>
The helpers in question:
def avo_textarea_field_tag(field, form:, **kwargs)
avo_tag_for(
Avo::Fields::TextareaField::EditComponent,
Avo::Fields::TextareaField,
field, form: form, **kwargs
)
end
def avo_select_field_tag(field, form:, **kwargs)
avo_tag_for(
Avo::Fields::SelectField::EditComponent,
Avo::Fields::SelectField,
field, form: form, **kwargs
)
end
#...
private
def avo_tag_for(edit_component_class, field_class, field, form:, **kwargs)
model, resource = get_model_and_resource(form)
render(edit_component_class.new(
field: field_class.new(field, **kwargs).hydrate(model: model),
resource: resource,
form: form
))
end
def get_model_and_resource(form)
model = form.object
hydrated_resource = Avo::App
.get_resource_by_model_name(model.class)
.hydrate(model: model)
[model, hydrated_resource]
end
I decided to share this, maybe it could help contribute to the nested fields feature. The helper wrappers accept any list of keyword arguments, so it should not be too hard to maintain it in the future if, for example, more field options get added.
Also, the hydration part is all in the one get_model_and_resource
method, so if we need to hydrate with additional data (view: :edit
for example) it can be all done in one place.
Hey @OlexYakov.
First of all, congrats for digging through the code and making sense of it. I hope you didn't pull your hair out while trying to make sense of it all :)
Second, thanks for putting in the effort to hack it to your needs and creating this ticket to show us your progress.
I was speaking with @Paul-Bob about making these kinds of tools and components more easily available to you, the developers, so you can create more meaningful experiences for your customers and users.
That being said, from day one, I was thinking of bringing this kind of API to developers to build with it, but not in this form. It's definitely painful to use it like so. You did an amazing job making it work, but I'd like us to make it better and easier to use. Your work definitely helps us by seeing an actual use-case from a real-world example.
I'll start another issue where I'll describe some ideas and reference your examples too.
Thank you!
This issue has been marked as stale because there was no activity for the past 15 days.
Hey @OlexYakov. The native fields feature is up on main
. I also published the docs on this.
I'd love to get your feedback on them.
Great! Will try them out as soon as possible! Thanks @adrianthedev !