form_forms 0.1.0 → 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.
- data/CHANGELOG.md +19 -0
- data/README.md +129 -19
- data/lib/form_forms/elements.rb +5 -0
- data/lib/form_forms/elements/base_element.rb +6 -1
- data/lib/form_forms/elements/block.rb +1 -7
- data/lib/form_forms/elements/field.rb +5 -3
- data/lib/form_forms/elements/fields.rb +4 -10
- data/lib/form_forms/elements/fieldset.rb +6 -3
- data/lib/form_forms/elements/sub_form.rb +12 -0
- data/lib/form_forms/elements/table_fields.rb +84 -0
- data/lib/form_forms/elements/table_header.rb +23 -0
- data/lib/form_forms/elements/table_header_field.rb +20 -0
- data/lib/form_forms/forms/form.rb +1 -0
- data/lib/form_forms/version.rb +1 -1
- data/test/form_forms/elements/field_test.rb +11 -0
- data/test/form_forms/elements/sub_form_test.rb +18 -0
- data/test/form_forms/elements/table_fields_test.rb +49 -0
- data/test/support/models.rb +24 -4
- metadata +19 -10
data/CHANGELOG.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# FormForms Change Log
|
2
|
+
|
3
|
+
## v0.2.0 - 2012-06-17
|
4
|
+
|
5
|
+
* Add generic `sub_form` element
|
6
|
+
* Add `table_fields` element to render a one-to-many or many-to-many
|
7
|
+
association in a table.
|
8
|
+
|
9
|
+
## v0.1.0 - 2012-06-16
|
10
|
+
|
11
|
+
First release containing the basic functionality to create forms:
|
12
|
+
|
13
|
+
* parent `Form` class
|
14
|
+
* `field` to render a single field
|
15
|
+
* `fields` to render an association
|
16
|
+
* `fieldset` to group elements in a HTML fieldset
|
17
|
+
* `block` to group elements in an arbitrary HTML block tag
|
18
|
+
|
19
|
+
It also introduces the basic `FormRegistry` class
|
data/README.md
CHANGED
@@ -9,7 +9,7 @@ plugins. Thus plugins can easily add, delete and edit form fields without
|
|
9
9
|
having to override whole views (which are almost impossible to patch) or
|
10
10
|
having to monkey patch existing code which is hard to maintain.
|
11
11
|
|
12
|
-
|
12
|
+
FormForms is originally intended to be used with the simple_form gem and uses
|
13
13
|
its API in several of its shipped element definitions. While it is possible
|
14
14
|
to not use those and provide custom definitions, we require simple_form as
|
15
15
|
a dependency right now.
|
@@ -28,17 +28,13 @@ Or install it yourself using
|
|
28
28
|
|
29
29
|
# Usage
|
30
30
|
|
31
|
-
|
31
|
+
FormForms is built around the idea that a Rails application is able to
|
32
32
|
define arbitrary forms using a simple yet powerful DSL. These form
|
33
33
|
definitions are typically contained in the `lib` directory and are loaded
|
34
34
|
during initialization. During rendering of a request, the view simply
|
35
35
|
retrieves an existing form definition and renders that form by parameterizing
|
36
36
|
it with some data object (e.g. an ActiveRecord model instance).
|
37
37
|
|
38
|
-
To handle several forms, form_forms ships with a simple registry. If you
|
39
|
-
need a more powerful system, you are of course free to handle your form
|
40
|
-
objects in any other way.
|
41
|
-
|
42
38
|
## Defining Forms
|
43
39
|
|
44
40
|
FormForms::Registry[:my_form] = FormForms::Form.new() do |form|
|
@@ -70,6 +66,22 @@ The render method takes two parameters: the model object that should be used
|
|
70
66
|
as the base object for the form and a view instance (which can be almost
|
71
67
|
always passed as `self`). The view instance is used to render the form fields.
|
72
68
|
|
69
|
+
## Form Registry
|
70
|
+
|
71
|
+
To handle several forms, FormForms ships with a simple registry. If you
|
72
|
+
need a more powerful system, you are of course free to handle your form
|
73
|
+
objects in any other way.
|
74
|
+
|
75
|
+
The default registry provides a hash-like interface on the
|
76
|
+
`FormForms::Registry` class. You can either directly assign form objects to
|
77
|
+
keys there as shown above or you can use the `register` method of all `Form`
|
78
|
+
classes as a shorthand:
|
79
|
+
|
80
|
+
FormForms::Form.register(:my_name) do |form|
|
81
|
+
# form definition
|
82
|
+
# [...]
|
83
|
+
end
|
84
|
+
|
73
85
|
## Adapting an existing form from plugins
|
74
86
|
|
75
87
|
Once a form is defined, it fields can be added, changed, and removed later on.
|
@@ -107,7 +119,7 @@ list, preventing it from rendering:
|
|
107
119
|
|
108
120
|
# Element Types
|
109
121
|
|
110
|
-
|
122
|
+
FormForms already ships with different element types which allow you to
|
111
123
|
create most of the common form elements. These elements are used to define
|
112
124
|
the actual form body.
|
113
125
|
|
@@ -130,12 +142,36 @@ simple_form object. Alternatively to a block, you can also directly pass a
|
|
130
142
|
string which will be emitted as-is. The usual HTML escape rules apply, i.e.
|
131
143
|
you have to use `html_save` correctly to avoid rendering unsafe data.
|
132
144
|
|
145
|
+
## `sub_form`
|
146
|
+
|
147
|
+
Almost all the other element types create a sub form. It allows to group
|
148
|
+
several sub elements into a single named element. While the `sub_form` acts
|
149
|
+
as a prototype for all other scoping elements (see below) which typically
|
150
|
+
surround child elements with HTML block tags during rendering, `sub_form`
|
151
|
+
just creates a logical scope in the form and doesn't affect rendering in any
|
152
|
+
way.
|
153
|
+
|
154
|
+
It allows you to group several sub-elements which can then handled as a
|
155
|
+
single element.
|
156
|
+
|
157
|
+
FormForms::Registry[:user] = FormForms::Form.new() do |form|
|
158
|
+
form.field(:street) {|f| f.input :street }
|
159
|
+
form.field(:city) {|f| f.input :city }
|
160
|
+
form.sub_form(:payment) do |sub_form|
|
161
|
+
sub_form.field(:credit_card) {|f| f.input :credit_card}
|
162
|
+
sub_form.field(:ccv) {|f| f.input :ccv}
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
During rendering, this example will generate the four forms exactly the same
|
167
|
+
as if they all would have been defined directly on the form.
|
168
|
+
|
133
169
|
## `fieldset`
|
134
170
|
|
135
171
|
A fieldset is used to group fields in a single form. During rendering, this
|
136
|
-
elements will create an HTML `<fieldset>` tag and a `<legend
|
137
|
-
naturally contains
|
138
|
-
fields.
|
172
|
+
elements will create an HTML `<fieldset>` tag and a `<legend>` around its
|
173
|
+
sub-elements. As a fieldset naturally contains other fields, its generator
|
174
|
+
block can be used to define fields.
|
139
175
|
|
140
176
|
FormForms::Registry[:user] = FormForms::Form.new() do |form|
|
141
177
|
form.field(:street) {|f| f.input :street }
|
@@ -167,11 +203,18 @@ markup the form using custom CSS rules. This element works similar to the
|
|
167
203
|
|
168
204
|
FormForms::Registry[:user] = FormForms::Form.new() do |form|
|
169
205
|
form.field(:street) {|f| f.input :street }
|
170
|
-
form.block(:box, :class => "red-and-blinky") do |block|
|
206
|
+
form.block(:box, :div, :class => "red-and-blinky") do |block|
|
171
207
|
block.field(:sell_your_soul) {|f| f.input :sell_your_soul}
|
172
208
|
end
|
173
209
|
end
|
174
210
|
|
211
|
+
Here, the block element takes the name of the newly created element, the type
|
212
|
+
of HTML tag to create and a hash of options to pass to the `content_tag`
|
213
|
+
helper of ´Rails which creates the tag internally.
|
214
|
+
|
215
|
+
The generator block creates a new element scope similar to the fieldset
|
216
|
+
element.
|
217
|
+
|
175
218
|
## `fields`
|
176
219
|
|
177
220
|
Using fields, you can create sub-forms for association of the model object.
|
@@ -195,19 +238,86 @@ rendered. For the example above, the `User` model is defined as follows:
|
|
195
238
|
end
|
196
239
|
|
197
240
|
The third argument is an options hash which is passed to association render
|
198
|
-
function of simple_form.
|
199
|
-
|
200
|
-
the passed collection.
|
241
|
+
function of simple_form. And finally we have the generator block which works
|
242
|
+
the same as for fieldsets and blocks.
|
201
243
|
|
202
|
-
|
244
|
+
In our example above, we pass the `:collection` attribute in the options
|
245
|
+
which instructs simple_form to render a select box containing the elements
|
246
|
+
of the passed collection. Here we have to do a little trick. If we simple
|
247
|
+
pass the array of tags here as in
|
203
248
|
|
204
249
|
form.fields(:tags, :tags, :collection => Tag.all) do |tags|
|
205
250
|
|
206
251
|
the `Tags.all` would be evaluated just once, during the initialization of the
|
207
|
-
application. New tags would not be included. To fix this, we
|
208
|
-
lambda block which is evaluated each time again during the form
|
209
|
-
This lambda is expected to return
|
210
|
-
method of simple_form.
|
252
|
+
application. New tags created later would not be included. To fix this, we
|
253
|
+
pass a `lambda` block which is evaluated each time again during the form
|
254
|
+
rendering. This lambda is expected to return a hash of options which is
|
255
|
+
passed to the `association` method of simple_form.
|
256
|
+
|
257
|
+
## `table_fields`
|
258
|
+
|
259
|
+
The `table_fields` element allows to render a nested association in a table.
|
260
|
+
This is especially suitable for editing a large amount of nested objects.
|
261
|
+
|
262
|
+
For additional forms functionality, you might want to use
|
263
|
+
[cocoon](https://github.com/nathanvda/cocoon) which adds some JQuery functions
|
264
|
+
to manipulate table rows in the browser (e.g. add or delete rows).
|
265
|
+
|
266
|
+
FormForms::Registry[:user] = FormForms::Form.new() do |form|
|
267
|
+
form.field(:name) {|f| f.input :name}
|
268
|
+
|
269
|
+
form.table_fields(:companies, :companies) do |table|
|
270
|
+
table.header do |header|
|
271
|
+
header.field(:name) {|f| "Name" }
|
272
|
+
header.field(:location) {|f| "Location" }
|
273
|
+
header.field(:description) {|f| "Description" }
|
274
|
+
header.field(:actions, :class => "actions") {|f| "Actions" }
|
275
|
+
end
|
276
|
+
|
277
|
+
# each data row gets these attributes
|
278
|
+
table.row_args({:class => "nested-fields"})
|
279
|
+
|
280
|
+
table.field(:name) {|f| f.input :name}
|
281
|
+
table.field(:location) {|f| f.input :location}
|
282
|
+
table.field(:description) {|f| f.input :description}
|
283
|
+
|
284
|
+
table.sub_form(:actions) do |actions|
|
285
|
+
# Each cell in the actions column gets a custom class
|
286
|
+
actions.cell_args ({:class => "actions"})
|
287
|
+
|
288
|
+
actions.field(:delete) {|f| content_tag(:a, :href => "#") { "Delete" } }
|
289
|
+
actions.field(:id) {|f| f.hidden_field :id }
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
For rendering a tabular form for an association, call `table_fields` with the
|
295
|
+
form element name and the name of the association. Optionally you can add
|
296
|
+
additional options which get passed to the simple_form `association` method.
|
297
|
+
|
298
|
+
In the `table_fields` generator, you have to define header and body fields.
|
299
|
+
You can use and element type for each of the fields. You just have to make
|
300
|
+
sure that the number of elements in the header and the body matches.
|
301
|
+
|
302
|
+
The `table_fields` environment slightly adapts all nested elements. To be
|
303
|
+
able to adapt the generated `<th` and `<td>` tags, each element has an
|
304
|
+
additional property called `cell_args`. If you set it to a hash, you can set
|
305
|
+
any HTML attributes of the generated cell tag. As before, you can also pass a
|
306
|
+
proc which return a hash for lazy evaluation.
|
307
|
+
|
308
|
+
Each element in the table body gets automatically wrapped in a `<td>` tag.
|
309
|
+
Each element in the association collection is rendered as s single row.
|
310
|
+
|
311
|
+
To customize the table, you can use the following properties:
|
312
|
+
|
313
|
+
* `table.args`: Hash of additional arguments for specifying the simple_form association.
|
314
|
+
* `table.table_args`: Hash of HTML attributes of the `<table>` tag.
|
315
|
+
* `table.row_args`: Hash of HTML attributes of each `<tr>` tag.
|
316
|
+
* `table.<element>.cell_args`: Hash of HTML attributes of the `<td>` tag for a field in a row.
|
317
|
+
|
318
|
+
* `table.header`
|
319
|
+
* `table.header.row_args`: Hash of HTML attributes of the header row
|
320
|
+
* `table.header.<element>.cell_args`: Hash of HTML attributes of the `<th>` tag for a field in the header row
|
211
321
|
|
212
322
|
# Development
|
213
323
|
|
data/lib/form_forms/elements.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
require 'form_forms/elements/base_element'
|
2
|
+
require 'form_forms/elements/sub_form'
|
2
3
|
require 'form_forms/elements/block'
|
3
4
|
require 'form_forms/elements/field'
|
4
5
|
require 'form_forms/elements/fields'
|
5
6
|
require 'form_forms/elements/fieldset'
|
7
|
+
|
8
|
+
require 'form_forms/elements/table_fields'
|
9
|
+
require 'form_forms/elements/table_header'
|
10
|
+
require 'form_forms/elements/table_header_field'
|
@@ -58,7 +58,8 @@ module FormForms
|
|
58
58
|
# # => [:subject, :name, :description]
|
59
59
|
|
60
60
|
def self.allowed_sub_element(type, klass=nil)
|
61
|
-
klass ||=
|
61
|
+
klass ||= element_class_name(type)
|
62
|
+
klass = klass.name if klass.is_a?(Class)
|
62
63
|
|
63
64
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
64
65
|
def #{type}(name, *args, &block)
|
@@ -86,6 +87,10 @@ module FormForms
|
|
86
87
|
RUBY
|
87
88
|
end
|
88
89
|
|
90
|
+
def self.element_class_name(type)
|
91
|
+
"::FormForms::Elements::#{type.to_s.classify}"
|
92
|
+
end
|
93
|
+
|
89
94
|
# Define a property of the element. Properties can either be given as
|
90
95
|
# a block, similar to the fields, or as a static object. Internally,
|
91
96
|
# we always use the block form.
|
@@ -1,12 +1,6 @@
|
|
1
1
|
module FormForms
|
2
2
|
module Elements
|
3
|
-
class Block <
|
4
|
-
allowed_sub_element :field
|
5
|
-
allowed_sub_element :block
|
6
|
-
allowed_sub_element :fieldset
|
7
|
-
allowed_sub_element :fields
|
8
|
-
allowed_sub_element :table_fields
|
9
|
-
|
3
|
+
class Block < SubForm
|
10
4
|
def initialize(element, args = {})
|
11
5
|
@element = element.to_sym
|
12
6
|
self.args args
|
@@ -19,13 +19,15 @@ module FormForms
|
|
19
19
|
# end
|
20
20
|
# end
|
21
21
|
# end
|
22
|
-
class Field
|
22
|
+
class Field < BaseElement
|
23
23
|
def initialize(&generator)
|
24
|
-
|
24
|
+
self.generator generator
|
25
25
|
end
|
26
26
|
|
27
|
+
property :generator
|
28
|
+
|
27
29
|
def render(builder, view)
|
28
|
-
|
30
|
+
eval_property(:generator, builder, view)
|
29
31
|
end
|
30
32
|
end
|
31
33
|
end
|
@@ -1,13 +1,7 @@
|
|
1
1
|
module FormForms
|
2
2
|
module Elements
|
3
|
-
class Fields <
|
4
|
-
|
5
|
-
allowed_sub_element :block
|
6
|
-
allowed_sub_element :fieldset
|
7
|
-
allowed_sub_element :fields
|
8
|
-
allowed_sub_element :table_fields
|
9
|
-
|
10
|
-
def initialize(association=nil, form_args={})
|
3
|
+
class Fields < SubForm
|
4
|
+
def initialize(association, form_args={})
|
11
5
|
self.association association
|
12
6
|
self.args form_args
|
13
7
|
super
|
@@ -20,8 +14,8 @@ module FormForms
|
|
20
14
|
association = eval_property(:association, builder, view)
|
21
15
|
form_args = eval_property(:args, builder, view)
|
22
16
|
|
23
|
-
builder.association(association, form_args) do |
|
24
|
-
|
17
|
+
builder.association(association, form_args) do |sub_builder|
|
18
|
+
super(sub_builder, view)
|
25
19
|
end
|
26
20
|
end
|
27
21
|
end
|
@@ -1,11 +1,14 @@
|
|
1
1
|
module FormForms
|
2
2
|
module Elements
|
3
|
-
class Fieldset <
|
4
|
-
def initialize(
|
3
|
+
class Fieldset < SubForm
|
4
|
+
def initialize(args={}, &generator)
|
5
5
|
self.legend nil
|
6
|
-
|
6
|
+
self.args args
|
7
|
+
|
8
|
+
super
|
7
9
|
end
|
8
10
|
|
11
|
+
property :args
|
9
12
|
property :legend
|
10
13
|
|
11
14
|
# Generate a fielset with a legend
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module FormForms
|
2
|
+
module Elements
|
3
|
+
class SubForm < BaseElement
|
4
|
+
allowed_sub_element :field
|
5
|
+
allowed_sub_element :sub_form
|
6
|
+
allowed_sub_element :block
|
7
|
+
allowed_sub_element :fieldset
|
8
|
+
allowed_sub_element :fields
|
9
|
+
allowed_sub_element :table_fields
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module FormForms
|
2
|
+
module Elements
|
3
|
+
class TableFields < BaseElement
|
4
|
+
protected
|
5
|
+
|
6
|
+
# For an allowed_sub_element, create a subclass which renders the
|
7
|
+
# element as a table cell. It wrapps the rendering of the original
|
8
|
+
# element in an HTML td tag.
|
9
|
+
# To allow to customize the tag, it adds a new property called
|
10
|
+
# +cell_args+ to the subclass which allows to set additional arguments
|
11
|
+
# to the +td+ tag.
|
12
|
+
def self.wrap_sub_element(type)
|
13
|
+
parent = element_class_name(type)
|
14
|
+
|
15
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
16
|
+
class #{parent.demodulize} < #{parent}
|
17
|
+
def initialize(*args, &block)
|
18
|
+
self.cell_args {}
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
property :cell_args
|
23
|
+
|
24
|
+
def render(builder, view)
|
25
|
+
cell_args = eval_property(:cell_args, builder, view)
|
26
|
+
view.content_tag(:td, cell_args) do
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
RUBY
|
32
|
+
|
33
|
+
"FormForms::Elements::TableFields::#{parent.demodulize}"
|
34
|
+
end
|
35
|
+
|
36
|
+
public
|
37
|
+
allowed_sub_element :field, wrap_sub_element(:field)
|
38
|
+
allowed_sub_element :sub_form, wrap_sub_element(:sub_form)
|
39
|
+
allowed_sub_element :block, wrap_sub_element(:block)
|
40
|
+
allowed_sub_element :fieldset, wrap_sub_element(:fieldset)
|
41
|
+
allowed_sub_element :fields, wrap_sub_element(:fields)
|
42
|
+
|
43
|
+
def initialize(association, form_args={})
|
44
|
+
self.association association
|
45
|
+
self.args form_args
|
46
|
+
self.table_args {}
|
47
|
+
self.row_args {}
|
48
|
+
|
49
|
+
super
|
50
|
+
end
|
51
|
+
|
52
|
+
property :association
|
53
|
+
property :args
|
54
|
+
property :table_args
|
55
|
+
property :row_args
|
56
|
+
|
57
|
+
def header(row_args = {}, &generator)
|
58
|
+
@header = TableHeader.new(row_args, &generator) if block_given?
|
59
|
+
@header
|
60
|
+
end
|
61
|
+
|
62
|
+
def render(builder, view)
|
63
|
+
association = eval_property(:association, builder, view)
|
64
|
+
form_args = eval_property(:args, builder, view)
|
65
|
+
table_args = eval_property(:table_args, builder, view)
|
66
|
+
|
67
|
+
view.content_tag(:table, table_args) do
|
68
|
+
# render the header row
|
69
|
+
view.concat view.content_tag(:thead, header.render(builder, view)) if @header
|
70
|
+
|
71
|
+
# render the table body
|
72
|
+
body = builder.association(association, form_args) do |sub_builder|
|
73
|
+
row_args = eval_property(:row_args, sub_builder, view)
|
74
|
+
view.content_tag(:tr, row_args) do
|
75
|
+
super(sub_builder, view)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
view.concat view.content_tag(:tbody, body)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module FormForms
|
2
|
+
module Elements
|
3
|
+
class TableHeader < BaseElement
|
4
|
+
allowed_sub_element :field, element_class_name(:table_header_field)
|
5
|
+
|
6
|
+
def initialize(row_args={})
|
7
|
+
self.row_args row_args
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
property :row_args
|
12
|
+
|
13
|
+
def render(builder, view)
|
14
|
+
return "" if self.elements.empty?
|
15
|
+
|
16
|
+
row_args = eval_property(:row_args, builder, view)
|
17
|
+
view.content_tag(:tr, row_args) do
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module FormForms
|
2
|
+
module Elements
|
3
|
+
class TableHeaderField < Field
|
4
|
+
def initialize(cell_args={}, &generator)
|
5
|
+
self.cell_args cell_args
|
6
|
+
super(&generator)
|
7
|
+
end
|
8
|
+
|
9
|
+
property :cell_args
|
10
|
+
|
11
|
+
def render(builder, view)
|
12
|
+
cell_args = eval_property(:cell_args, builder, view)
|
13
|
+
|
14
|
+
view.content_tag(:th, cell_args) do
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/form_forms/version.rb
CHANGED
@@ -95,4 +95,15 @@ class FieldTest < ActionView::TestCase
|
|
95
95
|
assert_select 'input.string#user_name'
|
96
96
|
assert_no_select 'textarea.text#user_description'
|
97
97
|
end
|
98
|
+
|
99
|
+
test "replace a field's generator" do
|
100
|
+
with_form_for(@user) do |form|
|
101
|
+
form.field(:name) {|f| f.input :name}
|
102
|
+
|
103
|
+
form.field(:name).generator {|f| f.input :description}
|
104
|
+
end
|
105
|
+
|
106
|
+
assert_no_select 'input.string#user_name'
|
107
|
+
assert_select 'textarea.text#user_description'
|
108
|
+
end
|
98
109
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class SubFormTest < ActionView::TestCase
|
4
|
+
test "create a subform" do
|
5
|
+
with_form_for(@user) do |form|
|
6
|
+
form.field(:name) {|f| f.input :name}
|
7
|
+
|
8
|
+
form.sub_form(:credit) do |block|
|
9
|
+
block.field(:credit_card) {|f| f.input :credit_card}
|
10
|
+
block.field(:credit_limit) {|f| f.input :credit_limit}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
assert_select 'form > div:nth-of-type(2) > input.string#user_name'
|
15
|
+
assert_select 'form > div:nth-of-type(3) > input.string#user_credit_card'
|
16
|
+
assert_select 'form > div:nth-of-type(4) > input.decimal#user_credit_limit'
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.expand_path('../../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class TableFieldsTest < ActionView::TestCase
|
4
|
+
test "render a sub-form as a table" do
|
5
|
+
with_form_for(@user) do |form|
|
6
|
+
form.field(:name) {|f| f.input :name}
|
7
|
+
|
8
|
+
form.table_fields(:companies, :companies) do |table|
|
9
|
+
table.header do |header|
|
10
|
+
header.field(:name) {|f| "Name" }
|
11
|
+
header.field(:location) {|f| "Location" }
|
12
|
+
header.field(:description) {|f| "Description" }
|
13
|
+
header.field(:actions, :class => "actions") {|f| "Actions" }
|
14
|
+
end
|
15
|
+
|
16
|
+
table.row_args({:class => "nested-fields"})
|
17
|
+
|
18
|
+
table.field(:name) {|f| f.input :name}
|
19
|
+
table.field(:location) {|f| f.input :location}
|
20
|
+
table.field(:description) {|f| f.input :description}
|
21
|
+
table.sub_form(:actions) do |actions|
|
22
|
+
actions.field(:delete) {|f| content_tag(:a, :href => "#") { "Delete" } }
|
23
|
+
actions.field(:id) {|f| f.hidden_field :id }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
assert_select 'form > div:nth-of-type(2) > input.string#user_name'
|
29
|
+
|
30
|
+
# Header rendered correctly?
|
31
|
+
assert_select 'form > table'
|
32
|
+
assert_select 'table > thead > tr > th:nth-of-type(1)', 'Name'
|
33
|
+
assert_select 'table > thead > tr > th:nth-of-type(2)', 'Location'
|
34
|
+
assert_select 'table > thead > tr > th:nth-of-type(4).actions', 'Actions'
|
35
|
+
|
36
|
+
# All rows fully rendered
|
37
|
+
(1..3).each do |i|
|
38
|
+
assert_select "table > tbody > tr:nth-of-type(#{i}) > td:nth-of-type(1) input.string#user_companies_attributes_#{i-1}_name[value='Company #{i}']"
|
39
|
+
assert_select "table > tbody > tr:nth-of-type(#{i}) > td:nth-of-type(2) textarea.text#user_companies_attributes_#{i-1}_location", Company.all[i-1].location
|
40
|
+
assert_select "table > tbody > tr:nth-of-type(#{i}) > td:nth-of-type(3) textarea.text#user_companies_attributes_#{i-1}_description", ""
|
41
|
+
|
42
|
+
assert_select "table > tbody > tr:nth-of-type(#{i}) > td:nth-of-type(4) a[href='#']", "Delete"
|
43
|
+
assert_select "table > tbody > tr:nth-of-type(#{i}) > td:nth-of-type(4) input#user_companies_attributes_#{i-1}_id[type=hidden][value=#{i}]"
|
44
|
+
end
|
45
|
+
|
46
|
+
# No more rows than expected
|
47
|
+
assert_no_select 'table > tbody > tr:nth-of-type(4)'
|
48
|
+
end
|
49
|
+
end
|
data/test/support/models.rb
CHANGED
@@ -8,16 +8,17 @@ class Column < ColumnParent
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
Association = Struct.new(:klass, :name, :macro, :options)
|
11
|
+
Association = Struct.new(:klass, :name, :macro, :options) unless defined?(Association)
|
12
12
|
|
13
13
|
# Always use the same parent, even after multiple requires
|
14
|
-
CompanyParent = Struct.new(:id, :name) unless defined?(CompanyParent)
|
14
|
+
CompanyParent = Struct.new(:id, :name, :location, :description) unless defined?(CompanyParent)
|
15
15
|
class Company < CompanyParent
|
16
16
|
extend ActiveModel::Naming
|
17
17
|
include ActiveModel::Conversion
|
18
18
|
|
19
19
|
def self.all(options={})
|
20
|
-
|
20
|
+
streets = ["Main Street", "Second Street", "Evergreen Terrace"]
|
21
|
+
all = (1..3).map{|i| Company.new(i, "Company #{i}", streets[i-1])}
|
21
22
|
return [all.first] if options[:conditions].present?
|
22
23
|
return [all.last] if options[:order].present?
|
23
24
|
return all[0..1] if options[:include].present?
|
@@ -29,6 +30,15 @@ class Company < CompanyParent
|
|
29
30
|
(a || {}).merge(b || {})
|
30
31
|
end
|
31
32
|
|
33
|
+
def column_for_attribute(attribute)
|
34
|
+
column_type, limit = case attribute.to_sym
|
35
|
+
when :name then [:string, 100]
|
36
|
+
when :location, :description then [:text, 200]
|
37
|
+
end
|
38
|
+
Column.new(attribute, column_type, limit)
|
39
|
+
end
|
40
|
+
|
41
|
+
|
32
42
|
def persisted?
|
33
43
|
true
|
34
44
|
end
|
@@ -48,7 +58,8 @@ class User
|
|
48
58
|
:description, :created_at, :updated_at, :credit_limit, :password, :url,
|
49
59
|
:delivery_time, :born_at, :special_company_id, :country, :tags, :tag_ids,
|
50
60
|
:avatar, :home_picture, :email, :status, :residence_country, :phone_number,
|
51
|
-
:post_count, :lock_version, :amount, :attempts, :action, :credit_card,
|
61
|
+
:post_count, :lock_version, :amount, :attempts, :action, :credit_card,
|
62
|
+
:gender, :company_ids, :companies
|
52
63
|
|
53
64
|
def initialize(options={})
|
54
65
|
@new_record = false
|
@@ -65,9 +76,16 @@ class User
|
|
65
76
|
!@new_record
|
66
77
|
end
|
67
78
|
|
79
|
+
def companies
|
80
|
+
Company.all
|
81
|
+
end
|
82
|
+
|
68
83
|
def company_attributes=(*)
|
69
84
|
end
|
70
85
|
|
86
|
+
def companies_attributes=(*)
|
87
|
+
end
|
88
|
+
|
71
89
|
def tags_attributes=(*)
|
72
90
|
end
|
73
91
|
|
@@ -109,6 +127,8 @@ class User
|
|
109
127
|
case association
|
110
128
|
when :company
|
111
129
|
Association.new(Company, association, :belongs_to, {})
|
130
|
+
when :companies
|
131
|
+
Association.new(Company, association, :has_many, {})
|
112
132
|
when :tags
|
113
133
|
Association.new(Tag, association, :has_many, {})
|
114
134
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: form_forms
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-06-
|
12
|
+
date: 2012-06-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
16
|
-
requirement: &
|
16
|
+
requirement: &2163568860 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '3.0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2163568860
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: actionpack
|
27
|
-
requirement: &
|
27
|
+
requirement: &2163568360 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '3.0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *2163568360
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: simple_form
|
38
|
-
requirement: &
|
38
|
+
requirement: &2163567980 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *2163567980
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: activemodel
|
49
|
-
requirement: &
|
49
|
+
requirement: &2163567440 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
version: '3.0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *2163567440
|
58
58
|
description: Configurable forms for Rails
|
59
59
|
email:
|
60
60
|
- hjust@meine-er.de
|
@@ -64,6 +64,7 @@ extra_rdoc_files: []
|
|
64
64
|
files:
|
65
65
|
- .gitignore
|
66
66
|
- .travis.yml
|
67
|
+
- CHANGELOG.md
|
67
68
|
- Gemfile
|
68
69
|
- LICENSE
|
69
70
|
- README.md
|
@@ -79,6 +80,10 @@ files:
|
|
79
80
|
- lib/form_forms/elements/field.rb
|
80
81
|
- lib/form_forms/elements/fields.rb
|
81
82
|
- lib/form_forms/elements/fieldset.rb
|
83
|
+
- lib/form_forms/elements/sub_form.rb
|
84
|
+
- lib/form_forms/elements/table_fields.rb
|
85
|
+
- lib/form_forms/elements/table_header.rb
|
86
|
+
- lib/form_forms/elements/table_header_field.rb
|
82
87
|
- lib/form_forms/form_registry.rb
|
83
88
|
- lib/form_forms/forms.rb
|
84
89
|
- lib/form_forms/forms/base_form.rb
|
@@ -88,6 +93,8 @@ files:
|
|
88
93
|
- test/form_forms/elements/field_test.rb
|
89
94
|
- test/form_forms/elements/fields_test.rb
|
90
95
|
- test/form_forms/elements/fieldset_test.rb
|
96
|
+
- test/form_forms/elements/sub_form_test.rb
|
97
|
+
- test/form_forms/elements/table_fields_test.rb
|
91
98
|
- test/form_forms/form_registry_test.rb
|
92
99
|
- test/form_forms/forms/form_test.rb
|
93
100
|
- test/support/misc_helpers.rb
|
@@ -123,6 +130,8 @@ test_files:
|
|
123
130
|
- test/form_forms/elements/field_test.rb
|
124
131
|
- test/form_forms/elements/fields_test.rb
|
125
132
|
- test/form_forms/elements/fieldset_test.rb
|
133
|
+
- test/form_forms/elements/sub_form_test.rb
|
134
|
+
- test/form_forms/elements/table_fields_test.rb
|
126
135
|
- test/form_forms/form_registry_test.rb
|
127
136
|
- test/form_forms/forms/form_test.rb
|
128
137
|
- test/support/misc_helpers.rb
|