formize 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +74 -0
- data/Rakefile +63 -0
- data/VERSION +1 -0
- data/assets/javascripts/formize.js +399 -0
- data/formize.gemspec +64 -0
- data/lib/formize/action_pack.rb +63 -0
- data/lib/formize/definition/element.rb +56 -0
- data/lib/formize/definition/field.rb +67 -0
- data/lib/formize/definition/field_set.rb +40 -0
- data/lib/formize/definition/form.rb +89 -0
- data/lib/formize/definition/form_element.rb +140 -0
- data/lib/formize/definition.rb +5 -0
- data/lib/formize/form_helper.rb +62 -0
- data/lib/formize/generator.rb +598 -0
- data/lib/formize.rb +34 -0
- data/test/helper.rb +9 -0
- data/test/test_formize.rb +7 -0
- metadata +100 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
module Formize
|
2
|
+
|
3
|
+
# Main class for Form definitions
|
4
|
+
# It permits to manage tree of form elements
|
5
|
+
class Element
|
6
|
+
attr_reader :parent, :children, :method_name, :id
|
7
|
+
@@count = 0
|
8
|
+
|
9
|
+
def initialize(parent = nil, is_method = false)
|
10
|
+
@parent = parent
|
11
|
+
@children = []
|
12
|
+
@is_method = is_method
|
13
|
+
@@count += 1
|
14
|
+
@id = @@count.to_s(36)
|
15
|
+
@method_name = "_formize_#{@id}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def is_method?
|
19
|
+
@is_method
|
20
|
+
end
|
21
|
+
|
22
|
+
def is_method!(value = true)
|
23
|
+
raise ArgumentError.new("Must be true or false (not #{value.inspect})") unless [TrueClass, FalseClass].include?(value.class)
|
24
|
+
@is_method = value
|
25
|
+
end
|
26
|
+
|
27
|
+
def method_name=(name)
|
28
|
+
raise ArgumentError.new("Name of field_set must be written only with a-z and 0-9 and _ (not #{name.inspect})") unless name.to_s == name.to_s.downcase.gsub(/[^a-z0-9\_]/, '')
|
29
|
+
@method_name = name
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def method_code(options={})
|
34
|
+
varh = options[:html_variable] ||= 'html'
|
35
|
+
code = "def #{method_name}(record)\n"
|
36
|
+
code << inner_method_code(options).gsub(/^/, ' ')
|
37
|
+
code << " return #{varh}\n"
|
38
|
+
code << "end\n"
|
39
|
+
return code
|
40
|
+
end
|
41
|
+
|
42
|
+
def method_call_code(options={})
|
43
|
+
return inner_method_code(options) unless self.is_method?
|
44
|
+
return "#{method_name}(record)"
|
45
|
+
end
|
46
|
+
|
47
|
+
def inner_method_code(options={})
|
48
|
+
# raise NotImplementedError.new
|
49
|
+
return content_tag(:strong, "'#{self.class.name} does not implement :#{__method__} method'", options)
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Formize
|
2
|
+
|
3
|
+
|
4
|
+
# Represents the field element
|
5
|
+
class Field < FormElement
|
6
|
+
attr_reader :name, :options, :column, :record_name, :method, :type, :required, :choices, :input_id, :source, :item_label, :field_id, :reflection, :html_options, :default, :search_attributes
|
7
|
+
|
8
|
+
TYPES = [:check_box, :choice, :date, :datetime, :label, :numeric, :password, :mono_choice, :string, :text_area].freeze
|
9
|
+
|
10
|
+
def initialize(form, parent, name, options={})
|
11
|
+
super(form, parent)
|
12
|
+
@name = name.to_s
|
13
|
+
@options = (options.is_a?(Hash) ? options : {})
|
14
|
+
@column = form.model.columns_hash[@name]
|
15
|
+
@record_name = form.record_name
|
16
|
+
@method = @name
|
17
|
+
unless @options[:default].nil?
|
18
|
+
@default = (@options[:default].is_a?(String) ? Code.new(@options[:default]) : @options[:default])
|
19
|
+
end
|
20
|
+
@html_options = @options.delete(:html_options)||{}
|
21
|
+
@depend_on = @options.delete(:depend_on)
|
22
|
+
raise ArgumentError.new("A depended element must defined before its dependencies (#{@depended.inspect})") if !@depend_on.blank? and form.fields[@depend_on].nil?
|
23
|
+
if type = @options.delete(:as)
|
24
|
+
raise ArgumentError.new("Unknown field type (got #{@options[:as].inspect}, expects #{TYPES.join(', ')})") unless TYPES.include? type
|
25
|
+
@type = type
|
26
|
+
else
|
27
|
+
@type = :password if @name.to_s.match /password/
|
28
|
+
if @choices = @options.delete(:choices)
|
29
|
+
if @choices.is_a? Array
|
30
|
+
@type = :choice
|
31
|
+
elsif [Symbol, Hash].include? @choices.class
|
32
|
+
@type = :mono_choice
|
33
|
+
@reflection = form.model.reflections[@method.to_sym]
|
34
|
+
@source = @options.delete(:source) # || @reflection.class_name
|
35
|
+
@is_method = true if @options[:new]
|
36
|
+
@method_name = self.form.unique_name + "_inf_" + @name
|
37
|
+
@method = @reflection.primary_key_name
|
38
|
+
unless @item_label = @options.delete(:item_label)
|
39
|
+
model = @reflection.class_name.constantize
|
40
|
+
available_methods = (model.columns_hash.keys+model.instance_methods).collect{|x| x.to_s}
|
41
|
+
@item_label = [:label, :name, :code, :number, :inspect].detect{|x| available_methods.include?(x.to_s)}
|
42
|
+
end
|
43
|
+
@search_attributes = @options[:search] || @reflection.class_name.constantize.content_columns.select{|c| c.type != :boolean and ![:created_at, :updated_at, :lock_version].include?(c.name.to_sym)}.collect{|c| c.name.to_sym}
|
44
|
+
else
|
45
|
+
raise ArgumentError.new("Option :choices must be Array, Symbol or Hash (got #{@choices.class.name})")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
if column
|
49
|
+
@type = :check_box if column.type == :boolean
|
50
|
+
@type = :date if column.type == :date
|
51
|
+
@type = :datetime if column.type==:datetime or column.type==:timestamp
|
52
|
+
@type = :numeric if [:integer, :float, :decimal].include? column.type
|
53
|
+
@type = :text_area if column.type == :text
|
54
|
+
end
|
55
|
+
@type = :label if @form.model.readonly_attributes.include? @record_name
|
56
|
+
@type ||= :string
|
57
|
+
end
|
58
|
+
@required = false
|
59
|
+
@required = !@column.null if @column
|
60
|
+
@required = true if @options.delete(:required).is_a?(TrueClass)
|
61
|
+
@input_id = form.model.name.underscore << '_' << method.to_s
|
62
|
+
@field_id = "ff" << Time.now.to_i.to_s(36) << rand.to_s[2..-1].to_i.to_s(36)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Formize
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
# Represents a group of fields which can depend on other fields
|
6
|
+
class FieldSet < FormElement
|
7
|
+
attr_reader :name, :options, :title, :html_options
|
8
|
+
|
9
|
+
def initialize(form, parent, name=nil, options={})
|
10
|
+
super(form, parent)
|
11
|
+
@title = nil
|
12
|
+
@name = if name.blank?
|
13
|
+
rand.to_s[2..-1].to_i.to_s(36)
|
14
|
+
else
|
15
|
+
raise ArgumentError.new("Name of field_set must be written only with a-z and 0-9 and _ (not #{name.inspect})") unless name.to_s == name.to_s.downcase.gsub(/[^a-z0-9\_]/, '')
|
16
|
+
@title = name
|
17
|
+
name.to_s
|
18
|
+
end
|
19
|
+
@depend_on = options.delete(:depend_on)
|
20
|
+
raise ArgumentError.new("A depended element must defined before its dependencies (#{@depended.inspect})") if !@depend_on.blank? and form.fields[@depend_on].nil?
|
21
|
+
@options = (options.is_a?(Hash) ? options : {})
|
22
|
+
@html_options = @options.delete(:html_options)||{}
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def field_set(name=nil, options={}, &block)
|
27
|
+
raise ArgumentError.new("Missing block") unless block_given?
|
28
|
+
field_set = self.new_child(FieldSet, name, options)
|
29
|
+
yield field_set
|
30
|
+
end
|
31
|
+
|
32
|
+
def field(name, options={})
|
33
|
+
self.new_child(Field, name, options)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Formize
|
2
|
+
|
3
|
+
# Represents an environment for a form or list of fields of one Record
|
4
|
+
class Form
|
5
|
+
attr_reader :model, :elements, :record_name, :unique_name, :options, :id
|
6
|
+
@@count = 0
|
7
|
+
|
8
|
+
|
9
|
+
def initialize(name, model, options={})
|
10
|
+
@name = name
|
11
|
+
@model = model
|
12
|
+
@options = options
|
13
|
+
@elements = []
|
14
|
+
@@count += 1
|
15
|
+
@id = @@count.to_s(36)
|
16
|
+
@unique_name = @options.delete(:unique_name) unless @options[:unique_name].blank?
|
17
|
+
@unique_name ||= "_formize#{@id}"
|
18
|
+
@record_name = @model.name.underscore
|
19
|
+
end
|
20
|
+
|
21
|
+
def field_set(name=nil, options={}, &block)
|
22
|
+
raise ArgumentError.new("Missing block") unless block_given?
|
23
|
+
field_set = new_element(FieldSet, name, options)
|
24
|
+
yield field_set
|
25
|
+
end
|
26
|
+
|
27
|
+
def field(name, options={})
|
28
|
+
return new_element(Field, name, options)
|
29
|
+
end
|
30
|
+
|
31
|
+
# def inner_method_code(options={})
|
32
|
+
# varh = options[:html_variable] || 'html'
|
33
|
+
# code = "#{varh} = ''\n"
|
34
|
+
# for child in children
|
35
|
+
# code << "#{varh} << " << child.method_call_code << "\n"
|
36
|
+
# end
|
37
|
+
# code << "return #{varh}\n"
|
38
|
+
# return code
|
39
|
+
# end
|
40
|
+
|
41
|
+
def controller_method_name
|
42
|
+
@options[:controller_method_name] || "formize_#{model.underscore}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def view_method_name
|
46
|
+
@options[:method_name] || "_form_#{model.underscore}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def action_name
|
50
|
+
@options[:action_name] || :formize
|
51
|
+
end
|
52
|
+
|
53
|
+
# def methodics
|
54
|
+
# return elements.collect{|e| e.methodics}.flatten
|
55
|
+
# end
|
56
|
+
|
57
|
+
def mono_choices
|
58
|
+
return elements.collect{|e| e.mono_choices}.flatten
|
59
|
+
end
|
60
|
+
|
61
|
+
def fields
|
62
|
+
return elements.inject(HashWithIndifferentAccess.new){|h, e| h.merge!(e.fields)}
|
63
|
+
end
|
64
|
+
|
65
|
+
def dependents
|
66
|
+
return elements.collect{|e| e.dependents}.flatten
|
67
|
+
end
|
68
|
+
|
69
|
+
def all_elements
|
70
|
+
return elements.collect{|e| e.all_elements}.flatten
|
71
|
+
end
|
72
|
+
|
73
|
+
def dependents_on(element)
|
74
|
+
return elements.collect{|e| e.dependents_on(element)}.flatten
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def new_element(klass, *args)
|
81
|
+
raise ArgumentError.new("Bad child type (#{klass.name}). Must be an Formize::FormElement") unless klass < FormElement
|
82
|
+
element = klass.new(self, nil, *args)
|
83
|
+
@elements << element
|
84
|
+
return element
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module Formize
|
2
|
+
|
3
|
+
# Main class for form elements
|
4
|
+
class FormElement
|
5
|
+
attr_reader :form, :parent, :children, :unique_name, :id, :depend_on, :html_id
|
6
|
+
@@count = 0
|
7
|
+
|
8
|
+
def initialize(form, parent = nil)
|
9
|
+
raise ArgumentError.new("Bad form (#{form.class.name}). Must be an Formize::Form") unless form.is_a? Formize::Form
|
10
|
+
@form = form
|
11
|
+
@parent = parent
|
12
|
+
@depend_on = nil
|
13
|
+
@children = []
|
14
|
+
@@count += 1
|
15
|
+
@id = @@count.to_s(36)
|
16
|
+
@html_id = "fz#{@id}"
|
17
|
+
@unique_name = self.form.unique_name + "_" + @html_id
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def dependeds
|
22
|
+
l = (self.parent ? self.parent.dependeds : [])
|
23
|
+
l << {:name=>self.depend_on} unless self.depend_on.blank?
|
24
|
+
return l
|
25
|
+
end
|
26
|
+
|
27
|
+
def arguments
|
28
|
+
args = []
|
29
|
+
args << {:name=>form.record_name}
|
30
|
+
# args += self.dependeds
|
31
|
+
# args << {:name=>@depend_on} if @depend_on
|
32
|
+
return args
|
33
|
+
end
|
34
|
+
|
35
|
+
def prototype
|
36
|
+
return "#{@unique_name}(" + arguments.collect{|x| x[:name]}.join(', ') + ")"
|
37
|
+
end
|
38
|
+
|
39
|
+
# def method_name=(name)
|
40
|
+
# raise ArgumentError.new("Name of field_set must be written only with a-z and 0-9 and _ (not #{name.inspect})") unless name.to_s == name.to_s.downcase.gsub(/[^a-z0-9\_]/, '')
|
41
|
+
# @method_name = name
|
42
|
+
# end
|
43
|
+
|
44
|
+
|
45
|
+
# def method_code(options={})
|
46
|
+
# varh = options[:html_variable] ||= 'html'
|
47
|
+
# code = "def #{method_name}(record)\n"
|
48
|
+
# code << inner_method_code(options).gsub(/^/, ' ')
|
49
|
+
# code << " return #{varh}\n"
|
50
|
+
# code << "end\n"
|
51
|
+
# return code
|
52
|
+
# end
|
53
|
+
|
54
|
+
# def method_call_code(options={})
|
55
|
+
# return inner_method_code(options) unless self.is_method?
|
56
|
+
# return "#{method_name}(record)"
|
57
|
+
# end
|
58
|
+
|
59
|
+
# def inner_method_code(options={})
|
60
|
+
# # raise NotImplementedError.new
|
61
|
+
# return content_tag(:strong, "'#{self.class.name} does not implement :#{__method__} method'", options)
|
62
|
+
# end
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
# def is_method?
|
67
|
+
# @depend_on.nil?
|
68
|
+
# end
|
69
|
+
|
70
|
+
# def methodics
|
71
|
+
# elements = []
|
72
|
+
# for child in self.children
|
73
|
+
# elements += child.methodics
|
74
|
+
# end
|
75
|
+
# elements << self if self.is_method?
|
76
|
+
# return elements
|
77
|
+
# end
|
78
|
+
|
79
|
+
def mono_choices
|
80
|
+
elements = []
|
81
|
+
for child in self.children
|
82
|
+
elements += child.mono_choices
|
83
|
+
end
|
84
|
+
elements << self if self.class == Formize::Field and self.type == :mono_choice
|
85
|
+
return elements
|
86
|
+
end
|
87
|
+
|
88
|
+
def fields
|
89
|
+
elements = HashWithIndifferentAccess.new()
|
90
|
+
for child in self.children
|
91
|
+
elements.merge!(child.fields)
|
92
|
+
end
|
93
|
+
elements[self.name] = self if self.class == Formize::Field
|
94
|
+
return elements
|
95
|
+
end
|
96
|
+
|
97
|
+
def dependents
|
98
|
+
elements = []
|
99
|
+
for child in self.children
|
100
|
+
elements += child.dependents
|
101
|
+
end
|
102
|
+
elements << self if self.options[:depend_on]
|
103
|
+
return elements
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
def all_elements
|
108
|
+
elements = self.children.collect{|c| c.all_elements}.flatten
|
109
|
+
elements << self
|
110
|
+
return elements
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# Find form elements
|
115
|
+
def dependents_on(element)
|
116
|
+
elements = []
|
117
|
+
for child in self.children
|
118
|
+
elements += child.dependents_on(element)
|
119
|
+
end
|
120
|
+
elements << self if self.depend_on and self.depend_on.to_s == element.name.to_s # form.fields[self.depend_on].name == element.name
|
121
|
+
return elements
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
|
126
|
+
|
127
|
+
protected
|
128
|
+
|
129
|
+
def new_child(klass, *args)
|
130
|
+
raise ArgumentError.new("Bad child type (#{klass.name}). Must be an Formize::FormElement") unless klass < FormElement
|
131
|
+
element = klass.new(self.form, self, *args)
|
132
|
+
@children << element
|
133
|
+
return element
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Formize
|
2
|
+
module FormHelper
|
3
|
+
|
4
|
+
# Generates a form with all its fields as defined in controller.
|
5
|
+
# If no name is given, it uses the name of the controller to find the corresponding model
|
6
|
+
def formize_form(*args)
|
7
|
+
name, options = nil, {}
|
8
|
+
name = args[0] if args[0].is_a? Symbol
|
9
|
+
options = args[-1] if args[-1].is_a? Hash
|
10
|
+
self.send("_#{options[:controller]||self.controller_name}_#{__method__}_#{name||self.controller_name}_tag")
|
11
|
+
end
|
12
|
+
|
13
|
+
# Generates all the fields as defined in controller with the <form> tag.
|
14
|
+
# If no name is given, it uses the name of the controller to find the corresponding model
|
15
|
+
def formize_fields(*args)
|
16
|
+
name, options = nil, {}
|
17
|
+
name = args[0] if args[0].is_a? Symbol
|
18
|
+
options = args[-1] if args[-1].is_a? Hash
|
19
|
+
self.send("_#{options[:controller]||self.controller_name}_#{__method__}_#{name||self.controller_name}_tag")
|
20
|
+
end
|
21
|
+
|
22
|
+
# Permits to use content_tag in helpers with easy add
|
23
|
+
def hard_content_tag(name, options={}, escape=true, &block)
|
24
|
+
content = ''
|
25
|
+
yield content
|
26
|
+
return content_tag(name, content, options, escape)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns a list of radio buttons for specified attribute (identified by +method+)
|
30
|
+
# on an object assigned to the template (identified by +object_name+). It works like +select+
|
31
|
+
def radio(object_name, method, choices, options = {}, html_options = {})
|
32
|
+
html = ""
|
33
|
+
html_options[:class] ||= :rad
|
34
|
+
for choice in choices
|
35
|
+
html << content_tag(:span, radio_button(object_name, method, choice[1]) + ' '.html_safe + label(object_name, method, choice[0], :value=>choice[1]), html_options)
|
36
|
+
end
|
37
|
+
return html
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns a text field which has the same behavior of +select+ but with a search
|
41
|
+
# action which permits to find easily in very long lists...
|
42
|
+
def unroll(object_name, method, choices, options = {}, input_options={}, html_options = {})
|
43
|
+
object = instance_variable_get("@#{object_name}")
|
44
|
+
label = options[:label]
|
45
|
+
if label.is_a?(String) or label.is_a?(Symbol)
|
46
|
+
label = Proc.new{|x| x.send(label)}
|
47
|
+
elsif !label.is_a?(Proc)
|
48
|
+
label = Proc.new{|x| x.inspect}
|
49
|
+
end
|
50
|
+
html = ""
|
51
|
+
html << hidden_field(object_name, method, input_options)
|
52
|
+
html << tag(:input, :type=>:text, "data-unroll"=>url_for(choices), "data-value-container"=>"#{object_name}_#{method}", :value=>label.call(object.send(method.to_s.gsub(/_id$/, ''))), :size=>html_options.delete(:size)||32)
|
53
|
+
return content_tag(:span, html, html_options)
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
ActionView::Base.send :include, Formize::FormHelper
|