datatable 0.1.0alpha2
Sign up to get free protection for your applications and to get access to all the features.
- data/.bundle/config +3 -0
- data/.gitignore +7 -0
- data/.rspec +1 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +83 -0
- data/MIT-LICENSE +20 -0
- data/README.md +135 -0
- data/Rakefile +27 -0
- data/TODO +71 -0
- data/datatable.gemspec +20 -0
- data/example_app/.DS_Store +0 -0
- data/example_app/.gitignore +6 -0
- data/example_app/.rspec +1 -0
- data/example_app/Gemfile +35 -0
- data/example_app/Gemfile.lock +126 -0
- data/example_app/Rakefile +7 -0
- data/example_app/app/controllers/application_controller.rb +3 -0
- data/example_app/app/controllers/orders_controller.rb +13 -0
- data/example_app/app/datatables/orders_index.rb +31 -0
- data/example_app/app/helpers/application_helper.rb +2 -0
- data/example_app/app/models/customer.rb +4 -0
- data/example_app/app/models/item.rb +4 -0
- data/example_app/app/models/order.rb +5 -0
- data/example_app/app/models/order_item.rb +4 -0
- data/example_app/app/models/sales_rep.rb +3 -0
- data/example_app/app/views/layouts/application.html.erb +12 -0
- data/example_app/app/views/orders/index.html.erb +14 -0
- data/example_app/config/application.rb +41 -0
- data/example_app/config/boot.rb +6 -0
- data/example_app/config/database.yml.mysql +20 -0
- data/example_app/config/database.yml.pg +20 -0
- data/example_app/config/environment.rb +5 -0
- data/example_app/config/environments/development.rb +28 -0
- data/example_app/config/environments/production.rb +49 -0
- data/example_app/config/environments/test.rb +35 -0
- data/example_app/config/initializers/backtrace_silencers.rb +7 -0
- data/example_app/config/initializers/datatable.rb +6 -0
- data/example_app/config/initializers/inflections.rb +10 -0
- data/example_app/config/initializers/mime_types.rb +5 -0
- data/example_app/config/initializers/secret_token.rb +9 -0
- data/example_app/config/initializers/session_store.rb +8 -0
- data/example_app/config/locales/en.yml +5 -0
- data/example_app/config/routes.rb +6 -0
- data/example_app/config.ru +4 -0
- data/example_app/db/migrate/20110429185712_create_customers.rb +15 -0
- data/example_app/db/migrate/20110429185742_create_sales_reps.rb +14 -0
- data/example_app/db/migrate/20110429185807_create_items.rb +15 -0
- data/example_app/db/migrate/20110429185913_create_orders.rb +15 -0
- data/example_app/db/migrate/20110429190005_create_order_items.rb +14 -0
- data/example_app/db/schema.rb +53 -0
- data/example_app/db/seeds.rb +49 -0
- data/example_app/lib/tasks/.gitkeep +0 -0
- data/example_app/lib/tasks/setup.rake +12 -0
- data/example_app/public/404.html +26 -0
- data/example_app/public/422.html +26 -0
- data/example_app/public/500.html +26 -0
- data/example_app/public/datatable/css/demo_page.css +99 -0
- data/example_app/public/datatable/css/demo_table.css +539 -0
- data/example_app/public/datatable/css/demo_table_jui.css +521 -0
- data/example_app/public/datatable/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/example_app/public/datatable/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/example_app/public/datatable/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/example_app/public/datatable/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/example_app/public/datatable/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/example_app/public/datatable/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/example_app/public/datatable/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/example_app/public/datatable/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/example_app/public/datatable/css/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/example_app/public/datatable/css/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/example_app/public/datatable/css/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/example_app/public/datatable/css/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/example_app/public/datatable/css/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/example_app/public/datatable/css/smoothness/jquery-ui-1.8.14.custom.css +568 -0
- data/example_app/public/datatable/images/back_disabled.jpg +0 -0
- data/example_app/public/datatable/images/back_enabled.jpg +0 -0
- data/example_app/public/datatable/images/favicon.ico +0 -0
- data/example_app/public/datatable/images/forward_disabled.jpg +0 -0
- data/example_app/public/datatable/images/forward_enabled.jpg +0 -0
- data/example_app/public/datatable/images/sort_asc.png +0 -0
- data/example_app/public/datatable/images/sort_asc_disabled.png +0 -0
- data/example_app/public/datatable/images/sort_both.png +0 -0
- data/example_app/public/datatable/images/sort_desc.png +0 -0
- data/example_app/public/datatable/images/sort_desc_disabled.png +0 -0
- data/example_app/public/datatable/js/jquery-ui-1.8.14.custom.min.js +789 -0
- data/example_app/public/datatable/js/jquery.dataTables.js +7347 -0
- data/example_app/public/datatable/js/jquery.dataTables.min.js +151 -0
- data/example_app/public/favicon.ico +0 -0
- data/example_app/public/flash/copy_cvs_xls.swf +0 -0
- data/example_app/public/flash/copy_cvs_xls_pdf.swf +0 -0
- data/example_app/public/images/rails.png +0 -0
- data/example_app/public/javascripts/application.js +2 -0
- data/example_app/public/javascripts/jquery.js +8936 -0
- data/example_app/public/javascripts/jquery.min.js +18 -0
- data/example_app/public/javascripts/jquery_ujs.js +316 -0
- data/example_app/public/robots.txt +5 -0
- data/example_app/public/stylesheets/.gitkeep +0 -0
- data/example_app/script/rails +6 -0
- data/example_app/spec/datatables/active_record_dsl_spec.rb +59 -0
- data/example_app/spec/datatables/active_record_link_to_spec.rb +22 -0
- data/example_app/spec/datatables/active_record_pagination_spec.rb +94 -0
- data/example_app/spec/datatables/active_record_table_operations_spec.rb +180 -0
- data/example_app/spec/datatables/config_spec.rb +10 -0
- data/example_app/spec/datatables/query_params_spec.rb +73 -0
- data/example_app/spec/datatables/sql_default_spec.rb +22 -0
- data/example_app/spec/datatables/sql_pagination_spec.rb +177 -0
- data/example_app/spec/datatables/sql_search_cast_spec.rb +6 -0
- data/example_app/spec/datatables/sql_search_global_spec.rb +107 -0
- data/example_app/spec/datatables/sql_search_individual_spec.rb +113 -0
- data/example_app/spec/datatables/sql_search_where_spec.rb +87 -0
- data/example_app/spec/datatables/sql_sorting_spec.rb +80 -0
- data/example_app/spec/datatables/sql_variables_spec.rb +104 -0
- data/example_app/spec/factories/customer_factory.rb +4 -0
- data/example_app/spec/factories/item_factory.rb +2 -0
- data/example_app/spec/factories/order_factory.rb +7 -0
- data/example_app/spec/factories/order_item_factory.rb +2 -0
- data/example_app/spec/factories/sales_rep_factory.rb +4 -0
- data/example_app/spec/helpers/aocolumn_spec.rb +239 -0
- data/example_app/spec/helpers/data_table_helper_spec.rb +148 -0
- data/example_app/spec/helpers/headings_spec.rb +71 -0
- data/example_app/spec/spec_helper.rb +29 -0
- data/generators.txt +6 -0
- data/images/datatable_screenshot.png +0 -0
- data/lib/datatable/active_record_dsl.rb +49 -0
- data/lib/datatable/errors.rb +5 -0
- data/lib/datatable/helper.rb +199 -0
- data/lib/datatable/railtie.rb +17 -0
- data/lib/datatable/version.rb +4 -0
- data/lib/datatable.rb +341 -0
- data/lib/generators/datatable/install_generator.rb +58 -0
- data/lib/generators/datatable/new_generator.rb +46 -0
- data/lib/generators/templates/datatable.rb +33 -0
- data/lib/generators/templates/datatable_initializer.rb +6 -0
- data/vendor/datatable/Readme.txt +11 -0
- data/vendor/datatable/extras/TableTools/media/css/TableTools.css +264 -0
- data/vendor/datatable/extras/TableTools/media/css/TableTools_JUI.css +182 -0
- data/vendor/datatable/extras/TableTools/media/images/background.png +0 -0
- data/vendor/datatable/extras/TableTools/media/images/collection.png +0 -0
- data/vendor/datatable/extras/TableTools/media/images/collection_hover.png +0 -0
- data/vendor/datatable/extras/TableTools/media/images/copy.png +0 -0
- data/vendor/datatable/extras/TableTools/media/images/copy_hover.png +0 -0
- data/vendor/datatable/extras/TableTools/media/images/csv.png +0 -0
- data/vendor/datatable/extras/TableTools/media/images/csv_hover.png +0 -0
- data/vendor/datatable/extras/TableTools/media/images/pdf.png +0 -0
- data/vendor/datatable/extras/TableTools/media/images/pdf_hover.png +0 -0
- data/vendor/datatable/extras/TableTools/media/images/print.png +0 -0
- data/vendor/datatable/extras/TableTools/media/images/print_hover.png +0 -0
- data/vendor/datatable/extras/TableTools/media/images/xls.png +0 -0
- data/vendor/datatable/extras/TableTools/media/images/xls_hover.png +0 -0
- data/vendor/datatable/extras/TableTools/media/js/TableTools.js +2410 -0
- data/vendor/datatable/extras/TableTools/media/js/TableTools.min.js +78 -0
- data/vendor/datatable/extras/TableTools/media/js/TableTools.min.js.gz +0 -0
- data/vendor/datatable/extras/TableTools/media/js/ZeroClipboard.js +365 -0
- data/vendor/datatable/extras/TableTools/media/swf/copy_cvs_xls.swf +0 -0
- data/vendor/datatable/extras/TableTools/media/swf/copy_cvs_xls_pdf.swf +0 -0
- data/vendor/datatable/license-bsd.txt +10 -0
- data/vendor/datatable/license-gpl2.txt +339 -0
- data/vendor/datatable/media/css/demo_page.css +99 -0
- data/vendor/datatable/media/css/demo_table.css +539 -0
- data/vendor/datatable/media/css/demo_table_jui.css +521 -0
- data/vendor/datatable/media/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/vendor/datatable/media/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/vendor/datatable/media/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/vendor/datatable/media/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/vendor/datatable/media/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/vendor/datatable/media/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/vendor/datatable/media/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/vendor/datatable/media/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/vendor/datatable/media/css/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/vendor/datatable/media/css/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/vendor/datatable/media/css/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/vendor/datatable/media/css/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/vendor/datatable/media/css/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/vendor/datatable/media/css/smoothness/jquery-ui-1.8.14.custom.css +568 -0
- data/vendor/datatable/media/images/back_disabled.jpg +0 -0
- data/vendor/datatable/media/images/back_enabled.jpg +0 -0
- data/vendor/datatable/media/images/favicon.ico +0 -0
- data/vendor/datatable/media/images/forward_disabled.jpg +0 -0
- data/vendor/datatable/media/images/forward_enabled.jpg +0 -0
- data/vendor/datatable/media/images/sort_asc.png +0 -0
- data/vendor/datatable/media/images/sort_asc_disabled.png +0 -0
- data/vendor/datatable/media/images/sort_both.png +0 -0
- data/vendor/datatable/media/images/sort_desc.png +0 -0
- data/vendor/datatable/media/images/sort_desc_disabled.png +0 -0
- data/vendor/datatable/media/js/jquery-ui-1.8.14.custom.min.js +789 -0
- data/vendor/datatable/media/js/jquery.dataTables.js +7347 -0
- data/vendor/datatable/media/js/jquery.dataTables.min.js +151 -0
- metadata +270 -0
@@ -0,0 +1,199 @@
|
|
1
|
+
module Datatable
|
2
|
+
module Helper
|
3
|
+
|
4
|
+
def datatable
|
5
|
+
"#{datatable_html} #{datatable_javascript}".html_safe
|
6
|
+
end
|
7
|
+
|
8
|
+
def datatable_html
|
9
|
+
if Datatable::Base.config.jquery_ui
|
10
|
+
datatable_base_html
|
11
|
+
else
|
12
|
+
datatable_html_with_wrapper
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def datatable_javascript
|
17
|
+
raise "No @datatable assign" unless @datatable
|
18
|
+
# TODO: this will escape ampersands
|
19
|
+
# ERB::Util.h('http://www.foo.com/ab/asdflkj?asdf=asdf&asdf=alsdf') => "http://www.foo.com/ab/asdflkj?asdf=asdf&asdf=alsdf"
|
20
|
+
"<script>
|
21
|
+
function replace(string, columns) {
|
22
|
+
var i = columns.length;
|
23
|
+
while(i--){
|
24
|
+
string = string.replace('{{' + i + '}}', columns[i]);
|
25
|
+
string = string.replace('%7B%7B' + i + '%7D%7D', columns[i]);
|
26
|
+
}
|
27
|
+
return string;
|
28
|
+
}
|
29
|
+
$(function(){
|
30
|
+
|
31
|
+
var oTable = $('#datatable').dataTable(#{javascript_options.to_json.gsub(/\"aocolumns_place_holder\"/, aocolumns_text)})
|
32
|
+
|
33
|
+
|
34
|
+
$('tfoot input').keyup( function () {
|
35
|
+
/* Filter on the column (the index) of this element */
|
36
|
+
oTable.fnFilter( this.value, $('tfoot input').index(this) );
|
37
|
+
} );
|
38
|
+
|
39
|
+
});
|
40
|
+
|
41
|
+
</script>".html_safe
|
42
|
+
end
|
43
|
+
|
44
|
+
def javascript_include_datatable
|
45
|
+
"".tap do |javascripts|
|
46
|
+
|
47
|
+
if Datatable::Base.config.jquery_ui
|
48
|
+
javascripts << (javascript_include_tag '/datatable/js/jquery-ui-1.8.14.custom.min.js')
|
49
|
+
end
|
50
|
+
|
51
|
+
javascripts << (javascript_include_tag '/datatable/js/jquery.dataTables.min.js')
|
52
|
+
|
53
|
+
if Datatable::Base.config.table_tools == true
|
54
|
+
javascripts << (javascript_include_tag '/datatable/js/TableTools.min.js')
|
55
|
+
end
|
56
|
+
|
57
|
+
end.html_safe
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def javascript_options
|
63
|
+
defaults = {
|
64
|
+
'oLanguage' => {
|
65
|
+
'sInfoFiltered' => '',
|
66
|
+
'sProcessing' => Datatable::Base.config.spinner || 'Loading'
|
67
|
+
|
68
|
+
},
|
69
|
+
'sAjaxSource' => h(request.path),
|
70
|
+
'sDom' => '<"H"lfr>t<"F"ip>',
|
71
|
+
'iDisplayLength' => 25,
|
72
|
+
'bProcessing' => true,
|
73
|
+
'bServerSide' => true,
|
74
|
+
'sPaginationType' => "full_numbers",
|
75
|
+
'aoColumns' => "aocolumns_place_holder"
|
76
|
+
|
77
|
+
}
|
78
|
+
|
79
|
+
if Datatable::Base.config.table_tools == true
|
80
|
+
defaults['oTableTools'] = {
|
81
|
+
'sSwfPath' => 'flash/copy_cvs_xls_pdf.swf'
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
defaults['bJQueryUI'] = Datatable::Base.config.jquery_ui ? true : false # Could use !! but less clear
|
86
|
+
|
87
|
+
|
88
|
+
defaults.merge(@datatable.javascript_options)
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
|
93
|
+
def datatable_html_with_wrapper
|
94
|
+
<<-CONTENT.gsub(/^\s{6}/,"").html_safe
|
95
|
+
<div id="dt_example" style="width: 100%;">
|
96
|
+
#{datatable_base_html}
|
97
|
+
</div>
|
98
|
+
CONTENT
|
99
|
+
end
|
100
|
+
|
101
|
+
def datatable_base_html
|
102
|
+
<<-CONTENT.gsub(/^\s{6}/,"").html_safe
|
103
|
+
<table id='datatable'>
|
104
|
+
<thead>
|
105
|
+
<tr>
|
106
|
+
#{headings}
|
107
|
+
</tr>
|
108
|
+
</thead>
|
109
|
+
<tbody>
|
110
|
+
</tbody>
|
111
|
+
<tfoot>
|
112
|
+
<tr>
|
113
|
+
#{individual_column_searching if @datatable.javascript_options['individual_column_searching']}
|
114
|
+
</tr>
|
115
|
+
</tfoot>
|
116
|
+
</table>
|
117
|
+
CONTENT
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
def headings
|
122
|
+
@datatable.columns.map do |key, value|
|
123
|
+
"<th>#{value[:heading] || humanize_column(key)}</th>"
|
124
|
+
end.join
|
125
|
+
end
|
126
|
+
|
127
|
+
def humanize_column(name)
|
128
|
+
columns = name.split('.')
|
129
|
+
[columns[0].singularize, columns[1]].compact.map(&:humanize).map(&:titleize).join(" ")
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
# returns a ruby hash of
|
134
|
+
def ruby_aocolumns
|
135
|
+
result = []
|
136
|
+
column_def_keys = %w[ asSorting bSearchable bSortable
|
137
|
+
bUseRendered bVisible fnRender iDataSort
|
138
|
+
mDataProp sClass sDefaultContent sName
|
139
|
+
sSortDataType sTitle sType sWidth link_to ]
|
140
|
+
index = 0
|
141
|
+
@datatable.columns.each_value do |column_hash|
|
142
|
+
column_result = {}
|
143
|
+
column_hash.each do |key,value|
|
144
|
+
if column_def_keys.include?(key.to_s)
|
145
|
+
column_result[key.to_s] = value
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# rewrite any link_to values as fnRender functions
|
150
|
+
if column_result.include?('link_to')
|
151
|
+
column_result['fnRender'] = %Q|function(oObj) { return replace('#{column_result['link_to']}', oObj.aData);}|
|
152
|
+
column_result.delete('link_to')
|
153
|
+
end
|
154
|
+
|
155
|
+
if column_result.empty?
|
156
|
+
result << nil
|
157
|
+
else
|
158
|
+
result << column_result
|
159
|
+
end
|
160
|
+
end
|
161
|
+
result
|
162
|
+
end
|
163
|
+
|
164
|
+
def aocolumns_text
|
165
|
+
outer = []
|
166
|
+
ruby_aocolumns.each do |column|
|
167
|
+
if column
|
168
|
+
inner = []
|
169
|
+
column.each do |key, value|
|
170
|
+
inner << case key
|
171
|
+
when 'fnRender'
|
172
|
+
"\"#{key.to_s}\": #{value.to_json[1..-2]}"
|
173
|
+
else
|
174
|
+
"\"#{key.to_s}\": #{value.to_json}"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
outer << "{" + inner.join(", ") + "}"
|
178
|
+
else
|
179
|
+
outer << "null"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
'[' + outer.join(', ') + ']'
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
def individual_column_searching
|
187
|
+
# TODO: placeholders only supported in HTML5
|
188
|
+
@datatable.columns.map do |key, value|
|
189
|
+
|
190
|
+
if @datatable.columns[key][:bSearchable] == false
|
191
|
+
%Q{ <th></th> }
|
192
|
+
else
|
193
|
+
%Q{ <th><input type="text" placeholder="#{ @datatable.columns[key][:sTitle] || key}" class="search_init" /></th> }
|
194
|
+
end
|
195
|
+
end.join
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'datatable/helper'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
module Datatable
|
5
|
+
class Railtie < ::Rails::Railtie
|
6
|
+
|
7
|
+
initializer 'datatable.load_later' do
|
8
|
+
datatable_path = Rails.application.config.eager_load_paths.grep(/app\/datatable/)
|
9
|
+
Rails.application.config.eager_load_paths -= datatable_path
|
10
|
+
end
|
11
|
+
|
12
|
+
initializer "datatable.helper" do
|
13
|
+
ActionView::Base.send :include, Datatable::Helper
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
data/lib/datatable.rb
ADDED
@@ -0,0 +1,341 @@
|
|
1
|
+
# 1. data table subclass is initialized
|
2
|
+
# class methods are going to be called on the class instance, and the class will store table data
|
3
|
+
#
|
4
|
+
# 2. instantiate an instance of the data table subclass ( OrdersIndex.new)
|
5
|
+
#
|
6
|
+
# 3. query the instance w/ pagination and sorting params
|
7
|
+
# a query gets executed w/ params -> ARel
|
8
|
+
# results get stored -> AR
|
9
|
+
# results get passed back as json
|
10
|
+
|
11
|
+
require 'datatable/railtie'
|
12
|
+
require 'datatable/errors'
|
13
|
+
require 'datatable/helper'
|
14
|
+
require 'datatable/active_record_dsl'
|
15
|
+
|
16
|
+
# during normal execution rails should have already pulled
|
17
|
+
# this in but we may have to do it ourselves in some tests
|
18
|
+
require 'action_view' unless defined?(ActionView)
|
19
|
+
require 'ostruct'
|
20
|
+
|
21
|
+
module Datatable
|
22
|
+
|
23
|
+
PARAM_MATCHERS = [
|
24
|
+
/\AiDisplayStart\z/,
|
25
|
+
/\AiDisplayLength\z/,
|
26
|
+
/\AiColumns\z/,
|
27
|
+
/\AsSearch\z/,
|
28
|
+
/\AbRegex\z/,
|
29
|
+
/\AbSearchable_\d+\z/,
|
30
|
+
/\AsSearch_\d+\z/,
|
31
|
+
/\AbRegex_\d+\z/,
|
32
|
+
/\AbSortable_\d+\z/,
|
33
|
+
/\AiSortingCols\z/,
|
34
|
+
/\AiSortCol_\d+\z/,
|
35
|
+
/\AsSortDir_\d+\z/,
|
36
|
+
/\AsEcho\z/
|
37
|
+
]
|
38
|
+
|
39
|
+
class Base
|
40
|
+
|
41
|
+
include Datatable::ActiveRecordDSL
|
42
|
+
extend ActionView::Helpers::UrlHelper
|
43
|
+
extend ActionView::Helpers::TagHelper
|
44
|
+
|
45
|
+
def self.config(&block)
|
46
|
+
@config ||= OpenStruct.new
|
47
|
+
if block_given?
|
48
|
+
yield @config
|
49
|
+
else
|
50
|
+
return @config
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.model
|
55
|
+
@model
|
56
|
+
end
|
57
|
+
|
58
|
+
attr_accessor :records
|
59
|
+
|
60
|
+
def self.sql(*args)
|
61
|
+
return @sql_string if args.empty?
|
62
|
+
|
63
|
+
@sql_string = args.first
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.count(*args)
|
67
|
+
if args.empty?
|
68
|
+
return @count_sql
|
69
|
+
else
|
70
|
+
@count_sql = args.first
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.where(*args)
|
75
|
+
if args.empty?
|
76
|
+
return @where_sql
|
77
|
+
else
|
78
|
+
@where_sql = args.first
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.sql_string
|
83
|
+
@sql_string
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.columns(*args)
|
87
|
+
if args.empty?
|
88
|
+
raise 'There are no columns on the Datatable (use assign__column_names)' unless @columns
|
89
|
+
return @columns
|
90
|
+
end
|
91
|
+
|
92
|
+
@columns = ActiveSupport::OrderedHash.new
|
93
|
+
args.each { |element| @columns[element.keys.first] = element.values.first }
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.option(key,value)
|
97
|
+
@javascript_options ||= {}
|
98
|
+
@javascript_options[key.to_s] = value
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.javascript_options
|
102
|
+
@javascript_options || {}
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.known_parameter(arg)
|
106
|
+
PARAM_MATCHERS.each do |matcher|
|
107
|
+
return true if arg =~ matcher
|
108
|
+
end
|
109
|
+
false
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.unknown_parameter(arg)
|
113
|
+
!(known_parameter(arg))
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.query(params, variables={})
|
117
|
+
# convert all of the keys to strings and filter out any non valid datatable parameters.
|
118
|
+
skparams = params.stringify_keys.delete_if{|k,v| unknown_parameter(k) }
|
119
|
+
|
120
|
+
# take advantage of the fact that Datatables uses hungarian notation
|
121
|
+
# and convert all of the parameters to their correct type here instead
|
122
|
+
# of worring about it all throughthe code base
|
123
|
+
skparams.each do |key, value|
|
124
|
+
skparams[key] = value.to_i if key =~ /^i/
|
125
|
+
skparams[key] = value.to_s if key =~ /^s/
|
126
|
+
skparams[key] = (value ? true : false) if key =~ /^b/
|
127
|
+
end
|
128
|
+
|
129
|
+
if @sql_string && !@already_substituted
|
130
|
+
substitute_variables(variables)
|
131
|
+
end
|
132
|
+
@already_substituted = true
|
133
|
+
|
134
|
+
datatable = new(skparams)
|
135
|
+
datatable.count
|
136
|
+
datatable.instance_query
|
137
|
+
datatable
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.substitute_variables(substitutions)
|
141
|
+
substitutions.stringify_keys.each do |key, value|
|
142
|
+
unless "#{@where_sql}#{@count_sql}#{@sql_string}" =~ /#{key}/m
|
143
|
+
fail "Substitution key: '#{key}' not in found in SQL text"
|
144
|
+
end
|
145
|
+
new_text = value.kind_of?(Array) ? "(#{value.join(', ')})" : value.to_s
|
146
|
+
@where_sql.try(:gsub!,"{{#{key}}}", new_text )
|
147
|
+
@sql_string.try(:gsub!, "{{#{key}}}", new_text)
|
148
|
+
@count_sql.try(:gsub!,"{{#{key}}}", new_text)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# only used in testing
|
153
|
+
def self.to_sql
|
154
|
+
relation.to_sql
|
155
|
+
end
|
156
|
+
|
157
|
+
# We want to allow people to access routes in data tables.
|
158
|
+
#
|
159
|
+
# Including the rails routes doesn't work b/c for some reason
|
160
|
+
# the route methods are not availble as class methods
|
161
|
+
# no matter if we use include or send.
|
162
|
+
#
|
163
|
+
# This works for now.
|
164
|
+
def self.method_missing(symbol, *args, &block)
|
165
|
+
if symbol.to_s =~ /(path|url)$/
|
166
|
+
return Rails.application.routes.url_helpers.send(symbol, *args)
|
167
|
+
end
|
168
|
+
|
169
|
+
super(symbol, *args, &block)
|
170
|
+
end
|
171
|
+
|
172
|
+
def initialize(params={})
|
173
|
+
@params = params
|
174
|
+
@records = []
|
175
|
+
end
|
176
|
+
|
177
|
+
def instance_query
|
178
|
+
@records = self.class.sql_string ? sql_instance_query : active_record_instance_query
|
179
|
+
self
|
180
|
+
end
|
181
|
+
|
182
|
+
def count
|
183
|
+
@count = self.class.sql_string ? sql_count : self.class.relation.count
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
def to_json
|
188
|
+
{
|
189
|
+
'sEcho' => (@params['sEcho'] || -1).to_i,
|
190
|
+
'aaData' => @records,
|
191
|
+
'iTotalRecords' => @records.length,
|
192
|
+
'iTotalDisplayRecords' => (@count || 0)
|
193
|
+
}
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
def sql_count
|
199
|
+
if self.class.count
|
200
|
+
count_sql = self.class.count.dup
|
201
|
+
if self.class.where && !@already_counted
|
202
|
+
count_sql << " WHERE " + self.class.where
|
203
|
+
end
|
204
|
+
@already_counted = true
|
205
|
+
else
|
206
|
+
count_sql = query_sql.sub(/^\s*SELECT(.*?)FROM/mi, 'SELECT count(*) FROM')
|
207
|
+
# we don't tak the where on because it's already been done inside query_sql
|
208
|
+
end
|
209
|
+
ActiveRecord::Base.connection.select_value(count_sql).to_i
|
210
|
+
end
|
211
|
+
|
212
|
+
def column_attributes
|
213
|
+
self.class.columns
|
214
|
+
end
|
215
|
+
|
216
|
+
def sql_instance_query
|
217
|
+
connection = self.class.model ? self.class.model.connection : ActiveRecord::Base.connection
|
218
|
+
connection.select_rows(query_sql + order_by_sql_fragment + limit_offset_sql_fragment)
|
219
|
+
end
|
220
|
+
|
221
|
+
def active_record_instance_query
|
222
|
+
raise "set_model not called on #{self.class.name}" unless self.class.model
|
223
|
+
relation = self.class.relation
|
224
|
+
if(search = search_string)
|
225
|
+
relation = relation.where(search)
|
226
|
+
end
|
227
|
+
relation = relation.order(order_string) if @params['iSortingCols'].to_i > 0
|
228
|
+
relation = relation.offset(@params['iDisplayStart']).limit(@params['iDisplayLength'])
|
229
|
+
self.class.model.connection.select_rows(relation.to_sql)
|
230
|
+
end
|
231
|
+
|
232
|
+
#
|
233
|
+
# TODO: look closer at this should it be isortingcols -1 ?
|
234
|
+
#
|
235
|
+
def order_string
|
236
|
+
result = []
|
237
|
+
|
238
|
+
@params['iSortingCols'].times do |count|
|
239
|
+
col_index = @params["iSortCol_#{count}"]
|
240
|
+
col_dir = @params["sSortDir_#{count}"]
|
241
|
+
col_name = column_attributes.keys[col_index]
|
242
|
+
result << " " + (col_name + " " + col_dir)
|
243
|
+
end
|
244
|
+
|
245
|
+
result.join(", ")
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
def sanitize(*args)
|
250
|
+
ActiveRecord::Base.send(:sanitize_sql_array, args)
|
251
|
+
end
|
252
|
+
|
253
|
+
def global_search_string
|
254
|
+
return nil unless @params['sSearch']
|
255
|
+
filter = @params['sSearch'].strip
|
256
|
+
result = []
|
257
|
+
|
258
|
+
column_attributes.keys.each_with_index do |col, i|
|
259
|
+
next if column_attributes[col][:bSearchable] == false
|
260
|
+
next unless @params["bSearchable_#{i}"]
|
261
|
+
attributes = column_attributes[col]
|
262
|
+
if attributes[:type] == :string
|
263
|
+
result << sanitize("#{col} #{like_sql} ?", "%#{filter}%")
|
264
|
+
elsif attributes[:type] == :integer
|
265
|
+
# to_i returns 0 on strings without numberic values so only search for 0 when the parameter actually contains '0'
|
266
|
+
if filter == "0" || filter.to_i != 0
|
267
|
+
result << "#{col} = #{filter.to_i}"
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
return nil if result.empty?
|
273
|
+
"(" + result.join(" OR ") + ")"
|
274
|
+
end
|
275
|
+
|
276
|
+
def individual_search_strings
|
277
|
+
keys = column_attributes.keys
|
278
|
+
result = []
|
279
|
+
((@params['iColumns']||1)).times do |i|
|
280
|
+
next if @params["sSearch_#{i}"].blank?
|
281
|
+
filter = @params["sSearch_#{i}"].strip
|
282
|
+
|
283
|
+
raise "can't search unsearchable column'" if column_attributes[keys[i]][:bSearchable] == false
|
284
|
+
attributes = column_attributes[keys[i]]
|
285
|
+
if attributes[:type] == :string
|
286
|
+
result << sanitize("#{keys[i]} #{like_sql} ?", "%#{filter}%")
|
287
|
+
elsif attributes[:type] == :integer
|
288
|
+
# to_i returns 0 on strings without numeric values only search for 0 when the parameter actually contains '0'
|
289
|
+
if filter == "0" || filter.to_i != 0
|
290
|
+
result << "#{keys[i]} = #{filter}"
|
291
|
+
else
|
292
|
+
result << '1 = 2'
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
return nil if result.empty?
|
297
|
+
"(" + result.join(" AND ") + ")"
|
298
|
+
end
|
299
|
+
|
300
|
+
def search_string
|
301
|
+
result = [global_search_string, individual_search_strings].compact
|
302
|
+
result.join(" AND ") if result.any?
|
303
|
+
end
|
304
|
+
|
305
|
+
def limit_offset_sql_fragment
|
306
|
+
return '' unless @params['iDisplayStart'].present? && @params['iDisplayLength'].present?
|
307
|
+
result = ""
|
308
|
+
result << " LIMIT #{@params['iDisplayLength']}" if @params['iDisplayLength']
|
309
|
+
result << " OFFSET #{@params['iDisplayStart']}" if @params['iDisplayStart']
|
310
|
+
result
|
311
|
+
end
|
312
|
+
|
313
|
+
def order_by_sql_fragment
|
314
|
+
if @params['iSortingCols'].to_i > 0
|
315
|
+
" ORDER BY" + order_string
|
316
|
+
else
|
317
|
+
""
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def query_sql
|
322
|
+
result = self.class.sql_string.dup
|
323
|
+
if self.class.where
|
324
|
+
result << " WHERE " + self.class.where
|
325
|
+
if search_string
|
326
|
+
result << " AND " + search_string
|
327
|
+
end
|
328
|
+
else
|
329
|
+
if search_string
|
330
|
+
result << " WHERE " + search_string
|
331
|
+
end
|
332
|
+
end
|
333
|
+
result
|
334
|
+
end
|
335
|
+
|
336
|
+
def like_sql
|
337
|
+
Datatable::Base.config.sql_like || "LIKE"
|
338
|
+
end
|
339
|
+
|
340
|
+
end
|
341
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Datatable
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path("../../../../vendor/datatable", __FILE__)
|
5
|
+
|
6
|
+
def copy_assets
|
7
|
+
say_status("copying", "dataTable assets", :green)
|
8
|
+
|
9
|
+
directory 'media/images', 'public/datatable/images'
|
10
|
+
directory 'media/css', 'public/datatable/css'
|
11
|
+
directory 'media/js', 'public/datatable/js'
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def copy_initializer
|
16
|
+
template "../../lib/generators/templates/datatable_initializer.rb", "config/initializers/datatable.rb"
|
17
|
+
end
|
18
|
+
|
19
|
+
def show_next_steps
|
20
|
+
|
21
|
+
puts "\n" * 3
|
22
|
+
|
23
|
+
puts <<-HELPFUL_INSTRUCTIONS
|
24
|
+
|
25
|
+
Next Steps:
|
26
|
+
|
27
|
+
0. You must be using and including JQuery.
|
28
|
+
|
29
|
+
# Gemfile
|
30
|
+
|
31
|
+
gem 'jquery-rails'
|
32
|
+
|
33
|
+
Then bundle and run rails g jquery:install
|
34
|
+
|
35
|
+
|
36
|
+
1. Put the asset tags into your layouts:
|
37
|
+
|
38
|
+
# app/views/layouts/admin.html.erb
|
39
|
+
|
40
|
+
<%= stylesheet_link_tag '/datatable/css/demo_table.css' %>
|
41
|
+
|
42
|
+
<%= javascript_include_tag :defaults %>
|
43
|
+
|
44
|
+
<%# The datatable javascript tag must come after you require jquery! %>
|
45
|
+
<%= javascript_include_datatable %>
|
46
|
+
|
47
|
+
|
48
|
+
2. Create a Datatable. We suggest naming it Controller#Action.
|
49
|
+
|
50
|
+
rails g datatable:new UsersIndex
|
51
|
+
HELPFUL_INSTRUCTIONS
|
52
|
+
puts "\n" * 5
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Datatable
|
2
|
+
module Generators
|
3
|
+
|
4
|
+
class NewGenerator < Rails::Generators::NamedBase
|
5
|
+
source_root File.expand_path("../../templates", __FILE__)
|
6
|
+
|
7
|
+
def create_datatable_file
|
8
|
+
template "datatable.rb", "app/datatables/#{file_name}.rb"
|
9
|
+
end
|
10
|
+
|
11
|
+
def show_next_steps
|
12
|
+
puts "\n" * 2
|
13
|
+
|
14
|
+
puts <<-HELPFUL_INSTRUCTIONS
|
15
|
+
Next Steps:
|
16
|
+
|
17
|
+
0. Setup the controller
|
18
|
+
|
19
|
+
# app/controllers/your_controller.rb
|
20
|
+
def index
|
21
|
+
@datatable = #{class_name}
|
22
|
+
respond_to do |format|
|
23
|
+
format.html
|
24
|
+
format.js { render :json => @datatable.query(params).to_json }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
1. Setup the view
|
29
|
+
|
30
|
+
# app/views/your_controller/your_view.html.erb
|
31
|
+
|
32
|
+
<%= datatable %>
|
33
|
+
|
34
|
+
2. Enjoy.
|
35
|
+
|
36
|
+
HELPFUL_INSTRUCTIONS
|
37
|
+
puts "\n" * 3
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class <%= class_name %> < Datatable::Base
|
2
|
+
|
3
|
+
# sql <<-SQL
|
4
|
+
# SELECT
|
5
|
+
# orders.id,
|
6
|
+
# orders.order_number,
|
7
|
+
# customers.first_name,
|
8
|
+
# customers.last_name,
|
9
|
+
# orders.memo
|
10
|
+
# FROM
|
11
|
+
# orders
|
12
|
+
# JOIN
|
13
|
+
# customers ON customers.id = orders.customer_id
|
14
|
+
# SQL
|
15
|
+
|
16
|
+
# columns(
|
17
|
+
# {"orders.id" => {:type => :integer, :sTitle => "Id", :sWidth => '50px'}},
|
18
|
+
# {"orders.order_number" => {:type => :integer, :link_to => link_to('{{1}}', order_path('{{0}}')),:sTitle => 'Order Number', :sWidth => '125px' }},
|
19
|
+
# {"customers.first_name" => {:type => :string, :link_to => link_to('{{2}}', order_path('{{0}}')),:sWidth => '200px' }},
|
20
|
+
# {"customers.last_name" => {:type => :string,:sWidth => '200px'}},
|
21
|
+
# {"orders.memo" => {:type => :string }}
|
22
|
+
# )
|
23
|
+
# option('bJQueryUI', true)
|
24
|
+
# option('individual_column_searching', true)
|
25
|
+
# #option('sDom', '<"H"lrf>t<"F"ip>') # use with pagination
|
26
|
+
# # to use pagination comment out following and enable previous line
|
27
|
+
# option('sDom', '<"clear"><"H"Trf>t<"F"i>')
|
28
|
+
# option('bScrollInfinite', true)
|
29
|
+
# option('bScrollCollapse', true)
|
30
|
+
# option('sScrollY', '200px')
|
31
|
+
|
32
|
+
end
|
33
|
+
|