dining-table 0.2.1 → 1.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +4 -3
- data/CHANGELOG.md +22 -0
- data/Gemfile +1 -1
- data/README.md +150 -17
- data/VERSION +1 -1
- data/dining-table.gemspec +15 -14
- data/lib/dining-table.rb +1 -0
- data/lib/dining-table/columns/actions_column.rb +4 -4
- data/lib/dining-table/columns/column.rb +1 -0
- data/lib/dining-table/presenters/csv_presenter.rb +6 -5
- data/lib/dining-table/presenters/excel_presenter.rb +4 -3
- data/lib/dining-table/presenters/html_presenter.rb +111 -31
- data/lib/dining-table/presenters/html_presenter_configuration.rb +103 -0
- data/lib/dining-table/presenters/presenter.rb +1 -1
- data/lib/dining-table/table.rb +18 -9
- data/spec/html_table_spec.rb +147 -3
- data/spec/spec_helper.rb +7 -1
- data/spec/tables/car_table_with_actions.rb +4 -3
- data/spec/tables/car_table_with_config_blocks.rb +33 -0
- data/spec/tables/car_table_with_options.rb +2 -2
- data/spec/tables/car_table_with_options_old_syntax.rb +10 -0
- data/spec/tables/car_table_without_header.rb +9 -0
- metadata +11 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e3d53dc971fea99ed47e6793a2cdf74aa9c1f02b96726e56947ce9b264e5b688
|
4
|
+
data.tar.gz: 60cb9fdb7f239528b13aea9eab65ec9185038162c8ee4b4506f54e3e607614ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 33ba0f66b71201b5adde87aee1237e8616fe371a17fa4651a4b006ca8b6a1f3780c69e8e71cf4d34ec93371f9598d20938491f039a520548c37b45467ec45466
|
7
|
+
data.tar.gz: 7eb66dae080a8efd67c3b5705f697a3d217c49c109c7d7eff7983e367ebfbb49e72bc2630558e149506e2c9935a789e3ec9eed652bb80cbe23f6a29da8f25680
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
## 1.1.3 (22/06/2020)
|
2
|
+
|
3
|
+
* Fix ruby 2.7 deprecation warning
|
4
|
+
|
5
|
+
## 1.1.2 (28/11/2019)
|
6
|
+
|
7
|
+
* Bugfix - fix html escaping issue
|
8
|
+
|
9
|
+
## 1.1.1 (12/10/2019)
|
10
|
+
|
11
|
+
* Bugfix - call per-cell configuration block with correct index for footer row
|
12
|
+
|
13
|
+
## 1.1.0 (28/11/2018)
|
14
|
+
|
15
|
+
* Allow passing in class of object to avoid header edge case when the collection is empty
|
16
|
+
|
17
|
+
## 1.0.0 (17/06/2018)
|
18
|
+
|
19
|
+
* New configuration mechanism for HTML presenter
|
20
|
+
* Described new configuration mechanism in readme
|
21
|
+
* Documented skip_header and added and documented skip_footer
|
22
|
+
|
1
23
|
## 0.2.1 (26/05/2018)
|
2
24
|
|
3
25
|
* Removed Gemfile.lock file
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# dining-table
|
2
2
|
[![Build Status](https://travis-ci.org/mvdamme/dining-table.png)](https://travis-ci.org/mvdamme/dining-table)
|
3
3
|
|
4
|
+
dining-table allows you to write clean Ruby classes instead of messy view code to generate HTML tables. You can re-use the same classes to
|
5
|
+
generate csv or xlsx output as well.
|
6
|
+
|
4
7
|
dining-table was inspired by the (now unfortunately unmaintained) [table_cloth](https://github.com/bobbytables/table_cloth) gem.
|
5
|
-
This gem is definitely not a drop-in replacement for [
|
6
|
-
(no Rails required to use `dining-table
|
7
|
-
In addition, it not only supports HTML output but you can output tabular data in csv or xlsx formats as well.
|
8
|
+
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
|
9
|
+
(no Rails required to use `dining-table`, in fact it has no dependencies (except if you chose to generate xlsx output)) and more flexible.
|
8
10
|
|
9
11
|
## Installation
|
10
12
|
|
@@ -71,6 +73,17 @@ end
|
|
71
73
|
|
72
74
|
The custom header can be a string, but also a lambda or a proc.
|
73
75
|
|
76
|
+
If for some reason you don't want a header, call `skip_header`:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
class CarTable < DiningTable::Table
|
80
|
+
def define
|
81
|
+
skip_header
|
82
|
+
column :brand
|
83
|
+
end
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
74
87
|
By default, `dining-table` doesn't add a footer to the table, except when at least one column explicitly specifies a footer:
|
75
88
|
|
76
89
|
```ruby
|
@@ -82,7 +95,27 @@ class CarTable < DiningTable::Table
|
|
82
95
|
end
|
83
96
|
```
|
84
97
|
|
85
|
-
Please note how the collection passed in when creating the table
|
98
|
+
Please note how the collection passed in when creating the table object (`@cars` in `CarTable.new(@cars, self)`) is available as `collection`.
|
99
|
+
|
100
|
+
Similarly to `skip_header`, if for some reason you don't want a footer (even though at least one column defines one), call `skip_footer`:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
class CarTable < DiningTable::Table
|
104
|
+
def define
|
105
|
+
skip_footer
|
106
|
+
column :brand, footer: 'Footer'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
```
|
110
|
+
#### Empty collection
|
111
|
+
|
112
|
+
Note that when the collection to be presented in the table is empty, `dining-table` can't determine table headers
|
113
|
+
that aren't explicitly specified as there are no objects to use with `human_attribute_name`. In order to avoid this
|
114
|
+
edge case, you can pass in the class of the objects normally present in the collection when creating the table:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
<%= CarTable.new(@cars, self, class: Car).render %>
|
118
|
+
```
|
86
119
|
|
87
120
|
### Links and view helpers
|
88
121
|
|
@@ -185,6 +218,8 @@ end
|
|
185
218
|
|
186
219
|
### HTML
|
187
220
|
|
221
|
+
#### Introduction
|
222
|
+
|
188
223
|
The default presenter is HTML (i.e. `DiningTable::Presenters::HTMLPresenter`), so `CarTable.new(@cars, self).render` will generate a table in HTML.
|
189
224
|
When defining columns, you can specify options that apply only when using a certain presenter. For example, here we provide css classes for `td` and `th`
|
190
225
|
elements for some columns in the html table:
|
@@ -193,29 +228,126 @@ elements for some columns in the html table:
|
|
193
228
|
class CarTable < DiningTable::Table
|
194
229
|
def define
|
195
230
|
column :brand
|
196
|
-
column :number_of_doors, html: {
|
197
|
-
column :stock, html: {
|
231
|
+
column :number_of_doors, html: { td: { class: 'center' }, th: { class: 'center' } }
|
232
|
+
column :stock, html: { td: { class: 'center' }, th: { class: 'center' } }
|
198
233
|
end
|
199
234
|
end
|
200
235
|
```
|
201
236
|
|
202
237
|
The same table class can also be used with other presenters (csv, xlsx or a custom presenter), but the options will only be in effect when using the HTML presenter.
|
203
238
|
|
204
|
-
|
239
|
+
#### Presenter configuration
|
240
|
+
|
241
|
+
By instantiating the presenter yourself it is possible to specify options for a specific table. Using the `:tags` key you can specify
|
242
|
+
options for all HTML tags used in the table. Example:
|
205
243
|
|
206
244
|
```ruby
|
207
|
-
<%= CarTable.new(@cars, self,
|
245
|
+
<%= CarTable.new(@cars, self,
|
246
|
+
presenter: DiningTable::Presenters::HTMLPresenter.new(
|
247
|
+
tags: { table: { class: 'table table-bordered', id: 'car_table' },
|
248
|
+
tr: { class: 'car_table_row' } } )).render %>
|
208
249
|
```
|
250
|
+
In the above example, we specify the CSS class and HTML id for the table, and the CSS class to be used for all rows in the table.
|
251
|
+
The supported HTML tags are: `table`, `thead`, `tbody`, `tfoot`, `tr`, `th`, `td`.
|
209
252
|
|
210
|
-
It is also possible to wrap the table in another tag (a div for instance):
|
253
|
+
It is also possible to wrap the table in another tag (a div for instance), and specify options for this tag:
|
211
254
|
|
212
255
|
```ruby
|
213
256
|
<%= CarTable.new(@cars, self,
|
214
|
-
presenter: DiningTable::Presenters::HTMLPresenter.new(
|
215
|
-
|
257
|
+
presenter: DiningTable::Presenters::HTMLPresenter.new(
|
258
|
+
tags: { table: { class: 'table table-bordered', id: 'car_table' },
|
259
|
+
wrap: { tag: :div, class: 'table-responsive' } )).render %>
|
216
260
|
```
|
217
261
|
|
218
|
-
|
262
|
+
Most of the html options are usually best set as defaults, see [Configuration](#configuration).
|
263
|
+
|
264
|
+
Note that configuration information provided to the presenter constructor is added to the default configuration,
|
265
|
+
it doesn't replace it. This means you can have the default configuration define the CSS class for the
|
266
|
+
table tag, for instance, and add the html id attribute when initializing the presenter, or from inside the
|
267
|
+
table definition.
|
268
|
+
|
269
|
+
#### Configuration inside the table definition
|
270
|
+
|
271
|
+
It is possible to specify or modify the configuration from within the table definition. This allows you to use custom
|
272
|
+
CSS classes, ids, etc. per row or even per cell. Example:
|
273
|
+
|
274
|
+
```ruby
|
275
|
+
class CarTableWithConfigBlocks < DiningTable::Table
|
276
|
+
def define
|
277
|
+
table_id = options[:table_id] # custom option, see 'Options' above
|
278
|
+
|
279
|
+
presenter.table_config do |config|
|
280
|
+
config.table.class = 'table-class'
|
281
|
+
config.table.id = table_id || 'table-id'
|
282
|
+
config.thead.class = 'thead-class'
|
283
|
+
end if presenter.type?(:html)
|
284
|
+
|
285
|
+
presenter.row_config do |config, index, object|
|
286
|
+
if index == :header
|
287
|
+
config.tr.class = 'header-tr'
|
288
|
+
config.th.class = 'header-th'
|
289
|
+
elsif index == :footer
|
290
|
+
config.tr.class = 'footer-tr'
|
291
|
+
else # normal row
|
292
|
+
config.tr.class = index.odd? ? 'odd' : 'even'
|
293
|
+
config.tr.class += ' lowstock' if object.stock < 10
|
294
|
+
end
|
295
|
+
end if presenter.type?(:html)
|
296
|
+
|
297
|
+
column :brand
|
298
|
+
column :stock, footer: 'Footer text'
|
299
|
+
end
|
300
|
+
end
|
301
|
+
```
|
302
|
+
This example shows how to use `presenter.table_config` to set the configuration for (in this case) the `table` and `thead`tags. The block you use with `table_config`
|
303
|
+
is called once, when the table is being rendered. A configuration object is passed in that allows you to set any HTML attribute of the
|
304
|
+
seven supported tags.
|
305
|
+
|
306
|
+
Note that the configuration object already contains the pre-existing configuration information (coming
|
307
|
+
from either the presenter initialisation and/or from the global configuration), so you can refine the configuration in the block
|
308
|
+
instead of having to re-specify it in full. This means you can easily add CSS classes without knowledge of previously existing
|
309
|
+
configuration:
|
310
|
+
```ruby
|
311
|
+
presenter.table_config do |config|
|
312
|
+
config.table.class += ' my-table-class'
|
313
|
+
end if presenter.type?(:html)
|
314
|
+
```
|
315
|
+
Per row configuration can be specified with `presenter.row_config`. The block used with this method is called once for each row being
|
316
|
+
rendered, and receives three parameters: the configuration object (identical as with `table_config`), an index value, and the object
|
317
|
+
containing the data being rendered in this row.
|
318
|
+
The index value is equal to the row number of the row being rendered (starting at zero), except for the header and footer rows, in which case it
|
319
|
+
is equal to `:header` and `:footer`, respectively. `object` is the current object being rendered (`nil` for the header and footer rows).
|
320
|
+
As above, the passed in configuration object already contains the configuration which is in effect before calling the block.
|
321
|
+
|
322
|
+
#### Per cell configuration
|
323
|
+
|
324
|
+
As shown above, you can specify per column configuration using a hash:
|
325
|
+
|
326
|
+
```ruby
|
327
|
+
class CarTable < DiningTable::Table
|
328
|
+
def define
|
329
|
+
column :number_of_doors, html: { td: { class: 'center' }, th: { class: 'center' } }
|
330
|
+
end
|
331
|
+
end
|
332
|
+
```
|
333
|
+
For each column, the per column configuration is merged with the row configuration (see `presenter.row_config` above) before
|
334
|
+
cells from the column are rendered.
|
335
|
+
|
336
|
+
Sometimes, you might want to specify the configuration per cell, for instance to add a CSS class for cells with a certain content.
|
337
|
+
This is possible by supplying a lamba or proc instead of a hash:
|
338
|
+
|
339
|
+
```ruby
|
340
|
+
class CarTable < DiningTable::Table
|
341
|
+
def define
|
342
|
+
number_of_doors_options = ->( config, index, object ) do
|
343
|
+
config.td.class = 'center'
|
344
|
+
config.td.class += ' five_doors' if object && object.number_of_doors == 5
|
345
|
+
end
|
346
|
+
column :number_of_doors, html: number_of_doors_options
|
347
|
+
end
|
348
|
+
end
|
349
|
+
```
|
350
|
+
The arguments provided to the lambda or proc are the same as in the case of `presenter.row_config`.
|
219
351
|
|
220
352
|
### CSV
|
221
353
|
|
@@ -271,14 +403,14 @@ end
|
|
271
403
|
|
272
404
|
### Excel (xlsx)
|
273
405
|
|
274
|
-
The Excel presenter depends on [axlsx](https://github.com/randym/axlsx). Note that `dining-table` doesn't require `axlsx`, you have to add
|
406
|
+
The Excel presenter depends on [xlsxtream](https://github.com/felixbuenemann/xlsxtream) or [axlsx](https://github.com/randym/axlsx). Note that `dining-table` doesn't require either `xlsxtream` or `axlsx`, you have to add one of them to
|
275
407
|
your Gemfile yourself if you want to use the Excel presenter.
|
276
408
|
|
277
|
-
In order to use the Excel presenter, pass it in as a presenter and provide
|
409
|
+
In order to use the Excel presenter, pass it in as a presenter and provide a xlsxtream or axlsx worksheet:
|
278
410
|
|
279
411
|
```ruby
|
280
412
|
collection = Car.order(:brand)
|
281
|
-
# sheet is the axlsx worksheet in which the table will be rendered
|
413
|
+
# sheet is the xlsxtream or axlsx worksheet in which the table will be rendered
|
282
414
|
CarTable.new( collection, nil, presenter: DiningTable::Presenters::ExcelPresenter.new( sheet ) ).render
|
283
415
|
```
|
284
416
|
|
@@ -315,7 +447,8 @@ You can set default options for the different presenters in an initializer (e.g.
|
|
315
447
|
|
316
448
|
```ruby
|
317
449
|
DiningTable.configure do |config|
|
318
|
-
config.html_presenter.default_options = { class: 'table table-bordered
|
450
|
+
config.html_presenter.default_options = { tags: { table: { class: 'table table-bordered' },
|
451
|
+
thead: { class: 'header' } },
|
319
452
|
wrap: { tag: :div, class: 'table-responsive' } }
|
320
453
|
config.csv_presenter.default_options = { csv: { col_sep: ';' } }
|
321
454
|
end
|
@@ -323,4 +456,4 @@ end
|
|
323
456
|
|
324
457
|
## Copyright
|
325
458
|
|
326
|
-
Copyright (c)
|
459
|
+
Copyright (c) 2018 Michaël Van Damme. See LICENSE.txt for further details.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
1.1.3
|
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
|
5
|
+
# stub: dining-table 1.1.3 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "dining-table".freeze
|
9
|
-
s.version = "
|
9
|
+
s.version = "1.1.3"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib".freeze]
|
13
|
-
s.authors = ["Micha\
|
14
|
-
s.date = "
|
13
|
+
s.authors = ["Micha\u00EBl Van Damme".freeze]
|
14
|
+
s.date = "2020-06-22"
|
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.".freeze
|
16
16
|
s.email = "michael.vandamme@vub.ac.be".freeze
|
17
17
|
s.extra_rdoc_files = [
|
@@ -34,6 +34,7 @@ Gem::Specification.new do |s|
|
|
34
34
|
"lib/dining-table/presenters/csv_presenter.rb",
|
35
35
|
"lib/dining-table/presenters/excel_presenter.rb",
|
36
36
|
"lib/dining-table/presenters/html_presenter.rb",
|
37
|
+
"lib/dining-table/presenters/html_presenter_configuration.rb",
|
37
38
|
"lib/dining-table/presenters/presenter.rb",
|
38
39
|
"lib/dining-table/presenters/spreadsheet_presenter.rb",
|
39
40
|
"lib/dining-table/table.rb",
|
@@ -44,27 +45,27 @@ Gem::Specification.new do |s|
|
|
44
45
|
"spec/spec_helper.rb",
|
45
46
|
"spec/tables/car_table.rb",
|
46
47
|
"spec/tables/car_table_with_actions.rb",
|
48
|
+
"spec/tables/car_table_with_config_blocks.rb",
|
47
49
|
"spec/tables/car_table_with_footer.rb",
|
48
50
|
"spec/tables/car_table_with_header.rb",
|
49
|
-
"spec/tables/car_table_with_options.rb"
|
51
|
+
"spec/tables/car_table_with_options.rb",
|
52
|
+
"spec/tables/car_table_with_options_old_syntax.rb",
|
53
|
+
"spec/tables/car_table_without_header.rb"
|
50
54
|
]
|
51
55
|
s.homepage = "http://github.com/mvdamme/dining-table".freeze
|
52
56
|
s.licenses = ["MIT".freeze]
|
53
|
-
s.rubygems_version = "
|
57
|
+
s.rubygems_version = "3.1.2".freeze
|
54
58
|
s.summary = "Create tables easily. Supports html, csv and xlsx.".freeze
|
55
59
|
|
56
60
|
if s.respond_to? :specification_version then
|
57
61
|
s.specification_version = 4
|
62
|
+
end
|
58
63
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
else
|
63
|
-
s.add_dependency(%q<bundler>.freeze, ["~> 1.0"])
|
64
|
-
s.add_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
|
65
|
-
end
|
64
|
+
if s.respond_to? :add_runtime_dependency then
|
65
|
+
s.add_development_dependency(%q<bundler>.freeze, [">= 0"])
|
66
|
+
s.add_development_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
|
66
67
|
else
|
67
|
-
s.add_dependency(%q<bundler>.freeze, ["
|
68
|
+
s.add_dependency(%q<bundler>.freeze, [">= 0"])
|
68
69
|
s.add_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
|
69
70
|
end
|
70
71
|
end
|
data/lib/dining-table.rb
CHANGED
@@ -6,6 +6,7 @@ require 'dining-table/columns/column'
|
|
6
6
|
require 'dining-table/columns/actions_column'
|
7
7
|
|
8
8
|
require 'dining-table/presenters/presenter'
|
9
|
+
require 'dining-table/presenters/html_presenter_configuration'
|
9
10
|
require 'dining-table/presenters/html_presenter'
|
10
11
|
require 'dining-table/presenters/spreadsheet_presenter'
|
11
12
|
require 'dining-table/presenters/csv_presenter'
|
@@ -6,7 +6,7 @@ module DiningTable
|
|
6
6
|
|
7
7
|
def value(object)
|
8
8
|
if block
|
9
|
-
@incremental_value = ''
|
9
|
+
@incremental_value = ''.html_safe
|
10
10
|
@current_object = object
|
11
11
|
self.instance_eval(&block)
|
12
12
|
@incremental_value
|
@@ -16,11 +16,11 @@ module DiningTable
|
|
16
16
|
private
|
17
17
|
|
18
18
|
def action(&block)
|
19
|
-
action_value =
|
19
|
+
action_value = table.instance_exec(@current_object, &block)
|
20
20
|
@incremental_value += action_value.to_s if action_value && action_value.respond_to?(:to_s)
|
21
21
|
end
|
22
22
|
|
23
|
-
# offer methods normally available on Table that could be used by the action
|
23
|
+
# offer methods normally available on Table that could be used by the action-column block
|
24
24
|
[ :h, :helpers, :collection, :index, :presenter ].each do |method|
|
25
25
|
self.class_eval <<-eos, __FILE__, __LINE__+1
|
26
26
|
def #{method}(*args)
|
@@ -28,7 +28,7 @@ module DiningTable
|
|
28
28
|
end
|
29
29
|
eos
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
end
|
33
33
|
|
34
34
|
end
|
@@ -26,6 +26,7 @@ module DiningTable
|
|
26
26
|
label = determine_label(:header)
|
27
27
|
return label if label
|
28
28
|
object_class = table.collection.first.class if table.collection.first
|
29
|
+
object_class ||= table.object_class if !object_class
|
29
30
|
object_class.human_attribute_name( name ) if object_class && object_class.respond_to?( :human_attribute_name )
|
30
31
|
end
|
31
32
|
end
|
@@ -5,7 +5,11 @@ module DiningTable
|
|
5
5
|
module Presenters
|
6
6
|
|
7
7
|
class CSVPresenter < SpreadsheetPresenter
|
8
|
-
|
8
|
+
|
9
|
+
attr_writer :output
|
10
|
+
attr_accessor :stringio
|
11
|
+
private :output, :stringio, :output=, :stringio=
|
12
|
+
|
9
13
|
def initialize( *args )
|
10
14
|
super
|
11
15
|
self.output = ''
|
@@ -21,14 +25,11 @@ module DiningTable
|
|
21
25
|
|
22
26
|
private
|
23
27
|
|
24
|
-
attr_writer :output
|
25
|
-
attr_accessor :stringio
|
26
|
-
|
27
28
|
def csv
|
28
29
|
@csv ||= begin
|
29
30
|
self.stringio = StringIO.new
|
30
31
|
csv_options = options[:csv] || { }
|
31
|
-
CSV.new(stringio, csv_options)
|
32
|
+
CSV.new(stringio, **csv_options)
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
@@ -3,7 +3,10 @@ module DiningTable
|
|
3
3
|
module Presenters
|
4
4
|
|
5
5
|
class ExcelPresenter < SpreadsheetPresenter
|
6
|
-
|
6
|
+
|
7
|
+
attr_accessor :worksheet
|
8
|
+
private :worksheet, :worksheet=
|
9
|
+
|
7
10
|
def initialize( worksheet, *args )
|
8
11
|
super( *args )
|
9
12
|
self.worksheet = worksheet
|
@@ -15,8 +18,6 @@ module DiningTable
|
|
15
18
|
|
16
19
|
private
|
17
20
|
|
18
|
-
attr_accessor :worksheet
|
19
|
-
|
20
21
|
def add_row(array)
|
21
22
|
worksheet.add_row( array )
|
22
23
|
end
|
@@ -3,10 +3,17 @@ module DiningTable
|
|
3
3
|
module Presenters
|
4
4
|
|
5
5
|
class HTMLPresenter < Presenter
|
6
|
-
|
7
|
-
|
6
|
+
|
7
|
+
attr_accessor :tags_configuration, :table_tags_configuration, :base_tags_configuration, :table_config_block, :row_config_block
|
8
|
+
|
9
|
+
attr_writer :output
|
10
|
+
private :output, :output=
|
11
|
+
|
12
|
+
def initialize( options = {} )
|
8
13
|
super
|
9
|
-
self.
|
14
|
+
self.base_tags_configuration = HTMLPresenterConfiguration::TagsConfiguration.from_hash( default_options )
|
15
|
+
base_tags_configuration.merge_hash( options )
|
16
|
+
self.output = ''.html_safe
|
10
17
|
end
|
11
18
|
|
12
19
|
def identifier
|
@@ -14,10 +21,11 @@ module DiningTable
|
|
14
21
|
end
|
15
22
|
|
16
23
|
def start_table
|
24
|
+
set_up_configuration
|
17
25
|
if options[:wrap]
|
18
26
|
add_tag(:start, wrap_tag, wrap_options )
|
19
27
|
end
|
20
|
-
add_tag(:start, :table,
|
28
|
+
add_tag(:start, :table, table_options )
|
21
29
|
end
|
22
30
|
|
23
31
|
def end_table
|
@@ -28,7 +36,7 @@ module DiningTable
|
|
28
36
|
end
|
29
37
|
|
30
38
|
def start_body
|
31
|
-
add_tag(:start, :tbody)
|
39
|
+
add_tag(:start, :tbody, tag_options(:tbody))
|
32
40
|
end
|
33
41
|
|
34
42
|
def end_body
|
@@ -36,33 +44,39 @@ module DiningTable
|
|
36
44
|
end
|
37
45
|
|
38
46
|
def render_row( object )
|
39
|
-
|
47
|
+
set_up_row_configuration( table.index, object )
|
48
|
+
add_tag(:start, :tr, row_options)
|
40
49
|
columns.each do |column|
|
41
50
|
value = column.value( object )
|
42
|
-
|
51
|
+
configuration = cell_configuration( tags_configuration, column, table.index, object )
|
52
|
+
render_cell( value, configuration )
|
43
53
|
end
|
44
54
|
add_tag(:end, :tr)
|
45
55
|
end
|
46
56
|
|
47
57
|
def render_header
|
48
|
-
|
49
|
-
add_tag(:start, :
|
58
|
+
set_up_row_configuration( :header, nil )
|
59
|
+
add_tag(:start, :thead, tag_options(:thead))
|
60
|
+
add_tag(:start, :tr, row_options)
|
50
61
|
columns.each do |column|
|
51
62
|
value = column.header
|
52
|
-
|
63
|
+
configuration = cell_configuration( tags_configuration, column, :header, nil )
|
64
|
+
render_header_cell( value, configuration )
|
53
65
|
end
|
54
66
|
add_tag(:end, :tr)
|
55
67
|
add_tag(:end, :thead)
|
56
68
|
end
|
57
69
|
|
58
70
|
def render_footer
|
71
|
+
set_up_row_configuration( :footer, nil )
|
59
72
|
footers = columns.each.map(&:footer)
|
60
73
|
if footers.map { |s| blank?(s) }.uniq != [ true ]
|
61
|
-
add_tag(:start, :tfoot)
|
62
|
-
add_tag(:start, :tr)
|
74
|
+
add_tag(:start, :tfoot, tag_options(:tfoot))
|
75
|
+
add_tag(:start, :tr, row_options)
|
63
76
|
columns.each_with_index do |column, index|
|
64
77
|
value = footers[index]
|
65
|
-
|
78
|
+
configuration = cell_configuration( tags_configuration, column, :footer, nil )
|
79
|
+
render_footer_cell( value, configuration )
|
66
80
|
end
|
67
81
|
add_tag(:end, :tr)
|
68
82
|
add_tag(:end, :tfoot)
|
@@ -70,13 +84,19 @@ module DiningTable
|
|
70
84
|
end
|
71
85
|
|
72
86
|
def output
|
73
|
-
@output
|
87
|
+
@output
|
74
88
|
end
|
75
|
-
|
89
|
+
|
90
|
+
def table_config(&block)
|
91
|
+
self.table_config_block = block
|
92
|
+
end
|
93
|
+
|
94
|
+
def row_config(&block)
|
95
|
+
self.row_config_block = block
|
96
|
+
end
|
97
|
+
|
76
98
|
private
|
77
99
|
|
78
|
-
attr_writer :output
|
79
|
-
|
80
100
|
def output_
|
81
101
|
@output
|
82
102
|
end
|
@@ -87,27 +107,27 @@ module DiningTable
|
|
87
107
|
end
|
88
108
|
|
89
109
|
def start_tag(tag, options = {})
|
90
|
-
"<#{ tag.to_s }#{ options_string(options) }>"
|
110
|
+
"<#{ tag.to_s }#{ options_string(options) }>".html_safe
|
91
111
|
end
|
92
112
|
|
93
113
|
def end_tag(tag, options = {})
|
94
|
-
"</#{ tag.to_s }>"
|
114
|
+
"</#{ tag.to_s }>".html_safe
|
95
115
|
end
|
96
|
-
|
97
|
-
def render_cell( string,
|
98
|
-
render_general_cell( string,
|
116
|
+
|
117
|
+
def render_cell( string, configuration )
|
118
|
+
render_general_cell( string, configuration, :td)
|
99
119
|
end
|
100
120
|
|
101
|
-
def render_header_cell( string,
|
102
|
-
render_general_cell( string,
|
121
|
+
def render_header_cell( string, configuration )
|
122
|
+
render_general_cell( string, configuration, :th)
|
103
123
|
end
|
104
|
-
|
105
|
-
def render_footer_cell( string,
|
106
|
-
|
124
|
+
|
125
|
+
def render_footer_cell( string, configuration )
|
126
|
+
render_cell( string, configuration )
|
107
127
|
end
|
108
|
-
|
109
|
-
def render_general_cell( string,
|
110
|
-
add_tag(:start, cell_tag,
|
128
|
+
|
129
|
+
def render_general_cell( string, configuration, cell_tag )
|
130
|
+
add_tag(:start, cell_tag, tag_options(cell_tag, configuration) )
|
111
131
|
output_ << string.to_s
|
112
132
|
add_tag(:end, cell_tag)
|
113
133
|
end
|
@@ -134,7 +154,67 @@ module DiningTable
|
|
134
154
|
options_.delete(:tag)
|
135
155
|
options_
|
136
156
|
end
|
137
|
-
|
157
|
+
|
158
|
+
def table_options
|
159
|
+
options_ = tag_options(:table)
|
160
|
+
return options_ unless options_.empty?
|
161
|
+
if options[:class]
|
162
|
+
warn "[DEPRECATION] dining-table: option \"class\" is deprecated, please use \"tags: { table: { class: 'my_class' } }\" instead."
|
163
|
+
{ :class => options[:class] }
|
164
|
+
else
|
165
|
+
{ }
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def row_options
|
170
|
+
tag_options(:tr)
|
171
|
+
end
|
172
|
+
|
173
|
+
def column_options_cache( column )
|
174
|
+
@column_options_cache ||= { }
|
175
|
+
@column_options_cache[ column ] ||= begin
|
176
|
+
column_options = column.options_for( identifier )
|
177
|
+
if column_options.is_a?(Hash)
|
178
|
+
if column_options[:th_options] || column_options[:td_options]
|
179
|
+
warn "[DEPRECATION] dining-table: options \"th_options\" and \"td_options\" are deprecated, please use \"th\" and \"td\" instead. Example: \"{ td: { class: 'my_class' } }\"."
|
180
|
+
column_options[:th] = column_options.delete(:th_options)
|
181
|
+
column_options[:td] = column_options.delete(:td_options)
|
182
|
+
end
|
183
|
+
column_options[:tags] ? column_options : { :tags => column_options }
|
184
|
+
elsif column_options.respond_to?(:call)
|
185
|
+
column_options
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def cell_configuration( start_configuration, column, index, object )
|
191
|
+
column_options = column_options_cache( column )
|
192
|
+
return start_configuration if !column_options
|
193
|
+
new_configuration = start_configuration.dup
|
194
|
+
if column_options.is_a?(Hash)
|
195
|
+
new_configuration.merge_hash( column_options )
|
196
|
+
else # callable
|
197
|
+
column_options.call( new_configuration, index, object )
|
198
|
+
new_configuration
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def tag_options( tag, configuration = nil )
|
203
|
+
configuration ||= tags_configuration
|
204
|
+
configuration.send( tag ).to_h
|
205
|
+
end
|
206
|
+
|
207
|
+
def set_up_configuration
|
208
|
+
self.table_tags_configuration = base_tags_configuration.dup
|
209
|
+
table_config_block.call( table_tags_configuration ) if table_config_block
|
210
|
+
self.tags_configuration = table_tags_configuration.dup
|
211
|
+
end
|
212
|
+
|
213
|
+
def set_up_row_configuration( index, object )
|
214
|
+
self.tags_configuration = table_tags_configuration.dup
|
215
|
+
row_config_block.call( tags_configuration, index, object ) if row_config_block
|
216
|
+
end
|
217
|
+
|
138
218
|
end
|
139
219
|
|
140
220
|
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module DiningTable
|
2
|
+
|
3
|
+
module Presenters
|
4
|
+
|
5
|
+
module HTMLPresenterConfiguration
|
6
|
+
|
7
|
+
# configuration classes that allow us to avoid implementing a deep-merge for the config hash,
|
8
|
+
# and are more user friendly for use in config blocks in the table definition
|
9
|
+
class TagConfiguration
|
10
|
+
|
11
|
+
attr_accessor :__data_hash
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
self.__data_hash = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(name, *args, &block)
|
18
|
+
if name.to_s[-1] == '='
|
19
|
+
key = name.to_s[0..-2] # strip away '='
|
20
|
+
__data_hash[ key.to_sym ] = args.first
|
21
|
+
else
|
22
|
+
__data_hash.key?( name ) ? __data_hash[ name ] : super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def respond_to_missing?(method_name, *args)
|
27
|
+
return true if method_name.to_s[-1] == '='
|
28
|
+
__data_hash.key?( method_name ) ? true : super
|
29
|
+
end
|
30
|
+
|
31
|
+
# override class method (since it is a very common html attribute), and the method missing approach doesn't
|
32
|
+
# work here, as it returns the Ruby class by default.
|
33
|
+
def class
|
34
|
+
__data_hash.key?( :class ) ? __data_hash[ :class ] : super
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_h
|
38
|
+
__data_hash
|
39
|
+
end
|
40
|
+
|
41
|
+
def merge_hash( hash )
|
42
|
+
return self if !hash
|
43
|
+
hash.each do |key, value|
|
44
|
+
self.send("#{ key }=", value)
|
45
|
+
end
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.from_hash( hash )
|
50
|
+
new.merge_hash( hash )
|
51
|
+
end
|
52
|
+
|
53
|
+
# for deep dup
|
54
|
+
def initialize_copy( source )
|
55
|
+
self.__data_hash = source.__data_hash.dup
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
class TagsConfiguration
|
61
|
+
TAGS = [ :table, :thead, :tbody, :tfoot, :tr, :th, :td ]
|
62
|
+
attr_accessor(*TAGS)
|
63
|
+
|
64
|
+
def initialize
|
65
|
+
TAGS.each do |tag|
|
66
|
+
self.send("#{ tag }=", TagConfiguration.new)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_h
|
71
|
+
hashes = TAGS.map do |identifier|
|
72
|
+
self.send(identifier).to_h
|
73
|
+
end
|
74
|
+
{ :tags => Hash[ TAGS.zip( hashes ) ] }
|
75
|
+
end
|
76
|
+
|
77
|
+
def merge_hash( hash )
|
78
|
+
return self if !hash
|
79
|
+
tags = hash[ :tags ]
|
80
|
+
TAGS.each do |tag|
|
81
|
+
self.send("#{ tag }").merge_hash( tags[ tag ] )
|
82
|
+
end if tags
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.from_hash( hash )
|
87
|
+
new.merge_hash( hash )
|
88
|
+
end
|
89
|
+
|
90
|
+
# for deep dup
|
91
|
+
def initialize_copy( source )
|
92
|
+
TAGS.each do |tag|
|
93
|
+
self.send("#{ tag }=", source.send( tag ).dup)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -22,7 +22,7 @@ module DiningTable
|
|
22
22
|
identifier == identifier_
|
23
23
|
end
|
24
24
|
|
25
|
-
[ :start_table, :end_table, :render_header, :start_body, :end_body, :
|
25
|
+
[ :start_table, :end_table, :render_header, :start_body, :end_body, :render_row, :render_footer, :output ].each do |method|
|
26
26
|
self.class_eval <<-eos, __FILE__, __LINE__+1
|
27
27
|
def #{method}(*args)
|
28
28
|
end
|
data/lib/dining-table/table.rb
CHANGED
@@ -3,7 +3,10 @@ module DiningTable
|
|
3
3
|
class Table
|
4
4
|
|
5
5
|
attr_accessor :collection, :presenter, :options, :index, :columns, :action_columns, :view_context
|
6
|
-
|
6
|
+
|
7
|
+
attr_accessor :no_header, :no_footer
|
8
|
+
private :no_header, :no_footer, :no_header=, :no_footer=
|
9
|
+
|
7
10
|
def initialize( collection, view_context, options = {} )
|
8
11
|
self.collection = collection
|
9
12
|
self.view_context = view_context
|
@@ -27,7 +30,7 @@ module DiningTable
|
|
27
30
|
presenter.render_row( object )
|
28
31
|
end
|
29
32
|
presenter.end_body
|
30
|
-
presenter.render_footer
|
33
|
+
presenter.render_footer unless no_footer
|
31
34
|
presenter.end_table
|
32
35
|
presenter.output
|
33
36
|
end
|
@@ -36,11 +39,21 @@ module DiningTable
|
|
36
39
|
view_context
|
37
40
|
end
|
38
41
|
alias_method :h, :helpers
|
39
|
-
|
42
|
+
|
43
|
+
def skip_header
|
44
|
+
self.no_header = true
|
45
|
+
end
|
46
|
+
|
47
|
+
def skip_footer
|
48
|
+
self.no_footer = true
|
49
|
+
end
|
50
|
+
|
51
|
+
def object_class
|
52
|
+
options[:class]
|
53
|
+
end
|
54
|
+
|
40
55
|
private
|
41
56
|
|
42
|
-
attr_accessor :no_header
|
43
|
-
|
44
57
|
# auxiliary function
|
45
58
|
def column(name, options = {}, &block)
|
46
59
|
klass = options[:class]
|
@@ -62,10 +75,6 @@ module DiningTable
|
|
62
75
|
Presenters::HTMLPresenter
|
63
76
|
end
|
64
77
|
|
65
|
-
def skip_header
|
66
|
-
self.no_header = true
|
67
|
-
end
|
68
|
-
|
69
78
|
end
|
70
79
|
|
71
80
|
end
|
data/spec/html_table_spec.rb
CHANGED
@@ -36,7 +36,19 @@ describe 'HTMLTableSpec' do
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
|
+
it "correctly renders a table's header when the table body is empty and a class is provided" do
|
41
|
+
html = CarTable.new([], nil, :class => CarWithHumanAttributeName).render
|
42
|
+
doc = document( html )
|
43
|
+
[ 'Brand', 'Number of doors', 'Stock' ].each_with_index do |header, col_index|
|
44
|
+
xpath = "/table/thead/tr[1]/th[#{ col_index + 1 }]"
|
45
|
+
check_not_empty(doc.elements, xpath)
|
46
|
+
doc.elements.each(xpath) do |element|
|
47
|
+
element.text.must_equal header
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
40
52
|
it "correctly renders a table's header when explicit headers are defined" do
|
41
53
|
@cars = CarWithHumanAttributeName.collection
|
42
54
|
html = CarTableWithHeader.new(@cars, @view_context).render
|
@@ -55,6 +67,23 @@ describe 'HTMLTableSpec' do
|
|
55
67
|
end
|
56
68
|
end
|
57
69
|
|
70
|
+
it "correctly renders a table's header when explicit headers are defined and the table body is empty" do
|
71
|
+
html = CarTableWithHeader.new([], @view_context).render
|
72
|
+
doc = document( html )
|
73
|
+
[ 'The brand', 'The number of doors' ].each_with_index do |header, col_index|
|
74
|
+
xpath = "/table/thead/tr[1]/th[#{ col_index + 1 }]"
|
75
|
+
check_not_empty(doc.elements, xpath)
|
76
|
+
doc.elements.each(xpath) do |element|
|
77
|
+
element.text.must_equal header
|
78
|
+
end
|
79
|
+
end
|
80
|
+
# last header has link
|
81
|
+
doc.elements.each("/table/thead/tr[1]/th[3]/a") do |element|
|
82
|
+
element.text.must_equal 'Stock'
|
83
|
+
element.attributes.get_attribute('href').value.must_equal "http://www.google.com"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
58
87
|
it "correctly renders a table's footer when footers are defined" do
|
59
88
|
@cars = CarWithHumanAttributeName.collection
|
60
89
|
html = CarTableWithFooter.new(@cars, @view_context).render
|
@@ -63,7 +92,8 @@ describe 'HTMLTableSpec' do
|
|
63
92
|
xpath = "/table/tfoot/tr[1]/td[#{ col_index + 1 }]"
|
64
93
|
check_not_empty(doc.elements, xpath)
|
65
94
|
doc.elements.each(xpath) do |element|
|
66
|
-
element.text.must_equal footer
|
95
|
+
element.text.must_equal footer if footer
|
96
|
+
element.text.must_be_nil if !footer # avoid minitest deprecation warning
|
67
97
|
end
|
68
98
|
end
|
69
99
|
# last footer has link
|
@@ -73,6 +103,15 @@ describe 'HTMLTableSpec' do
|
|
73
103
|
end
|
74
104
|
end
|
75
105
|
|
106
|
+
it "allows skipping header and footer" do
|
107
|
+
@cars = CarWithHumanAttributeName.collection
|
108
|
+
html = CarTableWithoutHeader.new(@cars, @view_context).render
|
109
|
+
doc = REXML::Document.new( html )
|
110
|
+
table = doc.elements.first
|
111
|
+
table.elements.size.must_equal 1 # only body
|
112
|
+
table.elements.first.name.must_equal 'tbody'
|
113
|
+
end
|
114
|
+
|
76
115
|
it "correctly renders a table with column options and column blocks" do
|
77
116
|
html = CarTableWithOptions.new(@cars, nil).render
|
78
117
|
doc = document( html )
|
@@ -96,6 +135,28 @@ describe 'HTMLTableSpec' do
|
|
96
135
|
end
|
97
136
|
end
|
98
137
|
|
138
|
+
it "still supports deprecated syntax for html column options" do
|
139
|
+
html = CarTableWithOptionsOldSyntax.new(@cars, nil).render
|
140
|
+
doc = document( html )
|
141
|
+
@cars.each_with_index do |car, index|
|
142
|
+
[ :brand, :stock ].each_with_index do |column, col_index|
|
143
|
+
xpath = "/table/tbody/tr[#{ index + 1 }]/td[#{ col_index + 1 }]"
|
144
|
+
check_not_empty(doc.elements, xpath)
|
145
|
+
doc.elements.each(xpath) do |element|
|
146
|
+
class_ = col_index == 0 ? 'center' : 'left'
|
147
|
+
element.attributes.get_attribute('class').value.must_equal class_
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
# also check header
|
152
|
+
[ 1, 2 ].each do |index|
|
153
|
+
doc.elements.each("/table/thead/tr[1]/th[#{ index }]") do |element|
|
154
|
+
class_ = index == 1 ? 'center' : 'left'
|
155
|
+
element.attributes.get_attribute('class').value.must_equal class_
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
99
160
|
it "correctly renders a table with actions" do
|
100
161
|
html = CarTableWithActions.new(@cars, @view_context).render
|
101
162
|
doc = document( html )
|
@@ -140,6 +201,38 @@ describe 'HTMLTableSpec' do
|
|
140
201
|
end
|
141
202
|
|
142
203
|
it "respects presenter options" do
|
204
|
+
html = CarTableWithFooter.new(@cars, @view_context,
|
205
|
+
:presenter => DiningTable::Presenters::HTMLPresenter.new(
|
206
|
+
:tags => { :table => { :class => 'table table-bordered', :id => 'my_table_id', :'data-custom' => 'custom1!' },
|
207
|
+
:thead => { :class => 'mythead', :id => 'my_thead_id', :'data-custom' => 'custom2!' },
|
208
|
+
:tbody => { :class => 'mytbody', :id => 'my_tbody_id', :'data-custom' => 'custom3!' },
|
209
|
+
:tfoot => { :class => 'mytfoot', :id => 'my_tfoot_id', :'data-custom' => 'custom4!' },
|
210
|
+
:tr => { :class => 'mytr', :'data-custom' => 'custom5!' },
|
211
|
+
:th => { :class => 'myth', :'data-custom' => 'custom6!' },
|
212
|
+
:td => { :class => 'mytd', :'data-custom' => 'custom7!' }
|
213
|
+
} ) ).render
|
214
|
+
doc = document( html )
|
215
|
+
table = doc.elements.first
|
216
|
+
check_attributes( table, ['class', 'id', 'data-custom'], ['table table-bordered', 'my_table_id', 'custom1!'])
|
217
|
+
header = table.elements[1] # 1 = first element (not second) in REXML
|
218
|
+
check_attributes( header, ['class', 'id', 'data-custom'], ['mythead', 'my_thead_id', 'custom2!'])
|
219
|
+
body = table.elements[2] # 2 = second element (not third) in REXML
|
220
|
+
check_attributes( body, ['class', 'id', 'data-custom'], ['mytbody', 'my_tbody_id', 'custom3!'])
|
221
|
+
footer = table.elements[3] # 3 = third element (not fourth) in REXML
|
222
|
+
check_attributes( footer, ['class', 'id', 'data-custom'], ['mytfoot', 'my_tfoot_id', 'custom4!'])
|
223
|
+
row = header.elements.first
|
224
|
+
check_attributes( row, ['class', 'data-custom'], ['mytr', 'custom5!'])
|
225
|
+
row.elements.each do |header_cell|
|
226
|
+
check_attributes( header_cell, ['class', 'data-custom'], ['myth', 'custom6!'])
|
227
|
+
end
|
228
|
+
body.elements.each do |row_|
|
229
|
+
check_attributes( row_, ['class', 'data-custom'], ['mytr', 'custom5!'])
|
230
|
+
end
|
231
|
+
row = footer.elements.first
|
232
|
+
check_attributes( row, ['class', 'data-custom'], ['mytr', 'custom5!'])
|
233
|
+
end
|
234
|
+
|
235
|
+
it "still supports old (deprecated) way of specifying the table class" do
|
143
236
|
html = CarTable.new(@cars, nil,
|
144
237
|
:presenter => DiningTable::Presenters::HTMLPresenter.new( :class => 'table table-bordered' ) ).render
|
145
238
|
doc = document( html )
|
@@ -156,7 +249,7 @@ describe 'HTMLTableSpec' do
|
|
156
249
|
|
157
250
|
it "respects global html options" do
|
158
251
|
DiningTable.configure do |config|
|
159
|
-
config.html_presenter.default_options = { :class => 'table-hover',
|
252
|
+
config.html_presenter.default_options = { :tags => { :table => { :class => 'table-hover' }, :tr => { :class => 'rowrow' } },
|
160
253
|
:wrap => { :tag => :div, :class => 'table-responsive' } }
|
161
254
|
end
|
162
255
|
html = CarTable.new(@cars, nil).render
|
@@ -165,12 +258,56 @@ describe 'HTMLTableSpec' do
|
|
165
258
|
doc.elements.first.attributes.get_attribute('class').value.must_equal 'table-responsive'
|
166
259
|
table = doc.elements.first.elements.first
|
167
260
|
table.attributes.get_attribute('class').value.must_equal 'table-hover'
|
261
|
+
body = table.elements[2]
|
262
|
+
body.elements.each do |row|
|
263
|
+
row.attributes.get_attribute('class').value.must_equal 'rowrow'
|
264
|
+
end
|
168
265
|
# reset configuration for other specs
|
169
266
|
DiningTable.configure do |config|
|
170
267
|
config.html_presenter.default_options = { }
|
171
268
|
end
|
172
269
|
end
|
173
270
|
|
271
|
+
it "respects in-table presenter config blocks" do
|
272
|
+
html = CarTableWithConfigBlocks.new(@cars, @view_context).render
|
273
|
+
doc = REXML::Document.new( html )
|
274
|
+
table = doc.elements.first
|
275
|
+
table.attributes.get_attribute('class').value.must_equal 'my-table-class'
|
276
|
+
header = table.elements.first
|
277
|
+
header.attributes.get_attribute('class').value.must_equal 'my-thead-class'
|
278
|
+
row = header.elements.first
|
279
|
+
row.attributes.get_attribute('class').value.must_equal 'header-tr'
|
280
|
+
row.elements.each do |cell|
|
281
|
+
cell.attributes.get_attribute('class').value.must_equal 'header-th'
|
282
|
+
end
|
283
|
+
body = table.elements[2]
|
284
|
+
body.elements.each_with_index do |row_, index|
|
285
|
+
row_.attributes.get_attribute('class').value.must_match( index.odd? ? /odd/ : /even/ )
|
286
|
+
row_.attributes.get_attribute('class').value.must_match( /lowstock/ ) if @cars[index].stock < 10
|
287
|
+
row_.elements.each_with_index do |td, td_index|
|
288
|
+
if td_index == 0
|
289
|
+
td.attributes.get_attribute('class').value.must_equal 'left'
|
290
|
+
elsif td_index == 1
|
291
|
+
td.attributes.get_attribute('class').value.must_match( /center/ )
|
292
|
+
td.attributes.get_attribute('class').value.must_match( /five_doors/ ) if @cars[index].number_of_doors == 5
|
293
|
+
else
|
294
|
+
td.attributes.get_attribute('class').must_be_nil if td_index != 1
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
footer = table.elements[3]
|
299
|
+
footer.attributes.get_attribute('class').value.must_equal 'my-tfoot-class'
|
300
|
+
row = footer.elements.first
|
301
|
+
row.attributes.get_attribute('class').value.must_equal 'footer-tr'
|
302
|
+
row.elements.each_with_index do |cell, index|
|
303
|
+
if index == 0
|
304
|
+
cell.attributes.get_attribute('class').value.must_equal 'left' # brand column
|
305
|
+
else
|
306
|
+
cell.attributes.get_attribute('class').value.must_equal 'footer-td'
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
174
311
|
def document( html )
|
175
312
|
doc = REXML::Document.new( html )
|
176
313
|
check_table_structure( doc )
|
@@ -203,6 +340,13 @@ describe 'HTMLTableSpec' do
|
|
203
340
|
not_empty?(node, xpath).must_equal true
|
204
341
|
end
|
205
342
|
|
343
|
+
def check_attributes( element, attributes, values )
|
344
|
+
attributes.each_with_index do |attribute, index|
|
345
|
+
value = values[ index ]
|
346
|
+
element.attributes.get_attribute( attribute ).value.must_equal value
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
206
350
|
class ViewContext
|
207
351
|
def link_to(text, url)
|
208
352
|
"<a href=\"#{ url }\">#{ text }</a>"
|
data/spec/spec_helper.rb
CHANGED
@@ -2,9 +2,10 @@ class CarTableWithActions < DiningTable::Table
|
|
2
2
|
def define
|
3
3
|
column :brand
|
4
4
|
column :number_of_doors
|
5
|
-
actions :header => 'Action', :html => { :
|
6
|
-
|
7
|
-
action { |
|
5
|
+
actions :header => 'Action', :html => { :td => { class: 'left' }, :th => { class: :left } } do |object|
|
6
|
+
h.link_to( 'Show', '#show' ) # doesn't do anything, simply verify that h helper is available
|
7
|
+
action { |object_| h.link_to( 'Show', '#show' ) }
|
8
|
+
action { |object_| h.link_to( 'Edit', '#edit' ) }
|
8
9
|
end
|
9
10
|
end
|
10
11
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class CarTableWithConfigBlocks < DiningTable::Table
|
2
|
+
def define
|
3
|
+
|
4
|
+
presenter.table_config do |config|
|
5
|
+
config.table.class = 'my-table-class'
|
6
|
+
config.thead.class = 'my-thead-class'
|
7
|
+
config.tfoot.class = 'my-tfoot-class'
|
8
|
+
end if presenter.type?(:html)
|
9
|
+
|
10
|
+
presenter.row_config do |config, index, object|
|
11
|
+
if index == :header
|
12
|
+
config.tr.class = 'header-tr'
|
13
|
+
config.th.class = 'header-th'
|
14
|
+
elsif index == :footer
|
15
|
+
config.tr.class = 'footer-tr'
|
16
|
+
config.td.class = 'footer-td'
|
17
|
+
else
|
18
|
+
config.tr.class = index.odd? ? 'odd' : 'even'
|
19
|
+
config.tr.class += ' lowstock' if object.stock < 10
|
20
|
+
end
|
21
|
+
end if presenter.type?(:html)
|
22
|
+
|
23
|
+
column :brand, :html => { :td => { :class => 'left' } }
|
24
|
+
|
25
|
+
number_of_doors_options = ->( config, index, object ) do
|
26
|
+
config.td.class = 'center' unless index == :footer
|
27
|
+
config.td.class += ' five_doors' if object && object.number_of_doors == 5
|
28
|
+
end
|
29
|
+
column :number_of_doors, :footer => 'Total', :html => number_of_doors_options
|
30
|
+
|
31
|
+
column :stock, :footer => lambda { h.link_to("Total: #{ collection.map(&:stock).inject(&:+) }", '#') }
|
32
|
+
end
|
33
|
+
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
class CarTableWithOptions < DiningTable::Table
|
2
2
|
def define
|
3
|
-
column :brand, :html => { :
|
3
|
+
column :brand, :html => { :td => { class: 'center' }, :th => { class: :center } } do |object|
|
4
4
|
object.brand.upcase
|
5
5
|
end
|
6
|
-
column :stock, :html => { :
|
6
|
+
column :stock, :html => { :td => { class: 'left' }, :th => { class: :left } }
|
7
7
|
column :launch_date if options[:with_normal_launch_date]
|
8
8
|
column :launch_date, :class => DateColumn if options[:with_date_column_launch_date]
|
9
9
|
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class CarTableWithOptionsOldSyntax < DiningTable::Table
|
2
|
+
def define
|
3
|
+
column :brand, :html => { :td => { class: 'center' }, :th => { class: :center } } do |object|
|
4
|
+
object.brand.upcase
|
5
|
+
end
|
6
|
+
column :stock, :html => { :td_options => { class: 'left' }, :th_options => { class: :left } } # deprecated options syntax
|
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,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dining-table
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.3
|
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:
|
11
|
+
date: 2020-06-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: juwelier
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -62,6 +62,7 @@ files:
|
|
62
62
|
- lib/dining-table/presenters/csv_presenter.rb
|
63
63
|
- lib/dining-table/presenters/excel_presenter.rb
|
64
64
|
- lib/dining-table/presenters/html_presenter.rb
|
65
|
+
- lib/dining-table/presenters/html_presenter_configuration.rb
|
65
66
|
- lib/dining-table/presenters/presenter.rb
|
66
67
|
- lib/dining-table/presenters/spreadsheet_presenter.rb
|
67
68
|
- lib/dining-table/table.rb
|
@@ -72,9 +73,12 @@ files:
|
|
72
73
|
- spec/spec_helper.rb
|
73
74
|
- spec/tables/car_table.rb
|
74
75
|
- spec/tables/car_table_with_actions.rb
|
76
|
+
- spec/tables/car_table_with_config_blocks.rb
|
75
77
|
- spec/tables/car_table_with_footer.rb
|
76
78
|
- spec/tables/car_table_with_header.rb
|
77
79
|
- spec/tables/car_table_with_options.rb
|
80
|
+
- spec/tables/car_table_with_options_old_syntax.rb
|
81
|
+
- spec/tables/car_table_without_header.rb
|
78
82
|
homepage: http://github.com/mvdamme/dining-table
|
79
83
|
licenses:
|
80
84
|
- MIT
|
@@ -94,8 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
98
|
- !ruby/object:Gem::Version
|
95
99
|
version: '0'
|
96
100
|
requirements: []
|
97
|
-
|
98
|
-
rubygems_version: 2.6.11
|
101
|
+
rubygems_version: 3.1.2
|
99
102
|
signing_key:
|
100
103
|
specification_version: 4
|
101
104
|
summary: Create tables easily. Supports html, csv and xlsx.
|