form_forms 0.1.0

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