datatable 0.1.0alpha2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.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
|
+
|