datatable 0.1.0alpha2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (187) hide show
  1. data/.bundle/config +3 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +9 -0
  5. data/Gemfile.lock +83 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +135 -0
  8. data/Rakefile +27 -0
  9. data/TODO +71 -0
  10. data/datatable.gemspec +20 -0
  11. data/example_app/.DS_Store +0 -0
  12. data/example_app/.gitignore +6 -0
  13. data/example_app/.rspec +1 -0
  14. data/example_app/Gemfile +35 -0
  15. data/example_app/Gemfile.lock +126 -0
  16. data/example_app/Rakefile +7 -0
  17. data/example_app/app/controllers/application_controller.rb +3 -0
  18. data/example_app/app/controllers/orders_controller.rb +13 -0
  19. data/example_app/app/datatables/orders_index.rb +31 -0
  20. data/example_app/app/helpers/application_helper.rb +2 -0
  21. data/example_app/app/models/customer.rb +4 -0
  22. data/example_app/app/models/item.rb +4 -0
  23. data/example_app/app/models/order.rb +5 -0
  24. data/example_app/app/models/order_item.rb +4 -0
  25. data/example_app/app/models/sales_rep.rb +3 -0
  26. data/example_app/app/views/layouts/application.html.erb +12 -0
  27. data/example_app/app/views/orders/index.html.erb +14 -0
  28. data/example_app/config/application.rb +41 -0
  29. data/example_app/config/boot.rb +6 -0
  30. data/example_app/config/database.yml.mysql +20 -0
  31. data/example_app/config/database.yml.pg +20 -0
  32. data/example_app/config/environment.rb +5 -0
  33. data/example_app/config/environments/development.rb +28 -0
  34. data/example_app/config/environments/production.rb +49 -0
  35. data/example_app/config/environments/test.rb +35 -0
  36. data/example_app/config/initializers/backtrace_silencers.rb +7 -0
  37. data/example_app/config/initializers/datatable.rb +6 -0
  38. data/example_app/config/initializers/inflections.rb +10 -0
  39. data/example_app/config/initializers/mime_types.rb +5 -0
  40. data/example_app/config/initializers/secret_token.rb +9 -0
  41. data/example_app/config/initializers/session_store.rb +8 -0
  42. data/example_app/config/locales/en.yml +5 -0
  43. data/example_app/config/routes.rb +6 -0
  44. data/example_app/config.ru +4 -0
  45. data/example_app/db/migrate/20110429185712_create_customers.rb +15 -0
  46. data/example_app/db/migrate/20110429185742_create_sales_reps.rb +14 -0
  47. data/example_app/db/migrate/20110429185807_create_items.rb +15 -0
  48. data/example_app/db/migrate/20110429185913_create_orders.rb +15 -0
  49. data/example_app/db/migrate/20110429190005_create_order_items.rb +14 -0
  50. data/example_app/db/schema.rb +53 -0
  51. data/example_app/db/seeds.rb +49 -0
  52. data/example_app/lib/tasks/.gitkeep +0 -0
  53. data/example_app/lib/tasks/setup.rake +12 -0
  54. data/example_app/public/404.html +26 -0
  55. data/example_app/public/422.html +26 -0
  56. data/example_app/public/500.html +26 -0
  57. data/example_app/public/datatable/css/demo_page.css +99 -0
  58. data/example_app/public/datatable/css/demo_table.css +539 -0
  59. data/example_app/public/datatable/css/demo_table_jui.css +521 -0
  60. data/example_app/public/datatable/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  61. data/example_app/public/datatable/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  62. data/example_app/public/datatable/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  63. data/example_app/public/datatable/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  64. data/example_app/public/datatable/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  65. data/example_app/public/datatable/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  66. data/example_app/public/datatable/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  67. data/example_app/public/datatable/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  68. data/example_app/public/datatable/css/smoothness/images/ui-icons_222222_256x240.png +0 -0
  69. data/example_app/public/datatable/css/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  70. data/example_app/public/datatable/css/smoothness/images/ui-icons_454545_256x240.png +0 -0
  71. data/example_app/public/datatable/css/smoothness/images/ui-icons_888888_256x240.png +0 -0
  72. data/example_app/public/datatable/css/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  73. data/example_app/public/datatable/css/smoothness/jquery-ui-1.8.14.custom.css +568 -0
  74. data/example_app/public/datatable/images/back_disabled.jpg +0 -0
  75. data/example_app/public/datatable/images/back_enabled.jpg +0 -0
  76. data/example_app/public/datatable/images/favicon.ico +0 -0
  77. data/example_app/public/datatable/images/forward_disabled.jpg +0 -0
  78. data/example_app/public/datatable/images/forward_enabled.jpg +0 -0
  79. data/example_app/public/datatable/images/sort_asc.png +0 -0
  80. data/example_app/public/datatable/images/sort_asc_disabled.png +0 -0
  81. data/example_app/public/datatable/images/sort_both.png +0 -0
  82. data/example_app/public/datatable/images/sort_desc.png +0 -0
  83. data/example_app/public/datatable/images/sort_desc_disabled.png +0 -0
  84. data/example_app/public/datatable/js/jquery-ui-1.8.14.custom.min.js +789 -0
  85. data/example_app/public/datatable/js/jquery.dataTables.js +7347 -0
  86. data/example_app/public/datatable/js/jquery.dataTables.min.js +151 -0
  87. data/example_app/public/favicon.ico +0 -0
  88. data/example_app/public/flash/copy_cvs_xls.swf +0 -0
  89. data/example_app/public/flash/copy_cvs_xls_pdf.swf +0 -0
  90. data/example_app/public/images/rails.png +0 -0
  91. data/example_app/public/javascripts/application.js +2 -0
  92. data/example_app/public/javascripts/jquery.js +8936 -0
  93. data/example_app/public/javascripts/jquery.min.js +18 -0
  94. data/example_app/public/javascripts/jquery_ujs.js +316 -0
  95. data/example_app/public/robots.txt +5 -0
  96. data/example_app/public/stylesheets/.gitkeep +0 -0
  97. data/example_app/script/rails +6 -0
  98. data/example_app/spec/datatables/active_record_dsl_spec.rb +59 -0
  99. data/example_app/spec/datatables/active_record_link_to_spec.rb +22 -0
  100. data/example_app/spec/datatables/active_record_pagination_spec.rb +94 -0
  101. data/example_app/spec/datatables/active_record_table_operations_spec.rb +180 -0
  102. data/example_app/spec/datatables/config_spec.rb +10 -0
  103. data/example_app/spec/datatables/query_params_spec.rb +73 -0
  104. data/example_app/spec/datatables/sql_default_spec.rb +22 -0
  105. data/example_app/spec/datatables/sql_pagination_spec.rb +177 -0
  106. data/example_app/spec/datatables/sql_search_cast_spec.rb +6 -0
  107. data/example_app/spec/datatables/sql_search_global_spec.rb +107 -0
  108. data/example_app/spec/datatables/sql_search_individual_spec.rb +113 -0
  109. data/example_app/spec/datatables/sql_search_where_spec.rb +87 -0
  110. data/example_app/spec/datatables/sql_sorting_spec.rb +80 -0
  111. data/example_app/spec/datatables/sql_variables_spec.rb +104 -0
  112. data/example_app/spec/factories/customer_factory.rb +4 -0
  113. data/example_app/spec/factories/item_factory.rb +2 -0
  114. data/example_app/spec/factories/order_factory.rb +7 -0
  115. data/example_app/spec/factories/order_item_factory.rb +2 -0
  116. data/example_app/spec/factories/sales_rep_factory.rb +4 -0
  117. data/example_app/spec/helpers/aocolumn_spec.rb +239 -0
  118. data/example_app/spec/helpers/data_table_helper_spec.rb +148 -0
  119. data/example_app/spec/helpers/headings_spec.rb +71 -0
  120. data/example_app/spec/spec_helper.rb +29 -0
  121. data/generators.txt +6 -0
  122. data/images/datatable_screenshot.png +0 -0
  123. data/lib/datatable/active_record_dsl.rb +49 -0
  124. data/lib/datatable/errors.rb +5 -0
  125. data/lib/datatable/helper.rb +199 -0
  126. data/lib/datatable/railtie.rb +17 -0
  127. data/lib/datatable/version.rb +4 -0
  128. data/lib/datatable.rb +341 -0
  129. data/lib/generators/datatable/install_generator.rb +58 -0
  130. data/lib/generators/datatable/new_generator.rb +46 -0
  131. data/lib/generators/templates/datatable.rb +33 -0
  132. data/lib/generators/templates/datatable_initializer.rb +6 -0
  133. data/vendor/datatable/Readme.txt +11 -0
  134. data/vendor/datatable/extras/TableTools/media/css/TableTools.css +264 -0
  135. data/vendor/datatable/extras/TableTools/media/css/TableTools_JUI.css +182 -0
  136. data/vendor/datatable/extras/TableTools/media/images/background.png +0 -0
  137. data/vendor/datatable/extras/TableTools/media/images/collection.png +0 -0
  138. data/vendor/datatable/extras/TableTools/media/images/collection_hover.png +0 -0
  139. data/vendor/datatable/extras/TableTools/media/images/copy.png +0 -0
  140. data/vendor/datatable/extras/TableTools/media/images/copy_hover.png +0 -0
  141. data/vendor/datatable/extras/TableTools/media/images/csv.png +0 -0
  142. data/vendor/datatable/extras/TableTools/media/images/csv_hover.png +0 -0
  143. data/vendor/datatable/extras/TableTools/media/images/pdf.png +0 -0
  144. data/vendor/datatable/extras/TableTools/media/images/pdf_hover.png +0 -0
  145. data/vendor/datatable/extras/TableTools/media/images/print.png +0 -0
  146. data/vendor/datatable/extras/TableTools/media/images/print_hover.png +0 -0
  147. data/vendor/datatable/extras/TableTools/media/images/xls.png +0 -0
  148. data/vendor/datatable/extras/TableTools/media/images/xls_hover.png +0 -0
  149. data/vendor/datatable/extras/TableTools/media/js/TableTools.js +2410 -0
  150. data/vendor/datatable/extras/TableTools/media/js/TableTools.min.js +78 -0
  151. data/vendor/datatable/extras/TableTools/media/js/TableTools.min.js.gz +0 -0
  152. data/vendor/datatable/extras/TableTools/media/js/ZeroClipboard.js +365 -0
  153. data/vendor/datatable/extras/TableTools/media/swf/copy_cvs_xls.swf +0 -0
  154. data/vendor/datatable/extras/TableTools/media/swf/copy_cvs_xls_pdf.swf +0 -0
  155. data/vendor/datatable/license-bsd.txt +10 -0
  156. data/vendor/datatable/license-gpl2.txt +339 -0
  157. data/vendor/datatable/media/css/demo_page.css +99 -0
  158. data/vendor/datatable/media/css/demo_table.css +539 -0
  159. data/vendor/datatable/media/css/demo_table_jui.css +521 -0
  160. data/vendor/datatable/media/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  161. data/vendor/datatable/media/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  162. data/vendor/datatable/media/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  163. data/vendor/datatable/media/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  164. data/vendor/datatable/media/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  165. data/vendor/datatable/media/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  166. data/vendor/datatable/media/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  167. data/vendor/datatable/media/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  168. data/vendor/datatable/media/css/smoothness/images/ui-icons_222222_256x240.png +0 -0
  169. data/vendor/datatable/media/css/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  170. data/vendor/datatable/media/css/smoothness/images/ui-icons_454545_256x240.png +0 -0
  171. data/vendor/datatable/media/css/smoothness/images/ui-icons_888888_256x240.png +0 -0
  172. data/vendor/datatable/media/css/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  173. data/vendor/datatable/media/css/smoothness/jquery-ui-1.8.14.custom.css +568 -0
  174. data/vendor/datatable/media/images/back_disabled.jpg +0 -0
  175. data/vendor/datatable/media/images/back_enabled.jpg +0 -0
  176. data/vendor/datatable/media/images/favicon.ico +0 -0
  177. data/vendor/datatable/media/images/forward_disabled.jpg +0 -0
  178. data/vendor/datatable/media/images/forward_enabled.jpg +0 -0
  179. data/vendor/datatable/media/images/sort_asc.png +0 -0
  180. data/vendor/datatable/media/images/sort_asc_disabled.png +0 -0
  181. data/vendor/datatable/media/images/sort_both.png +0 -0
  182. data/vendor/datatable/media/images/sort_desc.png +0 -0
  183. data/vendor/datatable/media/images/sort_desc_disabled.png +0 -0
  184. data/vendor/datatable/media/js/jquery-ui-1.8.14.custom.min.js +789 -0
  185. data/vendor/datatable/media/js/jquery.dataTables.js +7347 -0
  186. data/vendor/datatable/media/js/jquery.dataTables.min.js +151 -0
  187. 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
@@ -0,0 +1,4 @@
1
+ module Datatable
2
+ # http://semver.org
3
+ VERSION = "0.1.0alpha2"
4
+ 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
+