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.
Files changed (2) hide show
  1. data/lib/rgviz_rails/view_helper.rb +437 -0
  2. 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: 37
4
+ hash: 57
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 23
9
- version: "0.23"
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