table_helper 0.0.3

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.
@@ -0,0 +1,51 @@
1
+ module PluginAWeek #:nodoc:
2
+ module TableHelper
3
+ # Represents a single cell within a table. This can either be a regular
4
+ # data cell (td) or a header cell (th). By default, all cells will have
5
+ # their column name appended to the cell's class attribute.
6
+ #
7
+ # == Creating data cells
8
+ #
9
+ # Cell.new(:author, 'John Doe').build
10
+ #
11
+ # ...would generate the following tag:
12
+ #
13
+ # <td class="author">John Doe</td>
14
+ #
15
+ # == Creating header cells
16
+ #
17
+ # c = Cell.new(:author, 'Author Name')
18
+ # c.content_type = :header
19
+ # c.build
20
+ #
21
+ # ...would generate the following tag:
22
+ #
23
+ # <th class="author">Author Name</th>
24
+ class Cell < HtmlElement
25
+ def initialize(class_name, content = class_name.to_s.titleize, html_options = {}) #:nodoc
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
33
+
34
+ # Indicates what type of content will be stored in this cell. This can
35
+ # either be set to either :data or :header.
36
+ def content_type=(value)
37
+ raise ArgumentError, "content_type must be set to :data or :header, was: #{value.inspect}" if ![:data, :header].include?(value)
38
+ @content_type = value
39
+ end
40
+
41
+ private
42
+ def tag_name
43
+ @content_type == :data ? 'td' : 'th'
44
+ end
45
+
46
+ def content
47
+ @content
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,60 @@
1
+ require 'table_helper/html_element'
2
+ require 'table_helper/header'
3
+ require 'table_helper/body'
4
+ require 'table_helper/footer'
5
+
6
+ module PluginAWeek #:nodoc:
7
+ module TableHelper
8
+ # Represents a table that is displaying data for multiple objects within
9
+ # a collection.
10
+ class CollectionTable < HtmlElement
11
+ def initialize(collection, options = {}, html_options = {}) #:nodoc:
12
+ super(html_options)
13
+
14
+ options.assert_valid_keys(
15
+ :class,
16
+ :footer,
17
+ :header
18
+ )
19
+ @options = options.reverse_merge(
20
+ :header => true,
21
+ :footer => false
22
+ )
23
+
24
+ @html_options.reverse_merge!(
25
+ :cellspacing => '0',
26
+ :cellpadding => '0'
27
+ )
28
+
29
+ @header = Header.new(collection, options[:class])
30
+ @body = Body.new(collection, @header)
31
+ @footer = Footer.new(collection)
32
+ end
33
+
34
+ # Builds the table by rendering a header, body, and footer.
35
+ def build(&block)
36
+ @body.build # Build with the defaults
37
+
38
+ elements = []
39
+ elements << @header if @options[:header]
40
+ elements << @body
41
+ elements << @footer if @options[:footer]
42
+
43
+ yield *elements if block_given?
44
+
45
+ @content = ''
46
+ elements.each {|element| @content << element.html}
47
+ @content
48
+ end
49
+
50
+ private
51
+ def tag_name
52
+ 'table'
53
+ end
54
+
55
+ def content
56
+ @content
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,47 @@
1
+ require 'table_helper/row'
2
+
3
+ module PluginAWeek #:nodoc:
4
+ module TableHelper
5
+ # Represents the header of the table. In HTML, you can think of this as
6
+ # the tfoot tag of the table.
7
+ class Footer < HtmlElement
8
+ # The actual footer row
9
+ attr_reader :row
10
+
11
+ delegate :cell,
12
+ :to => :row
13
+
14
+ # Whether or not the footer should be hidden when the collection is
15
+ # empty. Default is true.
16
+ attr_accessor :hide_when_empty
17
+
18
+ def initialize(collection) #:nodoc:
19
+ super()
20
+
21
+ @collection = collection
22
+ @row = Row.new
23
+ @hide_when_empty = true
24
+ end
25
+
26
+ def html #:nodoc:
27
+ html_options = @html_options.dup
28
+ html_options[:style] = 'display: none;' if @collection.empty? && hide_when_empty
29
+
30
+ content_tag(tag_name, content, html_options)
31
+ end
32
+
33
+ private
34
+ def tag_name
35
+ 'tfoot'
36
+ end
37
+
38
+ # Generates the html for the footer. The footer generally consists of a
39
+ # summary of the data in the body. This row will be wrapped inside of
40
+ # a tfoot tag. If the collection is empty and hide_when_empty was set
41
+ # to true, then the footer will be hidden.
42
+ def content
43
+ @row.html
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,125 @@
1
+ require 'table_helper/row'
2
+
3
+ module PluginAWeek #:nodoc:
4
+ module TableHelper
5
+ # Represents the header of the table. In HTML, you can think of this as
6
+ # the <thead> tag of the table.
7
+ class Header < HtmlElement
8
+ # The actual header row
9
+ attr_reader :row
10
+
11
+ # Whether or not the header should be hidden when the collection is
12
+ # empty. Default is true.
13
+ attr_accessor :hide_when_empty
14
+
15
+ # Creates a new header for a collection that contains objects of the
16
+ # given class.
17
+ #
18
+ # If the class is known, then the header will be pre-filled with
19
+ # the columns defined in that class (assuming it's an ActiveRecord
20
+ # class).
21
+ def initialize(collection, klass = nil)
22
+ super()
23
+
24
+ @collection = collection
25
+ @row = Row.new
26
+
27
+ @hide_when_empty = true
28
+ @customized = true
29
+
30
+ # If we know what class the objects in the collection are and we can
31
+ # figure out what columns are defined in that class, then we can
32
+ # pre-fill the header with those columns so that the user doesn't
33
+ # have to
34
+ klass ||= class_for_collection(collection)
35
+ if klass && klass.respond_to?(:column_names)
36
+ klass.column_names.each {|name| column(name)}
37
+ @customized = false
38
+ end
39
+ end
40
+
41
+ # Gets the names of all of the columns being displayed in the table
42
+ def column_names
43
+ row.cell_names
44
+ end
45
+
46
+ # Creates a new column with the specified caption. Columns must be
47
+ # defined in the order in which they will be rendered.
48
+ #
49
+ # The caption determines what will be displayed in each cell of the
50
+ # header (if the header is rendered). For example,
51
+ #
52
+ # header.column :title, 'The Title'
53
+ #
54
+ # ...will create a column the displays "The Title" in the cell.
55
+ #
56
+ # = Setting html options
57
+ #
58
+ # In addition to customizing the content of the column, you can also
59
+ # specify html options like so:
60
+ #
61
+ # header.column :title, 'The Title', :class => 'pretty'
62
+ def column(name, *args)
63
+ # Clear the header row if this is being customized by the user
64
+ if !@customized
65
+ @customized = true
66
+
67
+ # Remove all of the shortcut methods
68
+ column_names.each do |column|
69
+ klass = class << self; self; end
70
+ klass.class_eval do
71
+ remove_method(column)
72
+ end
73
+ end
74
+
75
+ @row.clear
76
+ end
77
+
78
+ column = @row.cell(name, *args)
79
+ column.content_type = :header
80
+ column[:scope] ||= 'col'
81
+
82
+ # Define a shortcut method to the cell
83
+ name = name.to_s.gsub('-', '_')
84
+ unless respond_to?(name)
85
+ instance_eval <<-end_eval
86
+ def #{name}(*args)
87
+ if args.empty?
88
+ @row.#{name}
89
+ else
90
+ @row.#{name}(*args)
91
+ end
92
+ end
93
+ end_eval
94
+ end
95
+
96
+ column
97
+ end
98
+
99
+ # Creates and returns the generated html for the header
100
+ def html
101
+ html_options = @html_options.dup
102
+ html_options[:style] = 'display: none;' if @collection.empty? && hide_when_empty
103
+
104
+ content_tag(tag_name, content, html_options)
105
+ end
106
+
107
+ private
108
+ def tag_name
109
+ 'thead'
110
+ end
111
+
112
+ def content
113
+ @row.html
114
+ end
115
+
116
+ def class_for_collection(collection)
117
+ if collection.respond_to?(:proxy_reflection)
118
+ collection.proxy_reflection.klass
119
+ elsif !collection.empty?
120
+ collection.first.class
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,46 @@
1
+ module PluginAWeek #:nodoc:
2
+ module TableHelper
3
+ # Represents an HTML element
4
+ #
5
+ # == Modifying HTML options
6
+ #
7
+ # HTML options can normally be specified when creating the element.
8
+ # However, if they need to be modified after the element has been created,
9
+ # you can access the properties like so:
10
+ #
11
+ # r = Row.new
12
+ # r[:style] = 'display: none;'
13
+ #
14
+ # or for a cell:
15
+ #
16
+ # c = Cell.new
17
+ # c[:style] = 'display: none;'
18
+ class HtmlElement
19
+ include ActionView::Helpers::TagHelper
20
+
21
+ delegate :[],
22
+ :[]=,
23
+ :to => '@html_options'
24
+
25
+ def initialize(html_options = {}) #:nodoc:
26
+ @html_options = html_options.symbolize_keys
27
+ end
28
+
29
+ # Generates the html representing this element
30
+ def html
31
+ content_tag(tag_name, content, @html_options)
32
+ end
33
+
34
+ private
35
+ # The name of the element tag to use (e.g. td, th, tr, etc.)
36
+ def tag_name
37
+ ''
38
+ end
39
+
40
+ # The content that will be displayed inside of the tag
41
+ def content
42
+ ''
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,72 @@
1
+ require 'table_helper/cell'
2
+
3
+ module PluginAWeek #:nodoc:
4
+ module TableHelper
5
+ # Represents a single row within a table. A row can consist of either
6
+ # data cells or header cells.
7
+ class Row < HtmlElement
8
+ def initialize #:nodoc:
9
+ super
10
+
11
+ @cells = ActiveSupport::OrderedHash.new
12
+ end
13
+
14
+ # Creates a new cell with the given name and generates shortcut
15
+ # accessors for the method.
16
+ def cell(name, *args)
17
+ name = name.to_s if name
18
+
19
+ cell = Cell.new(name, *args)
20
+ @cells[name] = cell
21
+
22
+ define_cell_accessor(name) if name && !respond_to?(name)
23
+
24
+ cell
25
+ end
26
+
27
+ # The names of all cells in this row
28
+ def cell_names
29
+ @cells.keys
30
+ end
31
+
32
+ # Clears all of the current cells from the row
33
+ def clear
34
+ cell_names.each do |name|
35
+ klass = class << self; self; end
36
+ klass.class_eval do
37
+ remove_method(name)
38
+ end
39
+ end
40
+
41
+ @cells.clear
42
+ end
43
+
44
+ private
45
+ # Defines the accessor method for the given cell name. For example, if
46
+ # a cell with the name :title was defined, then the cell would be able
47
+ # to be read and written like so:
48
+ #
49
+ # row.title #=> Accesses the title
50
+ # row.title "Page Title" #=> Creates a new cell with "Page Title" as the content
51
+ def define_cell_accessor(name)
52
+ instance_eval <<-end_eval
53
+ def #{name.gsub('-', '_')}(*args)
54
+ if args.empty?
55
+ @cells[#{name.inspect}]
56
+ else
57
+ cell(#{name.inspect}, *args)
58
+ end
59
+ end
60
+ end_eval
61
+ end
62
+
63
+ def tag_name
64
+ 'tr'
65
+ end
66
+
67
+ def content
68
+ @cells.values.map(&:html).join
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,182 @@
1
+ require 'table_helper/collection_table'
2
+
3
+ module PluginAWeek #:nodoc:
4
+ # Provides a set of methods for turning a collection into a table.
5
+ #
6
+ # == Basic Example
7
+ #
8
+ # This example shows the most basic usage of +collection_table+ which takes
9
+ # information about a collection, the objects in them, the columns defined
10
+ # for the class, and generates a table based on that.
11
+ #
12
+ # Support you have a table generated by a migration like so:
13
+ #
14
+ # class CreatePeople < ActiveRecord::Base
15
+ # def self.up
16
+ # create_table do |t|
17
+ # t.string :first_name
18
+ # t.string :last_name
19
+ # t.integer :company_id
20
+ # t.string :role
21
+ # end
22
+ # end
23
+ # end
24
+ #
25
+ # ...then invoking the helper:
26
+ #
27
+ # <%= collection_table Person.find(:all) %>
28
+ #
29
+ # ...is compiled to (formatted here for the sake of sanity):
30
+ #
31
+ # <table cellpadding="0" cellspacing="0">
32
+ # <thead>
33
+ # <tr>
34
+ # <th class="first_name" scope="col">First Name</th>
35
+ # <th class="last_name" scope="col">Last Name</th>
36
+ # <th class="company_id" scope="col">Company</th>
37
+ # <th class="role" scope="col">Role</th>
38
+ # </tr>
39
+ # </thead>
40
+ # <tbody>
41
+ # <tr class="row">
42
+ # <td class="first_name">John</td>
43
+ # <td class="last_name">Doe</td>
44
+ # <td class="company_id">1</td>
45
+ # <td class="role">President</td>
46
+ # </tr>
47
+ # <tr class="row">
48
+ # <td class="first_name">Jane</td>
49
+ # <td class="last_name">Doe</td>
50
+ # <td class="company_id">1</td>
51
+ # <td class="role">Vice-President</td>
52
+ # </tr>
53
+ # </tbody>
54
+ # <table>
55
+ #
56
+ # == Advanced Example
57
+ #
58
+ # This example below shows how +collection_table+ can be customized to show
59
+ # specific headers, content, and footers.
60
+ #
61
+ # <%=
62
+ # collection_table(@posts, {}, :id => 'posts', :class => 'summary') do |header, body|
63
+ # header.column :title
64
+ # header.column :category
65
+ # header.column :author
66
+ # header.column :publish_date, 'Date<br \>Published'
67
+ # header.column :num_comments, '# Comments'
68
+ # header.column :num_trackbacks, '# Trackbacks'
69
+ #
70
+ # body.alternate = true
71
+ # body.build do |row, post, index|
72
+ # row.category post.category.name
73
+ # row.author post.author.name
74
+ # row.publish_date time_ago_in_words(post.published_on)
75
+ # row.num_comments post.comments.empty? ? '-' : post.comments.size
76
+ # row.num_trackbacks post.trackbacks.empty? ? '-' : post.trackbacks.size
77
+ # end
78
+ # end
79
+ # %>
80
+ #
81
+ # ...is compiled to (formatted here for the sake of sanity):
82
+ #
83
+ # <table cellpadding="0" cellspacing="0" class="summary" id="posts">
84
+ # <thead>
85
+ # <tr>
86
+ # <th class="title" scope="col">Title</th>
87
+ # <th class="category" scope="col">Category</th>
88
+ # <th class="author" scope="col">Author</th>
89
+ # <th class="publish_date" scope="col">Date<br \>Published</th>
90
+ # <th class="num_comments" scope="col"># Comments</th>
91
+ # <th class="num_trackbacks" scope="col"># Trackbacks</th>
92
+ # </tr>
93
+ # </thead>
94
+ # <tbody class="alternate">
95
+ # <tr class="row">
96
+ # <td class="title">Open-source projects: The good, the bad, and the ugly</td>
97
+ # <td class="category">General</td>
98
+ # <td class="author">John Doe</td>
99
+ # <td class="publish_date">23 days</td>
100
+ # <td class="num_comments">-</td>
101
+ # <td class="num_trackbacks">-</td>
102
+ # </tr>
103
+ # <tr class="row alternate">
104
+ # <td class="title">5 reasons you should care about Rails</td>
105
+ # <td class="category">Rails</td><td class="author">John Q. Public</td>
106
+ # <td class="publish_date">21 days</td>
107
+ # <td class="num_comments">-</td>
108
+ # <td class="num_trackbacks">-</td>
109
+ # </tr>
110
+ # <tr class="row">
111
+ # <td class="title">Deprecation: Stop digging yourself a hole</td>
112
+ # <td class="category">Rails</td>
113
+ # <td class="author">Jane Doe</td>
114
+ # <td class="publish_date">17 days</td>
115
+ # <td class="num_comments">-</td>
116
+ # <td class="num_trackbacks">-</td>
117
+ # </tr>
118
+ # <tr class="row alternate">
119
+ # <td class="title">Jumpstart your Rails career at RailsConf 2007</td>
120
+ # <td class="category">Conferences</td>
121
+ # <td class="author">Jane Doe</td>
122
+ # <td class="publish_date">4 days</td>
123
+ # <td class="num_comments">-</td>
124
+ # <td class="num_trackbacks">-</td>
125
+ # </tr>
126
+ # <tr class="row">
127
+ # <td class="title">Getting some REST</td>
128
+ # <td class="category">Rails</td>
129
+ # <td class="author">John Doe</td>
130
+ # <td class="publish_date">about 18 hours</td>
131
+ # <td class="num_comments">-</td>
132
+ # <td class="num_trackbacks">-</td>
133
+ # </tr>
134
+ # </tbody>
135
+ # </table>
136
+ #
137
+ # == Creating footers
138
+ #
139
+ # Footers allow you to show some sort of summary information based on the
140
+ # data displayed in the body of the table. Below is an example:
141
+ #
142
+ # <%
143
+ # collection_table(@posts, :footer => true) do |header, body, footer|
144
+ # header.column :title
145
+ # header.column :category
146
+ # header.column :author
147
+ # header.column :publish_date, 'Date<br \>Published'
148
+ # header.column :num_comments, '# Comments'
149
+ # header.column :num_trackbacks, '# Trackbacks'
150
+ #
151
+ # body.alternate = true
152
+ # body.build do |row, post, index|
153
+ # row.category post.category.name
154
+ # row.author post.author.name
155
+ # row.publish_date time_ago_in_words(post.published_on)
156
+ # row.num_comments post.comments.empty? ? '-' : post.comments.size
157
+ # row.num_trackbacks post.trackbacks.empty? ? '-' : post.trackbacks.size
158
+ # end
159
+ #
160
+ # footer.cell :num_comments, @posts.inject(0) {|sum, post| sum += post.comments.size}
161
+ # footer.cell :num_trackbacks, @posts.inject(0) {|sum, post| sum += post.trackbacks.size}
162
+ # end
163
+ # %>
164
+ module TableHelper
165
+ # Creates a new table based on the given collection
166
+ #
167
+ # Configuration options:
168
+ #
169
+ # * +class+ - Specify the type of objects expected in the collection if it can't be guessed from its contents.
170
+ # * +header+ - Specify if a header (thead) should be built into the table. Default is true.
171
+ # * +footer+ - Specify if a footer (tfoot) should be built into the table. Default is false.
172
+ def collection_table(collection, options = {}, html_options = {}, &block)
173
+ table = CollectionTable.new(collection, options, html_options)
174
+ table.build(&block)
175
+ table.html
176
+ end
177
+ end
178
+ end
179
+
180
+ ActionController::Base.class_eval do
181
+ helper PluginAWeek::TableHelper
182
+ end