headmin 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -1
- data/Gemfile.lock +1 -1
- data/app/assets/javascripts/headmin/controllers/date_range_controller.js +12 -6
- data/app/assets/javascripts/headmin/controllers/filter_controller.js +61 -11
- data/app/assets/javascripts/headmin/controllers/filter_row_controller.js +50 -0
- data/app/assets/javascripts/headmin/controllers/flatpickr_controller.js +2 -6
- data/app/assets/javascripts/headmin/controllers/popup_controller.js +3 -1
- data/app/assets/javascripts/headmin/controllers/table_actions_controller.js +16 -21
- data/app/assets/javascripts/headmin/index.js +2 -0
- data/app/assets/javascripts/headmin.js +119 -38
- data/app/assets/stylesheets/headmin/filter.scss +74 -0
- data/app/assets/stylesheets/headmin/general.scss +0 -1
- data/app/assets/stylesheets/headmin/layout/body.scss +5 -0
- data/app/assets/stylesheets/headmin/popup.scss +0 -1
- data/app/assets/stylesheets/headmin.css +67 -2
- data/app/controllers/concerns/headmin/filterable.rb +27 -0
- data/app/models/headmin/filter/base.rb +238 -0
- data/app/models/headmin/filter/base_view.rb +64 -0
- data/app/models/headmin/filter/boolean.rb +15 -0
- data/app/models/headmin/filter/boolean_view.rb +61 -0
- data/app/models/headmin/filter/button_view.rb +25 -0
- data/app/models/headmin/filter/conditional_view.rb +16 -0
- data/app/models/headmin/filter/date.rb +19 -0
- data/app/models/headmin/filter/date_view.rb +52 -0
- data/app/models/headmin/filter/flatpickr_view.rb +54 -0
- data/app/models/headmin/filter/menu_item_view.rb +6 -0
- data/app/models/headmin/filter/money.rb +13 -0
- data/app/models/headmin/filter/number.rb +27 -0
- data/app/models/headmin/filter/number_view.rb +54 -0
- data/app/models/headmin/filter/operator_view.rb +30 -0
- data/app/models/headmin/filter/options_view.rb +61 -0
- data/app/models/headmin/filter/row_view.rb +13 -0
- data/app/models/headmin/filter/search.rb +18 -0
- data/app/models/headmin/filter/search_view.rb +31 -0
- data/app/models/headmin/filter/text.rb +25 -0
- data/app/models/headmin/filter/text_view.rb +53 -0
- data/app/models/headmin/filters.rb +29 -0
- data/app/models/headmin/form/datetime_range_view.rb +25 -0
- data/app/models/headmin/form/datetime_view.rb +45 -0
- data/app/models/headmin/form/flatpickr_range_view.rb +4 -15
- data/app/models/headmin/form/flatpickr_view.rb +3 -12
- data/app/models/view_model.rb +1 -1
- data/app/views/examples/admin.html.erb +13 -13
- data/app/views/examples/auth.html.erb +1 -1
- data/app/views/headmin/_filters.html.erb +6 -6
- data/app/views/headmin/_form.html.erb +2 -2
- data/app/views/headmin/_index.html.erb +1 -1
- data/app/views/headmin/_pagination.html.erb +1 -1
- data/app/views/headmin/_popup.html.erb +2 -2
- data/app/views/headmin/_table.html.erb +1 -1
- data/app/views/headmin/dropdown/_devise.html.erb +8 -8
- data/app/views/headmin/dropdown/_locale.html.erb +4 -4
- data/app/views/headmin/filters/_base.html.erb +95 -0
- data/app/views/headmin/filters/_boolean.html.erb +23 -0
- data/app/views/headmin/filters/_date.html.erb +14 -38
- data/app/views/headmin/filters/_flatpickr.html.erb +15 -48
- data/app/views/headmin/filters/_number.html.erb +23 -0
- data/app/views/headmin/filters/_options.html.erb +24 -0
- data/app/views/headmin/filters/_search.html.erb +14 -12
- data/app/views/headmin/filters/_text.html.erb +23 -0
- data/app/views/headmin/filters/filter/_button.html.erb +9 -10
- data/app/views/headmin/filters/filter/_conditional.html.erb +18 -0
- data/app/views/headmin/filters/filter/_menu_item.html.erb +5 -2
- data/app/views/headmin/filters/filter/_null_select.html.erb +8 -0
- data/app/views/headmin/filters/filter/_operator.html.erb +16 -0
- data/app/views/headmin/filters/filter/_row.html.erb +11 -0
- data/app/views/headmin/forms/_blocks.html.erb +1 -1
- data/app/views/headmin/forms/_date_range.html.erb +3 -3
- data/app/views/headmin/forms/_datetime.html.erb +41 -0
- data/app/views/headmin/forms/_datetime_range.html.erb +40 -0
- data/app/views/headmin/forms/_file.html.erb +3 -3
- data/app/views/headmin/forms/_flatpickr.html.erb +1 -1
- data/app/views/headmin/forms/_flatpickr_range.html.erb +3 -4
- data/app/views/headmin/forms/_label.html.erb +1 -1
- data/app/views/headmin/forms/_repeater.html.erb +12 -12
- data/app/views/headmin/forms/fields/_base.html.erb +1 -1
- data/app/views/headmin/forms/fields/_file.html.erb +2 -2
- data/app/views/headmin/forms/fields/_files.html.erb +2 -2
- data/app/views/headmin/forms/fields/_group.html.erb +5 -5
- data/app/views/headmin/forms/fields/_list.html.erb +4 -4
- data/app/views/headmin/forms/fields/_text.html.erb +2 -2
- data/app/views/headmin/layout/_footer.html.erb +1 -1
- data/app/views/headmin/layout/_main.html.erb +1 -1
- data/app/views/headmin/nav/_dropdown.html.erb +3 -3
- data/app/views/headmin/nav/_item.html.erb +2 -2
- data/app/views/headmin/nav/item/_devise.html.erb +8 -8
- data/app/views/headmin/nav/item/_locale.html.erb +4 -4
- data/app/views/headmin/table/_actions.html.erb +3 -6
- data/app/views/headmin/table/actions/_export.html.erb +1 -1
- data/app/views/headmin/table/body/_row.html.erb +3 -3
- data/app/views/headmin/table/foot/_id.html.erb +1 -1
- data/app/views/headmin/views/devise/confirmations/_new.html.erb +1 -1
- data/app/views/headmin/views/devise/passwords/_edit.html.erb +2 -2
- data/app/views/headmin/views/devise/passwords/_new.html.erb +1 -1
- data/app/views/headmin/views/devise/registrations/_edit.html.erb +4 -4
- data/app/views/headmin/views/devise/registrations/_new.html.erb +3 -3
- data/app/views/headmin/views/devise/sessions/_new.html.erb +3 -3
- data/app/views/headmin/views/devise/unlocks/_new.html.erb +1 -1
- data/config/locales/en.yml +4 -0
- data/config/locales/headmin/dropdown/en.yml +6 -0
- data/config/locales/headmin/dropdown/nl.yml +6 -0
- data/config/locales/headmin/filters/en.yml +26 -1
- data/config/locales/headmin/filters/nl.yml +26 -1
- data/config/locales/headmin/layout/en.yml +0 -9
- data/config/locales/headmin/layout/nl.yml +0 -9
- data/config/locales/headmin/nav/en.yml +7 -0
- data/config/locales/headmin/nav/nl.yml +7 -0
- data/config/locales/nl.yml +4 -0
- data/lib/generators/templates/views/layouts/auth.html.erb +1 -1
- data/lib/headmin/version.rb +1 -1
- data/package.json +1 -1
- metadata +42 -6
- data/app/controllers/concerns/headmin/filter.rb +0 -5
- data/app/controllers/concerns/headmin/searchable.rb +0 -15
- data/app/views/headmin/filters/_select.html.erb +0 -45
- data/app/views/headmin/filters/filter/_template.html.erb +0 -13
@@ -13103,7 +13103,6 @@ input[type=search] {
|
|
13103
13103
|
}
|
13104
13104
|
html {
|
13105
13105
|
height: 100%;
|
13106
|
-
background: red;
|
13107
13106
|
}
|
13108
13107
|
.list-group-item.active .text-muted {
|
13109
13108
|
color: #fff !important;
|
@@ -13117,6 +13116,11 @@ mark,
|
|
13117
13116
|
background: rgba(0, 0, 0, 0.03);
|
13118
13117
|
height: 100vh;
|
13119
13118
|
overflow-y: scroll;
|
13119
|
+
display: flex;
|
13120
|
+
flex-direction: column;
|
13121
|
+
}
|
13122
|
+
.content {
|
13123
|
+
flex-grow: 1;
|
13120
13124
|
}
|
13121
13125
|
.sidebar {
|
13122
13126
|
font-size: 0.99rem;
|
@@ -13310,6 +13314,54 @@ body.empty {
|
|
13310
13314
|
.h-filter-popup.closed {
|
13311
13315
|
display: none;
|
13312
13316
|
}
|
13317
|
+
.h-filter-row {
|
13318
|
+
padding: 0.5rem 0 0.5rem 0;
|
13319
|
+
position: relative;
|
13320
|
+
flex-wrap: unset;
|
13321
|
+
display: flex;
|
13322
|
+
gap: 10px;
|
13323
|
+
}
|
13324
|
+
.h-filter-row:first-child {
|
13325
|
+
padding-top: 0;
|
13326
|
+
}
|
13327
|
+
.h-filter-row:hover .h-filter-add-input,
|
13328
|
+
.h-filter-row:hover .h-filter-remove-input {
|
13329
|
+
display: block;
|
13330
|
+
}
|
13331
|
+
.h-filter-row input,
|
13332
|
+
.h-filter-row select:not(.h-filter-operator) {
|
13333
|
+
min-width: 200px;
|
13334
|
+
}
|
13335
|
+
.h-filter-operator {
|
13336
|
+
width: 62px;
|
13337
|
+
}
|
13338
|
+
.h-filter-conditional {
|
13339
|
+
position: relative;
|
13340
|
+
display: flex;
|
13341
|
+
justify-content: center;
|
13342
|
+
align-items: center;
|
13343
|
+
}
|
13344
|
+
.h-filter-conditional select {
|
13345
|
+
width: 50px;
|
13346
|
+
}
|
13347
|
+
.h-filter-conditional::before {
|
13348
|
+
content: "";
|
13349
|
+
position: absolute;
|
13350
|
+
top: calc(50% - 1px);
|
13351
|
+
left: 0;
|
13352
|
+
width: calc(50% - 35px);
|
13353
|
+
height: 1px;
|
13354
|
+
background: rgba(0, 0, 0, 0.2);
|
13355
|
+
}
|
13356
|
+
.h-filter-conditional::after {
|
13357
|
+
content: "";
|
13358
|
+
position: absolute;
|
13359
|
+
top: calc(50% - 1px);
|
13360
|
+
left: calc(50% + 35px);
|
13361
|
+
width: calc(50% - 35px);
|
13362
|
+
height: 1px;
|
13363
|
+
background: rgba(0, 0, 0, 0.2);
|
13364
|
+
}
|
13313
13365
|
.h-filter-remove {
|
13314
13366
|
border-left: 1px solid #d1d5db;
|
13315
13367
|
padding-left: 8px;
|
@@ -13318,11 +13370,24 @@ body.empty {
|
|
13318
13370
|
.h-filter-remove i::before {
|
13319
13371
|
font-size: 0.8em;
|
13320
13372
|
}
|
13373
|
+
.h-filter-add-input {
|
13374
|
+
position: absolute !important;
|
13375
|
+
top: calc(100% - 12px);
|
13376
|
+
left: calc(50% - 17px);
|
13377
|
+
z-index: 9;
|
13378
|
+
display: none;
|
13379
|
+
}
|
13380
|
+
.h-filter-remove-input {
|
13381
|
+
position: absolute !important;
|
13382
|
+
top: calc(50% - 15px);
|
13383
|
+
left: calc(100% - 4px);
|
13384
|
+
z-index: 9;
|
13385
|
+
display: none;
|
13386
|
+
}
|
13321
13387
|
.h-popup {
|
13322
13388
|
padding: 10px;
|
13323
13389
|
background: #ffffff;
|
13324
13390
|
z-index: 1070;
|
13325
|
-
width: 276px;
|
13326
13391
|
font-family: var(--bs-font-sans-serif);
|
13327
13392
|
font-style: normal;
|
13328
13393
|
font-weight: 400;
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Headmin
|
2
|
+
module Filterable
|
3
|
+
# Will create a Headmin::Filters object with a default configuration for "id" and "search"
|
4
|
+
#
|
5
|
+
# Example:
|
6
|
+
#
|
7
|
+
# orders = Order
|
8
|
+
# orders = filter(orders, {
|
9
|
+
# status: :text,
|
10
|
+
# price: :number,
|
11
|
+
# in_stock: :boolean
|
12
|
+
# })
|
13
|
+
# @orders = orders.all
|
14
|
+
|
15
|
+
def filter(collection, filter_types = {})
|
16
|
+
type_hash = default_filter_types.merge(filter_types)
|
17
|
+
Headmin::Filters.new(params, type_hash).query(collection)
|
18
|
+
end
|
19
|
+
|
20
|
+
def default_filter_types
|
21
|
+
{
|
22
|
+
id: :number,
|
23
|
+
search: :search
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
module Headmin
|
2
|
+
module Filter
|
3
|
+
class Base
|
4
|
+
# Constants
|
5
|
+
OPERATORS = %w[eq not_eq gt gteq lt lteq between not_between in not_in matches does_not_match is_null is_not_null starts_with ends_with]
|
6
|
+
|
7
|
+
OPERATORS_CONVERT_TO = {
|
8
|
+
convert_to_range: %w[between not_between],
|
9
|
+
convert_to_array: %w[in not_in],
|
10
|
+
convert_to_value: %w[eq not_eq gt gteq lt lteq matches does_not_match is_null is_not_null starts_with ends_with]
|
11
|
+
}
|
12
|
+
|
13
|
+
QUERY_VALUE_CONVERT_TO = {
|
14
|
+
convert_value_to_match: %w[matches does_not_match],
|
15
|
+
convert_to_starts: %w[starts_with],
|
16
|
+
convert_to_ends: %w[ends_with]
|
17
|
+
}
|
18
|
+
|
19
|
+
QUERY_OPERATOR_CONVERT_TO = {
|
20
|
+
matches: %w[starts_with ends_with]
|
21
|
+
}
|
22
|
+
|
23
|
+
# Methods
|
24
|
+
def initialize(attribute, params)
|
25
|
+
@raw_value = params[attribute]
|
26
|
+
@attribute = attribute
|
27
|
+
@instructions = []
|
28
|
+
|
29
|
+
if params.key?(attribute)
|
30
|
+
parse(@raw_value)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :raw_value
|
35
|
+
attr_reader :attribute
|
36
|
+
|
37
|
+
def values
|
38
|
+
@instructions.map { |instruction| instruction[:value] }.compact
|
39
|
+
end
|
40
|
+
|
41
|
+
def operators
|
42
|
+
@instructions.map { |instruction| instruction[:operator] }.compact
|
43
|
+
end
|
44
|
+
|
45
|
+
def conditionals
|
46
|
+
@instructions.map { |instruction| instruction[:conditional] }.compact
|
47
|
+
end
|
48
|
+
|
49
|
+
def string(display_values = [])
|
50
|
+
build_instructions_string(display_values)
|
51
|
+
end
|
52
|
+
|
53
|
+
attr_reader :instructions
|
54
|
+
|
55
|
+
def query(collection)
|
56
|
+
return collection unless @instructions.any?
|
57
|
+
|
58
|
+
query = nil
|
59
|
+
|
60
|
+
@instructions.each do |instruction|
|
61
|
+
query = build_query(query, collection, instruction)
|
62
|
+
end
|
63
|
+
|
64
|
+
collection.where(query)
|
65
|
+
end
|
66
|
+
|
67
|
+
def cast_value(value)
|
68
|
+
# This method has to be implemented by the child.
|
69
|
+
|
70
|
+
raise NotImplementedMethodError, "Method cast_value has to be implemented by its child."
|
71
|
+
end
|
72
|
+
|
73
|
+
def display_value(value)
|
74
|
+
value
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def parse(string)
|
80
|
+
instructions = split_instructions(string)
|
81
|
+
parse_instructions(instructions)
|
82
|
+
end
|
83
|
+
|
84
|
+
def split_instructions(string)
|
85
|
+
# Regex: It divides the string into separate parts:
|
86
|
+
# - Parts starting with +AND+ OR +OR+ until the next occurrence of +
|
87
|
+
# - First part of the string till the first occurrence of +
|
88
|
+
string.scan(/\+AND\+[^+]* | \+OR\+[^+]* | ^[^+]*/x)
|
89
|
+
end
|
90
|
+
|
91
|
+
def parse_instructions(instructions)
|
92
|
+
instructions.map { |instruction| parse_instruction(instruction) }
|
93
|
+
end
|
94
|
+
|
95
|
+
def parse_instruction(string)
|
96
|
+
conditional_raw, operator_raw, value_raw = split_instruction(string)
|
97
|
+
|
98
|
+
conditional = parse_conditional(conditional_raw)
|
99
|
+
operator = parse_operator(operator_raw, value_raw)
|
100
|
+
value = parse_value(value_raw, operator)
|
101
|
+
|
102
|
+
@instructions << {conditional: conditional, operator: operator, value: value}
|
103
|
+
end
|
104
|
+
|
105
|
+
def split_instruction(string)
|
106
|
+
# Regex: takes out the +AND+ / +OR+, if not found returns nil
|
107
|
+
conditional_raw = string.scan(/\+AND\+|\+OR\+/)[0]
|
108
|
+
|
109
|
+
string = string.remove("+AND+").remove("+OR+")
|
110
|
+
|
111
|
+
# Regex: takes out the value before the : character
|
112
|
+
operator_raw = string.match(/^[^:]*/)[0] == string ? nil : string.match(/^[^:]*/)[0]
|
113
|
+
value_raw = string.remove("#{operator_raw}:")
|
114
|
+
|
115
|
+
[conditional_raw, operator_raw, value_raw]
|
116
|
+
end
|
117
|
+
|
118
|
+
def parse_conditional(string)
|
119
|
+
string.present? ? string.delete("+").downcase : nil
|
120
|
+
end
|
121
|
+
|
122
|
+
def parse_operator(string, value)
|
123
|
+
if self.class::OPERATORS.include?(string)
|
124
|
+
# Operator is recognized, return the operator
|
125
|
+
string
|
126
|
+
elsif string.nil?
|
127
|
+
# There was no operator present, default operator is eq
|
128
|
+
"eq"
|
129
|
+
else
|
130
|
+
# There was an operator passed but it was not recognized, raise exception
|
131
|
+
raise OperatorNotSupportedError, "This operator is not supported by this filter."
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def parse_value(string, operator)
|
136
|
+
if operator == "is_null" || operator == "is_not_null"
|
137
|
+
return string.to_i
|
138
|
+
end
|
139
|
+
process_value(string, operator)
|
140
|
+
end
|
141
|
+
|
142
|
+
def build_query(query, collection, instruction)
|
143
|
+
query_operator = convert_to_query_operator(instruction[:operator])
|
144
|
+
query_value = convert_to_query_value(instruction[:value], instruction[:operator])
|
145
|
+
|
146
|
+
query_operator, query_value = process_null_operators(query_operator, query_value)
|
147
|
+
|
148
|
+
model = collection.is_a?(Class) ? collection : collection.model
|
149
|
+
new_query = model.arel_table[attribute].send(query_operator, query_value)
|
150
|
+
|
151
|
+
query ? query.send(instruction[:conditional], new_query) : new_query
|
152
|
+
end
|
153
|
+
|
154
|
+
def process_null_operators(operator, value)
|
155
|
+
# In case of null operators (is_null and is_not_null), we have to intercept the operator and value values
|
156
|
+
# and transform them to the correct operator (eq or not_eq) and value (nil)
|
157
|
+
if operator == "is_null" && value == 1
|
158
|
+
["eq", nil]
|
159
|
+
elsif operator == "is_null" && value == 0
|
160
|
+
["not_eq", nil]
|
161
|
+
elsif operator == "is_not_null" && value == 1
|
162
|
+
["not_eq", nil]
|
163
|
+
elsif operator == "is_not_null" && value == 0
|
164
|
+
["eq", nil]
|
165
|
+
else
|
166
|
+
[operator, value]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def convert_to_range(value)
|
171
|
+
array = value.split(",")
|
172
|
+
cast_value(array[0])..cast_value(array[1])
|
173
|
+
end
|
174
|
+
|
175
|
+
def convert_to_array(value)
|
176
|
+
array = value.split(",")
|
177
|
+
array.map { |e| cast_value(e) }
|
178
|
+
end
|
179
|
+
|
180
|
+
def convert_to_value(value)
|
181
|
+
cast_value(value)
|
182
|
+
end
|
183
|
+
|
184
|
+
def convert_to_query_value(value, operator)
|
185
|
+
value_converter = self.class::QUERY_VALUE_CONVERT_TO.find { |key, values| values.include?(operator) }&.first
|
186
|
+
|
187
|
+
if value_converter.present?
|
188
|
+
send(value_converter, value)
|
189
|
+
else
|
190
|
+
value
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def convert_value_to_match(value)
|
195
|
+
"%#{value}%"
|
196
|
+
end
|
197
|
+
|
198
|
+
def convert_to_query_operator(operator)
|
199
|
+
self.class::QUERY_OPERATOR_CONVERT_TO.find { |key, values| values.include?(operator) }&.first || operator
|
200
|
+
end
|
201
|
+
|
202
|
+
def match_operator_with_convert(operator)
|
203
|
+
self.class::OPERATORS_CONVERT_TO.find { |key, values| values.include?(operator) }&.first
|
204
|
+
end
|
205
|
+
|
206
|
+
def process_value(value, operator)
|
207
|
+
send(match_operator_with_convert(operator), value)
|
208
|
+
end
|
209
|
+
|
210
|
+
def build_instructions_string(display_values)
|
211
|
+
strings = @instructions.map { |instruction| build_instruction_string(instruction, display_values) }
|
212
|
+
strings.join(" ")
|
213
|
+
end
|
214
|
+
|
215
|
+
def build_instruction_string(instruction, display_values)
|
216
|
+
conditional = instruction[:conditional].present? ? "#{I18n.t("headmin.filters.conditionals.#{instruction[:conditional]}")} " : nil
|
217
|
+
operator = I18n.t("headmin.filters.operators.#{instruction[:operator]}")
|
218
|
+
value = display_values[:display_values].find { |item| item.second == instruction[:value] }
|
219
|
+
value = value.present? ? value.first : instruction[:value]
|
220
|
+
|
221
|
+
value = if instruction[:operator] == "is_null" || instruction[:operator] == "is_not_null"
|
222
|
+
# In case of special operators (is_null & is_not_null), we intercept the value
|
223
|
+
value == 1 ? I18n.t("headmin.filters.values.yes") : I18n.t("headmin.filters.values.no")
|
224
|
+
else
|
225
|
+
display_value(value)
|
226
|
+
end
|
227
|
+
|
228
|
+
"#{conditional}#{operator} “#{value}”"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
class OperatorNotSupportedError < StandardError
|
233
|
+
end
|
234
|
+
|
235
|
+
class NotImplementedMethodError < StandardError
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Headmin
|
2
|
+
module Filter
|
3
|
+
class BaseView < ViewModel
|
4
|
+
def menu_item_options
|
5
|
+
keys = %i[name label]
|
6
|
+
options = to_h.slice(*keys)
|
7
|
+
default_menu_item_options.merge(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def filter_template_options
|
11
|
+
keys = %i[name label]
|
12
|
+
options = to_h.slice(*keys)
|
13
|
+
default_filter_template_options.merge(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def filter_operator_options
|
17
|
+
keys = %i[allowed_operators]
|
18
|
+
options = to_h.slice(*keys)
|
19
|
+
default_filter_operator_options.merge(options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def filter_button_options
|
23
|
+
keys = %i[name label display_values filter]
|
24
|
+
options = to_h.slice(*keys)
|
25
|
+
default_filter_button_options.merge(options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def label
|
29
|
+
@label || name.to_s.humanize
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def default_menu_item_options
|
35
|
+
{
|
36
|
+
name: nil,
|
37
|
+
label: label
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def default_filter_template_options
|
42
|
+
{
|
43
|
+
name: nil,
|
44
|
+
label: label
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def default_filter_operator_options
|
49
|
+
{
|
50
|
+
allowed_operators: []
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def default_filter_button_options
|
55
|
+
{
|
56
|
+
name: nil,
|
57
|
+
label: label,
|
58
|
+
display_values: [],
|
59
|
+
filter: nil
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Headmin
|
2
|
+
module Filter
|
3
|
+
class BooleanView < ViewModel
|
4
|
+
def base_options
|
5
|
+
keys = %i[name label form]
|
6
|
+
options = to_h.slice(*keys)
|
7
|
+
default_base_options.merge(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def input_options
|
11
|
+
keys = %i[form]
|
12
|
+
options = to_h.slice(*keys)
|
13
|
+
default_input_options.merge(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def value
|
19
|
+
# If the value is nil we want to display the first option (which is true)
|
20
|
+
@value.nil? ? @value = true : ""
|
21
|
+
end
|
22
|
+
|
23
|
+
def id
|
24
|
+
"#{name}_value"
|
25
|
+
end
|
26
|
+
|
27
|
+
def name
|
28
|
+
@name || attribute
|
29
|
+
end
|
30
|
+
|
31
|
+
def label
|
32
|
+
@label || I18n.t("attributes.#{attribute}", default: name.to_s)
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_base_options
|
36
|
+
{
|
37
|
+
label: label,
|
38
|
+
name: attribute,
|
39
|
+
filter: Headmin::Filter::Boolean.new(name, @params),
|
40
|
+
display_values: [[I18n.t("headmin.filters.values.true"), true], [I18n.t("headmin.filters.values.false"), false]],
|
41
|
+
allowed_operators: Headmin::Filter::Boolean::OPERATORS
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def default_input_options
|
46
|
+
{
|
47
|
+
label: false,
|
48
|
+
wrapper: false,
|
49
|
+
id: id,
|
50
|
+
collection: [[I18n.t("headmin.filters.values.true"), 1], [I18n.t("headmin.filters.values.false"), 0]],
|
51
|
+
selected: value ? 1 : 0,
|
52
|
+
data: {
|
53
|
+
action: "change->filter#updateHiddenValue",
|
54
|
+
filter_target: "value",
|
55
|
+
filter_row_target: "original"
|
56
|
+
}
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Headmin
|
2
|
+
module Filter
|
3
|
+
class ButtonView < ViewModel
|
4
|
+
def id
|
5
|
+
@id || SecureRandom.hex
|
6
|
+
end
|
7
|
+
|
8
|
+
def name
|
9
|
+
@name.to_sym
|
10
|
+
end
|
11
|
+
|
12
|
+
def label
|
13
|
+
@label || name.to_s.humanize
|
14
|
+
end
|
15
|
+
|
16
|
+
def display_values
|
17
|
+
@display_values || []
|
18
|
+
end
|
19
|
+
|
20
|
+
def filter
|
21
|
+
@filter || nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Headmin
|
2
|
+
module Filter
|
3
|
+
class ConditionalView < ViewModel
|
4
|
+
def selected
|
5
|
+
@selected ? "+#{@selected.to_s.upcase}+" : nil
|
6
|
+
end
|
7
|
+
|
8
|
+
def values
|
9
|
+
{
|
10
|
+
"+AND+": I18n.t("headmin.filters.filter.conditional.and"),
|
11
|
+
"+OR+": I18n.t("headmin.filters.filter.conditional.or")
|
12
|
+
}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Headmin
|
2
|
+
module Filter
|
3
|
+
class Date < Headmin::Filter::Base
|
4
|
+
OPERATORS = %w[eq not_eq gt gteq lt lteq between not_between in not_in is_null is_not_null]
|
5
|
+
|
6
|
+
def cast_value(value)
|
7
|
+
value.present? ? value.to_date : ::Date.current
|
8
|
+
rescue
|
9
|
+
raise TypeError, "The value that was passed to this filter is not a date"
|
10
|
+
end
|
11
|
+
|
12
|
+
def display_value(value)
|
13
|
+
# This uses the default date format of headmin.
|
14
|
+
# Can be overwritten by setting default date format of the application.
|
15
|
+
I18n.l(value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Headmin
|
2
|
+
module Filter
|
3
|
+
class DateView < ViewModel
|
4
|
+
def base_options
|
5
|
+
keys = %i[name label form]
|
6
|
+
options = to_h.slice(*keys)
|
7
|
+
default_base_options.merge(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def input_options
|
11
|
+
keys = %i[form]
|
12
|
+
options = to_h.slice(*keys)
|
13
|
+
default_input_options.merge(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def id
|
19
|
+
"#{name}_value"
|
20
|
+
end
|
21
|
+
|
22
|
+
def name
|
23
|
+
@name || attribute
|
24
|
+
end
|
25
|
+
|
26
|
+
def label
|
27
|
+
@label || I18n.t("attributes.#{attribute}", default: name.to_s)
|
28
|
+
end
|
29
|
+
|
30
|
+
def default_base_options
|
31
|
+
{
|
32
|
+
label: label,
|
33
|
+
name: attribute,
|
34
|
+
filter: Headmin::Filter::Date.new(name, @params),
|
35
|
+
allowed_operators: Headmin::Filter::Date::OPERATORS - %w[in not_in between not_between]
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def default_input_options
|
40
|
+
{
|
41
|
+
label: false,
|
42
|
+
wrapper: false,
|
43
|
+
id: id,
|
44
|
+
data: {action: "change->filter#updateHiddenValue",
|
45
|
+
filter_target: "value",
|
46
|
+
filter_row_target: "original"},
|
47
|
+
class: "form-control"
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Headmin
|
2
|
+
module Filter
|
3
|
+
class FlatpickrView < ViewModel
|
4
|
+
def base_options
|
5
|
+
keys = %i[name label form]
|
6
|
+
options = to_h.slice(*keys)
|
7
|
+
default_base_options.merge(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def input_options
|
11
|
+
keys = %i[form]
|
12
|
+
options = to_h.slice(*keys)
|
13
|
+
default_input_options.merge(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def id
|
19
|
+
"#{name}_value"
|
20
|
+
end
|
21
|
+
|
22
|
+
def name
|
23
|
+
@name || attribute
|
24
|
+
end
|
25
|
+
|
26
|
+
def label
|
27
|
+
@label || I18n.t("attributes.#{attribute}", default: name.to_s)
|
28
|
+
end
|
29
|
+
|
30
|
+
def default_base_options
|
31
|
+
{
|
32
|
+
label: label,
|
33
|
+
name: attribute,
|
34
|
+
filter: Headmin::Filter::Date.new(name, @params),
|
35
|
+
allowed_operators: Headmin::Filter::Date::OPERATORS - %w[in not_in between not_between]
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def default_input_options
|
40
|
+
{
|
41
|
+
label: false,
|
42
|
+
wrapper: false,
|
43
|
+
id: id,
|
44
|
+
data: {
|
45
|
+
action: "change->filter#updateHiddenValue",
|
46
|
+
filter_target: "value",
|
47
|
+
filter_row_target: "original"
|
48
|
+
},
|
49
|
+
class: "form-control"
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|