administrate icon indicating copy to clipboard operation
administrate copied to clipboard

Multiple "looks" per Field

Open ammancilla opened this issue 3 years ago • 10 comments

What would you like to be able to do? Can you provide some examples?

As a developer I can define multiple looks for a Field and determine which one to use when using the field.

For example, I have a dashboard with three Field::HasMany attributes. For one of them I want to use the "default look" (the set of partials provided by administrate) but for the other two I want to use two different "looks" defined by me.


How could we go about implementing that?

Introduce the concept of looks (or variants?). A look is a group of partials that determine how a Field is displayed in different pages. There is a default look provided by administrate (the existing partials for each field) and developers can create, within their applications, as many custom looks as needed.

app/
├─ views/
│  ├─ fields/
│  │  ├─ belongs_to/
│  │  │  ├─ looks/
│  │  │  │  ├─ default/
│  │  │  │  │  ├─ _form.html.erb
│  │  │  │  │  ├─ _show.html.erb
│  │  │  │  │  ├─ _index.html.erb
│  │  ├─ has_many/
│  │  │  ├─ looks/
│  │  │  │  ├─ default/

...

Creating custom looks

A custom look is created by adding a new folder (named after the look) under app/views/fields/<field_name>/looks/ with the respective partials to render a Field. A custom look might define all or a subset of the three partials needed for a Field. In case of missing partials, the ones defined in the default look are used as fallback.

app/
├─ views/
│  ├─ fields/
│  │  ├─ belongs_to/
│  │  │  ├─ looks/
│  │  │  │  ├─ simple/
│  │  │  │  │  ├─ _index.html.erb
│  │  │  │  ├─ extended/
│  │  │  │  │  ├─ _show.html.erb

Using a custom look

To use a custom look, its name is passed as an option to a Field. If the option is not given, the default look is used.

  # app/dashboards/book_dashboard.rb

  ATTRIBUTE_TYPES = {
    pages: Field::HasMany,
    authors: Field::HasMany.with_options(look: :simple),
    publishers: Field::HasMany.with_options(look: :extended)
  }
  # administrate/lib/administrate/field/base.rb

  def to_partial_path
    look = options.fetch(:look, :default)
    partial_path = partial_path(page, look)
    
    if look != :default && lookup_context.exists?(partial_path, [], true)
     partial_path
    else
     partial_path(page, :default)
    end
  end

  private

  def partial_path(page, look)
    "/fields/#{self.class.field_type}/#{look}/#{page}"
  end

Can you think of other approaches to the problem?

An alternative, in my opinion less structured approach, would be to allow developers to specify the individual custom partials to be used to render a Field, through a new option (partials or so). If no custom partials are given then the default ones are used.

  • Dashboard
  # app/dashboards/book_dashboard.rb

  ATTRIBUTE_TYPES = {
    pages: Field::HasMany,
    authors: Field::HasMany.with_options(partials: { index: '/path/to/custom/partial'}),
    publishers: Field::HasMany.with_options(partials: { show: '/path/to/custom/partial'})
  }
  • Aministrate Base field
# administrate/lib/administrate/field/base.rb

def to_partial_path
  options.dig(:partials, page) || "/fields/#{self.class.field_type}/#{page}"
end

The strategy I've been using so far.

So far, whenever I need an extra "look" for a Field I create a new Field (without custom behaviour), that inherits from the respective administrate Field. Then, use the custom field in the dashboard for the attributes that need the custom look.

For example:

def HasManyExtendedView < Field::HasMany; end

def HasManySimpleView < Field::HashMany; end
  # app/dashboards/book_dashboard.rb

  ATTRIBUTE_TYPES = {
    pages: Field::HasMany,
    authors: HasManySimpleView,
    publishers: HasManyExtendedView
  }

This approach doesn't scale and results in having a lot of unnecessary files/code (a bunch of custom fields without custom behaviour)

ammancilla avatar Nov 25 '22 12:11 ammancilla

Another option, I think, would be for each look to be a custom field type such that to_partial_path pointed to the correct partial in each case. The downside is that you need the extra field class file, but on the upside it should be possible to implement currently, while not needing to replicate all template files.

Your proposal is not without merit, but it's a change significant enough that I think needs discussing with the rest of the Administrate team. Which reminds me that I should set up some sort of meeting with them to figure out a roadmap for 2023.

pablobm avatar Dec 13 '22 14:12 pablobm