formize 0.0.2
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/.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
|