phlexi-display 0.0.1 → 0.0.3

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/lib/phlexi/display/base.rb +37 -169
  3. data/lib/phlexi/display/builder.rb +117 -0
  4. data/lib/phlexi/display/components/base.rb +1 -42
  5. data/lib/phlexi/display/components/concerns/displays_value.rb +54 -0
  6. data/lib/phlexi/display/components/date_time.rb +49 -0
  7. data/lib/phlexi/display/components/{error.rb → description.rb} +5 -5
  8. data/lib/phlexi/display/components/hint.rb +1 -1
  9. data/lib/phlexi/display/components/label.rb +3 -15
  10. data/lib/phlexi/display/components/number.rb +37 -0
  11. data/lib/phlexi/display/components/placeholder.rb +15 -0
  12. data/lib/phlexi/display/components/string.rb +17 -0
  13. data/lib/phlexi/display/components/wrapper.rb +4 -18
  14. data/lib/phlexi/display/theme.rb +22 -0
  15. data/lib/phlexi/display/version.rb +1 -1
  16. data/lib/phlexi/display.rb +1 -1
  17. metadata +24 -43
  18. data/lib/phlexi/display/components/checkbox.rb +0 -48
  19. data/lib/phlexi/display/components/collection_checkboxes.rb +0 -44
  20. data/lib/phlexi/display/components/collection_radio_buttons.rb +0 -35
  21. data/lib/phlexi/display/components/concerns/handles_array_input.rb +0 -21
  22. data/lib/phlexi/display/components/concerns/handles_input.rb +0 -53
  23. data/lib/phlexi/display/components/concerns/has_options.rb +0 -37
  24. data/lib/phlexi/display/components/concerns/submits_form.rb +0 -39
  25. data/lib/phlexi/display/components/file_input.rb +0 -32
  26. data/lib/phlexi/display/components/full_error.rb +0 -21
  27. data/lib/phlexi/display/components/input.rb +0 -84
  28. data/lib/phlexi/display/components/input_array.rb +0 -45
  29. data/lib/phlexi/display/components/radio_button.rb +0 -41
  30. data/lib/phlexi/display/components/select.rb +0 -69
  31. data/lib/phlexi/display/components/submit_button.rb +0 -41
  32. data/lib/phlexi/display/components/textarea.rb +0 -34
  33. data/lib/phlexi/display/field_options/associations.rb +0 -21
  34. data/lib/phlexi/display/field_options/autofocus.rb +0 -18
  35. data/lib/phlexi/display/field_options/collection.rb +0 -54
  36. data/lib/phlexi/display/field_options/disabled.rb +0 -18
  37. data/lib/phlexi/display/field_options/errors.rb +0 -92
  38. data/lib/phlexi/display/field_options/hints.rb +0 -22
  39. data/lib/phlexi/display/field_options/inferred_types.rb +0 -155
  40. data/lib/phlexi/display/field_options/labels.rb +0 -28
  41. data/lib/phlexi/display/field_options/length.rb +0 -53
  42. data/lib/phlexi/display/field_options/limit.rb +0 -66
  43. data/lib/phlexi/display/field_options/min_max.rb +0 -92
  44. data/lib/phlexi/display/field_options/multiple.rb +0 -65
  45. data/lib/phlexi/display/field_options/pattern.rb +0 -38
  46. data/lib/phlexi/display/field_options/placeholder.rb +0 -18
  47. data/lib/phlexi/display/field_options/readonly.rb +0 -18
  48. data/lib/phlexi/display/field_options/required.rb +0 -37
  49. data/lib/phlexi/display/field_options/themes.rb +0 -207
  50. data/lib/phlexi/display/field_options/validators.rb +0 -48
  51. data/lib/phlexi/display/option_mapper.rb +0 -154
  52. data/lib/phlexi/display/structure/dom.rb +0 -62
  53. data/lib/phlexi/display/structure/field_builder.rb +0 -236
  54. data/lib/phlexi/display/structure/field_collection.rb +0 -54
  55. data/lib/phlexi/display/structure/namespace.rb +0 -135
  56. data/lib/phlexi/display/structure/namespace_collection.rb +0 -48
  57. data/lib/phlexi/display/structure/node.rb +0 -18
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module Components
6
- class RadioButton < Input
7
- def view_template
8
- input(**attributes, value: @checked_value)
9
- end
10
-
11
- def extract_input(...)
12
- # when a radio is not submitted, nothing is returned
13
- super.compact
14
- end
15
-
16
- protected
17
-
18
- def build_input_attributes
19
- attributes[:type] = :radio
20
- super
21
-
22
- @checked_value = (attributes.key?(:checked_value) ? attributes.delete(:checked_value) : "1").to_s
23
-
24
- # this is a hack to workaround the fact that radio cannot be indexed/multiple
25
- attributes[:name] = attributes[:name].sub(/\[\]$/, "")
26
- attributes[:value] = @checked_value
27
- attributes[:checked] = attributes.fetch(:checked) { checked? }
28
- end
29
-
30
- def checked?
31
- field.dom.value == @checked_value
32
- end
33
-
34
- def normalize_input(...)
35
- input_value = super
36
- (input_value == @checked_value) ? input_value : nil
37
- end
38
- end
39
- end
40
- end
41
- end
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module Components
6
- class Select < Base
7
- include Concerns::HandlesInput
8
- include Concerns::HandlesArrayInput
9
- include Concerns::HasOptions
10
-
11
- def view_template
12
- PUI::Select.new
13
- end
14
-
15
- protected
16
-
17
- def options
18
- option_mapper.each do |value, label|
19
- option(selected: selected?(value), value: value) { label }
20
- end
21
- end
22
-
23
- def blank_option(&)
24
- option(selected: field.value.nil?, &)
25
- end
26
-
27
- def build_attributes
28
- super
29
-
30
- attributes[:id] = field.dom.id
31
- attributes[:name] = field.dom.name
32
-
33
- build_select_attributes
34
- end
35
-
36
- def build_select_attributes
37
- @include_blank = attributes.delete(:include_blank)
38
- @include_hidden = attributes.delete(:include_hidden)
39
-
40
- attributes[:autofocus] = attributes.fetch(:autofocus, field.focused?)
41
- attributes[:required] = attributes.fetch(:required, field.required?)
42
- attributes[:disabled] = attributes.fetch(:disabled, field.disabled?)
43
- attributes[:multiple] = attributes.fetch(:multiple, field.multiple?)
44
- attributes[:size] = attributes.fetch(:size, field.limit)
45
- end
46
-
47
- def blank_option_text
48
- field.placeholder
49
- end
50
-
51
- def include_blank?
52
- return true if @include_blank == true
53
-
54
- @include_blank != false && !attributes[:multiple]
55
- end
56
-
57
- def include_hidden?
58
- return false if @include_hidden == false
59
-
60
- attributes[:multiple]
61
- end
62
-
63
- def normalize_input(input_value)
64
- attributes[:multiple] ? normalize_array_input(input_value) : normalize_simple_input(input_value)
65
- end
66
- end
67
- end
68
- end
69
- end
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module Components
6
- class SubmitButton < Base
7
- include Concerns::SubmitsDisplay
8
-
9
- def view_template(&content)
10
- content ||= proc { submit_type_label }
11
- button(**attributes, &content)
12
- end
13
-
14
- protected
15
-
16
- def build_attributes
17
- root_key = field.dom.lineage.first.respond_to?(:dom_id) ? field.dom.lineage.first.dom_id : field.dom.lineage.first.key
18
- attributes.fetch(:id) { attributes[:id] = "#{root_key}_submit_button" }
19
- attributes[:class] = tokens(
20
- component_name,
21
- submit_type_value,
22
- attributes[:class]
23
- )
24
-
25
- build_button_attributes
26
- end
27
-
28
- def build_button_attributes
29
- formmethod = attributes[:formmethod]
30
- if formmethod.present? && !/post|get/i.match?(formmethod) && !attributes.key?(:name) && !attributes.key?(:value)
31
- attributes.merge! formmethod: :post, name: "_method", value: formmethod
32
- end
33
-
34
- attributes.fetch(:name) { attributes[:name] = "commit" }
35
- attributes.fetch(:value) { attributes[:value] = submit_type_label }
36
- attributes.fetch(:type) { attributes[:type] = :submit }
37
- end
38
- end
39
- end
40
- end
41
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module Components
6
- class Textarea < Base
7
- def view_template
8
- textarea(**attributes) { field.dom.value }
9
- end
10
-
11
- protected
12
-
13
- def build_attributes
14
- super
15
-
16
- attributes[:id] = field.dom.id
17
- attributes[:name] = field.dom.name
18
-
19
- build_textarea_attributes
20
- end
21
-
22
- def build_textarea_attributes
23
- attributes[:placeholder] = attributes.fetch(:placeholder, field.placeholder)
24
- attributes[:autofocus] = attributes.fetch(:autofocus, field.focused?)
25
- attributes[:minlength] = attributes.fetch(:minlength, field.minlength)
26
- attributes[:maxlength] = attributes.fetch(:maxlength, field.maxlength)
27
- attributes[:readonly] = attributes.fetch(:readonly, field.readonly?)
28
- attributes[:required] = attributes.fetch(:required, field.required?)
29
- attributes[:disabled] = attributes.fetch(:disabled, field.disabled?)
30
- end
31
- end
32
- end
33
- end
34
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module FieldOptions
6
- module Associations
7
- protected
8
-
9
- def reflection
10
- @reflection ||= find_association_reflection
11
- end
12
-
13
- def find_association_reflection
14
- if object.class.respond_to?(:reflect_on_association)
15
- object.class.reflect_on_association(key)
16
- end
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module FieldOptions
6
- module Autofocus
7
- def focused?
8
- options[:autofocus] == true
9
- end
10
-
11
- def focused!(autofocus = true)
12
- options[:autofocus] = autofocus
13
- self
14
- end
15
- end
16
- end
17
- end
18
- end
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module FieldOptions
6
- module Collection
7
- def collection(collection = nil)
8
- if collection.nil?
9
- options[:collection] = options.fetch(:collection) { infer_collection }
10
- else
11
- options[:collection] = collection
12
- self
13
- end
14
- end
15
-
16
- private
17
-
18
- def infer_collection
19
- collection_value_from_association || collection_value_from_validator
20
- end
21
-
22
- def collection_value_from_association
23
- return unless reflection
24
-
25
- relation = reflection.klass.all
26
-
27
- if reflection.respond_to?(:scope) && reflection.scope
28
- relation = if reflection.scope.parameters.any?
29
- reflection.klass.instance_exec(object, &reflection.scope)
30
- else
31
- reflection.klass.instance_exec(&reflection.scope)
32
- end
33
- else
34
- order = reflection.options[:order]
35
- conditions = reflection.options[:conditions]
36
- conditions = object.instance_exec(&conditions) if conditions.respond_to?(:call)
37
-
38
- relation = relation.where(conditions) if relation.respond_to?(:where) && conditions.present?
39
- relation = relation.order(order) if relation.respond_to?(:order)
40
- end
41
-
42
- relation
43
- end
44
-
45
- def collection_value_from_validator
46
- return unless has_validators?
47
-
48
- inclusion_validator = find_validator(:inclusion)
49
- inclusion_validator.options[:in] || inclusion_validator.options[:within] if inclusion_validator
50
- end
51
- end
52
- end
53
- end
54
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module FieldOptions
6
- module Disabled
7
- def disabled?
8
- options[:disabled] == true
9
- end
10
-
11
- def disabled!(disabled = true)
12
- options[:disabled] = disabled
13
- self
14
- end
15
- end
16
- end
17
- end
18
- end
@@ -1,92 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module FieldOptions
6
- module Errors
7
- def custom_error(error)
8
- options[:error] = error
9
- self
10
- end
11
-
12
- def error
13
- error_text if has_errors?
14
- end
15
-
16
- def full_error
17
- full_error_text if has_errors?
18
- end
19
-
20
- def has_errors?
21
- object_with_errors? || !object && has_custom_error?
22
- end
23
-
24
- def show_errors?
25
- options[:error] != false
26
- end
27
-
28
- def valid?
29
- !has_errors? && has_value?
30
- end
31
-
32
- protected
33
-
34
- def error_text
35
- text = has_custom_error? ? options[:error] : errors.send(error_method)
36
-
37
- "#{options[:error_prefix]} #{text}".lstrip
38
- end
39
-
40
- def full_error_text
41
- has_custom_error? ? options[:error] : full_errors.send(error_method)
42
- end
43
-
44
- def object_with_errors?
45
- object&.respond_to?(:errors) && errors.present?
46
- end
47
-
48
- def error_method
49
- options[:error_method] || :first
50
- end
51
-
52
- def errors
53
- @errors ||= (errors_on_attribute + errors_on_association).compact
54
- end
55
-
56
- def full_errors
57
- @full_errors ||= (full_errors_on_attribute + full_errors_on_association).compact
58
- end
59
-
60
- def errors_on_attribute
61
- object.errors[key] || []
62
- end
63
-
64
- def full_errors_on_attribute
65
- object.errors.full_messages_for(key)
66
- end
67
-
68
- def errors_on_association
69
- reflection ? object.errors[reflection.name] : []
70
- end
71
-
72
- def full_errors_on_association
73
- reflection ? object.errors.full_messages_for(reflection.name) : []
74
- end
75
-
76
- def has_custom_error?
77
- options[:error].is_a?(String)
78
- end
79
-
80
- # Determines if the associated object is in a valid state
81
- #
82
- # An object is considered valid if it is persisted and has no errors.
83
- #
84
- # @return [Boolean] true if the object is persisted and has no errors, false otherwise
85
- def object_valid?
86
- object.respond_to?(:persisted?) && object.persisted? &&
87
- object.respond_to?(:errors) && !object.errors.empty?
88
- end
89
- end
90
- end
91
- end
92
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module FieldOptions
6
- module Hints
7
- def hint(hint = nil)
8
- if hint.nil?
9
- options[:hint]
10
- else
11
- options[:hint] = hint
12
- self
13
- end
14
- end
15
-
16
- def has_hint?
17
- options[:hint] != false && hint.present?
18
- end
19
- end
20
- end
21
- end
22
- end
@@ -1,155 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bigdecimal"
4
-
5
- module Phlexi
6
- module Display
7
- module FieldOptions
8
- module InferredTypes
9
- def inferred_db_type
10
- @inferred_db_type ||= infer_db_type
11
- end
12
-
13
- def inferred_input_component
14
- @inferred_input_component ||= infer_input_component
15
- end
16
-
17
- def inferred_input_type
18
- @inferred_input_type ||= infer_input_type(inferred_input_component)
19
- end
20
-
21
- private
22
-
23
- # this returns the element type
24
- # one of :input, :textarea, :select, :botton
25
- def infer_input_component
26
- return :select unless collection.blank?
27
-
28
- case inferred_db_type
29
- when :text, :json, :jsonb, :hstore
30
- :textarea
31
- else
32
- :input
33
- end
34
- end
35
-
36
- # this only applies when input_component is `:input`
37
- # resolves the type attribute of input components
38
- def infer_input_type(component)
39
- case inferred_db_type
40
- when :string
41
- infer_string_input_type(key)
42
- when :integer, :float, :decimal
43
- :number
44
- when :date
45
- :date
46
- when :datetime
47
- :datetime
48
- when :time
49
- :time
50
- when :boolean
51
- :checkbox
52
- else
53
- :text
54
- end
55
- end
56
-
57
- def infer_db_type
58
- if object.class.respond_to?(:columns_hash)
59
- # ActiveRecord object
60
- column = object.class.columns_hash[key.to_s]
61
- return column.type if column
62
- end
63
-
64
- if object.class.respond_to?(:attribute_types)
65
- # ActiveModel::Attributes
66
- custom_type = object.class.attribute_types[key.to_s]
67
- return custom_type.type if custom_type
68
- end
69
-
70
- # Check if object responds to the key
71
- if object.respond_to?(key)
72
- # Fallback to inferring type from the value
73
- return infer_db_type_from_value(object.send(key))
74
- end
75
-
76
- # Default to string if we can't determine the type
77
- :string
78
- end
79
-
80
- def infer_db_type_from_value(value)
81
- case value
82
- when Integer
83
- :integer
84
- when Float, BigDecimal
85
- :float
86
- when TrueClass, FalseClass
87
- :boolean
88
- when Date
89
- :date
90
- when Time, DateTime
91
- :datetime
92
- else
93
- :string
94
- end
95
- end
96
-
97
- def infer_string_input_type(key)
98
- key = key.to_s.downcase
99
-
100
- return :password if is_password_field?
101
-
102
- custom_type = custom_string_input_type(key)
103
- return custom_type if custom_type
104
-
105
- if has_validators?
106
- infer_string_input_type_from_validations
107
- else
108
- :text
109
- end
110
- end
111
-
112
- def custom_string_input_type(key)
113
- custom_mappings = {
114
- /url$|^link|^site/ => :url,
115
- /^email/ => :email,
116
- /^search/ => :search,
117
- /phone|tel(ephone)?/ => :tel,
118
- /^time/ => :time,
119
- /^date/ => :date,
120
- /^number|_count$|_amount$/ => :number,
121
- /^color/ => :color
122
- }
123
-
124
- custom_mappings.each do |pattern, type|
125
- return type if key.match?(pattern)
126
- end
127
-
128
- nil
129
- end
130
-
131
- def infer_string_input_type_from_validations
132
- if attribute_validators.find { |v| v.kind == :numericality }
133
- :number
134
- elsif attribute_validators.find { |v| v.kind == :format && v.options[:with] == URI::MailTo::EMAIL_REGEXP }
135
- :email
136
- else
137
- :text
138
- end
139
- end
140
-
141
- def is_password_field?
142
- key = self.key.to_s.downcase
143
-
144
- exact_matches = ["password"]
145
- prefixes = ["encrypted_"]
146
- suffixes = ["_password", "_digest", "_hash"]
147
-
148
- exact_matches.include?(key) ||
149
- prefixes.any? { |prefix| key.start_with?(prefix) } ||
150
- suffixes.any? { |suffix| key.end_with?(suffix) }
151
- end
152
- end
153
- end
154
- end
155
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module FieldOptions
6
- module Labels
7
- def label(label = nil)
8
- if label.nil?
9
- options[:label] = options.fetch(:label) { calculate_label }
10
- else
11
- options[:label] = label
12
- self
13
- end
14
- end
15
-
16
- private
17
-
18
- def calculate_label
19
- if object.class.respond_to?(:human_attribute_name)
20
- object.class.human_attribute_name(key.to_s, {base: object})
21
- else
22
- key.to_s.humanize
23
- end
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module FieldOptions
6
- module Length
7
- def minlength(minlength = nil)
8
- if minlength.nil?
9
- options[:minlength] = options.fetch(:minlength) { calculate_minlength }
10
- else
11
- options[:minlength] = minlength
12
- self
13
- end
14
- end
15
-
16
- def maxlength(maxlength = nil)
17
- if maxlength.nil?
18
- options[:maxlength] = options.fetch(:maxlength) { calculate_maxlength }
19
- else
20
- options[:maxlength] = maxlength
21
- self
22
- end
23
- end
24
-
25
- private
26
-
27
- def calculate_minlength
28
- minimum_length_value_from(find_length_validator)
29
- end
30
-
31
- def minimum_length_value_from(length_validator)
32
- if length_validator
33
- length_validator.options[:is] || length_validator.options[:minimum]
34
- end
35
- end
36
-
37
- def calculate_maxlength
38
- maximum_length_value_from(find_length_validator)
39
- end
40
-
41
- def maximum_length_value_from(length_validator)
42
- if length_validator
43
- length_validator.options[:is] || length_validator.options[:maximum]
44
- end
45
- end
46
-
47
- def find_length_validator
48
- find_validator(:length)
49
- end
50
- end
51
- end
52
- end
53
- end