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.
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
+