form_forms 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|