fancygrid 1.0.0

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