avo icon indicating copy to clipboard operation
avo copied to clipboard

Using native Avo components in Resource Tools

Open OlexYakov opened this issue 2 years ago • 2 comments

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. image

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.

OlexYakov avatar Aug 08 '22 14:08 OlexYakov

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!

adrianthedev avatar Aug 09 '22 12:08 adrianthedev

This issue has been marked as stale because there was no activity for the past 15 days.

github-actions[bot] avatar Aug 25 '22 04:08 github-actions[bot]

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.

adrianthedev avatar Oct 04 '22 12:10 adrianthedev

Great! Will try them out as soon as possible! Thanks @adrianthedev !

OlexYakov avatar Oct 11 '22 08:10 OlexYakov