tabletastic 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +24 -5
- data/VERSION +1 -1
- data/lib/tabletastic.rb +104 -26
- data/spec/tabletastic_spec.rb +78 -1
- data/tabletastic.gemspec +2 -2
- metadata +2 -2
data/README.rdoc
CHANGED
@@ -126,11 +126,10 @@ will produce html like:
|
|
126
126
|
For even greater flexibility, you can pass +data+ a block:
|
127
127
|
|
128
128
|
<% table_for(@posts) do |t| %>
|
129
|
-
<% t.data do %>
|
129
|
+
<% t.data :actions => :all do %>
|
130
130
|
<%= t.cell(:title, :cell_html => {:class => "titlestring"}) %>
|
131
131
|
<%= t.cell(:body, :heading => "Content") {|p| truncate(p.body, 30)} %>
|
132
132
|
<%= t.cell(:author) {|p| p.author && link_to(p.author.name, p.author) } %>
|
133
|
-
<%= t.cell(:edit, :heading => '') {|p| link_to "Edit", edit_post_path(p) } %>
|
134
133
|
<% end -%>
|
135
134
|
<% end %>
|
136
135
|
|
@@ -143,6 +142,8 @@ will product html like:
|
|
143
142
|
<th>Content</th>
|
144
143
|
<th>Author</th>
|
145
144
|
<th></th>
|
145
|
+
<th></th>
|
146
|
+
<th></th>
|
146
147
|
</tr>
|
147
148
|
</thead>
|
148
149
|
<tbody>
|
@@ -152,9 +153,15 @@ will product html like:
|
|
152
153
|
<td>
|
153
154
|
<a href="/authors/1">Jim Bean</a>
|
154
155
|
</td>
|
155
|
-
<td>
|
156
|
+
<td class="actions show_link">
|
157
|
+
<a href="/posts/1">Show</a>
|
158
|
+
</td>
|
159
|
+
<td class="actions edit_link">
|
156
160
|
<a href="/posts/1/edit">Edit</a>
|
157
161
|
</td>
|
162
|
+
<td class="actions destroy_link">
|
163
|
+
<a href="/posts/1/edit">Destroy</a> <!-- inline javascript omitted -->
|
164
|
+
</td>
|
158
165
|
</tr>
|
159
166
|
<tr class="post even" id="post_2">
|
160
167
|
<td class="titlestring">Second Post</td>
|
@@ -162,17 +169,29 @@ will product html like:
|
|
162
169
|
<td>
|
163
170
|
<a href="/authors/2">Jack Daniels</a>
|
164
171
|
</td>
|
165
|
-
<td>
|
172
|
+
<td class="actions show_link">
|
173
|
+
<a href="/posts/2">Show</a>
|
174
|
+
</td>
|
175
|
+
<td class="actions edit_link">
|
166
176
|
<a href="/posts/2/edit">Edit</a>
|
167
177
|
</td>
|
178
|
+
<td class="actions destroy_link">
|
179
|
+
<a href="/posts/2/edit">Destroy</a> <!-- inline javascript omitted -->
|
180
|
+
</td>
|
168
181
|
</tr>
|
169
182
|
<tr class="post odd" id="post_3">
|
170
183
|
<td class="titlestring">Something else</td>
|
171
184
|
<td>Blah!</td>
|
172
185
|
<td></td>
|
173
|
-
<td>
|
186
|
+
<td class="actions show_link">
|
187
|
+
<a href="/posts/3">Show</a>
|
188
|
+
</td>
|
189
|
+
<td class="actions edit_link">
|
174
190
|
<a href="/posts/3/edit">Edit</a>
|
175
191
|
</td>
|
192
|
+
<td class="actions destroy_link">
|
193
|
+
<a href="/posts/3/edit">Destroy</a> <!-- inline javascript omitted -->
|
194
|
+
</td>
|
176
195
|
</tr>
|
177
196
|
</tbody>
|
178
197
|
</table>
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.1
|
data/lib/tabletastic.rb
CHANGED
@@ -2,40 +2,85 @@ module Tabletastic
|
|
2
2
|
|
3
3
|
# returns and outputs a table for the given active record collection
|
4
4
|
def table_for(collection, *args)
|
5
|
+
klass = default_class_for(collection)
|
5
6
|
options = args.extract_options!
|
6
7
|
options[:html] ||= {}
|
7
|
-
options[:html][:id] ||= get_id_for(
|
8
|
+
options[:html][:id] ||= get_id_for(klass)
|
8
9
|
concat(tag(:table, options[:html], true))
|
9
|
-
yield TableBuilder.new(collection, self)
|
10
|
+
yield TableBuilder.new(collection, klass, self)
|
10
11
|
concat("</table>")
|
11
12
|
end
|
12
13
|
|
13
|
-
|
14
|
-
|
14
|
+
private
|
15
|
+
# Finds the class representing the objects within the collection
|
16
|
+
def default_class_for(collection)
|
17
|
+
if collection.respond_to?(:proxy_reflection)
|
18
|
+
collection.proxy_reflection.klass
|
19
|
+
elsif !collection.empty?
|
20
|
+
collection.first.class
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_id_for(klass)
|
25
|
+
klass.to_s.tableize
|
15
26
|
end
|
16
27
|
|
17
28
|
class TableBuilder
|
18
29
|
@@association_methods = %w[display_name full_name name title username login value to_s]
|
19
30
|
attr_accessor :field_labels
|
31
|
+
attr_reader :collection, :klass
|
20
32
|
|
21
|
-
def initialize(collection, template)
|
22
|
-
@collection, @template = collection, template
|
33
|
+
def initialize(collection, klass, template)
|
34
|
+
@collection, @klass, @template = collection, klass, template
|
23
35
|
end
|
24
36
|
|
25
37
|
# builds up the fields that the table will include,
|
26
38
|
# returns table head and body with all data
|
27
|
-
|
39
|
+
#
|
40
|
+
# Can be used one of three ways:
|
41
|
+
#
|
42
|
+
# * Alone, which will try to detect all content columns on the resource
|
43
|
+
# * With an array of methods to call on each element in the collection
|
44
|
+
# * With a block, which assumes you will use +cell+ method to build up
|
45
|
+
# the table
|
46
|
+
#
|
47
|
+
#
|
48
|
+
def data(*args, &block) # :yields: tablebody
|
49
|
+
options = args.extract_options!
|
28
50
|
if block_given?
|
29
51
|
yield self
|
52
|
+
action_cells(options[:actions])
|
30
53
|
@template.concat(head)
|
31
54
|
@template.concat(body)
|
32
55
|
else
|
33
|
-
@fields = args
|
56
|
+
@fields = args.empty? ? fields : args
|
34
57
|
@field_labels = fields.map { |f| f.to_s.humanize }
|
58
|
+
action_cells(options[:actions])
|
35
59
|
[head, body].join("")
|
36
60
|
end
|
37
61
|
end
|
38
62
|
|
63
|
+
# individually specify a column, which will build up the header,
|
64
|
+
# and method or block to call on each resource in the array
|
65
|
+
#
|
66
|
+
# Should always be called within the block of +data+
|
67
|
+
#
|
68
|
+
# For example:
|
69
|
+
#
|
70
|
+
# t.cell :blah
|
71
|
+
#
|
72
|
+
# will simply call +blah+ on each resource
|
73
|
+
#
|
74
|
+
# You can also provide a block, which allows for other helpers
|
75
|
+
# or custom formatting. Since by default erb will just call +to_s+
|
76
|
+
# on an any element output, you can more greatly control the output:
|
77
|
+
#
|
78
|
+
# t.cell(:price) {|resource| number_to_currency(resource)}
|
79
|
+
#
|
80
|
+
# would output something like:
|
81
|
+
#
|
82
|
+
# <td>$1.50</td>
|
83
|
+
#
|
39
84
|
def cell(*args, &block)
|
40
85
|
options = args.extract_options!
|
41
86
|
@field_labels ||= []
|
@@ -43,12 +88,16 @@ module Tabletastic
|
|
43
88
|
|
44
89
|
method_or_attribute = args.first.to_sym
|
45
90
|
|
91
|
+
method_or_attribute_or_proc = if block_given?
|
92
|
+
block.to_proc
|
93
|
+
else
|
94
|
+
method_or_attribute
|
95
|
+
end
|
96
|
+
|
46
97
|
if cell_html = options.delete(:cell_html)
|
47
|
-
@fields << [
|
48
|
-
elsif block_given?
|
49
|
-
@fields << block.to_proc
|
98
|
+
@fields << [method_or_attribute_or_proc, cell_html]
|
50
99
|
else
|
51
|
-
@fields <<
|
100
|
+
@fields << method_or_attribute_or_proc
|
52
101
|
end
|
53
102
|
|
54
103
|
if heading = options.delete(:heading)
|
@@ -56,8 +105,9 @@ module Tabletastic
|
|
56
105
|
else
|
57
106
|
@field_labels << method_or_attribute.to_s.humanize
|
58
107
|
end
|
59
|
-
|
60
|
-
|
108
|
+
# Since this will likely be called with <%= %> (aka 'concat'), explicitly return an empty string
|
109
|
+
# This suppresses unwanted output
|
110
|
+
return ""
|
61
111
|
end
|
62
112
|
|
63
113
|
def head
|
@@ -85,12 +135,12 @@ module Tabletastic
|
|
85
135
|
@collection.inject("") do |rows, record|
|
86
136
|
rowclass = @template.cycle("odd","even")
|
87
137
|
rows += @template.content_tag_for(:tr, record, :class => rowclass) do
|
88
|
-
|
138
|
+
cells_for_row(record)
|
89
139
|
end
|
90
140
|
end
|
91
141
|
end
|
92
142
|
|
93
|
-
def
|
143
|
+
def cells_for_row(record)
|
94
144
|
fields.inject("") do |cells, field_or_array|
|
95
145
|
field = field_or_array
|
96
146
|
if field_or_array.is_a?(Array)
|
@@ -113,9 +163,34 @@ module Tabletastic
|
|
113
163
|
result.send(to_string) if to_string
|
114
164
|
end
|
115
165
|
|
166
|
+
# Used internally to build up cells for common CRUD actions
|
167
|
+
def action_cells(actions)
|
168
|
+
return if actions.blank?
|
169
|
+
actions = [actions] if !actions.respond_to?(:each)
|
170
|
+
actions = [:show, :edit, :destroy] if actions == [:all]
|
171
|
+
actions.each do |action|
|
172
|
+
action_link(action.to_sym)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Dynamically builds links for the action
|
177
|
+
def action_link(action)
|
178
|
+
html_class = "actions #{action.to_s}_link"
|
179
|
+
self.cell(action, :heading => "", :cell_html => {:class => html_class}) do |resource|
|
180
|
+
case action
|
181
|
+
when :show
|
182
|
+
@template.link_to("Show", resource)
|
183
|
+
when :edit
|
184
|
+
@template.link_to("Edit", @template.polymorphic_path(resource, :action => :edit))
|
185
|
+
when :destroy
|
186
|
+
@template.link_to("Destroy", resource, :method => :delete)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
116
191
|
def fields
|
117
192
|
return @fields if defined?(@fields)
|
118
|
-
@fields = @collection.empty? ? [] :
|
193
|
+
@fields = @collection.empty? ? [] : active_record_fields
|
119
194
|
end
|
120
195
|
|
121
196
|
protected
|
@@ -125,13 +200,14 @@ module Tabletastic
|
|
125
200
|
end
|
126
201
|
|
127
202
|
|
128
|
-
def
|
203
|
+
def active_record_fields
|
204
|
+
return [] if klass.blank?
|
129
205
|
# normal content columns
|
130
|
-
fields =
|
206
|
+
fields = klass.content_columns.map(&:name)
|
131
207
|
|
132
208
|
# active record associations
|
133
|
-
|
134
|
-
|
209
|
+
if klass.respond_to?(:reflect_on_all_associations)
|
210
|
+
associations = klass.reflect_on_all_associations(:belongs_to)
|
135
211
|
associations = associations.map(&:name)
|
136
212
|
fields += associations
|
137
213
|
end
|
@@ -141,16 +217,18 @@ module Tabletastic
|
|
141
217
|
fields = fields.map(&:to_sym)
|
142
218
|
end
|
143
219
|
|
220
|
+
def content_tag(name, content = nil, options = nil, escape = true, &block)
|
221
|
+
@template.content_tag(name, content, options, escape, &block)
|
222
|
+
end
|
223
|
+
|
224
|
+
private
|
225
|
+
|
144
226
|
def send_or_call(object, duck)
|
145
|
-
if duck.
|
227
|
+
if duck.respond_to?(:call)
|
146
228
|
duck.call(object)
|
147
229
|
else
|
148
230
|
object.send(duck)
|
149
231
|
end
|
150
232
|
end
|
151
|
-
|
152
|
-
def content_tag(name, content = nil, options = nil, escape = true, &block)
|
153
|
-
@template.content_tag(name, content, options, escape, &block)
|
154
|
-
end
|
155
233
|
end
|
156
234
|
end
|
data/spec/tabletastic_spec.rb
CHANGED
@@ -129,6 +129,57 @@ describe "Tabletastic#table_for" do
|
|
129
129
|
end
|
130
130
|
end
|
131
131
|
|
132
|
+
context "with options[:actions]" do
|
133
|
+
it "includes path to post for :show" do
|
134
|
+
table_for(@posts) do |t|
|
135
|
+
concat(t.data(:actions => :show))
|
136
|
+
end
|
137
|
+
output_buffer.should have_table_with_tag("a[@href=/posts/#{@post.id}]")
|
138
|
+
output_buffer.should have_table_with_tag("th", "")
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should have a cell with default class 'actions' and the action name" do
|
142
|
+
table_for(@posts) do |t|
|
143
|
+
concat(t.data(:actions => :show))
|
144
|
+
end
|
145
|
+
output_buffer.should have_tag("td.actions.show_link") do |td|
|
146
|
+
td.should have_tag("a")
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
it "includes path to post for :edit" do
|
151
|
+
table_for(@posts) do |t|
|
152
|
+
concat(t.data(:actions => :edit))
|
153
|
+
end
|
154
|
+
output_buffer.should have_tag("a[@href=/posts/#{@post.id}/edit]", "Edit")
|
155
|
+
end
|
156
|
+
|
157
|
+
it "includes path to post for :destroy" do
|
158
|
+
table_for(@posts) do |t|
|
159
|
+
concat(t.data(:actions => :destroy))
|
160
|
+
end
|
161
|
+
output_buffer.should have_table_with_tag("a[@href=/posts/#{@post.id}]", "Destroy")
|
162
|
+
output_buffer.should have_table_with_tag("th", "")
|
163
|
+
end
|
164
|
+
|
165
|
+
it "includes path to post for :show and :edit" do
|
166
|
+
table_for(@posts) do |t|
|
167
|
+
concat(t.data(:actions => [:show, :edit]))
|
168
|
+
end
|
169
|
+
output_buffer.should have_tag("td:nth-child(3) a[@href=/posts/#{@post.id}]", "Show")
|
170
|
+
output_buffer.should have_tag("td:nth-child(4) a[@href=/posts/#{@post.id}/edit]", "Edit")
|
171
|
+
end
|
172
|
+
|
173
|
+
it "includes path to post for :all" do
|
174
|
+
table_for(@posts) do |t|
|
175
|
+
concat(t.data(:actions => :all))
|
176
|
+
end
|
177
|
+
output_buffer.should have_tag("td:nth-child(3) a[@href=/posts/#{@post.id}]", "Show")
|
178
|
+
output_buffer.should have_tag("td:nth-child(4) a[@href=/posts/#{@post.id}/edit]", "Edit")
|
179
|
+
output_buffer.should have_tag("td:nth-child(5) a[@href=/posts/#{@post.id}]", "Destroy")
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
132
183
|
context "with a list of attributes" do
|
133
184
|
before do
|
134
185
|
table_for(@posts) do |t|
|
@@ -142,6 +193,20 @@ describe "Tabletastic#table_for" do
|
|
142
193
|
output_buffer.should_not have_table_with_tag("th", "Body")
|
143
194
|
end
|
144
195
|
end
|
196
|
+
|
197
|
+
context "with a list of attributes and options[:actions]" do
|
198
|
+
it "includes path to post for :show" do
|
199
|
+
table_for(@posts) do |t|
|
200
|
+
concat(t.data(:title, :created_at, :actions => :show))
|
201
|
+
end
|
202
|
+
output_buffer.should have_tag("th:nth-child(1)", "Title")
|
203
|
+
output_buffer.should have_tag("th:nth-child(2)", "Created at")
|
204
|
+
output_buffer.should have_tag("th:nth-child(3)", "")
|
205
|
+
output_buffer.should_not have_tag("th", "Body")
|
206
|
+
|
207
|
+
output_buffer.should have_tag("td:nth-child(3) a[@href=/posts/#{@post.id}]")
|
208
|
+
end
|
209
|
+
end
|
145
210
|
end
|
146
211
|
|
147
212
|
context "with a block" do
|
@@ -205,6 +270,18 @@ describe "Tabletastic#table_for" do
|
|
205
270
|
end
|
206
271
|
end
|
207
272
|
|
273
|
+
context "with options[:actions]" do
|
274
|
+
it "includes path to post for :show" do
|
275
|
+
table_for(@posts) do |t|
|
276
|
+
t.data(:actions => :show) do
|
277
|
+
concat(t.cell(:title))
|
278
|
+
concat(t.cell(:body))
|
279
|
+
end
|
280
|
+
end
|
281
|
+
output_buffer.should have_table_with_tag("td:nth-child(3) a[@href=/posts/#{@post.id}]")
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
208
285
|
context "and normal/association columns" do
|
209
286
|
before do
|
210
287
|
::Post.stub!(:reflect_on_all_associations).with(:belongs_to).and_return([@mock_reflection_belongs_to_author])
|
@@ -236,7 +313,7 @@ describe TableBuilder do
|
|
236
313
|
mock_everything
|
237
314
|
::Post.stub!(:content_columns).and_return([mock('column', :name => 'title'), mock('column', :name => 'body'), mock('column', :name => 'created_at')])
|
238
315
|
@posts = [@post, Post.new]
|
239
|
-
@builder = TableBuilder.new(@posts, nil)
|
316
|
+
@builder = TableBuilder.new(@posts, ::Post, nil)
|
240
317
|
end
|
241
318
|
|
242
319
|
it "should detect attributes" do
|
data/tabletastic.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{tabletastic}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Joshua Davey"]
|
12
|
-
s.date = %q{2009-11-
|
12
|
+
s.date = %q{2009-11-22}
|
13
13
|
s.description = %q{A table builder for active record collections that produces semantically rich and accessible markup}
|
14
14
|
s.email = %q{josh@joshuadavey.com}
|
15
15
|
s.extra_rdoc_files = [
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tabletastic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua Davey
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-11-
|
12
|
+
date: 2009-11-22 00:00:00 -06:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|