fluid_table 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/Gemfile +2 -0
- data/LICENSE +21 -0
- data/README.md +3 -0
- data/Rakefile +13 -0
- data/lib/fluid_table.rb +19 -0
- data/lib/fluid_table/class_methods.rb +22 -0
- data/lib/fluid_table/column.rb +200 -0
- data/lib/fluid_table/context.rb +28 -0
- data/lib/fluid_table/instance_methods.rb +86 -0
- data/lib/fluid_table/version.rb +3 -0
- data/test/support/user.rb +14 -0
- data/test/support/users.html.erb +1 -0
- data/test/support/users_table.rb +27 -0
- data/test/test_helper.rb +10 -0
- data/test/units/column_test.rb +92 -0
- data/test/units/configuration_test.rb +80 -0
- data/test/units/render_test.rb +29 -0
- data/test/units/table_test.rb +45 -0
- metadata +174 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2010-2012 Decisiv, Inc.
|
2
|
+
Copyright (c) 2009 Brennan Dunn
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
Bundler::GemHelper.install_tasks
|
5
|
+
|
6
|
+
desc 'Test fluid_table'
|
7
|
+
Rake::TestTask.new(:test) do |t|
|
8
|
+
t.libs << 'lib' << 'test'
|
9
|
+
t.pattern = 'test/**/*_test.rb'
|
10
|
+
t.verbose = true
|
11
|
+
end
|
12
|
+
|
13
|
+
task :default => [:test]
|
data/lib/fluid_table.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rails'
|
2
|
+
require 'action_controller'
|
3
|
+
require 'active_support/core_ext/proc'
|
4
|
+
|
5
|
+
require 'fluid_table/class_methods'
|
6
|
+
require 'fluid_table/instance_methods'
|
7
|
+
require 'fluid_table/column'
|
8
|
+
require 'fluid_table/context'
|
9
|
+
require 'fluid_table/version'
|
10
|
+
|
11
|
+
class FluidTable
|
12
|
+
include ActionView::Helpers::TagHelper
|
13
|
+
|
14
|
+
class_attribute :row_options, :class_columns, :table_options
|
15
|
+
attr_accessor :view, :records, :cloned_columns, :render_options, :cache_rendered_rows
|
16
|
+
|
17
|
+
extend ClassMethods
|
18
|
+
include InstanceMethods
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class FluidTable
|
2
|
+
module ClassMethods
|
3
|
+
|
4
|
+
def define_column(*args, &proc)
|
5
|
+
options = [Hash,Proc].include?(args.last.class) ? args.pop : {}
|
6
|
+
identity, alt_name = *args
|
7
|
+
Column.new(self, identity, alt_name, options, &proc).tap do |column|
|
8
|
+
(self.class_columns ||= Array.new).push(column)
|
9
|
+
column.default_position = class_columns.index(column)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def render(view,records = nil)
|
14
|
+
new(view).render(records)
|
15
|
+
end
|
16
|
+
|
17
|
+
def valid?
|
18
|
+
!class_columns.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
class FluidTable
|
2
|
+
class Column
|
3
|
+
include Comparable
|
4
|
+
include ActionView::Helpers::TagHelper
|
5
|
+
|
6
|
+
attr_accessor :table, :identity, :alt_name, :options, :html_options, :proc, :forced_options # basic init
|
7
|
+
attr_accessor :is_visible, :default_position, :configured_position # overrides
|
8
|
+
attr_accessor :table_instance # duped context
|
9
|
+
|
10
|
+
DefaultOptions = { :default => true, :cacheable => true }
|
11
|
+
|
12
|
+
def initialize(table, identity, alt_name = nil, options = {}, &proc)
|
13
|
+
self.table = table
|
14
|
+
self.identity = identity
|
15
|
+
self.alt_name = alt_name
|
16
|
+
self.options = options.reverse_merge(DefaultOptions)
|
17
|
+
self.html_options = options.delete(:html) || {}
|
18
|
+
self.proc = proc
|
19
|
+
self.forced_options = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def <=>(other_column)
|
23
|
+
if position == other_column.position
|
24
|
+
return -1 if positioned? && !other_column.positioned?
|
25
|
+
return 1 if !positioned? && other_column.positioned?
|
26
|
+
end
|
27
|
+
position <=> other_column.position
|
28
|
+
end
|
29
|
+
|
30
|
+
def current
|
31
|
+
table_instance.view.current
|
32
|
+
end
|
33
|
+
|
34
|
+
def position
|
35
|
+
configured_position || default_position
|
36
|
+
end
|
37
|
+
|
38
|
+
def cacheable?
|
39
|
+
options[:cacheable]
|
40
|
+
end
|
41
|
+
|
42
|
+
def visible?
|
43
|
+
return @visible unless @visible.nil?
|
44
|
+
@visible = options[:visible] ? options[:visible].bind(table_instance.view).call : true
|
45
|
+
end
|
46
|
+
|
47
|
+
def config_filter_key
|
48
|
+
:"filter_for_#{sort.field}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def filter
|
52
|
+
options[:filter]
|
53
|
+
end
|
54
|
+
|
55
|
+
def filter_distinct?
|
56
|
+
filter == :distinct
|
57
|
+
end
|
58
|
+
|
59
|
+
def filterable?
|
60
|
+
filter.present?
|
61
|
+
end
|
62
|
+
|
63
|
+
def filter_data
|
64
|
+
return [] unless filterable?
|
65
|
+
if filter_distinct?
|
66
|
+
filter_data_for_distinct
|
67
|
+
else
|
68
|
+
filter.first.is_a?(Array) ? filter.map(&:first) : filter
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def filter_data_for_current
|
73
|
+
return [] unless filterable?
|
74
|
+
data = filter_data
|
75
|
+
values = current_filter_values
|
76
|
+
data.sort.inject(ActiveSupport::OrderedHash.new) do |hsh, key|
|
77
|
+
checked = values.include?(key)
|
78
|
+
value = if !filter_distinct? && filter.first.is_a?(Array)
|
79
|
+
filter.detect{ |arr| arr.first == key }.try(:last)
|
80
|
+
else
|
81
|
+
checked ? 1 : 0
|
82
|
+
end
|
83
|
+
hsh[key] = [checked ? 1 : 0, key].join('|')
|
84
|
+
hsh
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def filter_condition
|
89
|
+
filter_values = current_filter_values.dup
|
90
|
+
conditions = []
|
91
|
+
if filter_values.delete('none...')
|
92
|
+
conditions << "#{sort.table}.#{sort.field} IS NULL"
|
93
|
+
end
|
94
|
+
unless filter_values.empty?
|
95
|
+
conditions << "#{sort.table}.#{sort.field} IN (?)"
|
96
|
+
end
|
97
|
+
return nil if conditions.empty?
|
98
|
+
condition = "(#{conditions.join(' OR ')})"
|
99
|
+
unless filter_values.empty?
|
100
|
+
condition = [condition, filter_values]
|
101
|
+
end
|
102
|
+
condition
|
103
|
+
end
|
104
|
+
|
105
|
+
def positioned?
|
106
|
+
!!configured_position
|
107
|
+
end
|
108
|
+
|
109
|
+
def name
|
110
|
+
alt_name || identity.to_s.humanize
|
111
|
+
end
|
112
|
+
|
113
|
+
def sort_on
|
114
|
+
options[:sort_on]
|
115
|
+
end
|
116
|
+
|
117
|
+
def sort
|
118
|
+
return nil unless sort_on
|
119
|
+
@sort ||= begin
|
120
|
+
stable, sfield = sort_on.split('.')
|
121
|
+
Struct.new(:table, :field).new(stable, sfield)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def html(scope)
|
126
|
+
attributes = html_options.is_a?(Proc) ? html_options.call(Context.new(table_instance, scope)) : html_options
|
127
|
+
merge_options = forced_options.is_a?(Proc) ? forced_options.call(Context.new(table_instance, scope)) : forced_options
|
128
|
+
content_tag(:td, interior_content(scope).to_s.html_safe, attributes.merge(merge_options))
|
129
|
+
end
|
130
|
+
|
131
|
+
def display?
|
132
|
+
is_visible.nil? ? options[:default] : is_visible
|
133
|
+
end
|
134
|
+
|
135
|
+
def reset!
|
136
|
+
self.is_visible = self.configured_position = nil
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
def current_user_filters
|
143
|
+
table_instance.current_user_filters
|
144
|
+
end
|
145
|
+
|
146
|
+
def current_filter_values
|
147
|
+
return [] unless filterable?
|
148
|
+
value = current_user_filters[config_filter_key] || ''
|
149
|
+
value.split(',').map(&:strip)
|
150
|
+
end
|
151
|
+
|
152
|
+
# FIXME this absolutely does not belong here
|
153
|
+
def filter_data_for_distinct
|
154
|
+
base_finders = {:select => sort.field, :group => sort.field}
|
155
|
+
set = case table.klass_name
|
156
|
+
when 'FleetRecentEstimates', 'FleetRequestedEstimates'
|
157
|
+
case sort_on
|
158
|
+
when 'vehicles.fleet_info'
|
159
|
+
current.dealer.fleet_vehicles.all(base_finders)
|
160
|
+
when 'estimates.fleet_status'
|
161
|
+
current.dealer.fleet_estimates.all(base_finders)
|
162
|
+
when 'dealer_info.dealer_name'
|
163
|
+
Dealer.find_by_sql(%|
|
164
|
+
SELECT [dealer_name]
|
165
|
+
FROM [dealer_info]
|
166
|
+
WHERE [dealer_id] IN (SELECT [dealer_id] FROM [estimates] WHERE [vehicle_owner] = #{Dealer.connection.quote(current.dealer.id)})
|
167
|
+
ORDER BY [dealer_name] ASC|.squish)
|
168
|
+
end
|
169
|
+
when 'SvcRecentEstimates', 'SvcAssignedEstimates'
|
170
|
+
case sort_on
|
171
|
+
when 'estimates.assigned_to', 'estimates.customer_company_name'
|
172
|
+
table_instance.scoped_records.all(base_finders)
|
173
|
+
end
|
174
|
+
end || []
|
175
|
+
fields = set.map { |o| o.send(sort.field) } + current_filter_values
|
176
|
+
fields.uniq.reject { |o| o.blank? }.sort
|
177
|
+
end
|
178
|
+
|
179
|
+
def interior_content(scope)
|
180
|
+
if proc && table_instance
|
181
|
+
call_by_arity(scope)
|
182
|
+
elsif scope.respond_to?(identity)
|
183
|
+
scope.send identity
|
184
|
+
else
|
185
|
+
''
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def call_by_arity(scope)
|
190
|
+
call_arguments = case proc.arity
|
191
|
+
when 1 then [scope]
|
192
|
+
when 2 then [scope, table_instance]
|
193
|
+
when 3 then [scope, table_instance, self]
|
194
|
+
else []
|
195
|
+
end
|
196
|
+
proc.bind(table_instance.view).call(*call_arguments)
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class FluidTable
|
2
|
+
class Context < Struct.new(:table,:record)
|
3
|
+
|
4
|
+
def view
|
5
|
+
table.view if table
|
6
|
+
end
|
7
|
+
|
8
|
+
def respond_to?(method)
|
9
|
+
view && view.respond_to?(method)
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def method_missing(method, *args)
|
16
|
+
if table.view
|
17
|
+
if block_given?
|
18
|
+
table.view.send(method, *args) { |*block_args| yield(*block_args) }
|
19
|
+
else
|
20
|
+
table.view.send(method, *args)
|
21
|
+
end
|
22
|
+
else
|
23
|
+
block_given? ? super { |*block_args| yield(*block_args) } : super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
class FluidTable
|
2
|
+
module InstanceMethods
|
3
|
+
|
4
|
+
def initialize(view,render_options={})
|
5
|
+
self.view = view
|
6
|
+
load_cloned_columns
|
7
|
+
self.render_options = render_options
|
8
|
+
load_customizations
|
9
|
+
end
|
10
|
+
|
11
|
+
def displayed_columns
|
12
|
+
cloned_columns.select(&:display?).sort
|
13
|
+
end
|
14
|
+
|
15
|
+
def hidden_columns
|
16
|
+
cloned_columns - displayed_columns
|
17
|
+
end
|
18
|
+
|
19
|
+
# Overwrite this method in your implementation to add table headers, etc.
|
20
|
+
def render(records = find_records)
|
21
|
+
self.records = records
|
22
|
+
rendered_table = content_tag :table, content_tag(:tbody,render_table_body), table_options || {}
|
23
|
+
render_header + rendered_table + render_footer
|
24
|
+
end
|
25
|
+
|
26
|
+
# Stub methods
|
27
|
+
def find_records ; [] ; end
|
28
|
+
def customize_column(column) ; column ; end
|
29
|
+
def render_header ; ActiveSupport::SafeBuffer.new ; end
|
30
|
+
def render_footer ; ActiveSupport::SafeBuffer.new ; end
|
31
|
+
def if_array_empty?
|
32
|
+
content_tag :td, %|There's nothing here|, :colspan => displayed_columns.size
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def load_cloned_columns
|
38
|
+
self.cloned_columns = class_columns.map { |cc| cloned = cc.dup ; cloned.table_instance = self ; cloned }
|
39
|
+
cloned_columns.reject! { |c| !c.visible? }
|
40
|
+
cloned_columns.sort!
|
41
|
+
end
|
42
|
+
|
43
|
+
def load_customizations
|
44
|
+
cloned_columns.each { |c| customize_column(c) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def render_table_body
|
48
|
+
return render_empty_table if records.blank?
|
49
|
+
records.map do |record|
|
50
|
+
content_tag(:tr,render_row(record),render_tr_options(record))
|
51
|
+
end.join.html_safe
|
52
|
+
end
|
53
|
+
|
54
|
+
def render_empty_table
|
55
|
+
content_tag(:tr,if_array_empty?)
|
56
|
+
end
|
57
|
+
|
58
|
+
def render_row(record)
|
59
|
+
# The goal is to have one cache hit for the row, per record. The challenge is that different users can have some
|
60
|
+
# columns on/off in each row. The solution is to use a hash for the entire row. If the users displayed_columns has
|
61
|
+
# all the columns from the row cache hash (write_col) then it will not re-write the cache with more data.
|
62
|
+
if cache_rendered_rows
|
63
|
+
cache_key = ActiveSupport::Cache.expand_cache_key ['views',configurator_id,record]
|
64
|
+
row_cache = Rails.cache.fetch(cache_key) { HashWithIndifferentAccess.new }
|
65
|
+
write_col = displayed_columns.select(&:cacheable?).map(&:identity).any? { |id| !row_cache.keys.include?(id) }
|
66
|
+
row_cache = row_cache.dup if write_col
|
67
|
+
end
|
68
|
+
# Rendering the row like normal FluidTable but building up the hash cache if needed.
|
69
|
+
rendered_row = displayed_columns.map do |c|
|
70
|
+
cache_rendered_rows && c.cacheable? ? (row_cache[c.identity] ||= c.html(record)) : c.html(record)
|
71
|
+
end.join
|
72
|
+
# Write the cache back if caching and any cols were not in the orig cache. Return rendered row.
|
73
|
+
Rails.cache.write(cache_key,row_cache) if cache_rendered_rows && write_col
|
74
|
+
return rendered_row.html_safe
|
75
|
+
end
|
76
|
+
|
77
|
+
def render_tr_options(record)
|
78
|
+
case opt = row_options
|
79
|
+
when Proc then opt.call(Context.new(self,record))
|
80
|
+
when Hash then opt
|
81
|
+
else {}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= UsersTable.render(self,User.all_users) %>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class UsersTable < FluidTable
|
2
|
+
|
3
|
+
self.table_options = { :class => 'dataTable' }
|
4
|
+
|
5
|
+
define_column :id, 'User #'
|
6
|
+
|
7
|
+
define_column :name do |user|
|
8
|
+
user.name.upcase
|
9
|
+
end
|
10
|
+
|
11
|
+
define_column :age, :html => Proc.new { |c| { :class => "table_id_#{c.table.object_id}" } }
|
12
|
+
define_column :gender
|
13
|
+
|
14
|
+
UsersTable.define_column :displayed_column
|
15
|
+
UsersTable.define_column :hidden_column, :default => false
|
16
|
+
|
17
|
+
# overwrites
|
18
|
+
|
19
|
+
def render_header
|
20
|
+
content_tag(:div, 'This is the header')
|
21
|
+
end
|
22
|
+
|
23
|
+
def render_footer
|
24
|
+
content_tag(:div, 'This is the footer')
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestTable < FluidTable
|
4
|
+
define_column :id, 'User #'
|
5
|
+
define_column :name, 'User Name'
|
6
|
+
define_column(:google){ link_to_google }
|
7
|
+
end
|
8
|
+
|
9
|
+
describe FluidTable::Column do
|
10
|
+
|
11
|
+
def self.test_order
|
12
|
+
:alpha
|
13
|
+
end
|
14
|
+
|
15
|
+
describe 'Constructing column objects' do
|
16
|
+
|
17
|
+
before do
|
18
|
+
@test_table = TestTable.new(nil,nil)
|
19
|
+
@id_column = @test_table.class_columns.detect { |c| c.identity == :id }
|
20
|
+
@name_column = @test_table.class_columns.detect { |c| c.identity == :name }
|
21
|
+
@google_column = @test_table.class_columns.detect { |c| c.identity == :google }
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should respond to it's identity" do
|
25
|
+
assert_equal :id, @id_column.identity
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should attempt to create a humanized string from the identity if an alt_name is not present' do
|
29
|
+
assert_equal 'User #', @id_column.name
|
30
|
+
assert_equal 'User Name', @name_column.name
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should be able to parse its sort on table and field" do
|
34
|
+
@name_column.options[:sort_on] = 'foo.bar'
|
35
|
+
assert @name_column.sort
|
36
|
+
assert_equal 'foo', @name_column.sort.table
|
37
|
+
assert_equal 'bar', @name_column.sort.field
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "Outputting a column's HTML" do
|
41
|
+
|
42
|
+
it 'should wrap output within a table cell' do
|
43
|
+
assert_equal_html '<td></td>', @id_column.html(mock)
|
44
|
+
end
|
45
|
+
|
46
|
+
# FIXME - I suck and my tests are order-dependent?
|
47
|
+
it 'should attempt to get cell content from scope object when proc is not available' do
|
48
|
+
assert_equal_html '<td>John Doe</td>', @name_column.html(mock({ :name => 'John Doe' }))
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should call a column's proc (if exists) against the scope of the current view" do
|
52
|
+
view = mock.tap { |m| m.stubs :link_to_google => 'http://google.com' }
|
53
|
+
table = TestTable.new(view)
|
54
|
+
google_column = table.cloned_columns.detect { |c| c.identity == :google }
|
55
|
+
assert_equal_html '<td>http://google.com</td>', google_column.html(User.all_users.first)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should be able to set custom HTML attributes when supplied a HASH' do
|
59
|
+
@name_column.html_options = { :class => 'foo' }
|
60
|
+
assert_match /class="foo"/, @name_column.html(mock)
|
61
|
+
@name_column.html_options = { :id => 'bar' }
|
62
|
+
assert_match /id="bar"/, @name_column.html(mock)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should be able to set custom HTML attributes when supplied a PROC' do
|
66
|
+
@name_column.html_options = Proc.new { |context| { :id => "name_#{context.record.id}" } }
|
67
|
+
assert_match /id="name_5"/, @name_column.html(mock(:id => '5'))
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'Columns within a FluidTable' do
|
75
|
+
|
76
|
+
it 'should not be valid? unless at least one column is defined' do
|
77
|
+
assert ! TestTable.class_columns.empty?
|
78
|
+
assert TestTable.valid?
|
79
|
+
TestTable.stubs(:class_columns).returns([])
|
80
|
+
assert ! TestTable.valid?
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def assert_equal_html(expected, actual)
|
89
|
+
assert_equal expected.strip.gsub(/\n\s*/, ''), actual.strip.gsub(/\n\s*/, '')
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe "Fluid table configuration" do
|
4
|
+
|
5
|
+
let(:table) { UsersTable.new(nil, nil) }
|
6
|
+
|
7
|
+
it 'should have an array of cloned class_columns for each table instance' do
|
8
|
+
table.cloned_columns.map(&:object_id).wont_equal UsersTable.class_columns.map(&:object_id)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should not affect a class column when modifying an instance column' do
|
12
|
+
instance_column = get_column(:age)
|
13
|
+
transform_column(instance_column, false)
|
14
|
+
assert_equal false, instance_column.display?
|
15
|
+
assert_equal true, get_table_column(:age).display?
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "Determining a column's default visibility" do
|
19
|
+
|
20
|
+
let(:displayed_column) { get_column(:displayed_column) }
|
21
|
+
let(:hidden_column) { get_column(:hidden_column) }
|
22
|
+
|
23
|
+
it 'should default to TRUE for a column display' do
|
24
|
+
assert displayed_column.display?
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should allow for a column's definition to default display to FALSE" do
|
28
|
+
assert !hidden_column.display?
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should include correct columns when returning from #displayed_columns or #hidden_columns' do
|
32
|
+
assert table.displayed_columns.include?(displayed_column)
|
33
|
+
assert !table.displayed_columns.include?(hidden_column)
|
34
|
+
assert table.hidden_columns.include?(hidden_column)
|
35
|
+
assert !table.hidden_columns.include?(displayed_column)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'When overriding position or visibility' do
|
41
|
+
|
42
|
+
it 'should call #customize_column with each column object on table instantiation' do
|
43
|
+
table.class_columns.each do |column|
|
44
|
+
table.expects(:customize_column).with(column)
|
45
|
+
end
|
46
|
+
table.send :load_customizations
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should override default column visibility settings when instructed' do
|
50
|
+
column = get_column(:age)
|
51
|
+
assert table.displayed_columns.include?(column)
|
52
|
+
transform_column(get_column(:age), false)
|
53
|
+
table.send :load_customizations
|
54
|
+
assert ! table.displayed_columns.include?(column)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should be able to set a column's position which trumps the default" do
|
58
|
+
age_column = get_column(:age)
|
59
|
+
transform_column(age_column, true, 0)
|
60
|
+
assert_equal age_column.identity, table.displayed_columns.first.identity
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def get_column(identity)
|
68
|
+
table.cloned_columns.detect { |c| c.identity == identity }
|
69
|
+
end
|
70
|
+
|
71
|
+
def get_table_column(identity)
|
72
|
+
table.class_columns.detect { |c| c.identity == identity }
|
73
|
+
end
|
74
|
+
|
75
|
+
def transform_column(column, is_visible = true, position = nil)
|
76
|
+
column.is_visible = is_visible
|
77
|
+
column.configured_position = position
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe FluidTable do
|
4
|
+
|
5
|
+
describe "#render" do
|
6
|
+
|
7
|
+
let(:text) { UsersTable.render(stub(:view), User.all_users) }
|
8
|
+
let(:html) { Nokogiri::HTML::DocumentFragment.parse(text) }
|
9
|
+
|
10
|
+
it "renders the header text in a header div" do
|
11
|
+
html.xpath(".//div").first.text.must_equal 'This is the header'
|
12
|
+
end
|
13
|
+
|
14
|
+
it "renders the footer text in a footer div" do
|
15
|
+
html.xpath(".//div").last.text.must_equal 'This is the footer'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "renders a row for each user" do
|
19
|
+
html.xpath(".//tbody/tr").length.must_equal User.all_users.length
|
20
|
+
end
|
21
|
+
|
22
|
+
it "render 'Nothing here' if there are no users" do
|
23
|
+
User.stubs(:all_users).returns([])
|
24
|
+
html.xpath(".//td").map(&:text).must_equal ["There's nothing here"]
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe "FluidTable render" do
|
4
|
+
|
5
|
+
describe 'A FluidTable object' do
|
6
|
+
|
7
|
+
it 'should have the option of setting attributes to the generated table (as opposed to overwriting #render)' do
|
8
|
+
UsersTable.table_options = { :class => 'dataTable' }
|
9
|
+
assert_match %r{table class="dataTable"}, render_users
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'Ordering of class_columns' do
|
13
|
+
|
14
|
+
it 'should be defaultly positioned in the order of definition' do
|
15
|
+
assert_equal 0, UsersTable.class_columns.first.default_position
|
16
|
+
assert_equal UsersTable.class_columns.size-1, UsersTable.class_columns.last.default_position
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should have the #sort order be identical to the default positioning when there are no custom positions' do
|
20
|
+
assert_equal UsersTable.class_columns, UsersTable.class_columns.sort
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'Supplying row (<tr>) options' do
|
26
|
+
|
27
|
+
it 'should output standard hash options' do
|
28
|
+
UsersTable.row_options = { :class => 'foo' }
|
29
|
+
assert_match %r{tr class="foo"}, render_users
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should yield a context object when given a Proc' do
|
33
|
+
UsersTable.row_options = Proc.new { |context| { :id => "user_#{context.record.id}" } }
|
34
|
+
assert_match %r{tr id="user_#{User::ALL.size}"}, render_users
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
def render_users
|
42
|
+
UsersTable.render(mock, User::ALL)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
metadata
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fluid_table
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 15
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 3
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 3.2.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Brennan Dunn
|
14
|
+
- Ken Collins
|
15
|
+
- Donald Ball
|
16
|
+
autorequire:
|
17
|
+
bindir: bin
|
18
|
+
cert_chain: []
|
19
|
+
|
20
|
+
date: 2012-03-13 00:00:00 Z
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: rails
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 15
|
31
|
+
segments:
|
32
|
+
- 3
|
33
|
+
- 2
|
34
|
+
- 0
|
35
|
+
version: 3.2.0
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id001
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: rake
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 63
|
47
|
+
segments:
|
48
|
+
- 0
|
49
|
+
- 9
|
50
|
+
- 2
|
51
|
+
version: 0.9.2
|
52
|
+
type: :development
|
53
|
+
version_requirements: *id002
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: minitest
|
56
|
+
prerelease: false
|
57
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 45
|
63
|
+
segments:
|
64
|
+
- 2
|
65
|
+
- 8
|
66
|
+
- 1
|
67
|
+
version: 2.8.1
|
68
|
+
type: :development
|
69
|
+
version_requirements: *id003
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: mocha
|
72
|
+
prerelease: false
|
73
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ~>
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
hash: 53
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
- 10
|
82
|
+
- 1
|
83
|
+
version: 0.10.1
|
84
|
+
type: :development
|
85
|
+
version_requirements: *id004
|
86
|
+
- !ruby/object:Gem::Dependency
|
87
|
+
name: nokogiri
|
88
|
+
prerelease: false
|
89
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ~>
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
hash: 3
|
95
|
+
segments:
|
96
|
+
- 1
|
97
|
+
- 5
|
98
|
+
- 0
|
99
|
+
version: 1.5.0
|
100
|
+
type: :development
|
101
|
+
version_requirements: *id005
|
102
|
+
description: Paginated data tables for rails
|
103
|
+
email:
|
104
|
+
- me@brennandunn.com
|
105
|
+
- ken@metaskills.net
|
106
|
+
- donald.ball@gmail.com
|
107
|
+
executables: []
|
108
|
+
|
109
|
+
extensions: []
|
110
|
+
|
111
|
+
extra_rdoc_files: []
|
112
|
+
|
113
|
+
files:
|
114
|
+
- .gitignore
|
115
|
+
- Gemfile
|
116
|
+
- LICENSE
|
117
|
+
- README.md
|
118
|
+
- Rakefile
|
119
|
+
- lib/fluid_table.rb
|
120
|
+
- lib/fluid_table/class_methods.rb
|
121
|
+
- lib/fluid_table/column.rb
|
122
|
+
- lib/fluid_table/context.rb
|
123
|
+
- lib/fluid_table/instance_methods.rb
|
124
|
+
- lib/fluid_table/version.rb
|
125
|
+
- test/support/user.rb
|
126
|
+
- test/support/users.html.erb
|
127
|
+
- test/support/users_table.rb
|
128
|
+
- test/test_helper.rb
|
129
|
+
- test/units/column_test.rb
|
130
|
+
- test/units/configuration_test.rb
|
131
|
+
- test/units/render_test.rb
|
132
|
+
- test/units/table_test.rb
|
133
|
+
homepage: http://github.com/Decisiv/fluid_table/
|
134
|
+
licenses: []
|
135
|
+
|
136
|
+
post_install_message:
|
137
|
+
rdoc_options:
|
138
|
+
- --charset=UTF-8
|
139
|
+
require_paths:
|
140
|
+
- lib
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
142
|
+
none: false
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
hash: 3
|
147
|
+
segments:
|
148
|
+
- 0
|
149
|
+
version: "0"
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
none: false
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
hash: 3
|
156
|
+
segments:
|
157
|
+
- 0
|
158
|
+
version: "0"
|
159
|
+
requirements: []
|
160
|
+
|
161
|
+
rubyforge_project:
|
162
|
+
rubygems_version: 1.8.17
|
163
|
+
signing_key:
|
164
|
+
specification_version: 3
|
165
|
+
summary: Paginated data tables for rails
|
166
|
+
test_files:
|
167
|
+
- test/support/user.rb
|
168
|
+
- test/support/users.html.erb
|
169
|
+
- test/support/users_table.rb
|
170
|
+
- test/test_helper.rb
|
171
|
+
- test/units/column_test.rb
|
172
|
+
- test/units/configuration_test.rb
|
173
|
+
- test/units/render_test.rb
|
174
|
+
- test/units/table_test.rb
|