tablette 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d1ea4083f7a84daf2dc1cdd21a1fddce42ab0e08
4
+ data.tar.gz: a194c4045ce19b05f0ec69c022c239cb7cef32a7
5
+ SHA512:
6
+ metadata.gz: 91950838b5802f5c25485d8bc1cb872d835501115cbc2b457105c12821898a3efa5dd157c2bd2900ad66e1f13bec45d1592fc1b4db9d1411945b78a5970e00fc
7
+ data.tar.gz: 723deac30f5dcf8f06a6f9580ea607dd1c99eb4a7016f1b6005a31a1bb6f7311c1d301f094583932f0b2a80f17933ffe8c54f00f787cba3672661cbbf86451e0
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in slashadmin-table_for.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Victor Sokolov
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # Tablette
2
+
3
+ Inspired by discussion at: https://github.com/evilmartians/slashadmin/issues/3.
4
+ Rails table renderer that tries to be flexible.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'tablette'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install tablette
19
+
20
+ ## Usage
21
+
22
+ Somwhere in your app:
23
+
24
+ ```ruby
25
+ short_table = Tablette::Table.new do
26
+ column :id
27
+ column :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 = Tablette::Table.new do
49
+ html_options class: 'table'
50
+
51
+ header do
52
+ row do
53
+ column 'Id', html_options: { colspan: 5 }
54
+ column 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
+ column html_options: ->(member, _) { { data: { value: member.id } } } do |_, index|
68
+ index
69
+ end
70
+ column :id
71
+ column :age do |member, _|
72
+ "Dead at #{member.age}"
73
+ end
74
+ column 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
+ column :test do
83
+ "test"
84
+ end
85
+
86
+ column :age, formatter: :sample_formatter
87
+ end
88
+ end
89
+
90
+ footer do
91
+ row do
92
+ column 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 column 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
+ Tablette::Table.config.html_options = { class: 'table' }
132
+ Tablette::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
+ HeaderColumn, BodyColumn, FooterColumn.
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
+ ```
151
+
152
+ ## TODO
153
+
154
+ 1. Think about sorting.
155
+ 2. Formatted output.
156
+ 3. Data attrs for everything.
157
+ 4. Authospan.
158
+ 5. :row as parameter.
159
+ 6. Reusable columns
160
+ 7. Shortened column definition
161
+ 8. Tablette.render() do
162
+
163
+ ## Contributing
164
+
165
+ 1. Fork it
166
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
167
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
168
+ 4. Push to the branch (`git push origin my-new-feature`)
169
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
5
+ task :default => :spec
@@ -0,0 +1,84 @@
1
+ module Tablette
2
+ class Column < Element
3
+ config.tag = 'td'
4
+
5
+ def initialize(*args, &block)
6
+ self.value = block
7
+ self.key = args.first if args.first.is_a?(String) or args.first.is_a?(Symbol)
8
+
9
+ config.builtin_html_options = ->(member, index = nil) do
10
+ classes = []
11
+
12
+ if self.key.present?
13
+ classes << self.key.to_s
14
+ end
15
+
16
+ if index.kind_of? Numeric
17
+ if index.odd?
18
+ classes << "odd"
19
+ else
20
+ classes << "even"
21
+ end
22
+ end
23
+
24
+ if classes.any?
25
+ { :class => classes.join(" ") }
26
+ else
27
+ {}
28
+ end
29
+ end
30
+
31
+ # Bypass block evaling: in this case it's not a config but a value formatter
32
+ super(*args, &nil)
33
+ end
34
+
35
+ def colspan(args)
36
+ override_html_options, html_options =
37
+ get_options([:override_html_options, :html_options], *args)
38
+
39
+ options = override_html_options.merge html_options
40
+ options[:colspan]
41
+ end
42
+
43
+ protected
44
+ attr_accessor :key, :value
45
+ end
46
+
47
+ class BodyColumn < Column
48
+ protected
49
+ def html_content(member, index)
50
+ value = @renderer.wrap_content(self.value).call(member, index) if self.value.present?
51
+ value ||= member.send(key) if key.present? and member.respond_to?(key)
52
+
53
+ if config.formatter.present?
54
+ value ||= send(config.formatter, key, member, index)
55
+ end
56
+
57
+ value
58
+ end
59
+ end
60
+
61
+ class HeaderColumn < Column
62
+ config.tag = 'th'
63
+
64
+ protected
65
+ def html_content(collection, resource_class = nil)
66
+ return @renderer.wrap_content(value).call(collection, resource_class) if value.is_a?(Proc)
67
+ if resource_class.respond_to?(:human_attribute_name) && key.present?
68
+ resource_class.human_attribute_name(key)
69
+ else
70
+ key
71
+ end
72
+ end
73
+ end
74
+
75
+ class FooterColumn < Column
76
+ config.tag = 'td'
77
+
78
+ protected
79
+ def html_content(*args)
80
+ return @renderer.wrap_content(value).call(*args) if value.is_a?(Proc)
81
+ key
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,14 @@
1
+ module Tablette
2
+ class Element
3
+ protected
4
+
5
+ config.allowed_configuration_options = %w(
6
+ tag html_options override_html_options
7
+ )
8
+ config.html_options = {}
9
+ config.builtin_html_options = {}
10
+ config.override_html_options = {}
11
+ config.render_nested_elements = []
12
+ config.tag = nil
13
+ end
14
+ end
@@ -0,0 +1,88 @@
1
+ module Tablette
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, opts = {})
21
+ ivar_name = opts[:ivar_name] || accessor_name
22
+ merge_children = opts[:merge_children] || false
23
+ define_method accessor_name do |*args, &block|
24
+ items = instance_variable_get("@#{ivar_name}") || []
25
+ return items if args.blank? && block.blank?
26
+
27
+ inherited_config = {
28
+ :renderer => @renderer,
29
+ :helper => @helper
30
+ }
31
+
32
+ if args.last.kind_of? Hash
33
+ last = args.pop
34
+ args << last.merge(inherited_config)
35
+ else
36
+ args << inherited_config
37
+ end
38
+
39
+ value = klass.new(*args, &block)
40
+ if merge_children && items.any?
41
+ items.first.merge_with! value
42
+ else
43
+ items.push(value)
44
+ end
45
+ instance_variable_set("@#{ivar_name}", items)
46
+ value
47
+ end
48
+ protected accessor_name
49
+ end
50
+
51
+ # Defines top-level shortcut DSL method.
52
+ #
53
+ # Example:
54
+ # class Body < Element
55
+ # nest :row, BodyRow
56
+ # end
57
+ #
58
+ # class Table < Element
59
+ # nest_through :body, :row, :column # Table#column calls .body.row.column
60
+ # end
61
+ #
62
+ # table = Table.new do
63
+ # column :id
64
+ # end
65
+ #
66
+ # table.body.first.row.first.column.first.to_html # <td data-name="id">...</td>
67
+ def nest_through(*chain)
68
+ nested_method = chain.last
69
+
70
+ define_method nested_method do |*args, &block|
71
+ _get_chained(self, chain.dup, *args, &block)
72
+ end
73
+ end
74
+ end
75
+
76
+ private
77
+ def _get_chained(context, chain, *args, &block)
78
+ key = chain.shift
79
+ if chain.empty?
80
+ context.send(key, *args, &block)
81
+ else
82
+ # Get last defined element, or define new blank
83
+ nested_item = context.send(key).last || context.send(key) { }
84
+ _get_chained(nested_item, chain, *args, &block)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,58 @@
1
+ module Tablette
2
+ class Element
3
+ # Translates element to html tag.
4
+ def to_html(*args)
5
+ tag, override_html_options, user_html_options, builtin_html_options =
6
+ get_options([:tag, :override_html_options, :html_options, :builtin_html_options], *args)
7
+
8
+ raise "Set tag option for #{self.class.name}" if tag.blank?
9
+
10
+ html_options = override_html_options.merge user_html_options
11
+
12
+ builtin_html_options.each do |key, value|
13
+ if html_options.include? key
14
+ html_options[key] = "#{value} #{html_options[key]}"
15
+ else
16
+ html_options[key] = value
17
+ end
18
+ end
19
+
20
+ @renderer.element = self
21
+ begin
22
+ @renderer.produce_element tag, html_options, html_content(*args)
23
+ ensure
24
+ @renderer.element = nil
25
+ end
26
+ end
27
+
28
+ def element_to_html(element, *args)
29
+ send(element).map do |item|
30
+ item.to_html(*args)
31
+ end
32
+ end
33
+
34
+ protected
35
+ # HTML content for element. Renders elements set by :render_nested_elements
36
+ # wrapped by :tag.
37
+ def html_content(*args)
38
+ nested = get_options(:render_nested_elements, *args).first
39
+
40
+ if nested.blank?
41
+ raise "Set render_nested_elements options or override #html_content/#to_html for #{self.class.name}"
42
+ end
43
+
44
+ nested.map { |e| element_to_html(e, *args) }.flatten!(1)
45
+ end
46
+
47
+ private
48
+
49
+ # Gets given option values. If an option is a block - yields it and
50
+ # returns value.
51
+ def get_options(keys, *args)
52
+ keys = Array.wrap(keys)
53
+ keys.map do |name|
54
+ config[name].is_a?(Proc) ? config[name].call(*args) : config[name]
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,51 @@
1
+ module Tablette
2
+ class Element
3
+ include ActiveSupport::Configurable
4
+
5
+ attr_accessor :renderer, :helper
6
+
7
+ def initialize(*args, &definition)
8
+ options = args.extract_options!
9
+ if options.include? :renderer
10
+ @renderer = options.delete :renderer
11
+ end
12
+
13
+ if options.include? :helper
14
+ @helper = options.delete :helper
15
+ end
16
+
17
+ instance_exec(&definition) if block_given?
18
+ config.merge!(options)
19
+ end
20
+
21
+ def respond_to?(method)
22
+ super || @helper.respond_to?(method) || config.allowed_configuration_options.include?(method.to_s)
23
+ end
24
+
25
+ def method_missing(method, *args, &block)
26
+ if @helper.respond_to? method
27
+ @helper.send method, *args, &block
28
+ elsif config.allowed_configuration_options.include?(method.to_s)
29
+ # Catches a call to configuration option setter.
30
+ #
31
+ # Example:
32
+ # body do
33
+ # html_options { class: 'test' } # Holds such calls
34
+ # end
35
+
36
+ config[method] = args.first || block
37
+
38
+ class_eval do
39
+ define_method method do |value|
40
+ config[method] = value
41
+ end
42
+ protected method
43
+ end
44
+
45
+ config[method]
46
+ else
47
+ super
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,44 @@
1
+ module Tablette
2
+ module HTMLRenderer
3
+ def self.element=(element)
4
+
5
+ end
6
+
7
+ def self.produce_element(tag, attributes, content)
8
+ content = content.join if content.kind_of? Array
9
+
10
+ "<#{tag} #{_to_html_args(attributes)}>#{content}</#{tag}>"
11
+ end
12
+
13
+ def self.to_html(root)
14
+ if root.kind_of? Array
15
+ root.join
16
+ else
17
+ root
18
+ end
19
+ end
20
+
21
+ def self.wrap_content(proc)
22
+ proc
23
+ end
24
+
25
+ # Translates html_options to HTML attributes string. Accepts nested
26
+ # data-attributes.
27
+ #
28
+ # Example:
29
+ # _to_html_args(ref: true, data: { id: 1 }) # ref="true" data-id="1"
30
+ def self._to_html_args(options, prepend = nil)
31
+ options = options || {}
32
+ html_args = options.map do |key, value|
33
+ if value.is_a?(Hash)
34
+ _to_html_args(value, key)
35
+ else
36
+ key = "#{prepend}-#{key}" if prepend.present?
37
+ %{#{key}="#{value}"}
38
+ end
39
+ end
40
+
41
+ html_args.join(' ')
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,70 @@
1
+ module Tablette
2
+ class Row < Element
3
+ attr_reader :columns
4
+
5
+ config.tag = 'tr'
6
+ config.render_nested_elements = %w(column)
7
+
8
+ def columns_for_row(args)
9
+ columns = 0
10
+
11
+ self.column.each do |column|
12
+ colspan = column.colspan(args)
13
+
14
+ if colspan.nil? || colspan == "auto"
15
+ columns += 1
16
+ else
17
+ columns += colspan
18
+ end
19
+ end
20
+
21
+ columns
22
+ end
23
+
24
+ def update_auto_colspan!(columns, args)
25
+ auto_column = nil
26
+
27
+ self.column.each do |column|
28
+ next unless column.colspan(args) == "auto"
29
+
30
+ if auto_column.nil?
31
+ auto_column = column
32
+ else
33
+ raise "multiple columns with colspan=auto are defined"
34
+ end
35
+ end
36
+
37
+ row_columns = columns_for_row(args)
38
+ if auto_column.nil?
39
+ if row_columns != columns
40
+ raise "mismatched number of columns in table: #{columns} in table, #{row_columns} defined in row"
41
+ end
42
+ else
43
+ auto_column.config[:html_options][:colspan] = columns - row_columns + 1
44
+ end
45
+ end
46
+ end
47
+
48
+ class BodyRow < Row
49
+ config.override_html_options = proc { |member, index|
50
+ { data: { id: member.try(:id) } }
51
+ }
52
+
53
+ nest :column, BodyColumn
54
+
55
+ protected
56
+ def html_content(member, index)
57
+ column.map do |column|
58
+ column.to_html(member, index)
59
+ end
60
+ end
61
+ end
62
+
63
+ class HeaderRow < Row
64
+ nest :column, HeaderColumn
65
+ end
66
+
67
+ class FooterRow < Row
68
+ nest :column, FooterColumn
69
+ end
70
+ end
@@ -0,0 +1,69 @@
1
+ module Tablette
2
+ class Section < Element
3
+ config.render_nested_elements = %w(row)
4
+
5
+ def columns_for_section(args)
6
+ columns = 0
7
+
8
+ self.row.each do |row|
9
+ row_columns = row.columns_for_row args
10
+ columns = row_columns if row_columns > columns
11
+ end
12
+
13
+ columns
14
+ end
15
+
16
+ def update_auto_colspan!(columns, args)
17
+ self.row.each do |row|
18
+ row.update_auto_colspan! columns, args
19
+ end
20
+ end
21
+ end
22
+
23
+ class Body < Section
24
+ config.tag = 'tbody'
25
+
26
+ nest :row, BodyRow
27
+ nest_through :row, :column
28
+
29
+ def columns_for_section(args)
30
+ collection, resource_class = args
31
+ return 0 if collection.empty?
32
+
33
+ super([collection.first, 0])
34
+ end
35
+
36
+ def update_auto_colspan!(columns, args)
37
+
38
+ end
39
+
40
+ protected
41
+ def html_content(collection, resource_class = nil)
42
+ collection.map.with_index do |member, index|
43
+ row.map { |row| row.to_html(member, index) }
44
+ end
45
+ end
46
+ end
47
+
48
+ class Header < Section
49
+ config.tag = 'thead'
50
+
51
+ nest :row, HeaderRow
52
+ nest_through :row, :column
53
+
54
+ def merge_with!(header)
55
+ self.row.concat header.row
56
+ end
57
+ end
58
+
59
+ class Footer < Section
60
+ config.tag = 'tfoot'
61
+
62
+ nest :row, FooterRow
63
+ nest_through :row, :column
64
+
65
+ def merge_with!(footer)
66
+ self.row.concat footer.row
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,49 @@
1
+ module Tablette
2
+ class Table < Element
3
+ config.tag = 'table'
4
+ config.render_nested_elements = %w(header! body footer!)
5
+ config.allowed_configuration_options = %w(tag html_options)
6
+ config.builtin_html_options = { :class => "tablette" }
7
+
8
+ nest :header!, Header, :ivar_name => :header, :merge_children => true
9
+ nest :body, Body
10
+ nest :footer!, Footer, :ivar_name => :footer, :merge_children => true
11
+
12
+ nest_through :body, :row, :column
13
+
14
+ def initialize(*args, &block)
15
+ @renderer = HTMLRenderer
16
+ @helper = nil
17
+
18
+ super
19
+ end
20
+
21
+ def header(&block)
22
+ header! do
23
+ column(html_options: { colspan: 'auto'}, &block)
24
+ end
25
+ end
26
+
27
+ def footer(&block)
28
+ footer! do
29
+ column(html_options: { colspan: 'auto'}, &block)
30
+ end
31
+ end
32
+
33
+ def to_html(*args)
34
+ columns = 0
35
+
36
+ all_sections = [ self.header!, self.body, self.footer! ].flatten
37
+ all_sections.each do |section|
38
+ section_columns = section.columns_for_section args
39
+ columns = section_columns if section_columns > columns
40
+ end
41
+
42
+ all_sections.each do |section|
43
+ section.update_auto_colspan! columns, args
44
+ end
45
+
46
+ @renderer.to_html(super)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ module Tablette
2
+ VERSION = "0.1.0"
3
+ end
data/lib/tablette.rb ADDED
@@ -0,0 +1,16 @@
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'
5
+ require 'active_support/configurable'
6
+
7
+ require 'tablette/version'
8
+ require 'tablette/element'
9
+ require 'tablette/element/configuration'
10
+ require 'tablette/element/rendering'
11
+ require 'tablette/element/nesting'
12
+ require 'tablette/columns'
13
+ require 'tablette/rows'
14
+ require 'tablette/sections'
15
+ require 'tablette/table'
16
+ require 'tablette/html_renderer'
data/spec/grid_spec.rb ADDED
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Grid' do
4
+ context 'defined fully' do
5
+ subject { sample_table_full_described }
6
+
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
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).join
39
+ end
40
+
41
+ it 'should render header, footer, body separately' do
42
+
43
+ subject.should_not have_tag 'thead'
44
+ subject.should_not have_tag 'tfoot'
45
+
46
+ subject.should have_tag 'tbody'
47
+ subject.should have_tag 'tr'
48
+ subject.should have_tag 'td'
49
+ end
50
+ end
51
+
52
+ context 'defined shortly' do
53
+ subject { sample_table_short }
54
+
55
+ it 'should render correctly' do
56
+ subject.should have_tag 'table', count: 1 do
57
+ with_tag 'tbody', count: 1
58
+ with_tag 'thead', count: 1
59
+ with_tag 'tr', count: 4
60
+ end
61
+ end
62
+ end
63
+
64
+ context 'with active record objects' do
65
+ subject { sample_table_active_record }
66
+
67
+ it 'should get right headings from active record' do
68
+ subject.should have_tag 'thead', count: 1 do
69
+ with_tag 'th', text: 'Humanized id', count: 1
70
+ with_tag 'th', text: 'Humanized age', count: 1
71
+ end
72
+
73
+ subject.should_not have_tag 'tfoot'
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'tablette'
5
+ require 'support/sample_table'
6
+ require 'rspec-html-matchers'
7
+
8
+ RSpec.configure do |config|
9
+ end
@@ -0,0 +1,103 @@
1
+ require 'ostruct'
2
+
3
+ class ActiveRecordMock
4
+ def self.human_attribute_name(name)
5
+ "Humanized #{name.to_s}"
6
+ end
7
+ end
8
+
9
+ def sample_collection
10
+ [
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')
14
+ ]
15
+ end
16
+
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]}"
23
+ end
24
+
25
+ def sample_table_full_described_definition
26
+ Tablette::Table.new do
27
+ html_options class: 'table'
28
+
29
+ header! do
30
+ column 'Id', html_options: { colspan: 5 }
31
+ column do
32
+ 'Doctor strangelove'
33
+ end
34
+ end
35
+
36
+ body do
37
+ html_options class: 'sortable'
38
+ row do
39
+ html_options do |member, index|
40
+ { data: { id: member.id, index: index } }
41
+ end
42
+
43
+ column html_options: ->(member, _) { { data: { value: member.id } } } do |_, index|
44
+ index
45
+ end
46
+ column :id
47
+ column :age do |member, _|
48
+ "Dead at #{member.age}"
49
+ end
50
+ column do |_, index|
51
+ sample_helper_function(index)
52
+ end
53
+ end
54
+
55
+ row html_options: { class: 'small' } do
56
+ tag 'overriden_tr'
57
+
58
+ column :test do
59
+ "test"
60
+ end
61
+
62
+ column :age, formatter: :sample_formatter
63
+ end
64
+ end
65
+
66
+ footer do
67
+ "On foot"
68
+ end
69
+ end
70
+ end
71
+
72
+ def sample_table_full_described
73
+ sample_table_full_described_definition.to_html(sample_collection)
74
+ end
75
+
76
+ def sample_table_short
77
+ table = Tablette::Table.new do
78
+ header do
79
+ column 'Id'
80
+ column 'Age'
81
+ end
82
+
83
+ column :id
84
+ column :age
85
+ end
86
+ table.to_html(sample_collection)
87
+ end
88
+
89
+ def sample_table_active_record
90
+ table = Tablette::Table.new do
91
+ header! do
92
+ column :id
93
+ column :age
94
+ column "Custom string"
95
+ column do
96
+ "Custom block"
97
+ end
98
+ end
99
+ column :id
100
+ column :age
101
+ end
102
+ table.to_html(sample_collection, ActiveRecordMock)
103
+ end
data/tablette.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tablette/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "tablette"
8
+ gem.version = Tablette::VERSION
9
+ gem.authors = ["Victor Sokolov", "Sergey Gridasov"]
10
+ gem.email = ["gzigzigzeo@gmail.com", "grindars@gmail.com"]
11
+ gem.description = %q{HTML table generator}
12
+ gem.summary = %q{HTML table generator}
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency 'activesupport', '>= 3'
21
+
22
+ gem.add_development_dependency 'rspec'
23
+ gem.add_development_dependency 'rake'
24
+ gem.add_development_dependency 'rspec-html-matchers'
25
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tablette
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Victor Sokolov
8
+ - Sergey Gridasov
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '3'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - '>='
26
+ - !ruby/object:Gem::Version
27
+ version: '3'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rspec
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - '>='
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec-html-matchers
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ description: HTML table generator
71
+ email:
72
+ - gzigzigzeo@gmail.com
73
+ - grindars@gmail.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - .gitignore
79
+ - .rspec
80
+ - .travis.yml
81
+ - Gemfile
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - lib/tablette.rb
86
+ - lib/tablette/columns.rb
87
+ - lib/tablette/element.rb
88
+ - lib/tablette/element/configuration.rb
89
+ - lib/tablette/element/nesting.rb
90
+ - lib/tablette/element/rendering.rb
91
+ - lib/tablette/html_renderer.rb
92
+ - lib/tablette/rows.rb
93
+ - lib/tablette/sections.rb
94
+ - lib/tablette/table.rb
95
+ - lib/tablette/version.rb
96
+ - spec/grid_spec.rb
97
+ - spec/spec_helper.rb
98
+ - spec/support/sample_table.rb
99
+ - tablette.gemspec
100
+ homepage: ''
101
+ licenses: []
102
+ metadata: {}
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 2.0.0
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: HTML table generator
123
+ test_files:
124
+ - spec/grid_spec.rb
125
+ - spec/spec_helper.rb
126
+ - spec/support/sample_table.rb