effective_datatables 1.8.3 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|