Skip to content

Instantly share code, notes, and snippets.

@RStankov
Created April 25, 2024 11:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RStankov/1117fcde73c70e5d4695119d77398ce0 to your computer and use it in GitHub Desktop.
Save RStankov/1117fcde73c70e5d4695119d77398ce0 to your computer and use it in GitHub Desktop.
ViewComponent Tips
class ApplicationComponent < ViewComponent::Base
private
def fetch_with_fallback(hash, key, fallback)
hash.fetch(key) do
ErrorReporting.capture_exception(%(key not found: "#{key}"))
fallback
end
end
def t_label(key)
return '' if key.blank?
return key if key.is_a?(String)
t(key, default: :"label_#{key}")
end
end
module ApplicationHelper
def component(name, *args, **kwargs, &)
render("#{name}_component".classify.constantize.new(*args, **kwargs), &)
end
end
<%= form_tag @action, method: :get, class: 'c-filter-form' do %>
<% inputs.each do |(label, input)| %>
<label class="flex items-center gap-x-2 c-hint">
<%= t_label(label) %>
<%= input %>
</label>
<% end %>
<%= submit_tag t(:button_filter), name: nil, class: 'c-button' %>
<% end %>
class FilterFormComponent < ApplicationComponent
attr_reader :action, :inputs
def initialize(action: nil, params: {})
@params = params
@action = action
@inputs = []
end
def before_render
# needed to trigger the builder methods
content
end
def search(name, label: nil)
@inputs << [label, render(SearchInputComponent.new(name, @params[name]))]
end
def select(name, options:, label: nil, placeholder: nil, classes: nil)
input = select_tag(
name,
options_for_select(options, @params[name]),
class: "c-input #{classes}",
)
@inputs << [label, input]
end
def text(name, label: nil, placeholder: nil, classes: nil)
input = text_field_tag(
name,
@params[name],
class: "c-input #{classes}",
placeholder: t_label(placeholder),
)
@inputs << [label, input]
end
def date_range(name)
gteq = date_field_tag("#{name}[gteq]", @params.dig(name, :gteq), class: 'c-input')
lteq = date_field_tag("#{name}[lteq]", @params.dig(name, :lteq), class: 'c-input')
@inputs << [:start_date, gteq]
@inputs << [:end_date, lteq]
end
end
<table class="c-table">
<thead class="header">
<tr valign="top">
<% columns.each do |column| %>
<%= column.render_header %>
<% end %>
</tr>
</thead>
<tbody>
<% records.each do |record| %>
<tr valign="top">
<% columns.each do |column| %>
<%= column.render_cell(record) %>
<% end %>
</tr>
<% end %>
</tbody>
<% if records.respond_to?(:current_page) && records.total_pages > 1 %>
<tfoot>
<tr>
<td colspan="<%= columns.size %>">
<%= paginate records %>
</td>
</tr>
</tfoot>
<% end %>
</table>
class TableComponent < ApplicationComponent
attr_reader :records, :columns
FORMAT_MONEY = -> { Format.money(_1) }
FORMAT_DATE = -> { Format.date(_1) }
def initialize(records)
@records = records
@columns = []
end
def before_render
content
end
def column(name, classes = nil, format = nil, &)
@columns << TableColumn.new(name, classes, format, helpers, &)
end
def number(name, &)
column(name, 'number', &)
end
def money(name, &)
column(name, 'number', FORMAT_MONEY, &)
end
def date(name, options = {}, &)
column(name, 'time', FORMAT_DATE, &)
end
def record(name, attribute_name = name)
column(name) do |record|
link_record = record.public_send(attribute_name)
if link_record.present?
helpers.link_to t_display(link_record), Routes.record_path(link_record)
end
end
end
class TableColumn
def initialize(name, classes, format, helpers, &block)
@name = name
@classes = classes
@format = format
@helpers = helpers
@block = block
end
def render_header
@helpers.tag.th(@helpers.t_label(@name), role: 'col', class: classes)
end
def render_cell(record)
content = if @block
@helpers.capture { @block.call(record).presence || '' }.strip
else
record.public_send(@name)
end
content = format.call(content) if content.present? && format.present?
@helpers.tag.td(content, class: classes)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment