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(new_filtering) ⇒ Object
DSL method Enables or disables filtering entirely (sorting is independent of this setting).
-
#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_intents: [], 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(new_pagination) ⇒ Object
DSL method Enables or disables pagination (caution: all records will be loaded).
-
#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_intents(**intent_opts, &block) ⇒ Object
DSL method If a block is given: Enters the DSL where row intents can be added or removed (use from Component#setup within the component).
-
#skip_column(name) ⇒ Object
DSL method Marks a single column as skipped.
-
#sort(name, label: nil) ⇒ Object
DSL method Adds a sorting criterion that will be processed by ransack.
-
#sorting(new_sorting) ⇒ Object
DSL method Enables or disables sorting entirely (both links and sorting input in filter).
-
#sorting_enabled? ⇒ Boolean
protected
Returns whether sorting is possible and wanted in general (regardless of whether there are any sorts defined).
-
#sorting_in_filter(new_sorting_in_filter) ⇒ Object
DSL method Enables or disables sorting in filter.
-
#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(new_sorting_links) ⇒ Object
DSL method Enables or disables sorting links.
-
#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
#before_render, comp_name, #content, #exposed_intents, family_name, #id, #id_path, #id_path_hash, #inspect, #param_name, #path, #remove_content, #remove_content!, #render, #resourceful?, #root_comp, #root_comp?, setup, #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_intents: [], 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 51 52 53 54 55 56 |
# 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_intents: [], skip_filters: [], default_sorting: 'id asc', **) @pagination = true @skip_pagination = !!skip_pagination @results_per_page = results_per_page @filtering = true @skip_filtering = !!skip_filtering @sorting = true @sorting_in_filter = true @sorting_links = true @skip_sorting_in_filter = !!skip_sorting || !!skip_sorting_in_filter @skip_sorting_links = !!skip_sorting || !!skip_sorting_links @columns = Compony::NaturalOrdering.new @row_intent_blocks = [] @skipped_columns = skip_columns.map(&:to_sym) @skipped_row_intents = skip_row_intents.is_a?(Enumerable) ? skip_row_intents.map(&:to_sym) : [] @skip_row_intents = skip_row_intents.is_a?(TrueClass) @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.
138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/compony/components/list.rb', line 138 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.
154 155 156 |
# File 'lib/compony/components/list.rb', line 154 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
97 98 99 |
# File 'lib/compony/components/list.rb', line 97 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.
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/compony/components/list.rb', line 187 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.
111 112 113 |
# File 'lib/compony/components/list.rb', line 111 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).
125 126 127 |
# File 'lib/compony/components/list.rb', line 125 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.
104 105 106 |
# File 'lib/compony/components/list.rb', line 104 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.
118 119 120 |
# File 'lib/compony/components/list.rb', line 118 def filter_select_class(class_str) @filter_select_class = class_str end |
#filtering(new_filtering) ⇒ Object
DSL method Enables or disables filtering entirely (sorting is independent of this setting).
72 73 74 |
# File 'lib/compony/components/list.rb', line 72 def filtering(new_filtering) @filtering = !!new_filtering end |
#filtering_enabled? ⇒ Boolean (protected)
Returns whether filtering is possible and wanted in general (regardless of whether there are any filters defined)
387 388 389 |
# File 'lib/compony/components/list.rb', line 387 def filtering_enabled? @filtering && defined?(Ransack) && !@skip_filtering end |
#filters(*filter_names) ⇒ Object
DSL method Adds multiple filters at once, sharing the same kwargs.
212 213 214 |
# File 'lib/compony/components/list.rb', line 212 def filters(*filter_names, **) filter_names.each { |filter_name| filter(filter_name, **) } end |
#pagination(new_pagination) ⇒ Object
DSL method Enables or disables pagination (caution: all records will be loaded).
60 61 62 |
# File 'lib/compony/components/list.rb', line 60 def pagination(new_pagination) @pagination = !!new_pagination end |
#pagination_enabled? ⇒ Boolean (protected)
Returns whether pagination is enabled (regardless of whether there is more than one page)
407 408 409 |
# File 'lib/compony/components/list.rb', line 407 def pagination_enabled? @pagination && !@skip_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.
233 234 235 236 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 |
# File 'lib/compony/components/list.rb', line 233 def process_data!(controller) fail('Data was already processed!') if @processed_data # Filtering if filtering_enabled? || sorting_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.
66 67 68 |
# File 'lib/compony/components/list.rb', line 66 def results_per_page(new_results_per_page) @results_per_page = new_results_per_page end |
#row_intents(**intent_opts, &block) ⇒ Object
DSL method
If a block is given: Enters the DSL where row intents can be added or removed (use from Component#setup within the component).
If no block is given: Builds the declared intents for the given record and returns them (use in content or before_render, pass kwarg :data).
168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/compony/components/list.rb', line 168 def row_intents(**intent_opts, &block) if block_given? # Enter DSL @row_intent_blocks << block else # Build the declared intents intents_ordering = NaturalOrdering.new @row_intent_blocks.each { |block| ManageIntentsDsl.new(intents_ordering, **intent_opts).evaluate(&block) } # this populates intents_ordering return intents_ordering.map!(&:payload) end end |
#skip_column(name) ⇒ Object
DSL method Marks a single column as skipped. It will not be displayed, even if it is defined.
161 162 163 |
# File 'lib/compony/components/list.rb', line 161 def skip_column(name) @skipped_columns << name.to_sym 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.
221 222 223 224 |
# File 'lib/compony/components/list.rb', line 221 def sort(name, label: nil) label ||= data_class.fields[name].label @sorts.natural_push(name.to_sym, nil, label:) end |
#sorting(new_sorting) ⇒ Object
DSL method Enables or disables sorting entirely (both links and sorting input in filter).
78 79 80 81 |
# File 'lib/compony/components/list.rb', line 78 def sorting(new_sorting) @sorting_in_filter = !!new_sorting @sorting_links = !!new_sorting end |
#sorting_enabled? ⇒ Boolean (protected)
Returns whether sorting is possible and wanted in general (regardless of whether there are any sorts defined)
392 393 394 |
# File 'lib/compony/components/list.rb', line 392 def sorting_enabled? ((@sorting_in_filter && !@skip_sorting_in_filter) || (@sorting_links && !@skip_sorting_links)) && defined?(Ransack) end |
#sorting_in_filter(new_sorting_in_filter) ⇒ Object
DSL method Enables or disables sorting in filter.
85 86 87 |
# File 'lib/compony/components/list.rb', line 85 def sorting_in_filter(new_sorting_in_filter) @sorting_in_filter = !!new_sorting_in_filter 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)
397 398 399 |
# File 'lib/compony/components/list.rb', line 397 def sorting_in_filter_enabled? sorting_enabled? && @sorting_in_filter && !@skip_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.
412 413 414 415 416 417 418 419 420 |
# File 'lib/compony/components/list.rb', line 412 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(new_sorting_links) ⇒ Object
DSL method Enables or disables sorting links.
91 92 93 |
# File 'lib/compony/components/list.rb', line 91 def sorting_links(new_sorting_links) @sorting_links = !!new_sorting_links 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)
402 403 404 |
# File 'lib/compony/components/list.rb', line 402 def sorting_links_enabled? sorting_enabled? && @sorting_links && !@skip_sorting_links end |
#sorts(*names) ⇒ Object
DSL method Adds multiple sorts at once, sharing the same kwargs.
228 229 230 |
# File 'lib/compony/components/list.rb', line 228 def sorts(*names, **) names.each { |name| sort(name, **) } end |