def table(collection, headers, options = {}, &proc) options.reverse_merge!({ :placeholder => 'Nothing to display', :caption => nil, :summary => nil, :footer => '' }) placeholder_unless collection.any?, options[:placeholder] do summary = options[:summary] || "A list of #{collection.first.class.to_s.pluralize}" output = "<table summary=\"#{summary}\">\n" output << content_tag('caption', options[:caption]) if options[:caption] output << "\t<caption>#{options[:caption]}</caption>\n" if options[:caption] output << content_tag('thead', content_tag('tr', headers.collect { |h| "\n\t" + content_tag('th', h) })) output << "<tfoot><tr>" + content_tag('th', options[:footer], :colspan => headers.size) + "</tr></tfoot>\n" if options[:footer] output << "<tbody>\n" concat(output, proc.binding) collection.each do |row| proc.call(row, cycle('odd', 'even')) end concat("</tbody>\n</table>\n", proc.binding) end end
Writing...
<% table(@posts, %w{ID title}) do |post, klass| -%> <tr class="<%= klass %>"> <td><%= post.id</td> <td><%= post.title </td> </tr> <% end -%>
results in...
<table summary="A list of posts"> <thead> <tr> <th>ID</th> <th>Title</th> </tr> </thead> <tfoot><tr><td colspan="2"></td></tr></tfoot> <tbody> <tr> <td>1</td> <td>My first post</td> </tr> </tbody> </table>
Or, when the collection is an empty array (collection.any? returns false), a placeholder message is displayed:
<p class="placeholder">Nothing to display</p>
So you pass in your collection and an array of strings as your table headers as the first two arguments, and the third is a hash of options you can use to set the contents of the table's summary-attribute, the caption and footer-elements and the placeholder.
The summary attribute defaults to "A list of [objects]", where 'objects' is derived from the class name of the collection.
The function finally takes a block for every element in the collection, yeilding it the element and either 'odd' or 'even' so you can use CSS-classes to apply a zebra stripes effect.