tabulatr 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +106 -0
- data/LICENSE +23 -0
- data/README.textile +284 -0
- data/Rakefile +11 -0
- data/assets/simple_table.png +0 -0
- data/assets/tabulatr.css +107 -0
- data/lib/initializers/action_view.rb +31 -0
- data/lib/initializers/active_record.rb +32 -0
- data/lib/initializers/mark_as_localizable.rb +43 -0
- data/lib/initializers/mongoid.rb +34 -0
- data/lib/tabulatr/tabulatr/batch_actions.rb +52 -0
- data/lib/tabulatr/tabulatr/check_controls.rb +43 -0
- data/lib/tabulatr/tabulatr/data_cell.rb +118 -0
- data/lib/tabulatr/tabulatr/filter_cell.rb +130 -0
- data/lib/tabulatr/tabulatr/finder/find_for_active_record_table.rb +162 -0
- data/lib/tabulatr/tabulatr/finder/find_for_mongoid_table.rb +116 -0
- data/lib/tabulatr/tabulatr/finder.rb +82 -0
- data/lib/tabulatr/tabulatr/formattr.rb +51 -0
- data/lib/tabulatr/tabulatr/header_cell.rb +94 -0
- data/lib/tabulatr/tabulatr/paginator.rb +88 -0
- data/lib/tabulatr/tabulatr/row_builder.rb +115 -0
- data/lib/tabulatr/tabulatr/settings.rb +221 -0
- data/lib/tabulatr/tabulatr.rb +238 -0
- data/lib/tabulatr.rb +34 -0
- data/tabulatr.gemspec +26 -0
- metadata +143 -0
@@ -0,0 +1,238 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2010-2011 Peter Horn, Provideal GmbH
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
# Tabulatr is a class to allow easy creation of data tables as you
|
25
|
+
# frequently find them on 'index' pages in rails. The 'table convention'
|
26
|
+
# here is that we consider every table to consist of three parts:
|
27
|
+
# * a header containing the names of the attribute of the column
|
28
|
+
# * a filter which is an input element to allow for searching the
|
29
|
+
# particular attribute
|
30
|
+
# * the rows with the actual data.
|
31
|
+
#
|
32
|
+
# Additionally, we expect that people want to 'select' rows and perform
|
33
|
+
# batch actions on these rows.
|
34
|
+
#
|
35
|
+
# Author:: Peter Horn, (mailto:peter.horn@provideal.net)
|
36
|
+
# Copyright:: Copyright (c) 2010-2011 by Provideal GmbH (http://www.provideal.net)
|
37
|
+
# License:: MIT Licence
|
38
|
+
class Tabulatr
|
39
|
+
|
40
|
+
include ActionView::Helpers::TagHelper
|
41
|
+
include ActionView::Helpers::FormTagHelper
|
42
|
+
include ActionView::Helpers::FormOptionsHelper
|
43
|
+
include ActionView::Helpers::TranslationHelper
|
44
|
+
|
45
|
+
# Constructor of Tabulatr
|
46
|
+
#
|
47
|
+
# Parameters:
|
48
|
+
# <tt>records</tt>:: the 'row' data of the table
|
49
|
+
# <tt>view</tt>:: the current instance of ActionView
|
50
|
+
# <tt>opts</tt>:: a hash of options specific for this table
|
51
|
+
def initialize(records, view=nil, toptions={})
|
52
|
+
@records = records
|
53
|
+
@view = view
|
54
|
+
@table_options = TABLE_OPTIONS.merge(toptions)
|
55
|
+
@table_form_options = TABLE_FORM_OPTIONS
|
56
|
+
@val = []
|
57
|
+
@record = nil
|
58
|
+
@row_mode = false
|
59
|
+
@classname = @records.send(FINDER_INJECT_OPTIONS[:classname])
|
60
|
+
@pagination = @records.send(FINDER_INJECT_OPTIONS[:pagination])
|
61
|
+
@filters = @records.send(FINDER_INJECT_OPTIONS[:filters])
|
62
|
+
@sorting = @records.send(FINDER_INJECT_OPTIONS[:sorting])
|
63
|
+
@checked = @records.send(FINDER_INJECT_OPTIONS[:checked])
|
64
|
+
@store_data = @records.send(FINDER_INJECT_OPTIONS[:store_data])
|
65
|
+
@should_translate = @table_options[:translate]
|
66
|
+
end
|
67
|
+
|
68
|
+
# the actual table definition method. It takes an Array of records, a hash of
|
69
|
+
# options and a block with the actual <tt>column</tt> calls.
|
70
|
+
#
|
71
|
+
# The following options are evaluated here:
|
72
|
+
# <tt>:table_html</tt>:: a hash with html-attributes added to the <table> created
|
73
|
+
# <tt>:header_html</tt>:: a hash with html-attributes added to the <tr> created
|
74
|
+
# for the header row
|
75
|
+
# <tt>:filter_html</tt>:: a hash with html-attributes added to the <tr> created
|
76
|
+
# for the filter row
|
77
|
+
# <tt>:row_html</tt>:: a hash with html-attributes added to the <tr>s created
|
78
|
+
# for the data rows
|
79
|
+
# <tt>:filter</tt>:: if set to false, no filter row is output
|
80
|
+
def build_table(&block)
|
81
|
+
@val = []
|
82
|
+
make_tag(@table_options[:make_form] ? :form : nil,
|
83
|
+
:method => :get,
|
84
|
+
:class => @table_options[:form_class],
|
85
|
+
'data-remote' => (@table_options[:remote] ? "true" : nil)) do
|
86
|
+
# TODO: make_tag(:input, :type => 'submit', :style => 'display:inline; width:1px; height:1px', :value => '__submit')
|
87
|
+
make_tag(:div, :class => @table_options[:control_div_class_before]) do
|
88
|
+
@table_options[:before_table_controls].each do |element|
|
89
|
+
render_element(element)
|
90
|
+
end
|
91
|
+
end if @table_options[:before_table_controls].present? # </div>
|
92
|
+
|
93
|
+
@store_data.each do |k,v|
|
94
|
+
make_tag(:input, :type => :hidden, :name => k, :value => h(v))
|
95
|
+
end
|
96
|
+
|
97
|
+
render_element(:table, &block)
|
98
|
+
|
99
|
+
make_tag(:div, :class => @table_options[:control_div_class_after]) do
|
100
|
+
@table_options[:after_table_controls].each do |element|
|
101
|
+
render_element(element)
|
102
|
+
end
|
103
|
+
end if @table_options[:after_table_controls].present? # </div>
|
104
|
+
|
105
|
+
end # </form>
|
106
|
+
@val.join("").html_safe
|
107
|
+
end
|
108
|
+
|
109
|
+
def render_element(element, &block)
|
110
|
+
case element
|
111
|
+
when :paginator then render_paginator if @table_options[:paginate]
|
112
|
+
when :hidden_submit then
|
113
|
+
when :submit then make_tag(:input, :type => 'submit',
|
114
|
+
:class => @table_options[:submit_class],
|
115
|
+
:value => t(@table_options[:submit_label]))
|
116
|
+
when :batch_actions then render_batch_actions if @table_options[:batch_actions]
|
117
|
+
when :select_controls then render_select_controls if @table_options[:selectable]
|
118
|
+
when :info_text
|
119
|
+
make_tag(:div, :class => @table_options[:info_text_class]) do
|
120
|
+
concat(format(t(@table_options[:info_text]),
|
121
|
+
@records.count, @pagination[:total], @checked[:selected].length, @pagination[:count]))
|
122
|
+
end if @table_options[:info_text]
|
123
|
+
when :table then render_table &block
|
124
|
+
else
|
125
|
+
if element.is_a?(String)
|
126
|
+
concat(element)
|
127
|
+
else
|
128
|
+
raise "unknown element '#{element}'"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def render_table(&block)
|
134
|
+
to = @table_options[:table_html]
|
135
|
+
to = (to || {}).merge(:class => @table_options[:table_class]) if @table_options[:table_class]
|
136
|
+
make_tag(:table, to) do
|
137
|
+
make_tag(:thead) do
|
138
|
+
render_table_header(&block)
|
139
|
+
render_table_filters(&block) if @table_options[:filter]
|
140
|
+
end # </thead>
|
141
|
+
make_tag(:tbody) do
|
142
|
+
render_table_rows(&block)
|
143
|
+
end # </tbody>
|
144
|
+
end # </table>
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
# either append to the internal string buffer or use
|
149
|
+
# ActionView#concat to output if an instance is available.
|
150
|
+
def concat(s, html_escape=false)
|
151
|
+
#@view.concat(s) if (Rails.version.to_f < 3.0 && @view)
|
152
|
+
#puts "\##{Rails.version.to_f} '#{s}'"
|
153
|
+
if s.present? then @val << (html_escape ? h(s) : s) end
|
154
|
+
end
|
155
|
+
|
156
|
+
def h(s)
|
157
|
+
ERB::Util.h(s)
|
158
|
+
end
|
159
|
+
|
160
|
+
def t(s)
|
161
|
+
return '' unless s.present?
|
162
|
+
begin
|
163
|
+
if s.respond_to?(:should_localize?) and s.should_localize?
|
164
|
+
translate(s)
|
165
|
+
else
|
166
|
+
case @should_translate
|
167
|
+
when :translate then translate(s)
|
168
|
+
when true then translate(s)
|
169
|
+
when :localize then localize(s)
|
170
|
+
else
|
171
|
+
if !@should_translate
|
172
|
+
s
|
173
|
+
elsif @should_translate.respond_to?(:call)
|
174
|
+
@should_translate.call(s)
|
175
|
+
else
|
176
|
+
raise "Wrong value '#{@should_translate}' for table option ':translate', should be false, true, :translate, :localize or a proc."
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
rescue
|
181
|
+
raise "Translating '#{s}' failed!"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# render the header row
|
186
|
+
def render_table_header(&block)
|
187
|
+
make_tag(:tr, @table_options[:header_html]) do
|
188
|
+
yield(header_row_builder)
|
189
|
+
end # </tr>"
|
190
|
+
end
|
191
|
+
|
192
|
+
# render the filter row
|
193
|
+
def render_table_filters(&block)
|
194
|
+
make_tag(:tr, @table_options[:filter_html]) do
|
195
|
+
yield(filter_row_builder)
|
196
|
+
end # </tr>
|
197
|
+
end
|
198
|
+
|
199
|
+
# render the table rows
|
200
|
+
def render_table_rows(&block)
|
201
|
+
row_classes = @table_options[:row_classes] || []
|
202
|
+
row_html = @table_options[:row_html] || {}
|
203
|
+
row_class = row_html[:class] || ""
|
204
|
+
@records.each_with_index do |record, i|
|
205
|
+
#concat("<!-- Row #{i} -->")
|
206
|
+
if row_classes.present?
|
207
|
+
rc = row_class.present? ? row_class + " " : ''
|
208
|
+
rc += row_classes[i % row_classes.length]
|
209
|
+
else
|
210
|
+
rc = nil
|
211
|
+
end
|
212
|
+
make_tag(:tr, row_html.merge(:class => rc)) do
|
213
|
+
yield(data_row_builder(record))
|
214
|
+
end # </tr>
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# stringly produce a tag w/ some options
|
219
|
+
def make_tag(name, hash={}, &block)
|
220
|
+
attrs = hash ? tag_options(hash) : ''
|
221
|
+
if block_given?
|
222
|
+
if name
|
223
|
+
concat("<#{name}#{attrs}>")
|
224
|
+
yield
|
225
|
+
concat("</#{name}>")
|
226
|
+
else
|
227
|
+
yield
|
228
|
+
end
|
229
|
+
else
|
230
|
+
concat("<#{name}#{attrs} />")
|
231
|
+
end
|
232
|
+
nil
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
Dir[File.join(File.dirname(__FILE__), "tabulatr", "*.rb")].each do |file|
|
237
|
+
require file
|
238
|
+
end
|
data/lib/tabulatr.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2010-2011 Peter Horn, Provideal GmbH
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require 'tabulatr/tabulatr'
|
25
|
+
require 'whiny_hash'
|
26
|
+
require 'id_stuffer'
|
27
|
+
|
28
|
+
#--
|
29
|
+
# Mainly Monkey Patching...
|
30
|
+
#--
|
31
|
+
Dir[File.join(File.dirname(__FILE__), "initializers", "*.rb")].each do |file|
|
32
|
+
require file
|
33
|
+
end
|
34
|
+
|
data/tabulatr.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
#require File.expand_path('../lib/tabulatr/version', __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.add_runtime_dependency('rails', '~> 3.0.3')
|
7
|
+
s.authors = ["Peter Horn"]
|
8
|
+
s.summary = "Tabulatr is a DSL to easily create tables for e.g. admin backends."
|
9
|
+
s.email = ['team@metaminded.com']
|
10
|
+
#s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
11
|
+
#s.extra_rdoc_files = ['README.mkd']
|
12
|
+
s.description = "Tabulatr enables you to create fancy tables with filtering, pagination, batch action, bells, AND whistles. " +
|
13
|
+
"You do this mainly by specifying the names of the columns. See the README for details."
|
14
|
+
s.files = (`git ls-files`.split("\n").select { |f| !f.start_with? "spec/"})
|
15
|
+
s.homepage = 'http://github.com/provideal/tabulatr'
|
16
|
+
s.name = 'tabulatr'
|
17
|
+
s.platform = Gem::Platform::RUBY
|
18
|
+
s.rdoc_options = ['--charset=UTF-8']
|
19
|
+
s.require_paths = ['lib']
|
20
|
+
s.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if s.respond_to? :required_rubygems_version=
|
21
|
+
s.rubyforge_project = 'tabulatr'
|
22
|
+
s.test_files = [] # `git ls-files -- {test,spec,features}/*`.split("\n")
|
23
|
+
s.version = "0.0.1" # MmCms::VERSION
|
24
|
+
s.add_dependency('whiny_hash', '>= 0.0.2')
|
25
|
+
s.add_dependency('id_stuffer', '>= 0.0.1')
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tabulatr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Peter Horn
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-03-16 00:00:00 +01:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rails
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 1
|
30
|
+
segments:
|
31
|
+
- 3
|
32
|
+
- 0
|
33
|
+
- 3
|
34
|
+
version: 3.0.3
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: whiny_hash
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 27
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
- 0
|
49
|
+
- 2
|
50
|
+
version: 0.0.2
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: id_stuffer
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 29
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
- 0
|
65
|
+
- 1
|
66
|
+
version: 0.0.1
|
67
|
+
type: :runtime
|
68
|
+
version_requirements: *id003
|
69
|
+
description: Tabulatr enables you to create fancy tables with filtering, pagination, batch action, bells, AND whistles. You do this mainly by specifying the names of the columns. See the README for details.
|
70
|
+
email:
|
71
|
+
- team@metaminded.com
|
72
|
+
executables: []
|
73
|
+
|
74
|
+
extensions: []
|
75
|
+
|
76
|
+
extra_rdoc_files: []
|
77
|
+
|
78
|
+
files:
|
79
|
+
- .gitignore
|
80
|
+
- Gemfile
|
81
|
+
- Gemfile.lock
|
82
|
+
- LICENSE
|
83
|
+
- README.textile
|
84
|
+
- Rakefile
|
85
|
+
- assets/simple_table.png
|
86
|
+
- assets/tabulatr.css
|
87
|
+
- lib/initializers/action_view.rb
|
88
|
+
- lib/initializers/active_record.rb
|
89
|
+
- lib/initializers/mark_as_localizable.rb
|
90
|
+
- lib/initializers/mongoid.rb
|
91
|
+
- lib/tabulatr.rb
|
92
|
+
- lib/tabulatr/tabulatr.rb
|
93
|
+
- lib/tabulatr/tabulatr/batch_actions.rb
|
94
|
+
- lib/tabulatr/tabulatr/check_controls.rb
|
95
|
+
- lib/tabulatr/tabulatr/data_cell.rb
|
96
|
+
- lib/tabulatr/tabulatr/filter_cell.rb
|
97
|
+
- lib/tabulatr/tabulatr/finder.rb
|
98
|
+
- lib/tabulatr/tabulatr/finder/find_for_active_record_table.rb
|
99
|
+
- lib/tabulatr/tabulatr/finder/find_for_mongoid_table.rb
|
100
|
+
- lib/tabulatr/tabulatr/formattr.rb
|
101
|
+
- lib/tabulatr/tabulatr/header_cell.rb
|
102
|
+
- lib/tabulatr/tabulatr/paginator.rb
|
103
|
+
- lib/tabulatr/tabulatr/row_builder.rb
|
104
|
+
- lib/tabulatr/tabulatr/settings.rb
|
105
|
+
- tabulatr.gemspec
|
106
|
+
has_rdoc: true
|
107
|
+
homepage: http://github.com/provideal/tabulatr
|
108
|
+
licenses: []
|
109
|
+
|
110
|
+
post_install_message:
|
111
|
+
rdoc_options:
|
112
|
+
- --charset=UTF-8
|
113
|
+
require_paths:
|
114
|
+
- lib
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
hash: 3
|
121
|
+
segments:
|
122
|
+
- 0
|
123
|
+
version: "0"
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
hash: 23
|
130
|
+
segments:
|
131
|
+
- 1
|
132
|
+
- 3
|
133
|
+
- 6
|
134
|
+
version: 1.3.6
|
135
|
+
requirements: []
|
136
|
+
|
137
|
+
rubyforge_project: tabulatr
|
138
|
+
rubygems_version: 1.3.7
|
139
|
+
signing_key:
|
140
|
+
specification_version: 3
|
141
|
+
summary: Tabulatr is a DSL to easily create tables for e.g. admin backends.
|
142
|
+
test_files: []
|
143
|
+
|