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.
@@ -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