effective_datatables 1.8.3 → 2.0.0
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.
- checksums.yaml +4 -4
- data/README.md +72 -55
- data/app/assets/javascripts/effective_datatables/initialize.js.coffee.erb +1 -1
- data/app/controllers/effective/datatables_controller.rb +5 -1
- data/app/helpers/effective_datatables_helper.rb +1 -6
- data/app/models/effective/datatable.rb +30 -388
- data/app/models/effective/effective_datatable/ajax.rb +105 -0
- data/app/models/effective/effective_datatable/dsl.rb +57 -0
- data/app/models/effective/effective_datatable/options.rb +113 -0
- data/app/models/effective/effective_datatable/rendering.rb +120 -0
- data/app/views/effective/datatables/_actions_column.html.haml +1 -1
- data/app/views/effective/datatables/_datatable.html.haml +1 -1
- data/lib/effective_datatables.rb +0 -11
- data/lib/effective_datatables/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42cbf488b1f1cf5d77c3ffd2dc352b06eee67d9c
|
4
|
+
data.tar.gz: d94cd33a3001743a072162855700dcdbf97e6dd2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69dab91b291914705aa7e9903b706772d73ec708b9a5c0ebdb0bb9494481fccdb0f0c8d124de634b3dbf952287fe99d62e5f5f48f43e718a38d2e7f85f3a5b44
|
7
|
+
data.tar.gz: 4b44b67e3385891730e63cea4c236967792d34959a4c65662cc7c9d4243eae39d5df61be52194b74f8eb49f84be2db522b052eafddf3d02ca7e8b34a8c08b7c9
|
data/README.md
CHANGED
@@ -67,10 +67,12 @@ This model exists at `/app/models/effective/datatables/posts.rb`:
|
|
67
67
|
module Effective
|
68
68
|
module Datatables
|
69
69
|
class Posts < Effective::Datatable
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
70
|
+
datatable do
|
71
|
+
table_column :id
|
72
|
+
table_column :user # if Post belongs_to :user
|
73
|
+
table_column :title
|
74
|
+
table_column :created_at
|
75
|
+
end
|
74
76
|
|
75
77
|
def collection
|
76
78
|
Post.all
|
@@ -88,7 +90,7 @@ We're going to display this DataTable on the posts#index action
|
|
88
90
|
```ruby
|
89
91
|
class PostsController < ApplicationController
|
90
92
|
def index
|
91
|
-
@datatable = Effective::Datatables::Posts.new
|
93
|
+
@datatable = Effective::Datatables::Posts.new
|
92
94
|
end
|
93
95
|
end
|
94
96
|
```
|
@@ -128,31 +130,33 @@ For example: `/app/models/effective/datatables/posts.rb`:
|
|
128
130
|
module Effective
|
129
131
|
module Datatables
|
130
132
|
class Posts < Effective::Datatable
|
131
|
-
|
132
|
-
|
133
|
+
datatable do
|
134
|
+
default_order :created_at, :desc
|
135
|
+
default_entries 25
|
133
136
|
|
134
|
-
|
137
|
+
table_column :id, :visible => false
|
135
138
|
|
136
|
-
|
139
|
+
table_column :created_at, :width => '25%'
|
137
140
|
|
138
|
-
|
141
|
+
table_column :updated_at, :proc => Proc.new { |post| nicetime(post.updated_at) } # just a standard helper as defined in helpers/application_helper.rb
|
139
142
|
|
140
|
-
|
143
|
+
table_column :user
|
141
144
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
+
table_column :post_category_id, :filter => {:type => :select, :values => Proc.new { PostCategory.all } } do |post|
|
146
|
+
post.post_category.name.titleize
|
147
|
+
end
|
145
148
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
149
|
+
array_column :comments do |post|
|
150
|
+
content_tag(:ul) do
|
151
|
+
post.comments.where(:archived => false).map do |comment|
|
152
|
+
content_tag(:li, comment.title)
|
153
|
+
end.join('').html_safe
|
154
|
+
end
|
151
155
|
end
|
152
|
-
end
|
153
156
|
|
154
|
-
|
155
|
-
|
157
|
+
table_column :title, :label => 'Post Title', :class => 'col-title'
|
158
|
+
table_column :actions, :sortable => false, :filter => false, :partial => '/posts/actions'
|
159
|
+
end
|
156
160
|
|
157
161
|
def collection
|
158
162
|
Post.where(:archived => false).includes(:post_category)
|
@@ -211,10 +215,12 @@ Define your collection as an Array of Arrays, declare only array_columns, and ev
|
|
211
215
|
module Effective
|
212
216
|
module Datatables
|
213
217
|
class ArrayBackedDataTable < Effective::Datatable
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
+
datatable do
|
219
|
+
array_column :id
|
220
|
+
array_column :first_name
|
221
|
+
array_column :last_name
|
222
|
+
array_column :email
|
223
|
+
end
|
218
224
|
|
219
225
|
def collection
|
220
226
|
[
|
@@ -268,8 +274,12 @@ table_column :created_at
|
|
268
274
|
table_column :user
|
269
275
|
|
270
276
|
# Will have the same behaviour as declaring
|
271
|
-
|
272
|
-
|
277
|
+
datatable do
|
278
|
+
if attributes[:user_id].blank?
|
279
|
+
table_column :user_id, :filter => {:type => :select, :values => Proc.new { User.all.map { |user| [user.id, user.to_s] }.sort { |x, y| x[1] <=> y[1] } } } do |post|
|
280
|
+
post.user.to_s
|
281
|
+
end
|
282
|
+
end
|
273
283
|
end
|
274
284
|
```
|
275
285
|
|
@@ -299,7 +309,6 @@ The following options control the general behaviour of the column:
|
|
299
309
|
```ruby
|
300
310
|
:column => 'users.id' # Set this if you're doing something tricky with the database. Used internally for .order() and .where() clauses
|
301
311
|
:type => :string # Derived from the ActiveRecord attribute default datatype. Controls searching behaviour. Valid options include :string, :text, :datetime, :integer, :boolean, :year
|
302
|
-
:if => Proc.new { attributes[:user_id].blank? } # Excludes this table_column entirely if false. See "Initialize with attributes" section of this README below
|
303
312
|
```
|
304
313
|
|
305
314
|
### Display Options
|
@@ -503,23 +512,28 @@ class PostsController < ApplicationController
|
|
503
512
|
end
|
504
513
|
```
|
505
514
|
|
506
|
-
|
515
|
+
And then in your datatable:
|
507
516
|
|
508
517
|
```ruby
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
end
|
516
|
-
|
518
|
+
module Effective
|
519
|
+
module Datatables
|
520
|
+
class Posts < Effective::Datatable
|
521
|
+
datatable do
|
522
|
+
if attributes[:user_id].blank?
|
523
|
+
table_column :user_id { |post| post.user.email }
|
524
|
+
end
|
525
|
+
end
|
517
526
|
|
518
|
-
|
527
|
+
def collection
|
528
|
+
if attributes[:user_id]
|
529
|
+
Post.where(user_id: attributes[:user_id])
|
530
|
+
else
|
531
|
+
Post.all
|
532
|
+
end
|
533
|
+
end
|
519
534
|
|
520
|
-
|
521
|
-
|
522
|
-
post.user.email
|
535
|
+
end
|
536
|
+
end
|
523
537
|
end
|
524
538
|
```
|
525
539
|
|
@@ -531,14 +545,6 @@ Any non-private methods defined in the datatable model will be available to your
|
|
531
545
|
module Effective
|
532
546
|
module Datatables
|
533
547
|
class Posts < Effective::Datatable
|
534
|
-
table_column :title do |post|
|
535
|
-
format_post_title(post)
|
536
|
-
end
|
537
|
-
|
538
|
-
def collection
|
539
|
-
Post.all
|
540
|
-
end
|
541
|
-
|
542
548
|
def format_post_title(post)
|
543
549
|
if post.title.start_with?('important')
|
544
550
|
link_to(post.title.upcase, post_path(post))
|
@@ -547,6 +553,15 @@ module Effective
|
|
547
553
|
end
|
548
554
|
end
|
549
555
|
|
556
|
+
datatable do
|
557
|
+
table_column :title do |post|
|
558
|
+
format_post_title(post)
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
def collection
|
563
|
+
Post.all
|
564
|
+
end
|
550
565
|
end
|
551
566
|
end
|
552
567
|
end
|
@@ -577,7 +592,7 @@ end
|
|
577
592
|
When working with an ActiveRecord collection that implements [effective_obfuscation](https://github.com/code-and-effect/effective_obfuscation) for the ID column,
|
578
593
|
that column's filters and sorting will be automatically configured.
|
579
594
|
|
580
|
-
Just define `table_column :id
|
595
|
+
Just define `table_column :id`
|
581
596
|
|
582
597
|
Unfortunately, due to the effective_obfuscation algorithm, sorting and filtering by partial values is not supported.
|
583
598
|
|
@@ -588,7 +603,7 @@ So the column may not be sorted, and may only be filtered by typing the entire 1
|
|
588
603
|
When working with an ActiveRecord collection that implements [effective_roles](https://github.com/code-and-effect/effective_roles),
|
589
604
|
the filters and sorting will be automatically configured.
|
590
605
|
|
591
|
-
Just define `table_column :roles
|
606
|
+
Just define `table_column :roles`
|
592
607
|
|
593
608
|
The `EffectiveRoles.roles` collection will be used for the filter values, and sorting will be done by roles_mask.
|
594
609
|
|
@@ -680,10 +695,12 @@ In this example, a User belongs_to an Applicant. But instead of using the built
|
|
680
695
|
module Effective
|
681
696
|
module Datatables
|
682
697
|
class Applicants < Effective::Datatable
|
683
|
-
|
698
|
+
datatable do
|
699
|
+
table_column :id, visible: true
|
684
700
|
|
685
|
-
|
686
|
-
|
701
|
+
table_column :user, :type => :string, :column => 'users.email' do |applicant|
|
702
|
+
link_to applicant.user.try(:email), edit_admin_user_path(applicant.user)
|
703
|
+
end
|
687
704
|
end
|
688
705
|
|
689
706
|
def collection
|
@@ -14,7 +14,7 @@ initializeDataTables = ->
|
|
14
14
|
pagingType: 'simple_numbers'
|
15
15
|
language: { 'lengthMenu': 'Show _MENU_ per page'}
|
16
16
|
lengthMenu: [[10, 25, 50, 100, 250, 1000, -1], ['10', '25', '50', '100', '250', '1000', 'All']]
|
17
|
-
iDisplayLength: datatable.data('
|
17
|
+
iDisplayLength: datatable.data('display-entries')
|
18
18
|
columnDefs: [
|
19
19
|
{ visible: false, targets: datatable.data('non-visible') },
|
20
20
|
{ sortable: false, targets: datatable.data('non-sortable') }
|
@@ -4,7 +4,7 @@ module Effective
|
|
4
4
|
|
5
5
|
# This will respond to both a GET and a POST
|
6
6
|
def show
|
7
|
-
@datatable =
|
7
|
+
@datatable = find_datatable(params[:id]).try(:new, params[:attributes])
|
8
8
|
@datatable.view = view_context if !@datatable.nil?
|
9
9
|
|
10
10
|
EffectiveDatatables.authorized?(self, :index, @datatable.try(:collection_class) || Effective::Datatable)
|
@@ -24,6 +24,10 @@ module Effective
|
|
24
24
|
|
25
25
|
private
|
26
26
|
|
27
|
+
def find_datatable(id)
|
28
|
+
"effective/datatables/#{id}".classify.tap { |klass| klass << 's' if id.to_s.end_with?('s') }.safe_constantize
|
29
|
+
end
|
30
|
+
|
27
31
|
def error_json
|
28
32
|
{
|
29
33
|
:draw => params[:draw].to_i,
|
@@ -54,12 +54,7 @@ module EffectiveDatatablesHelper
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def datatable_default_order(datatable)
|
57
|
-
[
|
58
|
-
if datatable.default_order.present?
|
59
|
-
index = (datatable.table_columns.values.find { |options| options[:name] == datatable.default_order.keys.first.to_s }[:index] rescue nil)
|
60
|
-
[index, datatable.default_order.values.first] if index.present?
|
61
|
-
end || [0, 'asc']
|
62
|
-
].to_json()
|
57
|
+
[datatable.order_index, datatable.order_direction.downcase].to_json()
|
63
58
|
end
|
64
59
|
|
65
60
|
def datatable_widths(datatable)
|
@@ -4,77 +4,23 @@ module Effective
|
|
4
4
|
|
5
5
|
delegate :render, :link_to, :mail_to, :to => :@view
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
EffectiveDatatables.datatables.map { |klass| klass.new() }
|
10
|
-
end
|
11
|
-
|
12
|
-
def find(obj, attributes = nil)
|
13
|
-
obj = obj.respond_to?(:to_param) ? obj.to_param : obj
|
14
|
-
EffectiveDatatables.datatables.find { |klass| klass.name.underscore.parameterize == obj }.try(:new, attributes.presence || {})
|
15
|
-
end
|
16
|
-
|
17
|
-
def table_column(name, options = {}, proc = nil, &block)
|
18
|
-
if block_given?
|
19
|
-
raise "You cannot use :partial => '' with the block syntax" if options[:partial]
|
20
|
-
raise "You cannot use :proc => ... with the block syntax" if options[:proc]
|
21
|
-
options[:block] = block
|
22
|
-
end
|
23
|
-
raise "You cannot use both :partial => '' and proc => ..." if options[:partial] && options[:proc]
|
24
|
-
|
25
|
-
send(:attr_accessor, name)
|
26
|
-
(@table_columns ||= HashWithIndifferentAccess.new())[name] = options
|
27
|
-
end
|
28
|
-
|
29
|
-
def table_columns(*names)
|
30
|
-
names.each { |name| table_column(name) }
|
31
|
-
end
|
32
|
-
|
33
|
-
def array_column(name, options = {}, proc = nil, &block)
|
34
|
-
table_column(name, options.merge!({:array_column => true}), proc, &block)
|
35
|
-
end
|
36
|
-
|
37
|
-
def actions_column(options = {}, proc = nil, &block)
|
38
|
-
show = options.fetch(:show, false)
|
39
|
-
edit = options.fetch(:edit, true)
|
40
|
-
destroy = options.fetch(:destroy, true)
|
41
|
-
name = options.fetch(:name, 'actions')
|
42
|
-
|
43
|
-
opts = {
|
44
|
-
sortable: false,
|
45
|
-
filter: false,
|
46
|
-
partial_local: :resource,
|
47
|
-
partial_locals: { show_action: show, edit_action: edit, destroy_action: destroy }
|
48
|
-
}
|
49
|
-
opts[:partial] = '/effective/datatables/actions_column' unless (block_given? || proc.present?)
|
50
|
-
|
51
|
-
table_column(name, opts, proc, &block)
|
52
|
-
end
|
53
|
-
|
54
|
-
def array_columns(*names)
|
55
|
-
names.each { |name| array_column(name) }
|
56
|
-
end
|
57
|
-
|
58
|
-
def default_order(name, direction = :asc)
|
59
|
-
@default_order = {name => direction}
|
60
|
-
end
|
61
|
-
|
62
|
-
def default_entries(entries)
|
63
|
-
@default_entries = entries
|
64
|
-
end
|
7
|
+
include Effective::EffectiveDatatable::Dsl
|
8
|
+
extend Effective::EffectiveDatatable::Dsl::ClassMethods
|
65
9
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
10
|
+
include Effective::EffectiveDatatable::Ajax
|
11
|
+
include Effective::EffectiveDatatable::Options
|
12
|
+
include Effective::EffectiveDatatable::Rendering
|
70
13
|
|
71
14
|
def initialize(*args)
|
72
|
-
if args.present?
|
73
|
-
raise
|
15
|
+
if args.present? && args.first != nil
|
16
|
+
raise "#{self.class.name}.new() can only be initialized with a Hash like arguments" unless args.first.kind_of?(Hash)
|
74
17
|
args.first.each { |k, v| self.attributes[k] = v }
|
75
18
|
end
|
76
19
|
|
77
|
-
|
20
|
+
initialize_datatable # This creates @table_columns based on the DSL datatable do .. end block
|
21
|
+
initialize_options # This normalizes all the options
|
22
|
+
|
23
|
+
unless active_record_collection? || array_collection?
|
78
24
|
raise "Unsupported collection type. Should be ActiveRecord class, ActiveRecord relation, or an Array of Arrays [[1, 'something'], [2, 'something else']]"
|
79
25
|
end
|
80
26
|
|
@@ -82,6 +28,10 @@ module Effective
|
|
82
28
|
search_terms.each { |column, term| self.send("#{column}=", term) }
|
83
29
|
end
|
84
30
|
|
31
|
+
def table_columns
|
32
|
+
@table_columns
|
33
|
+
end
|
34
|
+
|
85
35
|
# Any attributes set on initialize will be echoed back and available to the class
|
86
36
|
def attributes
|
87
37
|
@attributes ||= HashWithIndifferentAccess.new()
|
@@ -94,8 +44,12 @@ module Effective
|
|
94
44
|
@model_name ||= ActiveModel::Name.new(self.class)
|
95
45
|
end
|
96
46
|
|
47
|
+
def self.model_name # Searching & Filters
|
48
|
+
@model_name ||= ActiveModel::Name.new(self)
|
49
|
+
end
|
50
|
+
|
97
51
|
def to_param
|
98
|
-
self.class.name.underscore.
|
52
|
+
@to_param ||= self.class.name.underscore.sub('effective/datatables/', '')
|
99
53
|
end
|
100
54
|
|
101
55
|
def collection
|
@@ -106,30 +60,6 @@ module Effective
|
|
106
60
|
collection.respond_to?(:klass) ? collection.klass : self.class
|
107
61
|
end
|
108
62
|
|
109
|
-
def finalize(collection) # Override me if you like
|
110
|
-
collection
|
111
|
-
end
|
112
|
-
|
113
|
-
# Select only col[:if] == true columns, and then set the col[:index] accordingly
|
114
|
-
def table_columns
|
115
|
-
@table_columns ||= table_columns_with_defaults().select do |_, col|
|
116
|
-
col[:if] == nil || (col[:if].respond_to?(:call) ? (view || self).instance_exec(&col[:if]) : col[:if])
|
117
|
-
end.each_with_index { |(_, col), index| col[:index] = index }
|
118
|
-
end
|
119
|
-
|
120
|
-
# This is for the ColReorder plugin
|
121
|
-
# It sends us a list of columns that are different than our table_columns order
|
122
|
-
# So this method just returns an array of column names, as per ColReorder
|
123
|
-
def display_table_columns
|
124
|
-
if params[:columns].present?
|
125
|
-
HashWithIndifferentAccess.new().tap do |display_columns|
|
126
|
-
params[:columns].each do |_, values|
|
127
|
-
display_columns[values[:name]] = table_columns[values[:name]]
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
63
|
def to_json
|
134
64
|
raise 'Effective::Datatable to_json called with a nil view. Please call render_datatable(@datatable) or @datatable.view = view before this method' unless view.present?
|
135
65
|
|
@@ -149,97 +79,16 @@ module Effective
|
|
149
79
|
total_records.to_i == 0
|
150
80
|
end
|
151
81
|
|
152
|
-
def order_name
|
153
|
-
@order_name ||= begin
|
154
|
-
if params[:order] && params[:columns]
|
155
|
-
order_column_index = (params[:order].first[1][:column] rescue '0')
|
156
|
-
(params[:columns][order_column_index] || {})[:name]
|
157
|
-
elsif default_order.present?
|
158
|
-
default_order.keys.first
|
159
|
-
end || table_columns.keys.first
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
def order_direction
|
164
|
-
@order_direction ||= if params[:order].present?
|
165
|
-
params[:order].first[1][:dir] == 'desc' ? 'DESC' : 'ASC'
|
166
|
-
elsif default_order.present?
|
167
|
-
default_order.values.first.to_s.downcase == 'desc' ? 'DESC' : 'ASC'
|
168
|
-
else
|
169
|
-
'ASC'
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
def default_order
|
174
|
-
self.class.instance_variable_get(:@default_order)
|
175
|
-
end
|
176
|
-
|
177
|
-
def default_entries
|
178
|
-
@default_entries ||= begin
|
179
|
-
entries = (self.class.instance_variable_get(:@default_entries).presence || EffectiveDatatables.default_entries)
|
180
|
-
entries = -1 if entries.to_s.downcase == 'all'
|
181
|
-
[10, 25, 50, 100, 250, 1000, -1].include?(entries) ? entries : 25
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
def search_terms
|
186
|
-
@search_terms ||= HashWithIndifferentAccess.new().tap do |terms|
|
187
|
-
if params[:columns].present? # This is an AJAX request from the DataTable
|
188
|
-
(params[:columns] || {}).each do |_, column|
|
189
|
-
next if table_columns[column[:name]].blank? || (column[:search] || {})[:value].blank?
|
190
|
-
|
191
|
-
terms[column[:name]] = column[:search][:value]
|
192
|
-
end
|
193
|
-
else # This is the initial render, and we have to apply default search terms only
|
194
|
-
table_columns.each do |name, values|
|
195
|
-
terms[name] = values[:filter][:selected] if values[:filter][:selected].present?
|
196
|
-
end
|
197
|
-
end
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
# This is here so classes that inherit from Datatables can can override the specific where clauses on a search column
|
202
|
-
def search_column(collection, table_column, search_term)
|
203
|
-
if table_column[:array_column]
|
204
|
-
array_tool.search_column_with_defaults(collection, table_column, search_term)
|
205
|
-
else
|
206
|
-
table_tool.search_column_with_defaults(collection, table_column, search_term)
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
def per_page
|
211
|
-
length = (params[:length].presence || default_entries).to_i
|
212
|
-
|
213
|
-
if length == -1
|
214
|
-
9999999
|
215
|
-
elsif length > 0
|
216
|
-
length
|
217
|
-
else
|
218
|
-
25
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
def per_page=(length)
|
223
|
-
case length
|
224
|
-
when Integer
|
225
|
-
params[:length] = length
|
226
|
-
when :all
|
227
|
-
params[:length] = -1
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
def page
|
232
|
-
params[:start].to_i / per_page + 1
|
233
|
-
end
|
234
|
-
|
235
82
|
def total_records
|
236
83
|
@total_records ||= (
|
237
|
-
if active_record_collection?
|
84
|
+
if active_record_collection?
|
238
85
|
# https://github.com/rails/rails/issues/15331
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
86
|
+
if collection_class.connection.respond_to?(:unprepared_statement)
|
87
|
+
collection_sql = collection_class.connection.unprepared_statement { collection.to_sql }
|
88
|
+
(collection_class.connection.execute("SELECT COUNT(*) FROM (#{collection_sql}) AS datatables_total_count").first['count'] rescue 1).to_i
|
89
|
+
else
|
90
|
+
(collection_class.connection.execute("SELECT COUNT(*) FROM (#{collection.to_sql}) AS datatables_total_count").first['count'] rescue 1).to_i
|
91
|
+
end
|
243
92
|
else
|
244
93
|
collection.size
|
245
94
|
end
|
@@ -268,116 +117,8 @@ module Effective
|
|
268
117
|
@order_direction = nil
|
269
118
|
end
|
270
119
|
|
271
|
-
|
272
120
|
protected
|
273
121
|
|
274
|
-
# So the idea here is that we want to do as much as possible on the database in ActiveRecord
|
275
|
-
# And then run any array_columns through in post-processed results
|
276
|
-
def table_data
|
277
|
-
col = collection
|
278
|
-
|
279
|
-
if active_record_collection?
|
280
|
-
col = table_tool.order(col)
|
281
|
-
col = table_tool.search(col)
|
282
|
-
|
283
|
-
if table_tool.search_terms.present? && array_tool.search_terms.blank?
|
284
|
-
if collection_class.connection.respond_to?(:unprepared_statement)
|
285
|
-
# https://github.com/rails/rails/issues/15331
|
286
|
-
col_sql = collection_class.connection.unprepared_statement { col.to_sql }
|
287
|
-
self.display_records = (collection_class.connection.execute("SELECT COUNT(*) FROM (#{col_sql}) AS datatables_filtered_count").first['count'] rescue 1).to_i
|
288
|
-
else
|
289
|
-
self.display_records = (collection_class.connection.execute("SELECT COUNT(*) FROM (#{col.to_sql}) AS datatables_filtered_count").first['count'] rescue 1).to_i
|
290
|
-
end
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
|
-
if array_tool.search_terms.present?
|
295
|
-
col = self.arrayize(col)
|
296
|
-
col = array_tool.search(col)
|
297
|
-
self.display_records = col.size
|
298
|
-
end
|
299
|
-
|
300
|
-
if array_tool.order_column.present?
|
301
|
-
col = self.arrayize(col)
|
302
|
-
col = array_tool.order(col)
|
303
|
-
end
|
304
|
-
|
305
|
-
self.display_records ||= total_records
|
306
|
-
|
307
|
-
if col.kind_of?(Array)
|
308
|
-
col = array_tool.paginate(col)
|
309
|
-
else
|
310
|
-
col = table_tool.paginate(col)
|
311
|
-
col = self.arrayize(col)
|
312
|
-
end
|
313
|
-
|
314
|
-
col = self.finalize(col)
|
315
|
-
end
|
316
|
-
|
317
|
-
def arrayize(collection)
|
318
|
-
return collection if @arrayized # Prevent the collection from being arrayized more than once
|
319
|
-
@arrayized = true
|
320
|
-
|
321
|
-
# We want to use the render :collection for each column that renders partials
|
322
|
-
rendered = {}
|
323
|
-
table_columns.each do |name, opts|
|
324
|
-
if opts[:partial]
|
325
|
-
locals = {
|
326
|
-
datatable: self,
|
327
|
-
table_column: table_columns[name],
|
328
|
-
controller_namespace: view.controller_path.split('/')[0...-1].map { |path| path.downcase.to_sym if path.present? }.compact,
|
329
|
-
show_action: (opts[:partial_locals] || {})[:show_action],
|
330
|
-
edit_action: (opts[:partial_locals] || {})[:edit_action],
|
331
|
-
destroy_action: (opts[:partial_locals] || {})[:destroy_action]
|
332
|
-
}
|
333
|
-
locals.merge!(opts[:partial_locals]) if opts[:partial_locals]
|
334
|
-
|
335
|
-
rendered[name] = (render(
|
336
|
-
:partial => opts[:partial],
|
337
|
-
:as => opts[:partial_local],
|
338
|
-
:collection => collection,
|
339
|
-
:formats => :html,
|
340
|
-
:locals => locals,
|
341
|
-
:spacer_template => '/effective/datatables/spacer_template',
|
342
|
-
) || '').split('EFFECTIVEDATATABLESSPACER')
|
343
|
-
end
|
344
|
-
end
|
345
|
-
|
346
|
-
collection.each_with_index.map do |obj, index|
|
347
|
-
(display_table_columns || table_columns).map do |name, opts|
|
348
|
-
value = if opts[:partial]
|
349
|
-
rendered[name][index]
|
350
|
-
elsif opts[:block]
|
351
|
-
view.instance_exec(obj, collection, self, &opts[:block])
|
352
|
-
elsif opts[:proc]
|
353
|
-
view.instance_exec(obj, collection, self, &opts[:proc])
|
354
|
-
elsif opts[:type] == :belongs_to
|
355
|
-
val = (obj.send(name) rescue nil).to_s
|
356
|
-
elsif opts[:type] == :obfuscated_id
|
357
|
-
(obj.send(:to_param) rescue nil).to_s
|
358
|
-
elsif opts[:type] == :effective_roles
|
359
|
-
(obj.send(:roles) rescue []).join(', ')
|
360
|
-
else
|
361
|
-
val = (obj.send(name) rescue nil)
|
362
|
-
val = (obj[opts[:array_index]] rescue nil) if val == nil
|
363
|
-
val
|
364
|
-
end
|
365
|
-
|
366
|
-
# Last minute formatting of dates
|
367
|
-
case value
|
368
|
-
when Date
|
369
|
-
value.strftime(EffectiveDatatables.date_format)
|
370
|
-
when Time, DateTime
|
371
|
-
value.strftime(EffectiveDatatables.datetime_format)
|
372
|
-
else
|
373
|
-
value.to_s
|
374
|
-
end
|
375
|
-
end
|
376
|
-
end
|
377
|
-
end
|
378
|
-
|
379
|
-
private
|
380
|
-
|
381
122
|
def params
|
382
123
|
view.try(:params) || HashWithIndifferentAccess.new()
|
383
124
|
end
|
@@ -391,110 +132,11 @@ module Effective
|
|
391
132
|
end
|
392
133
|
|
393
134
|
def active_record_collection?
|
394
|
-
|
395
|
-
@active_record_collection = (collection.ancestors.include?(ActiveRecord::Base) rescue false)
|
396
|
-
else
|
397
|
-
@active_record_collection
|
398
|
-
end
|
399
|
-
end
|
400
|
-
|
401
|
-
def table_columns_with_defaults
|
402
|
-
unless self.class.instance_variable_get(:@table_columns_initialized)
|
403
|
-
self.class.instance_variable_set(:@table_columns_initialized, true)
|
404
|
-
initalize_table_columns(self.class.instance_variable_get(:@table_columns))
|
405
|
-
end
|
406
|
-
|
407
|
-
self.class.instance_variable_get(:@table_columns)
|
408
|
-
end
|
409
|
-
|
410
|
-
def initalize_table_columns(cols)
|
411
|
-
sql_table = (collection.table rescue nil)
|
412
|
-
|
413
|
-
# Here we identify all belongs_to associations and build up a Hash like:
|
414
|
-
# {:user => {:foreign_key => 'user_id', :klass => User}, :order => {:foreign_key => 'order_id', :klass => Effective::Order}}
|
415
|
-
belong_tos = (collection.ancestors.first.reflect_on_all_associations(:belongs_to) rescue []).inject(HashWithIndifferentAccess.new()) do |retval, bt|
|
416
|
-
unless bt.options[:polymorphic]
|
417
|
-
begin
|
418
|
-
klass = bt.klass || bt.foreign_type.sub('_type', '').classify.constantize
|
419
|
-
rescue => e
|
420
|
-
klass = nil
|
421
|
-
end
|
422
|
-
|
423
|
-
retval[bt.name] = {:foreign_key => bt.foreign_key, :klass => klass} if bt.foreign_key.present? && klass.present?
|
424
|
-
end
|
425
|
-
|
426
|
-
retval
|
427
|
-
end
|
428
|
-
|
429
|
-
cols.each_with_index do |(name, _), index|
|
430
|
-
# If this is a belongs_to, add an :if clause specifying a collection scope if
|
431
|
-
if belong_tos.key?(name)
|
432
|
-
cols[name][:if] ||= Proc.new { attributes[belong_tos[name][:foreign_key]].blank? }
|
433
|
-
end
|
434
|
-
|
435
|
-
sql_column = (collection.columns rescue []).find do |column|
|
436
|
-
column.name == name.to_s || (belong_tos.key?(name) && column.name == belong_tos[name][:foreign_key])
|
437
|
-
end
|
438
|
-
|
439
|
-
cols[name][:array_column] ||= false
|
440
|
-
cols[name][:array_index] = index # The index of this column in the collection, regardless of hidden table_columns
|
441
|
-
cols[name][:name] ||= name
|
442
|
-
cols[name][:label] ||= name.titleize
|
443
|
-
cols[name][:column] ||= (sql_table && sql_column) ? "\"#{sql_table.name}\".\"#{sql_column.name}\"" : name
|
444
|
-
cols[name][:width] ||= nil
|
445
|
-
cols[name][:sortable] = true if cols[name][:sortable] == nil
|
446
|
-
cols[name][:type] ||= (belong_tos.key?(name) ? :belongs_to : sql_column.try(:type).presence) || :string
|
447
|
-
cols[name][:class] = "col-#{cols[name][:type]} col-#{name} #{cols[name][:class]}".strip
|
448
|
-
|
449
|
-
# EffectiveObfuscation
|
450
|
-
if name == 'id' && defined?(EffectiveObfuscation) && collection.respond_to?(:deobfuscate)
|
451
|
-
cols[name][:sortable] = false
|
452
|
-
cols[name][:type] = :obfuscated_id
|
453
|
-
end
|
454
|
-
|
455
|
-
# EffectiveRoles, if you do table_column :roles, everything just works
|
456
|
-
if name == 'roles' && defined?(EffectiveRoles) && collection.respond_to?(:with_role)
|
457
|
-
cols[name][:sortable] = true
|
458
|
-
cols[name][:column] = sql_table.present? ? "\"#{sql_table.name}\".\"roles_mask\"" : name
|
459
|
-
cols[name][:type] = :effective_roles
|
460
|
-
end
|
461
|
-
|
462
|
-
if sql_table.present? && sql_column.blank? # This is a SELECT AS column
|
463
|
-
cols[name][:sql_as_column] = true
|
464
|
-
end
|
465
|
-
|
466
|
-
cols[name][:filter] = initialize_table_column_filter(cols[name][:filter], cols[name][:type], belong_tos[name])
|
467
|
-
|
468
|
-
if cols[name][:partial]
|
469
|
-
cols[name][:partial_local] ||= (sql_table.try(:name) || cols[name][:partial].split('/').last(2).first.presence || 'obj').singularize.to_sym
|
470
|
-
end
|
471
|
-
end
|
135
|
+
collection.ancestors.include?(ActiveRecord::Base) rescue false
|
472
136
|
end
|
473
137
|
|
474
|
-
def
|
475
|
-
|
476
|
-
|
477
|
-
filter = {type: filter.to_sym} if filter.kind_of?(String)
|
478
|
-
filter = {} unless filter.kind_of?(Hash)
|
479
|
-
|
480
|
-
# This is a fix for passing filter[:selected] == false, it needs to be 'false'
|
481
|
-
filter[:selected] = filter[:selected].to_s unless filter[:selected].nil?
|
482
|
-
|
483
|
-
case col_type
|
484
|
-
when :belongs_to
|
485
|
-
{
|
486
|
-
type: :select,
|
487
|
-
values: Proc.new { belongs_to[:klass].all.map { |obj| [obj.to_s, obj.id] }.sort { |x, y| x[1] <=> y[1] } }
|
488
|
-
}
|
489
|
-
when :effective_roles
|
490
|
-
{type: :select, values: EffectiveRoles.roles}
|
491
|
-
when :integer
|
492
|
-
{type: :number}
|
493
|
-
when :boolean
|
494
|
-
{type: :boolean, values: [true, false]}
|
495
|
-
else
|
496
|
-
{type: :string}
|
497
|
-
end.merge(filter.symbolize_keys)
|
138
|
+
def array_collection?
|
139
|
+
collection.kind_of?(Array) && collection.first.kind_of?(Array)
|
498
140
|
end
|
499
141
|
|
500
142
|
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# This is extended as class level into Datatable
|
2
|
+
|
3
|
+
module Effective
|
4
|
+
module EffectiveDatatable
|
5
|
+
module Ajax
|
6
|
+
|
7
|
+
# This is for the ColReorder plugin
|
8
|
+
# It sends us a list of columns that are different than our table_columns order
|
9
|
+
# So this method just returns an array of column names, as per ColReorder
|
10
|
+
def display_table_columns
|
11
|
+
if params[:columns].present?
|
12
|
+
HashWithIndifferentAccess.new().tap do |display_columns|
|
13
|
+
params[:columns].each do |_, values|
|
14
|
+
display_columns[values[:name]] = table_columns[values[:name]]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def order_name
|
21
|
+
@order_name ||= begin
|
22
|
+
if params[:order] && params[:columns]
|
23
|
+
order_column_index = (params[:order].first[1][:column] rescue '0')
|
24
|
+
(params[:columns][order_column_index] || {})[:name]
|
25
|
+
elsif @default_order.present?
|
26
|
+
@default_order.keys.first
|
27
|
+
end || table_columns.keys.first
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def order_index
|
32
|
+
(table_columns[order_name][:index] || 0) rescue 0
|
33
|
+
end
|
34
|
+
|
35
|
+
def order_direction
|
36
|
+
@order_direction ||= if params[:order].present?
|
37
|
+
params[:order].first[1][:dir] == 'desc' ? 'DESC' : 'ASC'
|
38
|
+
elsif @default_order.present?
|
39
|
+
@default_order.values.first.to_s.downcase == 'desc' ? 'DESC' : 'ASC'
|
40
|
+
else
|
41
|
+
'ASC'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def display_entries
|
46
|
+
@display_entries ||= begin
|
47
|
+
entries = (@default_entries.presence || EffectiveDatatables.default_entries)
|
48
|
+
entries = -1 if entries.to_s.downcase == 'all'
|
49
|
+
[10, 25, 50, 100, 250, 1000, -1].include?(entries) ? entries : 25
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def search_terms
|
54
|
+
@search_terms ||= HashWithIndifferentAccess.new().tap do |terms|
|
55
|
+
if params[:columns].present? # This is an AJAX request from the DataTable
|
56
|
+
(params[:columns] || {}).each do |_, column|
|
57
|
+
next if table_columns[column[:name]].blank? || (column[:search] || {})[:value].blank?
|
58
|
+
|
59
|
+
terms[column[:name]] = column[:search][:value]
|
60
|
+
end
|
61
|
+
else # This is the initial render, and we have to apply default search terms only
|
62
|
+
table_columns.each do |name, values|
|
63
|
+
terms[name] = values[:filter][:selected] if values[:filter][:selected].present?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# This is here so classes that inherit from Datatables can can override the specific where clauses on a search column
|
70
|
+
def search_column(collection, table_column, search_term)
|
71
|
+
if table_column[:array_column]
|
72
|
+
array_tool.search_column_with_defaults(collection, table_column, search_term)
|
73
|
+
else
|
74
|
+
table_tool.search_column_with_defaults(collection, table_column, search_term)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def per_page
|
79
|
+
length = (params[:length].presence || display_entries).to_i
|
80
|
+
|
81
|
+
if length == -1
|
82
|
+
9999999
|
83
|
+
elsif length > 0
|
84
|
+
length
|
85
|
+
else
|
86
|
+
25
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def per_page=(length)
|
91
|
+
case length
|
92
|
+
when Integer
|
93
|
+
params[:length] = length
|
94
|
+
when :all
|
95
|
+
params[:length] = -1
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def page
|
100
|
+
params[:start].to_i / per_page + 1
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# This is extended as class level into Datatable
|
2
|
+
|
3
|
+
module Effective
|
4
|
+
module EffectiveDatatable
|
5
|
+
module Dsl
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def datatable(&block)
|
9
|
+
define_method('initialize_datatable') { instance_exec(&block) }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Instance Methods inside the datatable do .. end block
|
14
|
+
def default_order(name, direction = :asc)
|
15
|
+
@default_order = {name => direction}
|
16
|
+
end
|
17
|
+
|
18
|
+
def default_entries(entries)
|
19
|
+
@default_entries = entries
|
20
|
+
end
|
21
|
+
|
22
|
+
def table_column(name, options = {}, proc = nil, &block)
|
23
|
+
if block_given?
|
24
|
+
raise "You cannot use partial: ... with the block syntax" if options[:partial]
|
25
|
+
raise "You cannot use proc: ... with the block syntax" if options[:proc]
|
26
|
+
options[:block] = block
|
27
|
+
end
|
28
|
+
raise "You cannot use both partial: ... and proc: ..." if options[:partial] && options[:proc]
|
29
|
+
|
30
|
+
self.class.send(:attr_accessor, name)
|
31
|
+
(@table_columns ||= HashWithIndifferentAccess.new())[name] = options
|
32
|
+
end
|
33
|
+
|
34
|
+
def array_column(name, options = {}, proc = nil, &block)
|
35
|
+
table_column(name, options.merge!(array_column: true), proc, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
def actions_column(options = {}, proc = nil, &block)
|
39
|
+
show = options.fetch(:show, false)
|
40
|
+
edit = options.fetch(:edit, true)
|
41
|
+
destroy = options.fetch(:destroy, true)
|
42
|
+
name = options.fetch(:name, 'actions')
|
43
|
+
|
44
|
+
opts = {
|
45
|
+
sortable: false,
|
46
|
+
filter: false,
|
47
|
+
partial_local: :resource,
|
48
|
+
partial_locals: { show_action: show, edit_action: edit, destroy_action: destroy }
|
49
|
+
}
|
50
|
+
opts[:partial] = '/effective/datatables/actions_column' unless (block_given? || proc.present?)
|
51
|
+
|
52
|
+
table_column(name, opts, proc, &block)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# This is extended as class level into Datatable
|
2
|
+
|
3
|
+
module Effective
|
4
|
+
module EffectiveDatatable
|
5
|
+
module Options
|
6
|
+
|
7
|
+
def initialize_options
|
8
|
+
@table_columns = initialize_column_options(@table_columns)
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def initialize_column_options(cols)
|
14
|
+
sql_table = (collection.table rescue nil)
|
15
|
+
|
16
|
+
# Here we identify all belongs_to associations and build up a Hash like:
|
17
|
+
# {user: {foreign_key: 'user_id', klass: User}, order: {foreign_key: 'order_id', klass: Effective::Order}}
|
18
|
+
belong_tos = (collection.ancestors.first.reflect_on_all_associations(:belongs_to) rescue []).inject(HashWithIndifferentAccess.new()) do |retval, bt|
|
19
|
+
unless bt.options[:polymorphic]
|
20
|
+
begin
|
21
|
+
klass = bt.klass || bt.foreign_type.sub('_type', '').classify.constantize
|
22
|
+
rescue => e
|
23
|
+
klass = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
retval[bt.name] = {foreign_key: bt.foreign_key, klass: klass} if bt.foreign_key.present? && klass.present?
|
27
|
+
end
|
28
|
+
|
29
|
+
retval
|
30
|
+
end
|
31
|
+
|
32
|
+
table_columns = cols.each_with_index do |(name, _), index|
|
33
|
+
# If this is a belongs_to, add an :if clause specifying a collection scope if
|
34
|
+
if belong_tos.key?(name)
|
35
|
+
cols[name][:if] ||= Proc.new { attributes[belong_tos[name][:foreign_key]].blank? }
|
36
|
+
end
|
37
|
+
|
38
|
+
sql_column = (collection.columns rescue []).find do |column|
|
39
|
+
column.name == name.to_s || (belong_tos.key?(name) && column.name == belong_tos[name][:foreign_key])
|
40
|
+
end
|
41
|
+
|
42
|
+
cols[name][:array_column] ||= false
|
43
|
+
cols[name][:array_index] = index # The index of this column in the collection, regardless of hidden table_columns
|
44
|
+
cols[name][:name] ||= name
|
45
|
+
cols[name][:label] ||= name.titleize
|
46
|
+
cols[name][:column] ||= (sql_table && sql_column) ? "\"#{sql_table.name}\".\"#{sql_column.name}\"" : name
|
47
|
+
cols[name][:width] ||= nil
|
48
|
+
cols[name][:sortable] = true if cols[name][:sortable] == nil
|
49
|
+
cols[name][:type] ||= (belong_tos.key?(name) ? :belongs_to : sql_column.try(:type).presence) || :string
|
50
|
+
cols[name][:class] = "col-#{cols[name][:type]} col-#{name} #{cols[name][:class]}".strip
|
51
|
+
|
52
|
+
# EffectiveObfuscation
|
53
|
+
if name == 'id' && defined?(EffectiveObfuscation) && collection.respond_to?(:deobfuscate)
|
54
|
+
cols[name][:sortable] = false
|
55
|
+
cols[name][:type] = :obfuscated_id
|
56
|
+
end
|
57
|
+
|
58
|
+
# EffectiveRoles, if you do table_column :roles, everything just works
|
59
|
+
if name == 'roles' && defined?(EffectiveRoles) && collection.respond_to?(:with_role)
|
60
|
+
cols[name][:sortable] = true
|
61
|
+
cols[name][:column] = sql_table.present? ? "\"#{sql_table.name}\".\"roles_mask\"" : name
|
62
|
+
cols[name][:type] = :effective_roles
|
63
|
+
end
|
64
|
+
|
65
|
+
if sql_table.present? && sql_column.blank? # This is a SELECT AS column
|
66
|
+
cols[name][:sql_as_column] = true
|
67
|
+
end
|
68
|
+
|
69
|
+
cols[name][:filter] = initialize_table_column_filter(cols[name][:filter], cols[name][:type], belong_tos[name])
|
70
|
+
|
71
|
+
if cols[name][:partial]
|
72
|
+
cols[name][:partial_local] ||= (sql_table.try(:name) || cols[name][:partial].split('/').last(2).first.presence || 'obj').singularize.to_sym
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# After everything is initialized
|
77
|
+
# Compute any col[:if] and assign an index
|
78
|
+
table_columns.select do |_, col|
|
79
|
+
col[:if] == nil || (col[:if].respond_to?(:call) ? (view || self).instance_exec(&col[:if]) : col[:if])
|
80
|
+
end.each_with_index { |(_, col), index| col[:index] = index }
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
def initialize_table_column_filter(filter, col_type, belongs_to)
|
85
|
+
return {type: :null} if filter == false
|
86
|
+
|
87
|
+
filter = {type: filter.to_sym} if filter.kind_of?(String)
|
88
|
+
filter = {} unless filter.kind_of?(Hash)
|
89
|
+
|
90
|
+
# This is a fix for passing filter[:selected] == false, it needs to be 'false'
|
91
|
+
filter[:selected] = filter[:selected].to_s unless filter[:selected].nil?
|
92
|
+
|
93
|
+
case col_type
|
94
|
+
when :belongs_to
|
95
|
+
{
|
96
|
+
type: :select,
|
97
|
+
values: Proc.new { belongs_to[:klass].all.map { |obj| [obj.to_s, obj.id] }.sort { |x, y| x[1] <=> y[1] } }
|
98
|
+
}
|
99
|
+
when :effective_roles
|
100
|
+
{type: :select, values: EffectiveRoles.roles}
|
101
|
+
when :integer
|
102
|
+
{type: :number}
|
103
|
+
when :boolean
|
104
|
+
{type: :boolean, values: [true, false]}
|
105
|
+
else
|
106
|
+
{type: :string}
|
107
|
+
end.merge(filter.symbolize_keys)
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# This is extended as class level into Datatable
|
2
|
+
|
3
|
+
module Effective
|
4
|
+
module EffectiveDatatable
|
5
|
+
module Rendering
|
6
|
+
|
7
|
+
def finalize(collection) # Override me if you like
|
8
|
+
collection
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
# So the idea here is that we want to do as much as possible on the database in ActiveRecord
|
14
|
+
# And then run any array_columns through in post-processed results
|
15
|
+
def table_data
|
16
|
+
col = collection
|
17
|
+
|
18
|
+
if active_record_collection?
|
19
|
+
col = table_tool.order(col)
|
20
|
+
col = table_tool.search(col)
|
21
|
+
|
22
|
+
if table_tool.search_terms.present? && array_tool.search_terms.blank?
|
23
|
+
if collection_class.connection.respond_to?(:unprepared_statement)
|
24
|
+
# https://github.com/rails/rails/issues/15331
|
25
|
+
col_sql = collection_class.connection.unprepared_statement { col.to_sql }
|
26
|
+
self.display_records = (collection_class.connection.execute("SELECT COUNT(*) FROM (#{col_sql}) AS datatables_filtered_count").first['count'] rescue 1).to_i
|
27
|
+
else
|
28
|
+
self.display_records = (collection_class.connection.execute("SELECT COUNT(*) FROM (#{col.to_sql}) AS datatables_filtered_count").first['count'] rescue 1).to_i
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
if array_tool.search_terms.present?
|
34
|
+
col = self.arrayize(col)
|
35
|
+
col = array_tool.search(col)
|
36
|
+
self.display_records = col.size
|
37
|
+
end
|
38
|
+
|
39
|
+
if array_tool.order_column.present?
|
40
|
+
col = self.arrayize(col)
|
41
|
+
col = array_tool.order(col)
|
42
|
+
end
|
43
|
+
|
44
|
+
self.display_records ||= total_records
|
45
|
+
|
46
|
+
if col.kind_of?(Array)
|
47
|
+
col = array_tool.paginate(col)
|
48
|
+
else
|
49
|
+
col = table_tool.paginate(col)
|
50
|
+
col = self.arrayize(col)
|
51
|
+
end
|
52
|
+
|
53
|
+
col = self.finalize(col)
|
54
|
+
end
|
55
|
+
|
56
|
+
def arrayize(collection)
|
57
|
+
return collection if @arrayized # Prevent the collection from being arrayized more than once
|
58
|
+
@arrayized = true
|
59
|
+
|
60
|
+
# We want to use the render :collection for each column that renders partials
|
61
|
+
rendered = {}
|
62
|
+
table_columns.each do |name, opts|
|
63
|
+
if opts[:partial]
|
64
|
+
locals = {
|
65
|
+
datatable: self,
|
66
|
+
table_column: table_columns[name],
|
67
|
+
controller_namespace: view.controller_path.split('/')[0...-1].map { |path| path.downcase.to_sym if path.present? }.compact,
|
68
|
+
show_action: (opts[:partial_locals] || {})[:show_action],
|
69
|
+
edit_action: (opts[:partial_locals] || {})[:edit_action],
|
70
|
+
destroy_action: (opts[:partial_locals] || {})[:destroy_action]
|
71
|
+
}
|
72
|
+
locals.merge!(opts[:partial_locals]) if opts[:partial_locals]
|
73
|
+
|
74
|
+
rendered[name] = (render(
|
75
|
+
:partial => opts[:partial],
|
76
|
+
:as => opts[:partial_local],
|
77
|
+
:collection => collection,
|
78
|
+
:formats => :html,
|
79
|
+
:locals => locals,
|
80
|
+
:spacer_template => '/effective/datatables/spacer_template',
|
81
|
+
) || '').split('EFFECTIVEDATATABLESSPACER')
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
collection.each_with_index.map do |obj, index|
|
86
|
+
(display_table_columns || table_columns).map do |name, opts|
|
87
|
+
value = if opts[:partial]
|
88
|
+
rendered[name][index]
|
89
|
+
elsif opts[:block]
|
90
|
+
view.instance_exec(obj, collection, self, &opts[:block])
|
91
|
+
elsif opts[:proc]
|
92
|
+
view.instance_exec(obj, collection, self, &opts[:proc])
|
93
|
+
elsif opts[:type] == :belongs_to
|
94
|
+
val = (obj.send(name) rescue nil).to_s
|
95
|
+
elsif opts[:type] == :obfuscated_id
|
96
|
+
(obj.send(:to_param) rescue nil).to_s
|
97
|
+
elsif opts[:type] == :effective_roles
|
98
|
+
(obj.send(:roles) rescue []).join(', ')
|
99
|
+
else
|
100
|
+
val = (obj.send(name) rescue nil)
|
101
|
+
val = (obj[opts[:array_index]] rescue nil) if val == nil
|
102
|
+
val
|
103
|
+
end
|
104
|
+
|
105
|
+
# Last minute formatting of dates
|
106
|
+
case value
|
107
|
+
when Date
|
108
|
+
value.strftime(EffectiveDatatables.date_format)
|
109
|
+
when Time, DateTime
|
110
|
+
value.strftime(EffectiveDatatables.datetime_format)
|
111
|
+
else
|
112
|
+
value.to_s
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end # / Rendering
|
119
|
+
end
|
120
|
+
end
|
@@ -10,5 +10,5 @@
|
|
10
10
|
|
11
11
|
- if destroy_action
|
12
12
|
- url = (polymorphic_path([*controller_namespace, resource]) rescue nil) || (polymorphic_path(resource) rescue nil)
|
13
|
-
%a{href: (
|
13
|
+
%a{href: (url || '#'), title: 'Delete', data: {method: :delete, confirm: 'Are you sure?'}}
|
14
14
|
%span.glyphicon.glyphicon-trash
|
@@ -14,7 +14,7 @@
|
|
14
14
|
'non-visible' => datatable_non_visible(datatable),
|
15
15
|
'widths' => datatable_widths(datatable),
|
16
16
|
'default-order' => datatable_default_order(datatable),
|
17
|
-
'
|
17
|
+
'display-entries' => datatable.display_entries,
|
18
18
|
'display-records' => (datatable.to_json[:recordsFiltered] || 0),
|
19
19
|
'total-records' => (datatable.to_json[:recordsTotal] || 0),
|
20
20
|
'column-classes' => datatable_column_classes(datatable)
|
data/lib/effective_datatables.rb
CHANGED
@@ -20,15 +20,4 @@ module EffectiveDatatables
|
|
20
20
|
true
|
21
21
|
end
|
22
22
|
|
23
|
-
def self.datatables
|
24
|
-
Rails.env.development? ? read_datatables : (@@datatables ||= read_datatables)
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def self.read_datatables
|
30
|
-
Rails.application.eager_load! unless Rails.configuration.cache_classes
|
31
|
-
Effective::Datatable.descendants.map { |klass| klass }.compact
|
32
|
-
end
|
33
|
-
|
34
23
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: effective_datatables
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Code and Effect
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-07-
|
11
|
+
date: 2015-07-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -123,6 +123,10 @@ files:
|
|
123
123
|
- app/models/effective/active_record_datatable_tool.rb
|
124
124
|
- app/models/effective/array_datatable_tool.rb
|
125
125
|
- app/models/effective/datatable.rb
|
126
|
+
- app/models/effective/effective_datatable/ajax.rb
|
127
|
+
- app/models/effective/effective_datatable/dsl.rb
|
128
|
+
- app/models/effective/effective_datatable/options.rb
|
129
|
+
- app/models/effective/effective_datatable/rendering.rb
|
126
130
|
- app/views/effective/datatables/_actions_column.html.haml
|
127
131
|
- app/views/effective/datatables/_datatable.html.haml
|
128
132
|
- app/views/effective/datatables/_spacer_template.html
|