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/body.rb
CHANGED
@@ -4,34 +4,40 @@ module TableHelper
|
|
4
4
|
# Represents the body of the table. In HTML, you can think of this as
|
5
5
|
# the <tbody> tag of the table.
|
6
6
|
class Body < HtmlElement
|
7
|
+
# The css class to apply for all rows in the body
|
8
|
+
cattr_accessor :empty_caption_class
|
9
|
+
@@empty_caption_class = 'ui-collection-empty'
|
10
|
+
|
11
|
+
# The table this body is a part of
|
12
|
+
attr_reader :table
|
13
|
+
|
7
14
|
# If set to :odd or :even, every odd or even-numbered row will have the
|
8
|
-
# class
|
9
|
-
attr_accessor :
|
15
|
+
# alternate class appended to its html attributes. Default is nil.
|
16
|
+
attr_accessor :alternate
|
10
17
|
|
11
18
|
# The caption to display in the collection is empty
|
12
19
|
attr_accessor :empty_caption
|
13
20
|
|
14
|
-
def initialize(
|
21
|
+
def initialize(table) #:nodoc:
|
15
22
|
super()
|
16
23
|
|
17
|
-
@
|
24
|
+
@table = table
|
18
25
|
@empty_caption = 'No matches found.'
|
19
26
|
end
|
20
27
|
|
21
|
-
def
|
22
|
-
raise ArgumentError, '
|
23
|
-
@
|
28
|
+
def alternate=(value) #:nodoc:
|
29
|
+
raise ArgumentError, 'alternate must be set to :odd or :even' if value && ![:odd, :even].include?(value)
|
30
|
+
@alternate = value
|
24
31
|
end
|
25
32
|
|
26
33
|
# Builds the body of the table. This includes the actual data that is
|
27
34
|
# generated for each object in the collection.
|
28
35
|
#
|
29
|
-
# +
|
30
|
-
# iteration of the block will provide the
|
31
|
-
#
|
32
|
-
# example,
|
36
|
+
# +each+ expects a block that defines the data in each cell. Each
|
37
|
+
# iteration of the block will provide the row within the table, the object
|
38
|
+
# being rendered, and the index of the object. For example,
|
33
39
|
#
|
34
|
-
#
|
40
|
+
# t.rows.each do |row, post, index|
|
35
41
|
# row.title "<div class=\"wrapped\">#{post.title}</div>"
|
36
42
|
# row.category post.category.name
|
37
43
|
# end
|
@@ -51,66 +57,57 @@ module TableHelper
|
|
51
57
|
# if a Post consists of the attribute +title+, then the cell for the
|
52
58
|
# title will be prepopulated with that attribute's value:
|
53
59
|
#
|
54
|
-
#
|
55
|
-
# row.
|
60
|
+
# t.rows.each do |row, post index|
|
61
|
+
# row.title post.title
|
56
62
|
# end
|
57
63
|
#
|
58
64
|
# <tt>row.title</tt> is already set to post.category so there's no need to
|
59
65
|
# manually set the value of that cell. However, it is always possible
|
60
66
|
# to override the default value like so:
|
61
67
|
#
|
62
|
-
#
|
68
|
+
# t.rows.each do |row, post, index|
|
63
69
|
# row.title link_to(post.title, post_url(post))
|
64
70
|
# row.category post.category.name
|
65
71
|
# end
|
66
|
-
def
|
67
|
-
@
|
68
|
-
|
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'
|
73
|
-
|
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
|
77
|
-
|
78
|
-
@content << row.html
|
79
|
-
else
|
80
|
-
@collection.each_with_index do |object, i|
|
81
|
-
@content << build_row(object, i, &block)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
@content
|
72
|
+
def each(&block)
|
73
|
+
@builder = block
|
86
74
|
end
|
87
75
|
|
88
76
|
# Builds a row for an object in the table.
|
89
77
|
#
|
90
78
|
# The provided block should set the values for each cell in the row.
|
91
|
-
def build_row(object, index =
|
92
|
-
row = BodyRow.new(object,
|
93
|
-
row.alternate =
|
94
|
-
|
95
|
-
yield row.builder, object, index if block_given?
|
96
|
-
|
79
|
+
def build_row(object, index = table.collection.index(object))
|
80
|
+
row = BodyRow.new(object, self)
|
81
|
+
row.alternate = alternate ? index.send("#{alternate}?") : false
|
82
|
+
@builder.call(row.builder, object, index) if @builder
|
97
83
|
row.html
|
98
84
|
end
|
99
85
|
|
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
86
|
private
|
108
87
|
def tag_name
|
109
88
|
'tbody'
|
110
89
|
end
|
111
90
|
|
112
91
|
def content
|
113
|
-
|
92
|
+
content = ''
|
93
|
+
|
94
|
+
if table.empty? && @empty_caption
|
95
|
+
# No objects to display
|
96
|
+
row = Row.new(self)
|
97
|
+
row[:class] = empty_caption_class
|
98
|
+
|
99
|
+
html_options = {}
|
100
|
+
html_options[:colspan] = table.header.columns.size if table.header.columns.size > 1
|
101
|
+
row.cell nil, @empty_caption, html_options
|
102
|
+
|
103
|
+
content << row.html
|
104
|
+
else
|
105
|
+
table.collection.each_with_index do |object, i|
|
106
|
+
content << build_row(object, i)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
content
|
114
111
|
end
|
115
112
|
end
|
116
113
|
end
|
@@ -9,28 +9,32 @@ module TableHelper
|
|
9
9
|
# Alternating rows can be automated by setting the +alternate+ property.
|
10
10
|
# For example,
|
11
11
|
#
|
12
|
-
# r = BodyRow.new
|
12
|
+
# r = BodyRow.new(object, parent)
|
13
13
|
# r.alternate = true
|
14
14
|
class BodyRow < Row
|
15
|
+
# The css class to apply for all rows in the body
|
16
|
+
cattr_accessor :result_class
|
17
|
+
@@result_class = 'ui-collection-result'
|
18
|
+
|
19
|
+
# The css class to apply for rows that are marked as "alternate"
|
20
|
+
cattr_accessor :alternate_class
|
21
|
+
@@alternate_class = 'ui-state-alternate'
|
22
|
+
|
15
23
|
# True if this is an alternating row, otherwise false. Default is false.
|
16
24
|
attr_accessor :alternate
|
17
25
|
|
18
|
-
def initialize(object,
|
19
|
-
super()
|
26
|
+
def initialize(object, parent) #:nodoc:
|
27
|
+
super(parent)
|
20
28
|
|
21
|
-
@header = header
|
22
29
|
@alternate = false
|
23
|
-
|
30
|
+
self[:class] = "#{self[:class]} #{table.object_name} #{result_class}".strip
|
24
31
|
|
25
32
|
# For each column defined in the table, see if we can prepopulate the
|
26
33
|
# cell based on the data in the object. If not, we can at least
|
27
34
|
# provide shortcut accessors to the cell
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
else
|
32
|
-
builder.define_cell(column)
|
33
|
-
end
|
35
|
+
table.header.column_names.each do |column|
|
36
|
+
value = object.respond_to?(column) ? object.send(column) : nil
|
37
|
+
cell(column, value)
|
34
38
|
end
|
35
39
|
end
|
36
40
|
|
@@ -38,7 +42,7 @@ module TableHelper
|
|
38
42
|
# (if specified)
|
39
43
|
def html
|
40
44
|
original_options = @html_options.dup
|
41
|
-
|
45
|
+
self[:class] = "#{self[:class]} #{alternate_class}".strip if alternate
|
42
46
|
html = super
|
43
47
|
@html_options = original_options
|
44
48
|
html
|
@@ -52,13 +56,13 @@ module TableHelper
|
|
52
56
|
number_to_skip = 0 # Keeps track of the # of columns to skip
|
53
57
|
|
54
58
|
html = ''
|
55
|
-
|
59
|
+
table.header.column_names.each do |column|
|
56
60
|
number_to_skip -= 1 and next if number_to_skip > 0
|
57
61
|
|
58
62
|
if cell = @cells[column]
|
59
63
|
number_to_skip = (cell[:colspan] || 1) - 1
|
60
64
|
else
|
61
|
-
cell = Cell.new(column,
|
65
|
+
cell = Cell.new(column, nil)
|
62
66
|
end
|
63
67
|
|
64
68
|
html << cell.html
|
data/lib/table_helper/cell.rb
CHANGED
@@ -1,31 +1,46 @@
|
|
1
1
|
module TableHelper
|
2
2
|
# Represents a single cell within a table. This can either be a regular
|
3
3
|
# data cell (td) or a header cell (th). By default, all cells will have
|
4
|
-
# their
|
4
|
+
# their name appended to the cell's class attribute.
|
5
5
|
#
|
6
|
-
# ==
|
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
|
6
|
+
# == Examples
|
15
7
|
#
|
8
|
+
# # Data cell
|
9
|
+
# c = Cell.new(:author, 'John Doe')
|
10
|
+
# c.html # => <td class="author">John Doe</td>
|
11
|
+
#
|
12
|
+
# # Header cell
|
16
13
|
# c = Cell.new(:author, 'Author Name')
|
17
14
|
# c.content_type = :header
|
18
|
-
# c.
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
15
|
+
# c.html
|
16
|
+
#
|
17
|
+
# # With namespace
|
18
|
+
# c = Cell.new(:author, :namespace => 'post')
|
19
|
+
# c.content_type = :header
|
20
|
+
# c.html # => <td class="post-author">Author</td>
|
23
21
|
class Cell < HtmlElement
|
24
|
-
|
22
|
+
# The css class to apply to empty cells
|
23
|
+
cattr_accessor :empty_class
|
24
|
+
@@empty_class = 'ui-state-empty'
|
25
|
+
|
26
|
+
# The content to display within the cell
|
27
|
+
attr_reader :content
|
28
|
+
|
29
|
+
# The type of content this cell represents (:data or :header)
|
30
|
+
attr_reader :content_type
|
31
|
+
|
32
|
+
def initialize(name, content = name.to_s.titleize, html_options = {}) #:nodoc
|
33
|
+
html_options, content = content, name.to_s.titleize if content.is_a?(Hash)
|
34
|
+
namespace = html_options.delete(:namespace)
|
25
35
|
super(html_options)
|
26
36
|
|
27
|
-
@content = content
|
28
|
-
|
37
|
+
@content = content.to_s
|
38
|
+
|
39
|
+
if name
|
40
|
+
name = "#{namespace}-#{name}" unless namespace.blank?
|
41
|
+
self[:class] = "#{self[:class]} #{name}".strip
|
42
|
+
end
|
43
|
+
self[:class] = "#{self[:class]} #{empty_class}".strip if content.blank?
|
29
44
|
|
30
45
|
self.content_type = :data
|
31
46
|
end
|
@@ -41,9 +56,5 @@ module TableHelper
|
|
41
56
|
def tag_name
|
42
57
|
@content_type == :data ? 'td' : 'th'
|
43
58
|
end
|
44
|
-
|
45
|
-
def content
|
46
|
-
@content
|
47
|
-
end
|
48
59
|
end
|
49
60
|
end
|
@@ -7,58 +7,132 @@ module TableHelper
|
|
7
7
|
# Represents a table that displays data for multiple objects within a
|
8
8
|
# collection.
|
9
9
|
class CollectionTable < HtmlElement
|
10
|
-
#
|
10
|
+
# The css class to apply for all menu bars
|
11
|
+
cattr_accessor :collection_class
|
12
|
+
@@collection_class = 'ui-collection'
|
13
|
+
|
14
|
+
# The class of objects that will be rendered in the table
|
15
|
+
attr_reader :klass
|
16
|
+
|
17
|
+
# The name of the objects in the collection. By default, this is the
|
18
|
+
# underscored name of the class of objects being rendered. This value is
|
19
|
+
# used for generates parts of the css classes in the table, such as rows
|
20
|
+
# and cells.
|
21
|
+
#
|
22
|
+
# For example, if the class is Post, then the object name will be "post".
|
23
|
+
attr_accessor :object_name
|
24
|
+
|
25
|
+
# The actual collection of objects being rendered in the table's body.
|
26
|
+
# This collection will also be used to help automatically determine what
|
27
|
+
# the class is.
|
28
|
+
attr_reader :collection
|
29
|
+
|
30
|
+
# The body of rows that will be generated for each object in the
|
31
|
+
# collection. The actual content for each row and the html options can
|
32
|
+
# be configured as well.
|
33
|
+
#
|
34
|
+
# See TableHelper::Body for more information.
|
11
35
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
|
36
|
+
# == Examples
|
37
|
+
#
|
38
|
+
# # Defining rows
|
39
|
+
# t.rows.each do |row, post, index|
|
40
|
+
# row.category post.category.name
|
41
|
+
# row.author post.author.name
|
42
|
+
# ...
|
43
|
+
# row[:style] = 'margin-top: 5px;'
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# # Customizing html options
|
47
|
+
# t.rows[:class] = 'pretty'
|
48
|
+
attr_reader :rows
|
49
|
+
|
50
|
+
# Creates a new table based on the objects within the given collection
|
51
|
+
def initialize(collection, klass = nil, html_options = {}) #:nodoc:
|
52
|
+
html_options, klass = klass, nil if klass.is_a?(Hash)
|
17
53
|
super(html_options)
|
18
54
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
:footer => false
|
27
|
-
)
|
55
|
+
@collection = collection
|
56
|
+
@klass = klass || default_class
|
57
|
+
@object_name = @klass ? @klass.name.split('::').last.underscore : nil
|
58
|
+
@html_options.reverse_merge!(:cellspacing => '0', :cellpadding => '0')
|
59
|
+
|
60
|
+
self[:class] = "#{self[:class]} #{@object_name.pluralize}".strip if @object_name
|
61
|
+
self[:class] = "#{self[:class]} #{collection_class}".strip
|
28
62
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
)
|
63
|
+
# Build actual content
|
64
|
+
@header = Header.new(self)
|
65
|
+
@rows = Body.new(self)
|
66
|
+
@footer = Footer.new(self)
|
33
67
|
|
34
|
-
|
35
|
-
@body = Body.new(collection, @header)
|
36
|
-
@footer = Footer.new(collection)
|
68
|
+
yield self if block_given?
|
37
69
|
end
|
38
70
|
|
39
|
-
#
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
71
|
+
# Will any rows be generated in the table's body?
|
72
|
+
def empty?
|
73
|
+
collection.empty?
|
74
|
+
end
|
75
|
+
|
76
|
+
# Creates a new header cell with the given name. Headers must be defined
|
77
|
+
# in the order in which they will be rendered.
|
78
|
+
#
|
79
|
+
# The actual caption and html options for the header can be configured
|
80
|
+
# as well. The caption determines what will be displayed in each cell.
|
81
|
+
#
|
82
|
+
# == Examples
|
83
|
+
#
|
84
|
+
# # Adding headers
|
85
|
+
# t.header :title # => <th class="post-title">Title</th>
|
86
|
+
# t.header :title, 'The Title' # => <th class="post-title">The Title</th>
|
87
|
+
# t.header :title, :class => 'pretty' # => <th class="post-title pretty">Title</th>
|
88
|
+
# t.header :title, 'The Title', :class => 'pretty' # => <th class="post-title pretty">The Title</th>
|
89
|
+
#
|
90
|
+
# # Customizing html options
|
91
|
+
# t.header[:class] = 'pretty'
|
92
|
+
def header(*args)
|
93
|
+
args.empty? ? @header : @header.column(*args)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Creates a new footer cell with the given name. Footers must be defined
|
97
|
+
# in the order in which they will be rendered.
|
98
|
+
#
|
99
|
+
# The actual caption and html options for the footer can be configured
|
100
|
+
# as well. The caption determines what will be displayed in each cell.
|
101
|
+
#
|
102
|
+
# == Examples
|
103
|
+
#
|
104
|
+
# # Adding footers
|
105
|
+
# t.footer :total # => <td class="post-total"></th>
|
106
|
+
# t.footer :total, 5 # => <td class="post-total">5</th>
|
107
|
+
# t.footer :total, :class => 'pretty' # => <td class="post-total pretty"></th>
|
108
|
+
# t.footer :total, 5, :class => 'pretty' # => <td class="post-total pretty">5</th>
|
109
|
+
#
|
110
|
+
# # Customizing html options
|
111
|
+
# t.footer[:class] = 'pretty'
|
112
|
+
def footer(*args)
|
113
|
+
args.empty? ? @footer : @footer.cell(*args)
|
53
114
|
end
|
54
115
|
|
55
116
|
private
|
117
|
+
# Finds the class representing the objects within the collection
|
118
|
+
def default_class
|
119
|
+
if collection.respond_to?(:proxy_reflection)
|
120
|
+
collection.proxy_reflection.klass
|
121
|
+
elsif !collection.empty?
|
122
|
+
collection.first.class
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
56
126
|
def tag_name
|
57
127
|
'table'
|
58
128
|
end
|
59
129
|
|
60
130
|
def content
|
61
|
-
|
131
|
+
content = ''
|
132
|
+
content << @header.html unless @header.empty?
|
133
|
+
content << @rows.html
|
134
|
+
content << @footer.html unless @footer.empty?
|
135
|
+
content
|
62
136
|
end
|
63
137
|
end
|
64
138
|
end
|