dining-table 0.2.1 → 1.1.3
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.
- 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
|
[](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.
|