magic_grid 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +19 -0
  3. data/Rakefile +44 -0
  4. data/lib/assets/javascripts/magic_grid.js +154 -0
  5. data/lib/locales/en.yml +7 -0
  6. data/lib/magic_grid.rb +7 -0
  7. data/lib/magic_grid/definition.rb +212 -0
  8. data/lib/magic_grid/engine.rb +14 -0
  9. data/lib/magic_grid/helpers.rb +243 -0
  10. data/lib/magic_grid/railtie.rb +9 -0
  11. data/lib/magic_grid/version.rb +3 -0
  12. data/lib/tasks/magic_grid_tasks.rake +4 -0
  13. data/test/dummy/Rakefile +7 -0
  14. data/test/dummy/app/assets/javascripts/application.js +9 -0
  15. data/test/dummy/app/assets/javascripts/posts.js +8 -0
  16. data/test/dummy/app/assets/javascripts/users.js +2 -0
  17. data/test/dummy/app/assets/stylesheets/application.css +7 -0
  18. data/test/dummy/app/assets/stylesheets/posts.css +4 -0
  19. data/test/dummy/app/assets/stylesheets/scaffold.css +56 -0
  20. data/test/dummy/app/assets/stylesheets/users.css +4 -0
  21. data/test/dummy/app/controllers/application_controller.rb +3 -0
  22. data/test/dummy/app/controllers/posts_controller.rb +104 -0
  23. data/test/dummy/app/controllers/users_controller.rb +84 -0
  24. data/test/dummy/app/helpers/application_helper.rb +2 -0
  25. data/test/dummy/app/helpers/posts_helper.rb +2 -0
  26. data/test/dummy/app/helpers/users_helper.rb +2 -0
  27. data/test/dummy/app/models/post.rb +6 -0
  28. data/test/dummy/app/models/user.rb +4 -0
  29. data/test/dummy/app/views/layouts/application.html.erb +19 -0
  30. data/test/dummy/app/views/posts/_form.html.erb +33 -0
  31. data/test/dummy/app/views/posts/by_user.html.erb +18 -0
  32. data/test/dummy/app/views/posts/edit.html.erb +6 -0
  33. data/test/dummy/app/views/posts/index.html.erb +76 -0
  34. data/test/dummy/app/views/posts/new.html.erb +5 -0
  35. data/test/dummy/app/views/posts/show.html.erb +25 -0
  36. data/test/dummy/app/views/users/_form.html.erb +25 -0
  37. data/test/dummy/app/views/users/edit.html.erb +6 -0
  38. data/test/dummy/app/views/users/index.html.erb +42 -0
  39. data/test/dummy/app/views/users/new.html.erb +5 -0
  40. data/test/dummy/app/views/users/show.html.erb +15 -0
  41. data/test/dummy/config.ru +4 -0
  42. data/test/dummy/config/application.rb +59 -0
  43. data/test/dummy/config/boot.rb +10 -0
  44. data/test/dummy/config/database.yml +25 -0
  45. data/test/dummy/config/environment.rb +5 -0
  46. data/test/dummy/config/environments/development.rb +40 -0
  47. data/test/dummy/config/environments/production.rb +67 -0
  48. data/test/dummy/config/environments/test.rb +37 -0
  49. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  50. data/test/dummy/config/initializers/inflections.rb +15 -0
  51. data/test/dummy/config/initializers/mime_types.rb +5 -0
  52. data/test/dummy/config/initializers/secret_token.rb +7 -0
  53. data/test/dummy/config/initializers/session_store.rb +8 -0
  54. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  55. data/test/dummy/config/locales/en.yml +5 -0
  56. data/test/dummy/config/routes.rb +68 -0
  57. data/test/dummy/db/development.sqlite3 +0 -0
  58. data/test/dummy/db/migrate/20111025191809_create_users.rb +10 -0
  59. data/test/dummy/db/migrate/20111025195229_create_posts.rb +13 -0
  60. data/test/dummy/db/schema.rb +34 -0
  61. data/test/dummy/db/test.sqlite3 +0 -0
  62. data/test/dummy/log/development.log +49565 -0
  63. data/test/dummy/log/passenger.3000.log +362 -0
  64. data/test/dummy/log/test.log +77 -0
  65. data/test/dummy/public/404.html +26 -0
  66. data/test/dummy/public/422.html +26 -0
  67. data/test/dummy/public/500.html +26 -0
  68. data/test/dummy/public/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  69. data/test/dummy/public/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  70. data/test/dummy/public/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  71. data/test/dummy/public/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  72. data/test/dummy/public/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  73. data/test/dummy/public/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  74. data/test/dummy/public/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  75. data/test/dummy/public/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  76. data/test/dummy/public/css/smoothness/images/ui-icons_222222_256x240.png +0 -0
  77. data/test/dummy/public/css/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  78. data/test/dummy/public/css/smoothness/images/ui-icons_454545_256x240.png +0 -0
  79. data/test/dummy/public/css/smoothness/images/ui-icons_888888_256x240.png +0 -0
  80. data/test/dummy/public/css/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  81. data/test/dummy/public/css/smoothness/jquery-ui-1.8.18.custom.css +565 -0
  82. data/test/dummy/public/favicon.ico +0 -0
  83. data/test/dummy/script/rails +6 -0
  84. data/test/dummy/test/fixtures/posts.yml +23 -0
  85. data/test/dummy/test/fixtures/users.yml +15 -0
  86. data/test/dummy/test/functional/posts_controller_test.rb +49 -0
  87. data/test/dummy/test/functional/users_controller_test.rb +50 -0
  88. data/test/dummy/test/integration/spider_test.rb +27 -0
  89. data/test/dummy/test/unit/helpers/posts_helper_test.rb +4 -0
  90. data/test/dummy/test/unit/helpers/users_helper_test.rb +4 -0
  91. data/test/dummy/test/unit/post_test.rb +7 -0
  92. data/test/dummy/test/unit/user_test.rb +7 -0
  93. data/test/magic_grid_test.rb +11 -0
  94. data/test/test_helper.rb +17 -0
  95. metadata +333 -0
@@ -0,0 +1,20 @@
1
+ Copyright 2011 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,19 @@
1
+ = MagicGrid
2
+
3
+ Takes a collection and creates a paginated table of using a supplied column definition.
4
+
5
+ == Basic Usage
6
+
7
+ In your +Gemfile+:
8
+
9
+ gem 'magic_grid', :git => 'git://github.com/rmg/magic_grid.git'
10
+
11
+ In your view:
12
+
13
+ <%= magic_grid(@posts, [:title, :author]) %>
14
+
15
+
16
+ == Development
17
+
18
+ To test, <code>(cd test/dummy && rake RAILS_ENV=test db:create db:migrate) && rake test</code>
19
+
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'MagicGrid'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:tests) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+ require 'rspec/core/rake_task'
37
+
38
+ RSpec::Core::RakeTask.new(:spec) do |t|
39
+ t.pattern = 'spec/**/*_spec.rb'
40
+ t.verbose = false
41
+ end
42
+
43
+ task :test => [:spec, :tests]
44
+ task :default => :test
@@ -0,0 +1,154 @@
1
+ // vim: ts=4 et sw=4 sts=4
2
+
3
+ $(function () {
4
+ // Micro-plugin for positioning the cursor
5
+ $.fn.setCursor = $.fn.setCursor || function(pos) {
6
+ return this.each(function() {
7
+ if (this.setSelectionRange) {
8
+ this.focus();
9
+ this.setSelectionRange(pos, pos);
10
+ } else if (this.createTextRange) {
11
+ var range = this.createTextRange();
12
+ range.collapse(true);
13
+ range.moveEnd('character', pos);
14
+ range.moveStart('character', pos);
15
+ range.select();
16
+ }
17
+ });
18
+ };
19
+
20
+ // Micro-plugin for getting the position of the cursor
21
+ $.fn.getCursor = $.fn.getCursor || function () {
22
+ // IE doesn't have selectionStart, and I'm too lazy to implement
23
+ // the IE specific version of this, so we'll just assume
24
+ // the cursor is at the end of the input for this case.
25
+ if (typeof this[0].selectionStart === 'undefined') {
26
+ return this.value.length;
27
+ } else {
28
+ return this[0].selectionStart;
29
+ }
30
+ };
31
+
32
+ // Micro-plugin for collecting all the listener and search inputs
33
+ $.fn.getGridParams = $.fn.getGridParams || function (extras) {
34
+ var listeners = this.data("listeners"),
35
+ params = $.extend({}, {'magic_grid_id' : this.attr('id')}, extras || {}),
36
+ $input, param_key, listened_id;
37
+ for (listened_id in listeners) {
38
+ param_key = listeners[listened_id];
39
+ $input = $("#" + listened_id);
40
+ if ($input.is(":checkbox")) {
41
+ params[param_key] = $input.is(":checked");
42
+ } else if ($input.is("select")) {
43
+ params[param_key] = $input.find("option:selected").val();
44
+ } else {
45
+ params[param_key] = $input.val();
46
+ }
47
+ }
48
+ if (this.is("[data-searcher]")) {
49
+ params[this.attr('id') + '_q'] = $("#" + this.data("searcher")).val();
50
+ }
51
+ return $.extend({}, params);
52
+ }
53
+
54
+ // Default handler for ajax events that displays 'Loading...' in
55
+ // the head of the table.
56
+ $(".magic_grid[data-default-ajax-handler=true]").on("magic_grid:loading", function (e) {
57
+ $(".magic_grid_spinner", this).
58
+ addClass("ui-icon-spinner").
59
+ html("<em>Loading...</em>");
60
+ });
61
+
62
+ $(".magic_grid[data-remote=true]").on( "click", ".pagination a, .sorter a", function (e) {
63
+ var $grid = $(e.delegateTarget),
64
+ url = this.href;
65
+ $grid.trigger("magic_grid:loading");
66
+ $grid.load(url + ' #' + $grid.attr('id') + " > *", function () {
67
+ $grid.trigger("magic_grid:loaded");
68
+ });
69
+ return false;
70
+ });
71
+
72
+ $(".magic_grid[data-searcher]").each( function () {
73
+ var $grid = $(this),
74
+ grid_id = this.id,
75
+ input_id = "#" + $grid.data("searcher"),
76
+ live = $grid.data("live-search"),
77
+ // keydown seems to be the only reliable and portable way to capture
78
+ // things like backspace and delete
79
+ events = (live ? "keydown change search" : "change search"),
80
+ timer = null,
81
+ internal = ($grid.has(input_id).length > 0),
82
+ listener = (internal ? $grid : $(input_id).parent()),
83
+ minLength = $(input_id).data("min-length") || 3;
84
+ listener.on(events, input_id, function (e) {
85
+ var is_manual = (e.type == 'search' ||
86
+ e.type == 'change' ||
87
+ (e.type == 'keydown' && e.which == '13')),
88
+ base_url = $grid.data("current") || "",
89
+ //40 wpm typists == 280ms/keystroke
90
+ //90 wpm typists == 120ms/keystroke
91
+ delay = is_manual ? 1 : (parseInt(live) || 500);
92
+ clearTimeout(timer);
93
+ timer = setTimeout(function () {
94
+ var $input = $(input_id),
95
+ current = $input.data("current"),
96
+ value = $input.val(),
97
+ length = value.length,
98
+ url = base_url + "?",
99
+ relevant = is_manual || (value != current && (length >= minLength || length == 0)),
100
+ pos = $input.getCursor();
101
+ clearTimeout(timer);
102
+ url += $.param($grid.getGridParams());
103
+ if (relevant) {
104
+ $grid.trigger("magic_grid:loading");
105
+ if ($grid.data("remote")) {
106
+ if (internal && live && !is_manual) {
107
+ $(input_id).prop('disabled', true);
108
+ }
109
+ $grid.load(url + ' #' + grid_id + ' > *', function () {
110
+ // Move the cursor back to where it was,
111
+ // less surprising that way.
112
+ // EXCEPT FOR IE, that is
113
+ $grid.trigger("magic_grid:loaded");
114
+ if (internal) {
115
+ $(input_id).setCursor(pos);
116
+ }
117
+ });
118
+ } else {
119
+ window.location = url;
120
+ }
121
+ }
122
+ }, delay);
123
+ });
124
+ });
125
+
126
+ $(".magic_grid[data-listeners]").each( function () {
127
+ var $grid = $(this),
128
+ listeners = $grid.data("listeners"),
129
+ grid_id = this.id
130
+ base_url = $grid.data("current"),
131
+ handler = function (change) {
132
+ var url = base_url + "?" + $.param($grid.getGridParams());
133
+ $grid.trigger("magic_grid:loading");
134
+ if ($grid.data("remote")) {
135
+ $grid.load(url + ' #' + grid_id + " > *", function () {
136
+ $grid.trigger("magic_grid:loaded");
137
+ });
138
+ } else {
139
+ window.location = url;
140
+ }
141
+ };
142
+ for (var k in listeners) {
143
+ if ($("#" + k).hasClass("ready")) {
144
+ $("#" + k).on("change", {"field": listeners[k]}, handler);
145
+ } else {
146
+ $("#" + k).on("ready", {"field": listeners[k]}, function (ready) {
147
+ $(this).on("change", ready.data, handler);
148
+ });
149
+ }
150
+ }
151
+ });
152
+
153
+ $(".magic_grid").trigger("magic_grid:loaded");
154
+ });
@@ -0,0 +1,7 @@
1
+ en:
2
+ magic_grid:
3
+ no_results: no results found
4
+ search:
5
+ label: search
6
+ tooltip: search... <enter>
7
+ button: Search
@@ -0,0 +1,7 @@
1
+ module MagicGrid
2
+ if ::Rails.version < "3.1"
3
+ require 'magic_grid/railtie'
4
+ else
5
+ require 'magic_grid/engine'
6
+ end
7
+ end
@@ -0,0 +1,212 @@
1
+ require 'will_paginate/view_helpers/action_view'
2
+
3
+ module MagicGrid
4
+ class Definition
5
+ include WillPaginate::ActionView
6
+ attr_accessor :columns, :collection, :magic_id, :options, :params, :accepted,
7
+ :current_sort_col, :current_order, :default_order
8
+
9
+ DEFAULTS = {
10
+ :class => [],
11
+ :top_pager => false,
12
+ :bottom_pager => true,
13
+ :remote => false,
14
+ :per_page => 30,
15
+ :searchable => false,
16
+ :search_method => :search,
17
+ :min_search_length => 3,
18
+ :id => false,
19
+ :searcher => false,
20
+ :needs_searcher => false,
21
+ :live_search => false,
22
+ :current_search => nil,
23
+ :listeners => {},
24
+ :listener_handler => nil,
25
+ :default_col => 0,
26
+ :default_order => :asc,
27
+ :empty_header => false,
28
+ :empty_footer => false,
29
+ :post_filter => false,
30
+ :collection_post_filter? => true,
31
+ :default_ajax_handler => true,
32
+ :search_button => false,
33
+ :searcher_size => nil,
34
+ }
35
+
36
+ def self.runtime_defaults
37
+ # run these lazily to catch any late I18n path changes
38
+ DEFAULTS.merge(
39
+ :if_empty => I18n.t("magic_grid.no_results").capitalize, # "No results found."
40
+ :searcher_label => I18n.t("magic_grid.search.label").capitalize + ': ', # "Search: "
41
+ :searcher_tooltip =>I18n.t("magic_grid.search.tooltip"), # "type.. + <return>"
42
+ :searcher_button =>I18n.t("magic_grid.search.button").capitalize, # "Search"
43
+ )
44
+ end
45
+
46
+ def initialize(cols_or_opts, collection = nil, controller = nil, opts = {})
47
+ if cols_or_opts.is_a? Hash
48
+ @options = self.class.runtime_defaults.merge(cols_or_opts.reject {|k| k == :cols})
49
+ @columns = cols_or_opts.fetch(:cols, [])
50
+ elsif cols_or_opts.is_a? Array
51
+ @options = self.class.runtime_defaults.merge opts
52
+ @columns = cols_or_opts
53
+ else
54
+ raise "I have no idea what that is, but it's not a Hash or an Array"
55
+ end
56
+ @default_order = @options[:default_order]
57
+ @params = controller.try(:params) || {}
58
+ @collection = collection
59
+ begin
60
+ #if @collection.respond_to? :table
61
+ table_name = @collection.quoted_table_name
62
+ table_columns = @collection.table.columns.map {|c| c.name}
63
+ rescue
64
+ Rails.logger.debug "Given collection doesn't respond to :table well"
65
+ table_name = nil
66
+ table_columns = @columns.each_index.to_a
67
+ end
68
+ i = 0
69
+ hash = []
70
+ @columns.map! do |c|
71
+ if c.is_a? Symbol
72
+ c = {:col => c}
73
+ elsif c.is_a? String
74
+ c = {:label => c}
75
+ end
76
+ c[:id] = i
77
+ i += 1
78
+ if c.key?(:col) and c[:col].is_a?(Symbol) and table_columns.include?(c[:col])
79
+ c[:sql] = "#{table_name}.#{@collection.connection.quote_column_name(c[:col].to_s)}" unless c.key?(:sql)
80
+ end
81
+ c[:label] = c[:col].to_s.titleize if not c.key? :label
82
+ hash << c[:label]
83
+ c
84
+ end
85
+ if @options[:id]
86
+ @magic_id = @options[:id]
87
+ else
88
+ @magic_id = hash.join.hash.abs.to_s(36)
89
+ @magic_id << @collection.to_sql.hash.abs.to_s(36) if @collection.respond_to? :to_sql
90
+ end
91
+ @current_sort_col = sort_col_i = param(:col, @options[:default_col]).to_i
92
+ if @collection.respond_to?(:order) and @columns.count > sort_col_i and @columns[sort_col_i].has_key?(:sql)
93
+ sort_col = @columns[sort_col_i][:sql]
94
+ @current_order = order(param(:order, @default_order))
95
+ sort_dir = order_sql(@current_order)
96
+ @collection = @collection.order("#{sort_col} #{sort_dir}")
97
+ else
98
+ Rails.logger.debug "#{self.class.name}: Ignoring sorting on non-AR collection"
99
+ end
100
+
101
+ @options[:searchable] = [] if @options[:searchable] and not @options[:searchable].kind_of? Array
102
+
103
+ @accepted = [:action, :controller, param_key(:page)]
104
+ @accepted << param_key(:q) if @options[:searchable]
105
+ @accepted += @options[:listeners].values
106
+
107
+ if @collection.respond_to?(:where) or @options[:listener_handler].respond_to?(:call)
108
+ if @options[:listener_handler].respond_to? :call
109
+ @collection = @options[:listener_handler].call(@collection)
110
+ else
111
+ @options[:listeners].each_pair do |key, value|
112
+ if @params[value] and not @params[value].to_s.empty?
113
+ @collection = @collection.where(key => @params[value])
114
+ end
115
+ end
116
+ end
117
+ else
118
+ unless @options[:listeners].empty?
119
+ Rails.logger.warn "#{self.class.name}: Ignoring listener on dumb collection"
120
+ @options[:listeners] = {}
121
+ end
122
+ end
123
+ @options[:current_search] ||= param(:q)
124
+ if (@collection.respond_to?(:where) or
125
+ (@options[:search_method] and @collection.respond_to?(@options[:search_method])))
126
+ if param(:q) and not param(:q).empty? and @options[:searchable]
127
+ orig_collection = @collection
128
+ begin
129
+ @collection = @collection.__send__(@options[:search_method], param(:q))
130
+ rescue
131
+ Rails.logger.debug "Given collection doesn't respond to #{@options[:search_method]} well"
132
+ @collection = orig_collection
133
+ search_cols = @options[:searchable].map do |searchable|
134
+ case searchable
135
+ when Symbol
136
+ known = @columns.find {|col| col[:col] == searchable}
137
+ if known and known.key?(:sql)
138
+ known[:sql]
139
+ else
140
+ "#{table_name}.#{@collection.connection.quote_column_name(searchable.to_s)}"
141
+ end
142
+ when Integer
143
+ @columns[searchable][:sql]
144
+ when String
145
+ searchable
146
+ else
147
+ raise "Searchable must be identifiable"
148
+ end
149
+ end
150
+ unless search_cols.empty?
151
+ begin
152
+ clauses = search_cols.map {|c| c << " LIKE :search" }.join(" OR ")
153
+ @collection = @collection.where(clauses, {:search => "%#{param(:q)}%"})
154
+ rescue
155
+ Rails.logger.debug "Given collection doesn't respond to :where well"
156
+ @collection = orig_collection
157
+ end
158
+ end
159
+ end
160
+ end
161
+ else
162
+ if @options[:searchable] or param(:q)
163
+ Rails.logger.warn "#{self.class.name}: Ignoring searchable fields on non-AR collection"
164
+ end
165
+ @options[:searchable] = false
166
+ end
167
+ if not @options[:searcher] and @options[:searchable]
168
+ @options[:needs_searcher] = true
169
+ @options[:searcher] = param_key(:searcher)
170
+ end
171
+ # Do collection filter first, may convert from AR to Array
172
+ if @options[:collection_post_filter?] and @collection.respond_to?(:post_filter)
173
+ @collection = @collection.post_filter(controller)
174
+ end
175
+ if @options[:post_filter] and @options[:post_filter].respond_to?(:call)
176
+ @collection = @options[:post_filter].call(@collection)
177
+ end
178
+ # Paginate at the very end, after all sorting, filtering, etc..
179
+ if @options[:per_page]
180
+ @collection = @collection.paginate(:page => param(:page, 1),
181
+ :per_page => @options[:per_page])
182
+ end
183
+ end
184
+
185
+ def param_key(key)
186
+ "#{@magic_id}_#{key}".to_sym
187
+ end
188
+
189
+ def param(key, default=nil)
190
+ @params.fetch(param_key(key), default)
191
+ end
192
+
193
+ def base_params
194
+ @params.select { |k,_| accepted.include? k.to_sym }.merge :magic_grid_id => @magic_id
195
+ end
196
+
197
+ def order(something)
198
+ case something
199
+ when 1, "1", :desc, :DESC, "desc", "DESC"
200
+ 1
201
+ #when 0, "0", :asc, :ASC, "asc", "ASC"
202
+ # 0
203
+ else
204
+ 0
205
+ end
206
+ end
207
+
208
+ def order_sql(something)
209
+ ["ASC", "DESC"][order(something)]
210
+ end
211
+ end
212
+ end