effective_datatables 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +581 -0
- data/Rakefile +23 -0
- data/app/assets/images/effective_datatables/copy_csv_xls_pdf.swf +0 -0
- data/app/assets/javascripts/effective_datatables.bootstrap2.js +11 -0
- data/app/assets/javascripts/effective_datatables.js +12 -0
- data/app/assets/javascripts/effective_datatables/initialize.js.coffee +65 -0
- data/app/assets/javascripts/vendor/jquery.dataTables.columnFilter.js +829 -0
- data/app/assets/stylesheets/effective_datatables.bootstrap2.css.scss +8 -0
- data/app/assets/stylesheets/effective_datatables.css.scss +8 -0
- data/app/assets/stylesheets/effective_datatables/_overrides.scss +72 -0
- data/app/controllers/effective/datatables_controller.rb +36 -0
- data/app/helpers/effective_datatables_helper.rb +74 -0
- data/app/models/effective/access_denied.rb +17 -0
- data/app/models/effective/active_record_datatable_tool.rb +87 -0
- data/app/models/effective/array_datatable_tool.rb +66 -0
- data/app/models/effective/datatable.rb +352 -0
- data/app/views/effective/datatables/_datatable.html.haml +11 -0
- data/app/views/effective/datatables/_spacer_template.html +1 -0
- data/config/routes.rb +11 -0
- data/lib/effective_datatables.rb +32 -0
- data/lib/effective_datatables/engine.rb +20 -0
- data/lib/effective_datatables/version.rb +3 -0
- data/lib/generators/effective_datatables/install_generator.rb +17 -0
- data/lib/generators/templates/README +1 -0
- data/lib/generators/templates/effective_datatables.rb +23 -0
- data/lib/tasks/effective_datatables_tasks.rake +17 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +59 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/schema.rb +16 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +17 -0
- data/spec/dummy/log/test.log +1 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/effective_datatables_spec.rb +7 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/support/factories.rb +1 -0
- metadata +217 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
.dataTables_wrapper { // This is the Header
|
2
|
+
overflow-x: auto;
|
3
|
+
|
4
|
+
.row {
|
5
|
+
margin-left: 0px;
|
6
|
+
margin-right: 0px;
|
7
|
+
}
|
8
|
+
|
9
|
+
table.dataTable.sorting-hidden {
|
10
|
+
thead {
|
11
|
+
.sorting_asc { background: none; cursor: default; }
|
12
|
+
.sorting_desc { background: none; cursor: default; }
|
13
|
+
.sorting { cursor: default; }
|
14
|
+
|
15
|
+
tr, th {
|
16
|
+
padding-left: 8px;
|
17
|
+
padding-right: 8px;
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
table.dataTable {
|
23
|
+
border-collapse: collapse;
|
24
|
+
box-sizing: border-box;
|
25
|
+
|
26
|
+
.form-inline, .form-control { width: 100%; }
|
27
|
+
thead, tr, th { padding-left: 6px; }
|
28
|
+
}
|
29
|
+
|
30
|
+
.dataTables_processing {
|
31
|
+
height: 60px;
|
32
|
+
}
|
33
|
+
|
34
|
+
.dataTables_paginate {
|
35
|
+
overflow: hidden;
|
36
|
+
|
37
|
+
.pagination {
|
38
|
+
padding: 0px;
|
39
|
+
|
40
|
+
.paginate_button {
|
41
|
+
padding: 0px;
|
42
|
+
|
43
|
+
&:hover {
|
44
|
+
background: none;
|
45
|
+
border: 1px solid transparent;
|
46
|
+
}
|
47
|
+
|
48
|
+
&:active {
|
49
|
+
background: none;
|
50
|
+
box-shadow: none;
|
51
|
+
outline: none;
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
.DTTT_container {
|
58
|
+
float: none;
|
59
|
+
width: 190px;
|
60
|
+
margin: auto;
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
ul.ColVis_collection {
|
65
|
+
width: 240px;
|
66
|
+
}
|
67
|
+
|
68
|
+
div.DTTT_print_info {
|
69
|
+
h6 { color: black; }
|
70
|
+
p { color: black; }
|
71
|
+
}
|
72
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Effective
|
2
|
+
class DatatablesController < ApplicationController
|
3
|
+
skip_log_page_views if defined?(EffectiveLogging)
|
4
|
+
|
5
|
+
def show
|
6
|
+
@datatable = Effective::Datatable.find(params[:id], params[:attributes])
|
7
|
+
@datatable.view = view_context if @datatable.present?
|
8
|
+
|
9
|
+
EffectiveDatatables.authorized?(self, :index, @datatable.collection_class)
|
10
|
+
|
11
|
+
respond_to do |format|
|
12
|
+
format.html
|
13
|
+
format.json {
|
14
|
+
if Rails.env.production?
|
15
|
+
render :json => (@datatable.to_json rescue error_json)
|
16
|
+
else
|
17
|
+
render :json => @datatable.to_json
|
18
|
+
end
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def error_json
|
27
|
+
{
|
28
|
+
:sEcho => params[:sEcho].to_i,
|
29
|
+
:aaData => [],
|
30
|
+
:iTotalRecords => 0,
|
31
|
+
:iTotalDisplayRecords => 0,
|
32
|
+
}.to_json
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module EffectiveDatatablesHelper
|
2
|
+
def render_datatable(datatable, opts = {})
|
3
|
+
datatable.view = self
|
4
|
+
locals = {:style => :full, :filterable => true, :sortable => true, :table_class => 'table-bordered table-striped'}.merge(opts)
|
5
|
+
locals[:table_class] = 'sorting-hidden ' + locals[:table_class].to_s if locals[:sortable] == false
|
6
|
+
|
7
|
+
render :partial => 'effective/datatables/datatable', :locals => locals.merge(:datatable => datatable)
|
8
|
+
end
|
9
|
+
|
10
|
+
def render_simple_datatable(datatable, opts = {})
|
11
|
+
datatable.view = self
|
12
|
+
locals = {:style => :simple, :filterable => false, :sortable => false, :table_class => ''}.merge(opts)
|
13
|
+
locals[:table_class] = 'sorting-hidden ' + locals[:table_class].to_s if locals[:sortable] == false
|
14
|
+
|
15
|
+
render :partial => 'effective/datatables/datatable', :locals => locals.merge(:datatable => datatable)
|
16
|
+
end
|
17
|
+
|
18
|
+
def datatable_filter(datatable, filterable = true)
|
19
|
+
return false unless filterable
|
20
|
+
|
21
|
+
filters = datatable.table_columns.values.map { |options, _| options[:filter] || {:type => 'null'} }
|
22
|
+
|
23
|
+
# Process any Procs
|
24
|
+
filters.each do |filter|
|
25
|
+
if filter[:values].respond_to?(:call)
|
26
|
+
filter[:values] = filter[:values].call()
|
27
|
+
|
28
|
+
if filter[:values].kind_of?(ActiveRecord::Relation) || (filter[:values].kind_of?(Array) && filter[:values].first.kind_of?(ActiveRecord::Base))
|
29
|
+
filter[:values] = filter[:values].map { |obj| [obj.id, obj.to_s] }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
filters.to_json()
|
35
|
+
end
|
36
|
+
|
37
|
+
def datatable_non_sortable(datatable, sortable = true)
|
38
|
+
[].tap do |nonsortable|
|
39
|
+
datatable.table_columns.values.each_with_index { |options, x| nonsortable << x if options[:sortable] == false || sortable == false }
|
40
|
+
end.to_json()
|
41
|
+
end
|
42
|
+
|
43
|
+
def datatable_non_visible(datatable)
|
44
|
+
[].tap do |nonvisible|
|
45
|
+
datatable.table_columns.values.each_with_index do |options, x|
|
46
|
+
visible = (options[:visible].respond_to?(:call) ? datatable.instance_exec(&options[:visible]) : options[:visible])
|
47
|
+
nonvisible << x if visible == false
|
48
|
+
end
|
49
|
+
end.to_json()
|
50
|
+
end
|
51
|
+
|
52
|
+
def datatable_default_order(datatable)
|
53
|
+
[
|
54
|
+
if datatable.default_order.present?
|
55
|
+
index = (datatable.table_columns.values.find { |options| options[:name] == datatable.default_order.keys.first.to_s }[:index] rescue nil)
|
56
|
+
[index, datatable.default_order.values.first] if index.present?
|
57
|
+
end || [0, 'asc']
|
58
|
+
].to_json()
|
59
|
+
end
|
60
|
+
|
61
|
+
def datatable_widths(datatable)
|
62
|
+
datatable.table_columns.values.map { |options| {'sWidth' => options[:width]} if options[:width] }.to_json()
|
63
|
+
end
|
64
|
+
|
65
|
+
def datatables_admin_path?
|
66
|
+
(attributes[:admin_path] || request.referer.downcase.include?('/admin/')) rescue false
|
67
|
+
end
|
68
|
+
|
69
|
+
# TODO: Improve on this
|
70
|
+
def datatables_active_admin_path?
|
71
|
+
attributes[:active_admin_path] rescue false
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
unless defined?(Effective::AccessDenied)
|
2
|
+
module Effective
|
3
|
+
class AccessDenied < StandardError
|
4
|
+
attr_reader :action, :subject
|
5
|
+
|
6
|
+
def initialize(message = nil, action = nil, subject = nil)
|
7
|
+
@message = message
|
8
|
+
@action = action
|
9
|
+
@subject = subject
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
@message || I18n.t(:'unauthorized.default', :default => 'Access Denied')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Effective
|
2
|
+
class ActiveRecordDatatableTool
|
3
|
+
attr_accessor :table_columns
|
4
|
+
|
5
|
+
delegate :order_column_index, :order_direction, :page, :per_page, :search_column, :to => :@datatable
|
6
|
+
|
7
|
+
def initialize(datatable, table_columns)
|
8
|
+
@datatable = datatable
|
9
|
+
@table_columns = table_columns
|
10
|
+
end
|
11
|
+
|
12
|
+
def order_column
|
13
|
+
@order_column ||= table_columns.find { |_, values| values[:index] == order_column_index }.try(:second) # This pulls out the values
|
14
|
+
end
|
15
|
+
|
16
|
+
def search_terms
|
17
|
+
@search_terms ||= @datatable.search_terms.select { |name, search_term| table_columns.key?(name) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def order(collection)
|
21
|
+
if order_column.present?
|
22
|
+
collection.order("#{order_column[:column]} #{order_direction} NULLS #{order_direction == 'ASC' ? 'LAST' : 'FIRST'}")
|
23
|
+
else
|
24
|
+
collection
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def search(collection)
|
29
|
+
search_terms.each do |name, search_term|
|
30
|
+
column_search = search_column(collection, table_columns[name], search_term)
|
31
|
+
raise 'search_column must return an ActiveRecord::Relation object' unless column_search.kind_of?(ActiveRecord::Relation)
|
32
|
+
collection = column_search
|
33
|
+
end
|
34
|
+
collection
|
35
|
+
end
|
36
|
+
|
37
|
+
def search_column_with_defaults(collection, table_column, term)
|
38
|
+
column = table_column[:column]
|
39
|
+
|
40
|
+
case table_column[:type]
|
41
|
+
when :string, :text
|
42
|
+
if table_column[:filter][:type] == :select && table_column[:filter][:fuzzy] != true
|
43
|
+
collection.where("#{column} = :term", :term => term)
|
44
|
+
else
|
45
|
+
collection.where("#{column} ILIKE :term", :term => "%#{term}%")
|
46
|
+
end
|
47
|
+
when :datetime
|
48
|
+
begin
|
49
|
+
digits = term.scan(/(\d+)/).flatten.map(&:to_i)
|
50
|
+
start_at = Time.zone.local(*digits)
|
51
|
+
|
52
|
+
case digits.length
|
53
|
+
when 1 # Year
|
54
|
+
end_at = start_at.end_of_year
|
55
|
+
when 2 # Year-Month
|
56
|
+
end_at = start_at.end_of_month
|
57
|
+
when 3 # Year-Month-Day
|
58
|
+
end_at = start_at.end_of_day
|
59
|
+
when 4 # Year-Month-Day Hour
|
60
|
+
end_at = start_at.end_of_hour
|
61
|
+
when 5 # Year-Month-Day Hour-Minute
|
62
|
+
end_at = start_at.end_of_minute
|
63
|
+
when 6
|
64
|
+
end_at = start_at + 1.second
|
65
|
+
else
|
66
|
+
end_at = start_at
|
67
|
+
end
|
68
|
+
|
69
|
+
collection.where("#{column} >= :start_at AND #{column} <= :end_at", :start_at => start_at, :end_at => end_at)
|
70
|
+
rescue => e
|
71
|
+
collection
|
72
|
+
end
|
73
|
+
when :integer
|
74
|
+
collection.where("#{column} = :term", :term => term.to_i)
|
75
|
+
when :year
|
76
|
+
collection.where("EXTRACT(YEAR FROM #{column}) = :term", :term => term.to_i)
|
77
|
+
else
|
78
|
+
collection.where("#{column} = :term", :term => term)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def paginate(collection)
|
83
|
+
collection.page(page).per(per_page)
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Effective
|
2
|
+
# The collection is an Array of Arrays
|
3
|
+
class ArrayDatatableTool
|
4
|
+
attr_accessor :table_columns
|
5
|
+
|
6
|
+
delegate :order_column_index, :order_direction, :page, :per_page, :search_column, :to => :@datatable
|
7
|
+
|
8
|
+
def initialize(datatable, table_columns)
|
9
|
+
@datatable = datatable
|
10
|
+
@table_columns = table_columns
|
11
|
+
end
|
12
|
+
|
13
|
+
def order_column
|
14
|
+
@order_column ||= table_columns.find { |_, values| values[:index] == order_column_index }.try(:second) # This pulls out the values
|
15
|
+
end
|
16
|
+
|
17
|
+
def search_terms
|
18
|
+
@search_terms ||= @datatable.search_terms.select { |name, search_term| table_columns.key?(name) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def order(collection)
|
22
|
+
if order_column.present?
|
23
|
+
if order_direction == 'ASC'
|
24
|
+
collection.sort! { |x, y| x[order_column[:index]] <=> y[order_column[:index]] }
|
25
|
+
else
|
26
|
+
collection.sort! { |x, y| y[order_column[:index]] <=> x[order_column[:index]] }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
collection
|
31
|
+
end
|
32
|
+
|
33
|
+
def search(collection)
|
34
|
+
search_terms.each do |name, search_term|
|
35
|
+
column_search = search_column(collection, table_columns[name], search_term)
|
36
|
+
raise 'search_column must return an Array object' unless column_search.kind_of?(Array)
|
37
|
+
collection = column_search
|
38
|
+
end
|
39
|
+
collection
|
40
|
+
end
|
41
|
+
|
42
|
+
def search_column_with_defaults(collection, table_column, search_term)
|
43
|
+
search_term = search_term.downcase
|
44
|
+
|
45
|
+
collection.select! do |row|
|
46
|
+
value = row[table_column[:index]].to_s.downcase
|
47
|
+
|
48
|
+
if table_column[:filter][:type] == :select && table_column[:filter][:fuzzy] != true
|
49
|
+
value == search_term
|
50
|
+
else
|
51
|
+
value.include?(search_term)
|
52
|
+
end
|
53
|
+
end || collection
|
54
|
+
end
|
55
|
+
|
56
|
+
def paginate(collection)
|
57
|
+
Kaminari.paginate_array(collection).page(page).per(per_page)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# [
|
63
|
+
# [1, 'title 1'],
|
64
|
+
# [2, 'title 2'],
|
65
|
+
# [3, 'title 3']
|
66
|
+
# ]
|
@@ -0,0 +1,352 @@
|
|
1
|
+
module Effective
|
2
|
+
class Datatable
|
3
|
+
attr_accessor :total_records, :display_records, :view, :attributes
|
4
|
+
|
5
|
+
delegate :render, :link_to, :mail_to, :to => :@view
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def all
|
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
|
+
(@table_columns ||= HashWithIndifferentAccess.new())[name] = options
|
26
|
+
end
|
27
|
+
|
28
|
+
def table_columns(*names)
|
29
|
+
names.each { |name| table_column(name) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def array_column(name, options = {}, proc = nil, &block)
|
33
|
+
table_column(name, options.merge({:array_column => true}), proc, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def array_columns(*names)
|
37
|
+
names.each { |name| array_column(name) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def default_order(name, direction = :asc)
|
41
|
+
@default_order = {name => direction}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(*args)
|
46
|
+
if args.present?
|
47
|
+
raise 'Effective::Datatable.new() can only be called with a Hash like arguments' unless args.first.kind_of?(Hash)
|
48
|
+
args.first.each { |k, v| self.attributes[k] = v }
|
49
|
+
end
|
50
|
+
|
51
|
+
unless active_record_collection? || (collection.kind_of?(Array) && collection.first.kind_of?(Array))
|
52
|
+
raise "Unsupported collection type. Should be ActiveRecord class, ActiveRecord relation, or an Array of Arrays [[1, 'something'], [2, 'something else']]"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Any attributes set on initialize will be echoed back and available to the class
|
57
|
+
def attributes
|
58
|
+
@attributes ||= HashWithIndifferentAccess.new()
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_param
|
62
|
+
self.class.name.underscore.parameterize
|
63
|
+
end
|
64
|
+
|
65
|
+
def collection
|
66
|
+
raise "You must define a collection. Something like an ActiveRecord User.all or an Array of Arrays [[1, 'something'], [2, 'something else']]"
|
67
|
+
end
|
68
|
+
|
69
|
+
def collection_class
|
70
|
+
collection.respond_to?(:klass) ? collection.klass : self.class
|
71
|
+
end
|
72
|
+
|
73
|
+
def finalize(collection) # Override me if you like
|
74
|
+
collection
|
75
|
+
end
|
76
|
+
|
77
|
+
# Select only col[:if] == true columns, and then set the col[:index] accordingly
|
78
|
+
def table_columns
|
79
|
+
@table_columns ||= table_columns_with_defaults().select do |_, col|
|
80
|
+
col[:if] == nil || (col[:if].respond_to?(:call) ? (view || self).instance_exec(&col[:if]) : col[:if])
|
81
|
+
end.each_with_index { |(_, col), index| col[:index] = index }
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_json(options = {})
|
85
|
+
{
|
86
|
+
:sEcho => params[:sEcho].to_i,
|
87
|
+
:aaData => table_data || [],
|
88
|
+
:iTotalRecords => (
|
89
|
+
unless total_records.kind_of?(Hash)
|
90
|
+
total_records.to_i
|
91
|
+
else
|
92
|
+
(total_records.keys.map(&:first).uniq.count rescue 1)
|
93
|
+
end),
|
94
|
+
:iTotalDisplayRecords => (
|
95
|
+
unless display_records.kind_of?(Hash)
|
96
|
+
display_records.to_i
|
97
|
+
else
|
98
|
+
(display_records.keys.map(&:first).uniq.count rescue 1)
|
99
|
+
end)
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
# Wish these were protected
|
104
|
+
|
105
|
+
def order_column_index
|
106
|
+
params[:iSortCol_0].to_i
|
107
|
+
end
|
108
|
+
|
109
|
+
def order_direction
|
110
|
+
params[:sSortDir_0].try(:downcase) == 'desc' ? 'DESC' : 'ASC'
|
111
|
+
end
|
112
|
+
|
113
|
+
def default_order
|
114
|
+
self.class.instance_variable_get(:@default_order)
|
115
|
+
end
|
116
|
+
|
117
|
+
def search_terms
|
118
|
+
@search_terms ||= HashWithIndifferentAccess.new().tap do |terms|
|
119
|
+
table_columns.keys.each_with_index do |col, x|
|
120
|
+
unless (params["sVisible_#{x}"] == 'false' && table_columns[col][:filter][:when_hidden] != true)
|
121
|
+
terms[col] = params["sSearch_#{x}"] if params["sSearch_#{x}"].present?
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# This is here so classes that inherit from Datatables can can override the specific where clauses on a search column
|
128
|
+
def search_column(collection, table_column, search_term)
|
129
|
+
if table_column[:array_column]
|
130
|
+
array_tool.search_column_with_defaults(collection, table_column, search_term)
|
131
|
+
else
|
132
|
+
table_tool.search_column_with_defaults(collection, table_column, search_term)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def per_page
|
137
|
+
length = params[:iDisplayLength].to_i
|
138
|
+
|
139
|
+
if length == -1
|
140
|
+
9999999
|
141
|
+
elsif length > 0
|
142
|
+
length
|
143
|
+
else
|
144
|
+
10
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def page
|
149
|
+
params[:iDisplayStart].to_i / per_page + 1
|
150
|
+
end
|
151
|
+
|
152
|
+
def view=(view_context)
|
153
|
+
@view = view_context
|
154
|
+
@view.formats = [:html]
|
155
|
+
@view.class.send(:attr_accessor, :attributes)
|
156
|
+
@view.attributes = self.attributes
|
157
|
+
end
|
158
|
+
|
159
|
+
protected
|
160
|
+
|
161
|
+
# So the idea here is that we want to do as much as possible on the database in ActiveRecord
|
162
|
+
# And then run any array_columns through in post-processed results
|
163
|
+
def table_data
|
164
|
+
col = collection
|
165
|
+
|
166
|
+
if active_record_collection?
|
167
|
+
self.total_records = (col.select('*').reorder(nil).count rescue 1)
|
168
|
+
|
169
|
+
col = table_tool.order(col)
|
170
|
+
col = table_tool.search(col)
|
171
|
+
|
172
|
+
if table_tool.search_terms.present? && array_tool.search_terms.blank?
|
173
|
+
self.display_records = (col.select('*').reorder(nil).count rescue 1)
|
174
|
+
end
|
175
|
+
else
|
176
|
+
self.total_records = col.size
|
177
|
+
end
|
178
|
+
|
179
|
+
if array_tool.search_terms.present?
|
180
|
+
col = self.arrayize(col)
|
181
|
+
col = array_tool.search(col)
|
182
|
+
self.display_records = col.size
|
183
|
+
end
|
184
|
+
|
185
|
+
if array_tool.order_column.present?
|
186
|
+
col = self.arrayize(col)
|
187
|
+
col = array_tool.order(col)
|
188
|
+
end
|
189
|
+
|
190
|
+
self.display_records ||= total_records
|
191
|
+
|
192
|
+
if col.kind_of?(Array)
|
193
|
+
col = array_tool.paginate(col)
|
194
|
+
else
|
195
|
+
col = table_tool.paginate(col)
|
196
|
+
col = self.arrayize(col)
|
197
|
+
end
|
198
|
+
|
199
|
+
col = self.finalize(col)
|
200
|
+
end
|
201
|
+
|
202
|
+
def arrayize(collection)
|
203
|
+
return collection if @arrayized # Prevent the collection from being arrayized more than once
|
204
|
+
@arrayized = true
|
205
|
+
|
206
|
+
# We want to use the render :collection for each column that renders partials
|
207
|
+
rendered = {}
|
208
|
+
table_columns.each do |name, opts|
|
209
|
+
if opts[:partial]
|
210
|
+
rendered[name] = (render(
|
211
|
+
:partial => opts[:partial],
|
212
|
+
:as => opts[:partial_local],
|
213
|
+
:collection => collection,
|
214
|
+
:formats => :html,
|
215
|
+
:locals => {:datatable => self},
|
216
|
+
:spacer_template => '/effective/datatables/spacer_template',
|
217
|
+
) || '').split('EFFECTIVEDATATABLESSPACER')
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
collection.each_with_index.map do |obj, index|
|
222
|
+
table_columns.map do |name, opts|
|
223
|
+
value = if opts[:partial]
|
224
|
+
rendered[name][index]
|
225
|
+
elsif opts[:block]
|
226
|
+
view.instance_exec(obj, collection, self, &opts[:block])
|
227
|
+
elsif opts[:proc]
|
228
|
+
view.instance_exec(obj, collection, self, &opts[:proc])
|
229
|
+
elsif opts[:type] == :belongs_to
|
230
|
+
val = (obj.send(name) rescue nil).to_s
|
231
|
+
else
|
232
|
+
val = (obj.send(name) rescue nil)
|
233
|
+
val = (obj[opts[:array_index]] rescue nil) if val == nil
|
234
|
+
val
|
235
|
+
end
|
236
|
+
|
237
|
+
# Last minute formatting of dates
|
238
|
+
case value
|
239
|
+
when Date
|
240
|
+
value.strftime("%Y-%m-%d")
|
241
|
+
when Time
|
242
|
+
value.strftime("%Y-%m-%d %H:%M:%S")
|
243
|
+
when DateTime
|
244
|
+
value.strftime("%Y-%m-%d %H:%M:%S")
|
245
|
+
else
|
246
|
+
value
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
private
|
253
|
+
|
254
|
+
def params
|
255
|
+
view.try(:params) || HashWithIndifferentAccess.new()
|
256
|
+
end
|
257
|
+
|
258
|
+
def table_tool
|
259
|
+
@table_tool ||= ActiveRecordDatatableTool.new(self, table_columns.select { |_, col| col[:array_column] == false })
|
260
|
+
end
|
261
|
+
|
262
|
+
def array_tool
|
263
|
+
@array_tool ||= ArrayDatatableTool.new(self, table_columns.select { |_, col| col[:array_column] == true })
|
264
|
+
end
|
265
|
+
|
266
|
+
def active_record_collection?
|
267
|
+
@active_record_collection ||= (collection.ancestors.include?(ActiveRecord::Base) rescue false)
|
268
|
+
end
|
269
|
+
|
270
|
+
def table_columns_with_defaults
|
271
|
+
unless self.class.instance_variable_get(:@table_columns_initialized)
|
272
|
+
self.class.instance_variable_set(:@table_columns_initialized, true)
|
273
|
+
initalize_table_columns(self.class.instance_variable_get(:@table_columns))
|
274
|
+
end
|
275
|
+
|
276
|
+
self.class.instance_variable_get(:@table_columns)
|
277
|
+
end
|
278
|
+
|
279
|
+
def initalize_table_columns(cols)
|
280
|
+
sql_table = (collection.table rescue nil)
|
281
|
+
|
282
|
+
# Here we identify all belongs_to associations and build up a Hash like:
|
283
|
+
# {:user => {:foreign_key => 'user_id', :klass => User}, :order => {:foreign_key => 'order_id', :klass => Effective::Order}}
|
284
|
+
belong_tos = (collection.ancestors.first.reflect_on_all_associations(:belongs_to) rescue []).inject(HashWithIndifferentAccess.new()) do |retval, bt|
|
285
|
+
unless bt.options[:polymorphic]
|
286
|
+
begin
|
287
|
+
klass = bt.klass || bt.foreign_type.gsub('_type', '').classify.constantize
|
288
|
+
rescue => e
|
289
|
+
klass = nil
|
290
|
+
end
|
291
|
+
|
292
|
+
retval[bt.name] = {:foreign_key => bt.foreign_key, :klass => klass} if bt.foreign_key.present? && klass.present?
|
293
|
+
end
|
294
|
+
|
295
|
+
retval
|
296
|
+
end
|
297
|
+
|
298
|
+
cols.each_with_index do |(name, _), index|
|
299
|
+
# If this is a belongs_to, add an :if clause specifying a collection scope if
|
300
|
+
if belong_tos.key?(name)
|
301
|
+
cols[name][:if] ||= Proc.new { attributes[belong_tos[name][:foreign_key]].blank? } # :if => Proc.new { attributes[:user_id].blank? }
|
302
|
+
end
|
303
|
+
|
304
|
+
sql_column = (collection.columns rescue []).find do |column|
|
305
|
+
column.name == name.to_s || (belong_tos.key?(name) && column.name == belong_tos[name][:foreign_key])
|
306
|
+
end
|
307
|
+
|
308
|
+
cols[name][:array_column] ||= false
|
309
|
+
cols[name][:array_index] = index # The index of this column in the collection, regardless of hidden table_columns
|
310
|
+
cols[name][:name] ||= name
|
311
|
+
cols[name][:label] ||= name.titleize
|
312
|
+
cols[name][:column] ||= (sql_table && sql_column) ? "\"#{sql_table.name}\".\"#{sql_column.name}\"" : name
|
313
|
+
cols[name][:type] ||= (belong_tos.key?(name) ? :belongs_to : sql_column.try(:type)).presence || :string
|
314
|
+
cols[name][:width] ||= nil
|
315
|
+
cols[name][:sortable] = true if cols[name][:sortable] == nil
|
316
|
+
cols[name][:filter] = initialize_table_column_filter(cols[name][:filter], cols[name][:type], belong_tos[name])
|
317
|
+
|
318
|
+
if cols[name][:partial]
|
319
|
+
cols[name][:partial_local] ||= (sql_table.try(:name) || cols[name][:partial].split('/').last(2).first.presence || 'obj').singularize.to_sym
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def initialize_table_column_filter(filter, col_type, belongs_to)
|
325
|
+
return {:type => :null, :when_hidden => false} if filter == false
|
326
|
+
|
327
|
+
if filter.kind_of?(Symbol)
|
328
|
+
filter = {:type => filter}
|
329
|
+
elsif filter.kind_of?(String)
|
330
|
+
filter = {:type => filter.to_sym}
|
331
|
+
elsif filter.kind_of?(Hash) == false
|
332
|
+
filter = {}
|
333
|
+
end
|
334
|
+
|
335
|
+
case col_type # null, number, select, number-range, date-range, checkbox, text(default)
|
336
|
+
when :belongs_to
|
337
|
+
{
|
338
|
+
:type => :select,
|
339
|
+
:when_hidden => false,
|
340
|
+
:values => Proc.new { belongs_to[:klass].all.map { |obj| [obj.id, obj.to_s] }.sort { |x, y| x[1] <=> y[1] } }
|
341
|
+
}.merge(filter)
|
342
|
+
when :integer
|
343
|
+
{:type => :number, :when_hidden => false}.merge(filter)
|
344
|
+
when :boolean
|
345
|
+
{:type => :select, :when_hidden => false, :values => [true, false]}.merge(filter)
|
346
|
+
else
|
347
|
+
{:type => :text, :when_hidden => false}.merge(filter)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
end
|
352
|
+
end
|