phlexi-form 0.2.0
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 +13 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +395 -0
- data/Rakefile +14 -0
- data/config.ru +9 -0
- data/gemfiles/default.gemfile +5 -0
- data/gemfiles/default.gemfile.lock +174 -0
- data/lib/generators/superform/install/USAGE +8 -0
- data/lib/generators/superform/install/install_generator.rb +34 -0
- data/lib/generators/superform/install/templates/application_form.rb +31 -0
- data/lib/phlexi/form/base.rb +234 -0
- data/lib/phlexi/form/components/base.rb +37 -0
- data/lib/phlexi/form/components/checkbox.rb +43 -0
- data/lib/phlexi/form/components/collection_checkboxes.rb +30 -0
- data/lib/phlexi/form/components/collection_radio_buttons.rb +29 -0
- data/lib/phlexi/form/components/concerns/has_options.rb +33 -0
- data/lib/phlexi/form/components/error.rb +21 -0
- data/lib/phlexi/form/components/full_error.rb +21 -0
- data/lib/phlexi/form/components/hint.rb +21 -0
- data/lib/phlexi/form/components/input.rb +78 -0
- data/lib/phlexi/form/components/label.rb +26 -0
- data/lib/phlexi/form/components/radio_button.rb +31 -0
- data/lib/phlexi/form/components/select.rb +57 -0
- data/lib/phlexi/form/components/textarea.rb +34 -0
- data/lib/phlexi/form/components/wrapper.rb +31 -0
- data/lib/phlexi/form/field_options/autofocus.rb +18 -0
- data/lib/phlexi/form/field_options/collection.rb +37 -0
- data/lib/phlexi/form/field_options/disabled.rb +18 -0
- data/lib/phlexi/form/field_options/errors.rb +82 -0
- data/lib/phlexi/form/field_options/hints.rb +22 -0
- data/lib/phlexi/form/field_options/labels.rb +28 -0
- data/lib/phlexi/form/field_options/length.rb +53 -0
- data/lib/phlexi/form/field_options/limit.rb +66 -0
- data/lib/phlexi/form/field_options/min_max.rb +92 -0
- data/lib/phlexi/form/field_options/multiple.rb +63 -0
- data/lib/phlexi/form/field_options/pattern.rb +38 -0
- data/lib/phlexi/form/field_options/placeholder.rb +18 -0
- data/lib/phlexi/form/field_options/readonly.rb +18 -0
- data/lib/phlexi/form/field_options/required.rb +37 -0
- data/lib/phlexi/form/field_options/type.rb +155 -0
- data/lib/phlexi/form/field_options/validators.rb +48 -0
- data/lib/phlexi/form/option_mapper.rb +154 -0
- data/lib/phlexi/form/structure/dom.rb +57 -0
- data/lib/phlexi/form/structure/field_builder.rb +199 -0
- data/lib/phlexi/form/structure/field_collection.rb +45 -0
- data/lib/phlexi/form/structure/namespace.rb +123 -0
- data/lib/phlexi/form/structure/namespace_collection.rb +48 -0
- data/lib/phlexi/form/structure/node.rb +18 -0
- data/lib/phlexi/form/version.rb +7 -0
- data/lib/phlexi/form.rb +28 -0
- data/lib/phlexi-form.rb +3 -0
- data/sig/phlexi/form.rbs +6 -0
- metadata +243 -0
@@ -0,0 +1,174 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ..
|
3
|
+
specs:
|
4
|
+
phlexi-form (0.1.0)
|
5
|
+
activesupport
|
6
|
+
phlex (~> 1.10)
|
7
|
+
zeitwerk
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
actionpack (7.1.3.4)
|
13
|
+
actionview (= 7.1.3.4)
|
14
|
+
activesupport (= 7.1.3.4)
|
15
|
+
nokogiri (>= 1.8.5)
|
16
|
+
racc
|
17
|
+
rack (>= 2.2.4)
|
18
|
+
rack-session (>= 1.0.1)
|
19
|
+
rack-test (>= 0.6.3)
|
20
|
+
rails-dom-testing (~> 2.2)
|
21
|
+
rails-html-sanitizer (~> 1.6)
|
22
|
+
actionview (7.1.3.4)
|
23
|
+
activesupport (= 7.1.3.4)
|
24
|
+
builder (~> 3.1)
|
25
|
+
erubi (~> 1.11)
|
26
|
+
rails-dom-testing (~> 2.2)
|
27
|
+
rails-html-sanitizer (~> 1.6)
|
28
|
+
activesupport (7.1.3.4)
|
29
|
+
base64
|
30
|
+
bigdecimal
|
31
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
32
|
+
connection_pool (>= 2.2.5)
|
33
|
+
drb
|
34
|
+
i18n (>= 1.6, < 2)
|
35
|
+
minitest (>= 5.1)
|
36
|
+
mutex_m
|
37
|
+
tzinfo (~> 2.0)
|
38
|
+
ansi (1.5.0)
|
39
|
+
appraisal (2.5.0)
|
40
|
+
bundler
|
41
|
+
rake
|
42
|
+
thor (>= 0.14.0)
|
43
|
+
ast (2.4.2)
|
44
|
+
base64 (0.2.0)
|
45
|
+
bigdecimal (3.1.8)
|
46
|
+
builder (3.3.0)
|
47
|
+
bundle-audit (0.1.0)
|
48
|
+
bundler-audit
|
49
|
+
bundler-audit (0.9.1)
|
50
|
+
bundler (>= 1.2.0, < 3)
|
51
|
+
thor (~> 1.0)
|
52
|
+
combustion (1.5.0)
|
53
|
+
activesupport (>= 3.0.0)
|
54
|
+
railties (>= 3.0.0)
|
55
|
+
thor (>= 0.14.6)
|
56
|
+
concurrent-ruby (1.3.3)
|
57
|
+
connection_pool (2.4.1)
|
58
|
+
crass (1.0.6)
|
59
|
+
drb (2.2.1)
|
60
|
+
erubi (1.13.0)
|
61
|
+
i18n (1.14.5)
|
62
|
+
concurrent-ruby (~> 1.0)
|
63
|
+
io-console (0.7.2)
|
64
|
+
irb (1.14.0)
|
65
|
+
rdoc (>= 4.0.0)
|
66
|
+
reline (>= 0.4.2)
|
67
|
+
json (2.7.2)
|
68
|
+
language_server-protocol (3.17.0.3)
|
69
|
+
lint_roller (1.1.0)
|
70
|
+
loofah (2.22.0)
|
71
|
+
crass (~> 1.0.2)
|
72
|
+
nokogiri (>= 1.12.0)
|
73
|
+
minitest (5.24.1)
|
74
|
+
minitest-reporters (1.7.1)
|
75
|
+
ansi
|
76
|
+
builder
|
77
|
+
minitest (>= 5.0)
|
78
|
+
ruby-progressbar
|
79
|
+
mutex_m (0.2.0)
|
80
|
+
nokogiri (1.16.7-x86_64-darwin)
|
81
|
+
racc (~> 1.4)
|
82
|
+
parallel (1.25.1)
|
83
|
+
parser (3.3.4.0)
|
84
|
+
ast (~> 2.4.1)
|
85
|
+
racc
|
86
|
+
phlex (1.11.0)
|
87
|
+
psych (5.1.2)
|
88
|
+
stringio
|
89
|
+
racc (1.8.1)
|
90
|
+
rack (3.1.7)
|
91
|
+
rack-session (2.0.0)
|
92
|
+
rack (>= 3.0.0)
|
93
|
+
rack-test (2.1.0)
|
94
|
+
rack (>= 1.3)
|
95
|
+
rackup (2.1.0)
|
96
|
+
rack (>= 3)
|
97
|
+
webrick (~> 1.8)
|
98
|
+
rails-dom-testing (2.2.0)
|
99
|
+
activesupport (>= 5.0.0)
|
100
|
+
minitest
|
101
|
+
nokogiri (>= 1.6)
|
102
|
+
rails-html-sanitizer (1.6.0)
|
103
|
+
loofah (~> 2.21)
|
104
|
+
nokogiri (~> 1.14)
|
105
|
+
railties (7.1.3.4)
|
106
|
+
actionpack (= 7.1.3.4)
|
107
|
+
activesupport (= 7.1.3.4)
|
108
|
+
irb
|
109
|
+
rackup (>= 1.0.0)
|
110
|
+
rake (>= 12.2)
|
111
|
+
thor (~> 1.0, >= 1.2.2)
|
112
|
+
zeitwerk (~> 2.6)
|
113
|
+
rainbow (3.1.1)
|
114
|
+
rake (13.2.1)
|
115
|
+
rdoc (6.7.0)
|
116
|
+
psych (>= 4.0.0)
|
117
|
+
regexp_parser (2.9.2)
|
118
|
+
reline (0.5.9)
|
119
|
+
io-console (~> 0.5)
|
120
|
+
rexml (3.3.2)
|
121
|
+
strscan
|
122
|
+
rubocop (1.64.1)
|
123
|
+
json (~> 2.3)
|
124
|
+
language_server-protocol (>= 3.17.0)
|
125
|
+
parallel (~> 1.10)
|
126
|
+
parser (>= 3.3.0.2)
|
127
|
+
rainbow (>= 2.2.2, < 4.0)
|
128
|
+
regexp_parser (>= 1.8, < 3.0)
|
129
|
+
rexml (>= 3.2.5, < 4.0)
|
130
|
+
rubocop-ast (>= 1.31.1, < 2.0)
|
131
|
+
ruby-progressbar (~> 1.7)
|
132
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
133
|
+
rubocop-ast (1.31.3)
|
134
|
+
parser (>= 3.3.1.0)
|
135
|
+
rubocop-performance (1.21.1)
|
136
|
+
rubocop (>= 1.48.1, < 2.0)
|
137
|
+
rubocop-ast (>= 1.31.1, < 2.0)
|
138
|
+
ruby-progressbar (1.13.0)
|
139
|
+
standard (1.39.2)
|
140
|
+
language_server-protocol (~> 3.17.0.2)
|
141
|
+
lint_roller (~> 1.0)
|
142
|
+
rubocop (~> 1.64.0)
|
143
|
+
standard-custom (~> 1.0.0)
|
144
|
+
standard-performance (~> 1.4)
|
145
|
+
standard-custom (1.0.2)
|
146
|
+
lint_roller (~> 1.0)
|
147
|
+
rubocop (~> 1.50)
|
148
|
+
standard-performance (1.4.0)
|
149
|
+
lint_roller (~> 1.1)
|
150
|
+
rubocop-performance (~> 1.21.0)
|
151
|
+
stringio (3.1.1)
|
152
|
+
strscan (3.1.0)
|
153
|
+
thor (1.3.1)
|
154
|
+
tzinfo (2.0.6)
|
155
|
+
concurrent-ruby (~> 1.0)
|
156
|
+
unicode-display_width (2.5.0)
|
157
|
+
webrick (1.8.1)
|
158
|
+
zeitwerk (2.6.17)
|
159
|
+
|
160
|
+
PLATFORMS
|
161
|
+
x86_64-darwin
|
162
|
+
|
163
|
+
DEPENDENCIES
|
164
|
+
appraisal
|
165
|
+
bundle-audit
|
166
|
+
combustion
|
167
|
+
minitest
|
168
|
+
minitest-reporters
|
169
|
+
phlexi-form!
|
170
|
+
rake
|
171
|
+
standard
|
172
|
+
|
173
|
+
BUNDLED WITH
|
174
|
+
2.5.16
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "bundler"
|
2
|
+
|
3
|
+
class Superform::InstallGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path("templates", __dir__)
|
5
|
+
|
6
|
+
APPLICATION_CONFIGURATION_PATH = Rails.root.join("config/application.rb")
|
7
|
+
|
8
|
+
def install_phlex_rails
|
9
|
+
return if gem_in_bundle? "phlex-rails"
|
10
|
+
|
11
|
+
gem "phlex-rails"
|
12
|
+
generate "phlex:install"
|
13
|
+
end
|
14
|
+
|
15
|
+
def autoload_components
|
16
|
+
return unless APPLICATION_CONFIGURATION_PATH.exist?
|
17
|
+
|
18
|
+
inject_into_class(
|
19
|
+
APPLICATION_CONFIGURATION_PATH,
|
20
|
+
"Application",
|
21
|
+
%( config.autoload_paths << "\#{root}/app/views/forms"\n)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_application_form
|
26
|
+
template "application_form.rb", Rails.root.join("app/views/forms/application_form.rb")
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def gem_in_bundle?(gem_name)
|
32
|
+
Bundler.load.specs.any? { |spec| spec.name == gem_name }
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class ApplicationForm < Superform::Rails::Form
|
2
|
+
include Phlex::Rails::Helpers::Pluralize
|
3
|
+
|
4
|
+
def row(component)
|
5
|
+
div do
|
6
|
+
render component.field.label(style: "display: block;")
|
7
|
+
render component
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def around_template(&)
|
12
|
+
super do
|
13
|
+
error_messages
|
14
|
+
yield
|
15
|
+
submit
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def error_messages
|
20
|
+
if model.errors.any?
|
21
|
+
div(style: "color: red;") do
|
22
|
+
h2 { "#{pluralize model.errors.count, "error"} prohibited this post from being saved:" }
|
23
|
+
ul do
|
24
|
+
model.errors.each do |error|
|
25
|
+
li { error.full_message }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_view/model_naming"
|
4
|
+
require "active_support/core_ext/module/delegation"
|
5
|
+
require "active_support/string_inquirer"
|
6
|
+
require "active_support/core_ext/object/blank"
|
7
|
+
|
8
|
+
module Phlexi
|
9
|
+
module Form
|
10
|
+
# A form component for building flexible and customizable forms.
|
11
|
+
#
|
12
|
+
# @example Basic usage
|
13
|
+
# Form.new(user, action: '/users', method: 'post') do |f|
|
14
|
+
# f.field :name
|
15
|
+
# f.field :email
|
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 < BaseComponent
|
21
|
+
include ActionView::ModelNaming
|
22
|
+
|
23
|
+
attr_reader :key, :object
|
24
|
+
|
25
|
+
delegate :field, :nest_one, :nest_many, to: :@namespace
|
26
|
+
|
27
|
+
# Initializes a new Form instance.
|
28
|
+
#
|
29
|
+
# @param record [ActiveModel::Model, Symbol, String] The form's associated record or key
|
30
|
+
# @param action [String, nil] The form's action URL
|
31
|
+
# @param method [String, nil] The form's HTTP method
|
32
|
+
# @param attributes [Hash] Additional HTML attributes for the form tag
|
33
|
+
# @param options [Hash] Additional options for form configuration
|
34
|
+
# @option options [String] :class CSS classes for the form
|
35
|
+
# @option options [Class] :namespace_klass Custom namespace class
|
36
|
+
# @option options [Class] :builder_klass Custom field builder class
|
37
|
+
def initialize(record, action: nil, method: nil, attributes: {}, **options)
|
38
|
+
@form_action = action
|
39
|
+
@form_method = method
|
40
|
+
@form_class = options.delete(:class)
|
41
|
+
@attributes = attributes
|
42
|
+
@options = options
|
43
|
+
|
44
|
+
initialize_object_and_key(record)
|
45
|
+
initialize_namespace
|
46
|
+
initialize_attributes
|
47
|
+
end
|
48
|
+
|
49
|
+
# Renders the form template.
|
50
|
+
#
|
51
|
+
# @return [void]
|
52
|
+
def view_template
|
53
|
+
form_tag { form_template }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Executes the form's content block.
|
57
|
+
# Override this in subclasses to defie a static form.
|
58
|
+
#
|
59
|
+
# @return [void]
|
60
|
+
def form_template
|
61
|
+
instance_exec(&@_content_block) if @_content_block
|
62
|
+
end
|
63
|
+
|
64
|
+
# Renders the form tag with its contents.
|
65
|
+
#
|
66
|
+
# @yield The form's content
|
67
|
+
# @return [void]
|
68
|
+
def form_tag(&block)
|
69
|
+
form(**form_attributes) do
|
70
|
+
render_hidden_method_field
|
71
|
+
render_authenticity_token if authenticity_token?
|
72
|
+
yield
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
attr_reader :options, :attributes
|
79
|
+
|
80
|
+
# Initializes the object and key based on the given record.
|
81
|
+
#
|
82
|
+
# @param record [ActiveModel::Model, Symbol, String] The form's associated record or key
|
83
|
+
# @return [void]
|
84
|
+
def initialize_object_and_key(record)
|
85
|
+
case record
|
86
|
+
when String, Symbol
|
87
|
+
@object = nil
|
88
|
+
@key = record
|
89
|
+
else
|
90
|
+
@object = convert_to_model(record)
|
91
|
+
@key = options.delete(:as)
|
92
|
+
|
93
|
+
if @key.nil?
|
94
|
+
unless object.respond_to?(:model_name) && object.model_name.respond_to?(:param_key) && object.model_name.param_key
|
95
|
+
raise ArgumentError, "record must respond to #model_name.param_key with a non nil value"
|
96
|
+
end
|
97
|
+
@key = object.model_name.param_key
|
98
|
+
end
|
99
|
+
end
|
100
|
+
@key = @key.to_sym
|
101
|
+
end
|
102
|
+
|
103
|
+
# Initializes the namespace for the form.
|
104
|
+
#
|
105
|
+
# @return [void]
|
106
|
+
def initialize_namespace
|
107
|
+
@namespace = namespace_klass.root(key, object: object, builder_klass: builder_klass)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Initializes form attributes.
|
111
|
+
#
|
112
|
+
# @return [void]
|
113
|
+
def initialize_attributes
|
114
|
+
attributes[:accept_charset] ||= "UTF-8"
|
115
|
+
end
|
116
|
+
|
117
|
+
# Determines the form's action URL.
|
118
|
+
#
|
119
|
+
# @return [String, nil] The form's action URL
|
120
|
+
def form_action
|
121
|
+
puts ""
|
122
|
+
# if @form_action != false
|
123
|
+
# @form_action ||= if options[:format].nil?
|
124
|
+
# polymorphic_path(object, {})
|
125
|
+
# else
|
126
|
+
# polymorphic_path(object, format: options[:format])
|
127
|
+
# end
|
128
|
+
# end
|
129
|
+
@form_action
|
130
|
+
end
|
131
|
+
|
132
|
+
# Determines the form's HTTP method.
|
133
|
+
#
|
134
|
+
# @return [ActiveSupport::StringInquirer] The form's HTTP method
|
135
|
+
def form_method
|
136
|
+
@form_method ||= (object_form_method || "get").to_s.downcase
|
137
|
+
ActiveSupport::StringInquirer.new(@form_method)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Retrieves the form's CSS classes.
|
141
|
+
#
|
142
|
+
# @return [String] The form's CSS classes
|
143
|
+
def form_class
|
144
|
+
@form_class || "flex flex-col space-y-6 px-4 py-2"
|
145
|
+
end
|
146
|
+
|
147
|
+
# Checks if the authenticity token should be included.
|
148
|
+
#
|
149
|
+
# @return [Boolean] True if the authenticity token should be included, false otherwise
|
150
|
+
def authenticity_token?
|
151
|
+
defined?(helpers) && options.fetch(:authenticity_token) { !form_method.get? }
|
152
|
+
end
|
153
|
+
|
154
|
+
# Retrieves the authenticity token.
|
155
|
+
#
|
156
|
+
# @return [String] The authenticity token
|
157
|
+
def authenticity_token
|
158
|
+
options.fetch(:authenticity_token) { helpers.form_authenticity_token }
|
159
|
+
end
|
160
|
+
|
161
|
+
# Renders the authenticity token field.
|
162
|
+
#
|
163
|
+
# @param name [String] The name attribute for the authenticity token field
|
164
|
+
# @param value [String] The value for the authenticity token field
|
165
|
+
# @return [void]
|
166
|
+
def authenticity_token_field(name = "authenticity_token", value = authenticity_token)
|
167
|
+
input(name: name, value: value, type: "hidden", hidden: true)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Determines the appropriate form method based on the object's state.
|
171
|
+
#
|
172
|
+
# @return [String, nil] The appropriate form method
|
173
|
+
def object_form_method
|
174
|
+
return unless object
|
175
|
+
object.persisted? ? "patch" : "post"
|
176
|
+
end
|
177
|
+
|
178
|
+
# Retrieves the namespace class.
|
179
|
+
#
|
180
|
+
# @return [Class] The namespace class
|
181
|
+
def namespace_klass
|
182
|
+
@namespace_klass ||= options.delete(:namespace_klass) || Structure::Namespace
|
183
|
+
end
|
184
|
+
|
185
|
+
# Retrieves the builder class.
|
186
|
+
#
|
187
|
+
# @return [Class] The builder class
|
188
|
+
def builder_klass
|
189
|
+
@builder_klass ||= options.delete(:builder_klass) || Structure::FieldBuilder
|
190
|
+
end
|
191
|
+
|
192
|
+
# Renders the hidden method field for non-standard HTTP methods.
|
193
|
+
#
|
194
|
+
# @return [void]
|
195
|
+
def render_hidden_method_field
|
196
|
+
return if standard_form_method?
|
197
|
+
input(name: "_method", value: form_method, type: "hidden", hidden: true)
|
198
|
+
end
|
199
|
+
|
200
|
+
# Checks if the form method is standard (GET or POST).
|
201
|
+
#
|
202
|
+
# @return [Boolean] True if the form method is standard, false otherwise
|
203
|
+
def standard_form_method?
|
204
|
+
form_method.get? || form_method.post?
|
205
|
+
end
|
206
|
+
|
207
|
+
# Returns the standardized form method for the HTML form tag.
|
208
|
+
#
|
209
|
+
# @return [String] The standardized form method
|
210
|
+
def standardized_form_method
|
211
|
+
standard_form_method? ? form_method : "post"
|
212
|
+
end
|
213
|
+
|
214
|
+
# Generates the form attributes hash.
|
215
|
+
#
|
216
|
+
# @return [Hash] The form attributes
|
217
|
+
def form_attributes
|
218
|
+
{
|
219
|
+
action: form_action,
|
220
|
+
method: standardized_form_method,
|
221
|
+
class: form_class,
|
222
|
+
**attributes
|
223
|
+
}
|
224
|
+
end
|
225
|
+
|
226
|
+
# Renders the authenticity token if required.
|
227
|
+
#
|
228
|
+
# @return [void]
|
229
|
+
def render_authenticity_token
|
230
|
+
authenticity_token_field
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Form
|
5
|
+
module Components
|
6
|
+
class Base < BaseComponent
|
7
|
+
attr_reader :field, :attributes
|
8
|
+
|
9
|
+
def initialize(field, **attributes)
|
10
|
+
@field = field
|
11
|
+
@attributes = attributes
|
12
|
+
|
13
|
+
build_attributes
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def build_attributes
|
19
|
+
attributes[:id] ||= "#{field.dom.id}_#{component_name}"
|
20
|
+
attributes[:class] = tokens(
|
21
|
+
component_name,
|
22
|
+
attributes[:class],
|
23
|
+
-> { field.required? } => "required",
|
24
|
+
-> { !field.required? } => "optional",
|
25
|
+
-> { field.has_errors? } => "invalid",
|
26
|
+
-> { field.readonly? } => "readonly",
|
27
|
+
-> { field.disabled? } => "disabled"
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def component_name
|
32
|
+
@component_name ||= self.class.name.demodulize.underscore
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Form
|
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
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Form
|
5
|
+
module Components
|
6
|
+
class CollectionCheckboxes < Base
|
7
|
+
include Concerns::HasOptions
|
8
|
+
|
9
|
+
def view_template
|
10
|
+
render field.input_tag(type: :hidden, value: "", theme: false, hidden: true, autocomplete: "off", multiple: true)
|
11
|
+
field.multi(option_mapper.values) do |builder|
|
12
|
+
field = builder.field(
|
13
|
+
label: option_mapper[builder.key],
|
14
|
+
attributes: {
|
15
|
+
checked_value: builder.key,
|
16
|
+
include_hidden: false
|
17
|
+
}
|
18
|
+
)
|
19
|
+
if block_given?
|
20
|
+
yield field
|
21
|
+
else
|
22
|
+
render field.checkbox_tag
|
23
|
+
render field.label_tag
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Form
|
5
|
+
module Components
|
6
|
+
class CollectionRadioButtons < Base
|
7
|
+
include Concerns::HasOptions
|
8
|
+
|
9
|
+
def view_template
|
10
|
+
render field.input_tag(type: :hidden, value: "", theme: false, hidden: true, autocomplete: "off")
|
11
|
+
field.multi(option_mapper.values) do |builder|
|
12
|
+
field = builder.field(
|
13
|
+
label: option_mapper[builder.key],
|
14
|
+
attributes: {
|
15
|
+
checked_value: builder.key
|
16
|
+
}
|
17
|
+
)
|
18
|
+
if block_given?
|
19
|
+
yield field
|
20
|
+
else
|
21
|
+
render field.radio_button_tag
|
22
|
+
render field.label_tag
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Form
|
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 field.multiple?
|
23
|
+
@options_list ||= Array(field.value)
|
24
|
+
@options_list.any? { |item| item.to_s == option.to_s }
|
25
|
+
else
|
26
|
+
field.dom.value == option.to_s
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Form
|
5
|
+
module Components
|
6
|
+
class Error < Base
|
7
|
+
def view_template
|
8
|
+
p(**attributes) do
|
9
|
+
field.error
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def render?
|
16
|
+
field.show_errors? && field.has_errors?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Form
|
5
|
+
module Components
|
6
|
+
class FullError < Base
|
7
|
+
def view_template
|
8
|
+
p(**attributes) do
|
9
|
+
field.full_error
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def render?
|
16
|
+
field.show_errors? && field.has_errors?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|