phlexi-display 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.ruby-version +1 -0
  4. data/Appraisals +8 -0
  5. data/CHANGELOG.md +5 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +13 -0
  8. data/Rakefile +14 -0
  9. data/TODO +0 -0
  10. data/config.ru +6 -0
  11. data/gemfiles/default.gemfile +5 -0
  12. data/gemfiles/rails_7.gemfile +8 -0
  13. data/lib/phlexi/display/base.rb +243 -0
  14. data/lib/phlexi/display/components/base.rb +51 -0
  15. data/lib/phlexi/display/components/checkbox.rb +48 -0
  16. data/lib/phlexi/display/components/collection_checkboxes.rb +44 -0
  17. data/lib/phlexi/display/components/collection_radio_buttons.rb +35 -0
  18. data/lib/phlexi/display/components/concerns/handles_array_input.rb +21 -0
  19. data/lib/phlexi/display/components/concerns/handles_input.rb +53 -0
  20. data/lib/phlexi/display/components/concerns/has_options.rb +37 -0
  21. data/lib/phlexi/display/components/concerns/submits_form.rb +39 -0
  22. data/lib/phlexi/display/components/error.rb +21 -0
  23. data/lib/phlexi/display/components/file_input.rb +32 -0
  24. data/lib/phlexi/display/components/full_error.rb +21 -0
  25. data/lib/phlexi/display/components/hint.rb +21 -0
  26. data/lib/phlexi/display/components/input.rb +84 -0
  27. data/lib/phlexi/display/components/input_array.rb +45 -0
  28. data/lib/phlexi/display/components/label.rb +27 -0
  29. data/lib/phlexi/display/components/radio_button.rb +41 -0
  30. data/lib/phlexi/display/components/select.rb +69 -0
  31. data/lib/phlexi/display/components/submit_button.rb +41 -0
  32. data/lib/phlexi/display/components/textarea.rb +34 -0
  33. data/lib/phlexi/display/components/wrapper.rb +31 -0
  34. data/lib/phlexi/display/field_options/associations.rb +21 -0
  35. data/lib/phlexi/display/field_options/autofocus.rb +18 -0
  36. data/lib/phlexi/display/field_options/collection.rb +54 -0
  37. data/lib/phlexi/display/field_options/disabled.rb +18 -0
  38. data/lib/phlexi/display/field_options/errors.rb +92 -0
  39. data/lib/phlexi/display/field_options/hints.rb +22 -0
  40. data/lib/phlexi/display/field_options/inferred_types.rb +155 -0
  41. data/lib/phlexi/display/field_options/labels.rb +28 -0
  42. data/lib/phlexi/display/field_options/length.rb +53 -0
  43. data/lib/phlexi/display/field_options/limit.rb +66 -0
  44. data/lib/phlexi/display/field_options/min_max.rb +92 -0
  45. data/lib/phlexi/display/field_options/multiple.rb +65 -0
  46. data/lib/phlexi/display/field_options/pattern.rb +38 -0
  47. data/lib/phlexi/display/field_options/placeholder.rb +18 -0
  48. data/lib/phlexi/display/field_options/readonly.rb +18 -0
  49. data/lib/phlexi/display/field_options/required.rb +37 -0
  50. data/lib/phlexi/display/field_options/themes.rb +207 -0
  51. data/lib/phlexi/display/field_options/validators.rb +48 -0
  52. data/lib/phlexi/display/option_mapper.rb +154 -0
  53. data/lib/phlexi/display/structure/dom.rb +62 -0
  54. data/lib/phlexi/display/structure/field_builder.rb +236 -0
  55. data/lib/phlexi/display/structure/field_collection.rb +54 -0
  56. data/lib/phlexi/display/structure/namespace.rb +135 -0
  57. data/lib/phlexi/display/structure/namespace_collection.rb +48 -0
  58. data/lib/phlexi/display/structure/node.rb +18 -0
  59. data/lib/phlexi/display/version.rb +7 -0
  60. data/lib/phlexi/display.rb +31 -0
  61. data/lib/phlexi-display.rb +3 -0
  62. data/sig/phlexi/display.rbs +6 -0
  63. metadata +262 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b140e54c49ca3a4a8808a12bb7a91dd8d250c91e2d7d29a8d7f9be2c20a7dbf5
4
+ data.tar.gz: 9c70cec19581873bb9b93fc887571d6c8d5d65c95a25576bdf26a67e2bb30046
5
+ SHA512:
6
+ metadata.gz: 466d34728adaedd7c4669a7419ec53929e49e1b4466135e5a7cc1eb42c88aa196b344ca4e29d8f27fb56a1e83437500678a2b003ca95fc82bbf70ab1ad017afc
7
+ data.tar.gz: 047a28cd2c6afba47d9b6c8a833437ac2c009e626f747663dc9f506ef8d805b7979b8f59e4b0fe6e59a8180538d7e66cf323aba83e26e671e567ff12a3236a2c
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.2.2
data/Appraisals ADDED
@@ -0,0 +1,8 @@
1
+ appraise "default" do
2
+ # gem "phlex", "~> 1.10"
3
+ end
4
+
5
+ appraise "rails-7" do
6
+ gem "rails", "~> 7.1.3", ">= 7.1.3.4"
7
+ gem "sqlite3", "~> 1.4"
8
+ end
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.2.0] - 2024-07-31
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Stefan Froelich
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # Phlexi::Display
2
+
3
+ Phlexi::Display is a flexible and powerful display builder for Ruby applications. It provides a more customizable and extensible way to create data displays.
4
+
5
+ [![Ruby](https://github.com/radioactive-labs/phlexi-display/actions/workflows/main.yml/badge.svg)](https://github.com/radioactive-labs/phlexi-display/actions/workflows/main.yml)
6
+
7
+ ## Contributing
8
+
9
+ Bug reports and pull requests are welcome on GitHub at https://github.com/radioactive-labs/phlexi-display.
10
+
11
+ ## License
12
+
13
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+ require "standard/rake"
6
+
7
+ task default: %i[test standard]
8
+
9
+ # https://juincc.medium.com/how-to-setup-minitest-for-your-gems-development-f29c4bee13c2
10
+ Rake::TestTask.new do |t|
11
+ t.libs << "test"
12
+ t.test_files = FileList["test/**/*_test.rb"]
13
+ t.verbose = true
14
+ end
data/TODO ADDED
File without changes
data/config.ru ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems"
4
+ require "bundler"
5
+
6
+ Bundler.require :default, :development
@@ -0,0 +1,5 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 7.1.3", ">= 7.1.3.4"
6
+ gem "sqlite3", "~> 1.4"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,243 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/delegation"
4
+ require "active_support/string_inquirer"
5
+ require "active_support/core_ext/hash/deep_merge"
6
+ require "active_support/core_ext/string/inflections"
7
+
8
+ module Phlexi
9
+ module Display
10
+ # A form component for building flexible and customizable forms.
11
+ #
12
+ # @example Basic usage
13
+ # Phlexi::Display.new(user, action: '/users', method: 'post') do |f|
14
+ # render field(:name).placeholder("Name").input_tag
15
+ # render field(:email).placeholder("Email").input_tag
16
+ # end
17
+ #
18
+ # @attr_reader [Symbol] key The form's key, derived from the record or explicitly set
19
+ # @attr_reader [ActiveModel::Model, nil] object The form's associated object
20
+ class Base < COMPONENT_BASE
21
+ class Namespace < Structure::Namespace; end
22
+
23
+ class FieldBuilder < Structure::FieldBuilder; end
24
+
25
+ attr_reader :key, :object
26
+
27
+ delegate :field, :submit_button, :nest_one, :nest_many, to: :@namespace
28
+
29
+ # Initializes a new Display instance.
30
+ #
31
+ # @param record [ActiveModel::Model, Symbol, String] The form's associated record or key
32
+ # @param action [String, nil] The form's action URL
33
+ # @param method [String, nil] The form's HTTP method
34
+ # @param attributes [Hash] Additional HTML attributes for the form tag
35
+ # @param options [Hash] Additional options for form configuration
36
+ # @option options [String] :class CSS classes for the form
37
+ # @option options [Class] :namespace_klass Custom namespace class
38
+ # @option options [Class] :builder_klass Custom field builder class
39
+ def initialize(record, action: nil, method: nil, attributes: {}, **options)
40
+ @form_action = action
41
+ @form_method = method
42
+ @form_class = options.delete(:class)
43
+ @attributes = attributes
44
+ @namespace_klass = options.delete(:namespace_klass) || default_namespace_klass
45
+ @builder_klass = options.delete(:builder_klass) || default_builder_klass
46
+ @options = options
47
+
48
+ initialize_object_and_key(record)
49
+ initialize_namespace
50
+ initialize_attributes
51
+ end
52
+
53
+ # Renders the form template.
54
+ #
55
+ # @return [void]
56
+ def view_template
57
+ form_tag { form_template }
58
+ end
59
+
60
+ # Executes the form's content block.
61
+ # Override this in subclasses to defie a static form.
62
+ #
63
+ # @return [void]
64
+ def form_template
65
+ instance_exec(&@_content_block) if @_content_block
66
+ end
67
+
68
+ # Renders the form tag with its contents.
69
+ #
70
+ # @yield The form's content
71
+ # @return [void]
72
+ def form_tag(&)
73
+ form(**form_attributes) do
74
+ render_hidden_method_field
75
+ render_authenticity_token if authenticity_token?
76
+ yield
77
+ end
78
+ end
79
+
80
+ def extract_input(params)
81
+ call unless @_rendered
82
+ @namespace.extract_input(params)
83
+ end
84
+
85
+ protected
86
+
87
+ attr_reader :options, :attributes, :namespace_klass, :builder_klass
88
+
89
+ # Initializes the object and key based on the given record.
90
+ #
91
+ # @param record [ActiveModel::Model, Symbol, String] The form's associated record or key
92
+ # @return [void]
93
+ def initialize_object_and_key(record)
94
+ # always pop these keys
95
+ # add support for `as` to make it more rails friendly
96
+ @key = options.delete(:key) || options.delete(:as)
97
+
98
+ case record
99
+ when String, Symbol
100
+ @object = nil
101
+ @key = record
102
+ else
103
+ @object = record
104
+ if @key.nil?
105
+ unless object.respond_to?(:model_name) && object.model_name.respond_to?(:param_key) && object.model_name.param_key.present?
106
+ raise ArgumentError, "record must respond to #model_name.param_key with a non nil value or set `key` option e.g. Phlexi::Display(record, key: :record)"
107
+ end
108
+ @key = object.model_name.param_key
109
+ end
110
+ end
111
+ @key = @key.to_sym
112
+ end
113
+
114
+ # Initializes the namespace for the form.
115
+ #
116
+ # @return [void]
117
+ def initialize_namespace
118
+ @namespace = namespace_klass.root(key, object: object, builder_klass: builder_klass)
119
+ end
120
+
121
+ # Initializes form attributes.
122
+ #
123
+ # @return [void]
124
+ def initialize_attributes
125
+ attributes.fetch(:accept_charset) { attributes[:accept_charset] = "UTF-8" }
126
+ end
127
+
128
+ # Determines the form's action URL.
129
+ #
130
+ # @return [String, nil] The form's action URL
131
+ def form_action
132
+ puts ""
133
+ # if @form_action != false
134
+ # @form_action ||= if options[:format].nil?
135
+ # polymorphic_path(object, {})
136
+ # else
137
+ # polymorphic_path(object, format: options[:format])
138
+ # end
139
+ # end
140
+ @form_action
141
+ end
142
+
143
+ # Determines the form's HTTP method.
144
+ #
145
+ # @return [ActiveSupport::StringInquirer] The form's HTTP method
146
+ def form_method
147
+ @form_method ||= (object_form_method || "get").to_s.downcase
148
+ ActiveSupport::StringInquirer.new(@form_method)
149
+ end
150
+
151
+ # Retrieves the form's CSS classes.
152
+ #
153
+ # @return [String] The form's CSS classes
154
+ attr_reader :form_class
155
+
156
+ # Checks if the authenticity token should be included.
157
+ #
158
+ # @return [Boolean] True if the authenticity token should be included, false otherwise
159
+ def authenticity_token?
160
+ defined?(helpers) && options.fetch(:authenticity_token) { !form_method.get? }
161
+ end
162
+
163
+ # Retrieves the authenticity token.
164
+ #
165
+ # @return [String] The authenticity token
166
+ def authenticity_token
167
+ options.fetch(:authenticity_token) { helpers.form_authenticity_token }
168
+ end
169
+
170
+ # Renders the authenticity token field.
171
+ #
172
+ # @param name [String] The name attribute for the authenticity token field
173
+ # @param value [String] The value for the authenticity token field
174
+ # @return [void]
175
+ def authenticity_token_field(name = "authenticity_token", value = authenticity_token)
176
+ input(name: name, value: value, type: "hidden", hidden: true)
177
+ end
178
+
179
+ # Determines the appropriate form method based on the object's state.
180
+ #
181
+ # @return [String, nil] The appropriate form method
182
+ def object_form_method
183
+ if object.respond_to?(:persisted?)
184
+ object.persisted? ? "patch" : "post"
185
+ elsif object.present?
186
+ "post"
187
+ end
188
+ end
189
+
190
+ # Renders the hidden method field for non-standard HTTP methods.
191
+ #
192
+ # @return [void]
193
+ def render_hidden_method_field
194
+ return if standard_form_method?
195
+ input(name: "_method", value: form_method, type: "hidden", hidden: true)
196
+ end
197
+
198
+ # Checks if the form method is standard (GET or POST).
199
+ #
200
+ # @return [Boolean] True if the form method is standard, false otherwise
201
+ def standard_form_method?
202
+ form_method.get? || form_method.post?
203
+ end
204
+
205
+ # Returns the standardized form method for the HTML form tag.
206
+ #
207
+ # @return [String] The standardized form method
208
+ def standardized_form_method
209
+ standard_form_method? ? form_method : "post"
210
+ end
211
+
212
+ # Generates the form attributes hash.
213
+ #
214
+ # @return [Hash] The form attributes
215
+ def form_attributes
216
+ {
217
+ id: @namespace.dom_id,
218
+ action: form_action,
219
+ method: standardized_form_method,
220
+ class: form_class,
221
+ **attributes
222
+ }
223
+ end
224
+
225
+ # Renders the authenticity token if required.
226
+ #
227
+ # @return [void]
228
+ def render_authenticity_token
229
+ authenticity_token_field
230
+ end
231
+
232
+ private
233
+
234
+ def default_namespace_klass
235
+ self.class::Namespace
236
+ end
237
+
238
+ def default_builder_klass
239
+ self.class::FieldBuilder
240
+ end
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexi
4
+ module Display
5
+ module Components
6
+ class Base < COMPONENT_BASE
7
+ attr_reader :field, :attributes
8
+
9
+ def initialize(field, **attributes)
10
+ @field = field
11
+ @attributes = attributes
12
+
13
+ build_attributes
14
+ append_attribute_classes
15
+ end
16
+
17
+ protected
18
+
19
+ def build_attributes
20
+ attributes.fetch(:id) { attributes[:id] = "#{field.dom.id}_#{component_name}" }
21
+ end
22
+
23
+ def append_attribute_classes
24
+ return if attributes[:class] == false
25
+
26
+ attributes[:class] = tokens(
27
+ component_name,
28
+ attributes[:class],
29
+ -> { attributes[:required] } => "required",
30
+ -> { !attributes[:required] } => "optional",
31
+ -> { field.has_errors? } => "invalid",
32
+ -> { attributes[:readonly] } => "readonly",
33
+ -> { attributes[:disabled] } => "disabled"
34
+ )
35
+ end
36
+
37
+ def component_name
38
+ @component_name ||= self.class.name.demodulize.underscore
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+
46
+
47
+ User
48
+ name: :string
49
+ validate :name, presence: true
50
+
51
+ f.input :name
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexi
4
+ module Display
5
+ module Components
6
+ class Checkbox < Input
7
+ def view_template
8
+ input(type: :hidden, name: attributes[:name], value: @unchecked_value, autocomplete: "off", hidden: true) if include_hidden?
9
+ input(**attributes, value: @checked_value)
10
+ end
11
+
12
+ protected
13
+
14
+ def build_input_attributes
15
+ attributes[:type] = :checkbox
16
+ super
17
+
18
+ @include_hidden = attributes.delete(:include_hidden)
19
+ @checked_value = (attributes.key?(:checked_value) ? attributes.delete(:checked_value) : "1").to_s
20
+ @unchecked_value = (attributes.key?(:unchecked_value) ? attributes.delete(:unchecked_value) : "0").to_s
21
+
22
+ attributes[:value] = @checked_value
23
+ attributes[:checked] = attributes.fetch(:checked) { checked? }
24
+ end
25
+
26
+ def include_hidden?
27
+ @include_hidden != false
28
+ end
29
+
30
+ def checked?
31
+ return false if field.dom.value == @unchecked_value
32
+
33
+ if @checked_value == "1" # using default values
34
+ # handle nils, numbers and booleans
35
+ !["", "0", "false"].include?(field.dom.value)
36
+ else # custom value, so explicit match
37
+ field.dom.value == @checked_value
38
+ end
39
+ end
40
+
41
+ def normalize_input(...)
42
+ input_value = super
43
+ [@checked_value, @unchecked_value].include?(input_value) ? input_value : @unchecked_value
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexi
4
+ module Display
5
+ module Components
6
+ class CollectionCheckboxes < Base
7
+ include Concerns::HandlesInput
8
+ include Concerns::HandlesArrayInput
9
+ include Concerns::HasOptions
10
+
11
+ def view_template
12
+ div(**attributes.slice(:id, :class)) do
13
+ field.multi(option_mapper.values) do |builder|
14
+ render builder.hidden_field_tag if builder.index == 0
15
+
16
+ field = builder.field(
17
+ label: option_mapper[builder.key],
18
+ # We set the attributes here so they are applied to all input components even if the user decides to use a block
19
+ input_attributes: {
20
+ checked_value: builder.key,
21
+ include_hidden: false,
22
+ checked: selected?(builder.key)
23
+ }
24
+ )
25
+ if block_given?
26
+ yield field
27
+ else
28
+ render field.checkbox_tag
29
+ render field.label_tag
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ protected
36
+
37
+ def build_attributes
38
+ super
39
+ attributes[:multiple] = true
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexi
4
+ module Display
5
+ module Components
6
+ class CollectionRadioButtons < Base
7
+ include Concerns::HandlesInput
8
+ include Concerns::HasOptions
9
+
10
+ def view_template
11
+ div(**attributes.slice(:id, :class)) do
12
+ field.multi(option_mapper.values) do |builder|
13
+ render builder.hidden_field_tag if builder.index == 0
14
+
15
+ field = builder.field(
16
+ label: option_mapper[builder.key],
17
+ # We set the attributes here so they are applied to all input components even if the user decides to use a block
18
+ input_attributes: {
19
+ checked_value: builder.key,
20
+ checked: selected?(builder.key)
21
+ }
22
+ )
23
+ if block_given?
24
+ yield field
25
+ else
26
+ render field.radio_button_tag
27
+ render field.label_tag
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexi
4
+ module Display
5
+ module Components
6
+ module Concerns
7
+ module HandlesArrayInput
8
+ protected
9
+
10
+ def normalize_input(input_value)
11
+ normalize_array_input(input_value)
12
+ end
13
+
14
+ def normalize_array_input(input_value)
15
+ Array(input_value).map { |nested_input_value| normalize_simple_input(nested_input_value) }.compact
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexi
4
+ module Display
5
+ module Components
6
+ module Concerns
7
+ module HandlesInput
8
+ # Collects parameters matching the input field's param key.
9
+ #
10
+ # @param params [Hash] the parameters to collect from.
11
+ # @return [Hash] the collected parameters.
12
+ def extract_input(params)
13
+ # # Handles multi parameter attributes
14
+ # # https://www.cookieshq.co.uk/posts/rails-spelunking-date-select
15
+ # # https://www.cookieshq.co.uk/posts/multiparameter-attributes
16
+
17
+ # # Matches
18
+ # # - parameter
19
+ # # - parameter(1)
20
+ # # - parameter(2)
21
+ # # - parameter(1i)
22
+ # # - parameter(2f)
23
+ # regex = /^#{param}(\(\d+[if]?\))?$/
24
+ # keys = params.select { |key, _| regex.match?(key) }.keys
25
+ # params.slice(*keys)
26
+
27
+ params ||= {}
28
+ {input_param => normalize_input(params[field.key])}
29
+ end
30
+
31
+ protected
32
+
33
+ def build_attributes
34
+ super
35
+ @input_param = attributes.delete(:input_param) || field.key
36
+ end
37
+
38
+ def input_param
39
+ @input_param
40
+ end
41
+
42
+ def normalize_input(input_value)
43
+ normalize_simple_input(input_value)
44
+ end
45
+
46
+ def normalize_simple_input(input_value)
47
+ input_value.to_s.presence
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexi
4
+ module Display
5
+ module Components
6
+ module Concerns
7
+ module HasOptions
8
+ protected
9
+
10
+ def build_attributes
11
+ super
12
+ @collection = attributes.delete(:collection) || field.collection
13
+ @label_method = attributes.delete(:label_method)
14
+ @value_method = attributes.delete(:value_method)
15
+ end
16
+
17
+ def option_mapper
18
+ @option_mapper ||= OptionMapper.new(@collection, label_method: @label_method, value_method: @value_method)
19
+ end
20
+
21
+ def selected?(option)
22
+ if attributes[:multiple]
23
+ @options_list ||= Array(field.value)
24
+ @options_list.any? { |item| item.to_s == option.to_s }
25
+ else
26
+ field.value.to_s == option.to_s
27
+ end
28
+ end
29
+
30
+ def normalize_simple_input(input_value)
31
+ ([super] & option_mapper.values)[0]
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexi
4
+ module Display
5
+ module Components
6
+ module Concerns
7
+ module SubmitsDisplay
8
+ def submit_type_value
9
+ if field.object.respond_to?(:persisted?)
10
+ field.object.persisted? ? :update : :create
11
+ else
12
+ :submit
13
+ end
14
+ end
15
+
16
+ def submit_type_label
17
+ @submit_type_label ||= begin
18
+ key = submit_type_value
19
+
20
+ model_object = field.dom.lineage.first.key.to_s
21
+ model_name_human = if field.object.respond_to?(:model_name)
22
+ field.object.model_name.human
23
+ else
24
+ model_object.humanize
25
+ end
26
+
27
+ defaults = []
28
+ defaults << :"helpers.submit.#{model_object}.#{key}"
29
+ defaults << :"helpers.submit.#{key}"
30
+ defaults << "#{key.to_s.humanize} #{model_name_human}"
31
+
32
+ I18n.t(defaults.shift, model: model_name_human, default: defaults)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end