rgviz-rails 0.23 → 0.25

Sign up to get free protection for your applications and to get access to all the features.
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