-
Notifications
You must be signed in to change notification settings - Fork 0
/
html_tablebakery.rb
251 lines (213 loc) · 9.67 KB
/
html_tablebakery.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# encoding: utf-8 # source files receive a US-ASCII Encoding, unless you say otherwise.
module HtmlTablebakery
public
# returns an html table
# Possible options:<br/>
#<br/>
# :html_class - tables class attribute (will be merged with default table classes) <br/>
# :join (Array) - fill cell with a list of related objects (build from Active Record reflections) <br/>
# :rowlinks - set hyperlink within any table cell (except actions-cell) to have a clickable row
# :actions - to be documented,
def htmltable_for(collection, *args)
table_classes = "table table-hover table-striped" # may be be expanded with :html_class
add_rowlinks = nil
append_actions_cell = nil
append_join_cell = nil
join_reflections = nil
join_as = nil
join_heading = nil
threat_as = nil # use this class to decide attributes of passed collection, may be overridden by :threat_as option
# *args is an Array and not a hash, so we need to make it a little more
# usable first! Scan for known options and use them
args.each do |args_object|
if args_object.is_a? Hash
if args_object.include? :html_class
table_classes = table_classes+' '+args_object[:html_class]
end
if args_object.include? :rowlinks
add_rowlinks = true
end
if args_object.include? :actions
append_actions_cell = args_object[:actions]
end
if args_object.include? :join
append_join_cell = true
join_reflections = HashWithIndifferentAccess.new
args_object[:join].each do |join_attr|
join_reflections[join_attr] = nil # prepare reflection, real information is appended later
end
end
if args_object.include? :join_as
join_as = args_object[:join_as]
end
if args_object.include? :join_heading
join_heading = args_object[:join_heading]
end
if args_object.include? :threat_as
threat_as = args_object[:threat_as]
end
end
end
attr_ignore = []
attr_hide = []
attr_available = []
attr_order = []
config_attr_ignore = nil
config_attr_order = nil
# test collection for validity
if collection.nil? or collection.empty?
return 'None yet <i class="fa fa-meh-o"></i>'
end
# get attributes via first element of passed collection and apply column visibility & ordering based on presets
sample_obj = collection.first
obj_id = sample_obj.id
obj_class_name = sample_obj.class.name
obj_class_symbol = threat_as ? threat_as.underscore.to_sym : obj_class_name.underscore.to_sym
# resolve default presets (ignore and order)
[:attr_order, :attr_ignore].each do |a|
if (TABLEBAKERY_PRESETS[obj_class_symbol] && TABLEBAKERY_PRESETS[obj_class_symbol][a])
eval("config_#{a.to_s} = TABLEBAKERY_PRESETS[obj_class_symbol][a]")
end
end
# any attr to ignore?
attr_ignore.concat(config_attr_ignore).uniq if config_attr_ignore
# which attr are available for collection sample?
sample_obj.attributes.each_key {|attr| attr_available.push(attr) unless attr_ignore.include?(attr) || attr_hide.include?(attr) }
# get the join attributes from associations of sample object
join_class= nil
join_collection=nil
join_through_class=nil
object_class_name = sample_obj.class.name
object_class = object_class_name.constantize
reflections = object_class.reflect_on_all_associations(:has_many) # :has_many, :has_one, :belongs_to
# iterate over wanted join attributes and get reflection details
unless join_reflections.nil?
join_reflections.each do |join_name,value|
reflections.each_with_index do |reflection, i|
#puts "#{object_class_name} »#{reflection.macro}« »#{reflection.plural_name}« #{reflection.options}"
# we want class that belongs to configured :join attribute name
if join_name == reflection.plural_name || join_name == reflection.name
join_class=reflection.name.to_s
# for :has_many through associations use the origin class name
unless reflection.options[:through].nil?
join_through_class=reflection.name.to_s
end
end
join_reflections[join_name] = { 'class_name' => join_through_class||join_class,
'heading' => join_heading||join_name.titleize,
'as' => join_as||nil}
end
end
end
# make sorting of 'actions' and (multiple) join cells possible
attr_available.push('actions') if append_actions_cell && append_actions_cell.has_value?(true)
if append_join_cell
join_reflections.each do |join_name, config|
attr_available.push("join_#{join_name}")
end
end
# ordering
#attr_order = attr_available.sort
if config_attr_order
attr_diff = attr_available.sort - config_attr_order.sort
attr_sorted = (config_attr_order & attr_available) + attr_diff
else
attr_sorted = attr_available.sort
end
# create table & headings
html = "<table class=\"#{table_classes}\">"
html += '<thead>'
html += '<tr>'
attr_sorted.each do |attr|
if attr.starts_with? 'join_' # special threatment for join columns
html += "<th>#{join_reflections[attr.gsub(/join_/, '') ]['heading']}</th>"
else
html += "<th>#{attr.titleize}</th>"
end
end
html += '</tr>'
html += '</thead>'
# generate table cells
html += '<tbody>'
collection.each do |item|
html += '<tr>'
# process cells and format value according to column name or values class name
attr_sorted.each do |attr|
#special treat for date columns
case attr
when 'actions'
# render additional action cell?
# TODO add more cleverness here, :destroy definitely needs special threatment, any other action could use <actionname>_<objectname>_path routes, move this into own helper method
ac=''
# destroy links need special handling
if append_actions_cell && append_actions_cell[:destroy]
classname = obj_class_name.underscore
l = "#{classname}_path(#{item[:id]})"
ac += link_to(raw('<span class="fa fa-times"></span> delete'), eval(l), method: :delete, class: "btn btn-default btn-xs tablebakery_delete delete_#{classname}", data: { id: obj_id, confirm: 'Are you sure?'} )
end
if append_actions_cell && append_actions_cell[:edit]
l = "edit_#{obj_class_name.underscore}_path(#{item[:id]})"
ac += link_to(raw('<span class="fa fa-wrench"></span> Edit'), eval(l), :class => 'btn btn-default btn-xs')
end
# run action should append data-id, have empty href and .run_test_plan to trigger js handlers
if append_actions_cell && append_actions_cell[:run]
ac += link_to(raw('<span class="fa fa-play"></span> Run'), '#', class: 'btn btn-default btn-xs run_test_plan', data: { id: item[:id]})
end
if append_actions_cell && append_actions_cell[:show]
l = "#{obj_class_name.underscore}_path(#{item[:id]})"
ac += link_to(raw('<span class="fa fa-eye"></span> Show'), eval(l), :class => 'btn btn-default btn-xs')
end
html += "<td class=\"actions\">#{ac}</td>"
# markup columns should receive syntax highlight; depends on ApplicationHelper methods!
when 'markup'
# try to find value of attribute "format" to decide type of markup, otherwise use text
format = item["format"].nil? ? "text" : item["format"]
html += "<td>#{item[attr.to_sym].html_safe? ? item[attr.to_sym] : coderay(item[attr.to_sym],format)}</td>"
# wrap format in a nice badge; depends on ApplicationHelper methods!
when 'format'
html += "<td>#{badge(item[attr.to_sym], 'info')}</td>"
# wrap type (TestCase, TestScript..) in a nice badge; depends on ApplicationHelper methods!
when 'type'
html += "<td>#{badge(item[attr.to_sym], 'primary')}</td>"
when /^join.*/
# render cell for join objects?
jc=''
if append_join_cell
join_collection=eval("item.#{join_reflections[attr.gsub(/join_/, '')]['class_name']}")
# genereate link to join_class or join_through_class (if set)
jc+=join_collection.map{|obj|
if join_through_class
eval("obj.name")
else
if join_as
link_to eval("obj.#{join_as}.name"), eval("obj.#{join_as}")
else
link_to eval("obj.#{join_class.singularize}.name"), eval("obj.#{join_class.singularize}")
end
end
}.join(", ")
end
html += "<td class=\"join\">#{jc}</td>"
# usually date columns are ending with *_at
when /.*_at$/
html+= "<td>"
html+=I18n.localize(item[attr.to_sym], :format => :short) unless item[attr.to_sym].nil?
html += "</td>"
# render just a regular text cell
else
html += "<td>#{add_rowlinks ? wrap_rowlinks(item[attr.to_sym], eval("#{obj_class_name.underscore}_path(#{item[:id]})")) : item[attr.to_sym]}</td>"
end #end case attr
end # end attr_sorted.each
end # end collection.each
html += '</tr>'
html += '</tbody>'
html += '</table>'
html
end
def wrap_rowlinks(cell_text, href)
l = "<a class=\"rowlink\" href=\"#{href}\">"
l << cell_text unless cell_text.nil?
l << '</a>'
l
end
end