acts_as_data_table 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +55 -0
  5. data/Rakefile +11 -0
  6. data/acts_as_data_table.gemspec +25 -0
  7. data/app/coffeescripts/acts_as_data_table.coffee +47 -0
  8. data/app/helpers/acts_as_data_table_helper.rb +170 -0
  9. data/config/locales/acts_as_data_table.en.yml +11 -0
  10. data/generators/acts_as_data_table/acts_as_data_table_generator.rb +13 -0
  11. data/generators/acts_as_data_table/templates/assets/js/acts_as_data_table.js +37 -0
  12. data/init.rb +1 -0
  13. data/lib/acts_as_data_table/multi_column_scopes.rb +116 -0
  14. data/lib/acts_as_data_table/scope_filters/action_controller.rb +139 -0
  15. data/lib/acts_as_data_table/scope_filters/active_record.rb +264 -0
  16. data/lib/acts_as_data_table/scope_filters/form_helper.rb +67 -0
  17. data/lib/acts_as_data_table/scope_filters/validator.rb +144 -0
  18. data/lib/acts_as_data_table/session_helper.rb +193 -0
  19. data/lib/acts_as_data_table/shared/action_controller.rb +25 -0
  20. data/lib/acts_as_data_table/shared/session.rb +312 -0
  21. data/lib/acts_as_data_table/sortable_columns/action_controller.rb +111 -0
  22. data/lib/acts_as_data_table/sortable_columns/active_record.rb +34 -0
  23. data/lib/acts_as_data_table/sortable_columns/renderers/bootstrap2.rb +17 -0
  24. data/lib/acts_as_data_table/sortable_columns/renderers/default.rb +82 -0
  25. data/lib/acts_as_data_table/version.rb +3 -0
  26. data/lib/acts_as_data_table.rb +71 -0
  27. data/lib/acts_as_data_table_helper.rb.bak +165 -0
  28. data/lib/named_scope_filters.rb +273 -0
  29. data/lib/tasks/acts_as_data_table.rake +4 -0
  30. data/rails/init.rb +4 -0
  31. data/test/acts_as_searchable_test.rb +8 -0
  32. data/test/test_helper.rb +4 -0
  33. 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