phlexi-display 0.0.1
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 +7 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/Appraisals +8 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +13 -0
- data/Rakefile +14 -0
- data/TODO +0 -0
- data/config.ru +6 -0
- data/gemfiles/default.gemfile +5 -0
- data/gemfiles/rails_7.gemfile +8 -0
- data/lib/phlexi/display/base.rb +243 -0
- data/lib/phlexi/display/components/base.rb +51 -0
- data/lib/phlexi/display/components/checkbox.rb +48 -0
- data/lib/phlexi/display/components/collection_checkboxes.rb +44 -0
- data/lib/phlexi/display/components/collection_radio_buttons.rb +35 -0
- data/lib/phlexi/display/components/concerns/handles_array_input.rb +21 -0
- data/lib/phlexi/display/components/concerns/handles_input.rb +53 -0
- data/lib/phlexi/display/components/concerns/has_options.rb +37 -0
- data/lib/phlexi/display/components/concerns/submits_form.rb +39 -0
- data/lib/phlexi/display/components/error.rb +21 -0
- data/lib/phlexi/display/components/file_input.rb +32 -0
- data/lib/phlexi/display/components/full_error.rb +21 -0
- data/lib/phlexi/display/components/hint.rb +21 -0
- data/lib/phlexi/display/components/input.rb +84 -0
- data/lib/phlexi/display/components/input_array.rb +45 -0
- data/lib/phlexi/display/components/label.rb +27 -0
- data/lib/phlexi/display/components/radio_button.rb +41 -0
- data/lib/phlexi/display/components/select.rb +69 -0
- data/lib/phlexi/display/components/submit_button.rb +41 -0
- data/lib/phlexi/display/components/textarea.rb +34 -0
- data/lib/phlexi/display/components/wrapper.rb +31 -0
- data/lib/phlexi/display/field_options/associations.rb +21 -0
- data/lib/phlexi/display/field_options/autofocus.rb +18 -0
- data/lib/phlexi/display/field_options/collection.rb +54 -0
- data/lib/phlexi/display/field_options/disabled.rb +18 -0
- data/lib/phlexi/display/field_options/errors.rb +92 -0
- data/lib/phlexi/display/field_options/hints.rb +22 -0
- data/lib/phlexi/display/field_options/inferred_types.rb +155 -0
- data/lib/phlexi/display/field_options/labels.rb +28 -0
- data/lib/phlexi/display/field_options/length.rb +53 -0
- data/lib/phlexi/display/field_options/limit.rb +66 -0
- data/lib/phlexi/display/field_options/min_max.rb +92 -0
- data/lib/phlexi/display/field_options/multiple.rb +65 -0
- data/lib/phlexi/display/field_options/pattern.rb +38 -0
- data/lib/phlexi/display/field_options/placeholder.rb +18 -0
- data/lib/phlexi/display/field_options/readonly.rb +18 -0
- data/lib/phlexi/display/field_options/required.rb +37 -0
- data/lib/phlexi/display/field_options/themes.rb +207 -0
- data/lib/phlexi/display/field_options/validators.rb +48 -0
- data/lib/phlexi/display/option_mapper.rb +154 -0
- data/lib/phlexi/display/structure/dom.rb +62 -0
- data/lib/phlexi/display/structure/field_builder.rb +236 -0
- data/lib/phlexi/display/structure/field_collection.rb +54 -0
- data/lib/phlexi/display/structure/namespace.rb +135 -0
- data/lib/phlexi/display/structure/namespace_collection.rb +48 -0
- data/lib/phlexi/display/structure/node.rb +18 -0
- data/lib/phlexi/display/version.rb +7 -0
- data/lib/phlexi/display.rb +31 -0
- data/lib/phlexi-display.rb +3 -0
- data/sig/phlexi/display.rbs +6 -0
- 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
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.2.2
|
data/Appraisals
ADDED
data/CHANGELOG.md
ADDED
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
|
+
[](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,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
|