magic_grid 0.9.2

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 (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