view icon indicating copy to clipboard operation
view copied to clipboard

Adding a resolve method to the View module

Open DannySantos opened this issue 5 years ago • 0 comments

I've had an issue recently where I've wanted to create "component" classes that are essentially POROs which return an HTML component (say a modal, button or form) using the HtmlBuilder.

Initially we defined them as methods on our view objects, but that didn't allow us to share them across multiple views.

Then we created modules which could be included in the views, but they weren't ideal for two main reasons: 1) we didn't need to explicitly pass in the required variables - they were just available - but it felt wrong to not pass them in too, and 2) we couldn't define private methods that wouldn't leak into the rest of the view and included modules. This was particularly an issue when multiple components of the same type might require the same method, such as fetch_header_text.

Elsewhere in the app we have implemented a similar idea to wrap up common form elements, and we've been able to achieve this nicely because of the resolve method that exists on both FormBuilder and HtmlBuilder in Hanami Helpers. It works something like this:

module Web
  module Views
    module Users
      module Common
        module FormDetails
          def form_details(context:)
            context.resolve do
              div class: 'row' do
                div class: 'input-field col s12' do
                  text_field :name
                  label 'Name', for: :name
                end
              end
            end
          end
        end
      end
    end
  end
end

And then to call it:

module Web
  module Views
    module Users
      module Common
        module NewForm
          include Web::Views::Users::Common::FormDetails

          def new_form(entity)
            form_for :entity, routes.entity_path(entity.id) do
              form_details(context: self)

              div do
                submit 'Create'
              end
            end
          end
        end
      end
    end
  end
end

We want to do this but with classes, and from the View module. Without being able to resolve the context it would mean appending all the view methods with context., which is less than ideal. As a test I added the same resolve method to it:

def resolve(&blk)
  @context = eval('self', blk.binding, __FILE__, __LINE__)
  instance_exec(&blk)
end

This achieved everything we needed. Can we add this into the View module?

DannySantos avatar Jun 16 '20 10:06 DannySantos