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.
@@ -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 'alternate' appended to its html attributes. Default is nil.
9
- attr_accessor :alternate_rows
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(collection, header) #:nodoc:
21
+ def initialize(table) #:nodoc:
15
22
  super()
16
23
 
17
- @collection, @header = collection, header
24
+ @table = table
18
25
  @empty_caption = 'No matches found.'
19
26
  end
20
27
 
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
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
- # +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,
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
- # body.build do |row, post, index|
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
- # body.build do |row, post index|
55
- # row.category post.category.name
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
- # body.build do |row, post, index|
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 build(&block)
67
- @content = ''
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 = @collection.index(object), &block)
92
- row = BodyRow.new(object, @header)
93
- row.alternate = alternate_rows ? index.send("#{@alternate_rows}?") : false
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
- @content
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, header) #:nodoc:
19
- super()
26
+ def initialize(object, parent) #:nodoc:
27
+ super(parent)
20
28
 
21
- @header = header
22
29
  @alternate = false
23
- @html_options[:class] = ('row ' + @html_options[:class].to_s).strip
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
- @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
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
- @html_options[:class] = (@html_options[:class].to_s + ' alternate').strip if alternate
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
- @header.column_names.each do |column|
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, '', :class => 'empty')
65
+ cell = Cell.new(column, nil)
62
66
  end
63
67
 
64
68
  html << cell.html
@@ -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 column name appended to the cell's class attribute.
4
+ # their name appended to the cell's class attribute.
5
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
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.build
19
- #
20
- # ...would generate the following tag:
21
- #
22
- # <th class="author">Author Name</th>
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
- def initialize(class_name, content = class_name.to_s.titleize, html_options = {}) #:nodoc
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
- @html_options[:class] = ("#{class_name} " + @html_options[:class].to_s).strip if class_name
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
- # Creates a new table based on the objects within the given collection.
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
- # Configuration options:
13
- # * +class+ - The actual class of the objects contained within the collection. This is used to help build the header columns.
14
- # * +header+ - Whether or not to display a header. Default is true.
15
- # * +footer+ - Whether or not to display a footer. Default is false.
16
- def initialize(collection, options = {}, html_options = {}) #:nodoc:
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
- options.assert_valid_keys(
20
- :class,
21
- :footer,
22
- :header
23
- )
24
- @options = options.reverse_merge(
25
- :header => true,
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
- @html_options.reverse_merge!(
30
- :cellspacing => '0',
31
- :cellpadding => '0'
32
- )
63
+ # Build actual content
64
+ @header = Header.new(self)
65
+ @rows = Body.new(self)
66
+ @footer = Footer.new(self)
33
67
 
34
- @header = Header.new(collection, options[:class])
35
- @body = Body.new(collection, @header)
36
- @footer = Footer.new(collection)
68
+ yield self if block_given?
37
69
  end
38
70
 
39
- # Builds the table by rendering a header (if enabled), body, and footer (if enabled).
40
- def build(&block)
41
- @body.build # Build with the defaults
42
-
43
- elements = []
44
- elements << @header.builder if @options[:header]
45
- elements << @body
46
- elements << @footer if @options[:footer]
47
-
48
- yield *elements if block_given?
49
-
50
- @content = ''
51
- elements.each {|element| @content << element.html}
52
- @content
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
- @content
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