paginated_table 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +132 -0
  3. data/Rakefile +38 -0
  4. data/lib/paginated_table.rb +7 -0
  5. data/lib/paginated_table/controller_helpers.rb +14 -0
  6. data/lib/paginated_table/engine.rb +7 -0
  7. data/lib/paginated_table/page.rb +87 -0
  8. data/lib/paginated_table/railtie.rb +12 -0
  9. data/lib/paginated_table/version.rb +3 -0
  10. data/lib/paginated_table/view_helpers.rb +168 -0
  11. data/lib/tasks/paginated_table_tasks.rake +4 -0
  12. data/test/dummy/README.rdoc +261 -0
  13. data/test/dummy/Rakefile +7 -0
  14. data/test/dummy/app/assets/javascripts/application.js +16 -0
  15. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  16. data/test/dummy/app/controllers/application_controller.rb +3 -0
  17. data/test/dummy/app/controllers/data_controller.rb +27 -0
  18. data/test/dummy/app/helpers/application_helper.rb +2 -0
  19. data/test/dummy/app/views/data/_data.html.erb +7 -0
  20. data/test/dummy/app/views/data/index.html.erb +3 -0
  21. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  22. data/test/dummy/config.ru +4 -0
  23. data/test/dummy/config/application.rb +59 -0
  24. data/test/dummy/config/boot.rb +10 -0
  25. data/test/dummy/config/database.yml +25 -0
  26. data/test/dummy/config/environment.rb +5 -0
  27. data/test/dummy/config/environments/development.rb +37 -0
  28. data/test/dummy/config/environments/production.rb +67 -0
  29. data/test/dummy/config/environments/test.rb +37 -0
  30. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  31. data/test/dummy/config/initializers/inflections.rb +15 -0
  32. data/test/dummy/config/initializers/mime_types.rb +5 -0
  33. data/test/dummy/config/initializers/secret_token.rb +7 -0
  34. data/test/dummy/config/initializers/session_store.rb +8 -0
  35. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  36. data/test/dummy/config/locales/en.yml +5 -0
  37. data/test/dummy/config/routes.rb +59 -0
  38. data/test/dummy/db/test.sqlite3 +0 -0
  39. data/test/dummy/log/test.log +56930 -0
  40. data/test/dummy/public/404.html +26 -0
  41. data/test/dummy/public/422.html +26 -0
  42. data/test/dummy/public/500.html +25 -0
  43. data/test/dummy/public/favicon.ico +0 -0
  44. data/test/dummy/script/rails +6 -0
  45. data/test/dummy/tmp/cache/assets/CB7/360/sprockets%2F8334f01490cb91467dfd76ad25b67780 +0 -0
  46. data/test/dummy/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
  47. data/test/dummy/tmp/cache/assets/D25/810/sprockets%2F4ff88255fbab9775241c5d8c8c6e2088 +0 -0
  48. data/test/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
  49. data/test/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
  50. data/test/dummy/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
  51. data/test/dummy/tmp/cache/assets/D62/6D0/sprockets%2F9b8014b0c5371c3bf5dd4f018a5ec71e +0 -0
  52. data/test/dummy/tmp/cache/assets/D71/1A0/sprockets%2F32c631252aee35736d93e06f3edffd6d +0 -0
  53. data/test/dummy/tmp/cache/assets/DD3/F90/sprockets%2Fc24290dff33aff9c3d2f971f6d8ae04b +0 -0
  54. data/test/dummy/tmp/cache/assets/DD4/950/sprockets%2F09e7f24ef1ff59b4fc390bdf415c60af +0 -0
  55. data/test/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +9809 -0
  56. data/test/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +9809 -0
  57. data/test/dummy/tmp/capybara/capybara-201204041545476374284085.html +12 -0
  58. data/test/integration/paginated_table_integration_test.rb +235 -0
  59. data/test/test_helper.rb +31 -0
  60. data/test/units/controller_helpers_test.rb +43 -0
  61. data/test/units/page_test.rb +192 -0
  62. data/test/units/view_helpers_test.rb +317 -0
  63. metadata +276 -0
@@ -0,0 +1,20 @@
1
+ Copyright 2012 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,132 @@
1
+ # PaginatedTable
2
+
3
+ [![Build Status](https://secure.travis-ci.org/dball/paginated_table.png)](http://travis-ci.org/dball/paginated\_table)
4
+
5
+ PaginatedTable is a Rails plugin that makes rendering paginated, sorted
6
+ HTML tables dead simple.
7
+
8
+ ## Requirements
9
+
10
+ * rails 3.2 (3.1 may work)
11
+ * will_paginate 3.0
12
+ * jquery-rails
13
+
14
+ ## Installation
15
+
16
+ Add `paginated\_table` to your `Gemfile` and `bundle install`.
17
+
18
+ Add the `paginated\_table` javascript to your application's javascript
19
+ requires after `jquery` and `jquery\_ujs`:
20
+
21
+ //= require jquery
22
+ //= require jquery_ujs
23
+ //= require paginated_table
24
+
25
+ ## Usage
26
+
27
+ PaginatedTable mixes helper methods into ActionController::Base and
28
+ ActionView::Base, conveniently named `paginated_table`.
29
+
30
+ ### Controller
31
+
32
+ The `paginated_table` helper is an instance method you can call in an
33
+ action that paginates a table:
34
+
35
+ class ProductsController < ApplicationController
36
+ def index
37
+ paginated_table :products => Product.all
38
+ end
39
+ end
40
+
41
+ This will sort the collection using Arel's order method, paginate
42
+ the given collection using will_paginate, store the
43
+ page in an instance variable with the name of the hash key, e.g.
44
+ `@products`, and if the request is AJAX, renders a partial response.
45
+
46
+ ### View
47
+
48
+ The `paginated_table` helper is an instance method you can call in a
49
+ view that renders a paginated table. To work with the AJAX pagination,
50
+ the call must appear in a partial with the same name as the table's
51
+ instance variable, e.g. `products.html.erb`. Thus, we might have:
52
+
53
+ In `index.html.erb`:
54
+
55
+ <h1>Products</h1>
56
+
57
+ <%= render :partial => 'products' %>
58
+
59
+ and in `products.html.erb`:
60
+
61
+ <%= paginated_table(@products) do |table|
62
+ table.column :name, :sortable => false
63
+ table.column :price do |price|
64
+ format_currency(price)
65
+ end
66
+ table.column :qty, :title => 'Quantity'
67
+ end %>
68
+
69
+ The `div.paginated_table` element on the page will be updated for successful
70
+ AJAX responses.
71
+
72
+ The table DSL provides a column method by which you describe the table.
73
+ The column calls correspond to columns in the rendered table. Columns
74
+ with no block send their name to the records to get their cell values, while
75
+ columns with blocks yield to them the records to get their cell values.
76
+ Columns are sortable by default, but may be rendered unsortable with the
77
+ :sortable option set to false.
78
+
79
+ The table gets a header row with titleized column names, and a
80
+ wrapping header and footer with pagination info and links. The
81
+ pagination links are decorated to be AJAX requests by jquery-rails, the
82
+ results of which overwrite the paginated_table div. The column names
83
+ corresponding to sortable columns are linked to sort the table
84
+ ascending, then descending, restarting at the first page of the
85
+ collection.
86
+
87
+ ### Output
88
+
89
+ <div class="paginated_table">
90
+ <div class="header">
91
+ <div class="info">
92
+ ... will_paginate info ...
93
+ </div>
94
+ <div class="links">
95
+ <div class="pagination">
96
+ ... will_paginate links ...
97
+ </div>
98
+ </div>
99
+ </div>
100
+ <table class="paginated">
101
+ <thead>
102
+ <tr>
103
+ <th class="sortable sorted_asc">...</th>
104
+ <th class="sortable">...</th>
105
+ </tr>
106
+ </thead>
107
+ <tbody>
108
+ <tr>
109
+ <td>...</td>
110
+ <td>...</td>
111
+ </tr>
112
+ </tbody>
113
+ </table>
114
+ <div class="footer">
115
+ <div class="info">
116
+ ... will_paginate info ...
117
+ </div>
118
+ <div class="links">
119
+ <div class="pagination">
120
+ ... will_paginate links ...
121
+ </div>
122
+ </div>
123
+ </div>
124
+ </div>
125
+
126
+ ## TODO
127
+
128
+ * AJAX links should be optional
129
+
130
+ * AJAX busy indicator
131
+
132
+ * AJAX error indicator
@@ -0,0 +1,38 @@
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 = 'PaginatedTable'
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
+
26
+ Bundler::GemHelper.install_tasks
27
+
28
+ require 'rake/testtask'
29
+
30
+ Rake::TestTask.new(:test) do |t|
31
+ t.libs << 'lib'
32
+ t.libs << 'test'
33
+ t.pattern = 'test/**/*_test.rb'
34
+ t.verbose = false
35
+ end
36
+
37
+
38
+ task :default => :test
@@ -0,0 +1,7 @@
1
+ module PaginatedTable
2
+ end
3
+
4
+ require 'will_paginate/railtie'
5
+ require 'paginated_table/railtie'
6
+ require 'paginated_table/engine'
7
+ require 'paginated_table/page'
@@ -0,0 +1,14 @@
1
+ module PaginatedTable
2
+ module ControllerHelpers
3
+ def paginated_table(tables)
4
+ raise ArgumentError if tables.length > 1
5
+ name, collection = tables.first
6
+ page = PageParams.create_page_from_params(params)
7
+ data_page = DataPage.new(collection, page)
8
+ instance_variable_set(:"@#{name}", data_page)
9
+ render :partial => name.to_s, :layout => false if request.xhr?
10
+ end
11
+ end
12
+ end
13
+
14
+ ActionController::Base.send :include, PaginatedTable::ControllerHelpers
@@ -0,0 +1,7 @@
1
+ module PaginatedTable
2
+ class Engine < ::Rails::Engine
3
+ initializer 'static_assets.load_static_assets' do |app|
4
+ app.middleware.use ::ActionDispatch::Static, "#{root}/vendor"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,87 @@
1
+ module PaginatedTable
2
+ class Page
3
+
4
+ SORT_DIRECTIONS = %w(asc desc)
5
+ DEFAULT_PER_PAGE = 10
6
+
7
+ attr_reader :number, :rows, :sort_column, :sort_direction
8
+
9
+ def self.opposite_sort_direction(sort_direction)
10
+ index = SORT_DIRECTIONS.index(sort_direction)
11
+ SORT_DIRECTIONS[index - 1]
12
+ end
13
+
14
+ def initialize(attributes)
15
+ @number = Integer(attributes[:number] || 1)
16
+ raise ArgumentError unless @number > 0
17
+ @rows = Integer(attributes[:rows] || DEFAULT_PER_PAGE)
18
+ raise ArgumentError unless @rows > 0
19
+ @sort_column = attributes[:sort_column] || 'id'
20
+ @sort_direction = attributes[:sort_direction] || 'asc'
21
+ raise ArgumentError unless SORT_DIRECTIONS.include?(@sort_direction)
22
+ end
23
+
24
+ def page_for_number(number)
25
+ Page.new(
26
+ :number => number,
27
+ :rows => rows,
28
+ :sort_column => sort_column,
29
+ :sort_direction => sort_direction
30
+ )
31
+ end
32
+
33
+ def page_for_sort_column(new_sort_column)
34
+ if sort_column == new_sort_column
35
+ new_sort_direction = self.class.opposite_sort_direction(sort_direction)
36
+ else
37
+ new_sort_direction = nil
38
+ end
39
+ Page.new(
40
+ :number => 1,
41
+ :rows => rows,
42
+ :sort_column => new_sort_column,
43
+ :sort_direction => new_sort_direction
44
+ )
45
+ end
46
+
47
+ end
48
+
49
+ class PageParams
50
+ def self.create_page_from_params(params)
51
+ Page.new(
52
+ :number => params[:page],
53
+ :rows => params[:per_page],
54
+ :sort_column => params[:sort_column],
55
+ :sort_direction => params[:sort_direction]
56
+ )
57
+ end
58
+
59
+ def self.to_params(page)
60
+ {
61
+ :page => page.number.to_s,
62
+ :per_page => page.rows.to_s,
63
+ :sort_column => page.sort_column,
64
+ :sort_direction => page.sort_direction
65
+ }
66
+ end
67
+ end
68
+
69
+ class DataPage
70
+ attr_reader :page, :data
71
+
72
+ def initialize(collection, page)
73
+ @page = page
74
+ @data = collection.order(order_clause).paginate(pagination_params)
75
+ end
76
+
77
+ private
78
+
79
+ def order_clause
80
+ "#{@page.sort_column} #{@page.sort_direction}"
81
+ end
82
+
83
+ def pagination_params
84
+ { :page => @page.number, :per_page => @page.rows }
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,12 @@
1
+ module PaginatedTable
2
+ class Railtie < Rails::Railtie
3
+ initializer "paginated_table" do |app|
4
+ ActiveSupport.on_load(:action_view) do
5
+ require 'paginated_table/view_helpers'
6
+ end
7
+ ActiveSupport.on_load :action_controller do
8
+ require 'paginated_table/controller_helpers'
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module PaginatedTable
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,168 @@
1
+ module PaginatedTable
2
+ module ViewHelpers
3
+ def paginated_table(data_page, &block)
4
+ table_description = TableDescription.new(block)
5
+ page = PageParams.create_page_from_params(params)
6
+ link_renderer = LinkRenderer.new(page)
7
+ table_renderer = RendersTable.new(self, table_description, data_page, link_renderer)
8
+ table_renderer.render
9
+ end
10
+ end
11
+
12
+ class TableDescription
13
+ attr_reader :columns
14
+
15
+ def initialize(description_proc = nil)
16
+ @columns = []
17
+ description_proc.call(self) if description_proc
18
+ end
19
+
20
+ def column(*args, &block)
21
+ @columns << Column.new(*args, &block)
22
+ end
23
+
24
+ class Column
25
+ attr_reader :name
26
+
27
+ def initialize(name, options = {}, &block)
28
+ @name = name
29
+ @block = block
30
+ @options = options
31
+ end
32
+
33
+ def render_header
34
+ @options.fetch(:title, @name.to_s.titleize)
35
+ end
36
+
37
+ def render_cell(datum)
38
+ if @block
39
+ @block.call(datum)
40
+ else
41
+ datum.send(@name)
42
+ end
43
+ end
44
+
45
+ def sortable?
46
+ @options.fetch(:sortable, true)
47
+ end
48
+ end
49
+ end
50
+
51
+ class RendersTable
52
+ def initialize(view, description, data_page, link_renderer)
53
+ @view = view
54
+ @description = description
55
+ @data_page = data_page
56
+ @link_renderer = link_renderer
57
+ end
58
+
59
+ def render
60
+ pagination_area = render_pagination_area
61
+ content = pagination_area + render_table + pagination_area
62
+ @view.content_tag('div', content, :class => 'paginated_table')
63
+ end
64
+
65
+ def render_pagination_area
66
+ content = render_pagination_info + render_pagination_links
67
+ @view.content_tag('div', content, :class => 'header')
68
+ end
69
+
70
+ def render_pagination_info
71
+ content = @view.page_entries_info(@data_page.data)
72
+ @view.content_tag('div', content, :class => 'info')
73
+ end
74
+
75
+ def render_pagination_links
76
+ content = @view.will_paginate(@data_page.data, :renderer => @link_renderer)
77
+ @view.content_tag('div', content, :class => 'links')
78
+ end
79
+
80
+ def render_table
81
+ content = render_table_header + render_table_body
82
+ @view.content_tag('table', content, :class => 'paginated')
83
+ end
84
+
85
+ def render_table_header
86
+ @view.content_tag('thead', render_table_header_row)
87
+ end
88
+
89
+ def render_table_header_row
90
+ content = @description.columns.map { |column|
91
+ render_table_header_column(column)
92
+ }.reduce(&:+)
93
+ @view.content_tag('tr', content)
94
+ end
95
+
96
+ def render_table_header_column(column)
97
+ css = []
98
+ if column.sortable?
99
+ css << 'sortable'
100
+ end
101
+ if @data_page.page.sort_column == column.name.to_s
102
+ css << "sorted_#{@data_page.page.sort_direction}"
103
+ end
104
+ attributes = {}
105
+ attributes[:class] = css.join(' ') unless css.empty?
106
+ @view.content_tag('th', render_table_header_column_content(column), attributes)
107
+ end
108
+
109
+ def render_table_header_column_content(column)
110
+ text = column.render_header
111
+ if column.sortable?
112
+ @link_renderer.sort_link(text, column.name.to_s)
113
+ else
114
+ text
115
+ end
116
+ end
117
+
118
+ def render_table_body
119
+ content = @data_page.data.map { |datum|
120
+ render_table_body_row(datum)
121
+ }.reduce(&:+)
122
+ @view.content_tag('tbody', content)
123
+ end
124
+
125
+ def render_table_body_row(datum)
126
+ content = @description.columns.map { |column|
127
+ render_table_body_cell(datum, column)
128
+ }.reduce(&:+)
129
+ @view.content_tag('tr', content)
130
+ end
131
+
132
+ def render_table_body_cell(datum, column)
133
+ @view.content_tag('td', column.render_cell(datum))
134
+ end
135
+ end
136
+
137
+ class LinkRenderer < WillPaginate::ActionView::LinkRenderer
138
+ def initialize(page)
139
+ super()
140
+ @paginated_table_page = page
141
+ end
142
+
143
+ def sort_link(text, sort_on)
144
+ @template.link_to(text, sort_url(sort_on), :remote => true)
145
+ end
146
+
147
+ def tag(name, value, attributes = {})
148
+ if name == :a
149
+ @template.link_to(value, attributes.delete(:href), attributes.merge(:remote => true))
150
+ else
151
+ super
152
+ end
153
+ end
154
+
155
+ private
156
+
157
+ def sort_url(sort_on)
158
+ new_page = @paginated_table_page.page_for_sort_column(sort_on)
159
+ @template.url_for(PageParams.to_params(new_page))
160
+ end
161
+
162
+ def default_url_params
163
+ PageParams.to_params(@paginated_table_page)
164
+ end
165
+ end
166
+ end
167
+
168
+ ActionView::Base.send :include, PaginatedTable::ViewHelpers