headmin 0.5.4 → 0.5.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c163e16eb068c93256e3a73a0ac3b58f569cc72c36da590f084ecb13873b8866
4
- data.tar.gz: 9297b7bfd0194980aa843c45e59f9180349bc17e571cfe8f9c75678738de7def
3
+ metadata.gz: 3445263c38deb23953f15bd095f0ab4d35acdba03e57863990afc270b3b0ec2d
4
+ data.tar.gz: 7ff3cbb8b497e7abfec49ee1222f3980b08779627179d788cd332794076c3ec7
5
5
  SHA512:
6
- metadata.gz: b72bc6a71c864837e78619f8bfc7b0d455d4f57d108790f6d6619fbb3fe2acb882d2be87236b8a2d87204b55995fd4234c8617ee93be5a587b6eafa2616aaac4
7
- data.tar.gz: 819ec0caa24dbfa82ac02dec7909d6c888550f87c71bf5eadae0b86b03a179c57b599c5a3af3b418e4435dfc569373a1b744ed5621380382bdf7cff4cb6d229d
6
+ metadata.gz: 6b6d3c45da2fc0d1e3d9d3f69daccfdb689eae2785cc46d19fdec80ffff497987193fcb251eb2576f95d2d7927179d2c0264fa0548c3363240193a0c51e7efcc
7
+ data.tar.gz: 1badf3f039045aaf9ddee10bcf1f2686bd3cd44f0e04db6f893c8e61c4cf8974b3464a427bcf6c66cbc7bfd03e2b502d48bd4edd8a4c26527484feefac74691b
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- headmin (0.5.3)
4
+ headmin (0.5.4)
5
5
  closure_tree (~> 7.4)
6
6
  inline_svg (~> 1.7)
7
7
  redcarpet (~> 3.5)
@@ -1,7 +1,7 @@
1
1
  @charset "UTF-8";
2
2
  @import "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css";
3
3
 
4
- /* sass-plugin-0:/opt/homebrew/var/www/headmin/src/scss/headmin.scss */
4
+ /* sass-plugin-0:/usr/local/var/www/headmin/src/scss/headmin.scss */
5
5
  :root {
6
6
  --bs-blue: #0d6efd;
7
7
  --bs-indigo: #6610f2;
@@ -2,7 +2,11 @@ module Headmin
2
2
  module Pagination
3
3
  def paginate(collection)
4
4
  @records_filtered = collection.count
5
- collection.page(page).per(per_page)
5
+ if collection.is_a?(Array)
6
+ Kaminari.paginate_array(collection).page(page).per(per_page)
7
+ else
8
+ collection.page(page).per(per_page)
9
+ end
6
10
  end
7
11
 
8
12
  def page
@@ -1,6 +1,6 @@
1
1
  module Headmin
2
2
  module Filter
3
- class AssociationView < ViewModel
3
+ class AssociationView < FilterView
4
4
  def base_options
5
5
  keys = %i[name label form]
6
6
  options = to_h.slice(*keys)
@@ -21,12 +21,13 @@ module Headmin
21
21
  }
22
22
 
23
23
  # Methods
24
- def initialize(attribute, params)
25
- @raw_value = params[attribute]
26
- @attribute = attribute
24
+ def initialize(attribute, params, association: nil)
25
+ @attribute = association ? "#{association}_#{attribute}".to_sym : attribute
26
+ @raw_value = params[@attribute]
27
+ @association = association
27
28
  @instructions = []
28
29
 
29
- if params.key?(attribute)
30
+ if params.key?(@attribute)
30
31
  parse(@raw_value)
31
32
  end
32
33
  end
@@ -61,7 +62,8 @@ module Headmin
61
62
  query = build_query(query, collection, instruction)
62
63
  end
63
64
 
64
- collection.where(query)
65
+ collection = collection.joins(@association) if @association
66
+ collection.distinct.where(query)
65
67
  end
66
68
 
67
69
  def cast_value(value)
@@ -74,6 +76,30 @@ module Headmin
74
76
  value
75
77
  end
76
78
 
79
+ def build_query(query, collection, instruction)
80
+ query_operator = convert_to_query_operator(instruction[:operator])
81
+ query_value = convert_to_query_value(instruction[:value], instruction[:operator])
82
+
83
+ query_operator, query_value = process_null_operators(query_operator, query_value)
84
+
85
+ model = collection.is_a?(Class) ? collection : collection.model
86
+
87
+ if @association
88
+ # Association attributes are passed through as {association}_{attribute}, so we need to transform this into {attribute}
89
+ new_attribute = attribute.to_s.gsub("#{@association}_", "").to_sym
90
+ new_model = model.reflect_on_association(@association)
91
+
92
+ # In case the association cannot be found, raise a well defined error
93
+ raise UnknownAssociation if new_model.nil?
94
+
95
+ new_query = new_model.klass.arel_table[new_attribute].send(query_operator, query_value)
96
+ else
97
+ new_query = model.arel_table[attribute].send(query_operator, query_value)
98
+ end
99
+
100
+ query ? query.send(instruction[:conditional], new_query) : new_query
101
+ end
102
+
77
103
  private
78
104
 
79
105
  def parse(string)
@@ -139,18 +165,6 @@ module Headmin
139
165
  process_value(string, operator)
140
166
  end
141
167
 
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
168
  def process_null_operators(operator, value)
155
169
  # In case of null operators (is_null and is_not_null), we have to intercept the operator and value values
156
170
  # and transform them to the correct operator (eq or not_eq) and value (nil)
@@ -237,5 +251,8 @@ module Headmin
237
251
 
238
252
  class NotImplementedMethodError < StandardError
239
253
  end
254
+
255
+ class UnknownAssociation < StandardError
256
+ end
240
257
  end
241
258
  end
@@ -1,6 +1,6 @@
1
1
  module Headmin
2
2
  module Filter
3
- class BooleanView < ViewModel
3
+ class BooleanView < FilterView
4
4
  def base_options
5
5
  keys = %i[name label form]
6
6
  options = to_h.slice(*keys)
@@ -28,10 +28,6 @@ module Headmin
28
28
  @name || attribute
29
29
  end
30
30
 
31
- def label
32
- @label || I18n.t("attributes.#{attribute}", default: name.to_s)
33
- end
34
-
35
31
  def default_base_options
36
32
  {
37
33
  label: label,
@@ -1,6 +1,6 @@
1
1
  module Headmin
2
2
  module Filter
3
- class DateView < ViewModel
3
+ class DateView < FilterView
4
4
  def base_options
5
5
  keys = %i[name label form]
6
6
  options = to_h.slice(*keys)
@@ -23,10 +23,6 @@ module Headmin
23
23
  @name || attribute
24
24
  end
25
25
 
26
- def label
27
- @label || I18n.t("attributes.#{attribute}", default: name.to_s)
28
- end
29
-
30
26
  def default_base_options
31
27
  {
32
28
  label: label,
@@ -0,0 +1,55 @@
1
+ module Headmin
2
+ module Filter
3
+ class Field < Headmin::Filter::Base
4
+ OPERATORS = %w[eq not_eq matches does_not_match]
5
+
6
+ def initialize(attribute, params, association: nil)
7
+ @attribute = association ? "#{association}_#{attribute}".to_sym : attribute
8
+ @attribute = "field_#{attribute}".to_sym
9
+ @raw_value = params[@attribute]
10
+ @association = association
11
+ @instructions = []
12
+
13
+ if params.key?(@attribute)
14
+ parse(@raw_value)
15
+ end
16
+ end
17
+
18
+ def cast_value(value)
19
+ value
20
+ end
21
+
22
+ def display_value(value)
23
+ value.downcase
24
+ end
25
+
26
+ def query(collection)
27
+ return collection unless @instructions.any?
28
+
29
+ query = nil
30
+
31
+ @instructions.each do |instruction|
32
+ query = build_query(query, collection, instruction)
33
+ end
34
+
35
+ collection = collection.joins(:fields)
36
+ collection.where(query)
37
+ end
38
+
39
+ def build_query(query, collection, instruction)
40
+ query_operator = convert_to_query_operator(instruction[:operator])
41
+ query_value = convert_to_query_value(instruction[:value], instruction[:operator])
42
+
43
+ query_operator, query_value = process_null_operators(query_operator, query_value)
44
+
45
+ model = collection.is_a?(Class) ? collection : collection.model
46
+
47
+ new_attribute = attribute.to_s.gsub("field_", "").to_sym
48
+ fields_model = model.reflect_on_association(:fields).klass
49
+ new_query = fields_model.arel_table[:name].matches(new_attribute).and(fields_model.arel_table[:value].send(query_operator, query_value))
50
+
51
+ query ? query.send(instruction[:conditional], new_query) : new_query
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,50 @@
1
+ module Headmin
2
+ module Filter
3
+ class FieldView < FilterView
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
+ "field_#{@name}".to_sym || attribute
24
+ end
25
+
26
+ def default_base_options
27
+ {
28
+ label: label,
29
+ name: "field_#{attribute}".to_sym,
30
+ filter: Headmin::Filter::Field.new("field_#{attribute}".to_sym, @params),
31
+ allowed_operators: Headmin::Filter::Field::OPERATORS - %w[in not_in]
32
+ }
33
+ end
34
+
35
+ def default_input_options
36
+ {
37
+ label: false,
38
+ wrapper: false,
39
+ id: id,
40
+ name: nil,
41
+ data: {
42
+ action: "change->filter#updateHiddenValue",
43
+ filter_target: "value",
44
+ filter_row_target: "original"
45
+ }
46
+ }
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,25 @@
1
+ module Headmin
2
+ module Filter
3
+ class FilterView < ViewModel
4
+ def attribute
5
+ @association ? "#{@association}_#{@attribute}".to_sym : @attribute
6
+ end
7
+
8
+ def label
9
+ @label || I18n.t("attributes.#{attribute}", default: @association ? "#{association_model.model_name.human(count: 1)} - #{association_model.human_attribute_name(@attribute)}" : name.to_s)
10
+ end
11
+
12
+ def reflection
13
+ if @association
14
+ form.object.class.reflect_on_association(@association)
15
+ end
16
+ end
17
+
18
+ def association_model
19
+ if @association
20
+ reflection.klass
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,6 +1,6 @@
1
1
  module Headmin
2
2
  module Filter
3
- class NumberView < ViewModel
3
+ class NumberView < FilterView
4
4
  def base_options
5
5
  keys = %i[name label form]
6
6
  options = to_h.slice(*keys)
@@ -23,10 +23,6 @@ module Headmin
23
23
  @name || attribute
24
24
  end
25
25
 
26
- def label
27
- @label || I18n.t("attributes.#{attribute}", default: name.to_s)
28
- end
29
-
30
26
  def default_base_options
31
27
  {
32
28
  label: label,
@@ -1,6 +1,6 @@
1
1
  module Headmin
2
2
  module Filter
3
- class OptionsView < ViewModel
3
+ class OptionsView < FilterView
4
4
  def base_options
5
5
  keys = %i[name label form]
6
6
  options = to_h.slice(*keys)
@@ -27,10 +27,6 @@ module Headmin
27
27
  @name || attribute
28
28
  end
29
29
 
30
- def label
31
- @label || I18n.t("attributes.#{attribute}", default: name.to_s)
32
- end
33
-
34
30
  def default_base_options
35
31
  {
36
32
  label: label,
@@ -1,6 +1,6 @@
1
1
  module Headmin
2
2
  module Filter
3
- class TextView < ViewModel
3
+ class TextView < FilterView
4
4
  def base_options
5
5
  keys = %i[name label form]
6
6
  options = to_h.slice(*keys)
@@ -23,10 +23,6 @@ module Headmin
23
23
  @name || attribute
24
24
  end
25
25
 
26
- def label
27
- @label || I18n.t("attributes.#{attribute}", default: name.to_s)
28
- end
29
-
30
26
  def default_base_options
31
27
  {
32
28
  label: label,
@@ -13,15 +13,30 @@ module Headmin
13
13
  @param_types = param_types
14
14
  end
15
15
 
16
- def parse(attribute, type)
16
+ def parse(attribute, type, association: nil)
17
17
  class_name = "Headmin::Filter::#{type.to_s.classify}".constantize
18
- class_name.new(attribute, @params)
18
+ class_name.new(attribute, @params, association: association)
19
19
  end
20
20
 
21
21
  def query(collection)
22
22
  @param_types.each do |attribute, type|
23
- filter = parse(attribute, type)
24
- collection = filter.query(collection)
23
+ if type.is_a? Hash
24
+ # We are given attribute filters of an association
25
+ association = attribute
26
+
27
+ # By default, we offer a filter of type association
28
+ association_filter = Headmin::Filter::Association.new(attribute, @params, association: nil)
29
+ collection = association_filter.query(collection)
30
+
31
+ # Query all the passed attribute filters for this association
32
+ type.each do |new_attribute, new_type|
33
+ filter = parse(new_attribute, new_type, association: association)
34
+ collection = filter.query(collection)
35
+ end
36
+ else
37
+ filter = parse(attribute, type)
38
+ collection = filter.query(collection)
39
+ end
25
40
  end
26
41
  collection
27
42
  end
@@ -16,7 +16,7 @@
16
16
  action = local_assigns.has_key?(:url) ? url : request.path
17
17
 
18
18
  begin
19
- model = controller_name.singularize.capitalize.constantize
19
+ model = controller_name.classify.constantize
20
20
  rescue
21
21
  raise "Cannot find class!"
22
22
  end
@@ -0,0 +1,23 @@
1
+ <%
2
+ # headmin/filters/field
3
+ #
4
+ # ==== Required parameters
5
+ # * +form+ - Form object
6
+ # * +attribute+ - Name of the attribute to be filtered
7
+ #
8
+ # ==== Optional parameters
9
+ # * +label+ - Display label
10
+ # * +name+ - Name of the filter parameter
11
+ #
12
+ # ==== Examples
13
+ # Basic version
14
+ # <%= render "headmin/filters", url: admin_orders_path do |form| %#>
15
+ # <%= render "headmin/filters/field", form: form, attribute: :title %#>
16
+ # <% end %#>
17
+
18
+ text = Headmin::Filter::FieldView.new(local_assigns.merge(params: params))
19
+ %>
20
+
21
+ <%= render "headmin/filters/base", text.base_options do |value| %>
22
+ <%= render "headmin/forms/text", text.input_options.merge({value: value}) %>
23
+ <% end %>
@@ -1,3 +1,3 @@
1
1
  module Headmin
2
- VERSION = "0.5.4"
2
+ VERSION = "0.5.5"
3
3
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "headmin",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Admin component library",
5
5
  "module": "app/assets/javascripts/headmin.js",
6
6
  "main": "app/assets/javascripts/headmin.js",
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: headmin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4
4
+ version: 0.5.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jef Vlamings
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-22 00:00:00.000000000 Z
11
+ date: 2022-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: closure_tree
@@ -268,6 +268,9 @@ files:
268
268
  - app/models/headmin/filter/conditional_view.rb
269
269
  - app/models/headmin/filter/date.rb
270
270
  - app/models/headmin/filter/date_view.rb
271
+ - app/models/headmin/filter/field.rb
272
+ - app/models/headmin/filter/field_view.rb
273
+ - app/models/headmin/filter/filter_view.rb
271
274
  - app/models/headmin/filter/flatpickr_view.rb
272
275
  - app/models/headmin/filter/menu_item_view.rb
273
276
  - app/models/headmin/filter/money.rb
@@ -336,6 +339,7 @@ files:
336
339
  - app/views/headmin/filters/_base.html.erb
337
340
  - app/views/headmin/filters/_boolean.html.erb
338
341
  - app/views/headmin/filters/_date.html.erb
342
+ - app/views/headmin/filters/_field.html.erb
339
343
  - app/views/headmin/filters/_flatpickr.html.erb
340
344
  - app/views/headmin/filters/_number.html.erb
341
345
  - app/views/headmin/filters/_options.html.erb