require "datagrid/utils"
require "active_support/core_ext/class/attribute"
module Datagrid
module Columns
require "datagrid/columns/column"
def self.included(base)
base.extend ClassMethods
base.class_eval do
include Datagrid::Core
end
base.send :include, InstanceMethods
end # self.included
module ClassMethods
# Returns a list of columns defined.
# All column definistion are returned by default
# You can limit the output with only columns you need like:
#
# grid.columns(:id, :name)
#
# Supported options:
#
# * :data - if true returns only non-html columns. Default: false.
def columns(*args)
options = args.extract_options!
args.compact!
args.map!(&:to_sym)
columns_array.select do |column|
(!options[:data] || column.data?) && (!options[:html] || column.html?)&& (args.empty? || args.include?(column.name))
end
end
# Defines new datagrid column
#
# Arguments:
#
# * name - column name
# * options - hash of options
# * block - proc to calculate a column value
#
# Available options:
#
# * :html - determines if current column should be present in html table and how is it formatted
# * :order - determines if this column could be sortable and how
# * :order_desc - determines a descending order for given column (only in case when :order can not be easily inverted
# * :url - a proc with one argument, pass this option to easily convert the value into an URL
# * :before - determines the position of this column, by adding it before the column passed here
# * :after - determines the position of this column, by adding it after the column passed here
#
# See: https://github.com/bogdan/datagrid/wiki/Columns for examples
def column(name, options = {}, &block)
check_scope_defined!("Scope should be defined before columns")
block ||= lambda do |model|
model.send(name)
end
position = Datagrid::Utils.extract_position_from_options(columns_array, options)
column = Datagrid::Columns::Column.new(self, name, options, &block)
columns_array.insert(position, column)
end
# Returns column definition with given name
def column_by_name(name)
self.columns.find do |col|
col.name.to_sym == name.to_sym
end
end
# Returns an array of all defined column names
def column_names
columns.map(&:name)
end
def respond_to(&block) #:nodoc:
Datagrid::Columns::Column::ResponseFormat.new(&block)
end
def format(value, &block)
if block_given?
respond_to do |f|
f.data { value }
f.html do
instance_exec(value, &block)
end
end
else
# Ruby Object#format exists.
# We don't want to change the behaviour and overwrite it.
super
end
end
def inherited(child_class) #:nodoc:
super(child_class)
child_class.columns_array = self.columns_array.clone
end
end # ClassMethods
module InstanceMethods
# Returns Array of human readable column names. See also "Localization" section
#
# Arguments:
#
# * column_names - list of column names if you want to limit data only to specified columns
def header(*column_names)
data_columns(*column_names).map(&:header)
end
# Returns Array column values for given asset
#
# Arguments:
#
# * column_names - list of column names if you want to limit data only to specified columns
def row_for(asset, *column_names)
data_columns(*column_names).map do |column|
column.data_value(asset, self)
end
end
# Returns Hash where keys are column names and values are column values for the given asset
def hash_for(asset)
result = {}
self.data_columns.each do |column|
result[column.name] = column.data_value(asset, self)
end
result
end
# Returns Array of Arrays with data for each row in datagrid assets without header.
#
# Arguments:
#
# * column_names - list of column names if you want to limit data only to specified columns
def rows(*column_names)
#TODO: find in batches
self.assets.map do |asset|
self.row_for(asset, *column_names)
end
end
# Returns Array of Arrays with data for each row in datagrid assets with header.
#
# Arguments:
#
# * column_names - list of column names if you want to limit data only to specified columns
def data(*column_names)
self.rows(*column_names).unshift(self.header(*column_names))
end
# Return Array of Hashes where keys are column names and values are column values
# for each row in datagrid #assets
#
# Example:
#
# class MyGrid
# scope { Model }
# column(:id)
# column(:name)
# end
#
# Model.create!(:name => "One")
# Model.create!(:name => "Two")
#
# MyGrid.new.data_hash # => [{:name => "One"}, {:name => "Two"}]
#
def data_hash
self.assets.map do |asset|
hash_for(asset)
end
end
# Returns a CSV representation of the data in the table
# You are able to specify which columns you want to see in CSV.
# All data columns are included by default
# Also you can specify options hash as last argument that is proxied to
# Ruby CSV library.
#
# Example:
#
# grid.to_csv
# grid.to_csv(:id, :name)
# grid.to_csv(:col_sep => ';')
def to_csv(*column_names)
options = column_names.extract_options!
klass = if RUBY_VERSION >= "1.9"
require 'csv'
CSV
else
require "fastercsv"
FasterCSV
end
klass.generate(
{:headers => self.header(*column_names), :write_headers => true}.merge(options)
) do |csv|
self.rows(*column_names).each do |row|
csv << row
end
end
end
# Returns all columns selected in grid instance
#
# Examples:
#
# MyGrid.new.columns # => all defined columns
# grid = MyGrid.new(:column_names => [:id, :name])
# grid.columns # => id and name columns
# grid.columns(:id, :category) # => id and category column
def columns(*args)
self.class.columns(*args)
end
# Returns all columns that can be represented in plain data(non-html) way
def data_columns(*names)
options = names.extract_options!
options[:data] = true
names << options
self.columns(*names)
end
# Returns all columns that can be represented in HTML table
def html_columns(*names)
options = names.extract_options!
options[:html] = true
names << options
self.columns(*names)
end
# Finds a column by name
def column_by_name(name)
self.class.column_by_name(name)
end
def format(value, &block)
if block_given?
self.class.format(value, &block)
else
super
end
end
end # InstanceMethods
end
end