phlexi-display 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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