dining-table 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +9 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +2 -0
- data/README.md +32 -5
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/dining-table.gemspec +16 -4
- data/lib/dining-table/columns/actions_column.rb +1 -1
- data/lib/dining-table/columns/column.rb +4 -3
- data/lib/dining-table/config.rb +0 -2
- data/lib/dining-table/presenters/csv_presenter.rb +2 -0
- data/lib/dining-table/presenters/html_presenter.rb +3 -3
- data/lib/dining-table/presenters/presenter.rb +5 -0
- data/lib/dining-table/presenters/spreadsheet_presenter.rb +1 -1
- data/lib/dining-table/table.rb +2 -0
- data/spec/collection.rb +23 -0
- data/spec/csv_table_spec.rb +54 -0
- data/spec/date_column.rb +12 -0
- data/spec/html_table_spec.rb +212 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/tables/car_table.rb +8 -0
- data/spec/tables/car_table_with_actions.rb +10 -0
- data/spec/tables/car_table_with_footer.rb +7 -0
- data/spec/tables/car_table_with_header.rb +7 -0
- data/spec/tables/car_table_with_options.rb +10 -0
- metadata +14 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5f2535c2cc619915268041c0bc43d5534172327c
|
4
|
+
data.tar.gz: d09637a314c28ba0928e3a25dd27a90aa0f7f184
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 980abf830c3bc889c0e4b1bb31927338ad07b1f75eb4c6f83b05f97d6cac6ef791774b1c3e22b0dfb39c11bff38285b0a53eb04b0b782ee807c0c39e16ea47b0
|
7
|
+
data.tar.gz: 4a19092fd8d14c30b3823f789debb18ad15443d2001434b099271e2c27432f778e184e9352506da3262918c0112288f81be90eee9b64a67f6aa0e5f72d3edd43
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -30,6 +30,7 @@ GEM
|
|
30
30
|
semver
|
31
31
|
jwt (1.5.1)
|
32
32
|
mini_portile2 (2.0.0)
|
33
|
+
minitest (5.9.0)
|
33
34
|
multi_json (1.12.0)
|
34
35
|
multi_xml (0.5.5)
|
35
36
|
multipart-post (2.0.0)
|
@@ -54,6 +55,7 @@ PLATFORMS
|
|
54
55
|
DEPENDENCIES
|
55
56
|
bundler (~> 1.0)
|
56
57
|
juwelier (~> 2.1.0)
|
58
|
+
minitest
|
57
59
|
|
58
60
|
BUNDLED WITH
|
59
61
|
1.12.3
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# dining-table
|
2
|
+
[![Build Status](https://travis-ci.org/mvdamme/dining-table.png)](https://travis-ci.org/mvdamme/dining-table)
|
2
3
|
|
3
4
|
dining-table was inspired by the (now unfortunately unmaintained) [table_cloth](https://github.com/bobbytables/table_cloth) gem.
|
4
5
|
This gem is definitely not a drop-in replacement for [table-cloth](https://github.com/bobbytables/table_cloth), it aims to be less dependent on Rails
|
@@ -224,15 +225,15 @@ the following in a Rails controller action, for instance:
|
|
224
225
|
```ruby
|
225
226
|
def export_csv
|
226
227
|
collection = Car.order(:brand)
|
227
|
-
csv = CarTable.new( collection, nil, :
|
228
|
-
send_data( csv, :
|
228
|
+
csv = CarTable.new( collection, nil, presenter: DiningTable::Presenters::CSVPresenter.new ).render
|
229
|
+
send_data( csv, filename: 'export.csv', content_type: "text/csv" )
|
229
230
|
end
|
230
231
|
```
|
231
232
|
|
232
233
|
The CSV Presenter uses the CSV class from the Ruby standard library. Options passed in through the :csv key will be passed on to `CSV.new`:
|
233
234
|
|
234
235
|
```ruby
|
235
|
-
csv = CarTable.new( collection, nil, :
|
236
|
+
csv = CarTable.new( collection, nil, presenter: DiningTable::Presenters::CSVPresenter.new( csv: { col_sep: ';' } ) ).render
|
236
237
|
```
|
237
238
|
|
238
239
|
CSV options can also be set as defaults, see [Configuration](#configuration)
|
@@ -278,7 +279,33 @@ In order to use the Excel presenter, pass it in as a presenter and provide an ax
|
|
278
279
|
```ruby
|
279
280
|
collection = Car.order(:brand)
|
280
281
|
# sheet is the axlsx worksheet in which the table will be rendered
|
281
|
-
CarTable.new( collection, nil, :
|
282
|
+
CarTable.new( collection, nil, presenter: DiningTable::Presenters::ExcelPresenter.new( sheet ) ).render
|
283
|
+
```
|
284
|
+
|
285
|
+
## Custom column classes
|
286
|
+
|
287
|
+
You can write your own column classes and use them for specific columns. For instance, the following column class will format a date using `I18n`:
|
288
|
+
|
289
|
+
```ruby
|
290
|
+
class DateColumn < DiningTable::Columns::Column
|
291
|
+
def value(object)
|
292
|
+
val = super
|
293
|
+
I18n.l( val ) if val
|
294
|
+
end
|
295
|
+
end
|
296
|
+
```
|
297
|
+
|
298
|
+
A column class has to be derived from `DiningTable::Columns::Column` and implement the `value` method. The object passed in is the object in the
|
299
|
+
collection for which the current line is being rendered. If you don't have too many custom column classes, an easy place to put the code is in an initializer
|
300
|
+
(e.g. `config/initializers/dining-table.rb`).
|
301
|
+
|
302
|
+
You can use custom column classes as follows:
|
303
|
+
|
304
|
+
```ruby
|
305
|
+
class CarTable < DiningTable::Table
|
306
|
+
def define
|
307
|
+
column :fabrication_date, class: DateColumn
|
308
|
+
end
|
282
309
|
```
|
283
310
|
|
284
311
|
## Configuration <a name="configuration"></a>
|
@@ -289,7 +316,7 @@ You can set default options for the different presenters in an initializer (e.g.
|
|
289
316
|
DiningTable.configure do |config|
|
290
317
|
config.html_presenter.default_options = { class: 'table table-bordered table-hover',
|
291
318
|
wrap: { tag: :div, class: 'table-responsive' } }
|
292
|
-
config.csv_presenter.default_options = { :
|
319
|
+
config.csv_presenter.default_options = { csv: { col_sep: ';' } }
|
293
320
|
end
|
294
321
|
```
|
295
322
|
|
data/Rakefile
CHANGED
@@ -27,8 +27,8 @@ Juwelier::RubygemsDotOrgTasks.new
|
|
27
27
|
|
28
28
|
require 'rake/testtask'
|
29
29
|
Rake::TestTask.new(:test) do |test|
|
30
|
-
test.libs << 'lib' << '
|
31
|
-
test.pattern = '
|
30
|
+
test.libs << 'lib' << 'spec'
|
31
|
+
test.pattern = 'spec/**/*_spec.rb'
|
32
32
|
test.verbose = true
|
33
33
|
end
|
34
34
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/dining-table.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: dining-table 0.
|
5
|
+
# stub: dining-table 0.2.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "dining-table"
|
9
|
-
s.version = "0.
|
9
|
+
s.version = "0.2.0"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
13
|
s.authors = ["Micha\u{eb}l Van Damme"]
|
14
|
-
s.date = "2016-
|
14
|
+
s.date = "2016-09-03"
|
15
15
|
s.description = "Easily output tabular data, be it in HTML, CSV or XLSX. Create clean table classes instead of messing with views to create nice tables."
|
16
16
|
s.email = "michael.vandamme@vub.ac.be"
|
17
17
|
s.extra_rdoc_files = [
|
@@ -19,6 +19,8 @@ Gem::Specification.new do |s|
|
|
19
19
|
"README.md"
|
20
20
|
]
|
21
21
|
s.files = [
|
22
|
+
".travis.yml",
|
23
|
+
"CHANGELOG.md",
|
22
24
|
"Gemfile",
|
23
25
|
"Gemfile.lock",
|
24
26
|
"LICENSE",
|
@@ -35,7 +37,17 @@ Gem::Specification.new do |s|
|
|
35
37
|
"lib/dining-table/presenters/html_presenter.rb",
|
36
38
|
"lib/dining-table/presenters/presenter.rb",
|
37
39
|
"lib/dining-table/presenters/spreadsheet_presenter.rb",
|
38
|
-
"lib/dining-table/table.rb"
|
40
|
+
"lib/dining-table/table.rb",
|
41
|
+
"spec/collection.rb",
|
42
|
+
"spec/csv_table_spec.rb",
|
43
|
+
"spec/date_column.rb",
|
44
|
+
"spec/html_table_spec.rb",
|
45
|
+
"spec/spec_helper.rb",
|
46
|
+
"spec/tables/car_table.rb",
|
47
|
+
"spec/tables/car_table_with_actions.rb",
|
48
|
+
"spec/tables/car_table_with_footer.rb",
|
49
|
+
"spec/tables/car_table_with_header.rb",
|
50
|
+
"spec/tables/car_table_with_options.rb"
|
39
51
|
]
|
40
52
|
s.homepage = "http://github.com/mvdamme/dining-table"
|
41
53
|
s.licenses = ["MIT"]
|
@@ -17,7 +17,7 @@ module DiningTable
|
|
17
17
|
|
18
18
|
def action(&block)
|
19
19
|
action_value = yield(@current_object)
|
20
|
-
@incremental_value += action_value.
|
20
|
+
@incremental_value += action_value.to_s if action_value && action_value.respond_to?(:to_s)
|
21
21
|
end
|
22
22
|
|
23
23
|
# offer methods normally available on Table that could be used by the action blocks
|
@@ -17,7 +17,7 @@ module DiningTable
|
|
17
17
|
if block
|
18
18
|
block.call(object, self)
|
19
19
|
else
|
20
|
-
object.send(name)
|
20
|
+
object.send(name) if object.respond_to?(name)
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -25,7 +25,7 @@ module DiningTable
|
|
25
25
|
@header ||= begin
|
26
26
|
label = determine_label(:header)
|
27
27
|
return label if label
|
28
|
-
object_class = table.collection.first.
|
28
|
+
object_class = table.collection.first.class if table.collection.first
|
29
29
|
object_class.human_attribute_name( name ) if object_class && object_class.respond_to?( :human_attribute_name )
|
30
30
|
end
|
31
31
|
end
|
@@ -44,7 +44,8 @@ module DiningTable
|
|
44
44
|
if options[ name ]
|
45
45
|
label_ = options[ name ]
|
46
46
|
label_ = label_.call if label_.respond_to?(:call)
|
47
|
-
|
47
|
+
label_ = label_.to_s if label_.respond_to?(:to_s)
|
48
|
+
return label_
|
48
49
|
end
|
49
50
|
end
|
50
51
|
|
data/lib/dining-table/config.rb
CHANGED
@@ -57,7 +57,7 @@ module DiningTable
|
|
57
57
|
|
58
58
|
def render_footer
|
59
59
|
footers = columns.each.map(&:footer)
|
60
|
-
if footers.map
|
60
|
+
if footers.map { |s| blank?(s) }.uniq != [ true ]
|
61
61
|
add_tag(:start, :tfoot)
|
62
62
|
add_tag(:start, :tr)
|
63
63
|
columns.each_with_index do |column, index|
|
@@ -70,7 +70,7 @@ module DiningTable
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def output
|
73
|
-
@output.html_safe
|
73
|
+
@output.respond_to?(:html_safe) ? @output.html_safe : @output
|
74
74
|
end
|
75
75
|
|
76
76
|
private
|
@@ -87,7 +87,7 @@ module DiningTable
|
|
87
87
|
end
|
88
88
|
|
89
89
|
def start_tag(tag, options = {})
|
90
|
-
"<#{ tag.to_s }
|
90
|
+
"<#{ tag.to_s }#{ options_string(options) }>"
|
91
91
|
end
|
92
92
|
|
93
93
|
def end_tag(tag, options = {})
|
data/lib/dining-table/table.rb
CHANGED
@@ -21,10 +21,12 @@ module DiningTable
|
|
21
21
|
def render
|
22
22
|
presenter.start_table
|
23
23
|
presenter.render_header unless no_header
|
24
|
+
presenter.start_body
|
24
25
|
collection.each_with_index do |object, index_|
|
25
26
|
self.index = index_
|
26
27
|
presenter.render_row( object )
|
27
28
|
end
|
29
|
+
presenter.end_body
|
28
30
|
presenter.render_footer
|
29
31
|
presenter.end_table
|
30
32
|
presenter.output
|
data/spec/collection.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
class Car < Struct.new(:brand, :type, :number_of_doors, :stock, :launch_date)
|
2
|
+
|
3
|
+
COLLECTION = [
|
4
|
+
[ 'Audi', 'A4', 3, 100, Date.new(2016, 1, 1) ],
|
5
|
+
[ 'Citroën', 'C4 Picasso', 5, 150, Date.new(2016, 2, 1) ],
|
6
|
+
[ 'Ferrari', 'F12', 3, 2, Date.new(2016, 3, 1) ]
|
7
|
+
]
|
8
|
+
|
9
|
+
def self.collection
|
10
|
+
COLLECTION.map do |data|
|
11
|
+
self.new( *data )
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
class CarWithHumanAttributeName < Car
|
18
|
+
|
19
|
+
def self.human_attribute_name(attribute)
|
20
|
+
attribute.to_s.gsub('_', ' ').capitalize
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'collection'
|
3
|
+
require 'rexml/document'
|
4
|
+
Dir["#{File.dirname(__FILE__)}/tables/**/*.rb"].each {|file| require file }
|
5
|
+
|
6
|
+
|
7
|
+
describe 'CSVTableSpec' do
|
8
|
+
before do
|
9
|
+
@cars = CarWithHumanAttributeName.collection
|
10
|
+
@presenter = DiningTable::Presenters::CSVPresenter.new
|
11
|
+
end
|
12
|
+
|
13
|
+
it "correctly renders a basic table" do
|
14
|
+
csv = CarTable.new(@cars, nil, :presenter => @presenter).render
|
15
|
+
data = CSV.parse(csv)
|
16
|
+
check_data( data )
|
17
|
+
end
|
18
|
+
|
19
|
+
it "respects csv options" do
|
20
|
+
@presenter = DiningTable::Presenters::CSVPresenter.new( :csv => { :col_sep => ';' } )
|
21
|
+
csv = CarTable.new(@cars, nil, :presenter => @presenter).render
|
22
|
+
data = CSV.parse(csv)
|
23
|
+
data.first.length.must_equal 1 # wrongly parsed due to using comma as a separator
|
24
|
+
data = CSV.parse(csv, :col_sep => ';')
|
25
|
+
check_data( data )
|
26
|
+
end
|
27
|
+
|
28
|
+
it "respects global csv options" do
|
29
|
+
DiningTable.configure do |config|
|
30
|
+
config.csv_presenter.default_options = { :csv => { :col_sep => ';' } }
|
31
|
+
end
|
32
|
+
@presenter = DiningTable::Presenters::CSVPresenter.new
|
33
|
+
csv = CarTable.new(@cars, nil, :presenter => @presenter).render
|
34
|
+
data = CSV.parse(csv)
|
35
|
+
data.first.length.must_equal 1 # wrongly parsed due to using comma as a separator
|
36
|
+
data = CSV.parse(csv, :col_sep => ';')
|
37
|
+
check_data( data )
|
38
|
+
# reset configuration for other specs
|
39
|
+
DiningTable.configure do |config|
|
40
|
+
config.csv_presenter.default_options = { }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def check_data( data )
|
45
|
+
data.first.must_equal ["Brand", "Type", "Number of doors", "Stock"]
|
46
|
+
@cars.each_with_index do |car, index|
|
47
|
+
line = [ :brand, :type, :number_of_doors, :stock ].map do |item|
|
48
|
+
car.send(item).to_s
|
49
|
+
end
|
50
|
+
data[index + 1].must_equal line
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
data/spec/date_column.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# note that this custom class is specific for testing (value contains minitest assertions)
|
2
|
+
class DateColumn < DiningTable::Columns::Column
|
3
|
+
def value(object)
|
4
|
+
val = super
|
5
|
+
val.class.name.must_equal 'Date' # make sure an actual object is returned, not a string representation
|
6
|
+
self.class.localize( val ) if val
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.localize(date)
|
10
|
+
"#{ date.day }/#{ date.month }/#{ date.year }"
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'collection'
|
3
|
+
require 'date_column'
|
4
|
+
require 'rexml/document'
|
5
|
+
Dir["#{File.dirname(__FILE__)}/tables/**/*.rb"].each {|file| require file }
|
6
|
+
|
7
|
+
describe 'HTMLTableSpec' do
|
8
|
+
before do
|
9
|
+
@cars = Car.collection
|
10
|
+
@view_context = ViewContext.new
|
11
|
+
end
|
12
|
+
|
13
|
+
it "correctly renders a basic table's body" do
|
14
|
+
html = CarTable.new(@cars, nil).render
|
15
|
+
doc = document( html )
|
16
|
+
@cars.each_with_index do |car, index|
|
17
|
+
[ :brand, :number_of_doors, :stock ].each_with_index do |column, col_index|
|
18
|
+
xpath = "/table/tbody/tr[#{ index + 1 }]/td[#{ col_index + 1 }]"
|
19
|
+
check_not_empty(doc.elements, xpath)
|
20
|
+
doc.elements.each(xpath) do |element|
|
21
|
+
element.text.must_equal car.send( column ).to_s
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "correctly renders a table's header when no explicit headers are defined" do
|
28
|
+
@cars = CarWithHumanAttributeName.collection
|
29
|
+
html = CarTable.new(@cars, nil).render
|
30
|
+
doc = document( html )
|
31
|
+
[ 'Brand', 'Number of doors', 'Stock' ].each_with_index do |header, col_index|
|
32
|
+
xpath = "/table/thead/tr[1]/th[#{ col_index + 1 }]"
|
33
|
+
check_not_empty(doc.elements, xpath)
|
34
|
+
doc.elements.each(xpath) do |element|
|
35
|
+
element.text.must_equal header
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "correctly renders a table's header when explicit headers are defined" do
|
41
|
+
@cars = CarWithHumanAttributeName.collection
|
42
|
+
html = CarTableWithHeader.new(@cars, @view_context).render
|
43
|
+
doc = document( html )
|
44
|
+
[ 'The brand', 'The number of doors' ].each_with_index do |header, col_index|
|
45
|
+
xpath = "/table/thead/tr[1]/th[#{ col_index + 1 }]"
|
46
|
+
check_not_empty(doc.elements, xpath)
|
47
|
+
doc.elements.each(xpath) do |element|
|
48
|
+
element.text.must_equal header
|
49
|
+
end
|
50
|
+
end
|
51
|
+
# last header has link
|
52
|
+
doc.elements.each("/table/thead/tr[1]/th[3]/a") do |element|
|
53
|
+
element.text.must_equal 'Stock'
|
54
|
+
element.attributes.get_attribute('href').value.must_equal "http://www.google.com"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it "correctly renders a table's footer when footers are defined" do
|
59
|
+
@cars = CarWithHumanAttributeName.collection
|
60
|
+
html = CarTableWithFooter.new(@cars, @view_context).render
|
61
|
+
doc = document( html )
|
62
|
+
[ nil, 'Total' ].each_with_index do |footer, col_index|
|
63
|
+
xpath = "/table/tfoot/tr[1]/td[#{ col_index + 1 }]"
|
64
|
+
check_not_empty(doc.elements, xpath)
|
65
|
+
doc.elements.each(xpath) do |element|
|
66
|
+
element.text.must_equal footer
|
67
|
+
end
|
68
|
+
end
|
69
|
+
# last footer has link
|
70
|
+
doc.elements.each("/table/tfoot/tr[1]/th[3]/a") do |element|
|
71
|
+
element.text.must_equal "Total: #{ 150 }"
|
72
|
+
element.attributes.get_attribute('href').value.must_equal "#"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it "correctly renders a table with column options and column blocks" do
|
77
|
+
html = CarTableWithOptions.new(@cars, nil).render
|
78
|
+
doc = document( html )
|
79
|
+
@cars.each_with_index do |car, index|
|
80
|
+
[ :brand, :stock ].each_with_index do |column, col_index|
|
81
|
+
xpath = "/table/tbody/tr[#{ index + 1 }]/td[#{ col_index + 1 }]"
|
82
|
+
check_not_empty(doc.elements, xpath)
|
83
|
+
doc.elements.each(xpath) do |element|
|
84
|
+
element.text.must_equal car.send( column ).to_s.upcase
|
85
|
+
class_ = col_index == 0 ? 'center' : 'left'
|
86
|
+
element.attributes.get_attribute('class').value.must_equal class_
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
# also check header
|
91
|
+
[ 1, 2 ].each do |index|
|
92
|
+
doc.elements.each("/table/thead/tr[1]/th[#{ index }]") do |element|
|
93
|
+
class_ = index == 1 ? 'center' : 'left'
|
94
|
+
element.attributes.get_attribute('class').value.must_equal class_
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
it "correctly renders a table with actions" do
|
100
|
+
html = CarTableWithActions.new(@cars, @view_context).render
|
101
|
+
doc = document( html )
|
102
|
+
@cars.each_with_index do |car, index|
|
103
|
+
xpath = "/table/tbody/tr[#{ index + 1 }]/td[3]/a"
|
104
|
+
check_not_empty(doc.elements, xpath)
|
105
|
+
doc.elements.each_with_index(xpath) do |element, idx|
|
106
|
+
text = (idx == 0 ? 'Show' : 'Edit')
|
107
|
+
link = (idx == 0 ? '#show' : '#edit')
|
108
|
+
element.text.must_equal text
|
109
|
+
element.attributes.get_attribute('href').value.must_equal link
|
110
|
+
end
|
111
|
+
# check options
|
112
|
+
xpath = "/table/tbody/tr[#{ index + 1 }]/td[3]"
|
113
|
+
doc.elements.each(xpath) do |element|
|
114
|
+
element.attributes.get_attribute('class').value.must_equal 'left'
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
it "respects custom columns when specified" do
|
120
|
+
# first without custom column
|
121
|
+
html = CarTableWithOptions.new(@cars, nil, :with_normal_launch_date => true).render
|
122
|
+
doc = document( html )
|
123
|
+
@cars.each_with_index do |car, index|
|
124
|
+
xpath = "/table/tbody/tr[#{ index + 1 }]/td[3]"
|
125
|
+
check_not_empty(doc.elements, xpath)
|
126
|
+
doc.elements.each(xpath) do |element|
|
127
|
+
element.text.must_equal car.launch_date.to_s
|
128
|
+
end
|
129
|
+
end
|
130
|
+
# now with custom date column
|
131
|
+
html = CarTableWithOptions.new(@cars, nil, :with_date_column_launch_date => true).render
|
132
|
+
doc = document( html )
|
133
|
+
@cars.each_with_index do |car, index|
|
134
|
+
xpath = "/table/tbody/tr[#{ index + 1 }]/td[3]"
|
135
|
+
check_not_empty(doc.elements, xpath)
|
136
|
+
doc.elements.each(xpath) do |element|
|
137
|
+
element.text.must_equal DateColumn.localize( car.launch_date )
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
it "respects presenter options" do
|
143
|
+
html = CarTable.new(@cars, nil,
|
144
|
+
:presenter => DiningTable::Presenters::HTMLPresenter.new( :class => 'table table-bordered' ) ).render
|
145
|
+
doc = document( html )
|
146
|
+
doc.elements.first.attributes.get_attribute('class').value.must_equal 'table table-bordered'
|
147
|
+
end
|
148
|
+
|
149
|
+
it "correctly wraps the table" do
|
150
|
+
html = CarTable.new(@cars, nil,
|
151
|
+
:presenter => DiningTable::Presenters::HTMLPresenter.new( :wrap => { :tag => :div, :class => 'table-responsive' } ) ).render
|
152
|
+
doc = REXML::Document.new( html )
|
153
|
+
doc.elements.first.name.must_equal 'div'
|
154
|
+
doc.elements.first.attributes.get_attribute('class').value.must_equal 'table-responsive'
|
155
|
+
end
|
156
|
+
|
157
|
+
it "respects global html options" do
|
158
|
+
DiningTable.configure do |config|
|
159
|
+
config.html_presenter.default_options = { :class => 'table-hover',
|
160
|
+
:wrap => { :tag => :div, :class => 'table-responsive' } }
|
161
|
+
end
|
162
|
+
html = CarTable.new(@cars, nil).render
|
163
|
+
doc = REXML::Document.new( html )
|
164
|
+
doc.elements.first.name.must_equal 'div'
|
165
|
+
doc.elements.first.attributes.get_attribute('class').value.must_equal 'table-responsive'
|
166
|
+
table = doc.elements.first.elements.first
|
167
|
+
table.attributes.get_attribute('class').value.must_equal 'table-hover'
|
168
|
+
# reset configuration for other specs
|
169
|
+
DiningTable.configure do |config|
|
170
|
+
config.html_presenter.default_options = { }
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def document( html )
|
175
|
+
doc = REXML::Document.new( html )
|
176
|
+
check_table_structure( doc )
|
177
|
+
doc
|
178
|
+
end
|
179
|
+
|
180
|
+
def check_table_structure( document )
|
181
|
+
document.elements.size.must_equal 1
|
182
|
+
table = document.elements.first
|
183
|
+
table.name.must_equal 'table'
|
184
|
+
(table.elements.size >= 2).must_equal true
|
185
|
+
header = table.elements.first
|
186
|
+
header.name.must_equal 'thead'
|
187
|
+
body = table.elements[2] # 2 = second element (not third) in REXML
|
188
|
+
body.name.must_equal 'tbody'
|
189
|
+
if table.elements.size == 3
|
190
|
+
footer = table.elements[3]
|
191
|
+
footer.name.must_equal 'tfoot'
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def not_empty?(node, xpath)
|
196
|
+
node.each(xpath) do
|
197
|
+
return true
|
198
|
+
end
|
199
|
+
false
|
200
|
+
end
|
201
|
+
|
202
|
+
def check_not_empty(node, xpath)
|
203
|
+
not_empty?(node, xpath).must_equal true
|
204
|
+
end
|
205
|
+
|
206
|
+
class ViewContext
|
207
|
+
def link_to(text, url)
|
208
|
+
"<a href=\"#{ url }\">#{ text }</a>"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
class CarTableWithActions < DiningTable::Table
|
2
|
+
def define
|
3
|
+
column :brand
|
4
|
+
column :number_of_doors
|
5
|
+
actions :header => 'Action', :html => { :td_options => { class: 'left' }, :th_options => { class: :left } } do |object|
|
6
|
+
action { |object| h.link_to( 'Show', '#show' ) }
|
7
|
+
action { |object| h.link_to( 'Edit', '#edit' ) }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class CarTableWithOptions < DiningTable::Table
|
2
|
+
def define
|
3
|
+
column :brand, :html => { :td_options => { class: 'center' }, :th_options => { class: :center } } do |object|
|
4
|
+
object.brand.upcase
|
5
|
+
end
|
6
|
+
column :stock, :html => { :td_options => { class: 'left' }, :th_options => { class: :left } }
|
7
|
+
column :launch_date if options[:with_normal_launch_date]
|
8
|
+
column :launch_date, :class => DateColumn if options[:with_date_column_launch_date]
|
9
|
+
end
|
10
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dining-table
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michaël Van Damme
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -47,6 +47,8 @@ extra_rdoc_files:
|
|
47
47
|
- LICENSE
|
48
48
|
- README.md
|
49
49
|
files:
|
50
|
+
- ".travis.yml"
|
51
|
+
- CHANGELOG.md
|
50
52
|
- Gemfile
|
51
53
|
- Gemfile.lock
|
52
54
|
- LICENSE
|
@@ -64,6 +66,16 @@ files:
|
|
64
66
|
- lib/dining-table/presenters/presenter.rb
|
65
67
|
- lib/dining-table/presenters/spreadsheet_presenter.rb
|
66
68
|
- lib/dining-table/table.rb
|
69
|
+
- spec/collection.rb
|
70
|
+
- spec/csv_table_spec.rb
|
71
|
+
- spec/date_column.rb
|
72
|
+
- spec/html_table_spec.rb
|
73
|
+
- spec/spec_helper.rb
|
74
|
+
- spec/tables/car_table.rb
|
75
|
+
- spec/tables/car_table_with_actions.rb
|
76
|
+
- spec/tables/car_table_with_footer.rb
|
77
|
+
- spec/tables/car_table_with_header.rb
|
78
|
+
- spec/tables/car_table_with_options.rb
|
67
79
|
homepage: http://github.com/mvdamme/dining-table
|
68
80
|
licenses:
|
69
81
|
- MIT
|