datagrid 0.6.4 → 0.7.0

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/Gemfile CHANGED
@@ -13,7 +13,6 @@ group :development do
13
13
  gem "debugger", :platform => "ruby_19"
14
14
 
15
15
  gem "rspec"
16
- gem "accept_values_for"
17
16
  gem "nokogiri" # used to test html output
18
17
 
19
18
  gem "sqlite3-ruby"
@@ -21,6 +21,8 @@ Ruby library that helps you to build and represent table-like data with:
21
21
  [Datagrid DEMO application](http://datagrid.heroku.com) is available live!
22
22
  [Demo source code](https://github.com/bogdan/datagrid-demo).
23
23
 
24
+ <img src="http://datagrid.heroku.com/datagrid_demo_screenshot.png" style="margin: 7px; border: 1px solid black">
25
+
24
26
  ### Example
25
27
 
26
28
  In order to create a grid:
@@ -38,7 +40,7 @@ class SimpleReport
38
40
  filter(:disabled, :eboolean)
39
41
  filter(:confirmed, :boolean)
40
42
  filter(:group_id, :integer, :multiple => true)
41
- integer_range_filter(:logins_count, :integer)
43
+ filter(:logins_count, :integer, :range => true)
42
44
  filter(:group_name, :string, :header => "Group") do |value|
43
45
  self.joins(:group).where(:groups => {:name => value})
44
46
  end
@@ -67,11 +69,11 @@ report = SimpleReport.new(
67
69
  report.assets # => Array of User instances:
68
70
  # SELECT * FROM users WHERE users.group_id in (1,2) AND users.logins_count >= 1 AND users.category = 'first' ORDER BY groups.name DESC
69
71
 
70
- report.header # => ["Group", "Name", "Activated"]
72
+ report.header # => ["Name", "Group", "Activated"]
71
73
  report.rows # => [
72
- # ["Steve", "Spammers", true],
73
- # [ "John", "Spoilers", true],
74
- # ["Berry", "Good people", false]
74
+ # ["Steve", "Spammers", false],
75
+ # [ "John", "Spoilers", false],
76
+ # ["Berry", "Good people", true]
75
77
  # ]
76
78
  report.data # => [ header, *rows]
77
79
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.4
1
+ 0.7.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "datagrid"
8
- s.version = "0.6.4"
8
+ s.version = "0.7.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Bogdan Gusiev"]
12
- s.date = "2012-09-17"
12
+ s.date = "2012-12-15"
13
13
  s.description = "This allows you to easily build datagrid aka data tables with sortable columns and filters"
14
14
  s.email = "agresso@gmail.com"
15
15
  s.extra_rdoc_files = [
@@ -48,6 +48,7 @@ Gem::Specification.new do |s|
48
48
  "lib/datagrid/filters/enum_filter.rb",
49
49
  "lib/datagrid/filters/float_filter.rb",
50
50
  "lib/datagrid/filters/integer_filter.rb",
51
+ "lib/datagrid/filters/ranged_filter.rb",
51
52
  "lib/datagrid/filters/string_filter.rb",
52
53
  "lib/datagrid/form_builder.rb",
53
54
  "lib/datagrid/helper.rb",
@@ -64,8 +65,10 @@ Gem::Specification.new do |s|
64
65
  "spec/datagrid/drivers/mongoid_spec.rb",
65
66
  "spec/datagrid/filters/boolean_enum_filter_spec.rb",
66
67
  "spec/datagrid/filters/composite_filters_spec.rb",
68
+ "spec/datagrid/filters/date_filter_spec.rb",
67
69
  "spec/datagrid/filters/enum_filter_spec.rb",
68
70
  "spec/datagrid/filters/float_filter_spec.rb",
71
+ "spec/datagrid/filters/integer_filter_spec.rb",
69
72
  "spec/datagrid/filters_spec.rb",
70
73
  "spec/datagrid/form_builder_spec.rb",
71
74
  "spec/datagrid/helper_spec.rb",
@@ -96,7 +99,6 @@ Gem::Specification.new do |s|
96
99
  s.add_development_dependency(%q<jeweler>, [">= 0"])
97
100
  s.add_development_dependency(%q<debugger>, [">= 0"])
98
101
  s.add_development_dependency(%q<rspec>, [">= 0"])
99
- s.add_development_dependency(%q<accept_values_for>, [">= 0"])
100
102
  s.add_development_dependency(%q<nokogiri>, [">= 0"])
101
103
  s.add_development_dependency(%q<sqlite3-ruby>, [">= 0"])
102
104
  s.add_development_dependency(%q<mongoid>, ["= 2.2.2"])
@@ -111,7 +113,6 @@ Gem::Specification.new do |s|
111
113
  s.add_dependency(%q<jeweler>, [">= 0"])
112
114
  s.add_dependency(%q<debugger>, [">= 0"])
113
115
  s.add_dependency(%q<rspec>, [">= 0"])
114
- s.add_dependency(%q<accept_values_for>, [">= 0"])
115
116
  s.add_dependency(%q<nokogiri>, [">= 0"])
116
117
  s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
117
118
  s.add_dependency(%q<mongoid>, ["= 2.2.2"])
@@ -127,7 +128,6 @@ Gem::Specification.new do |s|
127
128
  s.add_dependency(%q<jeweler>, [">= 0"])
128
129
  s.add_dependency(%q<debugger>, [">= 0"])
129
130
  s.add_dependency(%q<rspec>, [">= 0"])
130
- s.add_dependency(%q<accept_values_for>, [">= 0"])
131
131
  s.add_dependency(%q<nokogiri>, [">= 0"])
132
132
  s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
133
133
  s.add_dependency(%q<mongoid>, ["= 2.2.2"])
@@ -20,6 +20,13 @@ module Datagrid
20
20
 
21
21
  module ClassMethods
22
22
 
23
+ # Returns a list of columns defined.
24
+ # You can limit the output with only columns you need like:
25
+ #
26
+ # grid.columns(:id, :name)
27
+ #
28
+ # Supported options:
29
+ # * :data - if true returns only non-html columns. Default: false.
23
30
  def columns(*args)
24
31
  options = args.extract_options!
25
32
  args.compact!
@@ -29,6 +36,7 @@ module Datagrid
29
36
  end
30
37
  end
31
38
 
39
+ # Defines new datagrid column
32
40
  def column(name, options = {}, &block)
33
41
  check_scope_defined!("Scope should be defined before columns")
34
42
  block ||= lambda do |model|
@@ -53,13 +61,13 @@ module Datagrid
53
61
  module InstanceMethods
54
62
 
55
63
  # Returns <tt>Array</tt> of human readable column names. See also "Localization" section
56
- def header
57
- self.data_columns.map(&:header)
64
+ def header(*column_names)
65
+ data_columns(*column_names).map(&:header)
58
66
  end
59
67
 
60
68
  # Returns <tt>Array</tt> column values for given asset
61
- def row_for(asset)
62
- self.data_columns.map do |column|
69
+ def row_for(asset, *column_names)
70
+ data_columns(*column_names).map do |column|
63
71
  column.value(asset, self)
64
72
  end
65
73
  end
@@ -74,9 +82,10 @@ module Datagrid
74
82
  end
75
83
 
76
84
  # Returns Array of Arrays with data for each row in datagrid assets without header.
77
- def rows
85
+ def rows(*column_names)
86
+ #TODO: find in batches
78
87
  self.assets.map do |asset|
79
- self.row_for(asset)
88
+ self.row_for(asset, *column_names)
80
89
  end
81
90
  end
82
91
 
@@ -92,7 +101,8 @@ module Datagrid
92
101
  end
93
102
  end
94
103
 
95
- def to_csv(options = {})
104
+ def to_csv(*column_names)
105
+ options = columns.extract_options!
96
106
  klass = if RUBY_VERSION >= "1.9"
97
107
  require 'csv'
98
108
  CSV
@@ -101,9 +111,9 @@ module Datagrid
101
111
  FasterCSV
102
112
  end
103
113
  klass.generate(
104
- {:headers => self.header, :write_headers => true}.merge(options)
114
+ {:headers => self.header(*column_names), :write_headers => true}.merge(options)
105
115
  ) do |csv|
106
- self.rows.each do |row|
116
+ self.rows(*column_names).each do |row|
107
117
  csv << row
108
118
  end
109
119
  end
@@ -113,8 +123,9 @@ module Datagrid
113
123
  self.class.columns(*args)
114
124
  end
115
125
 
116
- def data_columns
117
- self.columns(:data => true)
126
+ def data_columns(*names)
127
+ names << {:data => true}
128
+ self.columns(*names)
118
129
  end
119
130
 
120
131
  def column_by_name(name)
@@ -1,12 +1,19 @@
1
1
  class Datagrid::Columns::Column
2
2
 
3
- attr_accessor :grid, :options, :block, :name
3
+ attr_accessor :grid, :options, :block, :name, :html_block
4
4
 
5
5
  def initialize(grid, name, options = {}, &block)
6
6
  self.grid = grid
7
7
  self.name = name.to_sym
8
8
  self.options = options
9
- self.block = block
9
+ if options[:html] == true
10
+ self.html_block = block
11
+ else
12
+ if options[:html].is_a? Proc
13
+ self.html_block = options[:html]
14
+ end
15
+ self.block = block
16
+ end
10
17
  if format
11
18
  ::Datagrid::Utils.warn_once(":format column option is deprecated. Use :url or :html option instead.")
12
19
  end
@@ -53,11 +60,11 @@ class Datagrid::Columns::Column
53
60
  end
54
61
 
55
62
  def html?
56
- !! self.options[:html]
63
+ self.html_block != nil
57
64
  end
58
65
 
59
66
  def data?
60
- !html?
67
+ self.block != nil
61
68
  end
62
69
 
63
70
  end
@@ -47,7 +47,7 @@ module Datagrid
47
47
 
48
48
  protected
49
49
  def check_scope_defined!(message = nil)
50
- message ||= "Scope not defined"
50
+ message ||= "#{self}.scope is not defined"
51
51
  raise(Datagrid::ConfigurationError, message) unless scope_value
52
52
  end
53
53
 
@@ -52,6 +52,10 @@ module Datagrid
52
52
  def has_column?(scope, column_name)
53
53
  raise NotImplementedError
54
54
  end
55
+
56
+ def reverse_order(scope)
57
+ raise NotImplementedError
58
+ end
55
59
  end
56
60
  end
57
61
  end
@@ -34,11 +34,11 @@ module Datagrid
34
34
  end
35
35
 
36
36
  def greater_equal(scope, field, value)
37
- scope.where(["#{field} >= ?", value])
37
+ scope.where(["#{scope.table_name}.#{field} >= ?", value])
38
38
  end
39
39
 
40
40
  def less_equal(scope, field, value)
41
- scope.where(["#{field} <= ?", value])
41
+ scope.where(["#{scope.table_name}.#{field} <= ?", value])
42
42
  end
43
43
 
44
44
  def has_column?(scope, column_name)
@@ -53,7 +53,6 @@ module Datagrid
53
53
  klass = type.is_a?(Class) ? type : FILTER_TYPES[type]
54
54
  raise ConfigurationError, "filter class #{type.inspect} not found" unless klass
55
55
 
56
- block ||= default_filter(attribute)
57
56
 
58
57
  filter = klass.new(self, attribute, options, &block)
59
58
  self.filters << filter
@@ -65,18 +64,6 @@ module Datagrid
65
64
  end
66
65
 
67
66
  protected
68
- def default_filter(attribute)
69
- check_scope_defined!("Scope should be defined before filters")
70
- if !driver.has_column?(scope, attribute) && driver.to_scope(scope).respond_to?(attribute)
71
- lambda do |value, scope, grid|
72
- grid.driver.to_scope(scope).send(attribute, value)
73
- end
74
- else
75
- lambda do |value, scope, grid|
76
- grid.driver.where(scope, attribute => value)
77
- end
78
- end
79
- end
80
67
 
81
68
  def inherited(child_class)
82
69
  super(child_class)
@@ -3,11 +3,11 @@ class Datagrid::Filters::BaseFilter
3
3
 
4
4
  attr_accessor :grid, :options, :block, :name
5
5
 
6
- def initialize(grid, name, options = {}, &block)
7
- self.grid = grid
6
+ def initialize(grid_class, name, options = {}, &block)
7
+ self.grid = grid_class
8
8
  self.name = name
9
9
  self.options = options
10
- self.block = block
10
+ self.block = block || default_filter_block
11
11
  end
12
12
 
13
13
  def format(value)
@@ -32,11 +32,11 @@ class Datagrid::Filters::BaseFilter
32
32
 
33
33
  def format_values(value)
34
34
  if !self.multiple && value.is_a?(Array)
35
- raise Datagrid::ArgumentError, "#{grid.class}.#{name} filter can not accept Array argument. Use :multiple option."
35
+ raise Datagrid::ArgumentError, "#{grid}##{name} filter can not accept Array argument. Use :multiple option."
36
36
  end
37
37
  values = Array.wrap(value)
38
- values.map! do |value|
39
- self.format(value)
38
+ values.map! do |v|
39
+ self.format(v)
40
40
  end
41
41
  self.multiple ? values : values.first
42
42
  end
@@ -71,5 +71,25 @@ class Datagrid::Filters::BaseFilter
71
71
  :"datagrid_#{self.to_s.demodulize.underscore}"
72
72
  end
73
73
 
74
+ def default_filter_block
75
+ filter = self
76
+ lambda do |value, scope, grid|
77
+ filter.default_filter(value, scope, grid)
78
+ end
79
+ end
80
+
81
+ def default_filter(value, scope, grid)
82
+ driver = grid.driver
83
+ if !driver.has_column?(scope, name) && driver.to_scope(scope).respond_to?(name)
84
+ driver.to_scope(scope).send(name, value)
85
+ else
86
+ default_filter_where(driver, scope, value)
87
+ end
88
+ end
89
+
90
+ def default_filter_where(driver, scope, value)
91
+ driver.where(scope, name => value)
92
+ end
93
+
74
94
  end
75
95
 
@@ -1,12 +1,26 @@
1
+ require "datagrid/filters/ranged_filter"
2
+
1
3
  class Datagrid::Filters::DateFilter < Datagrid::Filters::BaseFilter
2
- #TODO: more smart date normalizer
4
+
5
+ include RangedFilter
6
+
7
+ def apply(grid_object, scope, value)
8
+ if value.is_a?(Range)
9
+ value = value.first.beginning_of_day..value.last.end_of_day
10
+ end
11
+ super(grid_object, scope, value)
12
+ end
13
+
3
14
  def format(value)
4
15
  return nil if value.blank?
16
+ return value if value.is_a?(Range)
5
17
  return value.to_date if value.respond_to?(:to_date)
6
18
  return value unless value.is_a?(String)
19
+ #TODO: more smart date normalizer
7
20
  Date.parse(value)
8
21
  rescue ArgumentError
9
22
  nil
10
23
  end
24
+
11
25
  end
12
26
 
@@ -1,6 +1,12 @@
1
+ require "datagrid/filters/ranged_filter"
2
+
1
3
  class Datagrid::Filters::IntegerFilter < Datagrid::Filters::BaseFilter
4
+
5
+ include RangedFilter
6
+
2
7
  def format(value)
3
8
  return nil if value.blank?
9
+ return value if value.is_a?(Range)
4
10
  value.to_i
5
11
  end
6
12
  end
@@ -0,0 +1,55 @@
1
+ module RangedFilter
2
+
3
+
4
+ def initialize(grid, name, options, &block)
5
+ super(grid, name, options, &block)
6
+ if range?
7
+ options[:multiple] = true
8
+ end
9
+ end
10
+
11
+ def format_values(value)
12
+ result = super(value)
13
+ if range?
14
+ if result.is_a?(Array)
15
+ case result.size
16
+ when 0
17
+ nil
18
+ when 1
19
+ result.first
20
+ when 2
21
+ result
22
+ else
23
+ raise ArgumentError, "Can not create a date range from array of more than two: #{result.inspect}"
24
+ end
25
+ else
26
+ # Simulate single point range
27
+ result..result
28
+ end
29
+
30
+ else
31
+ result
32
+ end
33
+ end
34
+
35
+ def range?
36
+ options[:range]
37
+ end
38
+
39
+ def default_filter_where(driver, scope, value)
40
+ if range? && value.is_a?(Array)
41
+ left, right = value
42
+ if left
43
+ scope = driver.greater_equal(scope, name, left)
44
+ end
45
+ if right
46
+ scope = driver.less_equal(scope, name, right)
47
+ end
48
+ scope
49
+ else
50
+ super(driver, scope, value)
51
+ end
52
+ end
53
+
54
+
55
+ end
@@ -4,15 +4,13 @@ module Datagrid
4
4
  module FormBuilder
5
5
 
6
6
  def datagrid_filter(filter_or_attribute, options = {})
7
- filter = get_filter(filter_or_attribute)
8
- options[:class] ||= ""
9
- options[:class] += " " unless options[:class].blank?
10
- options[:class] += "#{filter.name} #{datagrid_filter_html_class(filter)}"
7
+ filter = datagrid_get_filter(filter_or_attribute)
8
+ options = Datagrid::Utils.add_html_classes(options, filter.name, datagrid_filter_html_class(filter))
11
9
  self.send(filter.form_builder_helper_name, filter, options)
12
10
  end
13
11
 
14
12
  def datagrid_label(filter_or_attribute, options = {})
15
- filter = get_filter(filter_or_attribute)
13
+ filter = datagrid_get_filter(filter_or_attribute)
16
14
  self.label(filter.name, filter.header, options)
17
15
  end
18
16
 
@@ -22,20 +20,19 @@ module Datagrid
22
20
  end
23
21
 
24
22
  def datagrid_boolean_filter(attribute_or_filter, options = {})
25
- check_box(get_attribute(attribute_or_filter), options)
23
+ check_box(datagrid_get_attribute(attribute_or_filter), options)
26
24
  end
27
25
 
28
26
  def datagrid_date_filter(attribute_or_filter, options = {})
29
- attribute = get_attribute(attribute_or_filter)
30
- text_field(attribute, options)
27
+ datagrid_range_filter(:date, attribute_or_filter, options)
31
28
  end
32
29
 
33
30
  def datagrid_default_filter(attribute_or_filter, options = {})
34
- text_field get_attribute(attribute_or_filter), options
31
+ text_field datagrid_get_attribute(attribute_or_filter), options
35
32
  end
36
33
 
37
34
  def datagrid_enum_filter(attribute_or_filter, options = {})
38
- filter = get_filter(attribute_or_filter)
35
+ filter = datagrid_get_filter(attribute_or_filter)
39
36
  if !options.has_key?(:multiple) && filter.multiple
40
37
  options[:multiple] = true
41
38
  end
@@ -43,11 +40,32 @@ module Datagrid
43
40
  end
44
41
 
45
42
  def datagrid_integer_filter(attribute_or_filter, options = {})
46
- filter = get_filter(attribute_or_filter)
43
+ filter = datagrid_get_filter(attribute_or_filter)
47
44
  if filter.multiple && self.object[filter.name].blank?
48
45
  options[:value] = ""
49
46
  end
50
- text_field filter.name, options
47
+ datagrid_range_filter(:integer, filter, options)
48
+ end
49
+
50
+ def datagrid_range_filter(type, attribute_or_filter, options = {})
51
+ filter = datagrid_get_filter(attribute_or_filter)
52
+ if filter.range?
53
+ options = options.merge(:multiple => true)
54
+
55
+ from_options = Datagrid::Utils.add_html_classes(options, "from")
56
+ from_value = object[filter.name].try(:first)
57
+
58
+ to_options = Datagrid::Utils.add_html_classes(options, "to")
59
+ to_value = object[filter.name].try(:last)
60
+ # 2 inputs: "from date" and "to date" to specify a range
61
+ [
62
+ text_field(filter.name, from_options.merge!(:value => from_value)),
63
+ I18n.t("datagrid.misc.#{type}_range_separator", :default => "<span class=\"separator #{type}\"> - </span>"),
64
+ text_field(filter.name, to_options.merge!(:value => to_value))
65
+ ].join.html_safe
66
+ else
67
+ text_field(filter.name, options)
68
+ end
51
69
  end
52
70
 
53
71
  def datagrid_string_filter(attribute_or_filter, options = {})
@@ -58,11 +76,11 @@ module Datagrid
58
76
  datagrid_default_filter(attribute_or_filter, options)
59
77
  end
60
78
 
61
- def get_attribute(attribute_or_filter)
79
+ def datagrid_get_attribute(attribute_or_filter)
62
80
  attribute_or_filter.is_a?(Symbol) ? attribute_or_filter : attribute_or_filter.name
63
81
  end
64
82
 
65
- def get_filter(attribute_or_filter)
83
+ def datagrid_get_filter(attribute_or_filter)
66
84
  if attribute_or_filter.is_a?(Symbol)
67
85
  object.class.filter_by_name(attribute_or_filter) ||
68
86
  raise(Error, "filter #{attribute_or_filter} not found")