interview 0.0.1 → 0.0.6
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.
- 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
|