paginated_table 0.0.1

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