table_cloth 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -37,7 +37,7 @@ class UserTable < TableCloth::Base
37
37
  # Columns can be provided a block
38
38
  #
39
39
  # column :name do |object|
40
- # object.downcase
40
+ # object.name.downcase
41
41
  # end
42
42
  #
43
43
  # Columns can also have conditionals if you want.
@@ -55,7 +55,9 @@ class UserTable < TableCloth::Base
55
55
  # Pass a block with an arity of 2, (object, view context).
56
56
  # You can add as many actions as you want.
57
57
  #
58
- # action {|object, view| view.link_to "Edit", edit_object_path(object) }
58
+ # actions do
59
+ # action {|object| link_to "Edit", edit_object_path(object) }
60
+ # end
59
61
  end
60
62
  ```
61
63
 
@@ -104,8 +106,8 @@ A lot of tables have an actions column to give you the full CRUD effect. They ca
104
106
  ```
105
107
  class UserTable < TableCloth::Base
106
108
  column :name
107
- action {|object, view| view.link_to 'View', object }
108
- action(if: :admin?) {|object, view| view.link_to 'Delete', object, method: :delete }
109
+ action {|object| link_to 'View', object }
110
+ action(if: :admin?) {|object| link_to 'Delete', object, method: :delete }
109
111
 
110
112
  def admin?
111
113
  view.current_user.admin?
@@ -131,6 +133,22 @@ TableCloth::Configuration.configure do |config|
131
133
  end
132
134
  ```
133
135
 
136
+ You can also configure specific tables separately.
137
+
138
+ ```ruby
139
+ class UserTable < TableCloth::Base
140
+ column :name, :email
141
+ action(:edit) {|object| link_to "Edit", edit_object_path(object) }
142
+
143
+ config.table.class = ''
144
+ config.thead.class = ''
145
+ config.th.class = ''
146
+ config.tbody.class = ''
147
+ config.tr.class = ''
148
+ config.td.class = ''
149
+ end
150
+ ```
151
+
134
152
  You can set any value on table element configurations. For example:
135
153
 
136
154
  ```ruby
@@ -138,6 +156,36 @@ config.table.cellpadding = 1
138
156
  config.td.valign = 'top'
139
157
  ```
140
158
 
159
+ You also have the option to specify options on a specific column with the ```td_options``` key.
160
+
161
+ ```ruby
162
+ class UserTable < TableCloth::Base
163
+ column :name, td_options: { class: "awesome-column" }
164
+ end
165
+ ```
166
+
167
+ Not good enough? Fine... you can do row / column specific config as well for a TD.
168
+
169
+ ```ruby
170
+ class UserTable < TableCloth::Base
171
+ column :name do |user|
172
+ [user.name, {class: "#{user.type}-user"}]
173
+ end
174
+ end
175
+
176
+ ```
177
+
178
+ This would render something alow the lines of:
179
+
180
+ ```html
181
+ <td class="admin-user">Robert Ross</td>
182
+ ```
183
+
184
+ ## Thanks
185
+
186
+ - TableCloth was built during my open source time at [philosophie](http://gophilosophie.com)
187
+ - simple_form for the idea of ```simple_table_for```
188
+
141
189
  ## Contributing
142
190
 
143
191
  1. Fork it
@@ -145,4 +193,4 @@ config.td.valign = 'top'
145
193
  3. CREATE A SPEC.
146
194
  4. Commit your changes (`git commit -am 'Add some feature'`)
147
195
  5. Push to the branch (`git push origin my-new-feature`)
148
- 6. Create new Pull Request
196
+ 6. Create new Pull Request
@@ -23,5 +23,7 @@ class <%= class_name %>Table < TableCloth::Base
23
23
  # Pass a block with an arity of 2, (object, view context).
24
24
  # You can add as many actions as you want.
25
25
  #
26
- # action {|object, view| view.link_to "Edit", edit_object_path(object) }
27
- end
26
+ # actions do
27
+ # action {|object| link_to "Edit", edit_object_path(object) }
28
+ # end
29
+ end
data/lib/table_cloth.rb CHANGED
@@ -8,6 +8,7 @@ module TableCloth
8
8
  autoload :Builder, 'table_cloth/builder'
9
9
  autoload :Column, 'table_cloth/column'
10
10
  autoload :Action, 'table_cloth/action'
11
+ autoload :Actions, 'table_cloth/actions'
11
12
  autoload :Presenter, 'table_cloth/presenter'
12
13
  autoload :ActionViewExtension, 'table_cloth/action_view_extension'
13
14
 
@@ -19,10 +20,10 @@ module TableCloth
19
20
  autoload :Action, 'table_cloth/columns/action'
20
21
  end
21
22
 
22
- extend self
23
- def self.config_for(type)
23
+ def config_for(type)
24
24
  Configuration.config_for(type)
25
25
  end
26
+ module_function :config_for
26
27
  end
27
28
 
28
29
  TableCloth::Base.presenter ::TableCloth::Presenters::Default
@@ -0,0 +1,23 @@
1
+ module TableCloth
2
+ class Actions
3
+ attr_reader :column, :options
4
+
5
+ def initialize(options={}, &block)
6
+ @options = options
7
+ @column = Columns::Action.new(:actions, options)
8
+
9
+ block.arity > 0 ? block.call(self) : instance_eval(&block)
10
+ end
11
+
12
+ def action(*args, &block)
13
+ options = args.extract_options! || {}
14
+ options[:proc] = block if block_given?
15
+
16
+ column.actions << Action.new(options)
17
+ end
18
+
19
+ def all
20
+ column.actions
21
+ end
22
+ end
23
+ end
@@ -1,5 +1,7 @@
1
1
  module TableCloth
2
2
  class Base
3
+ NoPresenterError = Class.new(Exception)
4
+
3
5
  attr_reader :collection, :view
4
6
 
5
7
  def initialize(collection, view)
@@ -29,11 +31,9 @@ module TableCloth
29
31
  end
30
32
 
31
33
  def presenter(klass=nil)
32
- if klass
33
- @presenter = klass
34
- else
35
- @presenter || (superclass.respond_to?(:presenter) ? superclass.presenter : raise("No Presenter"))
36
- end
34
+ return @presenter = klass if klass
35
+
36
+ @presenter || (superclass.respond_to?(:presenter) ? superclass.presenter : NoPresenterError.new("No Presenter"))
37
37
  end
38
38
 
39
39
  def column(*args, &block)
@@ -61,20 +61,13 @@ module TableCloth
61
61
  @columns[name] = column
62
62
  end
63
63
 
64
- def action(*args, &block)
65
- options = args.extract_options! || {}
66
- options[:proc] = block if block_given?
67
-
68
- add_action Action.new(options)
69
- end
70
-
71
- def add_action(action)
72
- unless has_actions?
73
- columns[:actions] = Columns::Action.new(:actions)
64
+ def actions(options={}, &block)
65
+ if block_given?
66
+ actions = Actions.new(options, &block)
67
+ columns[:actions] = actions.column
74
68
  end
75
69
 
76
- columns[:actions].actions << action
77
- action
70
+ columns[:actions].actions
78
71
  end
79
72
 
80
73
  def has_actions?
@@ -9,7 +9,7 @@ module TableCloth
9
9
 
10
10
  def value(object, view, table=nil)
11
11
  if options[:proc] && options[:proc].respond_to?(:call)
12
- view.capture(object, view, &options[:proc])
12
+ view.instance_exec(object, view, &options[:proc])
13
13
  else
14
14
  object.send(name)
15
15
  end
@@ -2,11 +2,10 @@ module TableCloth
2
2
  module Columns
3
3
  class Action < Column
4
4
  def value(object, view_context, table)
5
- actions_html = actions.inject('') do |links, action|
5
+ actions_html = actions.each_with_object('') do |action, links|
6
6
  if action.available?(table)
7
- links + "\n" + view_context.capture(object, view_context, &action.options[:proc])
8
- else
9
- links
7
+ links << "\n"
8
+ links << view_context.instance_exec(object, view_context, &action.options[:proc])
10
9
  end
11
10
  end
12
11
 
@@ -21,7 +21,16 @@ module TableCloth
21
21
 
22
22
  def render_td(column, object)
23
23
  td_options = column.options.delete(:td_options) || {}
24
- wrapper_tag(:td, column.value(object, view_context, table), td_options)
24
+ value = column.value(object, view_context, table)
25
+
26
+ if value.is_a?(Array)
27
+ options = value.pop
28
+ value = value.shift
29
+
30
+ td_options.update(options)
31
+ end
32
+
33
+ wrapper_tag(:td, value, td_options)
25
34
  end
26
35
 
27
36
  def render_header
@@ -1,3 +1,3 @@
1
1
  module TableCloth
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,29 @@
1
+ require "spec_helper"
2
+
3
+ describe TableCloth::Actions do
4
+ subject { TableCloth::Actions }
5
+
6
+ context "block syntax" do
7
+
8
+ it "accepts a block" do
9
+ actions = subject.new do
10
+ action { 'Edit' }
11
+ end
12
+
13
+ actions.column.should have(1).actions
14
+ end
15
+
16
+ it "accepts options with a block" do
17
+ expect do
18
+ subject.new(class: "actions") do
19
+ action { "Edit" }
20
+ end
21
+ end.not_to raise_error
22
+ end
23
+
24
+ it ".all returns all actions for the column" do
25
+ actions = subject.new { action { "Edit" } }
26
+ actions.should have(1).all
27
+ end
28
+ end
29
+ end
@@ -48,12 +48,15 @@ describe TableCloth::Base do
48
48
  end
49
49
 
50
50
  it '.column_names includes actions when given' do
51
- subject.action { '/' }
51
+ subject.actions { action { } }
52
52
  subject.new([], view_context).column_names.should include 'Actions'
53
53
  end
54
54
 
55
55
  it '.column_names does not include actions if all action conditions fail' do
56
- subject.action(if: :admin?) { '/' }
56
+ subject.actions do
57
+ action(if: :admin?) { '/' }
58
+ end
59
+
57
60
  table = subject.new([], view_context)
58
61
  def table.admin?
59
62
  false
@@ -63,8 +66,11 @@ describe TableCloth::Base do
63
66
  end
64
67
 
65
68
  it '.column_names include actions when only partial are available' do
66
- subject.action(if: :admin?) { '/' }
67
- subject.action(if: :awesome?) { '/' }
69
+ subject.actions do
70
+ action(if: :admin?) { '/' }
71
+ action(if: :awesome?) { '/' }
72
+ end
73
+
68
74
  table = subject.new([], view_context)
69
75
  def table.admin?
70
76
  false
@@ -134,29 +140,13 @@ describe TableCloth::Base do
134
140
  end
135
141
 
136
142
  context 'actions' do
137
- it 'has an action method' do
138
- subject.should respond_to :action
139
- end
140
-
141
- it 'it adds an acion' do
142
- subject.action { '/' }
143
- subject.columns[:actions].should have(1).actions
144
- end
145
-
146
- context 'conditionals' do
147
- let!(:table_class) { Class.new(DummyTable) }
148
- subject { table_class.new([dummy_model], view_context) }
149
-
150
- it 'accepts if condition' do
151
- action = table_class.action(if: :admin?) { '/conditioned' }
152
- action.available?(subject).should be_true
143
+ it "takes a block" do
144
+ subject.actions do
145
+ action { 'Edit' }
146
+ action { 'Delete' }
153
147
  end
154
148
 
155
-
156
- it 'accepts unless condition' do
157
- action = table_class.action(unless: :admin?) { '/conditioned' }
158
- action.available?(subject).should be_false
159
- end
149
+ subject.columns[:actions].should have(2).actions
160
150
  end
161
151
  end
162
152
  end
@@ -8,7 +8,9 @@ describe TableCloth::Builder do
8
8
  it 'can build a table on the fly with a block' do
9
9
  new_table = subject.build([], view_context) do |table|
10
10
  table.column :name
11
- table.action(:edit) { '/model/1/edit' }
11
+ table.actions do
12
+ action { '/model/1/edit' }
13
+ end
12
14
  end
13
15
 
14
16
  new_table.table.columns.length.should == 2
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  describe TableCloth::Columns::Action do
4
4
  let(:view_context) { ActionView::Base.new }
@@ -14,9 +14,10 @@ describe TableCloth::Columns::Action do
14
14
  subject { TableCloth::Columns::Action.new(object, view_context) }
15
15
 
16
16
  it '.value returns all actions in HTML' do
17
- dummy_table.action {|object, view| view.link_to "Edit", "#{object.id}"}
17
+ dummy_table.actions do
18
+ action {|object, view| view.link_to "Edit", "#{object.id}"}
19
+ end
18
20
  presenter = TableCloth::Presenters::Default.new([dummy_model], dummy_table, view_context)
19
-
20
21
  doc = Nokogiri::HTML(presenter.render_table)
21
22
 
22
23
  actions_column = doc.at_xpath('//tbody')
@@ -30,9 +31,12 @@ describe TableCloth::Columns::Action do
30
31
  end
31
32
 
32
33
  it '.value only returns actions that pass' do
33
- admin_action = dummy_table.action(if: :admin?) { '/admin' }
34
- moderator_action = dummy_table.action(if: :moderator?) { '/moderator' }
35
- table = dummy_table.new([], view_context)
34
+ dummy_table.actions do
35
+ action(if: :admin?) { '/admin' }
36
+ action(if: :moderator?) { '/moderator' }
37
+ end
38
+
39
+ table = dummy_table.new([], view_context)
36
40
 
37
41
  def table.admin?
38
42
  true
@@ -42,7 +46,28 @@ describe TableCloth::Columns::Action do
42
46
  false
43
47
  end
44
48
 
45
- dummy_table.columns[:actions].value(dummy_model, view_context, table).should include '/admin'
46
- dummy_table.columns[:actions].value(dummy_model, view_context, table).should_not include '/moderator'
49
+ actions_column = dummy_table.columns[:actions]
50
+ actions_value = actions_column.value(dummy_model, view_context, table)
51
+
52
+ actions_value.should include '/admin'
53
+ actions_value.should_not include '/moderator'
54
+ end
55
+
56
+ it "does not need to use the view context of a block" do
57
+ dummy_table.actions do
58
+ action {|object| link_to "Edit", "#{object.id}" }
59
+ end
60
+ presenter = TableCloth::Presenters::Default.new([dummy_model], dummy_table, view_context)
61
+
62
+ doc = Nokogiri::HTML(presenter.render_table)
63
+
64
+ actions_column = doc.at_xpath('//tbody')
65
+ .at_xpath('.//tr')
66
+ .xpath('.//td')
67
+ .last
68
+
69
+ link = actions_column.at_xpath('.//a')
70
+ link.should be_present
71
+ link[:href].should == '1'
47
72
  end
48
73
  end
@@ -29,10 +29,13 @@ describe TableCloth::Presenter do
29
29
  end
30
30
 
31
31
  it 'returns an edit link in the actions column' do
32
- dummy_table.action {|object, view| view.link_to 'Edit', '/model/1/edit' }
32
+ dummy_table.actions do
33
+ action {|object, view| link_to 'Edit', '/model/1/edit' }
34
+ end
33
35
  presenter = TableCloth::Presenter.new(objects, dummy_table, view_context)
34
36
 
35
- column = Nokogiri::HTML(presenter.row_values(dummy_model).last)
37
+ actions_value = presenter.row_values(dummy_model).last
38
+ column = Nokogiri::HTML(actions_value)
36
39
  column.at_xpath('//a')[:href].should == '/model/1/edit'
37
40
  column.at_xpath('//a').text.should == 'Edit'
38
41
  end
@@ -62,6 +62,28 @@ describe TableCloth::Presenters::Default do
62
62
  end
63
63
  end
64
64
 
65
+ context 'escaped values' do
66
+ let(:objects) do
67
+ model = DummyModel.new.tap do |d|
68
+ d.id = 1
69
+ d.email = 'robert@creativequeries.com'
70
+ d.name = '<script>alert("Im in your columns, snatching your main thread.")</script>'
71
+ end
72
+
73
+ [model]
74
+ end
75
+
76
+ it 'does not allow unescaped values in columns' do
77
+ rows = subject.render_rows
78
+ doc = Nokogiri::HTML(rows)
79
+
80
+ tbody = doc.xpath('//tbody')
81
+ tbody.xpath('//td').each do |td|
82
+ td.at_xpath('.//script').should_not be_present
83
+ end
84
+ end
85
+ end
86
+
65
87
  it 'creates an entire table' do
66
88
  doc = Nokogiri::HTML(subject.render_table)
67
89
  table = doc.xpath('//table')
@@ -117,7 +139,6 @@ describe TableCloth::Presenters::Default do
117
139
  end
118
140
 
119
141
  context 'specific configuration' do
120
- let(:doc) { Nokogiri::HTML(subject.render_table) }
121
142
  let(:dummy_table) do
122
143
  Class.new(TableCloth::Base) do
123
144
  column :email, td_options: { class: 'email_column' }
@@ -125,8 +146,30 @@ describe TableCloth::Presenters::Default do
125
146
  end
126
147
 
127
148
  it 'td has a class set' do
149
+ doc = Nokogiri::HTML(subject.render_table)
128
150
  doc.at_xpath('//td')[:class].should include 'email_column'
129
151
  end
152
+
153
+ context 'actions column' do
154
+ let(:dummy_table) { Class.new(DummyTableWithActions) }
155
+
156
+ specify 'actions column has a class set' do
157
+ doc = Nokogiri::HTML(subject.render_table)
158
+ td = doc.at_css('td:last')
159
+ td[:class].should include "actions"
160
+ end
161
+ end
162
+
163
+ context 'by value of row column' do
164
+ let(:dummy_table) { Class.new(DummyTableWithValueOptions) }
165
+
166
+ specify 'column has options because of value' do
167
+ doc = Nokogiri::HTML(subject.render_table)
168
+ td = doc.at_xpath('//td')
169
+ expect(td.text).to include "robert@creativequeries.com"
170
+ expect(td[:class]).to eq("special-class")
171
+ end
172
+ end
130
173
  end
131
174
 
132
175
  context 'table configuration' do
@@ -144,28 +187,24 @@ describe TableCloth::Presenters::Default do
144
187
  end
145
188
  end
146
189
 
147
- it 'tables have a class attached' do
148
- doc.at_xpath('//table')[:class].should include 'table2'
149
- end
190
+ include_examples "table configuration"
150
191
 
151
- it 'thead has a class attached' do
152
- doc.at_xpath('//thead')[:class].should include 'thead2'
153
- end
192
+ context "is extendable" do
193
+ let(:dummy_table) do
194
+ table = Class.new(TableCloth::Base) do
195
+ column :email
154
196
 
155
- it 'th has a class attached' do
156
- doc.at_xpath('//th')[:class].should include 'th2'
157
- end
158
-
159
- it 'tbody has a class attached' do
160
- doc.at_xpath('//tbody')[:class].should include 'tbody2'
161
- end
162
-
163
- it 'tr has a class attached' do
164
- doc.at_xpath('//tr')[:class].should include 'tr2'
165
- end
166
-
167
- it 'td has a class attached' do
168
- doc.at_xpath('//td')[:class].should include 'td2'
197
+ config.table.class = 'table2'
198
+ config.thead.class = 'thead2'
199
+ config.th.class = 'th2'
200
+ config.tbody.class = 'tbody2'
201
+ config.tr.class = 'tr2'
202
+ config.td.class = 'td2'
203
+ end
204
+ Class.new(table)
205
+ end
206
+
207
+ include_examples "table configuration"
169
208
  end
170
209
  end
171
210
  end
@@ -0,0 +1,7 @@
1
+ class DummyTableWithActions < TableCloth::Base
2
+ column :name
3
+
4
+ actions(td_options: {class: 'actions'}) do
5
+ action {|object| link_to "Edit", "/#{object.id}/edit" }
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ class DummyTableWithValueOptions < TableCloth::Base
2
+ column :email do
3
+ ["robert@creativequeries.com", {class: "special-class"}]
4
+ end
5
+ end
@@ -0,0 +1,25 @@
1
+ shared_examples "table configuration" do
2
+ it 'tables have a class attached' do
3
+ doc.at_xpath('//table')[:class].should include 'table2'
4
+ end
5
+
6
+ it 'thead has a class attached' do
7
+ doc.at_xpath('//thead')[:class].should include 'thead2'
8
+ end
9
+
10
+ it 'th has a class attached' do
11
+ doc.at_xpath('//th')[:class].should include 'th2'
12
+ end
13
+
14
+ it 'tbody has a class attached' do
15
+ doc.at_xpath('//tbody')[:class].should include 'tbody2'
16
+ end
17
+
18
+ it 'tr has a class attached' do
19
+ doc.at_xpath('//tr')[:class].should include 'tr2'
20
+ end
21
+
22
+ it 'td has a class attached' do
23
+ doc.at_xpath('//td')[:class].should include 'td2'
24
+ end
25
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: table_cloth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-11 00:00:00.000000000 Z
12
+ date: 2012-11-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -143,6 +143,7 @@ files:
143
143
  - lib/table_cloth.rb
144
144
  - lib/table_cloth/action.rb
145
145
  - lib/table_cloth/action_view_extension.rb
146
+ - lib/table_cloth/actions.rb
146
147
  - lib/table_cloth/base.rb
147
148
  - lib/table_cloth/builder.rb
148
149
  - lib/table_cloth/column.rb
@@ -153,6 +154,7 @@ files:
153
154
  - lib/table_cloth/presenters/default.rb
154
155
  - lib/table_cloth/version.rb
155
156
  - spec/lib/action_view_extension_spec.rb
157
+ - spec/lib/actions_spec.rb
156
158
  - spec/lib/base_spec.rb
157
159
  - spec/lib/builder_spec.rb
158
160
  - spec/lib/column_spec.rb
@@ -163,6 +165,9 @@ files:
163
165
  - spec/spec_helper.rb
164
166
  - spec/support/dummy_model.rb
165
167
  - spec/support/dummy_table.rb
168
+ - spec/support/dummy_table_with_actions.rb
169
+ - spec/support/dummy_table_with_value_options.rb
170
+ - spec/support/shared_config_examples.rb
166
171
  - spec/support/view_mocks.rb
167
172
  - table_cloth.gemspec
168
173
  homepage: http://www.github.com/bobbytables/table_cloth
@@ -192,6 +197,7 @@ summary: Table Cloth provides an easy and intuitive DSL for creating tables in r
192
197
  views.
193
198
  test_files:
194
199
  - spec/lib/action_view_extension_spec.rb
200
+ - spec/lib/actions_spec.rb
195
201
  - spec/lib/base_spec.rb
196
202
  - spec/lib/builder_spec.rb
197
203
  - spec/lib/column_spec.rb
@@ -202,4 +208,7 @@ test_files:
202
208
  - spec/spec_helper.rb
203
209
  - spec/support/dummy_model.rb
204
210
  - spec/support/dummy_table.rb
211
+ - spec/support/dummy_table_with_actions.rb
212
+ - spec/support/dummy_table_with_value_options.rb
213
+ - spec/support/shared_config_examples.rb
205
214
  - spec/support/view_mocks.rb