grid_fu 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.travis.yml +5 -0
- data/README.md +136 -17
- data/grid_fu.gemspec +2 -1
- data/lib/grid_fu.rb +10 -4
- data/lib/grid_fu/cells.rb +54 -0
- data/lib/grid_fu/element.rb +4 -71
- data/lib/grid_fu/element/configuration.rb +32 -0
- data/lib/grid_fu/element/nesting.rb +70 -0
- data/lib/grid_fu/element/rendering.rb +73 -0
- data/lib/grid_fu/rows.rb +32 -0
- data/lib/grid_fu/sections.rb +34 -0
- data/lib/grid_fu/table.rb +8 -17
- data/lib/grid_fu/version.rb +1 -1
- data/spec/grid_spec.rb +69 -9
- data/spec/spec_helper.rb +1 -1
- data/spec/support/sample_table.rb +81 -13
- metadata +28 -8
- data/lib/grid_fu/body.rb +0 -28
- data/lib/grid_fu/cell.rb +0 -32
- data/lib/grid_fu/row.rb +0 -26
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# GridFu
|
2
2
|
|
3
|
-
https://github.com/evilmartians/slashadmin/issues/3
|
3
|
+
Inspired by discussion at: https://github.com/evilmartians/slashadmin/issues/3.
|
4
|
+
Rails table renderer that tries to be flexible.
|
4
5
|
|
5
6
|
## Installation
|
6
7
|
|
@@ -18,25 +19,143 @@ Or install it yourself as:
|
|
18
19
|
|
19
20
|
## Usage
|
20
21
|
|
21
|
-
|
22
|
+
Somwhere in your app:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
short_table = GridFu.define do
|
26
|
+
cell :id
|
27
|
+
cell :name
|
28
|
+
end
|
29
|
+
|
30
|
+
puts short_table.to_html(collection, User)
|
31
|
+
```
|
32
|
+
|
33
|
+
You will see following:
|
34
|
+
|
35
|
+
```html
|
36
|
+
# <table>
|
37
|
+
# <thead><tr><th>Id</th><th>User name</th></tr></thead>
|
38
|
+
# <tbody>
|
39
|
+
# <tr><td>1</td><td>John Doe</td></tr>
|
40
|
+
# <tr>...</tr>
|
41
|
+
# </tbody>
|
42
|
+
# </table>
|
43
|
+
```
|
44
|
+
|
45
|
+
## Full definition
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
table = GridFu.define do
|
49
|
+
html_options class: 'table'
|
50
|
+
|
51
|
+
header do
|
52
|
+
row do
|
53
|
+
cell 'Id', html_options: { colspan: 5 }
|
54
|
+
cell do
|
55
|
+
'Doctor strangelove'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
body do
|
61
|
+
html_options class: 'sortable'
|
62
|
+
row do
|
63
|
+
html_options do |member, index|
|
64
|
+
{ data: { id: member.id, index: index } }
|
65
|
+
end
|
66
|
+
|
67
|
+
cell html_options: ->(member, _) { { data: { value: member.id } } } do |_, index|
|
68
|
+
index
|
69
|
+
end
|
70
|
+
cell :id
|
71
|
+
cell :age do |member, _|
|
72
|
+
"Dead at #{member.age}"
|
73
|
+
end
|
74
|
+
cell do |_, index|
|
75
|
+
sample_helper_function(index)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
row html_options: { class: 'small' } do
|
80
|
+
tag 'overriden_tr'
|
81
|
+
|
82
|
+
cell :test do
|
83
|
+
"test"
|
84
|
+
end
|
85
|
+
|
86
|
+
cell :age, formatter: :sample_formatter
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
footer do
|
91
|
+
row do
|
92
|
+
cell html_options: { rowspan: 3 } do
|
93
|
+
"On foot"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
puts table.to_html(collection)
|
100
|
+
```
|
101
|
+
|
102
|
+
Every element accepts:
|
103
|
+
* html_options - to customize default options.
|
104
|
+
* override_html_options - to completely override default html options.
|
105
|
+
* tag - to change tag name.
|
106
|
+
|
107
|
+
Default HTML options are:
|
108
|
+
* data-id - for tbody/tr.
|
109
|
+
* data-key - for tbody/tr/td.
|
110
|
+
|
111
|
+
Options which are set by blocks accepts:
|
112
|
+
* |member, index| - for row and cell inside body element.
|
113
|
+
* |collection, klass = nil| - for table, header and footer (and all nested elements)
|
114
|
+
* Same for body.
|
115
|
+
|
116
|
+
Method called with :formatter option accepts value, member and index.
|
117
|
+
|
118
|
+
You can override default html options for an element with :override_html_options
|
119
|
+
option.
|
120
|
+
|
121
|
+
You can specify two or more rows in body section. All of this rows will be
|
122
|
+
rendered for every collection item.
|
123
|
+
|
124
|
+
## Global configuration
|
125
|
+
|
126
|
+
Table elements can be customized at application level.
|
127
|
+
|
128
|
+
Somewhere in initializer:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
GridFu::Table.config.html_options = { class: 'table' }
|
132
|
+
GridFu::HeaderRow.config.html_options = proc { |_, resource_class = nil|
|
133
|
+
{ class: resource_class.name.underscore }
|
134
|
+
}
|
135
|
+
```
|
136
|
+
|
137
|
+
You can use: Table, Header, Body, Footer, HeaderRow, BodyRow, FooterRow,
|
138
|
+
HeaderCell, BodyCell, FooterCell.
|
139
|
+
|
140
|
+
So, you can replace table with ordered list or something you need.
|
141
|
+
|
142
|
+
## Partial rendering
|
143
|
+
|
144
|
+
Could be useful for twitter-style pagination:
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
table.element_to_html(:header, collection, User)
|
148
|
+
table.element_to_html(:body, collection, User)
|
149
|
+
table.element_to_html(:footer, collection, User)
|
150
|
+
```
|
22
151
|
|
23
152
|
## TODO
|
24
153
|
|
25
|
-
1.
|
26
|
-
2.
|
27
|
-
3.
|
28
|
-
4.
|
29
|
-
5.
|
30
|
-
6. Default data attrs for everything.
|
31
|
-
7. Rowspan
|
32
|
-
8. :span
|
33
|
-
9. Footer
|
34
|
-
10. Header
|
35
|
-
11. Base class for footer, header and tbody.
|
36
|
-
12. Avoid body block if there's no header/footer.
|
37
|
-
13. value: :function cell param
|
38
|
-
14. merge_html_options?
|
39
|
-
15. Bypass :value param.
|
154
|
+
1. Think about sorting.
|
155
|
+
2. Formatted output.
|
156
|
+
3. Data attrs for everything.
|
157
|
+
4. Authospan.
|
158
|
+
5. :row as parameter.
|
40
159
|
|
41
160
|
## Contributing
|
42
161
|
|
data/grid_fu.gemspec
CHANGED
@@ -17,8 +17,9 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
18
|
gem.require_paths = ["lib"]
|
19
19
|
|
20
|
-
gem.add_dependency '
|
20
|
+
gem.add_dependency 'activesupport', '~> 3'
|
21
21
|
|
22
22
|
gem.add_development_dependency 'rspec'
|
23
23
|
gem.add_development_dependency 'rake'
|
24
|
+
gem.add_development_dependency 'rspec-html-matchers'
|
24
25
|
end
|
data/lib/grid_fu.rb
CHANGED
@@ -1,15 +1,21 @@
|
|
1
|
-
require 'active_support/core_ext/class/attribute_accessors'
|
2
1
|
require 'active_support/core_ext/object/blank'
|
2
|
+
require 'active_support/core_ext/object/inclusion'
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
4
|
+
require 'active_support/core_ext/object/try'
|
3
5
|
require 'active_support/configurable'
|
4
6
|
|
5
7
|
require 'grid_fu/version'
|
6
8
|
require 'grid_fu/element'
|
9
|
+
require 'grid_fu/element/configuration'
|
10
|
+
require 'grid_fu/element/rendering'
|
11
|
+
require 'grid_fu/element/nesting'
|
12
|
+
require 'grid_fu/cells'
|
13
|
+
require 'grid_fu/rows'
|
14
|
+
require 'grid_fu/sections'
|
7
15
|
require 'grid_fu/table'
|
8
|
-
require 'grid_fu/row'
|
9
|
-
require 'grid_fu/body'
|
10
|
-
require 'grid_fu/cell'
|
11
16
|
|
12
17
|
module GridFu
|
18
|
+
# TODO: Custom table class
|
13
19
|
def define(*args, &block)
|
14
20
|
Table.new(*args, &block)
|
15
21
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module GridFu
|
2
|
+
class Cell < Element
|
3
|
+
def initialize(*args, &block)
|
4
|
+
self.value = block
|
5
|
+
self.key = args.first if args.first.is_a?(String) or args.first.is_a?(Symbol)
|
6
|
+
|
7
|
+
# Bypass block evaling: in this case it's not a config but a value formatter
|
8
|
+
super(*args, &nil)
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
attr_accessor :key, :value
|
13
|
+
end
|
14
|
+
|
15
|
+
class BodyCell < Cell
|
16
|
+
config.tag = 'td'
|
17
|
+
|
18
|
+
protected
|
19
|
+
def html_content(member, index)
|
20
|
+
value = self.value.call(member, index) if self.value.present?
|
21
|
+
value ||= member.send(key) if key.present? and member.respond_to?(key)
|
22
|
+
|
23
|
+
if config.formatter.present?
|
24
|
+
value ||= send(config.formatter, key, member, index)
|
25
|
+
end
|
26
|
+
|
27
|
+
value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class HeaderCell < Cell
|
32
|
+
config.tag = 'th'
|
33
|
+
|
34
|
+
protected
|
35
|
+
def html_content(collection, resource_class = nil)
|
36
|
+
return value.call(collection, resource_class) if value.is_a?(Proc)
|
37
|
+
if resource_class.respond_to?(:human_attribute_name)
|
38
|
+
resource_class.human_attribute_name(key)
|
39
|
+
else
|
40
|
+
key
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class FooterCell < Cell
|
46
|
+
config.tag = 'td'
|
47
|
+
|
48
|
+
protected
|
49
|
+
def html_content(*args)
|
50
|
+
return value.call(*args) if value.is_a?(Proc)
|
51
|
+
key
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/grid_fu/element.rb
CHANGED
@@ -2,77 +2,10 @@ module GridFu
|
|
2
2
|
class Element
|
3
3
|
include ActiveSupport::Configurable
|
4
4
|
|
5
|
-
def initialize(*args, &
|
6
|
-
instance_exec(&
|
7
|
-
|
8
|
-
config.html_options ||= {}
|
9
|
-
|
10
|
-
set_options([:tag, :html_options], *args)
|
11
|
-
end
|
12
|
-
|
13
|
-
def to_html(*args, &block)
|
14
|
-
tag, html_options = get_options([:tag, :html_options], *args)
|
15
|
-
|
16
|
-
html_options = _to_html_args(html_options)
|
17
|
-
|
18
|
-
html = []
|
19
|
-
html << "<#{tag}"
|
20
|
-
if html_options.present?
|
21
|
-
html << " #{html_options}"
|
22
|
-
end
|
23
|
-
html << '>'
|
24
|
-
if block_given?
|
25
|
-
html << yield(*args)
|
26
|
-
else
|
27
|
-
html << html_content(*args).to_s
|
28
|
-
end
|
29
|
-
html << "</#{tag}>"
|
30
|
-
|
31
|
-
html.join
|
32
|
-
end
|
33
|
-
|
34
|
-
protected
|
35
|
-
def html_content(*args)
|
36
|
-
raise NotImplementedError, "Must implement #html_content for #{self.class.name} or pass a block for #to_html"
|
37
|
-
end
|
38
|
-
|
39
|
-
def set_options(expected, *args)
|
5
|
+
def initialize(*args, &definition)
|
6
|
+
instance_exec(&definition) if block_given?
|
40
7
|
options = args.extract_options!
|
41
|
-
|
42
|
-
expected.each do |name|
|
43
|
-
config[name] = options.delete(name) if options.key?(name)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def get_options(expected, *args)
|
48
|
-
expected.map do |name|
|
49
|
-
config[name].is_a?(Proc) ? config[name].call(*args) : config[name]
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def self.option_setter(name)
|
54
|
-
define_method name do |value = nil, &block|
|
55
|
-
if value.present? || block.present?
|
56
|
-
config[name] = value || block
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
option_setter :tag
|
62
|
-
option_setter :html_options
|
63
|
-
|
64
|
-
private
|
65
|
-
def _to_html_args(options, prepend = nil)
|
66
|
-
options = options || {}
|
67
|
-
html_args = options.map do |key, value|
|
68
|
-
if value.is_a?(Hash)
|
69
|
-
_to_html_args(value, key)
|
70
|
-
else
|
71
|
-
key = "#{prepend}-#{key}" if prepend.present?
|
72
|
-
%{#{key}="#{value}"}
|
73
|
-
end
|
74
|
-
end
|
75
|
-
html_args.join(' ')
|
8
|
+
config.merge!(options)
|
76
9
|
end
|
77
10
|
end
|
78
|
-
end
|
11
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module GridFu
|
2
|
+
class Element
|
3
|
+
protected
|
4
|
+
# Catches a call to configuration option setter.
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
# body do
|
8
|
+
# html_options { class: 'test' } # Holds such calls
|
9
|
+
# end
|
10
|
+
def method_missing(method_name, *args, &block)
|
11
|
+
return super unless method_name.to_s.in?(config.allowed_configuration_options)
|
12
|
+
config[method_name] = args.first || block
|
13
|
+
|
14
|
+
class_eval do
|
15
|
+
define_method method_name do |value|
|
16
|
+
config[method_name] = value
|
17
|
+
end
|
18
|
+
protected method_name
|
19
|
+
end
|
20
|
+
|
21
|
+
config[method_name]
|
22
|
+
end
|
23
|
+
|
24
|
+
config.allowed_configuration_options = %w(
|
25
|
+
tag html_options override_html_options
|
26
|
+
)
|
27
|
+
config.html_options = {}
|
28
|
+
config.override_html_options = {}
|
29
|
+
config.render_nested_elements = []
|
30
|
+
config.tag = nil
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module GridFu
|
2
|
+
class Element
|
3
|
+
class << self
|
4
|
+
protected
|
5
|
+
# Defines DSL method for configuring nested element.
|
6
|
+
# If no args/block passed - returns currently defined elements as array.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
# class Table < Element
|
10
|
+
# nest :body, Body
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# table = Table.new do
|
14
|
+
# body do
|
15
|
+
# (...)
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# table.body.first.to_html # <tbody>...</tbody>
|
20
|
+
def nest(accessor_name, klass)
|
21
|
+
define_method accessor_name do |*args, &block|
|
22
|
+
items = instance_variable_get("@#{accessor_name}") || []
|
23
|
+
return items if args.blank? && block.blank?
|
24
|
+
|
25
|
+
value = klass.new(*args, &block)
|
26
|
+
items.push(value)
|
27
|
+
instance_variable_set("@#{accessor_name}", items)
|
28
|
+
value
|
29
|
+
end
|
30
|
+
protected accessor_name
|
31
|
+
end
|
32
|
+
|
33
|
+
# Defines top-level shortcut DSL method.
|
34
|
+
#
|
35
|
+
# Example:
|
36
|
+
# class Body < Element
|
37
|
+
# nest :row, BodyRow
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# class Table < Element
|
41
|
+
# nest_through :body, :row, :cell # Table#cell calls .body.row.cell
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# table = Table.new do
|
45
|
+
# cell :id
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# table.body.first.row.first.cell.first.to_html # <td data-name="id">...</td>
|
49
|
+
def nest_through(*chain)
|
50
|
+
nested_method = chain.last
|
51
|
+
|
52
|
+
define_method nested_method do |*args, &block|
|
53
|
+
_get_chained(self, chain.dup, *args, &block)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def _get_chained(context, chain, *args, &block)
|
60
|
+
key = chain.shift
|
61
|
+
if chain.empty?
|
62
|
+
context.send(key, *args, &block)
|
63
|
+
else
|
64
|
+
# Get last defined element, or define new blank
|
65
|
+
nested_item = context.send(key).last || context.send(key) { }
|
66
|
+
_get_chained(nested_item, chain, *args, &block)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module GridFu
|
2
|
+
class Element
|
3
|
+
# Translates element to html tag.
|
4
|
+
def to_html(*args)
|
5
|
+
tag, override_html_options, html_options =
|
6
|
+
get_options([:tag, :override_html_options, :html_options], *args)
|
7
|
+
|
8
|
+
raise "Set tag option for #{self.class.name}" if tag.blank?
|
9
|
+
|
10
|
+
html_options = override_html_options.merge(html_options)
|
11
|
+
html_options = _to_html_args(html_options)
|
12
|
+
|
13
|
+
html = []
|
14
|
+
|
15
|
+
html << "<#{tag}"
|
16
|
+
html << " #{html_options}" if html_options.present?
|
17
|
+
html << '>'
|
18
|
+
|
19
|
+
html << html_content(*args).to_s
|
20
|
+
|
21
|
+
html << "</#{tag}>"
|
22
|
+
html.join
|
23
|
+
end
|
24
|
+
|
25
|
+
def element_to_html(element, *args)
|
26
|
+
send(element).map { |item| item.to_html(*args) }.join
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
# HTML content for element. Renders elements set by :render_nested_elements
|
31
|
+
# wrapped by :tag.
|
32
|
+
def html_content(*args)
|
33
|
+
nested = get_options(:render_nested_elements, *args).first
|
34
|
+
|
35
|
+
if nested.blank?
|
36
|
+
raise "Set render_nested_elements options or override #html_content/#to_html for #{self.class.name}"
|
37
|
+
end
|
38
|
+
|
39
|
+
html = nested.map do |element|
|
40
|
+
self.send(element).map { |element| element.to_html(*args) }.join
|
41
|
+
end
|
42
|
+
html.join
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
# Translates html_options to HTML attributes string. Accepts nested
|
47
|
+
# data-attributes.
|
48
|
+
#
|
49
|
+
# Example:
|
50
|
+
# _to_html_args(ref: true, data: { id: 1 }) # ref="true" data-id="1"
|
51
|
+
def _to_html_args(options, prepend = nil)
|
52
|
+
options = options || {}
|
53
|
+
html_args = options.map do |key, value|
|
54
|
+
if value.is_a?(Hash)
|
55
|
+
_to_html_args(value, key)
|
56
|
+
else
|
57
|
+
key = "#{prepend}-#{key}" if prepend.present?
|
58
|
+
%{#{key}="#{value}"}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
html_args.join(' ')
|
62
|
+
end
|
63
|
+
|
64
|
+
# Gets given option values. If an option is a block - yields it and
|
65
|
+
# returns value.
|
66
|
+
def get_options(keys, *args)
|
67
|
+
keys = Array.wrap(keys)
|
68
|
+
keys.map do |name|
|
69
|
+
config[name].is_a?(Proc) ? config[name].call(*args) : config[name]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/grid_fu/rows.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module GridFu
|
2
|
+
class Row < Element
|
3
|
+
attr_reader :cells
|
4
|
+
|
5
|
+
config.tag = 'tr'
|
6
|
+
config.render_nested_elements = %w(cell)
|
7
|
+
end
|
8
|
+
|
9
|
+
class BodyRow < Row
|
10
|
+
config.override_html_options = proc { |member, index|
|
11
|
+
{ data: { id: member.try(:id) } }
|
12
|
+
}
|
13
|
+
|
14
|
+
nest :cell, BodyCell
|
15
|
+
|
16
|
+
protected
|
17
|
+
def html_content(member, index)
|
18
|
+
html = cell.map do |cell|
|
19
|
+
cell.to_html(member, index)
|
20
|
+
end
|
21
|
+
html.join
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class HeaderRow < Row
|
26
|
+
nest :cell, HeaderCell
|
27
|
+
end
|
28
|
+
|
29
|
+
class FooterRow < Row
|
30
|
+
nest :cell, FooterCell
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module GridFu
|
2
|
+
class Section < Element
|
3
|
+
config.render_nested_elements = %w(row)
|
4
|
+
end
|
5
|
+
|
6
|
+
class Body < Section
|
7
|
+
config.tag = 'tbody'
|
8
|
+
|
9
|
+
nest :row, BodyRow
|
10
|
+
nest_through :row, :cell
|
11
|
+
|
12
|
+
protected
|
13
|
+
def html_content(collection, resource_class = nil)
|
14
|
+
html = collection.map.with_index do |member, index|
|
15
|
+
row.map { |row| row.to_html(member, index) }.join(' ')
|
16
|
+
end
|
17
|
+
html.join
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Header < Section
|
22
|
+
config.tag = 'thead'
|
23
|
+
|
24
|
+
nest :row, HeaderRow
|
25
|
+
nest_through :row, :cell
|
26
|
+
end
|
27
|
+
|
28
|
+
class Footer < Section
|
29
|
+
config.tag = 'tfoot'
|
30
|
+
|
31
|
+
nest :row, FooterRow
|
32
|
+
nest_through :row, :cell
|
33
|
+
end
|
34
|
+
end
|
data/lib/grid_fu/table.rb
CHANGED
@@ -1,22 +1,13 @@
|
|
1
1
|
module GridFu
|
2
2
|
class Table < Element
|
3
|
-
|
3
|
+
config.tag = 'table'
|
4
|
+
config.render_nested_elements = %w(header body footer)
|
5
|
+
config.allowed_configuration_options = %w(tag html_options)
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
+
nest :header, Header
|
8
|
+
nest :body, Body
|
9
|
+
nest :footer, Footer
|
7
10
|
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
protected
|
12
|
-
def html_content(collection, resource_class = nil)
|
13
|
-
body_content.to_html(collection, resource_class)
|
14
|
-
end
|
15
|
-
|
16
|
-
def body(*args, &block)
|
17
|
-
self.body_content = Body.new(*args, &block)
|
18
|
-
end
|
19
|
-
|
20
|
-
attr_writer :header_content, :body_content, :footer_content
|
11
|
+
nest_through :body, :row, :cell
|
21
12
|
end
|
22
|
-
end
|
13
|
+
end
|
data/lib/grid_fu/version.rb
CHANGED
data/spec/grid_spec.rb
CHANGED
@@ -1,15 +1,75 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe '
|
4
|
-
|
3
|
+
describe 'Grid' do
|
4
|
+
context 'defined fully' do
|
5
|
+
subject { sample_table_full_described }
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
it 'should render correctly' do
|
8
|
+
subject.should have_tag 'table', with: { class: 'table' }, count: 1 do
|
9
|
+
with_tag 'thead', count: 1 do
|
10
|
+
with_tag 'th', text: 'Doctor strangelove', count: 1
|
11
|
+
with_tag 'th', text: 'Id', count: 1
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
+
with_tag 'tbody', with: { class: 'sortable' }, count: 1 do
|
15
|
+
sample_collection.each_with_index do |member, index|
|
16
|
+
with_tag "tr[data-id='#{member.id}'][data-index='#{index}']"
|
17
|
+
|
18
|
+
with_tag 'td', with: { 'data-value' => member.id }, text: index, count: 1
|
19
|
+
with_tag 'td', text: member.id, count: 1
|
20
|
+
with_tag 'td', text: "Dead at #{member.age}", count: 1
|
21
|
+
with_tag 'td', text: "I hope this helps #{index}", count: 1
|
22
|
+
|
23
|
+
with_tag 'overriden_tr'
|
24
|
+
|
25
|
+
with_tag 'td', text: member.age
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
with_tag 'tfoot', count: 1 do
|
30
|
+
with_tag 'td', text: 'On foot', count: 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'partial rendering' do
|
37
|
+
subject do
|
38
|
+
sample_table_full_described_definition.element_to_html(:body, sample_collection)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should render header, footer, body separately' do
|
42
|
+
subject.should_not have_tag 'thead'
|
43
|
+
subject.should_not have_tag 'tfoot'
|
44
|
+
|
45
|
+
subject.should have_tag 'tbody'
|
46
|
+
subject.should have_tag 'tr'
|
47
|
+
subject.should have_tag 'td'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'defined shortly' do
|
52
|
+
subject { sample_table_short }
|
53
|
+
|
54
|
+
it 'should render correctly' do
|
55
|
+
subject.should have_tag 'table', count: 1 do
|
56
|
+
with_tag 'tbody', count: 1
|
57
|
+
with_tag 'thead', count: 1
|
58
|
+
with_tag 'tr', count: 4
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'with active record objects' do
|
64
|
+
subject { sample_table_active_record }
|
65
|
+
|
66
|
+
it 'should get right headings from active record' do
|
67
|
+
subject.should have_tag 'thead', count: 1 do
|
68
|
+
with_tag 'th', text: 'Humanized id', count: 1
|
69
|
+
with_tag 'th', text: 'Humanized age', count: 1
|
70
|
+
end
|
71
|
+
|
72
|
+
subject.should_not have_tag 'tfoot'
|
73
|
+
end
|
14
74
|
end
|
15
75
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,41 +1,109 @@
|
|
1
1
|
require 'ostruct'
|
2
2
|
|
3
|
+
class ActiveRecordMock
|
4
|
+
def self.human_attribute_name(name)
|
5
|
+
"Humanized #{name.to_s}"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
3
9
|
def sample_collection
|
4
10
|
[
|
5
|
-
OpenStruct.new(id:
|
6
|
-
OpenStruct.new(id:
|
7
|
-
OpenStruct.new(id:
|
11
|
+
OpenStruct.new(id: 10, age: 27, value: 'Jim Morrison'),
|
12
|
+
OpenStruct.new(id: 20, age: 70, value: 'William Blake'),
|
13
|
+
OpenStruct.new(id: 30, age: 89, value: 'Robert Lee Frost')
|
8
14
|
]
|
9
15
|
end
|
10
16
|
|
11
|
-
def sample_helper_function
|
12
|
-
"I hope this helps"
|
17
|
+
def sample_helper_function(arg)
|
18
|
+
"I hope this helps #{arg}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def sample_formatter(key, member, index)
|
22
|
+
"Formatter for #{member[key]}"
|
13
23
|
end
|
14
24
|
|
15
|
-
def
|
25
|
+
def sample_table_full_described_definition
|
16
26
|
GridFu.define do
|
27
|
+
html_options class: 'table'
|
28
|
+
|
29
|
+
header do
|
30
|
+
row do
|
31
|
+
cell 'Id', html_options: { colspan: 5 }
|
32
|
+
cell do
|
33
|
+
'Doctor strangelove'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
17
38
|
body do
|
18
39
|
html_options class: 'sortable'
|
19
40
|
row do
|
20
41
|
html_options do |member, index|
|
21
|
-
{ data: { id: member.id } }
|
42
|
+
{ data: { id: member.id, index: index } }
|
22
43
|
end
|
23
44
|
|
24
|
-
cell html_options: ->(
|
45
|
+
cell html_options: ->(member, _) { { data: { value: member.id } } } do |_, index|
|
25
46
|
index
|
26
47
|
end
|
27
48
|
cell :id
|
28
|
-
cell :age do |
|
29
|
-
"Dead at #{
|
49
|
+
cell :age do |member, _|
|
50
|
+
"Dead at #{member.age}"
|
30
51
|
end
|
31
|
-
cell do
|
32
|
-
sample_helper_function
|
52
|
+
cell do |_, index|
|
53
|
+
sample_helper_function(index)
|
33
54
|
end
|
34
55
|
end
|
35
56
|
|
36
57
|
row html_options: { class: 'small' } do
|
37
|
-
tag '
|
58
|
+
tag 'overriden_tr'
|
59
|
+
|
60
|
+
cell :test do
|
61
|
+
"test"
|
62
|
+
end
|
63
|
+
|
64
|
+
cell :age, formatter: :sample_formatter
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
footer do
|
69
|
+
row do
|
70
|
+
cell html_options: { rowspan: 3 } do
|
71
|
+
"On foot"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def sample_table_full_described
|
79
|
+
sample_table_full_described_definition.to_html(sample_collection)
|
80
|
+
end
|
81
|
+
|
82
|
+
def sample_table_short
|
83
|
+
table = GridFu.define do
|
84
|
+
header do
|
85
|
+
cell 'Id'
|
86
|
+
cell 'Age'
|
87
|
+
end
|
88
|
+
|
89
|
+
cell :id
|
90
|
+
cell :age
|
91
|
+
end
|
92
|
+
table.to_html(sample_collection)
|
93
|
+
end
|
94
|
+
|
95
|
+
def sample_table_active_record
|
96
|
+
table = GridFu.define do
|
97
|
+
header do
|
98
|
+
cell :id
|
99
|
+
cell :age
|
100
|
+
cell "Custom string"
|
101
|
+
cell do
|
102
|
+
"Custom block"
|
38
103
|
end
|
39
104
|
end
|
105
|
+
cell :id
|
106
|
+
cell :age
|
40
107
|
end
|
108
|
+
table.to_html(sample_collection, ActiveRecordMock)
|
41
109
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: grid_fu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,10 +9,10 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
15
|
+
name: activesupport
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
@@ -59,6 +59,22 @@ dependencies:
|
|
59
59
|
- - ! '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec-html-matchers
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
62
78
|
description: HTML table generator
|
63
79
|
email:
|
64
80
|
- gzigzigzeo@gmail.com
|
@@ -68,16 +84,20 @@ extra_rdoc_files: []
|
|
68
84
|
files:
|
69
85
|
- .gitignore
|
70
86
|
- .rspec
|
87
|
+
- .travis.yml
|
71
88
|
- Gemfile
|
72
89
|
- LICENSE.txt
|
73
90
|
- README.md
|
74
91
|
- Rakefile
|
75
92
|
- grid_fu.gemspec
|
76
93
|
- lib/grid_fu.rb
|
77
|
-
- lib/grid_fu/
|
78
|
-
- lib/grid_fu/cell.rb
|
94
|
+
- lib/grid_fu/cells.rb
|
79
95
|
- lib/grid_fu/element.rb
|
80
|
-
- lib/grid_fu/
|
96
|
+
- lib/grid_fu/element/configuration.rb
|
97
|
+
- lib/grid_fu/element/nesting.rb
|
98
|
+
- lib/grid_fu/element/rendering.rb
|
99
|
+
- lib/grid_fu/rows.rb
|
100
|
+
- lib/grid_fu/sections.rb
|
81
101
|
- lib/grid_fu/table.rb
|
82
102
|
- lib/grid_fu/version.rb
|
83
103
|
- spec/grid_spec.rb
|
@@ -97,7 +117,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
97
117
|
version: '0'
|
98
118
|
segments:
|
99
119
|
- 0
|
100
|
-
hash:
|
120
|
+
hash: -31517031954940795
|
101
121
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
122
|
none: false
|
103
123
|
requirements:
|
@@ -106,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
126
|
version: '0'
|
107
127
|
segments:
|
108
128
|
- 0
|
109
|
-
hash:
|
129
|
+
hash: -31517031954940795
|
110
130
|
requirements: []
|
111
131
|
rubyforge_project:
|
112
132
|
rubygems_version: 1.8.25
|
data/lib/grid_fu/body.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
module GridFu
|
2
|
-
class Body < Element
|
3
|
-
attr_reader :rows
|
4
|
-
|
5
|
-
def initialize(*args, &block)
|
6
|
-
self.rows = []
|
7
|
-
|
8
|
-
config.tag ||= 'tbody'
|
9
|
-
|
10
|
-
super
|
11
|
-
end
|
12
|
-
|
13
|
-
protected
|
14
|
-
def html_content(collection, resource_class)
|
15
|
-
html = collection.map do |member|
|
16
|
-
rows.map.with_index { |row, index| row.to_html(member, index) }.join(' ')
|
17
|
-
end
|
18
|
-
html.join
|
19
|
-
end
|
20
|
-
|
21
|
-
protected
|
22
|
-
def row(*args, &block)
|
23
|
-
self.rows << Row.new(&block)
|
24
|
-
end
|
25
|
-
|
26
|
-
attr_writer :rows
|
27
|
-
end
|
28
|
-
end
|
data/lib/grid_fu/cell.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
module GridFu
|
2
|
-
class Cell < Element
|
3
|
-
def initialize(*args, &block)
|
4
|
-
self.value = block
|
5
|
-
self.key = args.first if args.first.is_a?(String) or args.first.is_a?(Symbol)
|
6
|
-
|
7
|
-
config.tag = 'td'
|
8
|
-
super(*args, &nil) # Bypass block evaling: in this case it's a value formatter
|
9
|
-
end
|
10
|
-
|
11
|
-
attr_reader :key, :value
|
12
|
-
|
13
|
-
def to_html(member, index, &block)
|
14
|
-
member_value = member.send(key) if key.present? and member.respond_to?(key)
|
15
|
-
member_value = if value.is_a?(Proc)
|
16
|
-
value.call(member_value, member, index)
|
17
|
-
else
|
18
|
-
member_value.to_s
|
19
|
-
end
|
20
|
-
|
21
|
-
super(*[member_value, member, index], &block)
|
22
|
-
end
|
23
|
-
|
24
|
-
protected
|
25
|
-
def html_content(value, member, index)
|
26
|
-
value
|
27
|
-
end
|
28
|
-
|
29
|
-
attr_writer :value
|
30
|
-
attr_writer :key
|
31
|
-
end
|
32
|
-
end
|
data/lib/grid_fu/row.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
module GridFu
|
2
|
-
class Row < Element
|
3
|
-
attr_reader :cells
|
4
|
-
|
5
|
-
def initialize(*args, &block)
|
6
|
-
self.cells = []
|
7
|
-
config.tag = 'tr'
|
8
|
-
|
9
|
-
super
|
10
|
-
end
|
11
|
-
|
12
|
-
protected
|
13
|
-
def html_content(member, index)
|
14
|
-
html = cells.map do |cell|
|
15
|
-
cell.to_html(member, index)
|
16
|
-
end
|
17
|
-
html.join
|
18
|
-
end
|
19
|
-
|
20
|
-
def cell(*args, &block)
|
21
|
-
self.cells << Cell.new(*args, &block)
|
22
|
-
end
|
23
|
-
|
24
|
-
attr_writer :cells
|
25
|
-
end
|
26
|
-
end
|