datagrid 0.6.4 → 0.7.0

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