rails_serverside_datatables 0.1.1
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/.gitignore +11 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +116 -0
- data/Rakefile +10 -0
- data/lib/rails_serverside_datatables.rb +15 -0
- data/lib/rails_serverside_datatables/column.rb +89 -0
- data/lib/rails_serverside_datatables/data_table.rb +143 -0
- data/lib/rails_serverside_datatables/expression_functions.rb +33 -0
- data/lib/rails_serverside_datatables/expression_tree_node.rb +99 -0
- data/lib/rails_serverside_datatables/expressions.rb +118 -0
- data/lib/rails_serverside_datatables/views/datatable.html.erb +43 -0
- data/lib/rails_serverside_datatables/views/railtie.rb +9 -0
- data/lib/rails_serverside_datatables/views/view_helpers.rb +53 -0
- data/rails_serverside_datatables.gemspec +28 -0
- metadata +120 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dd41b1c19949506f4a964d42f82d149295cb5109
|
4
|
+
data.tar.gz: df8b9eece21d75a403cb4624793d5e4db0062eb2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e193ecab2bc9f0e41bcdc55edc3bb67599878a89b8dc101eb559aafc4f5021854c74d5881eafe8c3a220b1a082abe7ce976a77ac623be0a83d6dbe980aab9962
|
7
|
+
data.tar.gz: 01b3fab809850eefc44bda16446140f7295638901f09069a0295d2423adbafae31f17438a7d779426a224b517e96b6b1311695984a6a8dc56c5b05cc96447109
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Oskar Kirmis
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
# Rails Serverside Datatables made easy
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
Add this line to your application's Gemfile:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem 'rails_serverside_datatables'
|
9
|
+
```
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install rails_serverside_datatables
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
### Basic usage
|
22
|
+
|
23
|
+
Lets say we have the following model `User`:
|
24
|
+
|
25
|
+
```
|
26
|
+
firstname | lastname | score
|
27
|
+
----------+----------+-------
|
28
|
+
Jane | Doe | 42
|
29
|
+
John | Doe | 23
|
30
|
+
... | ... | ...
|
31
|
+
```
|
32
|
+
|
33
|
+
To create a datatable you can sort and filter, just create a controller method like this one:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
class UsersController < ApplicationController
|
37
|
+
include RailsServersideDatatables
|
38
|
+
|
39
|
+
# ...
|
40
|
+
|
41
|
+
def ajax
|
42
|
+
datatable( [
|
43
|
+
{ display_name: 'Firstname', expression: :firstname },
|
44
|
+
{ display_name: 'Lastname', expression: :lastname },
|
45
|
+
{ display_name: 'Score', expression: :score }
|
46
|
+
], User.all )
|
47
|
+
end
|
48
|
+
|
49
|
+
# ...
|
50
|
+
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
Add the required entry to the `routes.rb`, e.g.:
|
55
|
+
|
56
|
+
`get '/users/datatable_ajax', to: 'users#ajax'`
|
57
|
+
|
58
|
+
Now you can reference you datatable by either creating the table on your own and initialize it using datatables... or you just use the following code in your view:
|
59
|
+
|
60
|
+
`<%= serverside_datatable( users_ajax_path, 'user-table', class: 'my-css-class' ) %>`
|
61
|
+
|
62
|
+
### Computed columns
|
63
|
+
|
64
|
+
Let's say you only want to show the name in the form `lastname, firstname` in one column and double the score adding 'points':
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
class UsersController < ApplicationController
|
68
|
+
include RailsServersideDatatables
|
69
|
+
|
70
|
+
# ...
|
71
|
+
|
72
|
+
def ajax
|
73
|
+
datatable( [
|
74
|
+
{ display_name: 'Firstname', expression: [ :lastname, ' ', :firstname ] },
|
75
|
+
{ display_name: 'Score', expression: [ text_cast( num_op( :score, '*', 2 ) ), ' points' ] }
|
76
|
+
], User.all )
|
77
|
+
end
|
78
|
+
|
79
|
+
# ...
|
80
|
+
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
### Decorator functions
|
85
|
+
|
86
|
+
If you want to display the data in a custom style, you can use structured data (e.g. additional data queried you can then use in the decorator method) and a decorator block.
|
87
|
+
|
88
|
+
The structured data can be passed in the `config` parameter. Every key that is not a predefined parameter like `display_name` is used as an additional expression queried from the database:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
class UsersController < ApplicationController
|
92
|
+
include RailsServersideDatatables
|
93
|
+
|
94
|
+
# ...
|
95
|
+
|
96
|
+
def ajax
|
97
|
+
datatable( [
|
98
|
+
Column.new( :name, [ :lastname, ' ', :firstname ], display_name: 'Full name', uid: :id ) {
|
99
|
+
|value, data| view_context.link_to value, user_path( data[:uid] )
|
100
|
+
},
|
101
|
+
Column.new( :score, :score, display_name: 'Score' ) {
|
102
|
+
|value, _| "#{value} points"
|
103
|
+
}
|
104
|
+
], User.all )
|
105
|
+
end
|
106
|
+
|
107
|
+
# ...
|
108
|
+
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
|
113
|
+
## License
|
114
|
+
|
115
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
116
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative 'rails_serverside_datatables/column'
|
2
|
+
require_relative 'rails_serverside_datatables/expression_functions'
|
3
|
+
require_relative 'rails_serverside_datatables/data_table'
|
4
|
+
|
5
|
+
require_relative 'rails_serverside_datatables/views/railtie' if defined?( Rails )
|
6
|
+
|
7
|
+
module RailsServersideDatatables
|
8
|
+
def datatable( column_definitions, query, parameters = nil )
|
9
|
+
|
10
|
+
table = DataTable.new( query, parameters || params )
|
11
|
+
column_definitions.each { |column| table.add_column column }
|
12
|
+
|
13
|
+
render json: table.as_json
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'cgi'
|
3
|
+
require_relative 'expressions'
|
4
|
+
|
5
|
+
module RailsServersideDatatables
|
6
|
+
|
7
|
+
class Column
|
8
|
+
CONFIGURATION_KEYS = [ :display_name, :column_filtering, :sorting, :filtering ]
|
9
|
+
|
10
|
+
attr_accessor :decorator
|
11
|
+
attr_accessor :display_name, :column_filtering, :sorting, :filtering
|
12
|
+
|
13
|
+
def initialize( name, definition, config = {}, &block )
|
14
|
+
@name = name
|
15
|
+
@definition = ExprTreeNode.create_by_type definition
|
16
|
+
@structured = config.reject {
|
17
|
+
|c| CONFIGURATION_KEYS.include? c
|
18
|
+
}.to_a.map {
|
19
|
+
|s| [s.first, ExprTreeNode.create_by_type(s.last ) ]
|
20
|
+
}.to_h
|
21
|
+
|
22
|
+
@decorator = block
|
23
|
+
|
24
|
+
@display_name = config.fetch( :display_name, name.to_s.capitalize )
|
25
|
+
@column_filtering = config.fetch( :column_filtering, true )
|
26
|
+
@sorting = config.fetch( :sorting, true )
|
27
|
+
@filtering = config.fetch( :filtering, true )
|
28
|
+
end
|
29
|
+
|
30
|
+
def order_definition=( definition )
|
31
|
+
@order_definition = definition
|
32
|
+
end
|
33
|
+
|
34
|
+
def structured( record )
|
35
|
+
@structured.to_a.map do |s|
|
36
|
+
[s.first, record.read_attribute(helper_column_name s.first)]
|
37
|
+
end.to_h
|
38
|
+
end
|
39
|
+
|
40
|
+
def value( record )
|
41
|
+
decorated record.read_attribute( main_column_name ), structured( record )
|
42
|
+
end
|
43
|
+
|
44
|
+
def select
|
45
|
+
@structured.to_a.map { |c| ExpressionAlias.new( c.last, helper_column_name(c.first) ) } +
|
46
|
+
[ ExpressionAlias.new( @definition, main_column_name ) ]
|
47
|
+
end
|
48
|
+
|
49
|
+
def order( direction = :ASC )
|
50
|
+
return Nothing.new unless sorting
|
51
|
+
OrderBy.new( @order_definition || main_column_name, direction )
|
52
|
+
end
|
53
|
+
|
54
|
+
def column_filter( filter )
|
55
|
+
return Nothing.new unless column_filtering
|
56
|
+
create_filter( filter )
|
57
|
+
end
|
58
|
+
|
59
|
+
def filter( filter )
|
60
|
+
return Nothing.new unless filtering
|
61
|
+
create_filter( filter )
|
62
|
+
end
|
63
|
+
|
64
|
+
def configuration
|
65
|
+
{
|
66
|
+
filtering: filtering, sorting: sorting,
|
67
|
+
column_filtering: true, display_name: display_name,
|
68
|
+
name: @name
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
def create_filter( filter )
|
74
|
+
FilterByText.new( @definition, filter )
|
75
|
+
end
|
76
|
+
|
77
|
+
def decorated( main, structured )
|
78
|
+
@decorator.nil? ? CGI.escapeHTML( main ) : @decorator.call( main, structured )
|
79
|
+
end
|
80
|
+
|
81
|
+
def main_column_name
|
82
|
+
"#{@name.to_s}_main".to_sym
|
83
|
+
end
|
84
|
+
|
85
|
+
def helper_column_name( structured_label )
|
86
|
+
"#{@name.to_s}_raw_#{structured_label.to_s}".to_sym
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require_relative 'column'
|
2
|
+
|
3
|
+
module RailsServersideDatatables
|
4
|
+
class DataTable
|
5
|
+
|
6
|
+
def initialize( query, parameters )
|
7
|
+
@columns = []
|
8
|
+
@parameters = parameters
|
9
|
+
|
10
|
+
@query = query
|
11
|
+
@total_records = query.size
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_column( definition, structured = {}, &block )
|
15
|
+
if definition.is_a? Column
|
16
|
+
@columns.push definition
|
17
|
+
else
|
18
|
+
if block.nil?
|
19
|
+
@columns.push Column.new( "cc_#{@columns.size}".to_sym, definition[:expression], structured )
|
20
|
+
else
|
21
|
+
@columns.push( Column.new( "cc_#{@columns.size}".to_sym, definition[:expression], structured ) do |value, structured|
|
22
|
+
block.call(value, structured)
|
23
|
+
end)
|
24
|
+
end
|
25
|
+
|
26
|
+
@columns.last.display_name = definition.fetch( :display_name, "#{@columns.size}" )
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def as_json
|
31
|
+
return configuration if config_request?
|
32
|
+
|
33
|
+
apply_select
|
34
|
+
apply_column_filters
|
35
|
+
apply_global_filter
|
36
|
+
|
37
|
+
@records_filtered = @query.size
|
38
|
+
|
39
|
+
apply_order
|
40
|
+
apply_offset
|
41
|
+
apply_limit
|
42
|
+
|
43
|
+
{
|
44
|
+
draw: @parameters[ :draw ] || 1,
|
45
|
+
recordsTotal: @total_records,
|
46
|
+
recordsFiltered: @records_filtered,
|
47
|
+
data: @query.map { |record| @columns.map { |col| col.value( record ) } }
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def config_request?
|
52
|
+
@parameters[ 'dt_config' ] == 'true'
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
def configuration
|
57
|
+
{
|
58
|
+
columns: @columns.map { |c| c.configuration }
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def apply_offset
|
63
|
+
@query = @query.offset( offset )
|
64
|
+
end
|
65
|
+
|
66
|
+
def apply_limit
|
67
|
+
@query = @query.limit( limit )
|
68
|
+
end
|
69
|
+
|
70
|
+
def apply_select
|
71
|
+
@query = @query.select( ExpressionList.new( @columns.map { |c| c.select }.flatten ).to_s )
|
72
|
+
end
|
73
|
+
|
74
|
+
def apply_column_filters
|
75
|
+
conditions = ExprTreeNode.new( 'AND', Type::OPERATOR, true )
|
76
|
+
column_filters.each { |filter| conditions.add_argument filter }
|
77
|
+
|
78
|
+
@query = @query.where( conditions.to_s ) if conditions.arguments?
|
79
|
+
end
|
80
|
+
|
81
|
+
def apply_global_filter
|
82
|
+
unless global_filter?
|
83
|
+
return
|
84
|
+
end
|
85
|
+
|
86
|
+
filter = ExprTreeNode.new( 'OR', Type::OPERATOR, false )
|
87
|
+
global_filter.each { |f| filter.add_argument f }
|
88
|
+
|
89
|
+
if filter.arguments?
|
90
|
+
@query = @query.where( filter.to_s )
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def apply_order
|
95
|
+
criteria = order
|
96
|
+
|
97
|
+
if criteria.expression.arguments?
|
98
|
+
@query = @query.order( order.to_s )
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
def offset
|
104
|
+
@parameters[:start] || 0
|
105
|
+
end
|
106
|
+
|
107
|
+
def limit
|
108
|
+
@parameters[:length] || 100
|
109
|
+
end
|
110
|
+
|
111
|
+
def column_filters
|
112
|
+
( @parameters[:columns] || {} ).to_a.map do |column|
|
113
|
+
filter = ( column.last[ :search ] || { value: nil } )[ :value ]
|
114
|
+
|
115
|
+
if filter.present?
|
116
|
+
@columns[ column.last[:data].to_i ].filter( filter )
|
117
|
+
else
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
end.reject { |v| v.nil? }
|
121
|
+
end
|
122
|
+
|
123
|
+
def global_filter_text
|
124
|
+
(@parameters[ :search ] || { value: nil } )[ :value ]
|
125
|
+
end
|
126
|
+
|
127
|
+
def global_filter?
|
128
|
+
global_filter_text.present?
|
129
|
+
end
|
130
|
+
|
131
|
+
def global_filter
|
132
|
+
@columns.map { |c| c.filter( global_filter_text ) }
|
133
|
+
end
|
134
|
+
|
135
|
+
def order
|
136
|
+
ExpressionList.new(
|
137
|
+
( @parameters[ :order ] || {} ).values.map { |column|
|
138
|
+
@columns[ column[:column].to_i ].order( column[:dir] )
|
139
|
+
}
|
140
|
+
)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative 'expressions'
|
2
|
+
|
3
|
+
module RailsServersideDatatables
|
4
|
+
|
5
|
+
def if_then( test_expr, comparison_value, then_expr, else_expr )
|
6
|
+
IfThen.new( test_expr, comparison_value, then_expr, else_expr )
|
7
|
+
end
|
8
|
+
|
9
|
+
def not_null( first, *rest )
|
10
|
+
FirstNotNull.new( first, *rest )
|
11
|
+
end
|
12
|
+
|
13
|
+
def text_cast( expression )
|
14
|
+
CastToText.new( expression )
|
15
|
+
end
|
16
|
+
|
17
|
+
def num_cast( expression )
|
18
|
+
CastToNumeric.new( expression )
|
19
|
+
end
|
20
|
+
|
21
|
+
def num_op( a, op, b )
|
22
|
+
NumOp.new( a, op, b )
|
23
|
+
end
|
24
|
+
|
25
|
+
def expr_alias( expression, name )
|
26
|
+
ExpressionAlias.new( expression, name )
|
27
|
+
end
|
28
|
+
|
29
|
+
def raw_expr( expr )
|
30
|
+
ExprTreeNode.raw expr
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module RailsServersideDatatables
|
2
|
+
module Type
|
3
|
+
COLUMN = :column
|
4
|
+
STRING = :string
|
5
|
+
NUMERIC = :numeric
|
6
|
+
FUNCTION = :function
|
7
|
+
OPERATOR = :operator
|
8
|
+
NULL = :null
|
9
|
+
RAW = :raw
|
10
|
+
end
|
11
|
+
|
12
|
+
class Nothing
|
13
|
+
end
|
14
|
+
|
15
|
+
class ExprTreeNode
|
16
|
+
|
17
|
+
def initialize(token, type, force_brackets = true)
|
18
|
+
@token = token
|
19
|
+
@type = type
|
20
|
+
@arguments = []
|
21
|
+
@force_brackets = force_brackets
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_argument(argument)
|
25
|
+
@arguments.push ExprTreeNode.create_by_type(argument) unless argument.is_a? Nothing
|
26
|
+
end
|
27
|
+
|
28
|
+
def arguments?
|
29
|
+
@arguments.any?
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
case @type
|
34
|
+
when Type::RAW
|
35
|
+
return @token.to_s
|
36
|
+
when Type::COLUMN
|
37
|
+
return ExprTreeNode.format_column @token
|
38
|
+
when Type::STRING
|
39
|
+
return ExprTreeNode.format_string @token
|
40
|
+
when Type::NUMERIC
|
41
|
+
return @token.is_a?(Numeric) ? @token.to_s : @token.to_f.to_s
|
42
|
+
when Type::FUNCTION
|
43
|
+
return ExprTreeNode.format_function @token, @arguments
|
44
|
+
when Type::OPERATOR
|
45
|
+
return ExprTreeNode.format_operator @token, @arguments, @force_brackets
|
46
|
+
when Type::NULL
|
47
|
+
return 'NULL'
|
48
|
+
else
|
49
|
+
raise 'Invalid fragment type passed.'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.create_by_type(type)
|
54
|
+
if type.is_a? Numeric
|
55
|
+
return ExprTreeNode.new(type, Type::NUMERIC)
|
56
|
+
elsif type.is_a?(Expression)
|
57
|
+
return type.expression
|
58
|
+
elsif type.nil?
|
59
|
+
return raw('NULL')
|
60
|
+
elsif type.is_a? Symbol
|
61
|
+
return ExprTreeNode.new(type, Type::COLUMN)
|
62
|
+
elsif type.is_a? String
|
63
|
+
return ExprTreeNode.new(type, Type::STRING)
|
64
|
+
elsif type.is_a? ExprTreeNode
|
65
|
+
return type
|
66
|
+
elsif type.is_a? Array
|
67
|
+
f = ExprTreeNode.new('||', Type::OPERATOR)
|
68
|
+
type.each { |e| f.add_argument e }
|
69
|
+
return f
|
70
|
+
else
|
71
|
+
raise 'Invalid type'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.raw(expr)
|
76
|
+
ExprTreeNode.new(expr.to_s, Type::RAW)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def self.format_column(name)
|
82
|
+
name.to_s.split('.').map { |f| '"' + f + '"' }.join '.'
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.format_string(name)
|
86
|
+
"#{ActiveRecord::Base.sanitize name}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.format_function(name, arguments)
|
90
|
+
name.to_s.upcase + '(' + arguments.map { |arg| arg.to_s }.join(',') + ')'
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.format_operator(operator, arguments, brackets)
|
94
|
+
term = arguments.map { |arg| arg.to_s }.join(" #{operator} ")
|
95
|
+
|
96
|
+
brackets ? '(' + term + ')' : term
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require_relative 'expression_tree_node'
|
2
|
+
|
3
|
+
module RailsServersideDatatables
|
4
|
+
class Expression
|
5
|
+
attr_reader :expression
|
6
|
+
|
7
|
+
def to_s
|
8
|
+
expression.to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
def raw( expr )
|
13
|
+
ExprTreeNode.raw(expr )
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class IfThen < Expression
|
18
|
+
|
19
|
+
def initialize( testexpr, comparison_val, thenexpr, elseexpr )
|
20
|
+
expression = ExprTreeNode.new('', Type::OPERATOR ) # Concat the statements only
|
21
|
+
|
22
|
+
expression.add_argument raw( 'CASE' )
|
23
|
+
expression.add_argument testexpr
|
24
|
+
|
25
|
+
if comparison_val.nil?
|
26
|
+
expression.add_argument raw( 'IS NULL' )
|
27
|
+
comparison_val = raw( true )
|
28
|
+
end
|
29
|
+
|
30
|
+
expression.add_argument raw( 'WHEN' )
|
31
|
+
expression.add_argument comparison_val
|
32
|
+
expression.add_argument raw( 'THEN' )
|
33
|
+
expression.add_argument thenexpr
|
34
|
+
expression.add_argument raw( 'ELSE' )
|
35
|
+
expression.add_argument elseexpr
|
36
|
+
expression.add_argument raw( 'END' )
|
37
|
+
|
38
|
+
@expression = expression
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class FirstNotNull < Expression
|
43
|
+
def initialize( test_value, *default )
|
44
|
+
f = ExprTreeNode.new(:coalesce, Type::FUNCTION )
|
45
|
+
f.add_argument test_value
|
46
|
+
|
47
|
+
default.each { |d| f.add_argument d }
|
48
|
+
@expression = f
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class CastToText < Expression
|
53
|
+
def initialize( expression )
|
54
|
+
f = ExprTreeNode.new('::', Type::OPERATOR )
|
55
|
+
f.add_argument expression
|
56
|
+
f.add_argument raw( 'text' )
|
57
|
+
@expression = f
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class CastToNumeric < Expression
|
62
|
+
def initialize( expression )
|
63
|
+
f = ExprTreeNode.new('::', Type::OPERATOR )
|
64
|
+
f.add_argument expression
|
65
|
+
f.add_argument raw( 'numeric' )
|
66
|
+
@expression = f
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class ExpressionAlias < Expression
|
71
|
+
def initialize( expression, name )
|
72
|
+
f = ExprTreeNode.new('AS', Type::OPERATOR, false )
|
73
|
+
f.add_argument expression
|
74
|
+
f.add_argument name
|
75
|
+
|
76
|
+
@expression = f
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class FilterByText < Expression
|
81
|
+
def initialize( expression, query )
|
82
|
+
f = ExprTreeNode.new('ILIKE', Type::OPERATOR, false )
|
83
|
+
f.add_argument CastToText.new( expression )
|
84
|
+
f.add_argument "%#{query}%"
|
85
|
+
|
86
|
+
@expression = f
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class OrderBy < Expression
|
91
|
+
def initialize( expression, direction = :ASC )
|
92
|
+
f = ExprTreeNode.new('', Type::OPERATOR, false )
|
93
|
+
f.add_argument expression
|
94
|
+
f.add_argument ExprTreeNode.raw( direction.to_s.downcase == 'desc' ? 'DESC' : 'ASC' )
|
95
|
+
|
96
|
+
@expression = f
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class ExpressionList < Expression
|
101
|
+
def initialize( expressions )
|
102
|
+
f = ExprTreeNode.new(',', Type::OPERATOR, false )
|
103
|
+
expressions.each { |expr| f.add_argument expr }
|
104
|
+
|
105
|
+
@expression = f
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class NumOp < Expression
|
110
|
+
def initialize( a, op, b )
|
111
|
+
f = ExprTreeNode.new( op.to_s, Type::OPERATOR )
|
112
|
+
f.add_argument CastToNumeric.new( a )
|
113
|
+
f.add_argument CastToNumeric.new( b )
|
114
|
+
|
115
|
+
@expression = f
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
<table<%= config.to_a.map { |attr| " #{attr.first}=\"#{h attr.last}\""}.join ' ' %>>
|
2
|
+
<thead>
|
3
|
+
<% columns.each do |column| %>
|
4
|
+
<th><%= column[:display_name] %></th>
|
5
|
+
<% end %>
|
6
|
+
</thead>
|
7
|
+
<tbody></tbody>
|
8
|
+
<% if filterable.any? %>
|
9
|
+
<tfoot>
|
10
|
+
<% columns.each do |column| %>
|
11
|
+
<th>
|
12
|
+
<% if column[:column_filtering] %>
|
13
|
+
<input type="text" placeholder="<%= column[:display_name] %>" id="<%=h config[:id] %>-column-filter-<%=h column[:name] %>" />
|
14
|
+
<% else %>
|
15
|
+
|
16
|
+
<% end %>
|
17
|
+
</th>
|
18
|
+
<% end %>
|
19
|
+
</tfoot>
|
20
|
+
<% end %>
|
21
|
+
</table>
|
22
|
+
|
23
|
+
<script type="text/javascript">
|
24
|
+
$(document).ready( function() {
|
25
|
+
var table = $('#<%= config[:id] %>').DataTable({
|
26
|
+
ajax: "<%=h serverside_path %>",
|
27
|
+
serverSide: true,
|
28
|
+
processing: true
|
29
|
+
});
|
30
|
+
|
31
|
+
<% if filterable.any? %>
|
32
|
+
table.columns().every( function () {
|
33
|
+
var that = this;
|
34
|
+
|
35
|
+
$( 'input', this.footer() ).on( 'keyup change', function () {
|
36
|
+
if ( that.search() !== this.value ) {
|
37
|
+
that.search( this.value ).draw();
|
38
|
+
}
|
39
|
+
} );
|
40
|
+
} );
|
41
|
+
<% end %>
|
42
|
+
});
|
43
|
+
</script>
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module RailsServersideDatatables
|
2
|
+
|
3
|
+
module ViewHelpers
|
4
|
+
def serverside_datatable( serverside_path, id, config = {}, parameters = {} )
|
5
|
+
# Our table requires an id
|
6
|
+
config[ :id ] = id
|
7
|
+
|
8
|
+
# Fetch the internal definition of the table
|
9
|
+
raw_configuration = serverside_datatable_config_request( serverside_path, parameters )
|
10
|
+
|
11
|
+
columns = []
|
12
|
+
begin
|
13
|
+
# Try to parse the (hopefully) JSON encoded response
|
14
|
+
configuration = JSON.parse( raw_configuration )
|
15
|
+
|
16
|
+
# Parse column information
|
17
|
+
configuration[ 'columns' ].each do |column|
|
18
|
+
columns.push( column.to_a.map { |f| [f.first.to_sym, f.last] }.to_h )
|
19
|
+
end
|
20
|
+
|
21
|
+
rescue Exception => e
|
22
|
+
raise 'Auto config failed - invalid response. Could not parse response. Details: ' + e.message
|
23
|
+
end
|
24
|
+
|
25
|
+
# Required in erb file...
|
26
|
+
filterable = columns.select { |c| c[:column_filtering] }
|
27
|
+
|
28
|
+
|
29
|
+
# Render the actual content
|
30
|
+
ERB.new(
|
31
|
+
File.read( File.dirname( __FILE__ ) + '/datatable.html.erb' )
|
32
|
+
).result( binding ).html_safe
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def serverside_datatable_config_request( url, parameters )
|
37
|
+
parameters[ 'dt_config' ] = true
|
38
|
+
|
39
|
+
# Route request internally
|
40
|
+
request = Rack::MockRequest.env_for(
|
41
|
+
url, params: parameters.to_query
|
42
|
+
).merge({ 'rack.session': session })
|
43
|
+
|
44
|
+
# Fetch configuration from the serverside definition
|
45
|
+
response = Rails.application.routes.call( request )
|
46
|
+
|
47
|
+
raise 'Auto config failed - invalid response. HTTP error code: ' + response.first unless response.first == 200
|
48
|
+
|
49
|
+
# Return the acutal content
|
50
|
+
response.last.body
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'rails_serverside_datatables'
|
7
|
+
spec.version = '0.1.1'
|
8
|
+
spec.authors = ['Oskar Kirmis']
|
9
|
+
spec.email = ['kirmis@st.ovgu.de']
|
10
|
+
|
11
|
+
spec.summary = %q{This gem allows you to implement serverside processing (filtering and sorting) easily for datatables, even with computed columns.}
|
12
|
+
spec.description = %q{This gem allows you to implement serverside processing (filtering and sorting) easily for datatables, even with computed columns. That allows you to perform filtering and sorting on complex expressions on database level while being very easy to use when this complexity is not required.}
|
13
|
+
spec.homepage = 'https://git.iftrue.de/okirmis/rails_serverside_datatables'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = 'exe'
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_dependency 'activerecord', '>= 4.1'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.14'
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
27
|
+
spec.add_development_dependency 'minitest', '~> 5.0'
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rails_serverside_datatables
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Oskar Kirmis
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-02-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.14'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.14'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '5.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '5.0'
|
69
|
+
description: This gem allows you to implement serverside processing (filtering and
|
70
|
+
sorting) easily for datatables, even with computed columns. That allows you to perform
|
71
|
+
filtering and sorting on complex expressions on database level while being very
|
72
|
+
easy to use when this complexity is not required.
|
73
|
+
email:
|
74
|
+
- kirmis@st.ovgu.de
|
75
|
+
executables: []
|
76
|
+
extensions: []
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- ".gitignore"
|
80
|
+
- ".travis.yml"
|
81
|
+
- Gemfile
|
82
|
+
- LICENSE.txt
|
83
|
+
- README.md
|
84
|
+
- Rakefile
|
85
|
+
- lib/rails_serverside_datatables.rb
|
86
|
+
- lib/rails_serverside_datatables/column.rb
|
87
|
+
- lib/rails_serverside_datatables/data_table.rb
|
88
|
+
- lib/rails_serverside_datatables/expression_functions.rb
|
89
|
+
- lib/rails_serverside_datatables/expression_tree_node.rb
|
90
|
+
- lib/rails_serverside_datatables/expressions.rb
|
91
|
+
- lib/rails_serverside_datatables/views/datatable.html.erb
|
92
|
+
- lib/rails_serverside_datatables/views/railtie.rb
|
93
|
+
- lib/rails_serverside_datatables/views/view_helpers.rb
|
94
|
+
- rails_serverside_datatables.gemspec
|
95
|
+
homepage: https://git.iftrue.de/okirmis/rails_serverside_datatables
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
metadata: {}
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 2.6.10
|
116
|
+
signing_key:
|
117
|
+
specification_version: 4
|
118
|
+
summary: This gem allows you to implement serverside processing (filtering and sorting)
|
119
|
+
easily for datatables, even with computed columns.
|
120
|
+
test_files: []
|