interview 0.0.1 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/interview.gemspec +4 -0
- data/lib/generators/interview/install/install_generator.rb +47 -0
- data/lib/generators/interview/install/templates/32px.png +0 -0
- data/lib/generators/interview/install/templates/40px.png +0 -0
- data/lib/generators/interview/install/templates/application.js +22 -0
- data/lib/generators/interview/install/templates/bootstrap_interview.css.scss +98 -0
- data/lib/generators/interview/install/templates/ckeditor_config.js +19 -0
- data/lib/generators/interview/install/templates/colors.css.scss +2 -0
- data/lib/generators/interview/install/templates/defaults_de.yml +204 -0
- data/lib/generators/interview/install/templates/interview.js.coffee +72 -0
- data/lib/generators/interview/install/templates/interview.rb +4 -0
- data/lib/generators/interview/install/templates/jstree.css +916 -0
- data/lib/generators/interview/install/templates/jstree.js +5892 -0
- data/lib/generators/interview/install/templates/missing_large.png +0 -0
- data/lib/generators/interview/install/templates/missing_medium.png +0 -0
- data/lib/generators/interview/install/templates/missing_thumb.png +0 -0
- data/lib/generators/interview/install/templates/throbber.gif +0 -0
- data/lib/generators/interview/install/templates/views_defaults_de.yml +8 -0
- data/lib/generators/interview/view_control/templates/card.rb +6 -0
- data/lib/generators/interview/view_control/templates/form.rb +6 -0
- data/lib/generators/interview/view_control/templates/list.rb +6 -0
- data/lib/generators/interview/view_control/view_control_generator.rb +34 -0
- data/lib/interview/actionbar.rb +38 -0
- data/lib/interview/association_attribute.rb +33 -0
- data/lib/interview/association_list_attribute.rb +36 -0
- data/lib/interview/association_methods.rb +23 -0
- data/lib/interview/attribute.rb +141 -0
- data/lib/interview/boolean_attribute.rb +59 -0
- data/lib/interview/breadcrumbs.rb +21 -0
- data/lib/interview/container_attribute.rb +30 -0
- data/lib/interview/control.rb +73 -0
- data/lib/interview/control_def.rb +36 -0
- data/lib/interview/date_attribute.rb +20 -0
- data/lib/interview/datetime_attribute.rb +20 -0
- data/lib/interview/decimal_attribute.rb +10 -0
- data/lib/interview/dropdown.rb +26 -0
- data/lib/interview/form.rb +69 -0
- data/lib/interview/form_errors.rb +22 -0
- data/lib/interview/grid.rb +53 -0
- data/lib/interview/has_controls.rb +43 -0
- data/lib/interview/hidden_attribute.rb +15 -0
- data/lib/interview/html_control.rb +15 -0
- data/lib/interview/html_text_attribute.rb +19 -0
- data/lib/interview/image_attribute.rb +34 -0
- data/lib/interview/integer_attribute.rb +5 -0
- data/lib/interview/link.rb +129 -0
- data/lib/interview/list.rb +21 -0
- data/lib/interview/navigation.rb +55 -0
- data/lib/interview/navigation_item.rb +26 -0
- data/lib/interview/nested_form.rb +51 -0
- data/lib/interview/nested_form_add_link.rb +80 -0
- data/lib/interview/nested_form_remove_link.rb +28 -0
- data/lib/interview/object_context.rb +17 -0
- data/lib/interview/option_attribute.rb +74 -0
- data/lib/interview/polymorphic_add_link.rb +28 -0
- data/lib/interview/space.rb +18 -0
- data/lib/interview/string_attribute.rb +5 -0
- data/lib/interview/tab.rb +21 -0
- data/lib/interview/tab_box.rb +29 -0
- data/lib/interview/text.rb +15 -0
- data/lib/interview/text_attribute.rb +21 -0
- data/lib/interview/tooltip.rb +38 -0
- data/lib/interview/tree.rb +33 -0
- data/lib/interview/version.rb +1 -1
- data/lib/interview/view.rb +87 -0
- data/lib/interview.rb +196 -0
- metadata +107 -2
@@ -0,0 +1,69 @@
|
|
1
|
+
module Interview
|
2
|
+
class Form < Control
|
3
|
+
include HasControls
|
4
|
+
|
5
|
+
attr_accessor :skip_submit, :multi_create, :align, :redirect_to
|
6
|
+
attr_reader :form_builder
|
7
|
+
|
8
|
+
def attribute_modus
|
9
|
+
return :write
|
10
|
+
end
|
11
|
+
|
12
|
+
def render # todo: Aufräumen
|
13
|
+
object = find_attribute!(:object)
|
14
|
+
model = object.class.lookup_ancestors.last.model_name.singular # todo
|
15
|
+
# if object.class.superclass.name == 'ActiveRecord::Base'
|
16
|
+
# model = object.class.name.underscore
|
17
|
+
# else
|
18
|
+
# model = object.class.superclass.name.underscore
|
19
|
+
# end
|
20
|
+
if object.id
|
21
|
+
url = h.send("#{model}_path", object)
|
22
|
+
else
|
23
|
+
url = h.send("#{model.pluralize}_path")
|
24
|
+
end
|
25
|
+
opts = {as: model.to_sym, url: url, role: 'form', html: {}}
|
26
|
+
opts[:html][:class] = 'form-horizontal' if @align == 'horizontal'
|
27
|
+
opts[:html][:class] = 'form-inline' if @align == 'inline'
|
28
|
+
html = Builder::XmlMarkup.new
|
29
|
+
html << h.form_for(object, opts) do |form_builder|
|
30
|
+
@form_builder = form_builder
|
31
|
+
form_html = Builder::XmlMarkup.new
|
32
|
+
form_html << FormErrors.new(parent: self).render
|
33
|
+
if @redirect_to == :parent and Object.const_defined? 'Gretel'
|
34
|
+
form_html << h.hidden_field_tag('redirect_to', h.parent_breadcrumb.url)
|
35
|
+
end
|
36
|
+
|
37
|
+
@controls.each do |control|
|
38
|
+
form_html << control.render
|
39
|
+
form_html.text! ' ' if @align == 'inline'
|
40
|
+
end
|
41
|
+
|
42
|
+
if not @skip_submit
|
43
|
+
if @align == 'horizontal'
|
44
|
+
form_html.div class: 'form-group' do
|
45
|
+
form_html.div class: 'col-sm-offset-3 col-sm-9' do
|
46
|
+
form_html << form_builder.submit(class: 'btn btn-primary')
|
47
|
+
if @multi_create || find_attribute(:multi_create)
|
48
|
+
form_html.text! ' '
|
49
|
+
form_html << h.check_box_tag('new_record', '1', true)
|
50
|
+
form_html.text! ' und weiteren Datesatz'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
else
|
55
|
+
form_html << form_builder.submit(class: 'btn btn-primary')
|
56
|
+
if @multi_create || find_attribute(:multi_create)
|
57
|
+
form_html.text! ' '
|
58
|
+
form_html << h.check_box_tag('new_record', '1', true)
|
59
|
+
form_html.text! ' und weiteren Datesatz'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
form_html.target!.html_safe
|
64
|
+
end
|
65
|
+
return html.target!
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Interview
|
2
|
+
class FormErrors < Control
|
3
|
+
|
4
|
+
def render
|
5
|
+
object = find_attribute!(:object)
|
6
|
+
|
7
|
+
html = Builder::XmlMarkup.new
|
8
|
+
if object.errors.any?
|
9
|
+
html.div class: 'error_explanation!' do
|
10
|
+
html.h2 "#{pluralize(object.errors.count, 'error')} prohibited this article from being saved:"
|
11
|
+
html.ul do
|
12
|
+
object.errors.full_messages.each do |msg|
|
13
|
+
html.li msg
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
return html.target!
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Interview
|
2
|
+
class Grid < Control
|
3
|
+
include HasControls
|
4
|
+
|
5
|
+
attr_accessor :objects, :sortable
|
6
|
+
attr_reader :object
|
7
|
+
|
8
|
+
def render
|
9
|
+
objects = @objects || find_attribute!(:objects)
|
10
|
+
@object = find_attribute!(:object)
|
11
|
+
sortable_id = "sortable_#{@object.class.model_name.plural}" if @sortable
|
12
|
+
html = Builder::XmlMarkup.new
|
13
|
+
html.table class: 'table' do
|
14
|
+
html.thead do
|
15
|
+
html.tr do
|
16
|
+
html.th '' if @sortable
|
17
|
+
@controls.each do |control|
|
18
|
+
html.th do
|
19
|
+
html << control.caption
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
html_opts = {}
|
25
|
+
html_opts[:id] = sortable_id if @sortable
|
26
|
+
html.tbody html_opts do
|
27
|
+
objects.each do |object|
|
28
|
+
@object = object
|
29
|
+
html_opts = {}
|
30
|
+
html_opts[:id] = "#{sortable_id}_#{object.id}" if @sortable
|
31
|
+
html.tr html_opts do
|
32
|
+
if @sortable
|
33
|
+
html.td do
|
34
|
+
html.span '', class: 'handle glyphicon glyphicon-resize-vertical'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
@controls.each do |control|
|
38
|
+
html.td do
|
39
|
+
html << control.render
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
if objects.empty?
|
47
|
+
html.p "Keine #{@object.class.model_name.human(count: 2)} vorhanden.", class: 'text-center text-muted'
|
48
|
+
end
|
49
|
+
return html.target!
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module HasControls
|
2
|
+
|
3
|
+
attr_reader :controls
|
4
|
+
|
5
|
+
def initialize(params={})
|
6
|
+
super
|
7
|
+
@controls = self.class.definition.build_controls_for self
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_control(control, &block)
|
11
|
+
control.parent = self
|
12
|
+
yield(control) if block_given?
|
13
|
+
@controls << control
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_controls(controls)
|
17
|
+
controls.each { |control| control.parent = self }
|
18
|
+
@controls += controls
|
19
|
+
end
|
20
|
+
|
21
|
+
def siblings
|
22
|
+
return @controls.map do |control|
|
23
|
+
if control.respond_to? :siblings
|
24
|
+
[control] + control.siblings
|
25
|
+
else
|
26
|
+
control
|
27
|
+
end
|
28
|
+
end.flatten
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.included(mod)
|
32
|
+
mod.extend ClassMethods
|
33
|
+
end
|
34
|
+
|
35
|
+
module ClassMethods
|
36
|
+
|
37
|
+
def control(klass, params={}, &block)
|
38
|
+
@definition.control(klass, params, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Interview
|
2
|
+
class HtmlTextAttribute < Attribute
|
3
|
+
|
4
|
+
attr_accessor :plain_text, :cut
|
5
|
+
|
6
|
+
def render_read
|
7
|
+
return '' if value.nil?
|
8
|
+
val = value
|
9
|
+
val.gsub! /<.+?>/, ' ' if @plain_text
|
10
|
+
val = val[0..29] + '...' if @cut and val.size > 30
|
11
|
+
return val.html_safe
|
12
|
+
end
|
13
|
+
|
14
|
+
def render_write
|
15
|
+
form_builder.cktext_area @method, class: 'form-control'
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Interview
|
2
|
+
class ImageAttribute < Attribute
|
3
|
+
|
4
|
+
attr_accessor :image_style, :hide_if_not_exists
|
5
|
+
|
6
|
+
def render_read
|
7
|
+
return '' if value.nil?
|
8
|
+
image_style = @image_style || :thumb
|
9
|
+
if not value.exists? and @hide_if_not_exists
|
10
|
+
return ''
|
11
|
+
else
|
12
|
+
return h.image_tag value.url(image_style)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def render_write
|
17
|
+
object = find_attribute(:object)
|
18
|
+
image = object.send @method
|
19
|
+
|
20
|
+
html = Builder::XmlMarkup.new
|
21
|
+
if image.exists? and @image_style
|
22
|
+
html.br
|
23
|
+
html << h.image_tag(image.url(@image_style))
|
24
|
+
end
|
25
|
+
html << form_builder.file_field(@method)
|
26
|
+
if image.exists?
|
27
|
+
html << form_builder.check_box("destroy_#{@method}")
|
28
|
+
html.text! ' Datei löschen'
|
29
|
+
end
|
30
|
+
return html.target!
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Interview
|
2
|
+
class Link < Control
|
3
|
+
include HasControls
|
4
|
+
|
5
|
+
attr_accessor :image, :caption, :hint, :style, :active,
|
6
|
+
:url, :controller, :object, :action, :http_method, :redirect_to, :nested_resource, :trail
|
7
|
+
attr_reader :url_params, :html_class
|
8
|
+
|
9
|
+
def initialize(params={})
|
10
|
+
@html_class = []
|
11
|
+
@url_params = {}
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def scope=(scope)
|
16
|
+
@url_params[:scope] = scope
|
17
|
+
end
|
18
|
+
|
19
|
+
def filter=(filter)
|
20
|
+
@url_params[:filter] = filter
|
21
|
+
end
|
22
|
+
|
23
|
+
def render
|
24
|
+
if @redirect_to
|
25
|
+
if @redirect_to == :current
|
26
|
+
@url_params[:redirect_to] = h.request.original_url
|
27
|
+
elsif @redirect_to == :parent and Object.const_defined? 'Gretel'
|
28
|
+
@url_params[:redirect_to] = h.parent_breadcrumb.url
|
29
|
+
else
|
30
|
+
@url_params[:redirect_to] = @redirect_to
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
if @trail and Object.const_defined? 'Gretel'
|
35
|
+
@url_params[:trail] = h.breadcrumb_trail
|
36
|
+
end
|
37
|
+
|
38
|
+
if @url
|
39
|
+
url = @url
|
40
|
+
elsif @controller
|
41
|
+
url_options = @url_option.dup
|
42
|
+
opts = {controller: @controller, action: @action}.merge @url_params
|
43
|
+
url = h.url_for opts
|
44
|
+
else
|
45
|
+
url_params = @url_params.dup
|
46
|
+
url_params[:action] = @action if @action and not %w(index show destroy).include? @action
|
47
|
+
object = @object || find_attribute!(:object)
|
48
|
+
url_params.each do |key, value|
|
49
|
+
url_params[key] = value.call(self) if value.is_a? Proc
|
50
|
+
end
|
51
|
+
if @nested_resource and assoc_object
|
52
|
+
url = h.polymorphic_path [assoc_object, object], url_params
|
53
|
+
else
|
54
|
+
url = h.polymorphic_path object, url_params
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
html_options = {}
|
59
|
+
html_class = @html_class.dup
|
60
|
+
if @action == 'destroy'
|
61
|
+
html_options[:method] = :delete
|
62
|
+
html_options[:data] = { confirm: 'Are you sure?' }
|
63
|
+
end
|
64
|
+
if @http_method
|
65
|
+
html_options[:method] = @http_method.to_sym
|
66
|
+
end
|
67
|
+
|
68
|
+
if @hint
|
69
|
+
html_class << 'action_hint'
|
70
|
+
html_options[:'data-toggle'] = 'tooltip'
|
71
|
+
html_options[:title] = @hint
|
72
|
+
end
|
73
|
+
|
74
|
+
if @style and respond_to? "render_#{@style}_style", true
|
75
|
+
return send "render_#{@style}_style", url, html_class, html_options
|
76
|
+
else
|
77
|
+
return render_link url, html_class, html_options
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def object # todo: Überdenken
|
82
|
+
find_attribute(:object)
|
83
|
+
end
|
84
|
+
|
85
|
+
def assoc_object # todo: Überdenken
|
86
|
+
find_attribute(:assoc_object)
|
87
|
+
end
|
88
|
+
|
89
|
+
protected
|
90
|
+
|
91
|
+
def render_button_style(url, html_class=[], html_options={})
|
92
|
+
html_class << 'btn btn-default'
|
93
|
+
return render_link(url, html_class, html_options)
|
94
|
+
end
|
95
|
+
|
96
|
+
def render_primary_button_style(url, html_class=[], html_options={})
|
97
|
+
html_class << 'btn btn-primary'
|
98
|
+
return render_link(url, html_class, html_options)
|
99
|
+
end
|
100
|
+
|
101
|
+
def render_list_style(url, html_class=[], html_options={})
|
102
|
+
html = Builder::XmlMarkup.new
|
103
|
+
li_class = @active ? 'active' : ''
|
104
|
+
html.li class: li_class do
|
105
|
+
html << render_link(url, html_class, html_options)
|
106
|
+
end
|
107
|
+
return html.target!
|
108
|
+
end
|
109
|
+
|
110
|
+
def render_link(url, html_class=[], html_options={})
|
111
|
+
class_string = html_class.join(' ')
|
112
|
+
html_options[:class] = class_string unless html_class.empty?
|
113
|
+
return h.link_to(url, html_options) do
|
114
|
+
html = Builder::XmlMarkup.new
|
115
|
+
if @controls.empty?
|
116
|
+
html.span '', class: "glyphicon glyphicon-#{@image}" if @image
|
117
|
+
html.text! ' ' if @image and @caption
|
118
|
+
html << @caption if @caption
|
119
|
+
else
|
120
|
+
@controls.each do |control|
|
121
|
+
html << control.render
|
122
|
+
end
|
123
|
+
end
|
124
|
+
html.target!.html_safe
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Interview
|
2
|
+
class List < Control
|
3
|
+
include HasControls
|
4
|
+
|
5
|
+
attr_accessor :objects
|
6
|
+
attr_reader :object
|
7
|
+
|
8
|
+
def render
|
9
|
+
objects = @objects || find_attribute!(:objects)
|
10
|
+
html = Builder::XmlMarkup.new
|
11
|
+
objects.each do |object|
|
12
|
+
@object = object
|
13
|
+
@controls.each do |control|
|
14
|
+
html << control.render
|
15
|
+
end
|
16
|
+
end
|
17
|
+
return html.target!
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Interview
|
2
|
+
class Navigation < Control
|
3
|
+
include HasControls
|
4
|
+
|
5
|
+
attr_accessor :controller_name, :action_name, :current_scope, :current_filter
|
6
|
+
|
7
|
+
def render
|
8
|
+
auto_set_active
|
9
|
+
|
10
|
+
html = Builder::XmlMarkup.new
|
11
|
+
html.ul class: 'nav nav-pills nav-stacked' do
|
12
|
+
@controls.each do |control|
|
13
|
+
html << control.render
|
14
|
+
end
|
15
|
+
end
|
16
|
+
return html.target!
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def auto_set_active
|
22
|
+
siblings.each { |sib| sib.active = false }
|
23
|
+
if (nav_item = siblings.find { |sib| controller_match(sib) && action_match(sib) && scope_match(sib) && filter_match(sib) })
|
24
|
+
nav_item.active = true
|
25
|
+
elsif (nav_item = siblings.find { |sib| controller_match(sib) && scope_match(sib) && filter_match(sib) })
|
26
|
+
nav_item.active = true
|
27
|
+
elsif (nav_item = siblings.find { |sib| controller_match(sib) && (scope_match(sib) || filter_match(sib)) })
|
28
|
+
nav_item.active = true
|
29
|
+
elsif (nav_item = siblings.find { |sib| controller_match(sib) })
|
30
|
+
nav_item.active = true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def controller_match(nav_item)
|
35
|
+
return @controller_name == nav_item.controller
|
36
|
+
end
|
37
|
+
|
38
|
+
def action_match(nav_item)
|
39
|
+
return @action_name == nav_item.action
|
40
|
+
end
|
41
|
+
|
42
|
+
def scope_match(nav_item)
|
43
|
+
return true if @current_scope.nil?
|
44
|
+
return false if nav_item.url_options[:scope].nil?
|
45
|
+
return true if nav_item.url_options[:scope] == @current_scope
|
46
|
+
end
|
47
|
+
|
48
|
+
def filter_match(nav_item)
|
49
|
+
return true if @current_filter.nil?
|
50
|
+
return false if nav_item.url_options[:filter].nil?
|
51
|
+
return @current_filter.all? { |key, value| nav_item.url_options[:filter][key] == value }
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Interview
|
2
|
+
class NavigationItem < Link
|
3
|
+
|
4
|
+
include HasControls
|
5
|
+
|
6
|
+
attr_accessor :active
|
7
|
+
|
8
|
+
def render
|
9
|
+
css_class = "level#{ancestors.count-1}"
|
10
|
+
css_class += " active" if @active
|
11
|
+
show_siblings = @active || siblings.any? { |sib| sib.active }
|
12
|
+
|
13
|
+
html = Builder::XmlMarkup.new
|
14
|
+
html.li class: css_class do
|
15
|
+
html << super
|
16
|
+
end
|
17
|
+
if show_siblings
|
18
|
+
@controls.each do |c|
|
19
|
+
html << c.render
|
20
|
+
end
|
21
|
+
end
|
22
|
+
return html.target!
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Interview
|
2
|
+
class NestedForm < Control
|
3
|
+
include HasControls
|
4
|
+
|
5
|
+
attr_accessor :polymorphic, :align # todo: align einbauen
|
6
|
+
attr_reader :form_builder, :object
|
7
|
+
|
8
|
+
def render
|
9
|
+
assoc_form_builder = find_attribute! :form_builder
|
10
|
+
assoc_method = find_attribute! :assoc_method
|
11
|
+
objects = find_attribute! :objects
|
12
|
+
|
13
|
+
html = Builder::XmlMarkup.new
|
14
|
+
html.div class: 'nested_forms' do
|
15
|
+
html << assoc_form_builder.fields_for(assoc_method.to_sym, objects) do |form_builder|
|
16
|
+
if @polymorphic
|
17
|
+
render_polymorphic_nested_form(form_builder)
|
18
|
+
else
|
19
|
+
render_nested_form(form_builder)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
return html.target!
|
24
|
+
end
|
25
|
+
|
26
|
+
def render_nested_form(form_builder)
|
27
|
+
@form_builder = form_builder
|
28
|
+
@object = form_builder.object
|
29
|
+
html = Builder::XmlMarkup.new
|
30
|
+
html.div class: 'nested_form collapse in' do
|
31
|
+
@controls.each do |control|
|
32
|
+
html << control.render
|
33
|
+
end
|
34
|
+
html << form_builder.hidden_field(:id)
|
35
|
+
html << form_builder.hidden_field(:_destroy, class: 'nested_form_destroy')
|
36
|
+
end
|
37
|
+
html.target!.html_safe
|
38
|
+
end
|
39
|
+
|
40
|
+
def render_polymorphic_nested_form(form_builder)
|
41
|
+
object = form_builder.object
|
42
|
+
if klass = "#{object.class.name}NestedForm".safe_constantize
|
43
|
+
nested_form = klass.new(parent: self)
|
44
|
+
return nested_form.render_nested_form(form_builder)
|
45
|
+
else
|
46
|
+
return self.render_nested_form(form_builder)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Interview
|
2
|
+
class NestedFormAddLink < Control
|
3
|
+
|
4
|
+
attr_accessor :style, :polymorphic_classes
|
5
|
+
|
6
|
+
def render
|
7
|
+
if @style and @style.to_sym == :horizontal_form
|
8
|
+
html = Builder::XmlMarkup.new # todo: in eigenes Objekt auslagern?
|
9
|
+
html.div class: 'row' do
|
10
|
+
html.div class: 'col-xs-9 col-xs-offset-3' do
|
11
|
+
html << render_link
|
12
|
+
end
|
13
|
+
end
|
14
|
+
return html.target!
|
15
|
+
else
|
16
|
+
return render_link
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def render_link
|
21
|
+
form_builder = find_attribute! :form_builder
|
22
|
+
assoc_method = find_attribute!(:assoc_method).to_sym
|
23
|
+
if @polymorphic_classes
|
24
|
+
return render_polymorphic_link(form_builder, assoc_method)
|
25
|
+
else
|
26
|
+
html = render_form(form_builder, assoc_method)
|
27
|
+
text = h.t('views.nested_form_add', association: find_attribute!(:singular_title))
|
28
|
+
return h.content_tag :a, text, { href: '#', class: 'nested_form_add_link', data: { content: CGI::escapeHTML(html) } }, false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def render_polymorphic_link(form_builder, assoc_method)
|
35
|
+
poly_classes = @polymorphic_classes.map do |poly_class|
|
36
|
+
poly_class.is_a?(String) ? poly_class.camelcase.constantize : poly_class
|
37
|
+
end
|
38
|
+
|
39
|
+
select_options = [[ h.t('helpers.select.prompt'), nil ]]
|
40
|
+
select_options += poly_classes.map do |poly_class|
|
41
|
+
[ poly_class.model_name.human, poly_class.model_name.singular ]
|
42
|
+
end
|
43
|
+
|
44
|
+
data_content = {}
|
45
|
+
poly_classes.each do |poly_class|
|
46
|
+
new_object = form_builder.object.association(assoc_method.to_sym).build(type: poly_class.name)
|
47
|
+
html = render_form(form_builder, assoc_method, new_object)
|
48
|
+
data_content[poly_class.model_name.singular] = html
|
49
|
+
end
|
50
|
+
|
51
|
+
html = Builder::XmlMarkup.new
|
52
|
+
html.div do
|
53
|
+
html << h.select_tag("add_link_class", h.options_for_select(select_options), class: 'form-control', style: 'display: inline; width: auto;')
|
54
|
+
html.text! ' '
|
55
|
+
html << h.content_tag(:a, h.t('views.add'), {href: '#', class: 'nested_form_polymorphic_add_link btn btn-default',
|
56
|
+
data: { content: CGI::escapeHTML(data_content.to_json) } }, false)
|
57
|
+
end
|
58
|
+
return html.target!
|
59
|
+
end
|
60
|
+
|
61
|
+
def render_form(form_builder, assoc_method, new_object=nil)
|
62
|
+
new_object ||= form_builder.object.association(assoc_method.to_sym).build
|
63
|
+
new_object.assign_attributes(new_object.class.defaults)
|
64
|
+
if new_object_assoc = new_object.class.reflect_on_all_associations.find { |a|
|
65
|
+
a.foreign_key == form_builder.object.association(assoc_method.to_sym).reflection.foreign_key }
|
66
|
+
new_object.association(new_object_assoc.name).target ||= form_builder.object
|
67
|
+
end
|
68
|
+
|
69
|
+
nested_form = find_attribute! :nested_form
|
70
|
+
return form_builder.fields_for(assoc_method, new_object, :child_index => "new_association") do |builder|
|
71
|
+
if @polymorphic_classes
|
72
|
+
nested_form.render_polymorphic_nested_form(builder)
|
73
|
+
else
|
74
|
+
nested_form.render_nested_form(builder)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Interview
|
2
|
+
class NestedFormRemoveLink < Control
|
3
|
+
|
4
|
+
attr_accessor :style
|
5
|
+
|
6
|
+
def render
|
7
|
+
if @style and @style.to_sym == :horizontal_form
|
8
|
+
html = Builder::XmlMarkup.new
|
9
|
+
html.div class: 'row' do
|
10
|
+
html.div class: 'col-xs-9 col-xs-offset-3' do
|
11
|
+
html << render_link
|
12
|
+
end
|
13
|
+
end
|
14
|
+
return html.target!
|
15
|
+
else
|
16
|
+
return render_link
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def render_link
|
21
|
+
assoc_object = find_attribute! :assoc_object
|
22
|
+
assoc_method = find_attribute!(:assoc_method).to_s
|
23
|
+
text = h.t('views.nested_form_remove', association: assoc_object.class.human_attribute_name(assoc_method.singularize)) # todo: in view auslagern
|
24
|
+
return h.content_tag :a, text, href: '#', class: 'nested_form_remove_link'
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|