Module: Spree::Admin::TableHelper

Defined in:
app/helpers/spree/admin/table_helper.rb

Constant Summary collapse

STATUS_BADGE_CLASSES =
{
  'active' => 'badge-active', 'complete' => 'badge-active', 'completed' => 'badge-active',
  'paid' => 'badge-active', 'shipped' => 'badge-active', 'available' => 'badge-active',
  'draft' => 'badge-warning', 'pending' => 'badge-warning', 'processing' => 'badge-warning', 'ready' => 'badge-warning',
  'archived' => 'badge-inactive', 'canceled' => 'badge-inactive', 'cancelled' => 'badge-inactive',
  'failed' => 'badge-inactive', 'void' => 'badge-inactive', 'inactive' => 'badge-inactive'
}.freeze

Instance Method Summary collapse

Instance Method Details

#column_cell_class(column) ⇒ String

Get column cell CSS class

Parameters:

Returns:

  • (String)


230
231
232
233
234
235
# File 'app/helpers/spree/admin/table_helper.rb', line 230

def column_cell_class(column)
  [].tap do |classes|
    classes << "text-#{column.align}" unless column.align.to_s == 'left'
    classes << (column.wrap ? 'text-wrap' : 'text-nowrap')
  end.join(' ')
end

#column_header_class(column) ⇒ String

Get column header CSS class

Parameters:

Returns:

  • (String)


220
221
222
223
224
225
# File 'app/helpers/spree/admin/table_helper.rb', line 220

def column_header_class(column)
  [].tap do |classes|
    classes << "text-#{column.align}" unless column.align.to_s == 'left'
    classes << "w-#{column.width}" if column.width.present?
  end.join(' ')
end

#count_applied_filters(query_state) ⇒ Integer

Count the number of applied filters from query_state parameter

Parameters:

  • query_state (String)

    JSON string of query state

Returns:

  • (Integer)

    total number of filters applied



254
255
256
257
258
259
260
261
262
263
# File 'app/helpers/spree/admin/table_helper.rb', line 254

def count_applied_filters(query_state)
  return 0 if query_state.blank? || query_state == '{}'

  begin
    state = JSON.parse(query_state)
    count_filters_in_state(state)
  rescue JSON::ParserError
    0
  end
end

#empty_column_placeholderString

Render placeholder for empty column values

Returns:

  • (String)


71
72
73
# File 'app/helpers/spree/admin/table_helper.rb', line 71

def empty_column_placeholder
  (:span, '-', class: 'text-gray-400')
end

#query_builder_fields_json(table) ⇒ String

Build query builder fields JSON for Stimulus controller

Parameters:

Returns:

  • (String)

    JSON string



240
241
242
243
# File 'app/helpers/spree/admin/table_helper.rb', line 240

def query_builder_fields_json(table)
  query_builder = Spree::Admin::Table::QueryBuilder.new(table)
  query_builder.available_fields(self).to_json
end

#query_builder_operators_jsonString

Build available operators JSON for Stimulus controller

Returns:

  • (String)

    JSON string



247
248
249
# File 'app/helpers/spree/admin/table_helper.rb', line 247

def query_builder_operators_json
  Spree::Admin::Table::Filter.operators_for_select.to_json
end

#render_association_column(value, column) ⇒ String

Render association column (e.g., taxons, tags)

Parameters:

Returns:

  • (String)


191
192
193
194
195
# File 'app/helpers/spree/admin/table_helper.rb', line 191

def render_association_column(value, column)
  return empty_column_placeholder if value.blank?

  h(value.to_s.truncate(100))
end

#render_boolean_column(value, column) ⇒ String

Render boolean column

Parameters:

Returns:

  • (String)


142
143
144
# File 'app/helpers/spree/admin/table_helper.rb', line 142

def render_boolean_column(value, column)
  active_badge(value)
end

#render_bulk_action(action, table:, **options) ⇒ String

Render a single bulk action button or link

Parameters:

Returns:

  • (String)

    rendered HTML



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'app/helpers/spree/admin/table_helper.rb', line 270

def render_bulk_action(action, table:, **options)
  return unless action.visible?(self)

  action_path = action.action_path.is_a?(Proc) ? action.action_path.call(self) : action.action_path
  modal_path = spree.new_admin_bulk_operation_path(kind: action.key, table_key: table.key)
  confirm_message = resolve_bulk_action_confirm(action.confirm)

  link_options = {
    class: options[:class] || 'btn',
    data: {
      action: 'click->bulk-operation#setBulkAction click->bulk-dialog#open',
      turbo_frame: :bulk_dialog,
      url: action_path,
      method: action.method
    }
  }

  link_options[:data][:confirm] = confirm_message if confirm_message.present?

  content = if action.icon.present?
              icon(action.icon) + ' ' + action.resolve_label
            else
              action.resolve_label
            end

  link_to content, modal_path, link_options
end

#render_bulk_actions_dropdown(actions, table: nil) ⇒ String

Render dropdown for secondary bulk actions

Parameters:

Returns:

  • (String)

    rendered HTML



348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'app/helpers/spree/admin/table_helper.rb', line 348

def render_bulk_actions_dropdown(actions, table: nil)
  dropdown(direction: 'top', portal: false) do
    toggle = dropdown_toggle do
      icon('dots-vertical', class: 'mr-0')
    end

    menu = dropdown_menu(class: 'mb-2') do
      items = actions.map do |action|
        render_bulk_action(action, table: table, class: 'dropdown-item')
      end
      safe_join(items)
    end

    safe_join([toggle, menu])
  end
end

#render_bulk_actions_panel(table) ⇒ String

Render bulk actions panel for a table

Parameters:

Returns:

  • (String)

    rendered HTML



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'app/helpers/spree/admin/table_helper.rb', line 317

def render_bulk_actions_panel(table)
  actions = table.visible_bulk_actions(self)
  return if actions.empty?

  # Split actions into primary (first 2) and secondary (rest in dropdown)
  primary_actions = actions.first(2)
  secondary_actions = actions.drop(2)

  (:div, id: 'bulk-panel', class: 'hidden', data: { bulk_operation_target: 'panel' }) do
    (:div, class: 'bulk-panel-container') do
      parts = []
      parts << bulk_operations_counter

      primary_actions.each do |action|
        parts << render_bulk_action(action, table: table)
      end

      if secondary_actions.any?
        parts << render_bulk_actions_dropdown(secondary_actions, table: table)
      end

      parts << bulk_operations_close_button
      safe_join(parts)
    end
  end
end

#render_column_value(record, column, table) ⇒ String

Render a single column value based on its type

Parameters:

Returns:

  • (String)

    rendered HTML



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'app/helpers/spree/admin/table_helper.rb', line 40

def render_column_value(record, column, table)
  value = column.resolve_value(record, self)

  case column.type.to_s
  when 'money'
    render_money_column(value, column)
  when 'date'
    render_date_column(value, column)
  when 'datetime'
    render_datetime_column(value, column)
  when 'status'
    render_status_column(value, column)
  when 'boolean'
    render_boolean_column(value, column)
  when 'link'
    render_link_column(record, value, column, table)
  when 'image'
    render_image_column(record, value, column)
  when 'custom'
    extra_locals = column.partial_locals.is_a?(Proc) ? column.partial_locals.call(record) : column.partial_locals
    locals = { record: record, column: column, value: value }.merge(extra_locals)
    render partial: column.partial, locals: locals
  when 'association'
    render_association_column(value, column)
  else
    render_string_column(value, column)
  end
end

#render_date_column(value, column) ⇒ String

Render date column

Parameters:

Returns:

  • (String)


95
96
97
# File 'app/helpers/spree/admin/table_helper.rb', line 95

def render_date_column(value, column)
  render_temporal_column(value, column, :spree_date)
end

#render_datetime_column(value, column) ⇒ String

Render datetime column

Parameters:

Returns:

  • (String)


103
104
105
# File 'app/helpers/spree/admin/table_helper.rb', line 103

def render_datetime_column(value, column)
  render_temporal_column(value, column, :spree_time_ago)
end

#render_image_column(record, value, column) ⇒ String

Render image column

Parameters:

  • record (Object)

    the record

  • value (Object)

    the value (ActiveStorage attachment or URL string)

  • column (Spree::Admin::Table::Column)

    column definition

Returns:

  • (String)


165
166
167
168
169
170
171
172
173
174
175
# File 'app/helpers/spree/admin/table_helper.rb', line 165

def render_image_column(record, value, column)
  return empty_column_placeholder if value.blank?

  if value.respond_to?(:attached?) && value.attached?
    spree_image_tag(value, width: 50, height: 50, class: 'rounded', loading: 'lazy')
  elsif value.is_a?(String)
    image_tag value, class: 'rounded', style: 'max-width: 50px; max-height: 50px;', loading: 'lazy'
  else
    empty_column_placeholder
  end
end

Render link column

Parameters:

Returns:

  • (String)


152
153
154
155
156
157
158
# File 'app/helpers/spree/admin/table_helper.rb', line 152

def render_link_column(record, value, column, table = nil)
  return empty_column_placeholder if value.blank?

  truncated_value = h(value.to_s.truncate(100))
  url = resolve_link_url(record, table)
  url ? link_to(truncated_value, url, data: { turbo_frame: '_top' }) : truncated_value
end

#render_money_column(value, column) ⇒ String

Render money column

Parameters:

Returns:

  • (String)


79
80
81
82
83
84
85
86
87
88
89
# File 'app/helpers/spree/admin/table_helper.rb', line 79

def render_money_column(value, column)
  return empty_column_placeholder if value.blank?

  if value.respond_to?(:display_amount)
    value.display_amount
  elsif value.is_a?(Spree::Money)
    value.to_html
  else
    Spree::Money.new(value, currency: current_currency).to_html
  end
end

#render_status_column(value, column) ⇒ String

Render status column as badge

Parameters:

Returns:

  • (String)


131
132
133
134
135
136
# File 'app/helpers/spree/admin/table_helper.rb', line 131

def render_status_column(value, column)
  return empty_column_placeholder if value.blank?

  css_class = STATUS_BADGE_CLASSES[value.to_s] || 'badge-light'
  (:span, Spree.t(value, scope: column.key, default: value.to_s.humanize), class: "badge #{css_class}")
end

#render_string_column(value, column) ⇒ String

Render string column

Parameters:

Returns:

  • (String)


181
182
183
184
185
# File 'app/helpers/spree/admin/table_helper.rb', line 181

def render_string_column(value, column)
  return empty_column_placeholder if value.blank?

  column.format.present? && respond_to?(column.format) ? send(column.format, value) : h(value.to_s.truncate(100))
end

#render_table(collection, table_key, **options) ⇒ String

Main helper to render a table

Parameters:

  • collection (ActiveRecord::Relation)

    the collection to render

  • table_key (Symbol)

    the table registry key (e.g., :products)

  • options (Hash)

    additional options

Options Hash (**options):

  • :bulk_operations (Boolean)

    enable bulk operations

  • :sortable (Boolean)

    enable drag-and-drop sorting

  • :frame_name (String)

    custom turbo frame name

  • :export_type (Class)

    export class (e.g., Spree::Exports::Customers)

Returns:

  • (String)

    rendered HTML



13
14
15
16
17
18
19
20
21
22
23
# File 'app/helpers/spree/admin/table_helper.rb', line 13

def render_table(collection, table_key, **options)
  table = Spree.admin.tables.get(table_key)
  selected_columns = session_selected_columns(table_key)

  render 'spree/admin/tables/table',
         collection: collection,
         table: table,
         table_key: table_key,
         selected_columns: selected_columns,
         **options
end

#render_temporal_column(value, column, default_formatter) ⇒ String

Render temporal (date/datetime) column with optional format

Parameters:

  • value (Object)

    the value

  • column (Spree::Admin::Table::Column)

    column definition

  • default_formatter (Symbol)

    default formatter method

Returns:

  • (String)


112
113
114
115
116
117
# File 'app/helpers/spree/admin/table_helper.rb', line 112

def render_temporal_column(value, column, default_formatter)
  return empty_column_placeholder if value.blank?

  formatter = column.format.present? && respond_to?(column.format) ? column.format : default_formatter
  send(formatter, value)
end

#resolve_bulk_action_confirm(confirm) ⇒ String?

Resolve confirm message for bulk action

Parameters:

  • confirm (String, Symbol, nil)

    the confirm value (can be i18n key or plain string)

Returns:

  • (String, nil)


301
302
303
304
305
306
307
308
309
310
311
# File 'app/helpers/spree/admin/table_helper.rb', line 301

def resolve_bulk_action_confirm(confirm)
  return nil if confirm.blank?

  if confirm.is_a?(Symbol)
    Spree.t(confirm)
  elsif confirm.is_a?(String) && confirm.start_with?('admin.')
    Spree.t(confirm, default: confirm)
  else
    confirm
  end
end

#session_selected_columns(table_key) ⇒ Array<String>?

Get selected column keys from session

Parameters:

  • table_key (Symbol)

    table key

Returns:

  • (Array<String>, nil)


28
29
30
31
32
33
# File 'app/helpers/spree/admin/table_helper.rb', line 28

def session_selected_columns(table_key)
  keys = session["table_columns_#{table_key}"]
  return nil if keys.blank?

  keys.split(',')
end

#table_sort_dropdown(table, current_sort) ⇒ String

Render sort dropdown for sortable columns

Parameters:

  • table (Spree::Admin::Table)

    the table

  • current_sort (String, nil)

    current sort value (e.g., “name asc”)

Returns:

  • (String)


201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'app/helpers/spree/admin/table_helper.rb', line 201

def table_sort_dropdown(table, current_sort)
  sortable = table.sortable_columns
  return '' if sortable.empty?

  current_field, current_direction = parse_current_sort(current_sort)
  current_label = find_sort_label(sortable, current_sort)
  current_q = params[:q].respond_to?(:to_unsafe_h) ? params[:q].to_unsafe_h : (params[:q] || {})
  default_field = sortable.first.ransack_attribute

  dropdown(portal: false) do
    toggle = sort_dropdown_toggle(current_direction, current_label)
    menu = sort_dropdown_menu(sortable, current_field, current_direction, current_q, default_field)
    safe_join([toggle, menu])
  end
end