form_forms 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.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .rvmrc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.3
5
+ - jruby-18mode # JRuby in 1.8 mode
6
+ - jruby-19mode # JRuby in 1.9 mode
7
+ - rbx-18mode
8
+ - rbx-19mode
9
+ gemfile:
10
+ - gemfiles/Gemfile.rails3_0
11
+ - gemfiles/Gemfile.rails3_1
12
+ - gemfiles/Gemfile.rails3_2
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in formforms.gemspec
4
+ gemspec
5
+
6
+ gem 'rake'
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Holger Just
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,239 @@
1
+ # FormForms [![Build Status](https://secure.travis-ci.org/meineerde/form_forms.png)](http://travis-ci.org/meineerde/form_forms) [![Dependency Status](https://gemnasium.com/meineerde/form_forms.png?travis)](https://gemnasium.com/meineerde/form_forms)
2
+
3
+ Configurable forms for Rails 3, based on the excellent
4
+ [simple_form](https://github.com/plataformatec/simple_form) gem.
5
+
6
+ The goal of this gem is to provide forms (as in views) which are flexible
7
+ enough to fulfill their intended usage but be able to be configured by
8
+ plugins. Thus plugins can easily add, delete and edit form fields without
9
+ having to override whole views (which are almost impossible to patch) or
10
+ having to monkey patch existing code which is hard to maintain.
11
+
12
+ form_forms is originally intended to be used with the simple_form gem and uses
13
+ its API in several of its shipped element definitions. While it is possible
14
+ to not use those and provide custom definitions, we require simple_form as
15
+ a dependency right now.
16
+
17
+ # Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ gem 'form_forms'
22
+
23
+ and then run `bundle install`.
24
+
25
+ Or install it yourself using
26
+
27
+ gem install form_forms
28
+
29
+ # Usage
30
+
31
+ form_forms is built around the idea that a Rails application is able to
32
+ define arbitrary forms using a simple yet powerful DSL. These form
33
+ definitions are typically contained in the `lib` directory and are loaded
34
+ during initialization. During rendering of a request, the view simply
35
+ retrieves an existing form definition and renders that form by parameterizing
36
+ it with some data object (e.g. an ActiveRecord model instance).
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
+ ## Defining Forms
43
+
44
+ FormForms::Registry[:my_form] = FormForms::Form.new() do |form|
45
+ form.field(:subject) {|f| f.input :subject}
46
+ form.field(:link) do |f|
47
+ content_tag :p do
48
+ content_tag(:a, :href => hint_path){ "This is a hint" }
49
+ end
50
+ end
51
+ end
52
+
53
+ This will register a new form object under the name `:my_form` which will
54
+ render two fields: an input field for the `subject` attribute of the passed
55
+ model instance and a "static" hint. Each fields definition has a block which
56
+ defines what will actually be rendered. This block will be executed in the
57
+ context of the current view which allows you to use any helper methods you
58
+ have defined in your application.
59
+
60
+ Each of the fields is identified with a name, passed as the first parameter
61
+ to the field definition. Using this name, plugins can later change or remove
62
+ individual fields. As such, the name of each field has to be unique on each
63
+ level of the form.
64
+
65
+ The form can be rendered in a view by using something like this:
66
+
67
+ <%= FormForms::Registry[:my_form].render(@model, self) %>
68
+
69
+ The render method takes two parameters: the model object that should be used
70
+ as the base object for the form and a view instance (which can be almost
71
+ always passed as `self`). The view instance is used to render the form fields.
72
+
73
+ ## Adapting an existing form from plugins
74
+
75
+ Once a form is defined, it fields can be added, changed, and removed later on.
76
+
77
+ New fields can be added either before or after already existing elements:
78
+
79
+ FormForms::Registry[:my_form].field_before(:description, :name) {|f| f.input :name}
80
+ FormForms::Registry[:my_form].field_after(:credit_card, :ccv) {|f| f.input :ccv}
81
+
82
+ This will insert the new field `:name` before the already defined field
83
+ `:description` and insert the field `:ccv` after the existing `:credit_card`
84
+ field. You can also insert elements at the very beginning and the very end of
85
+ the elements list:
86
+
87
+ FormForms::Registry[:my_form].field_first(:salutation) {|f| f.input :salutation}
88
+ FormForms::Registry[:my_form].field_last(:accept_terms) {|f| f.input :accept_terms}
89
+
90
+ Each of the element types defines the five methods
91
+
92
+ * `form.<type>`
93
+ * `form.<type>_first`
94
+ * `form.<type>_before`
95
+ * `form.<type>_after`
96
+ * `form.<type>_last` (an alias to `form.<type>`)
97
+
98
+ Each of the methods accepts all parameters of the respective element type.
99
+ Only the `form.<type>_before` and `form.<type>_after` methods take the name of
100
+ the element that should act as the respective index as the first argument.
101
+
102
+ Finally, there is the `form.delete` method which simply takes the name of an
103
+ element as its parameter. It complete deletes this element from the element
104
+ list, preventing it from rendering:
105
+
106
+ FormForms::Registry[:my_form].delete(:salutation)
107
+
108
+ # Element Types
109
+
110
+ form_forms already ships with different element types which allow you to
111
+ create most of the common form elements. These elements are used to define
112
+ the actual form body.
113
+
114
+ ## `field`
115
+
116
+ The most basic element type is a `field` which is rendered to a basic form
117
+ field or a snipped of static code. Fields are the most basic form of elements
118
+ and typically made of the bulk of the form definition.
119
+
120
+ For the definition, they take a `name` which is used for identifying the
121
+ field in the form as well as a generator block to render the field. The name,
122
+ always being the first attribute is *only* used for identifying the element
123
+ later on and is not passed to the generator block during rendering and thus
124
+ can be chosen arbitrarily. It must only be unique in the current scope.
125
+ Generally, it is advisable to chose a name similar to the model field that is
126
+ actually rendered.
127
+
128
+ The block receives one argument during rendering: the form builder, i.e. the
129
+ simple_form object. Alternatively to a block, you can also directly pass a
130
+ string which will be emitted as-is. The usual HTML escape rules apply, i.e.
131
+ you have to use `html_save` correctly to avoid rendering unsafe data.
132
+
133
+ ## `fieldset`
134
+
135
+ 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>`. As a fieldset
137
+ naturally contains other fields, its generator block can be used to define
138
+ fields.
139
+
140
+ FormForms::Registry[:user] = FormForms::Form.new() do |form|
141
+ form.field(:street) {|f| f.input :street }
142
+ form.field(:city) {|f| f.input :city }
143
+ form.fieldset(:payment, :id => "payment") do |fieldset|
144
+ fieldset.legend "Credit Card Data"
145
+ fieldset.field(:credit_card) {|f| f.input :credit_card}
146
+ fieldset.field(:ccv) {|f| f.input :ccv}
147
+ end
148
+ end
149
+
150
+ The fieldset element creates a new scope (or sub-form) which can be
151
+ arbitrarily nested. Apart from all the other elements types, it has a special
152
+ sub element: the `legend`. It doesn't take a name as its first parameter.
153
+ Instead, it just takes a string or a generator block (similar to a `field`)
154
+ which is used to render the content of the `<legend>` tag of the fieldset.
155
+
156
+ The fieldset tag receives one additional (optional) hash argument which
157
+ allows to define additional options (e.g. additional HTML attributes) for the
158
+ the fieldset tag. All options supported by Rails' `content_tag` helper are
159
+ allowed here.
160
+
161
+ ## `block`
162
+
163
+ A `block` creates a sub-form which nests form elements inside a HTML block
164
+ (e.g. a `<div>`) which is sometimes necessary to further group elements and
165
+ markup the form using custom CSS rules. This element works similar to the
166
+ `fieldset` described above.
167
+
168
+ FormForms::Registry[:user] = FormForms::Form.new() do |form|
169
+ form.field(:street) {|f| f.input :street }
170
+ form.block(:box, :class => "red-and-blinky") do |block|
171
+ block.field(:sell_your_soul) {|f| f.input :sell_your_soul}
172
+ end
173
+ end
174
+
175
+ ## `fields`
176
+
177
+ Using fields, you can create sub-forms for association of the model object.
178
+ This allows you to create forms for nested elements.
179
+
180
+ FormForms::Registry[:user] = FormForms::Form.new() do |form|
181
+ form.field(:name) {|f| f.input :name}
182
+
183
+ args = lambda{|f| {:collection => Tag.all} }
184
+ form.fields(:tags, :tags, args) do |tags|
185
+ tags.field(:name) {|f| f.input :name}
186
+ end
187
+ end
188
+
189
+ The fields element type takes four arguments. The first is the name of the
190
+ element as usual. The second is the name of the association which is to be
191
+ rendered. For the example above, the `User` model is defined as follows:
192
+
193
+ class User < ActiveRecord::Base
194
+ has_many :tags
195
+ end
196
+
197
+ The third argument is an options hash which is passed to association render
198
+ function of simple_form. In our example, we pass the `:collection` attribute
199
+ which instructs simple_form to render a select box containing the elements of
200
+ the passed collection.
201
+
202
+ Here we have to do a little trick. If we simple pass the array here as in
203
+
204
+ form.fields(:tags, :tags, :collection => Tag.all) do |tags|
205
+
206
+ 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 pass a
208
+ lambda block which is evaluated each time again during the form rendering.
209
+ This lambda is expected to return an array of options to the `association`
210
+ method of simple_form.
211
+
212
+ # Development
213
+
214
+ Install dependencies with
215
+
216
+ bundle install
217
+
218
+ then run tests with
219
+
220
+ bundle exec rake
221
+
222
+ ## Contributing
223
+
224
+ 1. Fork it
225
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
226
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
227
+ 4. Push to the branch (`git push origin my-new-feature`)
228
+ 5. Create new Pull Request
229
+
230
+ # License
231
+
232
+ > take my code with you<br/>
233
+ > and do whatever you want<br/>
234
+ > but please don’t blame me<br/>
235
+
236
+ [License Haiku](http://www.aaronsw.com/weblog/000360)
237
+
238
+ This library is licensed under the MIT license. See the `LICENSE` file for
239
+ more details.
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rake/testtask'
5
+
6
+ task :default => [:test]
7
+ Rake::TestTask.new(:test) do |test|
8
+ test.libs << 'test'
9
+ test.test_files = FileList['test/**/*_test.rb']
10
+ test.verbose = true
11
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/form_forms/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Holger Just"]
6
+ gem.email = ["hjust@meine-er.de"]
7
+ gem.description = %q{Configurable forms for Rails}
8
+ gem.summary = %q{Configurable forms for Rails}
9
+ gem.homepage = "https://github.com/meineerde/form_forms"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "form_forms"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Formforms::VERSION
17
+
18
+ gem.add_dependency "activesupport", "~> 3.0"
19
+ gem.add_dependency "actionpack", "~> 3.0"
20
+ gem.add_dependency "simple_form"
21
+
22
+ gem.add_development_dependency "activemodel", "~> 3.0"
23
+ end
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "activesupport", "~> 3.0.0"
4
+ gem "actionpack", "~> 3.0.0"
5
+ gem "simple_form"
6
+
7
+ group :test do
8
+ gem "activemodel", "~> 3.0.0"
9
+ gem "rake"
10
+ end
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "activesupport", "~> 3.1.0"
4
+ gem "actionpack", "~> 3.1.0"
5
+ gem "simple_form"
6
+
7
+ group :test do
8
+ gem "activemodel", "~> 3.1.0"
9
+ gem "rake"
10
+ end
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "activesupport", "~> 3.2.0"
4
+ gem "actionpack", "~> 3.2.0"
5
+ gem "simple_form"
6
+
7
+ group :test do
8
+ gem "activemodel", "~> 3.2.0"
9
+ gem "rake"
10
+ end
@@ -0,0 +1,10 @@
1
+ require 'form_forms/elements'
2
+ require 'form_forms/forms'
3
+
4
+ require 'form_forms/form_registry'
5
+
6
+ require "form_forms/version"
7
+
8
+ module FormForms
9
+ # Your code goes here...
10
+ end
@@ -0,0 +1,5 @@
1
+ require 'form_forms/elements/base_element'
2
+ require 'form_forms/elements/block'
3
+ require 'form_forms/elements/field'
4
+ require 'form_forms/elements/fields'
5
+ require 'form_forms/elements/fieldset'
@@ -0,0 +1,189 @@
1
+ require 'active_support/core_ext/string'
2
+
3
+ module FormForms
4
+ module Elements
5
+ # The generic base class for the FormForms
6
+ class BaseElement
7
+ def initialize(*)
8
+ @elements = []
9
+ @generators = {}
10
+
11
+ yield self
12
+ end
13
+
14
+ attr_reader :elements
15
+
16
+ # Render all of the elements of the current FormForm in their defined
17
+ # order. This method is expected to be called by sub-classes in the
18
+ # rendering chain. The parameters are a form builder object and an
19
+ # action view instance.
20
+ #
21
+ # The form builder is typically created by a "special" FormForm class
22
+ # and passed on to its sub-elements. See +FormForms::Forms::Form+
23
+ # for an implementation of such a special form.
24
+ def render(builder, view)
25
+ render_elements(builder, view)
26
+ end
27
+
28
+ def delete(name)
29
+ @elements.delete(name)
30
+ @generators.delete(name)
31
+ nil
32
+ end
33
+
34
+ protected
35
+
36
+ # Allow the passed form type as a sub-element of the current form.
37
+ # Sub-elements must have a unique name inside the current form scope.
38
+ # They can be of any allowed type.
39
+ #
40
+ # Elements can be added to the form using the generated +type+,
41
+ # +type_before+, and +type_after+ methods. Element definitions can be
42
+ # overridden by setting the element again with the same name.
43
+ #
44
+ # Example:
45
+ #
46
+ # class MyForm < BaseForm
47
+ # allowed_sub_element(:field)
48
+ # end
49
+ #
50
+ # my_form = MyForm.new() do |form|
51
+ # form.field(:subject) {|f| f.input :subject}
52
+ # form.field(:description) {|f| f.text_field :description}
53
+ # end
54
+ #
55
+ # my_form.field_before(:description, :name) {|f| f.input :name}
56
+ #
57
+ # my_form.elements
58
+ # # => [:subject, :name, :description]
59
+
60
+ def self.allowed_sub_element(type, klass=nil)
61
+ klass ||= "::FormForms::Elements::#{type.to_s.classify}"
62
+
63
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
64
+ def #{type}(name, *args, &block)
65
+ name = name.to_sym
66
+
67
+ if block_given?
68
+ element(name, #{klass}.new(*args, &block))
69
+ else
70
+ @generators[name]
71
+ end
72
+ end
73
+ alias_method :#{type}_last, :#{type}
74
+
75
+ def #{type}_first(name, *args, &block)
76
+ element_before(nil, name.to_sym, #{klass}.new(*args, &block))
77
+ end
78
+
79
+ def #{type}_before(before, name, *args, &block)
80
+ element_before(before.to_sym, name.to_sym, #{klass}.new(*args, &block))
81
+ end
82
+
83
+ def #{type}_after(after, name, *args, &block)
84
+ element_after(after.to_sym, name.to_sym, #{klass}.new(*args, &block))
85
+ end
86
+ RUBY
87
+ end
88
+
89
+ # Define a property of the element. Properties can either be given as
90
+ # a block, similar to the fields, or as a static object. Internally,
91
+ # we always use the block form.
92
+ def self.property(name, instance_variable=nil)
93
+ instance_variable ||= name
94
+
95
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
96
+ def #{name}(param=indicator=true, &generator) # def legend(param=indicator=true, &generator
97
+ if block_given? # if block_given?
98
+ @#{instance_variable} = generator # @legend = generator
99
+ elsif indicator.nil? # elsif indicator.nil? # parameter was given
100
+ @#{instance_variable} = param # @legend = param
101
+ else # else
102
+ @#{instance_variable} # @legend
103
+ end # end
104
+ end # end
105
+ RUBY
106
+ end
107
+
108
+ # Get the actual value of a parameter. If the parameter was set with a
109
+ # block or a lambda, evaluate the lambda in the context of the view
110
+ # and return the result. This can be used for delayed evaluation of
111
+ # parameter arguments.
112
+ def eval_property(name, builder, view)
113
+ property = self.send(name.to_sym)
114
+ if property.is_a?(Proc)
115
+ view.instance_exec(builder, &property)
116
+ else
117
+ property
118
+ end
119
+ end
120
+
121
+ # Append a generic element to the end of the elements list. This method
122
+ # is supposed tpo be called by the generated public methods of each
123
+ # sub-element type.
124
+ def element(name, generator)
125
+ @elements << name unless @elements.include? name
126
+ @generators[name] = generator
127
+ end
128
+
129
+ # Insert an element directly after an existing alement. The element
130
+ # +name+ can not be defined in the current form scope yet. The +after+
131
+ # element has to exist or be nil. If it is nil, the new element is
132
+ # appended to the end of the element list.
133
+ def element_after(after, name, generator)
134
+ if @elements.include? name
135
+ # Only allow new elements. Existing fields can be changed with #element
136
+ raise ArgumentError.new("#{name} is already registered.")
137
+ end
138
+
139
+ after_index = @elements.index(after)
140
+ if !after.nil? && after_index.nil?
141
+ # This method makes only sense if the before element actually exists
142
+ raise ArgumentError.new("#{after} is not registered. I can't insert after it.")
143
+ elsif after.nil? || after_index == @elements.length-1
144
+ # Append at the end
145
+ element(name, generator)
146
+ else
147
+ # Perform an insert
148
+ @elements.insert(after_index+1, name)
149
+ @generators[name] = generator
150
+ end
151
+ end
152
+
153
+ # Inserts an element directly before an existing alement. The element
154
+ # +name+ can not be defined in the current form scope yet. The +before+
155
+ # element has to exist or be nil. If it is nil, the new element is added
156
+ # at the very beginning of the list.
157
+ def element_before(before, name, generator)
158
+ before_index = @elements.index(before)
159
+
160
+ if @elements.include? name
161
+ # Only allow new elements. Existing fields can be changed with #element
162
+ raise ArgumentError.new("#{name} is already registered.")
163
+ elsif !before.nil? && before_index.nil?
164
+ # This method makes only sense if the before element actually exists
165
+ raise ArgumentError.new("#{before} is not registered. I can't insert before it.")
166
+ end
167
+
168
+ # Perform an insert
169
+ if before.nil?
170
+ # insert at the beginning of the list
171
+ @elements.unshift(name)
172
+ else
173
+ # insert the element before the named one
174
+ @elements.insert(before_index, name)
175
+ end
176
+ @generators[name] = generator
177
+ end
178
+
179
+ # Render all elements in the current
180
+ def render_elements(builder, view, before="", after="\n")
181
+ @elements.each do |name|
182
+ view.concat before
183
+ view.concat @generators[name].render(builder, view)
184
+ view.concat after
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end