table_helper 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +10 -0
- data/LICENSE +1 -1
- data/README.rdoc +78 -67
- data/Rakefile +1 -1
- data/lib/table_helper.rb +200 -169
- data/lib/table_helper/body.rb +46 -49
- data/lib/table_helper/body_row.rb +18 -14
- data/lib/table_helper/cell.rb +33 -22
- data/lib/table_helper/collection_table.rb +111 -37
- data/lib/table_helper/footer.rb +9 -7
- data/lib/table_helper/header.rb +31 -93
- data/lib/table_helper/html_element.rb +1 -3
- data/lib/table_helper/row.rb +16 -7
- data/test/helpers/table_helper_test.rb +20 -12
- data/test/unit/body_row_test.rb +66 -16
- data/test/unit/body_test.rb +159 -131
- data/test/unit/cell_test.rb +90 -16
- data/test/unit/collection_table_test.rb +166 -192
- data/test/unit/footer_test.rb +33 -8
- data/test/unit/header_test.rb +136 -114
- data/test/unit/row_builder_test.rb +16 -8
- data/test/unit/row_test.rb +106 -45
- metadata +3 -5
- data/test/unit/header_builder_test.rb +0 -48
data/lib/table_helper/footer.rb
CHANGED
@@ -4,27 +4,29 @@ module TableHelper
|
|
4
4
|
# Represents the header of the table. In HTML, you can think of this as
|
5
5
|
# the <tfoot> tag of the table.
|
6
6
|
class Footer < HtmlElement
|
7
|
+
# The table this footer is a part of
|
8
|
+
attr_reader :table
|
9
|
+
|
7
10
|
# The actual footer row
|
8
11
|
attr_reader :row
|
9
12
|
|
10
|
-
delegate :cell,
|
11
|
-
:to => :row
|
12
|
-
|
13
13
|
# Whether or not the footer should be hidden when the collection is
|
14
14
|
# empty. Default is true.
|
15
15
|
attr_accessor :hide_when_empty
|
16
16
|
|
17
|
-
|
17
|
+
delegate :cell, :empty?, :to => :row
|
18
|
+
|
19
|
+
def initialize(table) #:nodoc:
|
18
20
|
super()
|
19
21
|
|
20
|
-
@
|
21
|
-
@row = Row.new
|
22
|
+
@table = table
|
23
|
+
@row = Row.new(self)
|
22
24
|
@hide_when_empty = true
|
23
25
|
end
|
24
26
|
|
25
27
|
def html #:nodoc:
|
26
28
|
html_options = @html_options.dup
|
27
|
-
html_options[:style] =
|
29
|
+
html_options[:style] = "display: none; #{html_options[:style]}".strip if table.empty? && hide_when_empty
|
28
30
|
|
29
31
|
content_tag(tag_name, content, html_options)
|
30
32
|
end
|
data/lib/table_helper/header.rb
CHANGED
@@ -1,89 +1,45 @@
|
|
1
1
|
require 'table_helper/row'
|
2
2
|
|
3
3
|
module TableHelper
|
4
|
-
# Provides a blank class that can be used to build the columns for a header
|
5
|
-
class HeaderBuilder < BlankSlate #:nodoc:
|
6
|
-
reveal :respond_to?
|
7
|
-
|
8
|
-
attr_reader :header
|
9
|
-
|
10
|
-
# Creates a builder for the given header
|
11
|
-
def initialize(header)
|
12
|
-
@header = header
|
13
|
-
end
|
14
|
-
|
15
|
-
# Proxies all missed methods to the header
|
16
|
-
def method_missing(*args)
|
17
|
-
header.send(*args)
|
18
|
-
end
|
19
|
-
|
20
|
-
# Defines the accessor method for the given column name. For example, if
|
21
|
-
# a column with the name :title was defined, then the column would be able
|
22
|
-
# to be read and written like so:
|
23
|
-
#
|
24
|
-
# header.title #=> Accesses the title
|
25
|
-
# header.title "Page Title" #=> Creates a new column with "Page Title" as the content
|
26
|
-
def define_column(name)
|
27
|
-
method_name = name.to_s.gsub('-', '_')
|
28
|
-
|
29
|
-
klass = class << self; self; end
|
30
|
-
klass.class_eval do
|
31
|
-
define_method(method_name) do |*args|
|
32
|
-
header.row.builder.__send__(method_name, *args)
|
33
|
-
end
|
34
|
-
end unless klass.method_defined?(method_name)
|
35
|
-
end
|
36
|
-
|
37
|
-
# Removes the definition for the given cp;i,m
|
38
|
-
def undef_column(name)
|
39
|
-
klass = class << self; self; end
|
40
|
-
klass.class_eval do
|
41
|
-
remove_method(name.gsub('-', '_'))
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
4
|
# Represents the header of the table. In HTML, you can think of this as
|
47
5
|
# the <thead> tag of the table.
|
48
6
|
class Header < HtmlElement
|
7
|
+
# The table this header is a part of
|
8
|
+
attr_reader :table
|
9
|
+
|
49
10
|
# The actual header row
|
50
11
|
attr_reader :row
|
51
12
|
|
52
|
-
# The proxy class used externally to build the actual columns
|
53
|
-
attr_reader :builder
|
54
|
-
|
55
13
|
# Whether or not the header should be hidden when the collection is
|
56
14
|
# empty. Default is true.
|
57
15
|
attr_accessor :hide_when_empty
|
58
16
|
|
59
|
-
|
60
|
-
|
17
|
+
delegate :empty?, :to => :row
|
18
|
+
|
19
|
+
# Creates a new header for the given table.
|
61
20
|
#
|
62
21
|
# If the class is known, then the header will be pre-filled with
|
63
22
|
# the columns defined in that class (assuming it's an ActiveRecord
|
64
23
|
# class).
|
65
|
-
def initialize(
|
24
|
+
def initialize(table)
|
66
25
|
super()
|
67
26
|
|
68
|
-
@
|
69
|
-
@row = Row.new
|
70
|
-
@builder = HeaderBuilder.new(self)
|
71
|
-
|
27
|
+
@table = table
|
28
|
+
@row = Row.new(self)
|
72
29
|
@hide_when_empty = true
|
73
|
-
@customized = true
|
74
30
|
|
75
31
|
# If we know what class the objects in the collection are and we can
|
76
32
|
# figure out what columns are defined in that class, then we can
|
77
33
|
# pre-fill the header with those columns so that the user doesn't
|
78
34
|
# have to
|
79
|
-
klass
|
80
|
-
if klass && klass.respond_to?(:column_names)
|
81
|
-
|
82
|
-
|
83
|
-
end
|
35
|
+
klass = table.klass
|
36
|
+
column(*klass.column_names.map(&:to_sym)) if klass && klass.respond_to?(:column_names)
|
37
|
+
|
38
|
+
@customized = false
|
84
39
|
end
|
85
40
|
|
86
|
-
# The current columns in this header, in the order in which they will be
|
41
|
+
# The current columns in this header, in the order in which they will be
|
42
|
+
# built
|
87
43
|
def columns
|
88
44
|
row.cells
|
89
45
|
end
|
@@ -95,61 +51,43 @@ module TableHelper
|
|
95
51
|
|
96
52
|
# Clears all of the current columns from the header
|
97
53
|
def clear
|
98
|
-
# Remove all of the shortcut methods
|
99
|
-
column_names.each {|name| builder.undef_column(name)}
|
100
54
|
row.clear
|
101
55
|
end
|
102
56
|
|
103
|
-
# Creates
|
104
|
-
#
|
105
|
-
#
|
106
|
-
|
107
|
-
# header (if the header is rendered). For example,
|
108
|
-
#
|
109
|
-
# header.column :title, 'The Title'
|
110
|
-
#
|
111
|
-
# ...will create a column the displays "The Title" in the cell.
|
112
|
-
#
|
113
|
-
# = Setting html options
|
114
|
-
#
|
115
|
-
# In addition to customizing the content of the column, you can also
|
116
|
-
# specify html options like so:
|
117
|
-
#
|
118
|
-
# header.column :title, 'The Title', :class => 'pretty'
|
119
|
-
def column(name, *args)
|
57
|
+
# Creates one or more to columns in the header. This will clear any
|
58
|
+
# pre-existing columns if it is being customized for the first time after
|
59
|
+
# it was initially created.
|
60
|
+
def column(*names)
|
120
61
|
# Clear the header row if this is being customized by the user
|
121
62
|
unless @customized
|
122
63
|
@customized = true
|
123
64
|
clear
|
124
65
|
end
|
125
66
|
|
126
|
-
|
127
|
-
|
128
|
-
|
67
|
+
# Extract configuration
|
68
|
+
options = names.last.is_a?(Hash) ? names.pop : {}
|
69
|
+
content = names.last.is_a?(String) ? names.pop : nil
|
70
|
+
args = [content, options].compact
|
129
71
|
|
130
|
-
|
72
|
+
names.collect! do |name|
|
73
|
+
column = row.cell(name, *args)
|
74
|
+
column.content_type = :header
|
75
|
+
column[:scope] ||= 'col'
|
76
|
+
column
|
77
|
+
end
|
131
78
|
|
132
|
-
|
79
|
+
names.length == 1 ? names.first : names
|
133
80
|
end
|
134
81
|
|
135
82
|
# Creates and returns the generated html for the header
|
136
83
|
def html
|
137
84
|
html_options = @html_options.dup
|
138
|
-
html_options[:style] = 'display: none;' if
|
85
|
+
html_options[:style] = 'display: none;' if table.empty? && hide_when_empty
|
139
86
|
|
140
87
|
content_tag(tag_name, content, html_options)
|
141
88
|
end
|
142
89
|
|
143
90
|
private
|
144
|
-
# Finds the class representing the objects within the collection
|
145
|
-
def class_for_collection(collection)
|
146
|
-
if collection.respond_to?(:proxy_reflection)
|
147
|
-
collection.proxy_reflection.klass
|
148
|
-
elsif !collection.empty?
|
149
|
-
collection.first.class
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
91
|
def tag_name
|
154
92
|
'thead'
|
155
93
|
end
|
@@ -17,9 +17,7 @@ module TableHelper
|
|
17
17
|
class HtmlElement
|
18
18
|
include ActionView::Helpers::TagHelper
|
19
19
|
|
20
|
-
delegate
|
21
|
-
:[]=,
|
22
|
-
:to => '@html_options'
|
20
|
+
delegate :[], :[]=, :to => '@html_options'
|
23
21
|
|
24
22
|
def initialize(html_options = {}) #:nodoc:
|
25
23
|
@html_options = html_options.symbolize_keys
|
data/lib/table_helper/row.rb
CHANGED
@@ -5,8 +5,6 @@ module TableHelper
|
|
5
5
|
class RowBuilder < BlankSlate #:nodoc:
|
6
6
|
reveal :respond_to?
|
7
7
|
|
8
|
-
attr_reader :row
|
9
|
-
|
10
8
|
# Creates a builder for the given row
|
11
9
|
def initialize(row)
|
12
10
|
@row = row
|
@@ -14,7 +12,7 @@ module TableHelper
|
|
14
12
|
|
15
13
|
# Proxies all missed methods to the row
|
16
14
|
def method_missing(*args)
|
17
|
-
row.send(*args)
|
15
|
+
@row.send(*args)
|
18
16
|
end
|
19
17
|
|
20
18
|
# Defines the builder method for the given cell name. For example, if
|
@@ -30,9 +28,9 @@ module TableHelper
|
|
30
28
|
klass.class_eval do
|
31
29
|
define_method(method_name) do |*args|
|
32
30
|
if args.empty?
|
33
|
-
row.cells[name]
|
31
|
+
@row.cells[name]
|
34
32
|
else
|
35
|
-
row.cell(name, *args)
|
33
|
+
@row.cell(name, *args)
|
36
34
|
end
|
37
35
|
end
|
38
36
|
end unless klass.method_defined?(method_name)
|
@@ -58,9 +56,16 @@ module TableHelper
|
|
58
56
|
# The current cells in this row, in the order in which they will be built
|
59
57
|
attr_reader :cells
|
60
58
|
|
61
|
-
|
62
|
-
|
59
|
+
# The parent element for this row
|
60
|
+
attr_reader :parent
|
61
|
+
|
62
|
+
delegate :empty?, :to => :cells
|
63
|
+
delegate :table, :to => :parent
|
64
|
+
|
65
|
+
def initialize(parent) #:nodoc:
|
66
|
+
super()
|
63
67
|
|
68
|
+
@parent = parent
|
64
69
|
@cells = ActiveSupport::OrderedHash.new
|
65
70
|
@builder = RowBuilder.new(self)
|
66
71
|
end
|
@@ -70,6 +75,10 @@ module TableHelper
|
|
70
75
|
def cell(name, *args)
|
71
76
|
name = name.to_s if name
|
72
77
|
|
78
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
79
|
+
options[:namespace] = table.object_name
|
80
|
+
args << options
|
81
|
+
|
73
82
|
cell = Cell.new(name, *args)
|
74
83
|
cells[name] = cell
|
75
84
|
builder.define_cell(name) if name
|
@@ -3,33 +3,41 @@ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
|
3
3
|
class TableHelperTest < ActionView::TestCase
|
4
4
|
tests TableHelper
|
5
5
|
|
6
|
+
class Post
|
7
|
+
end
|
8
|
+
|
6
9
|
def test_should_build_collection_table
|
7
|
-
html = collection_table(['first', 'second', 'last']) do |
|
8
|
-
header
|
9
|
-
|
10
|
-
body.build do |row, post_title, index|
|
10
|
+
html = collection_table(['first', 'second', 'last'], Post) do |t|
|
11
|
+
t.header :title
|
12
|
+
t.rows.each do |row, post_title, index|
|
11
13
|
row.title post_title
|
12
14
|
end
|
15
|
+
t.footer :total, t.collection.length
|
13
16
|
end
|
14
17
|
|
15
18
|
expected = <<-end_str
|
16
|
-
<table cellpadding="0" cellspacing="0">
|
19
|
+
<table cellpadding="0" cellspacing="0" class="posts ui-collection">
|
17
20
|
<thead>
|
18
21
|
<tr>
|
19
|
-
<th class="title" scope="col">Title</th>
|
22
|
+
<th class="post-title" scope="col">Title</th>
|
20
23
|
</tr>
|
21
24
|
</thead>
|
22
25
|
<tbody>
|
23
|
-
<tr class="
|
24
|
-
<td class="title">first</td>
|
26
|
+
<tr class="post ui-collection-result">
|
27
|
+
<td class="post-title">first</td>
|
25
28
|
</tr>
|
26
|
-
<tr class="
|
27
|
-
<td class="title">second</td>
|
29
|
+
<tr class="post ui-collection-result">
|
30
|
+
<td class="post-title">second</td>
|
28
31
|
</tr>
|
29
|
-
<tr class="
|
30
|
-
<td class="title">last</td>
|
32
|
+
<tr class="post ui-collection-result">
|
33
|
+
<td class="post-title">last</td>
|
31
34
|
</tr>
|
32
35
|
</tbody>
|
36
|
+
<tfoot>
|
37
|
+
<tr>
|
38
|
+
<td class="post-total">3</td>
|
39
|
+
</tr>
|
40
|
+
</tfoot>
|
33
41
|
</table>
|
34
42
|
end_str
|
35
43
|
assert_html_equal expected, html
|
data/test/unit/body_row_test.rb
CHANGED
@@ -2,8 +2,13 @@ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
|
2
2
|
|
3
3
|
class BodyRowByDefaultTest < Test::Unit::TestCase
|
4
4
|
def setup
|
5
|
-
|
6
|
-
@
|
5
|
+
table = TableHelper::CollectionTable.new([])
|
6
|
+
@header = TableHelper::Header.new(table)
|
7
|
+
@row = TableHelper::BodyRow.new(Object.new, @header)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_should_have_a_parent
|
11
|
+
assert_equal @header, @row.parent
|
7
12
|
end
|
8
13
|
|
9
14
|
def test_should_not_alternate
|
@@ -11,7 +16,17 @@ class BodyRowByDefaultTest < Test::Unit::TestCase
|
|
11
16
|
end
|
12
17
|
|
13
18
|
def test_should_have_a_class_name
|
14
|
-
assert_equal '
|
19
|
+
assert_equal 'ui-collection-result', @row[:class]
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_should_use_custom_result_class_if_specified
|
23
|
+
original_result_class = TableHelper::BodyRow.result_class
|
24
|
+
TableHelper::BodyRow.result_class = 'ui-collection-item'
|
25
|
+
|
26
|
+
row = TableHelper::BodyRow.new(Object.new, @header)
|
27
|
+
assert_equal 'ui-collection-item', row[:class]
|
28
|
+
ensure
|
29
|
+
TableHelper::BodyRow.result_class = original_result_class
|
15
30
|
end
|
16
31
|
end
|
17
32
|
|
@@ -23,8 +38,10 @@ class BodyRowTest < Test::Unit::TestCase
|
|
23
38
|
end
|
24
39
|
|
25
40
|
def setup
|
26
|
-
|
41
|
+
table = TableHelper::CollectionTable.new([])
|
42
|
+
header = table.header
|
27
43
|
header.column :title
|
44
|
+
|
28
45
|
@row = TableHelper::BodyRow.new(Post.new, header)
|
29
46
|
end
|
30
47
|
|
@@ -34,18 +51,33 @@ class BodyRowTest < Test::Unit::TestCase
|
|
34
51
|
|
35
52
|
def test_should_override_default_cell_content_if_cell_specified
|
36
53
|
@row.builder.title 'Hello World'
|
37
|
-
assert_equal '<tr class="
|
54
|
+
assert_equal '<tr class="ui-collection-result"><td class="title">Hello World</td></tr>', @row.html
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class BodyRowWithTableObjectNameTest < Test::Unit::TestCase
|
59
|
+
def setup
|
60
|
+
table = TableHelper::CollectionTable.new([], Object)
|
61
|
+
header = table.header
|
62
|
+
|
63
|
+
@row = TableHelper::BodyRow.new(Object.new, header)
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_should_include_object_name_in_class
|
67
|
+
assert_equal 'object ui-collection-result', @row[:class]
|
38
68
|
end
|
39
69
|
end
|
40
70
|
|
41
71
|
class BodyRowWithNoColumnsTest < Test::Unit::TestCase
|
42
72
|
def setup
|
43
|
-
|
73
|
+
table = TableHelper::CollectionTable.new([])
|
74
|
+
header = table.header
|
75
|
+
|
44
76
|
@row = TableHelper::BodyRow.new(Object.new, header)
|
45
77
|
end
|
46
78
|
|
47
79
|
def test_should_not_build_cells
|
48
|
-
assert_equal '<tr class="
|
80
|
+
assert_equal '<tr class="ui-collection-result"></tr>', @row.html
|
49
81
|
end
|
50
82
|
end
|
51
83
|
|
@@ -57,49 +89,67 @@ class BodyRowWithCustomAttributeTest < Test::Unit::TestCase
|
|
57
89
|
end
|
58
90
|
|
59
91
|
def setup
|
60
|
-
|
92
|
+
table = TableHelper::CollectionTable.new([])
|
93
|
+
header = table.header
|
61
94
|
header.column :title
|
62
95
|
header.column :author_name
|
96
|
+
|
63
97
|
@row = TableHelper::BodyRow.new(Post.new, header)
|
64
98
|
end
|
65
99
|
|
66
100
|
def test_should_use_attribute_values_as_cell_content
|
67
101
|
@row.builder.author_name 'John Doe'
|
68
|
-
assert_equal '<tr class="
|
102
|
+
assert_equal '<tr class="ui-collection-result"><td class="title">Default Value</td><td class="author_name">John Doe</td></tr>', @row.html
|
69
103
|
end
|
70
104
|
end
|
71
105
|
|
72
106
|
class BodyRowWithMissingCellsTest < Test::Unit::TestCase
|
73
107
|
def setup
|
74
|
-
|
108
|
+
table = TableHelper::CollectionTable.new([])
|
109
|
+
header = table.header
|
75
110
|
header.column :title
|
76
111
|
header.column :author_name
|
112
|
+
|
77
113
|
@row = TableHelper::BodyRow.new(Object.new, header)
|
78
114
|
end
|
79
115
|
|
80
116
|
def test_should_build_missing_cells_if_cells_not_specified
|
81
|
-
assert_equal '<tr class="
|
117
|
+
assert_equal '<tr class="ui-collection-result"><td class="title ui-state-empty"></td><td class="author_name ui-state-empty"></td></tr>', @row.html
|
82
118
|
end
|
83
119
|
|
84
120
|
def test_should_skip_missing_cells_if_colspan_replaces_missing_cells
|
85
121
|
@row.builder.title 'Hello World', :colspan => 2
|
86
|
-
assert_equal '<tr class="
|
122
|
+
assert_equal '<tr class="ui-collection-result"><td class="title" colspan="2">Hello World</td></tr>', @row.html
|
87
123
|
end
|
88
124
|
|
89
125
|
def test_should_not_skip_missing_cells_if_colspan_doesnt_replace_missing_cells
|
90
126
|
@row.builder.title 'Hello World'
|
91
|
-
assert_equal '<tr class="
|
127
|
+
assert_equal '<tr class="ui-collection-result"><td class="title">Hello World</td><td class="author_name ui-state-empty"></td></tr>', @row.html
|
92
128
|
end
|
93
129
|
end
|
94
130
|
|
95
131
|
class BodyRowAlternatingTest < Test::Unit::TestCase
|
96
132
|
def setup
|
97
|
-
|
98
|
-
@
|
133
|
+
table = TableHelper::CollectionTable.new([])
|
134
|
+
@header = table.header
|
135
|
+
|
136
|
+
@row = TableHelper::BodyRow.new(Object.new, @header)
|
99
137
|
@row.alternate = true
|
100
138
|
end
|
101
139
|
|
102
140
|
def test_should_add_alternate_class
|
103
|
-
assert_equal '<tr class="
|
141
|
+
assert_equal '<tr class="ui-collection-result ui-state-alternate"></tr>', @row.html
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_should_use_custom_altenrate_class_if_specified
|
145
|
+
original_alternate_class = TableHelper::BodyRow.alternate_class
|
146
|
+
TableHelper::BodyRow.alternate_class = 'ui-row-alternate'
|
147
|
+
|
148
|
+
row = TableHelper::BodyRow.new(Object.new, @header)
|
149
|
+
row.alternate = true
|
150
|
+
|
151
|
+
assert_equal '<tr class="ui-collection-result ui-row-alternate"></tr>', @row.html
|
152
|
+
ensure
|
153
|
+
TableHelper::BodyRow.alternate_class = original_alternate_class
|
104
154
|
end
|
105
155
|
end
|