acts_as_data_table 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|