Class: Compony::Components::List
- Inherits:
-
Compony::Component
- Object
- Compony::Component
- Compony::Components::List
- Includes:
- Compony::ComponentMixins::Resourceful
- Defined in:
- lib/compony/components/list.rb
Overview
This component is used for the Rails list paradigm. It is meant to be nested inside the same family's ::Index or an owner's ::Show component.
Instance Attribute Summary
Attributes included from Compony::ComponentMixins::Resourceful
#data, #global_after_assign_attributes_block, #global_after_load_data_block, #global_assign_attributes_block, #global_load_data_block, #global_store_data_block
Attributes inherited from Compony::Component
#comp_opts, #content_blocks, #parent_comp
Instance Method Summary collapse
-
#column(name, label: nil, class: nil, link_opts: {}, &block) ⇒ Object
DSL method Adds a new column to the list.
-
#columns(*col_names) ⇒ Object
DSL method Adds multiple columns at once, sharing the same kwargs.
-
#default_sorting(new_default_sorting) ⇒ Object
DSL method Overrides the default sorting.
-
#filter(name, label: nil, &block) ⇒ Object
DSL method Adds a ransack filter.
-
#filter_input_class(class_str) ⇒ Object
DSL method Sets the CSS class attribute for string form inputs in filters.
-
#filter_item_wrapper_class(class_str) ⇒ Object
DSL method Sets the CSS class attribute for the div that wraps input-related elements in filters (inputs, selects, labels).
-
#filter_label_class(class_str) ⇒ Object
DSL method Sets the CSS class attribute for form label elements in filters.
-
#filter_select_class(class_str) ⇒ Object
DSL method Sets the CSS class attribute for form select inputs in filters.
-
#filtering_enabled? ⇒ Boolean
protected
Returns whether filtering is possible and wanted in general (regardless of whether there are any filters defined).
-
#filters(*filter_names) ⇒ Object
DSL method Adds multiple filters at once, sharing the same kwargs.
-
#initialize(skip_pagination: false, results_per_page: 20, skip_filtering: false, skip_sorting: false, skip_sorting_in_filter: false, skip_sorting_links: false, skip_columns: [], skip_row_actions: [], skip_filters: [], default_sorting: 'id asc') ⇒ List
constructor
The following parameters are meant for the case where this component is nested and therefore instanciated by a parent comp.
-
#pagination_enabled? ⇒ Boolean
protected
Returns whether pagination is enabled (regardless of whether there is more than one page).
-
#process_data!(controller) ⇒ Object
This method must be called before the data is read for the first time.
-
#results_per_page(new_results_per_page) ⇒ Object
DSL method In case pagination is active, defines the amount of records to display per page.
-
#row_action(name, button_opts: {}, &block) ⇒ Object
DSL method Adds a row action.
-
#skip_column(name) ⇒ Object
DSL method Marks a single column as skipped.
-
#skip_filtering! ⇒ Object
DSL method Disables filtering entirely (sorting is independent of this setting).
-
#skip_pagination! ⇒ Object
DSL method Disables pagination (caution: all records will be loaded).
-
#skip_row_action(name) ⇒ Object
DSL method Marks a single row action as skipped.
-
#skip_sorting! ⇒ Object
DSL method Disables sorting entirely (both links and sorting input in filter).
-
#skip_sorting_in_filter! ⇒ Object
DSL method Disables sorting in filter.
-
#skip_sorting_links! ⇒ Object
DSL method Disables sorting links.
-
#sort(name, label: nil) ⇒ Object
DSL method Adds a sorting criterion that will be processed by ransack.
-
#sorting_enabled? ⇒ Boolean
protected
Returns whether sorting is possible and wanted in general (regardless of whether there are any sorts defined).
-
#sorting_in_filter_enabled? ⇒ Boolean
protected
Returns whether sorting in filter is possible and wanted in general (regardless of whether there are any sorts defined).
-
#sorting_in_filter_select_opts ⇒ Object
protected
Returns the select options for sorting suitable for passing in a
f.select. -
#sorting_links_enabled? ⇒ Boolean
protected
Returns whether generating sorting links is possible and wanted in general (regardless of whether there are any sorts defined).
-
#sorts(*names) ⇒ Object
DSL method Adds multiple sorts at once, sharing the same kwargs.
Methods included from Compony::ComponentMixins::Resourceful
#after_assign_attributes, #after_load_data, #assign_attributes, #data_class, #load_data, #resourceful?, #resourceful_sub_comp, #store_data
Methods inherited from Compony::Component
#action, #before_render, comp_cst, comp_name, #content, family_cst, family_name, #id, #id_path, #id_path_hash, #inspect, #param_name, #path, #remove_content, #remove_content!, #render, #render_actions, #resourceful?, #root_comp, #root_comp?, setup, #skip_action, #sub_comp
Constructor Details
#initialize(skip_pagination: false, results_per_page: 20, skip_filtering: false, skip_sorting: false, skip_sorting_in_filter: false, skip_sorting_links: false, skip_columns: [], skip_row_actions: [], skip_filters: [], default_sorting: 'id asc') ⇒ List
The following parameters are meant for the case where this component is nested and therefore instanciated by a parent comp. If the component is to configure itself, use the DSL calls below instead.
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/compony/components/list.rb', line 20 def initialize(*, skip_pagination: false, results_per_page: 20, skip_filtering: false, skip_sorting: false, skip_sorting_in_filter: false, skip_sorting_links: false, skip_columns: [], skip_row_actions: [], skip_filters: [], default_sorting: 'id asc', **) @pagination = !skip_pagination @results_per_page = results_per_page @filtering = !skip_filtering @sorting_in_filter = !skip_sorting && !skip_sorting_in_filter @sorting_links = !skip_sorting && !skip_sorting_links @columns = Compony::NaturalOrdering.new @row_actions = Compony::NaturalOrdering.new @skipped_columns = skip_columns.map(&:to_sym) @skipped_row_actions = skip_row_actions.map(&:to_sym) @filters = Compony::NaturalOrdering.new @sorts = Compony::NaturalOrdering.new @skipped_filters = skip_filters.map(&:to_sym) @default_sorting = default_sorting @filter_label_class = 'list-filter-label' @filter_input_class = 'list-filter-input' @filter_select_class = 'list-filter-select' @filter_item_wrapper_class = nil super(*, **) end |
Instance Method Details
#column(name, label: nil, class: nil, link_opts: {}, &block) ⇒ Object
DSL method
Adds a new column to the list. If name corresponds to that of a field, everything is auto-inferred.
Custom columns can be added by providing at least label and a block that will be given a record and instance-execed for every row.
Please note that the column is only shown if the current user has permission to index the attribute.
132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/compony/components/list.rb', line 132 def column(name, label: nil, class: nil, link_opts: {}, **, &block) name = name.to_sym unless block_given? # Assume field column field = data_class.fields[name] || fail("Field #{name.inspect} was not found for data class #{data_class}") block = proc do |record| if controller.current_ability.permitted_attributes(:index, record).include?(field.name.to_sym) next field.value_for(record, link_to_component: :show, controller:, link_opts:).to_s end end end @columns.natural_push(name, block, label: label || field.label, class:, **) end |
#columns(*col_names) ⇒ Object
DSL method Adds multiple columns at once, sharing the same kwargs.
148 149 150 |
# File 'lib/compony/components/list.rb', line 148 def columns(*col_names, **) col_names.each { |col_name| column(col_name, **) } end |
#default_sorting(new_default_sorting) ⇒ Object
DSL method Overrides the default sorting
91 92 93 |
# File 'lib/compony/components/list.rb', line 91 def default_sorting(new_default_sorting) @default_sorting = new_default_sorting end |
#filter(name, label: nil, &block) ⇒ Object
DSL method
Adds a ransack filter. If name is the name of an existing model field, the filter is auto-generated.
If name is a valid Ransack search string (e.g. id_eq), all you need to pass is name and label.
To create a fully custom filter, pass name and block. The block will be given the Ransack search form and should return HTML.
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/compony/components/list.rb', line 191 def filter(name, label: nil, **, &block) name = name.to_sym unless block_given? field = data_class.fields[name] block ||= proc do |f| label ||= field.label if field fail("You must provide a label to filter #{name.inspect}.") unless label if field filter_name = field.ransack_filter_name filter_input_html = capture { field.ransack_filter_input(f, filter_input_class: @filter_input_class, filter_select_class: @filter_select_class) } else filter_name = name filter_input_html = capture { f.search_field(filter_name, class: @filter_input_class) } end div tag.label(label, for: filter_name, class: @filter_label_class), class: @filter_item_wrapper_class div filter_input_html, class: @filter_item_wrapper_class end end @filters.natural_push(name, block, **) end |
#filter_input_class(class_str) ⇒ Object
DSL method Sets the CSS class attribute for string form inputs in filters.
105 106 107 |
# File 'lib/compony/components/list.rb', line 105 def filter_input_class(class_str) @filter_input_class = class_str end |
#filter_item_wrapper_class(class_str) ⇒ Object
DSL method Sets the CSS class attribute for the div that wraps input-related elements in filters (inputs, selects, labels).
119 120 121 |
# File 'lib/compony/components/list.rb', line 119 def filter_item_wrapper_class(class_str) @filter_item_wrapper_class = class_str end |
#filter_label_class(class_str) ⇒ Object
DSL method Sets the CSS class attribute for form label elements in filters.
98 99 100 |
# File 'lib/compony/components/list.rb', line 98 def filter_label_class(class_str) @filter_label_class = class_str end |
#filter_select_class(class_str) ⇒ Object
DSL method Sets the CSS class attribute for form select inputs in filters.
112 113 114 |
# File 'lib/compony/components/list.rb', line 112 def filter_select_class(class_str) @filter_select_class = class_str end |
#filtering_enabled? ⇒ Boolean (protected)
Returns whether filtering is possible and wanted in general (regardless of whether there are any filters defined)
392 393 394 |
# File 'lib/compony/components/list.rb', line 392 def filtering_enabled? @filtering && defined?(Ransack) end |
#filters(*filter_names) ⇒ Object
DSL method Adds multiple filters at once, sharing the same kwargs.
216 217 218 |
# File 'lib/compony/components/list.rb', line 216 def filters(*filter_names, **) filter_names.each { |filter_name| filter(filter_name, **) } end |
#pagination_enabled? ⇒ Boolean (protected)
Returns whether pagination is enabled (regardless of whether there is more than one page)
412 413 414 |
# File 'lib/compony/components/list.rb', line 412 def pagination_enabled? @pagination end |
#process_data!(controller) ⇒ Object
This method must be called before the data is read for the first time. It makes the data fit for display. Only call it once.
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/compony/components/list.rb', line 237 def process_data!(controller) fail('Data was already processed!') if @processed_data # Filtering if filtering_enabled? @q = @data.ransack(controller.params[param_name(:q)], auth_object: controller.current_ability, search_key: param_name(:q)) @q.sorts = @default_sorting if @q.sorts.empty? filtered_data = @q.result.accessible_by(controller.current_ability) else filtered_data = @data end # Pagination if pagination_enabled? @page = controller.params[param_name('page')].presence&.to_i || 1 @pagination_offset = (@page - 1) * @results_per_page @total_pages = (filtered_data.count.to_f / @results_per_page).ceil if @pagination_offset < 0 || @pagination_offset >= filtered_data.count # out of bounds check @page = 1 @pagination_offset = 0 end @processed_data = filtered_data.offset(@pagination_offset).limit(@results_per_page) else @processed_data = filtered_data end # Apply skips to configs # Exclude columns that are skipped or the user is not allowed to display @columns.select! do |col, _| @skipped_columns.exclude?(col[:name]) && controller.current_ability.permitted_attributes(:index, data_class).include?(col[:name]) end # Exclude skipped filters @filters.select! { |filter, _| @skipped_filters.exclude?(filter[:name]) } end |
#results_per_page(new_results_per_page) ⇒ Object
DSL method In case pagination is active, defines the amount of records to display per page.
60 61 62 |
# File 'lib/compony/components/list.rb', line 60 def results_per_page(new_results_per_page) @results_per_page = new_results_per_page end |
#row_action(name, button_opts: {}, &block) ⇒ Object
DSL method
Adds a row action. The very last col provides actions such as :show, :edit or :destroy. Use this method to add your own.
In case the action exists as a component in the family of data_class, it is enough to pass the action's name, and the rest is auto-generated.
In order to create a custom row action, pass a block that will be given the current record and instance-execed once per row, for every record.
166 167 168 169 170 171 172 173 174 175 |
# File 'lib/compony/components/list.rb', line 166 def row_action(name, button_opts: {}, **, &block) name = name.to_sym unless block_given? block = proc do |record| next if Compony.comp_class_for(name, record).nil? (name, record, **) end end @row_actions.natural_push(name, block, **) end |
#skip_column(name) ⇒ Object
DSL method Marks a single column as skipped. It will not be displayed, even if it is defined.
155 156 157 |
# File 'lib/compony/components/list.rb', line 155 def skip_column(name) @skipped_columns << name.to_sym end |
#skip_filtering! ⇒ Object
DSL method Disables filtering entirely (sorting is independent of this setting).
66 67 68 |
# File 'lib/compony/components/list.rb', line 66 def skip_filtering! @filtering = false end |
#skip_pagination! ⇒ Object
DSL method Disables pagination (caution: all records will be loaded).
54 55 56 |
# File 'lib/compony/components/list.rb', line 54 def skip_pagination! @pagination = false end |
#skip_row_action(name) ⇒ Object
DSL method Marks a single row action as skipped. It will not be displayed, even if it is defined.
180 181 182 |
# File 'lib/compony/components/list.rb', line 180 def skip_row_action(name) @skipped_row_actions << name.to_sym end |
#skip_sorting! ⇒ Object
DSL method Disables sorting entirely (both links and sorting input in filter).
72 73 74 75 |
# File 'lib/compony/components/list.rb', line 72 def skip_sorting! @sorting_in_filter = false @sorting_links = false end |
#skip_sorting_in_filter! ⇒ Object
DSL method Disables sorting in filter.
79 80 81 |
# File 'lib/compony/components/list.rb', line 79 def skip_sorting_in_filter! @sorting_in_filter = false end |
#skip_sorting_links! ⇒ Object
DSL method Disables sorting links.
85 86 87 |
# File 'lib/compony/components/list.rb', line 85 def skip_sorting_links! @sorting_links = false end |
#sort(name, label: nil) ⇒ Object
DSL method
Adds a sorting criterion that will be processed by ransack. data_class must be sortable by this criterion. See Ransack's sorting for constraints.
For every call of this method, one sorting link and two entries (asc, desc) in the sorting-in-filter feature will be generated, if enabled.
225 226 227 228 |
# File 'lib/compony/components/list.rb', line 225 def sort(name, label: nil) label ||= data_class.fields[name].label @sorts.natural_push(name.to_sym, nil, label:) end |
#sorting_enabled? ⇒ Boolean (protected)
Returns whether sorting is possible and wanted in general (regardless of whether there are any sorts defined)
397 398 399 |
# File 'lib/compony/components/list.rb', line 397 def sorting_enabled? (@sorting_in_filter || @sorting_links) && defined?(Ransack) end |
#sorting_in_filter_enabled? ⇒ Boolean (protected)
Returns whether sorting in filter is possible and wanted in general (regardless of whether there are any sorts defined)
402 403 404 |
# File 'lib/compony/components/list.rb', line 402 def sorting_in_filter_enabled? sorting_enabled? && @sorting_in_filter end |
#sorting_in_filter_select_opts ⇒ Object (protected)
Returns the select options for sorting suitable for passing in a f.select. Used in sorting-in-filter feature. Useful for custom subclasses of List.
417 418 419 420 421 422 423 424 425 |
# File 'lib/compony/components/list.rb', line 417 def sorting_in_filter_select_opts @sorts.flat_map do |sort| %w[asc desc].map do |order| label = "#{sort[:label]} #{order == 'asc' ? '↑' : '↓'}" value = "#{sort[:name]} #{order}" [label, value] end end end |
#sorting_links_enabled? ⇒ Boolean (protected)
Returns whether generating sorting links is possible and wanted in general (regardless of whether there are any sorts defined)
407 408 409 |
# File 'lib/compony/components/list.rb', line 407 def sorting_links_enabled? sorting_enabled? && @sorting_links end |
#sorts(*names) ⇒ Object
DSL method Adds multiple sorts at once, sharing the same kwargs.
232 233 234 |
# File 'lib/compony/components/list.rb', line 232 def sorts(*names, **) names.each { |name| sort(name, **) } end |