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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/interview.gemspec +4 -0
  3. data/lib/generators/interview/install/install_generator.rb +47 -0
  4. data/lib/generators/interview/install/templates/32px.png +0 -0
  5. data/lib/generators/interview/install/templates/40px.png +0 -0
  6. data/lib/generators/interview/install/templates/application.js +22 -0
  7. data/lib/generators/interview/install/templates/bootstrap_interview.css.scss +98 -0
  8. data/lib/generators/interview/install/templates/ckeditor_config.js +19 -0
  9. data/lib/generators/interview/install/templates/colors.css.scss +2 -0
  10. data/lib/generators/interview/install/templates/defaults_de.yml +204 -0
  11. data/lib/generators/interview/install/templates/interview.js.coffee +72 -0
  12. data/lib/generators/interview/install/templates/interview.rb +4 -0
  13. data/lib/generators/interview/install/templates/jstree.css +916 -0
  14. data/lib/generators/interview/install/templates/jstree.js +5892 -0
  15. data/lib/generators/interview/install/templates/missing_large.png +0 -0
  16. data/lib/generators/interview/install/templates/missing_medium.png +0 -0
  17. data/lib/generators/interview/install/templates/missing_thumb.png +0 -0
  18. data/lib/generators/interview/install/templates/throbber.gif +0 -0
  19. data/lib/generators/interview/install/templates/views_defaults_de.yml +8 -0
  20. data/lib/generators/interview/view_control/templates/card.rb +6 -0
  21. data/lib/generators/interview/view_control/templates/form.rb +6 -0
  22. data/lib/generators/interview/view_control/templates/list.rb +6 -0
  23. data/lib/generators/interview/view_control/view_control_generator.rb +34 -0
  24. data/lib/interview/actionbar.rb +38 -0
  25. data/lib/interview/association_attribute.rb +33 -0
  26. data/lib/interview/association_list_attribute.rb +36 -0
  27. data/lib/interview/association_methods.rb +23 -0
  28. data/lib/interview/attribute.rb +141 -0
  29. data/lib/interview/boolean_attribute.rb +59 -0
  30. data/lib/interview/breadcrumbs.rb +21 -0
  31. data/lib/interview/container_attribute.rb +30 -0
  32. data/lib/interview/control.rb +73 -0
  33. data/lib/interview/control_def.rb +36 -0
  34. data/lib/interview/date_attribute.rb +20 -0
  35. data/lib/interview/datetime_attribute.rb +20 -0
  36. data/lib/interview/decimal_attribute.rb +10 -0
  37. data/lib/interview/dropdown.rb +26 -0
  38. data/lib/interview/form.rb +69 -0
  39. data/lib/interview/form_errors.rb +22 -0
  40. data/lib/interview/grid.rb +53 -0
  41. data/lib/interview/has_controls.rb +43 -0
  42. data/lib/interview/hidden_attribute.rb +15 -0
  43. data/lib/interview/html_control.rb +15 -0
  44. data/lib/interview/html_text_attribute.rb +19 -0
  45. data/lib/interview/image_attribute.rb +34 -0
  46. data/lib/interview/integer_attribute.rb +5 -0
  47. data/lib/interview/link.rb +129 -0
  48. data/lib/interview/list.rb +21 -0
  49. data/lib/interview/navigation.rb +55 -0
  50. data/lib/interview/navigation_item.rb +26 -0
  51. data/lib/interview/nested_form.rb +51 -0
  52. data/lib/interview/nested_form_add_link.rb +80 -0
  53. data/lib/interview/nested_form_remove_link.rb +28 -0
  54. data/lib/interview/object_context.rb +17 -0
  55. data/lib/interview/option_attribute.rb +74 -0
  56. data/lib/interview/polymorphic_add_link.rb +28 -0
  57. data/lib/interview/space.rb +18 -0
  58. data/lib/interview/string_attribute.rb +5 -0
  59. data/lib/interview/tab.rb +21 -0
  60. data/lib/interview/tab_box.rb +29 -0
  61. data/lib/interview/text.rb +15 -0
  62. data/lib/interview/text_attribute.rb +21 -0
  63. data/lib/interview/tooltip.rb +38 -0
  64. data/lib/interview/tree.rb +33 -0
  65. data/lib/interview/version.rb +1 -1
  66. data/lib/interview/view.rb +87 -0
  67. data/lib/interview.rb +196 -0
  68. metadata +107 -2
@@ -0,0 +1,17 @@
1
+ module Interview
2
+ module ObjectContext # todo: Überdenken
3
+
4
+ attr_reader :object
5
+
6
+ def object=(object)
7
+ if object.is_a? String
8
+ @object = Object::const_get(object.camelcase).new
9
+ elsif object.is_a? Class
10
+ @object = object.new
11
+ else
12
+ @object = object
13
+ end
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,74 @@
1
+ module Interview
2
+ class OptionAttribute < Attribute
3
+
4
+ attr_accessor :html_class, :use_radios
5
+
6
+ def render_read
7
+ return '' if value.nil? or value == ''
8
+ object = find_attribute! :object
9
+ if object.class.superclass.name == 'ActiveRecord::Base'
10
+ model = object.class.name.underscore
11
+ else
12
+ model = object.class.superclass.name.underscore
13
+ end
14
+ return h.t "activerecord.options.#{model}.#{@method}.#{value}"
15
+ end
16
+
17
+ def render_write
18
+ if @use_radios
19
+ render_radios
20
+ else
21
+ render_select
22
+ end
23
+ end
24
+
25
+ def render_select
26
+ object = find_attribute!(:object)
27
+ options = [[h.t('helpers.select.prompt'), nil]]
28
+ options += get_options(object)
29
+
30
+ html_class = 'form-control'
31
+ html_class += " #{@html_class}" if @html_class
32
+ form_builder.select @method, options, {}, {class: html_class}
33
+ end
34
+
35
+ def render_radios
36
+ object = find_attribute!(:object)
37
+ html = Builder::XmlMarkup.new
38
+ get_options(object).each do |option|
39
+ html.div class: 'radio' do
40
+ html.label do
41
+ html << form_builder.radio_button(@method, option[1])
42
+ html.text! ' '
43
+ html.text! option[0]
44
+ end
45
+ end
46
+ end
47
+ return html.target!
48
+ end
49
+
50
+ def get_options(object)
51
+ # todo: nach ActiveModel Translation auslagern
52
+ options = []
53
+ general_defaults = object.class.lookup_ancestors.map do |klass|
54
+ "#{object.class.i18n_scope}.options.#{klass.model_name.i18n_key}.#{method}"
55
+ end
56
+ if object.class.const_defined? "#{@method.upcase}_OPTIONS"
57
+ opts = object.class.const_get("#{@method.upcase}_OPTIONS")
58
+ elsif object.class.respond_to? :descendants and
59
+ (klass = object.class.descendants.find { |c| c.const_defined? "#{@method.upcase}_OPTIONS" })
60
+ opts = klass.const_get("#{@method.upcase}_OPTIONS")
61
+ end
62
+ if opts
63
+ opts.map do |option|
64
+ defaults = general_defaults.map do |default|
65
+ :"#{default}.#{option}"
66
+ end
67
+ options << [h.t(defaults.shift, default: defaults), option]
68
+ end
69
+ end
70
+ return options
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,28 @@
1
+ module Interview
2
+ class PolymorphicAddLink < Control
3
+
4
+ attr_accessor :polymorphic_classes
5
+
6
+ def render
7
+ poly_classes = @polymorphic_classes.map do |poly_class|
8
+ poly_class.is_a?(String) ? poly_class.camelcase.constantize : poly_class
9
+ end
10
+
11
+ select_options = [[ h.t('helpers.select.prompt'), nil ]]
12
+ select_options += poly_classes.map do |poly_class|
13
+ [ poly_class.model_name.human, poly_class.name ]
14
+ end
15
+
16
+ html = Builder::XmlMarkup.new
17
+ html.div do
18
+ html << h.select_tag("add_link_class", h.options_for_select(select_options), class: 'form-control', style: 'display: inline; width: auto;')
19
+ html.text! ' '
20
+ link = Link.new parent: self, caption: h.t('views.add'), action: 'new', style: 'button'
21
+ link.html_class << 'polymorphic_add_link'
22
+ html << link.render
23
+ end
24
+ return html.target!
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ module Interview
2
+ class Space < Control
3
+ attr_accessor :style
4
+
5
+ def render
6
+ html = Builder::XmlMarkup.new
7
+ if @style and @style.to_sym == :line
8
+ html.hr
9
+ elsif @style and @style.to_sym == :break
10
+ html.br
11
+ else
12
+ html.text! ' '
13
+ end
14
+ return html.target!
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ module Interview
2
+ class StringAttribute < Attribute
3
+
4
+ end
5
+ end
@@ -0,0 +1,21 @@
1
+ module Interview
2
+ class Tab < Control
3
+
4
+ include HasControls
5
+
6
+ attr_accessor :caption, :active
7
+
8
+ def render
9
+ html = Builder::XmlMarkup.new
10
+ html_class = 'tab-pane'
11
+ html_class += ' active' if @active
12
+ html.div class: html_class, id: @caption do
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,29 @@
1
+ module Interview
2
+ class TabBox < Control
3
+
4
+ include HasControls
5
+
6
+ def render
7
+ if @controls.none? { |c| c.active }
8
+ @controls.first.active = true
9
+ end
10
+
11
+ html = Builder::XmlMarkup.new
12
+ html.ul class: 'nav nav-tabs tab_box' do
13
+ @controls.each do |control|
14
+ html_class = control.active ? 'active' : ''
15
+ html.li class: html_class do
16
+ html.a control.caption, href: "##{control.caption}", :'data-toogle' => 'tab'
17
+ end
18
+ end
19
+ end
20
+ html.div class: 'tab-content' do
21
+ @controls.each do |control|
22
+ html << control.render
23
+ end
24
+ end
25
+ return html.target!
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ module Interview
2
+ class Text < Control
3
+
4
+ attr_accessor :text, :bold
5
+
6
+ def render
7
+ if @bold
8
+ return "<b>#{@text}</b>"
9
+ else
10
+ return @text
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ module Interview
2
+ class TextAttribute < Attribute
3
+
4
+ attr_accessor :line_break, :cut, :rows
5
+
6
+ def render_read
7
+ return '' if value.nil?
8
+ line_break = @line_break || true
9
+
10
+ value = self.value.gsub(/\n/, '<br/>') if line_break
11
+ value = value[0..29] + '...' if @cut and value.size > 30
12
+ return value.html_safe
13
+ end
14
+
15
+ def render_write
16
+ rows = @rows || 8
17
+ form_builder.text_area @method, class: 'form-control', rows: rows
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,38 @@
1
+ module Interview
2
+ class Tooltip < Control
3
+
4
+ attr_accessor :tooltip, :style
5
+
6
+ def tooltip
7
+ @tooltip || find_attribute(:tooltip)
8
+ end
9
+
10
+ def render
11
+ if @style and respond_to?("render_#{@style}_style", true)
12
+ return send "render_#{@style}_style"
13
+ else
14
+ return render_default_style
15
+ end
16
+ end
17
+
18
+ protected
19
+
20
+ def render_default_style
21
+ return "<p class='text-muted'>#{tooltip}</p>".html_safe
22
+ end
23
+
24
+ def render_box_style
25
+ return "<div class='alert alert-info'>#{tooltip}</div>".html_safe
26
+ end
27
+
28
+ def render_popover_style
29
+ html = Builder::XmlMarkup.new
30
+ html.text! ' '
31
+ html.a href: '#', class: 'tip', :'data-toggle' => 'popover', :'data-content' => tooltip do
32
+ html.span '', class: 'glyphicon glyphicon-question-sign'
33
+ end
34
+ return html.target!
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,33 @@
1
+ module Interview
2
+ class Tree < Control
3
+
4
+ attr_accessor :sortable, :icon
5
+
6
+ def render
7
+ defaults = {}
8
+ defaults[:icon] = @icon if @icon and @icon != :polymorphic
9
+ objects = find_attribute! :objects
10
+ data_content = objects.map do |object|
11
+ object_data = {
12
+ id: object.id.to_s,
13
+ parent: object.parent_id ? object.parent_id.to_s : '#',
14
+ text: object.human_id,
15
+ state: {
16
+ opened: true
17
+ },
18
+ a_attr: {
19
+ href: h.polymorphic_path(object)
20
+ }
21
+ }.merge defaults
22
+ object_data[:icon] = object.icon if @icon == :polymorphic
23
+ object_data
24
+ end
25
+ data = { content: CGI::escapeHTML(data_content.to_json) }
26
+ data[:sortable] = true if @sortable
27
+
28
+ return h.content_tag(:div, '', { class: 'jstree', data: data }, false)
29
+
30
+ end
31
+
32
+ end
33
+ end
@@ -1,3 +1,3 @@
1
1
  module Interview
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -0,0 +1,87 @@
1
+ module Interview
2
+ class View < Control
3
+ include HasControls
4
+
5
+ attr_accessor :object, :objects, :filter, :scope, :title, :auto_title_for
6
+ attr_reader :assoc_object, :assoc_method
7
+
8
+ def nested_form
9
+ return siblings.find { |sib| sib.is_a? Interview::NestedForm }
10
+ end
11
+
12
+ def title
13
+ if @title # todo: Benötige ich den Titel?
14
+ return @title
15
+ elsif @auto_title_for
16
+ return case @auto_title_for.to_sym
17
+ when :collection
18
+ if @assoc_object
19
+ @assoc_object.class.human_attribute_name(@assoc_method)
20
+ else
21
+ @object.class.model_name.human(count: 2)
22
+ end
23
+ when :object
24
+ @object.human_id
25
+ when :form
26
+ text = @object.created_at ? 'views.edit' : 'views.new'
27
+ h.t(text, model: @object.class.model_name.human )
28
+ end
29
+ end
30
+ end
31
+
32
+ def singular_title # todo: Überdenken
33
+ if @auto_title_for
34
+ if @assoc_object
35
+ return @assoc_object.class.human_attribute_name(@assoc_method.to_s.singularize)
36
+ else
37
+ return @object.class.model_name.human
38
+ end
39
+ end
40
+ end
41
+
42
+ def tooltip
43
+ return h.t("activerecord.tooltips.#{@object.class.name.underscore}.model", default: '')
44
+ end
45
+
46
+ def render
47
+ if @object.is_a? String
48
+ # if Object::const_defined?(@object.camelcase) # todo: Überdenken!
49
+ # @object = @object.camelcase.constantize.new
50
+ # else
51
+ @assoc_object = find_attribute! :object
52
+ @object = @object.split('.').inject(@assoc_object, :send)
53
+ # end
54
+ elsif @object.is_a? Class
55
+ @object = @object.new
56
+ end
57
+
58
+ if @objects.is_a? String
59
+ @assoc_object = find_attribute! :object
60
+ @assoc_method = @objects
61
+
62
+ @objects = @assoc_object.send @assoc_method
63
+ @objects = @objects.send @scope if @scope
64
+ @object = @objects.klass.new
65
+ @scope = nil
66
+ @filter = { "#{assoc_object.class.name.underscore}_id" => assoc_object.id }
67
+ end
68
+
69
+ if Object.const_defined? 'Gretel' and parent.nil?
70
+ model_name = @object.class.lookup_ancestors.last.model_name
71
+ case h.action_name
72
+ when 'index'
73
+ h.breadcrumb model_name.plural.to_sym
74
+ when 'show'
75
+ h.breadcrumb model_name.singular.to_sym, @object
76
+ when 'new'
77
+ h.breadcrumb "new_#{model_name.singular}".to_sym, @object
78
+ when 'edit'
79
+ h.breadcrumb "edit_#{model_name.singular}".to_sym, @object
80
+ end
81
+ end
82
+
83
+ return @controls.map { |c| c.render }.join
84
+ end
85
+
86
+ end
87
+ end
data/lib/interview.rb CHANGED
@@ -1,5 +1,201 @@
1
+ require "active_support/inflector"
2
+ require "draper"
3
+ require "builder"
4
+
1
5
  require "interview/version"
2
6
 
7
+ require "interview/control_def"
8
+ require "interview/control"
9
+ require "interview/has_controls"
10
+ require "interview/object_context"
11
+
12
+ require "interview/text"
13
+ require "interview/link"
14
+ require "interview/dropdown"
15
+ require "interview/space"
16
+ require "interview/tooltip"
17
+ require "interview/html_control"
18
+ require "interview/actionbar"
19
+ require "interview/grid"
20
+ require "interview/list"
21
+ require "interview/tree"
22
+ require "interview/navigation"
23
+ require "interview/navigation_item"
24
+ require "interview/tab_box"
25
+ require "interview/tab"
26
+ require "interview/breadcrumbs"
27
+
28
+ require "interview/attribute"
29
+ require "interview/string_attribute"
30
+ require "interview/text_attribute"
31
+ require "interview/integer_attribute"
32
+ require "interview/decimal_attribute"
33
+ require "interview/boolean_attribute"
34
+ require "interview/date_attribute"
35
+ require "interview/datetime_attribute"
36
+ require "interview/option_attribute"
37
+ require "interview/html_text_attribute"
38
+ require "interview/image_attribute"
39
+ require "interview/association_attribute"
40
+ require "interview/association_list_attribute"
41
+ require "interview/hidden_attribute"
42
+ require "interview/container_attribute"
43
+
44
+ require "interview/view"
45
+ require "interview/form"
46
+ require "interview/nested_form"
47
+ require "interview/nested_form_remove_link"
48
+ require "interview/nested_form_add_link"
49
+ require "interview/form_errors"
50
+ require "interview/polymorphic_add_link"
51
+
52
+ require "interview/association_methods"
53
+
3
54
  module Interview
4
55
  # Your code goes here...
5
56
  end
57
+
58
+ module ActiveRecord
59
+ class Base
60
+
61
+ before_save :set_human_id
62
+
63
+ def set_human_id
64
+ if respond_to? :human_id
65
+ if respond_to? :name
66
+ self.human_id = self.name.to_s
67
+ elsif respond_to? :title
68
+ self.human_id = self.title.to_s
69
+ elsif respond_to? :description
70
+ self.human_id = self.description.to_s
71
+ else
72
+ self.human_id = self.id.to_s
73
+ end
74
+ end
75
+ end
76
+
77
+ def self.human_ids
78
+ objects = select("\"#{self.table_name}\".\"human_id\"").order(:human_id)
79
+ return objects.map { |object| object.human_id }
80
+ end
81
+
82
+ # todo: auslagern in eigenes gem
83
+ def self.filter(filter)
84
+ relation = self
85
+ self.joins_for_filter(filter).each do |join_table|
86
+ relation = relation.joins(join_table)
87
+ end
88
+ return relation.where(*self.filter_to_sql(filter))
89
+ end
90
+
91
+ def self.joins_for_filter(filter)
92
+ join_tables = []
93
+ filter.each do |attr, filter|
94
+ if assoc = self.reflect_on_association(attr) and assoc.macro != :belongs_to
95
+ join_tables << assoc.plural_name.to_sym # todo
96
+ end
97
+ end
98
+ return join_tables.uniq
99
+ end
100
+
101
+ def self.filter_to_sql(filter)
102
+ sqls = []
103
+ values = []
104
+ filter.each do |attr, filter|
105
+ if self.reflect_on_association(attr)
106
+ result = self.assoc_filter_to_sql(attr, filter)
107
+ elsif column = self.columns_hash[attr.to_s]
108
+ result = case column.type
109
+ when :string
110
+ self.string_filter_to_sql(attr, filter)
111
+ when :integer, :decimal
112
+ self.number_filter_to_sql(attr, filter)
113
+ when :date
114
+ self.date_filter_to_sql(attr, filter)
115
+ when :boolean
116
+ self.boolean_filter_to_sql(attr, filter)
117
+ end
118
+ # todo: erweitern
119
+ else
120
+ raise "Not possible to set a filter for #{attr}"
121
+ end
122
+ sqls << result.first
123
+ values += result[1..-1]
124
+ end
125
+ sql = sqls.join(' AND ')
126
+ return [sql] + values
127
+ end
128
+
129
+ def self.string_filter_to_sql(attr, filter)
130
+ if filter.respond_to?(:each)
131
+ sql = "#{attr} in (?)"
132
+ else
133
+ sql = "#{attr} = ?"
134
+ end
135
+ return [sql, filter]
136
+ end
137
+
138
+ def self.number_filter_to_sql(attr, filter)
139
+ if filter.respond_to?(:each) and filter.none? { |f| f.is_a? String }
140
+ sql = "#{attr} in (?)"
141
+ values = [filter]
142
+ elsif filter.respond_to?(:each)
143
+ sqls = []
144
+ values = []
145
+ filter.each do |f|
146
+ result = self.number_filter_to_sql(attr, f)
147
+ sqls << result.first
148
+ values += results[1..-1]
149
+ end
150
+ sql = sqls.join(' OR ')
151
+ elsif filter.is_a? String
152
+ unless result = filter.match(/^([<>=]*)(\d+\.?\d*)$/)
153
+ raise "invalid number filter: '#{filter}'"
154
+ end
155
+ operator = result[1] != '' ? result[1] : '='
156
+ value = result[2].include?('.') ? result[2].to_f : result[2].to_i
157
+ sql = "#{attr} #{operator} ?"
158
+ values = [value]
159
+ else
160
+ sql = "#{attr} = ?"
161
+ values = [filter]
162
+ end
163
+ return [sql] + values
164
+ end
165
+
166
+ def self.date_filter_to_sql(attr, filter)
167
+ if filter.respond_to?(:each)
168
+ if filter.size != 2
169
+ raise "invalid date filter: #{filter.inspect} 2 elements expected, #{filter.size} given"
170
+ end
171
+ sql = "#{attr} BETWEEN ? AND ?"
172
+ values = filter
173
+ else
174
+ sql = "#{attr} = ?"
175
+ values = [filter]
176
+ end
177
+ return [sql] + values
178
+ end
179
+
180
+ def self.boolean_filter_to_sql(attr, filter)
181
+ return ["#{attr} = ?", filter]
182
+ end
183
+
184
+ def self.assoc_filter_to_sql(attr, filter)
185
+ assoc = self.reflect_on_association(attr)
186
+ if assoc.macro == :belongs_to
187
+ attr = "#{attr}_id"
188
+ else
189
+ attr = "#{assoc.plural_name}.id" # todo
190
+ end
191
+ return self.number_filter_to_sql(attr, filter)
192
+ end
193
+
194
+ end
195
+ end
196
+
197
+ module ActiveRecord::Associations::Builder
198
+ class CollectionAssociation < Association
199
+ include AssociationMethods
200
+ end
201
+ end