table_cloth 0.1.2 → 0.2.0

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.
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