sortable_table 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +7 -0
- data/Rakefile +38 -0
- data/TODO +0 -0
- data/VERSION +1 -0
- data/example/controllers/cablecar/users_controller.rb +49 -0
- data/example/models/cablecar/contact_info.rb +9 -0
- data/example/models/cablecar/user.rb +7 -0
- data/example/views/cablecar/users/index.html.erb +1 -0
- data/images/arrow_down.gif +0 -0
- data/images/arrow_up.gif +0 -0
- data/images/logo_opaque.png +0 -0
- data/init.rb +33 -0
- data/install.rb +1 -0
- data/lib/sortable.rb +299 -0
- data/lib/sortable_helper.rb +81 -0
- data/sortable_table.gemspec +63 -0
- data/test/example_test_routing.rb +16 -0
- data/test/test_helper.rb +15 -0
- data/test/users_controller_test.rb +346 -0
- data/views/sortable/_table.html.erb +74 -0
- data/views/sortable/index.erb +1 -0
- metadata +78 -0
data/README
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Sortable
|
2
|
+
========
|
3
|
+
|
4
|
+
See my blog posting on sortable here: http://javathehutt.blogspot.com/2009/06/mo-simple-sortable-tables-in-rails.html
|
5
|
+
|
6
|
+
For more usage information see sortable.rb and sortable_helper.rb. Also see the example app in the example dir as
|
7
|
+
well as the tests that go along with it.
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the sortable plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation for the sortable plugin.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'Sortable'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
23
|
+
|
24
|
+
begin
|
25
|
+
require 'jeweler'
|
26
|
+
Jeweler::Tasks.new do |gemspec|
|
27
|
+
gemspec.name = "sortable_table"
|
28
|
+
gemspec.summary = "Rails plugin to produce a sortable, paginated, searchable table for any model"
|
29
|
+
gemspec.description = "Rails plugin to produce a sortable, paginated, searchable table for any model"
|
30
|
+
gemspec.email = ""
|
31
|
+
gemspec.homepage = "http://github.com/kovacs/sortable"
|
32
|
+
gemspec.authors = ["Michael Kovacs"]
|
33
|
+
end
|
34
|
+
Jeweler::GemcutterTasks.new
|
35
|
+
rescue LoadError
|
36
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
37
|
+
end
|
38
|
+
|
data/TODO
ADDED
File without changes
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Cablecar
|
2
|
+
class UsersController < ActionController::Base
|
3
|
+
|
4
|
+
# By default this will create an index action that uses the default sortable table partial to render the list of objects
|
5
|
+
sortable_table Cablecar::User
|
6
|
+
|
7
|
+
# The following index action demonstrates various ways that you can fetch and display your objects in a paginated, sortable,
|
8
|
+
# searchable table. Each clause in the elsif chain below could be its own index action on its own but all are included here
|
9
|
+
# for demo and testing purposes. Each example is triggered by passing the appropriate param value to trigger the desired
|
10
|
+
# clause to demo.
|
11
|
+
def index
|
12
|
+
if params[:filter_example]
|
13
|
+
# This demonstrates the ability to add additional conditions to fetching the list of objects. This will filter
|
14
|
+
# the results to the list of active users
|
15
|
+
if params[:active]
|
16
|
+
conditions = 'cablecar_users.status = "active"'
|
17
|
+
end
|
18
|
+
get_sorted_objects(params, :conditions => conditions)
|
19
|
+
elsif params[:complex_example]
|
20
|
+
# This example demonstrates a more complicated example that displays the flexibility of creating the sortable table.
|
21
|
+
# Here we are showing an attribute on a related object as a sortable column in the table, namely the contact_info name
|
22
|
+
# column. We're also restricting the visible columns to 3 columns as well as defining the sort order for each column
|
23
|
+
# as well as the default sort column. We're also specifying what columns should be used when a search is performed.
|
24
|
+
# Finally we're also setting the number of results to show per page.
|
25
|
+
get_sorted_objects(params, :include_relations => [:contact_info],
|
26
|
+
:table_headings => [['Username', 'username'], ['Status', 'status'], ['Name', 'name']],
|
27
|
+
:sort_map => {:username => [['cablecar_users.username', 'DESC'],
|
28
|
+
['cablecar_users.status', 'DESC']],
|
29
|
+
:status => ['cablecar_users.status', 'ASC'],
|
30
|
+
:name => ['cablecar_contact_infos.name', 'DESC']},
|
31
|
+
:default_sort => ['name', 'ASC'],
|
32
|
+
:search_array => ['cablecar_users.username', 'cablecar_contact_infos.name'],
|
33
|
+
:per_page => 15)
|
34
|
+
elsif params[:no_pagination]
|
35
|
+
@hide_pagination = true
|
36
|
+
get_sorted_objects(params)
|
37
|
+
else
|
38
|
+
if params[:use_default]
|
39
|
+
super
|
40
|
+
else
|
41
|
+
# If you wish to override how the objects are fetched this is the simplest way to do so
|
42
|
+
# The key is that you simply need to call get_sorted_objects with your params and any optional overrides for
|
43
|
+
# sortable defaults. See sortable.rb for more details on overrides.
|
44
|
+
get_sorted_objects(params)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class Cablecar::ContactInfo < ActiveRecord::Base
|
2
|
+
set_table_name 'cablecar_contact_infos'
|
3
|
+
has_one :user, :class_name => Cablecar::User.to_s
|
4
|
+
end
|
5
|
+
|
6
|
+
# to solve circular dependency
|
7
|
+
Cablecar::User.class_eval do
|
8
|
+
belongs_to :contact_info, :class_name => Cablecar::ContactInfo.to_s
|
9
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= sortable_table :paginate => !@hide_pagination%>
|
Binary file
|
data/images/arrow_up.gif
ADDED
Binary file
|
Binary file
|
data/init.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'sortable'
|
2
|
+
ActionController::Base.send(:include, Sortable)
|
3
|
+
|
4
|
+
VIEW_PATH = File.join(RAILS_ROOT, 'vendor', 'plugins', 'sortable', 'views')
|
5
|
+
ActionController::Base.append_view_path(VIEW_PATH) unless ActionController::Base.view_paths.include?(VIEW_PATH)
|
6
|
+
|
7
|
+
require 'sortable_helper'
|
8
|
+
ActionView::Base.send(:include, SortableHelper)
|
9
|
+
|
10
|
+
if RAILS_ENV == 'test'
|
11
|
+
# only load the example files/routes in development mode
|
12
|
+
# TODO easy way to load all controller/model paths instead? Don't know off the top of my head but am sure it's easy
|
13
|
+
require File.join(File.dirname(__FILE__), 'example', 'controllers', 'cablecar', 'users_controller')
|
14
|
+
require File.join(File.dirname(__FILE__), 'example', 'models', 'cablecar', 'user')
|
15
|
+
require File.join(File.dirname(__FILE__), 'example', 'models', 'cablecar', 'contact_info')
|
16
|
+
|
17
|
+
# install the routes
|
18
|
+
require File.join(File.dirname(__FILE__), 'test', 'example_test_routing')
|
19
|
+
|
20
|
+
# install the example app view path
|
21
|
+
VIEW_PATH = File.join(RAILS_ROOT, 'vendor', 'plugins', 'sortable', 'example', 'views')
|
22
|
+
ActionController::Base.append_view_path(VIEW_PATH) unless ActionController::Base.view_paths.include?(VIEW_PATH)
|
23
|
+
end
|
24
|
+
|
25
|
+
COPY_IMAGES = true
|
26
|
+
|
27
|
+
if COPY_IMAGES
|
28
|
+
require "ftools"
|
29
|
+
|
30
|
+
File.copy(File.join(File.dirname(__FILE__), 'images', 'logo_opaque.png'), File.join(RAILS_ROOT, 'public', 'images'))
|
31
|
+
File.copy(File.join(File.dirname(__FILE__), 'images', 'arrow_up.gif'), File.join(RAILS_ROOT, 'public', 'images'))
|
32
|
+
File.copy(File.join(File.dirname(__FILE__), 'images', 'arrow_down.gif'), File.join(RAILS_ROOT, 'public', 'images'))
|
33
|
+
end
|
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
data/lib/sortable.rb
ADDED
@@ -0,0 +1,299 @@
|
|
1
|
+
require 'generator'
|
2
|
+
|
3
|
+
module Sortable
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
# Class method to setup a controller to create a sortable table.
|
10
|
+
#
|
11
|
+
# usage: sortable_table class_to_tabularize, optional_params
|
12
|
+
#
|
13
|
+
# simplest example possible:
|
14
|
+
#
|
15
|
+
# In your controller...
|
16
|
+
#
|
17
|
+
# sortable_table User
|
18
|
+
#
|
19
|
+
# This will create a sortable table over all your objects and display all columns.
|
20
|
+
# The simplest way to trim down the columns to display is to pass in a parameter specifying which columns
|
21
|
+
# on your object you'd like to display:
|
22
|
+
#
|
23
|
+
# sortable_table User, :display_columns => ['id', 'email', 'name', 'state', 'created_at']
|
24
|
+
#
|
25
|
+
# This will show the same sortable, searchable, paginated table with only these columns
|
26
|
+
#
|
27
|
+
# If you need a bit more control over how the objects are fetchd and displayed this is
|
28
|
+
# the next simplest example:
|
29
|
+
#
|
30
|
+
# Override the index action in your controller:
|
31
|
+
#
|
32
|
+
# def index
|
33
|
+
# get_sorted_objects(params)
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# In your index action template (within the same controller) put in a helper call to show a sortable table. You can
|
37
|
+
# create your own table partial to be used to display the objects. See the sortable/views/sortable/_table.html.erb
|
38
|
+
# (which is the default template used by the plugin) for an example.
|
39
|
+
#
|
40
|
+
# <%= sortable_table %>
|
41
|
+
#
|
42
|
+
# The view method will automatically generate a paginated, sortable table for the class type declared in the controller
|
43
|
+
#
|
44
|
+
# optional_params:
|
45
|
+
#
|
46
|
+
# :per_page - Number of records to show per page. Default is 10
|
47
|
+
#
|
48
|
+
# The next section deals with how to change what's displayed in the table. The first and simplest option is :display_columns.
|
49
|
+
# The more flexible option is to use :table_headings and :sort_map. First we'll go over the simpler option.
|
50
|
+
#
|
51
|
+
# :display_columns - Specifies which columns that you'd like to display in the table.
|
52
|
+
#
|
53
|
+
# For more flexibility you can use :table_headings and :sort_map. You would most likely use this when you want to display
|
54
|
+
# attributes from more than one object in the same table or if you need more flexibility with regards to sort rules.
|
55
|
+
# :table_headings - The table heading label and sort key. Default is all the column names for the given class
|
56
|
+
# :sort_map - The mapping between the sort key and the table.column to sort on. Default is all the columns for the given class
|
57
|
+
# :include_relations - Relations to include if you choose to display a column in a related object's table
|
58
|
+
#
|
59
|
+
# :default_sort - The default sorting column and direction when displaying the table without any sort params. Default is 'id DESC'
|
60
|
+
#
|
61
|
+
# Note: You *must* override both :table_headings and :sort_map if you do choose to override so that
|
62
|
+
# the contents of the column headings match up with the contents of the sort_map they associate with.
|
63
|
+
# Also if you override :default_sort you'll need to change the :table_headings and :sort_map if the new :default_sort
|
64
|
+
# column doesn't currently reside within the :table_heading and :sort_map collections
|
65
|
+
#
|
66
|
+
# Example of modifying :table_headings or :sort_map :
|
67
|
+
# :table_headings => [['Name', 'name'], ['Status', 'status']]
|
68
|
+
# :sort_map => {'name' => ['users.name'],
|
69
|
+
# 'status' => ['users.status']}
|
70
|
+
#
|
71
|
+
# Note that both 'name' and 'status' are sort keys that map to both the table heading label and the
|
72
|
+
# database table.column combination that the heading refers to.
|
73
|
+
#
|
74
|
+
# Also note that :default_sort now needs to change as well since the table no longer contains the :default_sort
|
75
|
+
# column of 'id':
|
76
|
+
#
|
77
|
+
# :default_sort => ['name', 'DESC']
|
78
|
+
#
|
79
|
+
# Example of modifying :table_headings to include a column from a related object:
|
80
|
+
# :table_headings => [['Name', 'name'], ['Status', 'status'], ['Role', 'role']]
|
81
|
+
# :sort_map => {'name' => ['users.name'],
|
82
|
+
# 'status' => ['users.status'],
|
83
|
+
# 'role' => ['roles.role']}
|
84
|
+
#
|
85
|
+
# Note that we've now added 'roles.role' to the list of columns to display and sort on. In order to
|
86
|
+
# make the find work properly we also need to include the related object, so we pass in the following param:
|
87
|
+
# :include_relations => [:role]
|
88
|
+
#
|
89
|
+
# Perhaps we want to sort by role ascending by default as well. We'd pass the param value:
|
90
|
+
# :default_sort => ['role', 'ASC']
|
91
|
+
#
|
92
|
+
# and the table is now sortable by a related object's column and is the default sort value for the table.
|
93
|
+
#
|
94
|
+
# Search. You can specify what columns are searchable on your objects as follows:
|
95
|
+
# :search_array => ['cablecar_users.username', 'cablecar_users.name']
|
96
|
+
#
|
97
|
+
# Now search queries will only search username and name for users. By default search is enabled for all columns
|
98
|
+
# that are being displayed in the table. This allows you to expand or constrain those values.
|
99
|
+
#
|
100
|
+
def sortable_table(klass, options={})
|
101
|
+
@@sortable_table_options ||={}
|
102
|
+
|
103
|
+
sort_map = HashWithIndifferentAccess.new
|
104
|
+
|
105
|
+
if options[:table_headings] &&
|
106
|
+
options[:sort_map]
|
107
|
+
table_headings = options[:table_headings]
|
108
|
+
sort_map.merge!(options[:sort_map])
|
109
|
+
else
|
110
|
+
display_columns = options[:display_columns].blank? ? klass.column_names : options[:display_columns]
|
111
|
+
table_headings = []
|
112
|
+
klass.column_names.each do |att|
|
113
|
+
if display_columns.include?(att)
|
114
|
+
table_headings << [att.humanize, att]
|
115
|
+
sort_map[att] = ["#{klass.table_name}.#{att}", 'DESC']
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
default_sort = options[:default_sort].nil? ? ['id', 'DESC'] : options[:default_sort]
|
121
|
+
per_page = options[:per_page].nil? ? 10 : options[:per_page]
|
122
|
+
include_relations = options[:include_relations].nil? ? [] : options[:include_relations]
|
123
|
+
|
124
|
+
search_array = options[:search_array].nil? ? sort_map.values.collect {|v| v[0]} : options[:search_array]
|
125
|
+
|
126
|
+
@@sortable_table_options[controller_name] = {:class => klass,
|
127
|
+
:table_headings => table_headings,
|
128
|
+
:default_sort => default_sort,
|
129
|
+
:sort_map => sort_map,
|
130
|
+
:search_array => search_array,
|
131
|
+
:per_page => per_page,
|
132
|
+
:include_relations => include_relations}
|
133
|
+
|
134
|
+
module_eval do
|
135
|
+
include Sortable::InstanceMethods
|
136
|
+
def sortable_class
|
137
|
+
@@sortable_table_options[controller_name][:class]
|
138
|
+
end
|
139
|
+
|
140
|
+
def sortable_table_headings
|
141
|
+
@@sortable_table_options[controller_name][:table_headings]
|
142
|
+
end
|
143
|
+
|
144
|
+
def sortable_default_sort
|
145
|
+
@@sortable_table_options[controller_name][:default_sort]
|
146
|
+
end
|
147
|
+
|
148
|
+
def sortable_sort_map
|
149
|
+
@@sortable_table_options[controller_name][:sort_map]
|
150
|
+
end
|
151
|
+
|
152
|
+
def sortable_search_array
|
153
|
+
@@sortable_table_options[controller_name][:search_array]
|
154
|
+
end
|
155
|
+
|
156
|
+
def sortable_per_page
|
157
|
+
@@sortable_table_options[controller_name][:per_page]
|
158
|
+
end
|
159
|
+
|
160
|
+
def sortable_include_relations
|
161
|
+
@@sortable_table_options[controller_name][:include_relations]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
module InstanceMethods
|
169
|
+
# default impl for listing a collection of objects. Override this action in your controller to fetch objects
|
170
|
+
# differently and/or to render a different template.
|
171
|
+
def index
|
172
|
+
get_sorted_objects(params)
|
173
|
+
render :template => 'sortable/index'
|
174
|
+
end
|
175
|
+
|
176
|
+
# Users can also pass in optional conditions that are used by the finder method call. For example if only wanted to
|
177
|
+
# show the items that had a certain status value you could pass in a condition 'mytable.status == 300' for example
|
178
|
+
# as the conditions parameter and when the finder is called the sortable table will only display objects that meet those
|
179
|
+
# conditions. Additionally you can paginate and sort the objects that are returned and apply the conditions to them.
|
180
|
+
def get_sorted_objects(params, options={})
|
181
|
+
objects = options[:objects].nil? ? sortable_class : options[:objects]
|
182
|
+
include_rel = options[:include_relations].nil? ? sortable_include_relations : options[:include_relations]
|
183
|
+
@headings = options[:table_headings].nil? ? sortable_table_headings : options[:table_headings]
|
184
|
+
sort_map = options[:sort_map].nil? ? sortable_sort_map : HashWithIndifferentAccess.new(options[:sort_map])
|
185
|
+
default_sort = options[:default_sort].nil? ? sortable_default_sort : options[:default_sort]
|
186
|
+
conditions = options[:conditions].nil? ? '' : options[:conditions]
|
187
|
+
search_array = options[:search_array].nil? ? sortable_search_array : options[:search_array]
|
188
|
+
|
189
|
+
conditions = process_search(params, conditions, search_array)
|
190
|
+
items_per_page = options[:per_page].nil? ? sortable_per_page : options[:per_page]
|
191
|
+
|
192
|
+
@sort_map = sort_map
|
193
|
+
sort = process_sort(params, sort_map, default_sort)
|
194
|
+
page = params[:page]
|
195
|
+
page ||= 1
|
196
|
+
# fetch the objects, paginated and sorted as desired along with any extra filtering conditions
|
197
|
+
get_paginated_objects(objects, sort, include_rel, conditions, page, items_per_page)
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
def get_paginated_objects(objects, sort, include_rel, conditions, page, items_per_page)
|
202
|
+
@objects = objects.paginate(:include => include_rel,
|
203
|
+
:order => sort,
|
204
|
+
:conditions => conditions,
|
205
|
+
:page => page,
|
206
|
+
:per_page => items_per_page)
|
207
|
+
end
|
208
|
+
|
209
|
+
def process_search(params, conditions, search_array)
|
210
|
+
if !params[:q].blank?
|
211
|
+
columns_to_search = ''
|
212
|
+
values = Array.new
|
213
|
+
g = Generator.new(search_array)
|
214
|
+
g.each do |col|
|
215
|
+
columns_to_search += col + ' LIKE ? '
|
216
|
+
columns_to_search += 'OR ' unless g.end?
|
217
|
+
values<< "%#{params[:q]}%"
|
218
|
+
end
|
219
|
+
conditions += ' and' if !conditions.blank?
|
220
|
+
conditions = [conditions + ' (' + columns_to_search + ')'] + values
|
221
|
+
end
|
222
|
+
return conditions
|
223
|
+
end
|
224
|
+
|
225
|
+
def value_provided?(params, name)
|
226
|
+
!params[name].nil? && !params[name].empty?
|
227
|
+
end
|
228
|
+
|
229
|
+
def process_sort(params, sort_map, default_sort)
|
230
|
+
if params['sort']
|
231
|
+
sort = process_sort_param(params['sort'], sort_map)
|
232
|
+
else
|
233
|
+
# fetch the table.column from the sortmap for the given sort key and append the sort direction
|
234
|
+
sort = sort_map[default_sort[0]][0] + ' ' + default_sort[1]
|
235
|
+
|
236
|
+
# These variables are used in the sort_link_helper and sort_td_class_helper to build the column link headings
|
237
|
+
# and create the proper CSS class for the column heading for the case where there is no sort param.
|
238
|
+
@default_sort = default_sort[0]
|
239
|
+
if default_sort[1] && default_sort[1] == 'DESC'
|
240
|
+
@default_sort_key = default_sort[0] + '_reverse'
|
241
|
+
@sortclass = 'sortup'
|
242
|
+
else
|
243
|
+
@default_sort_key = default_sort[0]
|
244
|
+
@sortclass = 'sortdown'
|
245
|
+
end
|
246
|
+
end
|
247
|
+
return sort
|
248
|
+
end
|
249
|
+
|
250
|
+
def process_sort_param(sort, sort_map)
|
251
|
+
mapKey = get_sort_key(sort)
|
252
|
+
|
253
|
+
if sort_map[mapKey].nil?
|
254
|
+
raise Exception.new("Invalid sort parameter passed #{sort}")
|
255
|
+
end
|
256
|
+
|
257
|
+
result = ''
|
258
|
+
sort_array = sort_map[mapKey]
|
259
|
+
# this adds support for more than one sort criteria for a given column
|
260
|
+
# for example, status DESC, created_at ASC
|
261
|
+
if sort_array[0].class == Array
|
262
|
+
g = Generator.new(sort_array)
|
263
|
+
g.each do |sort_value|
|
264
|
+
result = get_sort_direction(sort, sort_value)
|
265
|
+
result += ', ' unless g.end?
|
266
|
+
end
|
267
|
+
else
|
268
|
+
result = get_sort_direction(sort, sort_array)
|
269
|
+
end
|
270
|
+
|
271
|
+
return result
|
272
|
+
end
|
273
|
+
|
274
|
+
def get_sort_direction(sort, sort_value)
|
275
|
+
result = ''
|
276
|
+
column = sort_value[0]
|
277
|
+
direction = sort_value[1]
|
278
|
+
if /_reverse$/.match(sort)
|
279
|
+
if direction == 'DESC'
|
280
|
+
direction = 'ASC'
|
281
|
+
else
|
282
|
+
direction = 'DESC'
|
283
|
+
end
|
284
|
+
end
|
285
|
+
result += column + ' ' + direction
|
286
|
+
end
|
287
|
+
|
288
|
+
def get_sort_key(sort)
|
289
|
+
i = sort.index('_reverse')
|
290
|
+
if i
|
291
|
+
mapKey = sort[0, i]
|
292
|
+
else
|
293
|
+
mapKey = sort
|
294
|
+
end
|
295
|
+
return mapKey
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module SortableHelper
|
2
|
+
# Helper method to generate a sortable table in the view
|
3
|
+
#
|
4
|
+
# usage: <%= sortable_table(optional_params) %>
|
5
|
+
#
|
6
|
+
# optional_params:
|
7
|
+
#
|
8
|
+
# :paginate - Whether or not to display pagination links for the table. Default is true.
|
9
|
+
# :partial - Name of the partial containing the table to display. Default is the table partial in the sortable
|
10
|
+
# plugin: 'sortable/table'
|
11
|
+
#
|
12
|
+
# If you choose to create your own partial in place of the one distributed with the sortable plugin
|
13
|
+
# your partial has the following available to it for generating the table:
|
14
|
+
# @headings contains the table heading values
|
15
|
+
# @objects contains the collection of objects to be displayed in the table
|
16
|
+
#
|
17
|
+
# :search - Whether or not to include a search field for the table. Default is true.
|
18
|
+
#
|
19
|
+
def sortable_table(options={})
|
20
|
+
paginate = options[:paginate].nil? ? true : options[:paginate]
|
21
|
+
partial = options[:partial].nil? ? 'sortable/table' : options[:partial]
|
22
|
+
search = options[:search].nil? ? true : options[:search]
|
23
|
+
|
24
|
+
result = render(:partial => partial, :locals => {:search => search})
|
25
|
+
result += will_paginate(@objects).to_s if paginate
|
26
|
+
result
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def sort_td_class_helper(param)
|
31
|
+
result = 'sortup' if params[:sort] == param
|
32
|
+
result = 'sortdown' if params[:sort] == param + "_reverse"
|
33
|
+
result = @sortclass if @default_sort && @default_sort == param
|
34
|
+
return result
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def sort_link_helper(action, text, param, params, extra_params={})
|
39
|
+
options = build_url_params(action, param, params, extra_params)
|
40
|
+
html_options = {:title => "Sort by this field"}
|
41
|
+
|
42
|
+
link_to(text, options, html_options)
|
43
|
+
end
|
44
|
+
|
45
|
+
def build_url_params(action, param, params, extra_params={})
|
46
|
+
key = param
|
47
|
+
if @default_sort_key && @default_sort == param
|
48
|
+
key = @default_sort_key
|
49
|
+
else
|
50
|
+
key += "_reverse" if params[:sort] == param
|
51
|
+
end
|
52
|
+
params = {:sort => key,
|
53
|
+
:page => nil, # when sorting we start over on page 1
|
54
|
+
:q => params[:q]}
|
55
|
+
params.merge!(extra_params)
|
56
|
+
|
57
|
+
return {:action => action, :params => params}
|
58
|
+
end
|
59
|
+
|
60
|
+
def row_cell_link(new_location)
|
61
|
+
mouseover_pointer + "onclick='window.location=\"#{new_location}\"'"
|
62
|
+
end
|
63
|
+
|
64
|
+
def mouseover_pointer
|
65
|
+
"onmouseover='this.style.cursor = \"pointer\"' onmouseout='this.style.cursor=\"auto\"'"
|
66
|
+
end
|
67
|
+
|
68
|
+
def table_header
|
69
|
+
result = "<tr class='tableHeaderRow'>"
|
70
|
+
@headings.each do |heading|
|
71
|
+
sort_class = sort_td_class_helper heading[1]
|
72
|
+
result += "<td"
|
73
|
+
result += " class='#{sort_class}'" if !sort_class.blank?
|
74
|
+
result += ">"
|
75
|
+
result += sort_link_helper @action, heading[0], heading[1], params
|
76
|
+
result += "</td>"
|
77
|
+
end
|
78
|
+
result += "</tr>"
|
79
|
+
return result
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{sortable_table}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Michael Kovacs"]
|
12
|
+
s.date = %q{2010-01-12}
|
13
|
+
s.description = %q{Rails plugin to produce a sortable, paginated, searchable table for any model}
|
14
|
+
s.email = %q{}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README",
|
17
|
+
"TODO"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
"README",
|
21
|
+
"Rakefile",
|
22
|
+
"TODO",
|
23
|
+
"VERSION",
|
24
|
+
"example/controllers/cablecar/users_controller.rb",
|
25
|
+
"example/models/cablecar/contact_info.rb",
|
26
|
+
"example/models/cablecar/user.rb",
|
27
|
+
"example/views/cablecar/users/index.html.erb",
|
28
|
+
"images/arrow_down.gif",
|
29
|
+
"images/arrow_up.gif",
|
30
|
+
"images/logo_opaque.png",
|
31
|
+
"init.rb",
|
32
|
+
"install.rb",
|
33
|
+
"lib/sortable.rb",
|
34
|
+
"lib/sortable_helper.rb",
|
35
|
+
"sortable_table.gemspec",
|
36
|
+
"test/example_test_routing.rb",
|
37
|
+
"test/test_helper.rb",
|
38
|
+
"test/users_controller_test.rb",
|
39
|
+
"views/sortable/_table.html.erb",
|
40
|
+
"views/sortable/index.erb"
|
41
|
+
]
|
42
|
+
s.homepage = %q{http://github.com/kovacs/sortable}
|
43
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
44
|
+
s.require_paths = ["lib"]
|
45
|
+
s.rubygems_version = %q{1.3.5}
|
46
|
+
s.summary = %q{Rails plugin to produce a sortable, paginated, searchable table for any model}
|
47
|
+
s.test_files = [
|
48
|
+
"test/example_test_routing.rb",
|
49
|
+
"test/test_helper.rb",
|
50
|
+
"test/users_controller_test.rb"
|
51
|
+
]
|
52
|
+
|
53
|
+
if s.respond_to? :specification_version then
|
54
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
55
|
+
s.specification_version = 3
|
56
|
+
|
57
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
58
|
+
else
|
59
|
+
end
|
60
|
+
else
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
ENV["RAILS_ENV"] = 'test'
|
2
|
+
|
3
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb'))
|
4
|
+
|
5
|
+
require 'test/unit'
|
6
|
+
require 'test_help'
|
7
|
+
|
8
|
+
class Test::Unit::TestCase
|
9
|
+
|
10
|
+
self.fixture_path = File.expand_path( File.join(File.dirname(__FILE__), 'fixtures') )
|
11
|
+
|
12
|
+
self.use_transactional_fixtures = true
|
13
|
+
self.use_instantiated_fixtures = false
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,346 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
|
3
|
+
|
4
|
+
def setup_sortable_db
|
5
|
+
old_stdout = $stdout
|
6
|
+
|
7
|
+
# AR keeps printing annoying schema statements
|
8
|
+
$stdout = StringIO.new
|
9
|
+
|
10
|
+
ActiveRecord::Base.logger
|
11
|
+
ActiveRecord::Schema.define(:version => 1) do
|
12
|
+
create_table :cablecar_users do |t|
|
13
|
+
t.column :username, :string
|
14
|
+
t.column :status, :string
|
15
|
+
t.column :contact_info_id, :integer
|
16
|
+
end
|
17
|
+
|
18
|
+
create_table :cablecar_contact_infos do |t|
|
19
|
+
t.column :name, :string
|
20
|
+
t.column :phone, :string
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
$stdout = old_stdout
|
25
|
+
end
|
26
|
+
|
27
|
+
setup_sortable_db
|
28
|
+
|
29
|
+
require File.dirname(__FILE__) + '/../example/controllers/cablecar/users_controller'
|
30
|
+
|
31
|
+
# Re-raise errors caught by the controller.
|
32
|
+
class Cablecar::UsersController
|
33
|
+
def rescue_action(e) raise e end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
class UsersControllerTest < Test::Unit::TestCase
|
38
|
+
|
39
|
+
def teardown_db
|
40
|
+
ActiveRecord::Base.connection.tables.each do |table|
|
41
|
+
ActiveRecord::Base.connection.drop_table(table)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
#fixtures :widgets
|
46
|
+
|
47
|
+
def setup
|
48
|
+
@controller = Cablecar::UsersController.new
|
49
|
+
@request = ActionController::TestRequest.new
|
50
|
+
@response = ActionController::TestResponse.new
|
51
|
+
|
52
|
+
30.times do |n|
|
53
|
+
c = Cablecar::ContactInfo.new(:name => "name#{n}")
|
54
|
+
c.user = Cablecar::User.new(:username => "user#{n}")
|
55
|
+
c.save!
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def teardown
|
60
|
+
teardown_db
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_should_show_paginated_table_using_default_action
|
64
|
+
get :index, :use_default => true
|
65
|
+
assert_equal 10, assigns(:objects).size
|
66
|
+
|
67
|
+
verify_sortable_table_html
|
68
|
+
verify_pagination_html
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_should_show_paginated_table
|
72
|
+
get :index
|
73
|
+
assert_equal 10, assigns(:objects).size
|
74
|
+
|
75
|
+
verify_sortable_table_html
|
76
|
+
verify_pagination_html
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_should_show_sorable_table_without_pagination
|
80
|
+
get :index, :no_pagination => true
|
81
|
+
assert_equal 10, assigns(:objects).size
|
82
|
+
|
83
|
+
verify_sortable_table_html
|
84
|
+
assert_select 'div.pagination', false
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_should_show_paginated_table_with_overrides_and_related_columns
|
88
|
+
get :index, :complex_example => true
|
89
|
+
assert_equal 15, assigns(:objects).size
|
90
|
+
assert_select 'thead' do
|
91
|
+
assert_select 'tr' do
|
92
|
+
assert_select 'td', :count => 3
|
93
|
+
assert_select 'td.sortdown', 'Name'
|
94
|
+
assert_select 'td.sortdown' do
|
95
|
+
assert_select 'a[href=/cablecar/users?sort=name]', 'Name'
|
96
|
+
assert_select 'a[title=Sort by this field]', 'Name'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
assert_select 'tbody' do
|
101
|
+
assert_select 'tr', :count => 15
|
102
|
+
verify_name_user_row_data(11, 22)
|
103
|
+
end
|
104
|
+
verify_pagination_html(2)
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_should_sortdown_up_by_related_field
|
108
|
+
get :index, :sort => 'name', :complex_example => true
|
109
|
+
assert_equal 15, assigns(:objects).size
|
110
|
+
assert_select 'thead' do
|
111
|
+
assert_select 'tr' do
|
112
|
+
assert_select 'td', :count => 3
|
113
|
+
assert_select 'td.sortup', 'Name'
|
114
|
+
assert_select 'td.sortup' do
|
115
|
+
assert_select 'a[href=/cablecar/users?sort=name_reverse]', 'Name'
|
116
|
+
assert_select 'a[title=Sort by this field]', 'Name'
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
assert_select 'tbody' do
|
121
|
+
assert_select 'tr', :count => 15
|
122
|
+
verify_name_user_row_data(4, 10)
|
123
|
+
verify_name_user_row_data(23, 30)
|
124
|
+
end
|
125
|
+
verify_pagination_html(2)
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_should_sortup_up_by_field_with_desc_default_sort
|
129
|
+
get :index, :sort => 'name_reverse', :complex_example => true
|
130
|
+
assert_equal 15, assigns(:objects).size
|
131
|
+
assert_select 'thead' do
|
132
|
+
assert_select 'tr' do
|
133
|
+
assert_select 'td', :count => 3
|
134
|
+
assert_select 'td.sortdown', 'Name'
|
135
|
+
assert_select 'td.sortdown' do
|
136
|
+
assert_select 'a[href=/cablecar/users?sort=name]', 'Name'
|
137
|
+
assert_select 'a[title=Sort by this field]', 'Name'
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
assert_select 'tbody' do
|
142
|
+
assert_select 'tr', :count => 15
|
143
|
+
verify_name_user_row_data(11, 22)
|
144
|
+
end
|
145
|
+
verify_pagination_html(2)
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_should_sortup_up_by_field_with_asc_default_sort
|
149
|
+
get :index, :sort => 'status_reverse', :complex_example => true
|
150
|
+
assert_equal 15, assigns(:objects).size
|
151
|
+
assert_select 'thead' do
|
152
|
+
assert_select 'tr' do
|
153
|
+
assert_select 'td', :count => 3
|
154
|
+
assert_select 'td.sortdown', 'Status'
|
155
|
+
assert_select 'td.sortdown' do
|
156
|
+
assert_select 'a[href=/cablecar/users?sort=status]', 'Status'
|
157
|
+
assert_select 'a[title=Sort by this field]', 'Status'
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
assert_select 'tbody' do
|
162
|
+
assert_select 'tr', :count => 15
|
163
|
+
verify_name_user_row_data(1, 15)
|
164
|
+
end
|
165
|
+
verify_pagination_html(2)
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_should_sortup_up_by_field_with_more_than_one_sort_param
|
169
|
+
get :index, :sort => 'username_reverse', :complex_example => true
|
170
|
+
assert_equal 15, assigns(:objects).size
|
171
|
+
assert_select 'thead' do
|
172
|
+
assert_select 'tr' do
|
173
|
+
assert_select 'td', :count => 3
|
174
|
+
assert_select 'td.sortdown', 'Username'
|
175
|
+
assert_select 'td.sortdown' do
|
176
|
+
assert_select 'a[href=/cablecar/users?sort=username]', 'Username'
|
177
|
+
assert_select 'a[title=Sort by this field]', 'Username'
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
assert_select 'tbody' do
|
182
|
+
assert_select 'tr', :count => 15
|
183
|
+
verify_name_user_row_data(1, 15)
|
184
|
+
end
|
185
|
+
verify_pagination_html(2)
|
186
|
+
end
|
187
|
+
|
188
|
+
def test_should_fail_to_sort_with_invalid_sort_param
|
189
|
+
assert_raise Exception do |ex|
|
190
|
+
get :index, :sort => 'username_invalid', :complex_example => true
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_should_override_class_defaults
|
195
|
+
Cablecar::UsersController.class_eval do
|
196
|
+
sortable_table Cablecar::User, {:include_relations => [:contact_info],
|
197
|
+
:table_headings => [['Username', 'username'], ['Status', 'status'], ['Name', 'name']],
|
198
|
+
:sort_map => {:username => [['cablecar_users.username', 'DESC'], ['cablecar_users.status', 'DESC']],
|
199
|
+
:status => ['cablecar_users.status', 'ASC'],
|
200
|
+
:name => ['cablecar_contact_infos.name', 'DESC']},
|
201
|
+
:default_sort => ['name', 'ASC'],
|
202
|
+
:per_page => 15}
|
203
|
+
end
|
204
|
+
get :index, :sort => 'name_reverse'
|
205
|
+
assert_equal 15, assigns(:objects).size
|
206
|
+
assert_select 'thead' do
|
207
|
+
assert_select 'tr' do
|
208
|
+
assert_select 'td', :count => 3
|
209
|
+
assert_select 'td.sortdown', 'Name'
|
210
|
+
assert_select 'td.sortdown' do
|
211
|
+
assert_select 'a[href=/cablecar/users?sort=name]', 'Name'
|
212
|
+
assert_select 'a[title=Sort by this field]', 'Name'
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
assert_select 'tbody' do
|
217
|
+
assert_select 'tr', :count => 15
|
218
|
+
verify_name_user_row_data(11, 22)
|
219
|
+
end
|
220
|
+
verify_pagination_html(2)
|
221
|
+
|
222
|
+
Cablecar::UsersController.class_eval do
|
223
|
+
sortable_table Cablecar::User # put things back where you found them
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def verify_name_user_row_data(start, finish)
|
228
|
+
start.upto(finish) do |n|
|
229
|
+
assert_select "tr#cablecar_user_#{n}" do
|
230
|
+
assert_select 'td', :count => 3
|
231
|
+
assert_select 'td', {:minimum => 1}, "name#{n}" # test that the name value is there
|
232
|
+
assert_select 'td', {:minimum => 1}, "user#{n}"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def verify_sortable_table_html(count=10)
|
238
|
+
verify_sortable_table_header
|
239
|
+
assert_select 'tbody' do
|
240
|
+
assert_select 'tr', :count => count
|
241
|
+
30.downto(21) do |n|
|
242
|
+
assert_select "tr#cablecar_user_#{n}" do
|
243
|
+
assert_select 'td', :count => 4
|
244
|
+
assert_select 'td', {:minimum => 1}, n # test that the id value is there
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def verify_sortable_table_header(href='/cablecar/users?sort=id_reverse')
|
251
|
+
assert_select 'thead' do
|
252
|
+
assert_select 'tr' do
|
253
|
+
assert_select 'td', :count => 4
|
254
|
+
assert_select 'td.sortup', 'Id'
|
255
|
+
assert_select 'td.sortup' do
|
256
|
+
assert_select "a[href=#{href}]", 'Id'
|
257
|
+
assert_select 'a[title=Sort by this field]', 'Id'
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def verify_pagination_html(pages=3)
|
264
|
+
assert_select 'div.pagination' do
|
265
|
+
assert_select 'span', :count => 2
|
266
|
+
assert_select 'a', :count => pages
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def test_truth
|
271
|
+
true
|
272
|
+
end
|
273
|
+
|
274
|
+
def test_should_search
|
275
|
+
get :index, :q => 'user3'
|
276
|
+
assert_equal 1, assigns(:objects).size
|
277
|
+
|
278
|
+
verify_sortable_table_header('/cablecar/users?q=user3&sort=id_reverse')
|
279
|
+
|
280
|
+
assert_select 'tbody' do
|
281
|
+
assert_select 'tr', :count => 1
|
282
|
+
assert_select "tr#cablecar_user_4" do
|
283
|
+
assert_select 'td', :count => 4
|
284
|
+
assert_select 'td', {:minimum => 1}, 4 # test that the id value is there
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def test_should_search_and_return_multiple_results
|
290
|
+
get :index, :q => 'user4'
|
291
|
+
assert_equal 2, assigns(:objects).size
|
292
|
+
|
293
|
+
verify_sortable_table_header('/cablecar/users?q=user4&sort=id_reverse')
|
294
|
+
assert_select 'tbody' do
|
295
|
+
assert_select 'tr', :count => 2
|
296
|
+
assert_select "tr#cablecar_user_5" do
|
297
|
+
assert_select 'td', :count => 4
|
298
|
+
assert_select 'td', {:minimum => 1}, 4 # test that the id value is there
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|
303
|
+
|
304
|
+
def create_a_dupe_user
|
305
|
+
c = Cablecar::ContactInfo.new(:name => "dupe_name4")
|
306
|
+
c.user = Cablecar::User.new(:username => "dupe_user4")
|
307
|
+
c.user.status = 'active'
|
308
|
+
c.save!
|
309
|
+
end
|
310
|
+
|
311
|
+
def test_should_search_and_filter_results
|
312
|
+
create_a_dupe_user
|
313
|
+
# This tests the ability to search as well as apply an additional filter that is used in conjunction with the
|
314
|
+
# search conditions
|
315
|
+
get :index, :q => 'user4', :active => 'true', :filter_example => true
|
316
|
+
assert_equal 1, assigns(:objects).size
|
317
|
+
|
318
|
+
verify_sortable_table_header('/cablecar/users?q=user4&sort=id_reverse')
|
319
|
+
assert_select 'tbody' do
|
320
|
+
assert_select 'tr', :count => 1
|
321
|
+
assert_select "tr#cablecar_user_31" do
|
322
|
+
assert_select 'td', :count => 4
|
323
|
+
assert_select 'td', {:minimum => 1}, 4 # test that the id value is there
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
end
|
328
|
+
|
329
|
+
def test_should_search_and_return_multiple_results
|
330
|
+
create_a_dupe_user
|
331
|
+
|
332
|
+
get :index, :q => 'user4'
|
333
|
+
assert_equal 2, assigns(:objects).size
|
334
|
+
|
335
|
+
verify_sortable_table_header('/cablecar/users?q=user4&sort=id_reverse')
|
336
|
+
assert_select 'tbody' do
|
337
|
+
assert_select 'tr', :count => 2
|
338
|
+
assert_select "tr#cablecar_user_5" do
|
339
|
+
assert_select 'td', :count => 4
|
340
|
+
assert_select 'td', {:minimum => 1}, 4 # test that the id value is there
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
end
|
345
|
+
|
346
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
<style>
|
2
|
+
.sortableTable {
|
3
|
+
border: 1px solid #999;
|
4
|
+
background-image:url(../images/logo_opaque.png);
|
5
|
+
background-position: center;
|
6
|
+
background-repeat:no-repeat;
|
7
|
+
}
|
8
|
+
|
9
|
+
.sortableTable td {
|
10
|
+
font-size: .95em;
|
11
|
+
padding: 3px 6px;
|
12
|
+
color: #101010;
|
13
|
+
border-bottom: 1px solid #999;
|
14
|
+
}
|
15
|
+
|
16
|
+
.sortableTable tr.tableHeaderRow td {
|
17
|
+
color: #830519;
|
18
|
+
background: #c7e1f0;
|
19
|
+
border-top: 1px solid #830519;
|
20
|
+
border-bottom: 1px solid #830519;
|
21
|
+
font-weight: bold;
|
22
|
+
}
|
23
|
+
|
24
|
+
.sortableTable tr.tableHeaderRow td.sortup {
|
25
|
+
background-color: #faf1ed;
|
26
|
+
background-image:url(../images/arrow_down.gif);
|
27
|
+
background-position: top right;
|
28
|
+
background-repeat:no-repeat;
|
29
|
+
|
30
|
+
}
|
31
|
+
|
32
|
+
.sortableTable tr.tableHeaderRow td.sortdown {
|
33
|
+
background-color: #faf1ed;
|
34
|
+
background-image:url(../images/arrow_up.gif);
|
35
|
+
background-position: top right;
|
36
|
+
background-repeat:no-repeat;
|
37
|
+
}
|
38
|
+
|
39
|
+
#search {
|
40
|
+
padding: 10px;
|
41
|
+
}
|
42
|
+
</style>
|
43
|
+
|
44
|
+
<% if search %>
|
45
|
+
<div id='search'>
|
46
|
+
<% form_tag({:action => @action}, {:method => 'get', :id => 'search_form'}) do %>
|
47
|
+
<%=text_field_tag("q", params['q'], :size => 10 ) %>
|
48
|
+
<%=submit_tag 'Search'%>
|
49
|
+
<% end %>
|
50
|
+
</div>
|
51
|
+
<% end %>
|
52
|
+
|
53
|
+
<table width="100%" class='sortableTable' cellpadding="0" cellspacing="0">
|
54
|
+
<thead>
|
55
|
+
<%= table_header %>
|
56
|
+
</thead>
|
57
|
+
<tbody>
|
58
|
+
<%if @objects.size == 0%>
|
59
|
+
<tr><td colspan='<%= @headings.size %>'>No items in the list</td></tr>
|
60
|
+
<%else
|
61
|
+
@objects.each do |object|
|
62
|
+
klass = cycle('odd', 'even')
|
63
|
+
%>
|
64
|
+
<tr id='<%=dom_id(object)%>' class='<%= klass %>'>
|
65
|
+
<% @headings.each do |heading| %>
|
66
|
+
<td>
|
67
|
+
<%= object.instance_eval(heading[1]) %>
|
68
|
+
</td>
|
69
|
+
<% end %>
|
70
|
+
</tr>
|
71
|
+
<% end
|
72
|
+
end%>
|
73
|
+
</tbody>
|
74
|
+
</table>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= sortable_table %>
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sortable_table
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Kovacs
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-12 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Rails plugin to produce a sortable, paginated, searchable table for any model
|
17
|
+
email: ""
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
- TODO
|
25
|
+
files:
|
26
|
+
- README
|
27
|
+
- Rakefile
|
28
|
+
- TODO
|
29
|
+
- VERSION
|
30
|
+
- example/controllers/cablecar/users_controller.rb
|
31
|
+
- example/models/cablecar/contact_info.rb
|
32
|
+
- example/models/cablecar/user.rb
|
33
|
+
- example/views/cablecar/users/index.html.erb
|
34
|
+
- images/arrow_down.gif
|
35
|
+
- images/arrow_up.gif
|
36
|
+
- images/logo_opaque.png
|
37
|
+
- init.rb
|
38
|
+
- install.rb
|
39
|
+
- lib/sortable.rb
|
40
|
+
- lib/sortable_helper.rb
|
41
|
+
- sortable_table.gemspec
|
42
|
+
- test/example_test_routing.rb
|
43
|
+
- test/test_helper.rb
|
44
|
+
- test/users_controller_test.rb
|
45
|
+
- views/sortable/_table.html.erb
|
46
|
+
- views/sortable/index.erb
|
47
|
+
has_rdoc: true
|
48
|
+
homepage: http://github.com/kovacs/sortable
|
49
|
+
licenses: []
|
50
|
+
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options:
|
53
|
+
- --charset=UTF-8
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.3.5
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: Rails plugin to produce a sortable, paginated, searchable table for any model
|
75
|
+
test_files:
|
76
|
+
- test/example_test_routing.rb
|
77
|
+
- test/test_helper.rb
|
78
|
+
- test/users_controller_test.rb
|