fluid_table 3.2.0
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.
- 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
|