acts_as_data_table 0.0.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.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +55 -0
- data/Rakefile +11 -0
- data/acts_as_data_table.gemspec +25 -0
- data/app/coffeescripts/acts_as_data_table.coffee +47 -0
- data/app/helpers/acts_as_data_table_helper.rb +170 -0
- data/config/locales/acts_as_data_table.en.yml +11 -0
- data/generators/acts_as_data_table/acts_as_data_table_generator.rb +13 -0
- data/generators/acts_as_data_table/templates/assets/js/acts_as_data_table.js +37 -0
- data/init.rb +1 -0
- data/lib/acts_as_data_table/multi_column_scopes.rb +116 -0
- data/lib/acts_as_data_table/scope_filters/action_controller.rb +139 -0
- data/lib/acts_as_data_table/scope_filters/active_record.rb +264 -0
- data/lib/acts_as_data_table/scope_filters/form_helper.rb +67 -0
- data/lib/acts_as_data_table/scope_filters/validator.rb +144 -0
- data/lib/acts_as_data_table/session_helper.rb +193 -0
- data/lib/acts_as_data_table/shared/action_controller.rb +25 -0
- data/lib/acts_as_data_table/shared/session.rb +312 -0
- data/lib/acts_as_data_table/sortable_columns/action_controller.rb +111 -0
- data/lib/acts_as_data_table/sortable_columns/active_record.rb +34 -0
- data/lib/acts_as_data_table/sortable_columns/renderers/bootstrap2.rb +17 -0
- data/lib/acts_as_data_table/sortable_columns/renderers/default.rb +82 -0
- data/lib/acts_as_data_table/version.rb +3 -0
- data/lib/acts_as_data_table.rb +71 -0
- data/lib/acts_as_data_table_helper.rb.bak +165 -0
- data/lib/named_scope_filters.rb +273 -0
- data/lib/tasks/acts_as_data_table.rake +4 -0
- data/rails/init.rb +4 -0
- data/test/acts_as_searchable_test.rb +8 -0
- data/test/test_helper.rb +4 -0
- metadata +142 -0
@@ -0,0 +1,264 @@
|
|
1
|
+
module Acts
|
2
|
+
module DataTable
|
3
|
+
module ScopeFilters
|
4
|
+
module ActiveRecord
|
5
|
+
def self.included(base)
|
6
|
+
base.send :extend, ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
#
|
11
|
+
# Generates a scope filter in the given group, meaning that only one of the
|
12
|
+
# filters in the same group may be active at one time.
|
13
|
+
#
|
14
|
+
# @param [String, Symbol] group
|
15
|
+
# The group name the new filter should be generated in
|
16
|
+
#
|
17
|
+
# @param [String, Symbol] scope
|
18
|
+
# The scope name the filter depends on
|
19
|
+
#
|
20
|
+
# @param [Hash] options
|
21
|
+
# Additional options to customize the generated filter
|
22
|
+
#
|
23
|
+
# ----
|
24
|
+
#
|
25
|
+
# @option options [Array<String, Symbol>] :args ([])
|
26
|
+
# Arguments needed by the scope. This is the case if the scope consists
|
27
|
+
# of a lambda object.
|
28
|
+
# The argument names have to be given in the same order as the scope function's
|
29
|
+
# formal parameters.
|
30
|
+
#
|
31
|
+
# @option options [Symbol, String, Proc] :caption (nil)
|
32
|
+
# The caption displayed if the filter is used in the application
|
33
|
+
# The caption is either hardcoded or interpreted based on the given type:
|
34
|
+
#
|
35
|
+
# - String: The given string is used as is
|
36
|
+
# - Symbol: The system expects a function with the given name to be defined in the current model.
|
37
|
+
# It is called with 3 arguments:
|
38
|
+
# 1. The group name
|
39
|
+
# 2. The scope name
|
40
|
+
# 3. The arguments the scope was called with (if any)
|
41
|
+
# - Proc: The given proc is executed with a hash containing the given arguments (if any)
|
42
|
+
#
|
43
|
+
# If nothing is given, the default caption is used, meaining the system will use I18n.t
|
44
|
+
# to search for a defined scope name within the scope 'activerecord.scope_filters.scopes.model.SCOPE'
|
45
|
+
# Possible scope arguments are passed in as strings
|
46
|
+
#
|
47
|
+
# @option options [Proc, Symbol] :validate (nil)
|
48
|
+
# A validation method for the scope filter. The filter is only applied if the validation method returns +true+
|
49
|
+
# This is useful if e.g. a scope needs two dates to work correctly and should only be applied
|
50
|
+
# if the dates have the correct format.
|
51
|
+
# If a Symbol is given, the system expects a method with that name
|
52
|
+
# to be defined as class method in the model class.
|
53
|
+
#
|
54
|
+
# The validation method is supposed to return an Array<String>
|
55
|
+
# containing the error messages or a simple boolean value.
|
56
|
+
#
|
57
|
+
# ----
|
58
|
+
#
|
59
|
+
# @example A filter group for locked and not locked records, expecting the scopes :locked and :not_locked to be present.
|
60
|
+
#
|
61
|
+
# has_scope_filter :status, :locked
|
62
|
+
# has_scope_filter :status, :not_locked
|
63
|
+
#
|
64
|
+
# @example A full text search with one argument (scope: full_text, formal parameter: text)
|
65
|
+
#
|
66
|
+
# has_scope_filter :quick_search, :full_text, [:text]
|
67
|
+
#
|
68
|
+
# @example A date range filter
|
69
|
+
#
|
70
|
+
# has_scope_filter :date, :between, [:start_date, :end_date]
|
71
|
+
#
|
72
|
+
def has_scope_filter(group, scope, options = {})
|
73
|
+
#Load additional helper methods into the model class
|
74
|
+
extend Acts::DataTable::ScopeFilters::ActiveRecord::OnDemand
|
75
|
+
|
76
|
+
model = self
|
77
|
+
|
78
|
+
unless scopes.has_key?(:with_scope_filters)
|
79
|
+
# Generate the named scope which will handle the dynamically given filters
|
80
|
+
# A filter is only applied if a given validation method returns +true+
|
81
|
+
#
|
82
|
+
# Usually, the filters are automatically fetched from the current
|
83
|
+
# thread space, however, in some cases it might be necessary to pass
|
84
|
+
# them in manually (e.g. when using a delayed job to fetch records).
|
85
|
+
# Please only do this if it is really necessary.
|
86
|
+
named_scope :with_scope_filters, lambda {|*args|
|
87
|
+
filters = args.first
|
88
|
+
filters ||= Acts::DataTable::ScopeFilters::ActionController.get_request_filters
|
89
|
+
|
90
|
+
scope_chain = self
|
91
|
+
filters.each do |group_name, (scope, args)|
|
92
|
+
|
93
|
+
#Filters should already be validated when they are added through
|
94
|
+
#the controller, this check is to ensure that no invalid filter is
|
95
|
+
#ever applied to the model as final protection.
|
96
|
+
# TODO: Check if the filter is causing an exception (probably through a wrong valiation method)
|
97
|
+
# And remove it in this case, adding an error to the log or the module.
|
98
|
+
if Acts::DataTable::ScopeFilters::Validator.new(model, group_name, scope, args).valid?
|
99
|
+
actual_args = Acts::DataTable::ScopeFilters::ActiveRecord.actual_params(model, group_name, scope, args)
|
100
|
+
scope_chain = scope_chain.send(scope, *actual_args)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
results = Acts::DataTable.lookup_nested_hash(scope_chain.current_scoped_methods, :find)
|
105
|
+
results || {}
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
Acts::DataTable::ScopeFilters::ActiveRecord.register_filter(self, group, scope, options)
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# Registers multiple simple filters at once.
|
114
|
+
# Important: This does not allow setting validations, custom captions or any other customization options.
|
115
|
+
#
|
116
|
+
def has_scope_filters(group, *scopes)
|
117
|
+
scopes.each do |s|
|
118
|
+
has_scope_filter(group, s)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
#
|
125
|
+
# This module is only included if at least one filter group was added to the model
|
126
|
+
# to avoid pollution in other models.
|
127
|
+
#
|
128
|
+
module OnDemand
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.registered_filters
|
133
|
+
@@registered_filters ||= {}
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# Registers a filter in the system which is then accessed when applying filters through a scope
|
138
|
+
# The filters are kept in this module as it can be accessed from everywhere,
|
139
|
+
# while a model class may not be known from within these methods.
|
140
|
+
#
|
141
|
+
# @param [ActiveRecord::Base] model
|
142
|
+
# The model class the scope to be added belongs to
|
143
|
+
#
|
144
|
+
# @param [String, Symbol] group
|
145
|
+
# The group name the scope belongs to within the +model+
|
146
|
+
#
|
147
|
+
# @param [String, Symbol] scope
|
148
|
+
# The scope name, it has to be a valid (named) scope within +model+
|
149
|
+
#
|
150
|
+
# @param [Hash] options
|
151
|
+
# Filter options, see #has_scope_filter
|
152
|
+
#
|
153
|
+
def self.register_filter(model, group, scope, options)
|
154
|
+
unless model.scopes.has_key?(scope.to_sym)
|
155
|
+
raise ArgumentError.new "The scope '#{scope}' in group '#{group}' does not exist in the model class '#{model.to_s}'"
|
156
|
+
end
|
157
|
+
|
158
|
+
Acts::DataTable.ensure_nested_hash!(self.registered_filters, model.to_s, group.to_s)
|
159
|
+
self.registered_filters[model.to_s][group.to_s][scope.to_s] = options
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# @see #register_filter for arguments
|
164
|
+
#
|
165
|
+
# @return [Hash, NilClass] options given for the chosen filter if available
|
166
|
+
#
|
167
|
+
def self.filter_options(model, group, scope)
|
168
|
+
res = Acts::DataTable.lookup_nested_hash(self.registered_filters, model.to_s, group.to_s, scope.to_s)
|
169
|
+
unless res
|
170
|
+
raise ArgumentError.new("The scope '#{scope}' was expected to be defined in group '#{group}' of model '#{model}' but couldn't be found.")
|
171
|
+
end
|
172
|
+
res
|
173
|
+
end
|
174
|
+
|
175
|
+
#
|
176
|
+
# @return [Array<String, Symbol>] the args set up when registering the given filter
|
177
|
+
#
|
178
|
+
# @see #filter_options for parameters
|
179
|
+
#
|
180
|
+
def self.filter_args(*args)
|
181
|
+
self.filter_options(*args)[:args] || []
|
182
|
+
end
|
183
|
+
|
184
|
+
#
|
185
|
+
# @return [Fixnum] the amount of formal parameters the scope is defined with
|
186
|
+
# Note that this will return 0 for no formal parameters which differs to the way
|
187
|
+
# ruby 1.8 handles lambda expressions (returning -1)
|
188
|
+
#
|
189
|
+
# @see #filter_options for parameters
|
190
|
+
#
|
191
|
+
def self.filter_arity(*args)
|
192
|
+
self.filter_args(*args).size
|
193
|
+
end
|
194
|
+
|
195
|
+
#
|
196
|
+
# @return [TrueClass, FalseClass] +true+ if the given filter was actually registered before.
|
197
|
+
#
|
198
|
+
# @see #filter_options for parameters
|
199
|
+
#
|
200
|
+
def self.registered_filter?(*args)
|
201
|
+
!!(self.filter_options(*args))
|
202
|
+
end
|
203
|
+
|
204
|
+
#
|
205
|
+
# Checks whether the given argument count matches the given scope filter's formal
|
206
|
+
# parameter count.
|
207
|
+
#
|
208
|
+
# @return [TrueClass, FalseClass] +true+ if the given argument count is
|
209
|
+
# sufficient for the chosen filter.
|
210
|
+
#
|
211
|
+
def self.matching_arity?(model, group, scope, arg_count)
|
212
|
+
self.filter_arity(model, group, scope) == arg_count
|
213
|
+
end
|
214
|
+
|
215
|
+
#
|
216
|
+
# @return [String] The scope filter caption for the given scope
|
217
|
+
# @see #has_scope_filter for more information about how the caption is generated.
|
218
|
+
#
|
219
|
+
def self.scope_filter_caption(model, group, scope, args)
|
220
|
+
args ||= {}
|
221
|
+
|
222
|
+
case caption = self.filter_options(model, group, scope)[:caption]
|
223
|
+
when String
|
224
|
+
caption
|
225
|
+
when Symbol
|
226
|
+
if model.respond_to?(caption)
|
227
|
+
model.send(caption, group, scope, args)
|
228
|
+
else
|
229
|
+
raise ArgumentError.new "The method '#{caption}' was set up as scope filter caption method in model '#{model.name}', but doesn't exist."
|
230
|
+
end
|
231
|
+
when Proc
|
232
|
+
caption.call(args)
|
233
|
+
else
|
234
|
+
options = {:scope => "activerecord.scope_filters.scopes.#{model.name.underscore}"}
|
235
|
+
options.merge!(args.symbolize_keys)
|
236
|
+
I18n.t(scope, options)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
#
|
241
|
+
# @return [Array<Object>] The actual parameters for a scope call
|
242
|
+
# based on the registered filter and the given arguments.
|
243
|
+
# This method should be used to ensure the correct order
|
244
|
+
# of arguments when applying a scope.
|
245
|
+
#
|
246
|
+
# @example Actual parameter generation for a date range scope
|
247
|
+
#
|
248
|
+
# #has_scope_filter :date, :between, [:start_date, :end_date]
|
249
|
+
# actual_params(model, :date, :between, {:end_date => '2015-04-01', :start_date => '2015-03-01'})
|
250
|
+
# #=> ['2015-03-01', '2015-04-01']
|
251
|
+
#
|
252
|
+
def self.actual_params(model, group, scope, args)
|
253
|
+
res = []
|
254
|
+
self.filter_args(model, group, scope).each do |arg_name|
|
255
|
+
res << args.stringify_keys[arg_name.to_s]
|
256
|
+
end
|
257
|
+
res
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Acts
|
2
|
+
module DataTable
|
3
|
+
module ScopeFilters
|
4
|
+
class FormHelper
|
5
|
+
def initialize(action_view, group, scope)
|
6
|
+
@action_view = action_view
|
7
|
+
@group = group.to_s
|
8
|
+
@scope = scope.to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
def text_field(arg, options = {})
|
12
|
+
value = options.delete(:value) || current_arg(arg)
|
13
|
+
options[:id] = FormHelper.field_id(@group, @scope, arg)
|
14
|
+
@action_view.text_field_tag(FormHelper.field_name(arg), value, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# @return [TrueClass, FalseClass] +true+ if the current filter is currently active
|
19
|
+
#
|
20
|
+
def active?
|
21
|
+
@action_view.acts_as_data_table_session.active_filter(@group) == @scope
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# @return [String] The URL to remove the current filter group from the active filters
|
26
|
+
#
|
27
|
+
def remove_url
|
28
|
+
@action_view.url_for({:scope_filters => {:action => 'remove', :group => @group}})
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# @return [Array] Filter validation errors on the current group
|
33
|
+
#
|
34
|
+
def errors
|
35
|
+
@action_view.acts_as_data_table_session.errors_on(@group)
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# @return [String] A generated field name for the given arg to be used in filter forms
|
40
|
+
#
|
41
|
+
def self.field_name(arg)
|
42
|
+
"scope_filters[args][#{arg}]"
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# @return [String] A generated DOM id for the given group, scope and arg
|
47
|
+
#
|
48
|
+
def self.field_id(group, scope, arg)
|
49
|
+
[group, scope, arg].map(&:to_s).join('_')
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
#
|
55
|
+
# Retrieves the current value for the given arg name from the session.
|
56
|
+
# Also searches the current request's params if the arg couldn't be found in the session.
|
57
|
+
# This is useful to keep given values in case of validation errors.
|
58
|
+
#
|
59
|
+
def current_arg(arg)
|
60
|
+
Acts::DataTable.lookup_nested_hash(@action_view.acts_as_data_table_session.active_filters, @group, @scope.to_s, arg.to_s) ||
|
61
|
+
Acts::DataTable.lookup_nested_hash(@action_view.params, :scope_filters, :args, arg)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Acts
|
2
|
+
module DataTable
|
3
|
+
module ScopeFilters
|
4
|
+
class Validator
|
5
|
+
|
6
|
+
def initialize(model, group, scope, args)
|
7
|
+
@model = model
|
8
|
+
@group = group
|
9
|
+
@scope = scope
|
10
|
+
@args = args
|
11
|
+
end
|
12
|
+
|
13
|
+
#
|
14
|
+
# Validates the given scope filter and returns possible error messages
|
15
|
+
#
|
16
|
+
# @see {Acts::DataTable::ScopeFilters::ActiveRecord}#register_filter for arguments
|
17
|
+
#
|
18
|
+
def validate
|
19
|
+
validations = Acts::DataTable::ScopeFilters::ActiveRecord.filter_options(@model, @group, @scope)[:validate]
|
20
|
+
|
21
|
+
Array(validations).inject([]) do |res, proc|
|
22
|
+
res += run_validation(proc)
|
23
|
+
res
|
24
|
+
end.uniq.compact
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid?
|
28
|
+
validate.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
#----------------------------------------------------------------
|
34
|
+
# Built-in Validations
|
35
|
+
#----------------------------------------------------------------
|
36
|
+
|
37
|
+
#
|
38
|
+
# @return [TrueClass, FalseClass] +true+ if the given validation
|
39
|
+
# is a built-in one.
|
40
|
+
#
|
41
|
+
def built_in_validation?(validation_name)
|
42
|
+
private_methods.include?("validate_#{validation_name}")
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Runs a built-in validation
|
47
|
+
#
|
48
|
+
def built_in_validation(validation_name)
|
49
|
+
send("validate_#{validation_name}")
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# Each of the given arguments has to be parseable by Date.parse
|
54
|
+
#
|
55
|
+
def validate_all_dates
|
56
|
+
validate_all :invalid_date do |k, v|
|
57
|
+
Date.parse(v) rescue nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Each of the given arguments has to be present (!blank?)
|
63
|
+
#
|
64
|
+
def validate_all_present
|
65
|
+
validate_all :blank do |k, v|
|
66
|
+
v.present?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Validates that all given arguments with names of the format /[a-z]+_id/
|
72
|
+
# actually refer to valid records in the system
|
73
|
+
#
|
74
|
+
def validate_record_existence
|
75
|
+
validate_all :invalid_record do |k, v|
|
76
|
+
m = /([a-z]+)_id/.match(k.to_s)
|
77
|
+
if m
|
78
|
+
model = m[1].camelize.constantize
|
79
|
+
!!model.find_by_id(v)
|
80
|
+
else
|
81
|
+
true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
#----------------------------------------------------------------
|
87
|
+
# Helper Methods
|
88
|
+
#----------------------------------------------------------------
|
89
|
+
|
90
|
+
def run_validation(proc)
|
91
|
+
case proc
|
92
|
+
when Proc
|
93
|
+
result = proc.call(@args)
|
94
|
+
when Symbol
|
95
|
+
if built_in_validation?(proc)
|
96
|
+
result = built_in_validation(proc)
|
97
|
+
elsif @model.respond_to?(proc)
|
98
|
+
result = @model.send(proc, @args)
|
99
|
+
else
|
100
|
+
raise ArgumentError.new "The method '#{@proc}' was set up as scope filter validation method in model '#{@model.name}', but doesn't exist."
|
101
|
+
end
|
102
|
+
when NilClass
|
103
|
+
result = true
|
104
|
+
else
|
105
|
+
raise ArgumentError.new "An invalid validations method was given for the scope '#{@scope}' in group '#{@group}' of model '#{@model.name}'"
|
106
|
+
end
|
107
|
+
|
108
|
+
#If the result is already an array of error messages, we can simply return it.
|
109
|
+
#Otherwise, we have to generate an error array based on the boolean result
|
110
|
+
#the validation method produced.
|
111
|
+
if result.is_a?(Array)
|
112
|
+
result
|
113
|
+
else
|
114
|
+
result ? [] : [Acts::DataTable.t('scope_filters.validations.general_error')]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def validate_all(error, &proc)
|
119
|
+
@args.inject([]) do |res, (k, v)|
|
120
|
+
unless proc.call(k, v)
|
121
|
+
res << Acts::DataTable.t("scope_filters.validations.#{error}", :arg_name => localized_arg_name(k), :arg_value => v)
|
122
|
+
end
|
123
|
+
res
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
# Looks up the given argument name in activerecord.scope_filters.args.MODEL.
|
129
|
+
#
|
130
|
+
# If no translation was specified, it behaves like I18n.t in rails 4 and tries
|
131
|
+
# to make the best of the given given argument name
|
132
|
+
#
|
133
|
+
def localized_arg_name(arg_name)
|
134
|
+
l = I18n.t(arg_name, :scope => "activerecord.scope_filters.args.#{@model.to_s.underscore}.#{@scope}")
|
135
|
+
if l =~ /translation missing/
|
136
|
+
arg_name.to_s.split('_').map(&:camelize).join(' ')
|
137
|
+
else
|
138
|
+
l
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
module Stex
|
2
|
+
module Acts
|
3
|
+
module DataTable
|
4
|
+
class SessionHelper
|
5
|
+
def initialize(session, controller_path, action, model_name)
|
6
|
+
@session = session
|
7
|
+
@controller_path = controller_path
|
8
|
+
@action = action
|
9
|
+
@model = model_name.to_s.classify.constantize
|
10
|
+
end
|
11
|
+
|
12
|
+
def controller_path
|
13
|
+
@controller_path
|
14
|
+
end
|
15
|
+
|
16
|
+
def action_name
|
17
|
+
@action
|
18
|
+
end
|
19
|
+
|
20
|
+
def model
|
21
|
+
@model
|
22
|
+
end
|
23
|
+
|
24
|
+
#----------------------------------------------------------------
|
25
|
+
# Filters
|
26
|
+
#----------------------------------------------------------------
|
27
|
+
|
28
|
+
# Adds a filter for a specific view
|
29
|
+
# If a filter for this group is already set, the new filter
|
30
|
+
# will override the existing one.
|
31
|
+
#--------------------------------------------------------------
|
32
|
+
def add_filter(group, scope, args)
|
33
|
+
controller_key = action_key(controller_path, action_name)
|
34
|
+
@session[:scope_filters] ||= {}
|
35
|
+
@session[:scope_filters][controller_key] ||= {}
|
36
|
+
|
37
|
+
args = hash_keys_to_strings(args) if args
|
38
|
+
|
39
|
+
@session[:scope_filters][controller_key][group.to_s] = {scope.to_s => args}
|
40
|
+
end
|
41
|
+
|
42
|
+
# Removes an active filter. As only one filter per group can
|
43
|
+
# be active at a time, we can simply delete the whole group.
|
44
|
+
# If all filters are deleted, we can also remove the namespaces
|
45
|
+
# in the session
|
46
|
+
#--------------------------------------------------------------
|
47
|
+
def remove_filter(group)
|
48
|
+
controller_key = action_key(controller_path, action_name)
|
49
|
+
return unless @session[:scope_filters][controller_key]
|
50
|
+
@session[:scope_filters][controller_key].delete(group.to_s)
|
51
|
+
@session[:scope_filters].delete(controller_key) if @session[:scope_filters][controller_key].empty?
|
52
|
+
@session.delete(:scope_filters) if @session[:scope_filters].empty?
|
53
|
+
end
|
54
|
+
|
55
|
+
# Removes all filters from the given controller + action
|
56
|
+
#--------------------------------------------------------------
|
57
|
+
def remove_all_filters
|
58
|
+
@session[:scope_filters].delete(action_key(controller_path, action_name))
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns all active filters for the given controller and action
|
62
|
+
#--------------------------------------------------------------
|
63
|
+
def active_filters
|
64
|
+
return {} unless @session[:scope_filters]
|
65
|
+
@session[:scope_filters][action_key(controller_path, action_name)] || {}
|
66
|
+
end
|
67
|
+
|
68
|
+
# Checks if the given filter is currently active
|
69
|
+
#--------------------------------------------------------------
|
70
|
+
def active_filter?(group, scope, args)
|
71
|
+
active = active_filters
|
72
|
+
args = hash_keys_to_strings(args) if args
|
73
|
+
|
74
|
+
active.any? &&
|
75
|
+
active[group.to_s] &&
|
76
|
+
active[group.to_s].has_key?(scope.to_s) &&
|
77
|
+
(active[group.to_s][scope.to_s].to_a - args.to_a).empty? #sometimes the session hash contains {:raise => true}, whereever it comes from...
|
78
|
+
end
|
79
|
+
|
80
|
+
#----------------------------------------------------------------
|
81
|
+
# Column Sorting
|
82
|
+
#----------------------------------------------------------------
|
83
|
+
|
84
|
+
# Replaces all current sorting columns with the new one
|
85
|
+
#--------------------------------------------------------------
|
86
|
+
def replace_sorting_column(model_name, column_name)
|
87
|
+
args = generate_sorting_arguments(model_name, column_name)
|
88
|
+
return unless args[:model].column_names.include?(column_name.to_s)
|
89
|
+
|
90
|
+
@session[:column_sorting] ||= {}
|
91
|
+
@session[:column_sorting][args[:key]] = [[args[:column], 'ASC']]
|
92
|
+
end
|
93
|
+
|
94
|
+
# Adds a new sorting column to the current controller and action
|
95
|
+
#--------------------------------------------------------------
|
96
|
+
def add_or_toggle_sorting_column(model_name, column_name)
|
97
|
+
args = generate_sorting_arguments(model_name, column_name)
|
98
|
+
|
99
|
+
return unless args[:model].column_names.include?(column_name.to_s)
|
100
|
+
|
101
|
+
@session[:column_sorting] ||= {}
|
102
|
+
@session[:column_sorting][args[:key]] ||= []
|
103
|
+
|
104
|
+
existing_entry = @session[:column_sorting][args[:key]].assoc(args[:column])
|
105
|
+
|
106
|
+
#Toggle the direction
|
107
|
+
if existing_entry
|
108
|
+
idx = @session[:column_sorting][args[:key]].index(existing_entry)
|
109
|
+
|
110
|
+
direction = existing_entry.last == 'ASC' ? 'DESC' : 'ASC'
|
111
|
+
|
112
|
+
@session[:column_sorting][args[:key]].delete(existing_entry)
|
113
|
+
|
114
|
+
@session[:column_sorting][args[:key]].insert(idx, [args[:column], direction])
|
115
|
+
else
|
116
|
+
#Simply append the new column to the sorting list
|
117
|
+
@session[:column_sorting][args[:key]] << [args[:column], 'ASC']
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Removes a sorting column from current controller and action
|
122
|
+
#--------------------------------------------------------------
|
123
|
+
def remove_sorting_column(model_name, column_name)
|
124
|
+
args = generate_sorting_arguments(model_name, column_name)
|
125
|
+
return if @session[:column_sorting].nil? || @session[:column_sorting].empty?
|
126
|
+
return unless @session[:column_sorting].has_key?(args[:key])
|
127
|
+
|
128
|
+
existing_entry = @session[:column_sorting][args[:key]].assoc(args[:column])
|
129
|
+
@session[:column_sorting][args[:key]].delete(existing_entry) if existing_entry
|
130
|
+
|
131
|
+
#Remove the controller namespace from the session if it's empty
|
132
|
+
@session[:column_sorting].delete(args[:key]) if @session[:column_sorting][args[:key]].empty?
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns all sorting columns as a string
|
136
|
+
#--------------------------------------------------------------
|
137
|
+
def sorting_columns_string
|
138
|
+
controller_key = action_key(controller_path, action_name)
|
139
|
+
return nil if @session[:column_sorting].nil? || @session[:column_sorting].empty?
|
140
|
+
return nil unless @session[:column_sorting].has_key?(controller_key)
|
141
|
+
|
142
|
+
@session[:column_sorting][controller_key].map {|column_and_direction| column_and_direction.join(' ')}.join(', ')
|
143
|
+
end
|
144
|
+
|
145
|
+
# Returns the current sorting direction for a given column
|
146
|
+
# or nil if this column is currently not active.
|
147
|
+
#--------------------------------------------------------------
|
148
|
+
def sorting_direction(model_name, column_name)
|
149
|
+
args = generate_sorting_arguments(model_name, column_name)
|
150
|
+
|
151
|
+
return nil if @session[:column_sorting].nil? || @session[:column_sorting].empty?
|
152
|
+
return nil unless @session[:column_sorting].has_key?(args[:key])
|
153
|
+
entry = @session[:column_sorting][args[:key]].assoc(args[:column])
|
154
|
+
entry ? entry.last : nil
|
155
|
+
end
|
156
|
+
|
157
|
+
# loads the sorting defaults for the current action
|
158
|
+
# Parameters==
|
159
|
+
# defaults:: [['users.last_name', 'ASC'], ['users.first_name', 'ASC']]
|
160
|
+
#--------------------------------------------------------------
|
161
|
+
def load_sorting_defaults(defaults = [])
|
162
|
+
return if defaults.empty?
|
163
|
+
controller_key = action_key(controller_path, action_name)
|
164
|
+
@session[:column_sorting] = {}
|
165
|
+
@session[:column_sorting][controller_key] = defaults
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
|
170
|
+
def generate_sorting_arguments(model_name, column_name)
|
171
|
+
model = model_name.to_s.classify.constantize
|
172
|
+
{
|
173
|
+
:model => model,
|
174
|
+
:key => action_key(controller_path, action_name),
|
175
|
+
:column => "#{model.table_name}.#{column_name}"
|
176
|
+
}
|
177
|
+
end
|
178
|
+
|
179
|
+
def action_key(controller, action)
|
180
|
+
[controller.gsub("/", "_"), action].join('_')
|
181
|
+
end
|
182
|
+
|
183
|
+
def hash_keys_to_strings(hash)
|
184
|
+
new_hash = {}
|
185
|
+
hash.each do |key, value|
|
186
|
+
new_hash[key.to_s] = value
|
187
|
+
end
|
188
|
+
new_hash
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Acts
|
2
|
+
module DataTable
|
3
|
+
module Shared
|
4
|
+
module ActionController
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
module OnDemand
|
14
|
+
def self.included(base)
|
15
|
+
base.helper_method :acts_as_data_table_session
|
16
|
+
end
|
17
|
+
|
18
|
+
def acts_as_data_table_session
|
19
|
+
@acts_as_data_table_session ||= Acts::DataTable::Shared::Session.new(session, controller_path, action_name)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|