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)


224
225
226
227
228
229
# File 'app/helpers/spree/admin/table_helper.rb', line 224

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)


214
215
216
217
218
219
# File 'app/helpers/spree/admin/table_helper.rb', line 214

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



248
249
250
251
252
253
254
255
256
257
# File 'app/helpers/spree/admin/table_helper.rb', line 248

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



234
235
236
237
# File 'app/helpers/spree/admin/table_helper.rb', line 234

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



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

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)


185
186
187
188
189
# File 'app/helpers/spree/admin/table_helper.rb', line 185

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)


136
137
138
# File 'app/helpers/spree/admin/table_helper.rb', line 136

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



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'app/helpers/spree/admin/table_helper.rb', line 264

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



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'app/helpers/spree/admin/table_helper.rb', line 342

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



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'app/helpers/spree/admin/table_helper.rb', line 311

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)


89
90
91
# File 'app/helpers/spree/admin/table_helper.rb', line 89

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)


97
98
99
# File 'app/helpers/spree/admin/table_helper.rb', line 97

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)


159
160
161
162
163
164
165
166
167
168
169
# File 'app/helpers/spree/admin/table_helper.rb', line 159

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)


146
147
148
149
150
151
152
# File 'app/helpers/spree/admin/table_helper.rb', line 146

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
# File 'app/helpers/spree/admin/table_helper.rb', line 79

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

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

#render_status_column(value, column) ⇒ String

Render status column as badge

Parameters:

Returns:

  • (String)


125
126
127
128
129
130
# File 'app/helpers/spree/admin/table_helper.rb', line 125

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)


175
176
177
178
179
# File 'app/helpers/spree/admin/table_helper.rb', line 175

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)


106
107
108
109
110
111
# File 'app/helpers/spree/admin/table_helper.rb', line 106

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)


295
296
297
298
299
300
301
302
303
304
305
# File 'app/helpers/spree/admin/table_helper.rb', line 295

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)


195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'app/helpers/spree/admin/table_helper.rb', line 195

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