datagrid 1.6.3 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -1
- data/Readme.markdown +2 -2
- data/datagrid.gemspec +10 -14
- data/lib/datagrid/active_model.rb +25 -29
- data/lib/datagrid/column_names_attribute.rb +9 -7
- data/lib/datagrid/columns/column.rb +3 -1
- data/lib/datagrid/columns.rb +139 -171
- data/lib/datagrid/configuration.rb +6 -1
- data/lib/datagrid/core.rb +43 -22
- data/lib/datagrid/drivers/abstract_driver.rb +2 -1
- data/lib/datagrid/drivers/active_record.rb +2 -1
- data/lib/datagrid/drivers/array.rb +2 -1
- data/lib/datagrid/drivers/mongo_mapper.rb +2 -1
- data/lib/datagrid/drivers/mongoid.rb +3 -2
- data/lib/datagrid/drivers/sequel.rb +2 -1
- data/lib/datagrid/drivers.rb +2 -1
- data/lib/datagrid/engine.rb +3 -2
- data/lib/datagrid/filters/base_filter.rb +5 -2
- data/lib/datagrid/filters/boolean_enum_filter.rb +5 -3
- data/lib/datagrid/filters/boolean_filter.rb +2 -1
- data/lib/datagrid/filters/composite_filters.rb +5 -11
- data/lib/datagrid/filters/extended_boolean_filter.rb +2 -1
- data/lib/datagrid/filters/select_options.rb +34 -1
- data/lib/datagrid/filters.rb +38 -15
- data/lib/datagrid/form_builder.rb +10 -3
- data/lib/datagrid/helper.rb +47 -21
- data/lib/datagrid/ordering.rb +14 -14
- data/lib/datagrid/renderer.rb +2 -1
- data/lib/datagrid/scaffold.rb +1 -1
- data/lib/datagrid/utils.rb +1 -0
- data/lib/datagrid/version.rb +1 -1
- data/lib/datagrid.rb +2 -1
- metadata +8 -8
data/lib/datagrid/core.rb
CHANGED
@@ -4,6 +4,7 @@ require "active_support/core_ext/class/attribute"
|
|
4
4
|
module Datagrid
|
5
5
|
module Core
|
6
6
|
|
7
|
+
# @!visibility private
|
7
8
|
def self.included(base)
|
8
9
|
base.extend ClassMethods
|
9
10
|
base.class_eval do
|
@@ -12,7 +13,7 @@ module Datagrid
|
|
12
13
|
class_attribute :datagrid_attributes, instance_writer: false
|
13
14
|
self.datagrid_attributes = []
|
14
15
|
|
15
|
-
class_attribute :dynamic_block, :
|
16
|
+
class_attribute :dynamic_block, instance_writer: false
|
16
17
|
class_attribute :forbidden_attributes_protection, instance_writer: false
|
17
18
|
self.forbidden_attributes_protection = false
|
18
19
|
if defined?(::ActiveModel::AttributeAssignment)
|
@@ -20,11 +21,12 @@ module Datagrid
|
|
20
21
|
end
|
21
22
|
end
|
22
23
|
base.send :include, InstanceMethods
|
23
|
-
end
|
24
|
+
end
|
24
25
|
|
25
26
|
module ClassMethods
|
26
27
|
|
27
|
-
|
28
|
+
# @!visibility private
|
29
|
+
def datagrid_attribute(name, &block)
|
28
30
|
unless datagrid_attributes.include?(name)
|
29
31
|
block ||= lambda do |value|
|
30
32
|
value
|
@@ -41,6 +43,11 @@ module Datagrid
|
|
41
43
|
end
|
42
44
|
|
43
45
|
# Defines a scope at class level
|
46
|
+
# @return [void]
|
47
|
+
# @example
|
48
|
+
# scope { User }
|
49
|
+
# scope { Project.where(deleted: false) }
|
50
|
+
# scope { Project.preload(:stages) }
|
44
51
|
def scope(&block)
|
45
52
|
if block
|
46
53
|
current_scope = scope_value
|
@@ -54,12 +61,17 @@ module Datagrid
|
|
54
61
|
end
|
55
62
|
end
|
56
63
|
|
57
|
-
|
64
|
+
# @!visibility private
|
65
|
+
def driver
|
58
66
|
@driver ||= Drivers::AbstractDriver.guess_driver(scope).new
|
59
67
|
end
|
60
68
|
|
61
69
|
# Allows dynamic columns definition, that could not be defined at class level
|
62
|
-
#
|
70
|
+
# Columns that depend on the database state or third party service
|
71
|
+
# can be defined this way.
|
72
|
+
# @param block [Proc] block that defines dynamic columns
|
73
|
+
# @return [void]
|
74
|
+
# @example
|
63
75
|
# class MerchantsGrid
|
64
76
|
#
|
65
77
|
# scope { Merchant }
|
@@ -69,12 +81,15 @@ module Datagrid
|
|
69
81
|
# dynamic do
|
70
82
|
# PurchaseCategory.all.each do |category|
|
71
83
|
# column(:"#{category.name.underscore}_sales") do |merchant|
|
72
|
-
# merchant.purchases.where(:
|
84
|
+
# merchant.purchases.where(category_id: category.id).count
|
73
85
|
# end
|
74
86
|
# end
|
75
87
|
# end
|
76
88
|
# end
|
77
89
|
#
|
90
|
+
# ProductCategory.create!(name: 'Swimwear')
|
91
|
+
# ProductCategory.create!(name: 'Sportswear')
|
92
|
+
#
|
78
93
|
# grid = MerchantsGrid.new
|
79
94
|
# grid.data # => [
|
80
95
|
# # [ "Name", "Swimwear Sales", "Sportswear Sales", ... ]
|
@@ -95,6 +110,7 @@ module Datagrid
|
|
95
110
|
end
|
96
111
|
|
97
112
|
protected
|
113
|
+
|
98
114
|
def check_scope_defined!(message = nil)
|
99
115
|
message ||= "#{self}.scope is not defined"
|
100
116
|
raise(Datagrid::ConfigurationError, message) unless scope_value
|
@@ -105,7 +121,7 @@ module Datagrid
|
|
105
121
|
child_class.datagrid_attributes = self.datagrid_attributes.clone
|
106
122
|
end
|
107
123
|
|
108
|
-
end
|
124
|
+
end
|
109
125
|
|
110
126
|
module InstanceMethods
|
111
127
|
|
@@ -122,8 +138,7 @@ module Datagrid
|
|
122
138
|
end
|
123
139
|
end
|
124
140
|
|
125
|
-
#
|
126
|
-
# and ordering values
|
141
|
+
# @return [Hash<Symbol, Object>] grid attributes including filter values and ordering values
|
127
142
|
def attributes
|
128
143
|
result = {}
|
129
144
|
self.datagrid_attributes.each do |name|
|
@@ -132,21 +147,24 @@ module Datagrid
|
|
132
147
|
result
|
133
148
|
end
|
134
149
|
|
135
|
-
#
|
150
|
+
# @return [Object] Any datagrid attribute value
|
136
151
|
def [](attribute)
|
137
152
|
self.send(attribute)
|
138
153
|
end
|
139
154
|
|
155
|
+
# Assigns any datagrid attribute
|
156
|
+
# @param attribute [Symbol, String] Datagrid attribute name
|
157
|
+
# @param value [Object] Datagrid attribute value
|
158
|
+
# @return [void]
|
140
159
|
def []=(attribute, value)
|
141
160
|
self.send(:"#{attribute}=", value)
|
142
161
|
end
|
143
162
|
|
144
|
-
#
|
163
|
+
# @return [Object] a scope relation (e.g ActiveRecord::Relation) with all applied filters
|
145
164
|
def assets
|
146
165
|
driver.to_scope(scope)
|
147
166
|
end
|
148
167
|
|
149
|
-
|
150
168
|
# Updates datagrid attributes with a passed hash argument
|
151
169
|
def attributes=(attributes)
|
152
170
|
if respond_to?(:assign_attributes)
|
@@ -163,7 +181,7 @@ module Datagrid
|
|
163
181
|
end
|
164
182
|
|
165
183
|
# Returns serializable query arguments skipping all nil values
|
166
|
-
#
|
184
|
+
# @example
|
167
185
|
# grid = ProductsGrid.new(category: 'dresses', available: true)
|
168
186
|
# grid.as_query # => {category: 'dresses', available: true}
|
169
187
|
def as_query
|
@@ -174,8 +192,8 @@ module Datagrid
|
|
174
192
|
attributes
|
175
193
|
end
|
176
194
|
|
177
|
-
#
|
178
|
-
#
|
195
|
+
# @return [Hash<Symbol, Hash<Symbol, Object>>] query parameters to link this grid from a page
|
196
|
+
# @example
|
179
197
|
# grid = ProductsGrid.new(category: 'dresses', available: true)
|
180
198
|
# Rails.application.routes.url_helpers.products_path(grid.query_params)
|
181
199
|
# # => "/products?products_grid[category]=dresses&products_grid[available]=true"
|
@@ -184,19 +202,18 @@ module Datagrid
|
|
184
202
|
end
|
185
203
|
|
186
204
|
# Redefines scope at instance level
|
187
|
-
#
|
205
|
+
# @example
|
188
206
|
# class MyGrid
|
189
207
|
# scope { Article.order('created_at desc') }
|
190
208
|
# end
|
191
209
|
#
|
192
210
|
# grid = MyGrid.new
|
193
211
|
# grid.scope do |scope|
|
194
|
-
# scope.where(:
|
212
|
+
# scope.where(author_id: current_user.id)
|
195
213
|
# end
|
196
214
|
# grid.assets
|
197
215
|
# # => SELECT * FROM articles WHERE author_id = ?
|
198
216
|
# # ORDER BY created_at desc
|
199
|
-
#
|
200
217
|
def scope(&block)
|
201
218
|
if block_given?
|
202
219
|
current_scope = scope
|
@@ -211,23 +228,27 @@ module Datagrid
|
|
211
228
|
end
|
212
229
|
|
213
230
|
# Resets current instance scope to default scope defined in a class
|
231
|
+
# @return [void]
|
214
232
|
def reset_scope
|
215
233
|
self.scope_value = self.class.scope_value
|
216
234
|
end
|
217
235
|
|
218
|
-
#
|
236
|
+
# @return [Boolean] true if the scope was redefined for this instance of grid object
|
219
237
|
def redefined_scope?
|
220
238
|
self.class.scope_value != scope_value
|
221
239
|
end
|
222
240
|
|
223
|
-
|
241
|
+
# @!visibility private
|
242
|
+
def driver
|
224
243
|
self.class.driver
|
225
244
|
end
|
226
245
|
|
227
|
-
|
246
|
+
# @!visibility private
|
247
|
+
def check_scope_defined!(message = nil)
|
228
248
|
self.class.send :check_scope_defined!, message
|
229
249
|
end
|
230
250
|
|
251
|
+
# @return [String] a datagrid attributes and their values in inspection form
|
231
252
|
def inspect
|
232
253
|
attrs = attributes.map do |key, value|
|
233
254
|
"#{key}: #{value.inspect}"
|
@@ -240,6 +261,6 @@ module Datagrid
|
|
240
261
|
attributes == other.attributes &&
|
241
262
|
scope == other.scope
|
242
263
|
end
|
243
|
-
end
|
264
|
+
end
|
244
265
|
end
|
245
266
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Datagrid
|
2
2
|
module Drivers
|
3
|
-
|
3
|
+
# @!visibility private
|
4
|
+
class Mongoid < AbstractDriver
|
4
5
|
|
5
6
|
def self.match?(scope)
|
6
7
|
return false unless defined?(::Mongoid)
|
@@ -63,7 +64,7 @@ module Datagrid
|
|
63
64
|
return nil unless type
|
64
65
|
{
|
65
66
|
[BigDecimal , String, Symbol, Range, Array, Hash, ] => :string,
|
66
|
-
[Boolean] => :boolean,
|
67
|
+
[::Mongoid::Boolean] => :boolean,
|
67
68
|
|
68
69
|
[Date] => :date,
|
69
70
|
|
data/lib/datagrid/drivers.rb
CHANGED
data/lib/datagrid/engine.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
require "rails/engine"
|
2
2
|
|
3
3
|
module Datagrid
|
4
|
+
# @!private
|
4
5
|
class Engine < ::Rails::Engine
|
5
6
|
initializer "datagrid.helpers" do
|
6
7
|
#TODO: check why it doesn't work
|
7
8
|
ActiveSupport.on_load :action_view do
|
8
9
|
include Datagrid::Helper
|
9
|
-
end
|
10
|
-
end
|
10
|
+
end
|
11
|
+
end
|
11
12
|
end
|
12
13
|
end
|
@@ -1,7 +1,10 @@
|
|
1
|
+
# An error raise when datagrid filter is defined incorrectly and
|
2
|
+
# causes filtering chain to be broken
|
1
3
|
class Datagrid::FilteringError < StandardError
|
2
4
|
end
|
3
5
|
|
4
|
-
|
6
|
+
# @!visibility private
|
7
|
+
class Datagrid::Filters::BaseFilter
|
5
8
|
|
6
9
|
attr_accessor :grid_class, :options, :block, :name
|
7
10
|
|
@@ -26,7 +29,7 @@ class Datagrid::Filters::BaseFilter #:nodoc:
|
|
26
29
|
result = execute(value, scope, grid_object)
|
27
30
|
|
28
31
|
return scope unless result
|
29
|
-
if result
|
32
|
+
if result == Datagrid::Filters::DEFAULT_FILTER_BLOCK
|
30
33
|
result = default_filter(value, scope, grid_object)
|
31
34
|
end
|
32
35
|
unless grid_object.driver.match?(result)
|
@@ -1,17 +1,19 @@
|
|
1
|
-
|
1
|
+
# @!visibility private
|
2
|
+
class Datagrid::Filters::BooleanEnumFilter < Datagrid::Filters::EnumFilter
|
2
3
|
|
3
4
|
YES = "YES"
|
4
5
|
NO = "NO"
|
5
6
|
|
6
7
|
def initialize(report, attribute, options = {}, &block)
|
8
|
+
Datagrid::Utils.warn_once("Datagrid eboolean filter is deprecated in favor of xboolean filter")
|
7
9
|
options[:select] = [YES, NO].map do |key, value|
|
8
|
-
[I18n.t("datagrid.filters.eboolean.#{key.downcase}", :
|
10
|
+
[I18n.t("datagrid.filters.eboolean.#{key.downcase}", default: key.downcase.capitalize), key]
|
9
11
|
end
|
10
12
|
super(report, attribute, options, &block)
|
11
13
|
end
|
12
14
|
|
13
15
|
|
14
16
|
def checkbox_id(value)
|
15
|
-
[object_name, name, value].join('_').underscore
|
17
|
+
[object_name, name, value].join('_').underscore
|
16
18
|
end
|
17
19
|
end
|
@@ -1,15 +1,15 @@
|
|
1
1
|
module Datagrid
|
2
2
|
module Filters
|
3
|
-
|
3
|
+
# @!visibility private
|
4
|
+
module CompositeFilters
|
4
5
|
|
5
6
|
def self.included(base)
|
6
7
|
base.extend ClassMethods
|
7
8
|
base.class_eval do
|
8
|
-
|
9
9
|
end
|
10
|
-
|
11
|
-
end # self.included
|
10
|
+
end
|
12
11
|
|
12
|
+
# @!visibility private
|
13
13
|
module ClassMethods
|
14
14
|
|
15
15
|
def date_range_filters(field, from_options = {}, to_options = {})
|
@@ -41,13 +41,7 @@ module Datagrid
|
|
41
41
|
end
|
42
42
|
options
|
43
43
|
end
|
44
|
-
end
|
45
|
-
|
46
|
-
module InstanceMethods
|
47
|
-
|
48
|
-
|
49
|
-
end # InstanceMethods
|
50
|
-
|
44
|
+
end
|
51
45
|
end
|
52
46
|
end
|
53
47
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
module Datagrid::Filters::SelectOptions
|
2
|
-
|
3
2
|
def select(object)
|
4
3
|
select = self.options[:select]
|
5
4
|
if select.is_a?(Symbol)
|
@@ -11,6 +10,18 @@ module Datagrid::Filters::SelectOptions
|
|
11
10
|
end
|
12
11
|
end
|
13
12
|
|
13
|
+
def select_values(object)
|
14
|
+
options = select(object)
|
15
|
+
groups_used = grouped_choices?(options)
|
16
|
+
options.map do |option|
|
17
|
+
if groups_used
|
18
|
+
option[1].map {|o| option_text_and_value(o)}
|
19
|
+
else
|
20
|
+
option_text_and_value(option)
|
21
|
+
end
|
22
|
+
end.map(&:last)
|
23
|
+
end
|
24
|
+
|
14
25
|
def include_blank
|
15
26
|
unless prompt
|
16
27
|
options.has_key?(:include_blank) ?
|
@@ -21,4 +32,26 @@ module Datagrid::Filters::SelectOptions
|
|
21
32
|
def prompt
|
22
33
|
options.has_key?(:prompt) ? Datagrid::Utils.callable(options[:prompt]) : false
|
23
34
|
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
# Rails built-in method:
|
39
|
+
# https://github.com/rails/rails/blob/94e80269e36caf7b2d22a7ab68e6898d3a824122/actionview/lib/action_view/helpers/form_options_helper.rb#L789
|
40
|
+
# Code reuse is difficult, so it is easier to duplicate it
|
41
|
+
def option_text_and_value(option)
|
42
|
+
# Options are [text, value] pairs or strings used for both.
|
43
|
+
if !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
|
44
|
+
option = option.reject { |e| Hash === e } if Array === option
|
45
|
+
[option.first, option.last]
|
46
|
+
else
|
47
|
+
[option, option]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Rails built-in method:
|
52
|
+
# https://github.com/rails/rails/blob/f95c0b7e96eb36bc3efc0c5beffbb9e84ea664e4/actionview/lib/action_view/helpers/tags/select.rb#L36
|
53
|
+
# Code reuse is difficult, so it is easier to duplicate it
|
54
|
+
def grouped_choices?(choices)
|
55
|
+
!choices.blank? && choices.first.respond_to?(:last) && Array === choices.first.last
|
56
|
+
end
|
24
57
|
end
|
data/lib/datagrid/filters.rb
CHANGED
@@ -31,10 +31,10 @@ module Datagrid
|
|
31
31
|
:dynamic => Filters::DynamicFilter
|
32
32
|
}
|
33
33
|
|
34
|
-
|
35
|
-
end
|
34
|
+
DEFAULT_FILTER_BLOCK = Object.new
|
36
35
|
|
37
|
-
|
36
|
+
# @!visibility private
|
37
|
+
def self.included(base)
|
38
38
|
base.extend ClassMethods
|
39
39
|
base.class_eval do
|
40
40
|
|
@@ -45,13 +45,18 @@ module Datagrid
|
|
45
45
|
|
46
46
|
end
|
47
47
|
base.send :include, InstanceMethods
|
48
|
-
end
|
48
|
+
end
|
49
49
|
|
50
50
|
module ClassMethods
|
51
51
|
|
52
52
|
# Returns filter definition object by name
|
53
53
|
def filter_by_name(attribute)
|
54
|
-
|
54
|
+
if attribute.is_a?(Datagrid::Filters::BaseFilter)
|
55
|
+
unless ancestors.include?(attribute.grid_class)
|
56
|
+
raise "#{attribute.grid_class}##{attribute.name} filter doen't belong to #{self.class}"
|
57
|
+
end
|
58
|
+
return attribute
|
59
|
+
end
|
55
60
|
self.filters.find do |filter|
|
56
61
|
filter.name == attribute.to_sym
|
57
62
|
end
|
@@ -108,7 +113,7 @@ module Datagrid
|
|
108
113
|
end
|
109
114
|
|
110
115
|
def default_filter
|
111
|
-
|
116
|
+
DEFAULT_FILTER_BLOCK
|
112
117
|
end
|
113
118
|
|
114
119
|
def inspect
|
@@ -132,11 +137,12 @@ module Datagrid
|
|
132
137
|
"#{filter.name}: #{filter.type}"
|
133
138
|
end.join(", ")
|
134
139
|
end
|
135
|
-
end
|
140
|
+
end
|
136
141
|
|
137
142
|
module InstanceMethods
|
138
143
|
|
139
|
-
|
144
|
+
# @!visibility private
|
145
|
+
def initialize(*args, &block)
|
140
146
|
self.filters_array = self.class.filters_array.clone
|
141
147
|
self.filters_array.each do |filter|
|
142
148
|
self[filter.name] = filter.default(self)
|
@@ -144,7 +150,8 @@ module Datagrid
|
|
144
150
|
super(*args, &block)
|
145
151
|
end
|
146
152
|
|
147
|
-
|
153
|
+
# @!visibility private
|
154
|
+
def assets
|
148
155
|
apply_filters(super, filters)
|
149
156
|
end
|
150
157
|
|
@@ -178,11 +185,18 @@ module Datagrid
|
|
178
185
|
# Returns select options for specific filter or filter name
|
179
186
|
# If given filter doesn't support select options raises `ArgumentError`
|
180
187
|
def select_options(filter)
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
188
|
+
find_select_filter(filter).select(self)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Sets all options as selected for a filter that has options
|
192
|
+
def select_all(filter)
|
193
|
+
filter = find_select_filter(filter)
|
194
|
+
self[filter.name] = select_values(filter)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Returns all values that can be set to a filter with select options
|
198
|
+
def select_values(filter)
|
199
|
+
find_select_filter(filter).select_values(self)
|
186
200
|
end
|
187
201
|
|
188
202
|
def default_filter
|
@@ -198,12 +212,21 @@ module Datagrid
|
|
198
212
|
|
199
213
|
protected
|
200
214
|
|
215
|
+
def find_select_filter(filter)
|
216
|
+
filter = filter_by_name(filter)
|
217
|
+
unless filter.class.included_modules.include?(::Datagrid::Filters::SelectOptions)
|
218
|
+
raise ::Datagrid::ArgumentError,
|
219
|
+
"#{self.class.name}##{filter.name} with type #{FILTER_TYPES.invert[filter.class].inspect} can not have select options"
|
220
|
+
end
|
221
|
+
filter
|
222
|
+
end
|
223
|
+
|
201
224
|
def apply_filters(current_scope, filters)
|
202
225
|
filters.inject(current_scope) do |result, filter|
|
203
226
|
filter.apply(self, result, filter_value(filter))
|
204
227
|
end
|
205
228
|
end
|
206
|
-
end
|
229
|
+
end
|
207
230
|
|
208
231
|
end
|
209
232
|
end
|
@@ -2,8 +2,12 @@ require "action_view"
|
|
2
2
|
|
3
3
|
module Datagrid
|
4
4
|
module FormBuilder
|
5
|
-
|
6
|
-
#
|
5
|
+
# @param filter_or_attribute [Datagrid::Filters::BaseFilter, String, Symbol] filter object or filter name
|
6
|
+
# @param options [Hash] options of rails form input helper
|
7
|
+
# @return [String] a form input html for the corresponding filter name
|
8
|
+
# * <tt>select</tt> for enum, xboolean filter types
|
9
|
+
# * <tt>check_box</tt> for boolean filter type
|
10
|
+
# * <tt>text_field</tt> for other filter types
|
7
11
|
def datagrid_filter(filter_or_attribute, options = {}, &block)
|
8
12
|
filter = datagrid_get_filter(filter_or_attribute)
|
9
13
|
options = {
|
@@ -15,7 +19,10 @@ module Datagrid
|
|
15
19
|
self.send(filter.form_builder_helper_name, filter, options, &block)
|
16
20
|
end
|
17
21
|
|
18
|
-
#
|
22
|
+
# @param filter_or_attribute [Datagrid::Filters::BaseFilter, String, Symbol] filter object or filter name
|
23
|
+
# @param text [String, nil] label text, defaults to <tt>filter.header</tt>
|
24
|
+
# @param options [Hash] options of rails <tt>label</tt> helper
|
25
|
+
# @return [String] a form label html for the corresponding filter name
|
19
26
|
def datagrid_label(filter_or_attribute, text = nil, **options, &block)
|
20
27
|
filter = datagrid_get_filter(filter_or_attribute)
|
21
28
|
label(filter.name, text || filter.header, **filter.label_options, **options, &block)
|