# encoding: UTF-8
module Wice
module Controller
def self.included(base) #:nodoc:
base.extend(ClassMethods)
end
module ClassMethods
# Used to add query processing action methods into a controller.
# Read section "Saving Queries How-To" in README for more details.
def save_wice_grid_queries
include Wice::SerializedQueriesControllerMixin
end
end
protected
attr_accessor :wice_grid_instances
# Creates a grid object to be used in the view. This is the main model, and the underlying table is the default table -
# if in other parameters a column name is mentioned without the name of the table, this table is implied.
# Just like in an ordinary ActiveRecord find you can use :joins, :include, and :conditions.
#
# The first parameter is an ActiveRecord class name or an ActiveRecord::Relation instance. The generated ActiveRecord call will use it as the
# receiver of the paginate method: klass.paginate(...)
#
# The second parameters is a hash of parameters:
# * :joins - ActiveRecord :joins option.
# * :include - ActiveRecord :include option.
# * :conditions - ActiveRecord :conditions option.
# * :per_page - Number of rows per one page. The default is 10.
# * :page - The page to show when rendering the grid for the first time. The default is one, naturally.
# * :order - Name of the column to sort by. Can be of a short form (just the name of the column) if this
# is a column of the main table (the table of the main ActiveRecord model, the first parameter of initialize_grid),
# or a fully qualified name with the name of the table.
# * :order_direction - :asc for ascending or :desc for descending. The default is :asc.
# * :name - name of the grid. Only needed if there is a second grid on a page. The name serves as the base name for
# HTTP parametes, DOM IDs, etc. The shorter the name, the shorter the GET request is. The name can only contain alphanumeruc characters.
# * :enable_export_to_csv - :csv_file_name - Name of the exported CSV file. If the parameter is missing, the name of the grid will be used instead.
# * :csv_field_separator - field separator for CSV files. The default is defined in +CSV_FIELD_SEPARATOR+ in the config file.
# * :custom_order - used for overriding the ORDER BY clause with custom sql code (for example, including a function).
# The value of the parameter is a hash where keys are fully qualified names
# of database columns, and values the required chunks of SQL to use in the ORDER BY clause, either as strings or Proc object
# evaluating to string. See section 'Custom Ordering' in the README.
# * :saved_query - id of the saved query or the query object itself to load initially.
# Read section "Saving Queries How-To" in README for more details.
# * :after - defined a name of a controller method which would be called by the grid after all user input has been processed,
# with a single parameter which is a Proc object. Once called, the object returns a list of all records of the current selection
# throughout all pages. See section "Integration With The Application" in the README.
# * :total_entries - If not specified, will_paginate will run a select count
# * :select - ActiveRecord :select option. Please do not forget that :select is ignored
# when :include is present. It is unlikely you would need :select with WiceGrid, but if you do,
# use it with care :)
# * :group - ActiveRecord :group option. Use it if you are sure you know what you are doing :)
# * :with_paginated_resultset - a callback executed from within the plugin to process records of the current page.
# Can be a lambda object or a controller method name (symbol). The argument to the callback is the array of the records.
# * :with_resultset - a callback executed from within the plugin to process all records browsable through
# all pages with the current filters. Can be a lambda object or a controller method name (symbol). The argument to
# the callback is a lambda object which returns the list of records when called. See the README for the explanation.
#
# Defaults for parameters :per_page, :order_direction, and :name
# can be changed in lib/wice_grid_config.rb, this is convenient if you want to set a project wide setting
# without having to repeat it for every grid instance.
def initialize_grid(klass, opts = {})
wg = WiceGrid.new(klass, self, opts)
self.wice_grid_instances = [] if self.wice_grid_instances.nil?
self.wice_grid_instances << wg
wg
end
# +export_grid_if_requested+ is a controller method which should be called at the end of each action containing grids with enabled
# CSV export.
#
# CSV export will only work if each WiceGrid helper is placed in a partial of its own (requiring it from the master template
# of course for the usual flow).
# +export_grid_if_requested+ intercepts CSV export requests and evaluates the corresponding partial with the required grid helper.
# By default for each grid +export_grid_if_requested+ will look for a partial
# whose name follows the following pattern:
#
# _GRID_NAME_grid.html.erb
#
# For example, a grid named +orders+ is supposed to be found in a template called _orders_grid.html.erb,
# Remember that the default name of grids is +grid+.
#
# This convention can be easily overridden by supplying a hash parameter to +export_grid_if_requested+ where each key is the name of
# a grid, and the value is the name of the template (like it is specified for +render+, i.e. without '_' and extensions):
#
# export_grid_if_requested(:grid => 'orders', 'grid2' => 'invoices')
#
# If the request is not a CSV export request, the method does nothing and returns +false+, if it is a CSV export request,
# the method returns +true+.
#
# If the action has no explicit +render+ call, it's OK to just place +export_grid_if_requested+ as the last line of the action. Otherwise,
# to avoid double rendering, use the return value of the method to conditionally call your +render+ :
#
# export_grid_if_requested || render(:action => 'index')
#
# It's also possible to supply a block which will be called if no CSV export is requested:
#
# export_grid_if_requested do
# render(:action => 'index')
# end
def export_grid_if_requested(opts = {})
grid = self.wice_grid_instances.detect(&:output_csv?)
if grid
template_name = opts[grid.name] || opts[grid.name.intern]
template_name ||= grid.name + '_grid'
temp_filename = render_to_string(:partial => template_name)
temp_filename = temp_filename.strip
filename = (grid.csv_file_name || grid.name ) + '.csv'
grid.csv_tempfile.close
send_file_rails2 temp_filename, :filename => filename, :type => 'text/csv'
grid.csv_tempfile = nil
true
else
yield if block_given?
false
end
end
# +wice_grid_custom_filter_params+ generates HTTP parameters understood by WiceGrid custom filters.
# Combined with Rails route helpers it allows to generate links leading to
# grids with pre-selected custom filters.
#
# Parameters:
# * :grid_name - The name of the grid. Just like parameter :name of
# initialize_grid, the parameter is optional, and when absent, the name
# 'grid' is assumed
# * :attribute and :model - should be the same as :attribute and
# :model of the column declaration with the target custom filter.
# * :value - the value of the column filter.
def wice_grid_custom_filter_params(opts = {})
options = {:grid_name => 'grid',
:attribute => nil,
:model => nil,
:value => nil}
options.merge!(opts)
[:attribute, :value].each do |key|
raise ::Wice::WiceGridArgumentError.new("wice_grid_custom_filter_params: :#{key} is a mandatory argument") unless options[key]
end
attr_name = if options[:model]
unless options[:model].nil?
options[:model] = options[:model].constantize if options[:model].is_a? String
raise Wice::WiceGridArgumentError.new("Option :model can be either a class or a string instance") unless options[:model].is_a? Class
end
options[:model].table_name + '.' + options[:attribute]
else
options[:attribute]
end
{"#{options[:grid_name]}[f][#{attr_name}][]" => options[:value]}
end
private
def send_file_rails2(path, options = {}) #:doc:
raise "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
options[:length] ||= File.size(path)
options[:filename] ||= File.basename(path) unless options[:url_based_filename]
send_file_headers_rails2! options
@performed_render = false
logger.info "Sending file #{path}" unless logger.nil?
File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read }
end
DEFAULT_SEND_FILE_OPTIONS_RAILS2 = {
:type => 'application/octet-stream'.freeze,
:disposition => 'attachment'.freeze,
:stream => true,
:buffer_size => 4096,
:x_sendfile => false
}.freeze
def send_file_headers_rails2!(options) #:doc:
options.update(DEFAULT_SEND_FILE_OPTIONS_RAILS2.merge(options))
[:length, :type, :disposition].each do |arg|
raise ArgumentError, ":#{arg} option required" if options[arg].nil?
end
disposition = options[:disposition].dup || 'attachment'
disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
content_type = options[:type]
content_type = content_type.to_s.strip # fixes a problem with extra '\r' with some browsers
headers.merge!(
'Content-Length' => options[:length].to_s,
'Content-Type' => content_type,
'Content-Disposition' => disposition,
'Content-Transfer-Encoding' => 'binary'
)
# Fix a problem with IE 6.0 on opening downloaded files:
# If Cache-Control: no-cache is set (which Rails does by default),
# IE removes the file it just downloaded from its cache immediately
# after it displays the "open/save" dialog, which means that if you
# hit "open" the file isn't there anymore when the application that
# is called for handling the download is run, so let's workaround that
headers['Cache-Control'] = 'private' if headers['Cache-Control'] == 'no-cache'
end
end
end