rgviz-rails 0.23 → 0.25
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/lib/rgviz_rails/view_helper.rb +437 -0
- metadata +4 -3
@@ -0,0 +1,437 @@
|
|
1
|
+
module Rgviz
|
2
|
+
module ViewHelper
|
3
|
+
def rgviz(options = {})
|
4
|
+
options = options.with_indifferent_access
|
5
|
+
|
6
|
+
id = options[:id]
|
7
|
+
kind = options[:kind]
|
8
|
+
url = options[:url]
|
9
|
+
query = options[:query] || ''
|
10
|
+
events = options[:events] || {}
|
11
|
+
hidden = options[:hidden]
|
12
|
+
|
13
|
+
html_prefix = (options[:html_prefix] || 'html').to_s
|
14
|
+
js_prefix = (options[:js_prefix] || 'js').to_s
|
15
|
+
param_prefix = (options[:param_prefix] || 'param').to_s
|
16
|
+
|
17
|
+
html_prefix += '_'
|
18
|
+
js_prefix += '_'
|
19
|
+
param_prefix += '_'
|
20
|
+
|
21
|
+
debug = options[:debug]
|
22
|
+
opts = options[:options] || {}
|
23
|
+
opts[:width] = 640 unless opts[:width]
|
24
|
+
opts[:height] = 480 unless opts[:height]
|
25
|
+
opts = opts.to_json
|
26
|
+
|
27
|
+
raise "Must specify an :id" unless id
|
28
|
+
raise "Must specify a :kind" unless kind
|
29
|
+
raise "Must specify a :url" unless url
|
30
|
+
|
31
|
+
url = url_for url
|
32
|
+
|
33
|
+
# Parse the query
|
34
|
+
query = Parser.new(query).parse
|
35
|
+
|
36
|
+
# And replace the html_ and javascript_ magic names
|
37
|
+
visitor = MagicNamesVisitor.new(html_prefix, js_prefix, param_prefix)
|
38
|
+
query.accept visitor
|
39
|
+
query_builder = visitor.query_builder
|
40
|
+
query_builder_var = visitor.query_builder_var
|
41
|
+
params = visitor.params
|
42
|
+
params = params.sort!.map{|i| "param_#{i}"}
|
43
|
+
|
44
|
+
out = ''
|
45
|
+
|
46
|
+
# Output the google jsapi tag the first time
|
47
|
+
@first_time ||= 1
|
48
|
+
if @first_time == 1
|
49
|
+
out << "<script type=\"text/javascript\" src=\"http://www.google.com/jsapi\"></script>\n"
|
50
|
+
end
|
51
|
+
@first_time = 0
|
52
|
+
|
53
|
+
# Now the real script
|
54
|
+
out << "<script type=\"text/javascript\">\n"
|
55
|
+
|
56
|
+
# Load visualizations and the package, if not already loaded
|
57
|
+
pack = kind.downcase
|
58
|
+
@packages ||= []
|
59
|
+
unless @packages.include?(pack)
|
60
|
+
out << "google.load(\"visualization\", \"1\", {'packages':['#{pack}']});\n"
|
61
|
+
@packages << pack
|
62
|
+
end
|
63
|
+
|
64
|
+
callback = "draw_rgviz_#{id}"
|
65
|
+
|
66
|
+
# Set the callback if the function doesn't have params and if the
|
67
|
+
# user didn't request to hide the visualization
|
68
|
+
if !hidden && params.empty?
|
69
|
+
out << "google.setOnLoadCallback(#{callback});\n"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Define the visualization var and data
|
73
|
+
out << "var rgviz_#{id} = null;\n"
|
74
|
+
out << "var rgviz_#{id}_data = null;\n"
|
75
|
+
|
76
|
+
# And define the callback
|
77
|
+
out << "function #{callback}(#{params.join(', ')}) {\n"
|
78
|
+
out << "var query = new google.visualization.Query('#{url}');\n"
|
79
|
+
out << "#{query_builder}\n"
|
80
|
+
out << "alert(#{query_builder_var});\n" if debug
|
81
|
+
out << "query.setQuery(#{query_builder_var});\n"
|
82
|
+
out << "query.send(function(response) {\n"
|
83
|
+
out << "rgviz_#{id} = new google.visualization.#{kind}(document.getElementById('#{id}'));\n"
|
84
|
+
events.each do |name, handler|
|
85
|
+
out << "google.visualization.events.addListener(rgviz_#{id}, '#{name}', #{handler});\n"
|
86
|
+
end
|
87
|
+
out << "rgviz_#{id}_data = response.getDataTable();\n"
|
88
|
+
out << "rgviz_#{id}.draw(rgviz_#{id}_data, #{opts});\n"
|
89
|
+
out << "});\n"
|
90
|
+
out << "}\n"
|
91
|
+
|
92
|
+
out << "</script>\n"
|
93
|
+
|
94
|
+
# Write the div
|
95
|
+
out << "<div id=\"#{id}\"></div>\n"
|
96
|
+
|
97
|
+
out
|
98
|
+
end
|
99
|
+
|
100
|
+
module_function :rgviz
|
101
|
+
end
|
102
|
+
|
103
|
+
class MagicNamesVisitor < Visitor
|
104
|
+
def initialize(html_prefix, js_prefix, param_prefix)
|
105
|
+
@html_prefix = html_prefix
|
106
|
+
@js_prefix = js_prefix
|
107
|
+
@param_prefix = param_prefix
|
108
|
+
@s = ''
|
109
|
+
@params = []
|
110
|
+
end
|
111
|
+
|
112
|
+
def query_builder
|
113
|
+
@s.strip
|
114
|
+
end
|
115
|
+
|
116
|
+
def query_builder_var
|
117
|
+
'q'
|
118
|
+
end
|
119
|
+
|
120
|
+
def params
|
121
|
+
@params
|
122
|
+
end
|
123
|
+
|
124
|
+
def visit_query(node)
|
125
|
+
@s << "var e = null;\n"
|
126
|
+
@s << "var q = '"
|
127
|
+
node.select.accept self if node.select
|
128
|
+
node.where.accept self if node.where
|
129
|
+
node.group_by.accept self if node.group_by
|
130
|
+
node.pivot.accept self if node.pivot
|
131
|
+
node.order_by.accept self if node.order_by
|
132
|
+
@s << "limit #{node.limit} " if node.limit
|
133
|
+
@s << "offset #{node.offset} " if node.offset
|
134
|
+
if node.labels
|
135
|
+
@s << "label "
|
136
|
+
node.labels.each_with_index do |l, i|
|
137
|
+
@s << ', ' if i > 0
|
138
|
+
l.accept self
|
139
|
+
end
|
140
|
+
end
|
141
|
+
if node.formats
|
142
|
+
@s << "format "
|
143
|
+
node.formats.each_with_index do |f, i|
|
144
|
+
@s << ', ' if i > 0
|
145
|
+
f.accept self
|
146
|
+
end
|
147
|
+
end
|
148
|
+
if node.options
|
149
|
+
@s << "options "
|
150
|
+
@s << "no_values " if node.options.no_values
|
151
|
+
@s << "no_format " if node.options.no_format
|
152
|
+
end
|
153
|
+
@s << "';\n"
|
154
|
+
false
|
155
|
+
end
|
156
|
+
|
157
|
+
def visit_select(node)
|
158
|
+
@s << "select ";
|
159
|
+
print_columns node
|
160
|
+
@s << " "
|
161
|
+
false
|
162
|
+
end
|
163
|
+
|
164
|
+
def visit_where(node)
|
165
|
+
@s << "where "
|
166
|
+
node.expression.accept self
|
167
|
+
@s << " "
|
168
|
+
false
|
169
|
+
end
|
170
|
+
|
171
|
+
def visit_group_by(node)
|
172
|
+
@s << "group by "
|
173
|
+
print_columns node
|
174
|
+
@s << " "
|
175
|
+
false
|
176
|
+
end
|
177
|
+
|
178
|
+
def visit_pivot(node)
|
179
|
+
@s << "pivot "
|
180
|
+
print_columns node
|
181
|
+
@s << " "
|
182
|
+
false
|
183
|
+
end
|
184
|
+
|
185
|
+
def visit_order_by(node)
|
186
|
+
@s << "order_by "
|
187
|
+
node.sorts.each_with_index do |s, i|
|
188
|
+
@s << ', ' if i > 0
|
189
|
+
s.column.accept self
|
190
|
+
@s << ' '
|
191
|
+
@s << s.order
|
192
|
+
end
|
193
|
+
@s << " "
|
194
|
+
false
|
195
|
+
end
|
196
|
+
|
197
|
+
def visit_label(node)
|
198
|
+
node.column.accept self
|
199
|
+
@s << ' '
|
200
|
+
if node.label.include?("'")
|
201
|
+
val = node.label.gsub("'", "\\'")
|
202
|
+
@s << "\"#{val}\""
|
203
|
+
else
|
204
|
+
@s << "\\'#{node.label}\\'"
|
205
|
+
end
|
206
|
+
false
|
207
|
+
end
|
208
|
+
|
209
|
+
def visit_format(node)
|
210
|
+
node.column.accept self
|
211
|
+
@s << ' '
|
212
|
+
if node.pattern.include?("'")
|
213
|
+
@s << "\"#{node.pattern}\""
|
214
|
+
else
|
215
|
+
@s << "'#{node.pattern}'"
|
216
|
+
end
|
217
|
+
false
|
218
|
+
end
|
219
|
+
|
220
|
+
def visit_binary_expression(node)
|
221
|
+
if node.operator == BinaryExpression::Eq
|
222
|
+
source = has_magic_name?(node.right)
|
223
|
+
if source
|
224
|
+
@s << "';\n"
|
225
|
+
case source[:source]
|
226
|
+
when :html
|
227
|
+
@s << "e = document.getElementById('#{source[:id]}');\n"
|
228
|
+
@s << "if (e.tagName.toLowerCase() == 'select' && e.multiple) {\n"
|
229
|
+
@s << "var s = [];\n"
|
230
|
+
@s << "var o = e.options;\n"
|
231
|
+
@s << "for(var i = 0; i < o.length; i++) {\n"
|
232
|
+
@s << "if (o[i].selected)\n";
|
233
|
+
@s << "s.push(o[i].value);\n"
|
234
|
+
@s << "}\n"
|
235
|
+
append_selections node, source
|
236
|
+
@s << "} else {\n"
|
237
|
+
@s << "q += '"
|
238
|
+
node.left.accept self
|
239
|
+
@s << " #{node.operator} "
|
240
|
+
@s << "';\n"
|
241
|
+
append_before_source_type source[:type]
|
242
|
+
@s << "e.value"
|
243
|
+
append_after_source_type source[:type]
|
244
|
+
@s << "}\n";
|
245
|
+
when :js
|
246
|
+
@s << "var s = #{source[:id]}();\n"
|
247
|
+
@s << "if (typeof(s) != 'object') s = [s];\n"
|
248
|
+
append_selections node, source
|
249
|
+
when :param
|
250
|
+
@s << "var s = param_#{source[:id]};\n"
|
251
|
+
@s << "if (typeof(s) != 'object') s = [s];\n"
|
252
|
+
append_selections node, source
|
253
|
+
@params << source[:id].to_i unless @params.include?(source[:id])
|
254
|
+
end
|
255
|
+
@s << "q += '"
|
256
|
+
return false
|
257
|
+
end
|
258
|
+
end
|
259
|
+
node.left.accept self
|
260
|
+
@s << " #{node.operator} "
|
261
|
+
node.right.accept self
|
262
|
+
false
|
263
|
+
end
|
264
|
+
|
265
|
+
def visit_unary_expression(node)
|
266
|
+
if node.operator == UnaryExpression::Not
|
267
|
+
@s << "not "
|
268
|
+
node.operand.accept self
|
269
|
+
else
|
270
|
+
node.operand.accept self
|
271
|
+
@s << " #{node.operator}"
|
272
|
+
end
|
273
|
+
false
|
274
|
+
end
|
275
|
+
|
276
|
+
def visit_id_column(node)
|
277
|
+
source = get_source node.name
|
278
|
+
case source[:source]
|
279
|
+
when nil
|
280
|
+
@s << "`#{node.name}`"
|
281
|
+
when :html
|
282
|
+
@s << "';\n"
|
283
|
+
append_before_source_type source[:type]
|
284
|
+
@s << "document.getElementById('#{source[:id]}').value"
|
285
|
+
append_after_source_type source[:type]
|
286
|
+
@s << "q += '"
|
287
|
+
when :js
|
288
|
+
@s << "';\n"
|
289
|
+
append_before_source_type source[:type]
|
290
|
+
@s << "#{source[:id]}()"
|
291
|
+
append_after_source_type source[:type]
|
292
|
+
@s << "q += '"
|
293
|
+
when :param
|
294
|
+
@s << "';\n"
|
295
|
+
append_before_source_type source[:type]
|
296
|
+
@s << "param_#{source[:id]}"
|
297
|
+
append_after_source_type source[:type]
|
298
|
+
@s << "q += '"
|
299
|
+
@params << source[:id].to_i unless @params.include?(source[:id])
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
def visit_number_column(node)
|
304
|
+
@s << node.value.to_s
|
305
|
+
end
|
306
|
+
|
307
|
+
def visit_string_column(node)
|
308
|
+
@s << node.value
|
309
|
+
end
|
310
|
+
|
311
|
+
def visit_boolean_column(node)
|
312
|
+
@s << node.value.to_s
|
313
|
+
end
|
314
|
+
|
315
|
+
def visit_date_column(node)
|
316
|
+
@s << node.to_s
|
317
|
+
end
|
318
|
+
|
319
|
+
def visit_date_time_column(node)
|
320
|
+
@s << node.to_s
|
321
|
+
end
|
322
|
+
|
323
|
+
def visit_time_of_day_column(node)
|
324
|
+
@s << node.to_s
|
325
|
+
end
|
326
|
+
|
327
|
+
def visit_scalar_function_column(node)
|
328
|
+
case node.function
|
329
|
+
when ScalarFunctionColumn::Sum, ScalarFunctionColumn::Difference,
|
330
|
+
ScalarFunctionColumn::Product, ScalarFunctionColumn::Quotient
|
331
|
+
node.arguments[0].accept node
|
332
|
+
@s << " #{node.function} "
|
333
|
+
node.arguments[1].accept node
|
334
|
+
else
|
335
|
+
@s << "#{node.function}("
|
336
|
+
node.arguments.each_with_index do |a, i|
|
337
|
+
@s << ', ' if i > 0
|
338
|
+
a.accept self
|
339
|
+
end
|
340
|
+
@s << ")"
|
341
|
+
end
|
342
|
+
false
|
343
|
+
end
|
344
|
+
|
345
|
+
def visit_aggregate_column(node)
|
346
|
+
@s << "#{node.function}("
|
347
|
+
node.argument.accept self
|
348
|
+
@s << ")"
|
349
|
+
false
|
350
|
+
end
|
351
|
+
|
352
|
+
def print_columns(node)
|
353
|
+
node.columns.each_with_index do |c, i|
|
354
|
+
@s << ', ' if i > 0
|
355
|
+
c.accept self
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
def get_source(name)
|
360
|
+
if name.start_with?(@html_prefix)
|
361
|
+
get_source_type :html, name[@html_prefix.length .. -1]
|
362
|
+
elsif name.start_with?(@js_prefix)
|
363
|
+
get_source_type :js, name[@js_prefix.length .. -1]
|
364
|
+
elsif name.start_with?(@param_prefix)
|
365
|
+
get_source_type :param, name[@param_prefix.length .. -1]
|
366
|
+
else
|
367
|
+
{}
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
def get_source_type(source, name)
|
372
|
+
if name.start_with?('number_')
|
373
|
+
{:source => source, :id => name[7 .. -1], :type => :number}
|
374
|
+
elsif name.start_with?('string_')
|
375
|
+
{:source => source, :id => name[7 .. -1], :type => :string}
|
376
|
+
elsif name.start_with?('date_')
|
377
|
+
{:source => source, :id => name[5 .. -1], :type => :date}
|
378
|
+
elsif name.start_with?('datetime_')
|
379
|
+
{:source => source, :id => name[9 .. -1], :type => :datetime}
|
380
|
+
elsif name.start_with?('timeofday_')
|
381
|
+
{:source => source, :id => name[10 .. -1], :type => :timeofday}
|
382
|
+
else
|
383
|
+
{:source => source, :id => name, :type => :string}
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
def append_before_source_type(type)
|
388
|
+
case type
|
389
|
+
when :number
|
390
|
+
@s << "q += "
|
391
|
+
when :string
|
392
|
+
@s << "q += \"'\" +"
|
393
|
+
when :date
|
394
|
+
@s << "q += \"date '\" + "
|
395
|
+
when :datetime
|
396
|
+
@s << "q += \"datetime '\" + "
|
397
|
+
when :timeofday
|
398
|
+
@s << "q += \"timeofday '\" + "
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
def append_after_source_type(type)
|
403
|
+
case type
|
404
|
+
when :number
|
405
|
+
return
|
406
|
+
else
|
407
|
+
@s << " + \"'\";\n"
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
def append_selections(node, source)
|
412
|
+
@s << "if (s.length == 0) {\n"
|
413
|
+
@s << "q += '1 = 2';\n";
|
414
|
+
@s << "} else {\n"
|
415
|
+
@s << "q += '(';\n"
|
416
|
+
@s << "for(var i = 0; i < s.length; i++) {\n";
|
417
|
+
@s << "if (i > 0) q += ' or ';\n"
|
418
|
+
@s << "q += '"
|
419
|
+
node.left.accept self
|
420
|
+
@s << " #{node.operator} "
|
421
|
+
@s << "';\n"
|
422
|
+
append_before_source_type source[:type]
|
423
|
+
@s << "s[i]"
|
424
|
+
append_after_source_type source[:type]
|
425
|
+
@s << "}";
|
426
|
+
@s << "q += ')';\n"
|
427
|
+
@s << "}\n"
|
428
|
+
end
|
429
|
+
|
430
|
+
def has_magic_name?(node)
|
431
|
+
return false unless node.kind_of?(IdColumn)
|
432
|
+
source = get_source node.name
|
433
|
+
return false unless source[:source]
|
434
|
+
return source
|
435
|
+
end
|
436
|
+
end
|
437
|
+
end
|
metadata
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rgviz-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 57
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: "0.
|
8
|
+
- 25
|
9
|
+
version: "0.25"
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Ary Borenszweig
|
@@ -29,6 +29,7 @@ extra_rdoc_files:
|
|
29
29
|
files:
|
30
30
|
- lib/rgviz_rails.rb
|
31
31
|
- lib/rgviz_rails/executor.rb
|
32
|
+
- lib/rgviz_rails/view_helper.rb
|
32
33
|
- lib/rgviz_rails/adapters/mysql_adapter.rb
|
33
34
|
- lib/rgviz_rails/adapters/postgresql_adapter.rb
|
34
35
|
- lib/rgviz_rails/adapters/sqlite_adapter.rb
|