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