table_helper 0.1.0 → 0.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/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
|