action_form 0.1.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/.qlty/.gitignore +7 -0
- data/.qlty/qlty.toml +86 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +1169 -0
- data/Rakefile +12 -0
- data/lib/action_form/base.rb +110 -0
- data/lib/action_form/element.rb +153 -0
- data/lib/action_form/elements_dsl.rb +46 -0
- data/lib/action_form/input.rb +72 -0
- data/lib/action_form/rails/base.rb +116 -0
- data/lib/action_form/rails/rendering.rb +42 -0
- data/lib/action_form/rails/subform.rb +44 -0
- data/lib/action_form/rendering.rb +74 -0
- data/lib/action_form/schema_dsl.rb +41 -0
- data/lib/action_form/subform.rb +57 -0
- data/lib/action_form/subforms_collection.rb +96 -0
- data/lib/action_form/version.rb +5 -0
- data/lib/action_form.rb +21 -0
- data/sig/action_form.rbs +4 -0
- data/sig/easy_form.rbs +4 -0
- metadata +109 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionForm
|
4
|
+
# Provides DSL methods for defining form schemas and converting them to EasyParams
|
5
|
+
module SchemaDSL
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods # rubocop:disable Style/Documentation
|
11
|
+
def params_class
|
12
|
+
EasyParams::Base
|
13
|
+
end
|
14
|
+
|
15
|
+
def params_definition(*) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
16
|
+
schema = Class.new(params_class)
|
17
|
+
elements.each do |name, element_definition|
|
18
|
+
if element_definition < ActionForm::SubformsCollection
|
19
|
+
# nested forms are passed as a hash that looks like this:
|
20
|
+
# { "0" => { "id" => "1" }, "1" => { "id" => "2" } }
|
21
|
+
# it is coercing to an array of hashes:
|
22
|
+
# [['0', { "id" => "1" }], ['1', { "id" => "2" }]]
|
23
|
+
# we need to normalize it to an array of hashes:
|
24
|
+
# [ { "id" => "1" }, { "
|
25
|
+
# id" => "2" } ]
|
26
|
+
schema.each(:"#{name}_attributes", element_definition.subform_definition.params_definition,
|
27
|
+
normalize: ->(value) { value.flatten.select { |v| v.is_a?(Hash) } },
|
28
|
+
default: element_definition.default)
|
29
|
+
elsif element_definition < ActionForm::Subform
|
30
|
+
schema.has(:"#{name}_attributes", element_definition.params_definition, default: element_definition.default)
|
31
|
+
elsif element_definition < ActionForm::Element
|
32
|
+
options = element_definition.output_options.dup
|
33
|
+
method_name = options.delete(:type)
|
34
|
+
schema.public_send(method_name, name, **options)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
schema
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionForm
|
4
|
+
# Subform class for ActionForm that handles nested form structures.
|
5
|
+
# It allows building forms within forms, supporting has_one and has_many relationships.
|
6
|
+
# Includes schema and element DSL functionality for defining form elements.
|
7
|
+
class Subform < ::Phlex::HTML
|
8
|
+
include ActionForm::Rendering
|
9
|
+
include ActionForm::SchemaDSL
|
10
|
+
include ActionForm::ElementsDSL
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_accessor :default
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :elements_instances, :tags, :name, :object
|
17
|
+
attr_accessor :helpers
|
18
|
+
|
19
|
+
def initialize(name:, scope: nil, model: nil, params: nil, **tags)
|
20
|
+
super()
|
21
|
+
@name = name
|
22
|
+
@scope = scope
|
23
|
+
@object = model
|
24
|
+
@params = params
|
25
|
+
@elements_instances = []
|
26
|
+
@tags = tags
|
27
|
+
build_from_object
|
28
|
+
end
|
29
|
+
|
30
|
+
def build_from_object
|
31
|
+
self.class.elements.each do |element_name, element_definition|
|
32
|
+
@elements_instances << element_definition.new(element_name, @params || @object, parent_name: @scope)
|
33
|
+
@elements_instances.last.tags.merge!(subform: @name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def render?
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def template_html_id
|
42
|
+
"#{name}_template"
|
43
|
+
end
|
44
|
+
|
45
|
+
def html_id
|
46
|
+
"#{name}_#{tags[:index]}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def html_class
|
50
|
+
"#{name}_subform"
|
51
|
+
end
|
52
|
+
|
53
|
+
def view_template
|
54
|
+
render_elements
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionForm
|
4
|
+
# Collection of subforms that can be iterated and rendered
|
5
|
+
class SubformsCollection < ::Phlex::HTML
|
6
|
+
extend Forwardable
|
7
|
+
include ActionForm::Rendering
|
8
|
+
|
9
|
+
def_delegators :@subforms, :last, :first, :length, :size, :[], :<<
|
10
|
+
|
11
|
+
attr_reader :subforms, :tags, :name
|
12
|
+
attr_accessor :helpers
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_reader :subform_definition
|
16
|
+
attr_accessor :default, :host_class
|
17
|
+
|
18
|
+
def subform(subform_class = nil, &block)
|
19
|
+
@subform_definition = subform_class || Class.new(host_class.subform_class)
|
20
|
+
@subform_definition.class_eval(&block) if block
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(name)
|
25
|
+
super()
|
26
|
+
@name = name
|
27
|
+
@subforms = []
|
28
|
+
@tags = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def each(&block)
|
32
|
+
return to_enum(:each) unless block
|
33
|
+
|
34
|
+
@subforms.each(&block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def render?
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def template_html_id
|
42
|
+
"#{name}_template"
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_subform_js
|
46
|
+
<<~JS
|
47
|
+
function easyFormAddSubform(event) {
|
48
|
+
event.preventDefault()
|
49
|
+
var template = document.querySelector("##{template_html_id}")
|
50
|
+
const content = template.innerHTML.replace(/NEW_RECORD/g, new Date().getTime().toString())
|
51
|
+
var beforeElement = event.target.closest(event.target.dataset.insertBeforeSelector)
|
52
|
+
if (beforeElement) {
|
53
|
+
beforeElement.insertAdjacentHTML("beforebegin", content)
|
54
|
+
} else {
|
55
|
+
event.target.parentElement.insertAdjacentHTML("beforebegin", content)
|
56
|
+
}
|
57
|
+
}
|
58
|
+
JS
|
59
|
+
end
|
60
|
+
|
61
|
+
def remove_subform_js
|
62
|
+
<<~JS
|
63
|
+
function easyFormRemoveSubform(event) {
|
64
|
+
event.preventDefault()
|
65
|
+
var subform = event.target.closest(".new_#{name}")
|
66
|
+
if (subform) { subform.remove() }
|
67
|
+
var subform = event.target.closest(".#{name}_subform")
|
68
|
+
if (subform) {
|
69
|
+
subform.style.display = "none"
|
70
|
+
var input = subform.querySelector("input[name*='_destroy']")
|
71
|
+
if (input) { input.value = "1" }
|
72
|
+
}
|
73
|
+
}
|
74
|
+
JS
|
75
|
+
end
|
76
|
+
|
77
|
+
def view_template # rubocop:disable Metrics/AbcSize
|
78
|
+
script(type: "text/javascript") { raw safe(remove_subform_js) }
|
79
|
+
script(type: "text/javascript") { raw safe(add_subform_js) }
|
80
|
+
subforms.each do |subform|
|
81
|
+
subform.helpers = helpers
|
82
|
+
if subform.tags[:template]
|
83
|
+
render_subform_template(subform)
|
84
|
+
else
|
85
|
+
div(id: subform.html_id, class: subform.html_class) { render_subform(subform) }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def render_subform_template(subform)
|
91
|
+
template(id: template_html_id) do
|
92
|
+
div(class: "new_#{name}") { render_subform(subform) }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/action_form.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "phlex"
|
4
|
+
require "easy_params"
|
5
|
+
require "forwardable"
|
6
|
+
require_relative "action_form/version"
|
7
|
+
require_relative "action_form/schema_dsl"
|
8
|
+
require_relative "action_form/elements_dsl"
|
9
|
+
require_relative "action_form/input"
|
10
|
+
require_relative "action_form/rendering"
|
11
|
+
require_relative "action_form/subform"
|
12
|
+
require_relative "action_form/subforms_collection"
|
13
|
+
require_relative "action_form/element"
|
14
|
+
require_relative "action_form/base"
|
15
|
+
require_relative "action_form/rails/rendering"
|
16
|
+
require_relative "action_form/rails/subform"
|
17
|
+
require_relative "action_form/rails/base"
|
18
|
+
|
19
|
+
module ActionForm
|
20
|
+
class Error < StandardError; end
|
21
|
+
end
|
data/sig/action_form.rbs
ADDED
data/sig/easy_form.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: action_form
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrii Baran
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: easy_params
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: 0.6.3
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 0.6.3
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: phlex
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '2'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: railties
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 6.0.0
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 6.0.0
|
54
|
+
description: Action form builder for Rails
|
55
|
+
email:
|
56
|
+
- andriy.baran.v@gmail.com
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- ".qlty/.gitignore"
|
62
|
+
- ".qlty/qlty.toml"
|
63
|
+
- CHANGELOG.md
|
64
|
+
- CODE_OF_CONDUCT.md
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- lib/action_form.rb
|
69
|
+
- lib/action_form/base.rb
|
70
|
+
- lib/action_form/element.rb
|
71
|
+
- lib/action_form/elements_dsl.rb
|
72
|
+
- lib/action_form/input.rb
|
73
|
+
- lib/action_form/rails/base.rb
|
74
|
+
- lib/action_form/rails/rendering.rb
|
75
|
+
- lib/action_form/rails/subform.rb
|
76
|
+
- lib/action_form/rendering.rb
|
77
|
+
- lib/action_form/schema_dsl.rb
|
78
|
+
- lib/action_form/subform.rb
|
79
|
+
- lib/action_form/subforms_collection.rb
|
80
|
+
- lib/action_form/version.rb
|
81
|
+
- sig/action_form.rbs
|
82
|
+
- sig/easy_form.rbs
|
83
|
+
homepage: https://github.com/andriy-baran/action_form
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata:
|
87
|
+
allowed_push_host: https://rubygems.org
|
88
|
+
homepage_uri: https://github.com/andriy-baran/action_form
|
89
|
+
source_code_uri: https://github.com/andriy-baran/action_form
|
90
|
+
changelog_uri: https://github.com/andriy-baran/action_form/blob/main/CHANGELOG.md
|
91
|
+
rubygems_mfa_required: 'true'
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: 2.7.0
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
requirements: []
|
106
|
+
rubygems_version: 3.7.1
|
107
|
+
specification_version: 4
|
108
|
+
summary: Action form builder for Rails
|
109
|
+
test_files: []
|