tabulatr2 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/Changelog.textile +124 -0
- data/Gemfile +21 -0
- data/LICENSE +23 -0
- data/README.md +242 -0
- data/Rakefile +11 -0
- data/app/assets/images/tabulatr/buttons_lite_background.png +0 -0
- data/app/assets/images/tabulatr/pager_arrow_left.gif +0 -0
- data/app/assets/images/tabulatr/pager_arrow_left_off.gif +0 -0
- data/app/assets/images/tabulatr/pager_arrow_right.gif +0 -0
- data/app/assets/images/tabulatr/pager_arrow_right_off.gif +0 -0
- data/app/assets/images/tabulatr/sort_arrow_down.gif +0 -0
- data/app/assets/images/tabulatr/sort_arrow_down_off.gif +0 -0
- data/app/assets/images/tabulatr/sort_arrow_up.gif +0 -0
- data/app/assets/images/tabulatr/sort_arrow_up_off.gif +0 -0
- data/app/assets/javascripts/tabulatr/application.js +452 -0
- data/app/assets/javascripts/tabulatr/jquery.inview.min.js +3 -0
- data/app/assets/javascripts/tabulatr.js +1 -0
- data/app/assets/stylesheets/tabulatr/application.css +40 -0
- data/app/assets/stylesheets/tabulatr.css +4 -0
- data/init.rb +1 -0
- data/lib/generators/tabulatr/install_generator.rb +44 -0
- data/lib/generators/tabulatr/templates/tabulatr.rb +5 -0
- data/lib/generators/tabulatr/templates/tabulatr.yml +14 -0
- data/lib/initializers/action_controller.rb +13 -0
- data/lib/initializers/action_view.rb +31 -0
- data/lib/initializers/active_record.rb +48 -0
- data/lib/initializers/mark_as_localizable.rb +43 -0
- data/lib/tabulatr/engine.rb +3 -0
- data/lib/tabulatr/tabulatr/adapter/active_record.rb +84 -0
- data/lib/tabulatr/tabulatr/adapter.rb +55 -0
- data/lib/tabulatr/tabulatr/batch_actions.rb +51 -0
- data/lib/tabulatr/tabulatr/data_cell.rb +132 -0
- data/lib/tabulatr/tabulatr/dummy_record.rb +40 -0
- data/lib/tabulatr/tabulatr/empty_cell.rb +44 -0
- data/lib/tabulatr/tabulatr/filter_cell.rb +145 -0
- data/lib/tabulatr/tabulatr/filter_icon.rb +6 -0
- data/lib/tabulatr/tabulatr/finder/find_for_table.rb +187 -0
- data/lib/tabulatr/tabulatr/finder.rb +64 -0
- data/lib/tabulatr/tabulatr/formattr.rb +55 -0
- data/lib/tabulatr/tabulatr/header_cell.rb +146 -0
- data/lib/tabulatr/tabulatr/json_builder.rb +57 -0
- data/lib/tabulatr/tabulatr/paginator.rb +76 -0
- data/lib/tabulatr/tabulatr/row_builder.rb +128 -0
- data/lib/tabulatr/tabulatr/security.rb +21 -0
- data/lib/tabulatr/tabulatr/settings.rb +158 -0
- data/lib/tabulatr/tabulatr.rb +343 -0
- data/lib/tabulatr/version.rb +3 -0
- data/lib/tabulatr.rb +34 -0
- data/spec/dummy/.gitignore +18 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +16 -0
- data/spec/dummy/app/assets/stylesheets/application.css.scss +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/controllers/products_controller.rb +24 -0
- data/spec/dummy/app/controllers/tags_controller.rb +5 -0
- data/spec/dummy/app/controllers/vendors_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +9 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/models/product.rb +5 -0
- data/spec/dummy/app/models/tag.rb +3 -0
- data/spec/dummy/app/models/vendor.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/products/count_tags.html.erb +9 -0
- data/spec/dummy/app/views/products/one_item_per_page.html.erb +9 -0
- data/spec/dummy/app/views/products/simple_index.html.erb +9 -0
- data/spec/dummy/app/views/products/stupid_array.html.erb +20 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config/application.rb +23 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +29 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +36 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +12 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/tabulatr.rb +5 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/locales/tabulatr.yml +14 -0
- data/spec/dummy/config/routes.rb +13 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20130730132101_create_vendors.rb +12 -0
- data/spec/dummy/db/migrate/20130730132321_create_products.rb +12 -0
- data/spec/dummy/db/migrate/20130730132348_create_tags.rb +14 -0
- data/spec/dummy/db/schema.rb +47 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +58 -0
- data/spec/dummy/public/422.html +58 -0
- data/spec/dummy/public/500.html +57 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/features/tabulatrs_spec.rb +227 -0
- data/spec/lib/tabulatr/tabulatr/finder/find_for_table_spec.rb +187 -0
- data/spec/spec_helper.rb +45 -0
- data/tabulatr.gemspec +29 -0
- metadata +258 -0
@@ -0,0 +1,128 @@
|
|
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
|
+
# These are extensions for use as a row builder
|
25
|
+
# In a seperate class call only for clearity
|
26
|
+
|
27
|
+
class Tabulatr
|
28
|
+
|
29
|
+
# called inside the build_table block, branches into data, header,
|
30
|
+
# or filter building methods depending on the current mode
|
31
|
+
def column(name, opts={}, &block)
|
32
|
+
#puts "column: '#{name}'"
|
33
|
+
case @row_mode
|
34
|
+
when :data then data_column(name, opts, &block)
|
35
|
+
when :header then
|
36
|
+
@attributes << name.to_s
|
37
|
+
header_column(name, opts, &block)
|
38
|
+
when :filter then filter_column(name, opts, &block)
|
39
|
+
when :empty then empty_column(name, opts, &block)
|
40
|
+
else raise "Wrong row mode '#{@row_mode}'"
|
41
|
+
end # case
|
42
|
+
end
|
43
|
+
|
44
|
+
# called inside the build_table block, branches into data, header,
|
45
|
+
# or filter building methods depending on the current mode
|
46
|
+
def association(relation, name, opts={}, &block)
|
47
|
+
#puts "assoc: '#{relation}.#{name}'"
|
48
|
+
case @row_mode
|
49
|
+
when :data then data_association(relation, name, opts, &block)
|
50
|
+
when :header then
|
51
|
+
@attributes << "#{relation}:#{name}"
|
52
|
+
header_association(relation, name, opts, &block)
|
53
|
+
when :filter then filter_association(relation, name, opts, &block)
|
54
|
+
when :empty then empty_column(name, opts, &block)
|
55
|
+
else raise "Wrong row mode '#{@row_mode}'"
|
56
|
+
end # case
|
57
|
+
end
|
58
|
+
|
59
|
+
# called inside the build_table block, branches into data, header,
|
60
|
+
# or filter building methods depending on the current mode
|
61
|
+
def checkbox(opts={}, &block)
|
62
|
+
#puts "column: '#{name}'"
|
63
|
+
case @row_mode
|
64
|
+
when :data then data_checkbox(opts, &block)
|
65
|
+
when :header then header_checkbox(opts, &block)
|
66
|
+
when :filter then filter_checkbox(opts, &block)
|
67
|
+
when :empty then nil
|
68
|
+
else raise "Wrong row mode '#{@row_mode}'"
|
69
|
+
end # case
|
70
|
+
end
|
71
|
+
|
72
|
+
def action(opts={}, &block)
|
73
|
+
#puts "column: '#{name}'"
|
74
|
+
case @row_mode
|
75
|
+
when :data then data_action(opts, &block)
|
76
|
+
when :header then header_action(opts, &block)
|
77
|
+
when :filter then filter_action(opts, &block)
|
78
|
+
when :empty then nil
|
79
|
+
else raise "Wrong row mode '#{@row_mode}'"
|
80
|
+
end # case
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# returns self, sets record to nil and row_mode as required for a
|
86
|
+
# header row
|
87
|
+
def header_row_builder
|
88
|
+
@record = nil
|
89
|
+
@row_mode = :header
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
def filter_form_builder
|
94
|
+
@record = nil
|
95
|
+
@row_mode = :filter
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
def empty_row_builder
|
100
|
+
@record = nil
|
101
|
+
@row_mode = :empty
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
def data_row_builder(record)
|
106
|
+
@record = record
|
107
|
+
@row_mode = :data
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
# some preprocessing of the options
|
113
|
+
def normalize_column_options(name, opts)
|
114
|
+
preset = column_preset_for(opts[:preset] || name)
|
115
|
+
opts = Tabulatr::COLUMN_OPTIONS.merge(preset).merge(opts)
|
116
|
+
{:width => 'width', :align => 'text-align', :valign => 'vertical-align'}.each do |key,css|
|
117
|
+
if opts[key]
|
118
|
+
[:th_html, :filter_html].each do |set|
|
119
|
+
opts[set] ||= {}
|
120
|
+
opts[set][:style] = (opts[set][:style] ? opts[set][:style] << "; " : "") << "#{css}: #{opts[key]}"
|
121
|
+
end # each
|
122
|
+
end # if
|
123
|
+
end # each
|
124
|
+
# more to come!
|
125
|
+
opts
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
class Tabulatr
|
4
|
+
class Security
|
5
|
+
def self.sign(arglist, salt=nil)
|
6
|
+
salt ||= SecureRandom.base64
|
7
|
+
str = "#{Tabulatr.secret_tokens.first}-#{salt}-#{arglist}-#{Rails.application.config.secret_token}-#{Tabulatr.secret_tokens.last}"
|
8
|
+
hash = Digest::SHA1.hexdigest(str)
|
9
|
+
"#{arglist}-#{salt}-#{hash[5..40]}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.validate(str)
|
13
|
+
arglist, salt, hash = str.split('-')
|
14
|
+
str == sign(arglist, salt)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.validate!(str)
|
18
|
+
validate(str) or raise "SECURITY!"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,158 @@
|
|
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 'whiny_hash'
|
25
|
+
|
26
|
+
class Tabulatr
|
27
|
+
|
28
|
+
# Hash keeping the defaults for the table options, may be overriden in the
|
29
|
+
# table_for call
|
30
|
+
TABLE_OPTIONS = WhinyHash.new({ # WhinyHash.new({
|
31
|
+
:table_class => 'tabulatr_table', # class for the actual data table
|
32
|
+
:control_div_class_before => 'table-controls', # class of the upper div containing the paging and batch action controls
|
33
|
+
:control_div_class_after => 'table-controls', # class of the lower div containing the paging and batch action controls
|
34
|
+
:paginator_div_class => 'pagination', # class of the div containing the paging controls
|
35
|
+
|
36
|
+
# which controls to be rendered above and below the tabel and in which order
|
37
|
+
:before_table_controls => [:filter, :paginator],
|
38
|
+
:after_table_controls => [],
|
39
|
+
|
40
|
+
:table_html => false, # a hash with html attributes for the table
|
41
|
+
:row_html => false, # a hash with html attributes for the normal trs
|
42
|
+
:header_html => false, # a hash with html attributes for the header trs
|
43
|
+
:filter_html => false, # a hash with html attributes for the filter trs
|
44
|
+
:filter => true, # false for no filter row at all
|
45
|
+
:paginate => false, # true to show paginator
|
46
|
+
:default_pagesize => 10, # default pagesize
|
47
|
+
:sortable => true, # true to allow sorting (can be specified for every sortable column)
|
48
|
+
:batch_actions => false, # :name => value hash of batch action stuff
|
49
|
+
:footer_content => false, # if given, add a <%= content_for <footer_content> %> before the </table>
|
50
|
+
:path => '#' # where to send the AJAX-requests to
|
51
|
+
})
|
52
|
+
|
53
|
+
# these settings are considered constant for the whole application, can not be overridden
|
54
|
+
# on a per-table basis.
|
55
|
+
# That's necessary to allow find_for_table to work properly
|
56
|
+
TABLE_FORM_OPTIONS = WhinyHash.new({
|
57
|
+
:filter_postfix => '_filter', # postfix for name of the filter in the params :hash => xxx_filter
|
58
|
+
:sort_postfix => '_sort', # postfix for name of the filter in the params :hash => xxx_filter
|
59
|
+
:associations_filter => '__association', # name of the associations in the filter hash
|
60
|
+
:batch_postfix => '_batch', # postfix for name of the batch action select
|
61
|
+
:checked_separator => ',' # symbol to separate the checked ids
|
62
|
+
})
|
63
|
+
|
64
|
+
# these settings are considered constant for the whole application, can not be overridden
|
65
|
+
# on a per-table basis.
|
66
|
+
# That's necessary to allow find_for_table to work properly
|
67
|
+
PAGINATE_OPTIONS = ActiveSupport::HashWithIndifferentAccess.new({
|
68
|
+
:page => 1,
|
69
|
+
:pagesize => 10,
|
70
|
+
:pagesizes => [10, 20, 50]
|
71
|
+
})
|
72
|
+
|
73
|
+
# Hash keeping the defaults for the column options
|
74
|
+
COLUMN_OPTIONS = ActiveSupport::HashWithIndifferentAccess.new({
|
75
|
+
:header => false, # a string to write into the header cell
|
76
|
+
:width => false, # the width of the cell
|
77
|
+
:align => false, # horizontal alignment
|
78
|
+
:valign => false, # vertical alignment
|
79
|
+
:wrap => true, # wraps
|
80
|
+
:type => :string, # :integer, :date
|
81
|
+
:th_html => false, # a hash with html attributes for the header cell
|
82
|
+
:filter_html => false, # a hash with html attributes for the filter cell
|
83
|
+
:filter => true, # false for no filter field,
|
84
|
+
# container for options_for_select
|
85
|
+
# String from options_from_collection_for_select or the like
|
86
|
+
# :range for range spec
|
87
|
+
# :checkbox for a 0/1 valued checkbox
|
88
|
+
:checkbox_value => '1', # value if checkbox is checked
|
89
|
+
:checkbox_label => '', # text behind the checkbox
|
90
|
+
:filter_width => '97%', # width of the filter <input>
|
91
|
+
:range_filter_symbol => '–', # put between the <inputs> of the range filter
|
92
|
+
:sortable => true, # if set, sorting-stuff is added to the header cell
|
93
|
+
:format_methods => [] # javascript methods to execute on this column
|
94
|
+
})
|
95
|
+
|
96
|
+
# defaults for the find_for_table
|
97
|
+
FINDER_OPTIONS = WhinyHash.new({
|
98
|
+
:default_order => false,
|
99
|
+
:precondition => false,
|
100
|
+
:store_data => false,
|
101
|
+
:name_mapping => nil,
|
102
|
+
:action => nil
|
103
|
+
})
|
104
|
+
|
105
|
+
# Stupid hack
|
106
|
+
SQL_OPTIONS = WhinyHash.new({
|
107
|
+
:like => nil
|
108
|
+
})
|
109
|
+
|
110
|
+
def self.finder_options(n=nil)
|
111
|
+
FINDER_OPTIONS.merge!(n) if n
|
112
|
+
FINDER_OPTIONS
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.column_options(n=nil)
|
116
|
+
COLUMN_OPTIONS.merge!(n) if n
|
117
|
+
COLUMN_OPTIONS
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.table_options(n=nil)
|
121
|
+
TABLE_OPTIONS.merge!(n) if n
|
122
|
+
TABLE_OPTIONS
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.paginate_options(n=nil)
|
126
|
+
PAGINATE_OPTIONS.merge!(n) if n
|
127
|
+
PAGINATE_OPTIONS
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.table_form_options(n=nil)
|
131
|
+
TABLE_FORM_OPTIONS.merge!(n) if n
|
132
|
+
TABLE_FORM_OPTIONS
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.table_design_options(n=nil)
|
136
|
+
raise("table_design_options stopped existing. Use table_options instead.")
|
137
|
+
end
|
138
|
+
def table_design_options(n=nil) self.class.table_design_options(n) end
|
139
|
+
|
140
|
+
def self.sql_options(n=nil)
|
141
|
+
SQL_OPTIONS.merge!(n) if n
|
142
|
+
SQL_OPTIONS
|
143
|
+
end
|
144
|
+
def sql_options(n=nil) self.class.sql_options(n) end
|
145
|
+
|
146
|
+
COLUMN_PRESETS = {}
|
147
|
+
def self.column_presets(n=nil)
|
148
|
+
COLUMN_PRESETS.merge!(n) if n
|
149
|
+
COLUMN_PRESETS
|
150
|
+
end
|
151
|
+
def column_presets(n=nil) self.class.column_presets(n) end
|
152
|
+
def column_preset_for(name)
|
153
|
+
h = COLUMN_PRESETS[name.to_sym]
|
154
|
+
return {} unless h
|
155
|
+
return h if h.is_a? Hash
|
156
|
+
COLUMN_PRESETS[h] || {}
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,343 @@
|
|
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
|
+
include ActionView::Helpers::RecordTagHelper
|
45
|
+
# include ActionView::Helpers::AssetTagHelper
|
46
|
+
# include Rails::Application::Configurable
|
47
|
+
|
48
|
+
# Constructor of Tabulatr
|
49
|
+
#
|
50
|
+
# Parameters:
|
51
|
+
# <tt>klass</tt>:: the klass of the data for the table
|
52
|
+
# <tt>view</tt>:: the current instance of ActionView
|
53
|
+
# <tt>opts</tt>:: a hash of options specific for this table
|
54
|
+
def initialize(klass_or_record, view=nil, toptions={})
|
55
|
+
if klass_or_record.is_a?(Class) && klass_or_record < ActiveRecord::Base
|
56
|
+
@klass = klass_or_record
|
57
|
+
@records = nil
|
58
|
+
elsif klass_or_record.respond_to?(:each)
|
59
|
+
@records = klass_or_record
|
60
|
+
@klass = @records.first.try(:class)
|
61
|
+
toptions = toptions.merge! \
|
62
|
+
:filter => false,
|
63
|
+
:paginate => false,
|
64
|
+
:sortable => false
|
65
|
+
else
|
66
|
+
raise "Give a model-class or an collection to `table_for'"
|
67
|
+
end
|
68
|
+
@view = view
|
69
|
+
@table_options = TABLE_OPTIONS.merge(toptions)
|
70
|
+
@table_form_options = TABLE_FORM_OPTIONS
|
71
|
+
@val = []
|
72
|
+
@record = nil
|
73
|
+
@row_mode = false
|
74
|
+
@classname = @klass.to_s.downcase.gsub("/","_")
|
75
|
+
@attributes = []
|
76
|
+
end
|
77
|
+
|
78
|
+
cattr_accessor :bootstrap_paginator, instance_accessor: false do
|
79
|
+
'create_ul_paginator'
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.config &block
|
83
|
+
yield self
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.secret_tokens=(secret_tokens)
|
87
|
+
@@secret_tokens = secret_tokens
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.secret_tokens
|
91
|
+
@@secret_tokens ||= []
|
92
|
+
end
|
93
|
+
|
94
|
+
# the actual table definition method. It takes an Array of records, a hash of
|
95
|
+
# options and a block with the actual <tt>column</tt> calls.
|
96
|
+
#
|
97
|
+
# The following options are evaluated here:
|
98
|
+
# <tt>:table_html</tt>:: a hash with html-attributes added to the <table> created
|
99
|
+
# <tt>:header_html</tt>:: a hash with html-attributes added to the <tr> created
|
100
|
+
# for the header row
|
101
|
+
# <tt>:filter_html</tt>:: a hash with html-attributes added to the <tr> created
|
102
|
+
# for the filter row
|
103
|
+
# <tt>:row_html</tt>:: a hash with html-attributes added to the <tr>s created
|
104
|
+
# for the data rows
|
105
|
+
# <tt>:filter</tt>:: if set to false, no filter row is output
|
106
|
+
def build_table(&block)
|
107
|
+
return nil if @records && @records.blank?
|
108
|
+
@val = []
|
109
|
+
# TODO: make_tag(:input, :type => 'submit', :style => 'display:inline; width:1px; height:1px', :value => '__submit')
|
110
|
+
unless @records
|
111
|
+
render_table_controls(:control_div_class_before, :before_table_controls)
|
112
|
+
end
|
113
|
+
|
114
|
+
render_element(:table, &block)
|
115
|
+
|
116
|
+
unless @records
|
117
|
+
render_table_controls(:control_div_class_after, :after_table_controls)
|
118
|
+
make_tag(:div, class: "tabulatr_count",
|
119
|
+
:'data-table' => "#{@klass.to_s.downcase}_table",
|
120
|
+
:'data-format-string' => I18n.t('tabulatr.count')){}
|
121
|
+
|
122
|
+
render_filter_dialog &block
|
123
|
+
sec_hash = Tabulatr::Security.sign(@attributes.join(','))
|
124
|
+
make_tag(:span, :id => "tabulatr_security_#{@klass.to_s.downcase}",
|
125
|
+
:'data-salt' => sec_hash.split('-')[1],
|
126
|
+
:'data-hash' => sec_hash.split('-')[2]){}
|
127
|
+
end
|
128
|
+
@val.join("").html_safe
|
129
|
+
end
|
130
|
+
|
131
|
+
def render_element(element, &block)
|
132
|
+
case element
|
133
|
+
when :filter then render_filter_icon
|
134
|
+
when :paginator then render_paginator
|
135
|
+
when :table then render_table &block
|
136
|
+
else
|
137
|
+
if element.is_a?(String)
|
138
|
+
concat(element)
|
139
|
+
else
|
140
|
+
raise "unknown element '#{element}'"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
def render_table(&block)
|
147
|
+
to = @table_options[:table_html]
|
148
|
+
to = (to || {}).merge(:class => "#{@table_options[:table_class]} table",
|
149
|
+
:'data-path' => @table_options[:path], :id => "#{@klass.to_s.downcase}_table")
|
150
|
+
make_tag(:table, to) do
|
151
|
+
make_tag(:thead) do
|
152
|
+
render_table_header(&block)
|
153
|
+
end # </thead>
|
154
|
+
if @records
|
155
|
+
make_tag(:tbody) do
|
156
|
+
render_table_rows(&block)
|
157
|
+
end # </tbody>
|
158
|
+
else
|
159
|
+
make_tag(:tbody) do
|
160
|
+
render_empty_start_row(&block)
|
161
|
+
end # </tbody>
|
162
|
+
end
|
163
|
+
content_for(@table_options[:footer_content]) if @table_options[:footer_content]
|
164
|
+
end # </table>
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def readable_name_for(name, relation=nil)
|
170
|
+
if relation
|
171
|
+
"#{@klass.human_attribute_name(relation).titlecase}
|
172
|
+
#{@klass.reflect_on_association(relation).klass.
|
173
|
+
human_attribute_name(name).titlecase}"
|
174
|
+
else
|
175
|
+
@klass.human_attribute_name(name).titlecase
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# either append to the internal string buffer or use
|
180
|
+
# ActionView#concat to output if an instance is available.
|
181
|
+
def concat(s, html_escape=false)
|
182
|
+
#@view.concat(s) if (Rails.version.to_f < 3.0 && @view)
|
183
|
+
#puts "\##{Rails.version.to_f} '#{s}'"
|
184
|
+
if s.present? then @val << (html_escape ? h(s) : s) end
|
185
|
+
end
|
186
|
+
|
187
|
+
def h(s)
|
188
|
+
ERB::Util.h(s)
|
189
|
+
end
|
190
|
+
|
191
|
+
def t(s)
|
192
|
+
return '' unless s.present?
|
193
|
+
begin
|
194
|
+
if s.respond_to?(:should_localize?) and s.should_localize?
|
195
|
+
translate(s)
|
196
|
+
else
|
197
|
+
case @should_translate
|
198
|
+
when :translate then translate(s)
|
199
|
+
when true then translate(s)
|
200
|
+
when :localize then localize(s)
|
201
|
+
else
|
202
|
+
if !@should_translate
|
203
|
+
s
|
204
|
+
elsif @should_translate.respond_to?(:call)
|
205
|
+
@should_translate.call(s)
|
206
|
+
else
|
207
|
+
raise "Wrong value '#{@should_translate}' for table option ':translate', should be false, true, :translate, :localize or a proc."
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
rescue
|
212
|
+
raise "Translating '#{s}' failed!"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# render the header row
|
217
|
+
def render_table_header(&block)
|
218
|
+
make_tag(:tr, @table_options[:header_html]) do
|
219
|
+
yield(header_row_builder)
|
220
|
+
end # </tr>"
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
def render_filter_options(&block)
|
225
|
+
yield(filter_form_builder)
|
226
|
+
make_tag(:input, :type => 'hidden', :name => 'sort_by')
|
227
|
+
make_tag(:input, :type => 'hidden', :name => 'orientation')
|
228
|
+
end
|
229
|
+
|
230
|
+
def render_empty_start_row(&block)
|
231
|
+
row_html = @table_options[:row_html] || {}
|
232
|
+
row_html[:class] = 'empty_row'
|
233
|
+
make_tag(:tr, row_html) do
|
234
|
+
yield empty_row_builder
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def render_table_controls div_class, before_or_after
|
239
|
+
make_tag(:div, :class => @table_options[div_class]) do
|
240
|
+
@table_options[before_or_after].each do |element|
|
241
|
+
render_element(element)
|
242
|
+
end
|
243
|
+
end if @table_options[before_or_after].present? # </div>
|
244
|
+
end
|
245
|
+
|
246
|
+
def render_filter_dialog &block
|
247
|
+
make_tag(:div, class: 'modal fade', id: "tabulatr_filter_dialog_#{@klass.to_s.downcase}", style: "display:none ;") do
|
248
|
+
make_tag(:div, class: 'modal-dialog') do
|
249
|
+
make_tag(:div, class: 'modal-content') do
|
250
|
+
make_tag(:div, class: 'modal-header') do
|
251
|
+
make_tag(:button, class: :close, :'data-dismiss' => :modal,
|
252
|
+
:'aria-hidden' => true) do
|
253
|
+
concat "×"
|
254
|
+
end
|
255
|
+
make_tag(:h3, class: 'modal-title') do
|
256
|
+
concat I18n.t('tabulatr.filter')
|
257
|
+
end
|
258
|
+
end
|
259
|
+
make_tag(:form, :'data-table' => "#{@klass.to_s.downcase}_table",
|
260
|
+
class: 'form-horizontal tabulatr_filter_form', :'data-remote' => true) do
|
261
|
+
make_tag(:div, class: 'modal-body') do
|
262
|
+
render_filter_options &block
|
263
|
+
end
|
264
|
+
make_tag(:div, class: 'modal-footer') do
|
265
|
+
make_tag(:input, :type => 'submit',
|
266
|
+
:class => 'submit-table btn btn-primary',
|
267
|
+
:value => I18n.t('tabulatr.apply_filters'))
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end # modal-content
|
271
|
+
end # modal-dialog
|
272
|
+
end # modal fade
|
273
|
+
end
|
274
|
+
|
275
|
+
# render the table rows, only used if records are passed
|
276
|
+
def render_table_rows(&block)
|
277
|
+
# row_classes = @table_options[:row_classes] || []
|
278
|
+
# row_html = @table_options[:row_html] || {}
|
279
|
+
# row_class = row_html[:class] || ""
|
280
|
+
@records.each_with_index do |record, i|
|
281
|
+
#concat("<!-- Row #{i} -->")
|
282
|
+
# if row_classes.present?
|
283
|
+
# rc = row_class.present? ? row_class + " " : ''
|
284
|
+
# rc += row_classes[i % row_classes.length]
|
285
|
+
# else
|
286
|
+
# rc = nil
|
287
|
+
# end
|
288
|
+
rc = nil
|
289
|
+
make_tag(:tr, :class => rc, :id => dom_id(record)) do
|
290
|
+
yield(data_row_builder(record))
|
291
|
+
end # </tr>
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def render_table_row(&block)
|
296
|
+
row_html = @table_options[:row_html] || {}
|
297
|
+
row_html[:class] = 'empty_row'
|
298
|
+
make_tag(:tr, row_html) do
|
299
|
+
yield empty_row_builder
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
|
304
|
+
# stringly produce a tag w/ some options
|
305
|
+
def make_tag(name, hash={}, &block)
|
306
|
+
attrs = hash ? tag_options(hash) : ''
|
307
|
+
if block_given?
|
308
|
+
if name
|
309
|
+
concat("<#{name}#{attrs}>")
|
310
|
+
yield
|
311
|
+
concat("</#{name}>")
|
312
|
+
else
|
313
|
+
yield
|
314
|
+
end
|
315
|
+
else
|
316
|
+
concat("<#{name}#{attrs} />")
|
317
|
+
end
|
318
|
+
nil
|
319
|
+
end
|
320
|
+
|
321
|
+
def make_image_button(options)
|
322
|
+
inactive = options.delete(:inactive)
|
323
|
+
if(options['data-sort'] == 'desc')
|
324
|
+
icon_class = 'icon-arrow-down'
|
325
|
+
else
|
326
|
+
icon_class = 'icon-arrow-up'
|
327
|
+
end
|
328
|
+
if !inactive
|
329
|
+
make_tag(:i,
|
330
|
+
options.merge(
|
331
|
+
:class => "tabulatr-sort #{icon_class}"
|
332
|
+
)
|
333
|
+
)
|
334
|
+
else
|
335
|
+
make_tag(:i, :class => "tabulatr-sort #{icon_class}")
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
end
|
340
|
+
|
341
|
+
Dir[File.join(File.dirname(__FILE__), "tabulatr", "*.rb")].each do |file|
|
342
|
+
require file
|
343
|
+
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 'tabulatr/engine'
|
26
|
+
require 'whiny_hash'
|
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
|
+
|