table_helper 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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