table_helper 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/{CHANGELOG → CHANGELOG.rdoc} +11 -10
- data/{MIT-LICENSE → LICENSE} +0 -0
- data/{README → README.rdoc} +9 -9
- data/Rakefile +44 -36
- data/lib/table_helper.rb +173 -175
- data/lib/table_helper/body.rb +102 -105
- data/lib/table_helper/body_row.rb +58 -74
- data/lib/table_helper/cell.rb +44 -46
- data/lib/table_helper/collection_table.rb +53 -49
- data/lib/table_helper/footer.rb +36 -38
- data/lib/table_helper/header.rb +138 -140
- data/lib/table_helper/html_element.rb +39 -41
- data/lib/table_helper/row.rb +85 -87
- data/test/{table_helper_test.rb → helpers/table_helper_test.rb} +5 -5
- data/test/test_helper.rb +4 -7
- data/test/{body_row_test.rb → unit/body_row_test.rb} +13 -13
- data/test/{body_test.rb → unit/body_test.rb} +35 -35
- data/test/{cell_test.rb → unit/cell_test.rb} +7 -7
- data/test/{collection_table_test.rb → unit/collection_table_test.rb} +31 -31
- data/test/{footer_test.rb → unit/footer_test.rb} +12 -12
- data/test/{header_builder_test.rb → unit/header_builder_test.rb} +8 -8
- data/test/{header_test.rb → unit/header_test.rb} +36 -36
- data/test/{html_element_test.rb → unit/html_element_test.rb} +8 -8
- data/test/{row_builder_test.rb → unit/row_builder_test.rb} +9 -9
- data/test/{row_test.rb → unit/row_test.rb} +11 -11
- metadata +37 -35
data/lib/table_helper/body.rb
CHANGED
@@ -1,119 +1,116 @@
|
|
1
1
|
require 'table_helper/body_row'
|
2
2
|
|
3
|
-
module
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
3
|
+
module TableHelper
|
4
|
+
# Represents the body of the table. In HTML, you can think of this as
|
5
|
+
# the <tbody> tag of the table.
|
6
|
+
class Body < HtmlElement
|
7
|
+
# If set to :odd or :even, every odd or even-numbered row will have the
|
8
|
+
# class 'alternate' appended to its html attributes. Default is nil.
|
9
|
+
attr_accessor :alternate_rows
|
10
|
+
|
11
|
+
# The caption to display in the collection is empty
|
12
|
+
attr_accessor :empty_caption
|
13
|
+
|
14
|
+
def initialize(collection, header) #:nodoc:
|
15
|
+
super()
|
12
16
|
|
13
|
-
|
14
|
-
|
17
|
+
@collection, @header = collection, header
|
18
|
+
@empty_caption = 'No matches found.'
|
19
|
+
end
|
20
|
+
|
21
|
+
def alternate_rows=(value) #:nodoc:
|
22
|
+
raise ArgumentError, 'alternate_rows must be set to :odd or :even' if value && ![:odd, :even].include?(value)
|
23
|
+
@alternate_rows = value
|
24
|
+
end
|
25
|
+
|
26
|
+
# Builds the body of the table. This includes the actual data that is
|
27
|
+
# generated for each object in the collection.
|
28
|
+
#
|
29
|
+
# +build+ expects a block that defines the data in each cell. Each
|
30
|
+
# iteration of the block will provide the object being rendered, the row
|
31
|
+
# within the table that will be built and the index of the object. For
|
32
|
+
# example,
|
33
|
+
#
|
34
|
+
# body.build do |row, post, index|
|
35
|
+
# row.title "<div class=\"wrapped\">#{post.title}</div>"
|
36
|
+
# row.category post.category.name
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# In addition, to specifying the data, you can also modify the html
|
40
|
+
# options of the row. For more information on doing this, see the
|
41
|
+
# BodyRow class.
|
42
|
+
#
|
43
|
+
# If the collection is empty and +empty_caption+ is set on the body,
|
44
|
+
# then the actual body will be replaced by a single row containing the
|
45
|
+
# html that was stored in +empty_caption+.
|
46
|
+
#
|
47
|
+
# == Default Values
|
48
|
+
#
|
49
|
+
# Whenever possible, the default value of a cell will be set to the
|
50
|
+
# object's attribute with the same name as the cell. For example,
|
51
|
+
# if a Post consists of the attribute +title+, then the cell for the
|
52
|
+
# title will be prepopulated with that attribute's value:
|
53
|
+
#
|
54
|
+
# body.build do |row, post index|
|
55
|
+
# row.category post.category.name
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# <tt>row.title</tt> is already set to post.category so there's no need to
|
59
|
+
# manually set the value of that cell. However, it is always possible
|
60
|
+
# to override the default value like so:
|
61
|
+
#
|
62
|
+
# body.build do |row, post, index|
|
63
|
+
# row.title link_to(post.title, post_url(post))
|
64
|
+
# row.category post.category.name
|
65
|
+
# end
|
66
|
+
def build(&block)
|
67
|
+
@content = ''
|
15
68
|
|
16
|
-
|
17
|
-
|
69
|
+
# Display nothing if there are no objects to display
|
70
|
+
if @collection.empty? && @empty_caption
|
71
|
+
row = Row.new
|
72
|
+
row[:class] = 'no_content'
|
18
73
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
def alternate_rows=(value) #:nodoc:
|
24
|
-
raise ArgumentError, 'alternate_rows must be set to :odd or :even' if value && ![:odd, :even].include?(value)
|
25
|
-
@alternate_rows = value
|
26
|
-
end
|
27
|
-
|
28
|
-
# Builds the body of the table. This includes the actual data that is
|
29
|
-
# generated for each object in the collection.
|
30
|
-
#
|
31
|
-
# build expects a block that defines the data in each cell. Each
|
32
|
-
# iteration of the block will provide the object being rendered, the row
|
33
|
-
# within the table that will be built and the index of the object. For
|
34
|
-
# example,
|
35
|
-
#
|
36
|
-
# body.build do |row, post, index|
|
37
|
-
# row.title "<div class=\"wrapped\">#{post.title}</div>"
|
38
|
-
# row.category post.category.name
|
39
|
-
# end
|
40
|
-
#
|
41
|
-
# In addition, to specifying the data, you can also modify the html
|
42
|
-
# options of the row. For more information on doing this, see the
|
43
|
-
# BodyRow class.
|
44
|
-
#
|
45
|
-
# If the collection is empty and +empty_caption+ is set on the Body,
|
46
|
-
# then the actual body will be replaced by a single row containing the
|
47
|
-
# html that was stored in +empty_caption+.
|
48
|
-
#
|
49
|
-
# == Default Values
|
50
|
-
#
|
51
|
-
# Whenever possible, the default value of a cell will be set to the
|
52
|
-
# object's attribute with the same name as the cell. For example,
|
53
|
-
# if a Post consists of the attribute +title+, then the cell for the
|
54
|
-
# title will be prepopulated with that attribute's value:
|
55
|
-
#
|
56
|
-
# body.build do |row, post index|
|
57
|
-
# row.category post.category.name
|
58
|
-
# end
|
59
|
-
#
|
60
|
-
# +row.title+ is already set to post.category so there's no need to
|
61
|
-
# manually set the value of that cell. However, it is always possible
|
62
|
-
# to override the default value like so:
|
63
|
-
#
|
64
|
-
# body.build do |row, post, index|
|
65
|
-
# row.title link_to(post.title, post_url(post))
|
66
|
-
# row.category post.category.name
|
67
|
-
# end
|
68
|
-
def build(&block)
|
69
|
-
@content = ''
|
74
|
+
html_options = {}
|
75
|
+
html_options[:colspan] = @header.column_names.size if @header.column_names.size > 1
|
76
|
+
row.cell nil, @empty_caption, html_options
|
70
77
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
html_options = {}
|
77
|
-
html_options[:colspan] = @header.column_names.size if @header.column_names.size > 1
|
78
|
-
row.cell nil, @empty_caption, html_options
|
79
|
-
|
80
|
-
@content << row.html
|
81
|
-
else
|
82
|
-
@collection.each_with_index do |object, i|
|
83
|
-
@content << build_row(object, i, &block)
|
84
|
-
end
|
78
|
+
@content << row.html
|
79
|
+
else
|
80
|
+
@collection.each_with_index do |object, i|
|
81
|
+
@content << build_row(object, i, &block)
|
85
82
|
end
|
86
|
-
|
87
|
-
@content
|
88
83
|
end
|
89
84
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
row.html
|
100
|
-
end
|
85
|
+
@content
|
86
|
+
end
|
87
|
+
|
88
|
+
# Builds a row for an object in the table.
|
89
|
+
#
|
90
|
+
# The provided block should set the values for each cell in the row.
|
91
|
+
def build_row(object, index = @collection.index(object), &block)
|
92
|
+
row = BodyRow.new(object, @header)
|
93
|
+
row.alternate = alternate_rows ? index.send("#{@alternate_rows}?") : false
|
101
94
|
|
102
|
-
|
103
|
-
html_options = @html_options.dup
|
104
|
-
html_options[:class] = (html_options[:class].to_s + ' alternate').strip if alternate_rows
|
105
|
-
|
106
|
-
content_tag(tag_name, content, html_options)
|
107
|
-
end
|
95
|
+
yield row.builder, object, index if block_given?
|
108
96
|
|
109
|
-
|
110
|
-
def tag_name
|
111
|
-
'tbody'
|
112
|
-
end
|
113
|
-
|
114
|
-
def content
|
115
|
-
@content
|
116
|
-
end
|
97
|
+
row.html
|
117
98
|
end
|
99
|
+
|
100
|
+
def html #:nodoc:
|
101
|
+
html_options = @html_options.dup
|
102
|
+
html_options[:class] = (html_options[:class].to_s + ' alternate').strip if alternate_rows
|
103
|
+
|
104
|
+
content_tag(tag_name, content, html_options)
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
def tag_name
|
109
|
+
'tbody'
|
110
|
+
end
|
111
|
+
|
112
|
+
def content
|
113
|
+
@content
|
114
|
+
end
|
118
115
|
end
|
119
116
|
end
|
@@ -1,86 +1,70 @@
|
|
1
1
|
require 'table_helper/row'
|
2
2
|
|
3
|
-
module
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
# r.border[:style] = 'color: #ff0000;'
|
21
|
-
#
|
22
|
-
# == Alternating rows
|
23
|
-
#
|
24
|
-
# Alternating rows can be automated by setting the +alternate+ property.
|
25
|
-
# For example,
|
26
|
-
#
|
27
|
-
# r = BodyRow.new
|
28
|
-
# r.alternate = true
|
29
|
-
class BodyRow < Row
|
30
|
-
# True if this is an alternating row, otherwise false. Default is false.
|
31
|
-
attr_accessor :alternate
|
3
|
+
module TableHelper
|
4
|
+
# Represents a single row within the body of a table. The row can consist
|
5
|
+
# of either data cells or header cells.
|
6
|
+
#
|
7
|
+
# == Alternating rows
|
8
|
+
#
|
9
|
+
# Alternating rows can be automated by setting the +alternate+ property.
|
10
|
+
# For example,
|
11
|
+
#
|
12
|
+
# r = BodyRow.new
|
13
|
+
# r.alternate = true
|
14
|
+
class BodyRow < Row
|
15
|
+
# True if this is an alternating row, otherwise false. Default is false.
|
16
|
+
attr_accessor :alternate
|
17
|
+
|
18
|
+
def initialize(object, header) #:nodoc:
|
19
|
+
super()
|
32
20
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
21
|
+
@header = header
|
22
|
+
@alternate = false
|
23
|
+
@html_options[:class] = ('row ' + @html_options[:class].to_s).strip
|
24
|
+
|
25
|
+
# For each column defined in the table, see if we can prepopulate the
|
26
|
+
# cell based on the data in the object. If not, we can at least
|
27
|
+
# provide shortcut accessors to the cell
|
28
|
+
@header.column_names.each do |column|
|
29
|
+
if object.respond_to?(column)
|
30
|
+
cell(column, object.send(column))
|
31
|
+
else
|
32
|
+
builder.define_cell(column)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Generates the html for this row in additional to the border row
|
38
|
+
# (if specified)
|
39
|
+
def html
|
40
|
+
original_options = @html_options.dup
|
41
|
+
@html_options[:class] = (@html_options[:class].to_s + ' alternate').strip if alternate
|
42
|
+
html = super
|
43
|
+
@html_options = original_options
|
44
|
+
html
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
# Builds the row's cells based on the order of the columns in the
|
49
|
+
# header. If a cell cannot be found for a specific column, then a blank
|
50
|
+
# cell is rendered.
|
51
|
+
def content
|
52
|
+
number_to_skip = 0 # Keeps track of the # of columns to skip
|
39
53
|
|
40
|
-
|
41
|
-
# cell based on the data in the object. If not, we can at least
|
42
|
-
# provide shortcut accessors to the cell
|
54
|
+
html = ''
|
43
55
|
@header.column_names.each do |column|
|
44
|
-
if
|
45
|
-
|
56
|
+
number_to_skip -= 1 and next if number_to_skip > 0
|
57
|
+
|
58
|
+
if cell = @cells[column]
|
59
|
+
number_to_skip = (cell[:colspan] || 1) - 1
|
46
60
|
else
|
47
|
-
|
61
|
+
cell = Cell.new(column, '', :class => 'empty')
|
48
62
|
end
|
63
|
+
|
64
|
+
html << cell.html
|
49
65
|
end
|
50
|
-
|
51
|
-
|
52
|
-
# Generates the html for this row in additional to the border row
|
53
|
-
# (if specified)
|
54
|
-
def html
|
55
|
-
original_options = @html_options.dup
|
56
|
-
@html_options[:class] = (@html_options[:class].to_s + ' alternate').strip if alternate
|
57
|
-
html = super
|
58
|
-
@html_options = original_options
|
66
|
+
|
59
67
|
html
|
60
68
|
end
|
61
|
-
|
62
|
-
private
|
63
|
-
# Builds the row's cells based on the order of the columns in the
|
64
|
-
# header. If a cell cannot be found for a specific column, then a blank
|
65
|
-
# cell is rendered.
|
66
|
-
def content
|
67
|
-
number_to_skip = 0 # Keeps track of the # of columns to skip
|
68
|
-
|
69
|
-
html = ''
|
70
|
-
@header.column_names.each do |column|
|
71
|
-
number_to_skip -= 1 and next if number_to_skip > 0
|
72
|
-
|
73
|
-
if cell = @cells[column]
|
74
|
-
number_to_skip = (cell[:colspan] || 1) - 1
|
75
|
-
else
|
76
|
-
cell = Cell.new(column, '', :class => 'empty')
|
77
|
-
end
|
78
|
-
|
79
|
-
html << cell.html
|
80
|
-
end
|
81
|
-
|
82
|
-
html
|
83
|
-
end
|
84
|
-
end
|
85
69
|
end
|
86
70
|
end
|
data/lib/table_helper/cell.rb
CHANGED
@@ -1,51 +1,49 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
super(html_options)
|
27
|
-
|
28
|
-
@content = content
|
29
|
-
@html_options[:class] = ("#{class_name} " + @html_options[:class].to_s).strip if class_name
|
30
|
-
|
31
|
-
self.content_type = :data
|
32
|
-
end
|
1
|
+
module TableHelper
|
2
|
+
# Represents a single cell within a table. This can either be a regular
|
3
|
+
# data cell (td) or a header cell (th). By default, all cells will have
|
4
|
+
# their column name appended to the cell's class attribute.
|
5
|
+
#
|
6
|
+
# == Creating data cells
|
7
|
+
#
|
8
|
+
# Cell.new(:author, 'John Doe').build
|
9
|
+
#
|
10
|
+
# ...would generate the following tag:
|
11
|
+
#
|
12
|
+
# <td class="author">John Doe</td>
|
13
|
+
#
|
14
|
+
# == Creating header cells
|
15
|
+
#
|
16
|
+
# c = Cell.new(:author, 'Author Name')
|
17
|
+
# c.content_type = :header
|
18
|
+
# c.build
|
19
|
+
#
|
20
|
+
# ...would generate the following tag:
|
21
|
+
#
|
22
|
+
# <th class="author">Author Name</th>
|
23
|
+
class Cell < HtmlElement
|
24
|
+
def initialize(class_name, content = class_name.to_s.titleize, html_options = {}) #:nodoc
|
25
|
+
super(html_options)
|
33
26
|
|
34
|
-
|
35
|
-
|
36
|
-
def content_type=(value)
|
37
|
-
raise ArgumentError, "content_type must be set to :data or :header, was: #{value.inspect}" unless [:data, :header].include?(value)
|
38
|
-
@content_type = value
|
39
|
-
end
|
27
|
+
@content = content
|
28
|
+
@html_options[:class] = ("#{class_name} " + @html_options[:class].to_s).strip if class_name
|
40
29
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
30
|
+
self.content_type = :data
|
31
|
+
end
|
32
|
+
|
33
|
+
# Indicates what type of content will be stored in this cell. This can
|
34
|
+
# be set to either :data or :header.
|
35
|
+
def content_type=(value)
|
36
|
+
raise ArgumentError, "content_type must be set to :data or :header, was: #{value.inspect}" unless [:data, :header].include?(value)
|
37
|
+
@content_type = value
|
49
38
|
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def tag_name
|
42
|
+
@content_type == :data ? 'td' : 'th'
|
43
|
+
end
|
44
|
+
|
45
|
+
def content
|
46
|
+
@content
|
47
|
+
end
|
50
48
|
end
|
51
49
|
end
|