fancygrid 1.0.0

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 (97) hide show
  1. data/.bundle/config +2 -0
  2. data/.rspec +1 -0
  3. data/CHANGELOG +34 -0
  4. data/Gemfile +15 -0
  5. data/Gemfile.lock +125 -0
  6. data/LICENSE +20 -0
  7. data/README.rdoc +299 -0
  8. data/ROADMAP +1 -0
  9. data/Rakefile +45 -0
  10. data/VERSION +1 -0
  11. data/app/views/fancygrid/_cells.html.haml +13 -0
  12. data/app/views/fancygrid/base/controls.html.haml +40 -0
  13. data/app/views/fancygrid/base/list_frame.html.haml +37 -0
  14. data/app/views/fancygrid/base/search.html.haml +33 -0
  15. data/app/views/fancygrid/base/sort.html.haml +20 -0
  16. data/app/views/fancygrid/base/table_frame.html.haml +45 -0
  17. data/config/initializers/fancygrid.rb +67 -0
  18. data/config/locales/fancygrid.de.yml +41 -0
  19. data/config/locales/fancygrid.en.yml +42 -0
  20. data/fancygrid.gemspec +162 -0
  21. data/init.rb +1 -0
  22. data/lib/fancygrid.rb +73 -0
  23. data/lib/fancygrid/grid.rb +387 -0
  24. data/lib/fancygrid/helper.rb +129 -0
  25. data/lib/fancygrid/node.rb +533 -0
  26. data/lib/fancygrid/query_generator.rb +338 -0
  27. data/lib/fancygrid/view.rb +148 -0
  28. data/lib/generators/install_generator.rb +61 -0
  29. data/lib/generators/views_generator.rb +25 -0
  30. data/lib/version.rb +0 -0
  31. data/public/images/fancygrid/add.png +0 -0
  32. data/public/images/fancygrid/clear.png +0 -0
  33. data/public/images/fancygrid/ddn.png +0 -0
  34. data/public/images/fancygrid/dn.png +0 -0
  35. data/public/images/fancygrid/dots.png +0 -0
  36. data/public/images/fancygrid/loading.gif +0 -0
  37. data/public/images/fancygrid/magnifier.png +0 -0
  38. data/public/images/fancygrid/next.png +0 -0
  39. data/public/images/fancygrid/order.png +0 -0
  40. data/public/images/fancygrid/prev.png +0 -0
  41. data/public/images/fancygrid/reload.png +0 -0
  42. data/public/images/fancygrid/remove.png +0 -0
  43. data/public/images/fancygrid/spacer.gif +0 -0
  44. data/public/images/fancygrid/submit.png +0 -0
  45. data/public/images/fancygrid/th_bg.png +0 -0
  46. data/public/images/fancygrid/up.png +0 -0
  47. data/public/images/fancygrid/uup.png +0 -0
  48. data/public/javascripts/fancygrid.js +477 -0
  49. data/public/javascripts/fancygrid.min.js +17 -0
  50. data/public/stylesheets/fancygrid.css +289 -0
  51. data/public/stylesheets/fancygrid.scss +302 -0
  52. data/spec/dummy/Rakefile +7 -0
  53. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  54. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  55. data/spec/dummy/app/models/project.rb +3 -0
  56. data/spec/dummy/app/models/ticket.rb +3 -0
  57. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  58. data/spec/dummy/config.ru +4 -0
  59. data/spec/dummy/config/application.rb +45 -0
  60. data/spec/dummy/config/boot.rb +10 -0
  61. data/spec/dummy/config/database.yml +22 -0
  62. data/spec/dummy/config/environment.rb +5 -0
  63. data/spec/dummy/config/environments/development.rb +26 -0
  64. data/spec/dummy/config/environments/production.rb +49 -0
  65. data/spec/dummy/config/environments/test.rb +35 -0
  66. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  67. data/spec/dummy/config/initializers/inflections.rb +10 -0
  68. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  69. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  70. data/spec/dummy/config/initializers/session_store.rb +8 -0
  71. data/spec/dummy/config/locales/en.yml +5 -0
  72. data/spec/dummy/config/routes.rb +58 -0
  73. data/spec/dummy/db/migrate/20110112183948_create_projects.rb +11 -0
  74. data/spec/dummy/db/migrate/20110112183956_create_tickets.rb +14 -0
  75. data/spec/dummy/db/test.sqlite3 +0 -0
  76. data/spec/dummy/log/development.log +0 -0
  77. data/spec/dummy/log/production.log +0 -0
  78. data/spec/dummy/log/server.log +0 -0
  79. data/spec/dummy/log/test.log +1026 -0
  80. data/spec/dummy/public/404.html +26 -0
  81. data/spec/dummy/public/422.html +26 -0
  82. data/spec/dummy/public/500.html +26 -0
  83. data/spec/dummy/public/favicon.ico +0 -0
  84. data/spec/dummy/public/javascripts/application.js +2 -0
  85. data/spec/dummy/public/javascripts/controls.js +965 -0
  86. data/spec/dummy/public/javascripts/dragdrop.js +974 -0
  87. data/spec/dummy/public/javascripts/effects.js +1123 -0
  88. data/spec/dummy/public/javascripts/prototype.js +6001 -0
  89. data/spec/dummy/public/javascripts/rails.js +175 -0
  90. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  91. data/spec/dummy/script/rails +6 -0
  92. data/spec/grid_spec.rb +15 -0
  93. data/spec/integration/navigation_spec.rb +9 -0
  94. data/spec/node_spec.rb +326 -0
  95. data/spec/query_generator_spec.rb +358 -0
  96. data/spec/spec_helper.rb +53 -0
  97. metadata +214 -0
@@ -0,0 +1,338 @@
1
+ require "active_support/hash_with_indifferent_access"
2
+
3
+ module Fancygrid
4
+ class QueryGenerator#:nodoc:
5
+
6
+ OPERATOR_NAMES = [
7
+ :equal, :not_equal, :less, :less_equal, :greater, :greater_equal, :starts_with, :ends_with,
8
+ :like, :is_null, :is_not_null, :is_true, :is_not_true, :is_false, :is_not_false, :in, :not_in
9
+ ]
10
+
11
+ attr_accessor :query
12
+
13
+ def initialize(options=nil)
14
+ options ||= {}
15
+ options = ActiveSupport::HashWithIndifferentAccess.new(options)
16
+
17
+ self.query = {}
18
+
19
+ self.select(options[:select])
20
+ self.apply_pagination(options[:pagination])
21
+ self.apply_search_conditions(options[:operator] || :and, options[:conditions])
22
+ self.apply_sort_order(options[:order])
23
+ end
24
+
25
+ def parse_options(options=nil)#:nodoc:
26
+ options ||= {}
27
+ [:conditions, :order, :group, :having, :limit, :offset, :joins, :include, :select, :from, :readonly, :lock].each do |option|
28
+ self.send(option, options[option]) unless options[option].nil?
29
+ end
30
+ end
31
+
32
+ # Takes a hash like { :page => 2, :per_page => 20 } and translates it into :limit and :offset options which are
33
+ # then applied to the final query
34
+ #
35
+ def apply_pagination(options=nil)
36
+ options ||= {}
37
+ options = ActiveSupport::HashWithIndifferentAccess.new(options)
38
+ self.limit(options[:per_page].to_i)
39
+ self.offset(options[:page].to_i * self.limit())
40
+ end
41
+
42
+ # Takes a hash like { :column => "users.name", :order => "asc" } and translates it into the :order option and
43
+ # then applies it to the final query
44
+ #
45
+ def apply_sort_order(options=nil)
46
+ self.order("#{options[:column]} #{options[:order].to_s.upcase}") if options
47
+ end
48
+
49
+ # Takes an operator and an conditions hash like { :<table> => { :<column> => [{ :oparator => <op>, :value => <value> }] } }
50
+ # and converts them into a query joined by the given operator
51
+ #
52
+ def apply_search_conditions(operator, search_conditions)
53
+ return unless search_conditions
54
+
55
+ operator = logical_operator(operator)
56
+
57
+ conditions = []
58
+ arguments = []
59
+
60
+ # backward compatibility
61
+ search_conditions = search_conditions.map do |table, columns|
62
+ columns.map do |column, value|
63
+ if value.is_a?(Hash)
64
+ if value.keys.all? { |key| key.to_s.match(/^\d+$/) }
65
+ # for hashes like this
66
+ # :<table> => {
67
+ # :<column> => {
68
+ # "0" => { :oparator => <op>, :value => <value> },
69
+ # "1" => { :oparator => <op>, :value => <value> },
70
+ # "2" => { :oparator => <op>, :value => <value> }
71
+ # }
72
+ # }
73
+ #
74
+ value.map{ |key, opts|
75
+ { :column => "#{table}.#{column}", :operator => opts[:operator], :value => opts[:value] }
76
+ }
77
+ else
78
+ # for hashes like this
79
+ # :<table> => {
80
+ # :<column> => {
81
+ # :oparator => <op>, :value => <value>
82
+ # }
83
+ # }
84
+ #
85
+ { :column => "#{table}.#{column}", :operator => value[:operator], :value => value[:value] }
86
+ end
87
+ elsif value.is_a?(Array)
88
+ # for hashes like this
89
+ # :<table> => {
90
+ # :<column> => {
91
+ # [{ :oparator => <op>, :value => <value> },
92
+ # { :oparator => <op>, :value => <value> },
93
+ # { :oparator => <op>, :value => <value> }]
94
+ # }
95
+ # }
96
+ #
97
+ value.map{ |opts|
98
+ { :column => "#{table}.#{column}", :operator => opts[:operator], :value => opts[:value] }
99
+ }
100
+ else
101
+ # for hashes like this
102
+ # :<table> => {
103
+ # :<column> => <value>
104
+ # }
105
+ #
106
+ unless value.blank?
107
+ { :column => "#{table}.#{column}", :operator => :like, :value => value }
108
+ else
109
+ nil
110
+ end
111
+ end
112
+ end
113
+ end
114
+ search_conditions = search_conditions.flatten
115
+
116
+ search_conditions.each do |options|
117
+ next unless options
118
+ sql_query, value = comparison_operator(options[:column], options[:operator], options[:value])
119
+ conditions << sql_query
120
+ arguments << value if (value)
121
+ end
122
+
123
+ conditions = [conditions.join(operator)] + arguments
124
+ append_conditions(:and, conditions)
125
+ end
126
+
127
+ # Joins two conditions arrays or strings with the given operator
128
+ #
129
+ # === Example
130
+ #
131
+ # condition1 = ["first_name = ?", first_name]
132
+ # condition2 = ["last_name = ?", last_name]
133
+ #
134
+ # join_conditions(:and, condition1, condition2)
135
+ # # => ["(first_name = ?) AND (last_name = ?)", first_name, last_name]
136
+ #
137
+ def join_conditions(operator, conditions1, conditions2)
138
+ conditions1 = Array(conditions1)
139
+ conditions2 = Array(conditions2)
140
+ operator = logical_operator(operator).gsub(" ", "")
141
+
142
+ if conditions1.empty?
143
+ return [] if conditions2.empty?
144
+ return conditions2
145
+ elsif conditions2.empty?
146
+ return conditions1
147
+ end
148
+
149
+ left_sql = conditions1.shift
150
+ right_sql = conditions2.shift
151
+
152
+ if left_sql.blank?
153
+ return [] if right_sql.blank?
154
+ return [right_sql] + conditions2
155
+ elsif right_sql.blank?
156
+ return [left_sql] + conditions1
157
+ end
158
+
159
+ conditions = "(#{left_sql}) #{operator} (#{right_sql})"
160
+ return [conditions] + conditions1 + conditions2
161
+ end
162
+
163
+ def append_conditions(operator, conditions)
164
+ self.query[:conditions] = join_conditions(operator, self.query[:conditions], conditions)
165
+ end
166
+
167
+ # An SQL fragment like “administrator = 1”, ["user_name = ?", username], or ["user_name = :user_name", { :user_name => user_name }]
168
+ #
169
+ def conditions(conditions=nil)
170
+ if conditions
171
+ append_conditions(:and, conditions)
172
+ end
173
+ self.query[:conditions]
174
+ end
175
+
176
+ # An SQL fragment like “created_at DESC, name”.
177
+ #
178
+ def order(order_by=nil)
179
+ self.query[:order] = order_by if order_by
180
+ self.query[:order]
181
+ end
182
+
183
+ # An SQL fragment like “created_at DESC, name”.
184
+ #
185
+ def group(group_by=nil)
186
+ self.query[:group] = group_by if group_by
187
+ self.query[:group]
188
+ end
189
+
190
+ # An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
191
+ #
192
+ def having(having_sql=nil)
193
+ self.query[:having] = having_sql if having_sql
194
+ self.query[:having]
195
+ end
196
+
197
+ # An integer determining the limit on the number of rows that should be returned.
198
+ #
199
+ def limit(num=nil)
200
+ self.query[:limit] = num if num
201
+ self.query[:limit]
202
+ end
203
+
204
+ # An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
205
+ #
206
+ def offset(num=nil)
207
+ self.query[:offset] = num if num
208
+ self.query[:offset]
209
+ end
210
+
211
+ # Either an SQL fragment for additional joins like “LEFT JOIN comments ON comments.post_id = id” (rarely needed),
212
+ # named associations in the same form used for the :include option, which will perform an INNER JOIN on the
213
+ # associated table(s), or an array containing a mixture of both strings and named associations. If the value is a
214
+ # string, then the records will be returned read-only since they will have attributes that do not correspond to the
215
+ # table’s columns. Pass :readonly => false to override.
216
+ #
217
+ def joins(to_join_with=nil)
218
+ self.query[:joins] = to_join_with if to_join_with
219
+ self.query[:joins]
220
+ end
221
+
222
+ # Names associations that should be loaded alongside. The symbols named refer to already defined associations.
223
+ # See eager loading under Associations.
224
+ #
225
+ def include(to_include=nil)
226
+ self.query[:include] = to_include if to_include
227
+ self.query[:include]
228
+ end
229
+
230
+ # By default, this is “*” as in “SELECT * FROM”, but can be changed if you, for example, want to do a join but not
231
+ # include the joined columns. Takes a string with the SELECT SQL fragment (e.g. “id, name”).
232
+ #
233
+ def select(select = nil)
234
+ if select
235
+ self.query[:select] = Array(self.query[:select])
236
+ self.query[:select] |= Array(select)
237
+
238
+ if self.query[:select].include?("*")
239
+ self.query[:select] = ["*"]
240
+ end
241
+ end
242
+ self.query[:select]
243
+ end
244
+
245
+ # By default, this is the table name of the class, but can be changed to an alternate table name
246
+ # (or even the name of a database view).
247
+ #
248
+ def from(table_name=nil)
249
+ self.query[:from] = table_name if table_name
250
+ self.query[:from]
251
+ end
252
+
253
+ # Mark the returned records read-only so they cannot be saved or updated.
254
+ #
255
+ def readonly(value=nil)
256
+ self.query[:readonly] = value unless value.nil?
257
+ self.query[:readonly]
258
+ end
259
+
260
+ # An SQL fragment like “FOR UPDATE” or “LOCK IN SHARE MODE”. :lock => true gives connection’s default exclusive
261
+ # lock, usually “FOR UPDATE”.
262
+ #
263
+ def lock(value=nil)
264
+ self.query[:lock] = value unless value.nil?
265
+ self.query[:lock]
266
+ end
267
+
268
+ private
269
+ def comparison_operator(column, operator, value)
270
+ operator = case operator.to_s
271
+ when "equal"
272
+ "="
273
+ when "not_equal"
274
+ "!="
275
+ when "less"
276
+ "<"
277
+ when "less_equal"
278
+ "<="
279
+ when "greater"
280
+ ">"
281
+ when "greater_equal"
282
+ ">="
283
+ when "starts_with"
284
+ value = "#{value.to_param}%"
285
+ "LIKE"
286
+ when "ends_with"
287
+ value = "%#{value.to_param}"
288
+ "LIKE"
289
+ when "like"
290
+ value = "%#{value.to_param}%"
291
+ "LIKE"
292
+ when "is_null"
293
+ value = nil
294
+ "IS NULL"
295
+ when "is_not_null"
296
+ value = nil
297
+ "IS NOT NULL"
298
+ when "is_true"
299
+ value = nil
300
+ "IS TRUE"
301
+ when "is_not_true"
302
+ value = nil
303
+ "IS NOT TRUE"
304
+ when "is_false"
305
+ value = nil
306
+ "IS FALSE"
307
+ when "is_not_false"
308
+ value = nil
309
+ "IS NOT FALSE"
310
+ when "in"
311
+ value = value.split(",")
312
+ "IN"
313
+ when "not_in"
314
+ value = value.split(",")
315
+ "NOT IN"
316
+ else
317
+ "="
318
+ end
319
+
320
+ if value.nil?
321
+ return "( #{column} #{operator} )", value
322
+ else
323
+ return "( #{column} #{operator} (?) )", value
324
+ end
325
+
326
+ end
327
+
328
+ def logical_operator(name)
329
+ case name.to_s
330
+ when "all", "and"
331
+ " AND "
332
+ else
333
+ " OR "
334
+ end
335
+ end
336
+
337
+ end
338
+ end
@@ -0,0 +1,148 @@
1
+ require "active_support/hash_with_indifferent_access"
2
+
3
+ module Fancygrid#:nodoc:
4
+
5
+ class View
6
+
7
+ attr_accessor :grid
8
+ attr_accessor :view
9
+
10
+ def initialize(view)
11
+ load(view)
12
+ end
13
+
14
+ def load(view)
15
+ raise "'view' must be a hash" unless view.is_a?(Hash)
16
+ self.view = ActiveSupport::HashWithIndifferentAccess.new(view)
17
+ end
18
+
19
+ def dump
20
+ return self.view
21
+ end
22
+
23
+ def get_node_view_options(node)
24
+ opts = self.view[:columns] || {}
25
+ opts = opts[node.record_table_name] || {}
26
+ opts[node.name] or {}
27
+ end
28
+
29
+ def get_node_position(node)
30
+ opts = get_node_view_options(node)[:position]
31
+ if opts
32
+ opts.to_i
33
+ else
34
+ -1
35
+ end
36
+ end
37
+
38
+ def get_node_visibility(node)
39
+ opts = get_node_view_options(node)[:visible].to_s
40
+ if %w(true false).include?(opts)
41
+ opts == "true"
42
+ else
43
+ node.visible
44
+ end
45
+ end
46
+
47
+ def get_node_search_value(node)
48
+ hash = get_node_search_conditions(node).first
49
+ hash and hash[:value]
50
+ end
51
+
52
+ def get_node_search_conditions(node)
53
+ opts = self.view[:search] || {}
54
+ opts = self.view[:conditions] || {}
55
+ opts = opts[node.record_table_name] || {}
56
+ opts = opts[node.name] || {}
57
+
58
+ if opts.is_a?(String)
59
+ [{
60
+ :operator => :like,
61
+ :value => opts
62
+ }]
63
+ elsif opts.is_a?(Hash)
64
+ opts.map { |index, value|
65
+ if value.is_a?(Hash)
66
+ {
67
+ :operator => value[:operator].to_s || :like,
68
+ :value => value[:value].to_s
69
+ }
70
+ else
71
+ {
72
+ :operator => :like,
73
+ :value => value.to_s
74
+ }
75
+ end
76
+ }
77
+ elsif opts.is_a?(Array)
78
+ opts
79
+ else
80
+ []
81
+ end
82
+ end
83
+
84
+ def get_search_operator
85
+ opts = self.view[:search] || {}
86
+ opts = self.view[:operator].to_s
87
+ return opts if %w(all any).include?(opts)
88
+ return :any
89
+ end
90
+
91
+ def get_sort_order
92
+ opts = self.view[:order] || {}
93
+ if opts[:table] && opts[:column] && opts[:direction]
94
+ "#{opts[:table]}.#{opts[:column]} #{opts[:direction]}"
95
+ else
96
+ nil
97
+ end
98
+ end
99
+
100
+ def get_pagination_page
101
+ opts = self.view[:pagination] || {}
102
+ opts[:page].to_i
103
+ end
104
+
105
+ def get_pagination_per_page
106
+ opts = self.view[:pagination] || {}
107
+ opts[:per_page].to_i
108
+ end
109
+
110
+ def search_visible
111
+ self.view[:search_visible]
112
+ end
113
+
114
+ end
115
+ end
116
+
117
+ # :fancygrid => {
118
+ # :<grid-name> => {
119
+ # :columns => {
120
+ # :<table> => {
121
+ # :<column> => {
122
+ # :visible => :<value>,
123
+ # :position => :<value>
124
+ # }
125
+ # }
126
+ # },
127
+ # :conditions => {
128
+ # :<table> => {
129
+ # :<column> => {
130
+ # :<number> => {
131
+ # :operator => :<operator>,
132
+ # :value => :<value>
133
+ # }
134
+ # }
135
+ # }
136
+ # },
137
+ # :operator => :<operator>,
138
+ # :order => {
139
+ # :table => :<table>,
140
+ # :column => :<column>,
141
+ # :direction => :<direction>
142
+ # },
143
+ # :pagination => {
144
+ # :page => :<value>,
145
+ # :per_page => :<value>
146
+ # }
147
+ # }
148
+ # }