datagrid 0.0.3
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/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +26 -0
- data/Gemfile.lock +111 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +49 -0
- data/Readme.md +88 -0
- data/VERSION +1 -0
- data/datagrid.gemspec +106 -0
- data/lib/datagrid.rb +37 -0
- data/lib/datagrid/columns.rb +105 -0
- data/lib/datagrid/columns/column.rb +46 -0
- data/lib/datagrid/core.rb +99 -0
- data/lib/datagrid/filters.rb +150 -0
- data/lib/datagrid/filters/base_filter.rb +33 -0
- data/lib/datagrid/filters/boolean_enum_filter.rb +21 -0
- data/lib/datagrid/filters/boolean_filter.rb +7 -0
- data/lib/datagrid/filters/composite_filters.rb +41 -0
- data/lib/datagrid/filters/date_filter.rb +10 -0
- data/lib/datagrid/filters/default_filter.rb +5 -0
- data/lib/datagrid/filters/enum_filter.rb +32 -0
- data/lib/datagrid/filters/filter_eval.rb +25 -0
- data/lib/datagrid/filters/integer_filter.rb +7 -0
- data/lib/datagrid/form_builder.rb +56 -0
- data/lib/datagrid/helper.rb +80 -0
- data/lib/datagrid/rspec.rb +68 -0
- data/spec/datagrid/form_builder_spec.rb +68 -0
- data/spec/datagrid/helper_spec.rb +46 -0
- data/spec/datagrid_spec.rb +56 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/equal_to_dom.rb +42 -0
- data/spec/support/schema.rb +33 -0
- data/spec/support/simple_report.rb +28 -0
- metadata +278 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
module Datagrid
|
2
|
+
module Filters
|
3
|
+
module CompositeFilters
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
base.class_eval do
|
8
|
+
|
9
|
+
end
|
10
|
+
base.send :include, InstanceMethods
|
11
|
+
end # self.included
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
|
15
|
+
|
16
|
+
def date_range_filters(field, from_name = :"from_#{field}", to_name = :"to_#{field}")
|
17
|
+
filter(from_name, :date) do |date|
|
18
|
+
self.from_date(date, field)
|
19
|
+
end
|
20
|
+
filter(to_name, :date) do |date|
|
21
|
+
self.to_date(date, field)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def integer_range_filters(field, from_name = :"from_#{field}", to_name = :"to_#{field}")
|
26
|
+
filter(from_name, :integer) do |value|
|
27
|
+
self.scoped(:conditions => "#{field} >= #{value}")
|
28
|
+
end
|
29
|
+
filter(to_name, :integer) do |value|
|
30
|
+
self.scoped(:conditions => "#{field} <= #{value}")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end # ClassMethods
|
34
|
+
|
35
|
+
module InstanceMethods
|
36
|
+
|
37
|
+
end # InstanceMethods
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Datagrid::Filters::EnumFilter < Datagrid::Filters::BaseFilter
|
2
|
+
|
3
|
+
def initialize(*args)
|
4
|
+
super(*args)
|
5
|
+
raise Datagrid::ConfigurationError, ":select option not specified" unless select
|
6
|
+
end
|
7
|
+
def format(value)
|
8
|
+
values = Array.new([*value])
|
9
|
+
values.reject do |value|
|
10
|
+
#TODO: really impelement #strict option
|
11
|
+
self.strict && !select.include?(value)
|
12
|
+
end
|
13
|
+
self.multiple ? values : values.first
|
14
|
+
end
|
15
|
+
|
16
|
+
def select
|
17
|
+
self.options[:select]
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def include_blank
|
22
|
+
self.options.has_key?(:include_blank) ? options[:include_blank] : true
|
23
|
+
end
|
24
|
+
|
25
|
+
def strict
|
26
|
+
self.options[:strict]
|
27
|
+
end
|
28
|
+
|
29
|
+
def multiple
|
30
|
+
self.options[:multiple]
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# ActiveRecord is a little brain fuck.
|
2
|
+
# We can not call instance_eval on ActiveRecord::Relation class
|
3
|
+
# because it will automatically convert it to an array because #instance_eval
|
4
|
+
# is not included in the method list that do not cause force result loading
|
5
|
+
# That is why we need thi helper class
|
6
|
+
class Datagrid::Filters::FilterEval
|
7
|
+
|
8
|
+
def initialize(filter, scope, value)
|
9
|
+
@filter = filter
|
10
|
+
@scope = scope
|
11
|
+
@value = value
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
instance_exec @value, &(@filter.block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(meth, *args, &blk)
|
19
|
+
if @scope.respond_to?(meth)
|
20
|
+
@scope.send(meth, *args, &blk)
|
21
|
+
else
|
22
|
+
super(meth, *args, &blk)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "action_view"
|
2
|
+
|
3
|
+
module Datagrid
|
4
|
+
module FormBuilder
|
5
|
+
|
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_class(filter.class)}"
|
11
|
+
self.send(:"datagrid_#{filter.class.to_s.underscore.split('/').last}", filter, options)
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
def datagrid_boolean_enum_filter(attribute_or_filter, options = {})
|
16
|
+
datagrid_enum_filter(attribute_or_filter, options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def datagrid_boolean_filter(attribute_or_filter, options = {})
|
20
|
+
check_box(get_attribute(attribute_or_filter), options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def datagrid_date_filter(attribute_or_filter, options = {})
|
24
|
+
attribute = get_attribute(attribute_or_filter)
|
25
|
+
text_field(attribute, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def datagrid_default_filter(attribute_or_filter, options = {})
|
29
|
+
text_field get_attribute(attribute_or_filter), options
|
30
|
+
end
|
31
|
+
|
32
|
+
def datagrid_enum_filter(attribute_or_filter, options = {})
|
33
|
+
filter = get_filter(attribute_or_filter)
|
34
|
+
select filter.name, filter.select || [], {:include_blank => filter.include_blank}, {:multiple => filter.multiple}.merge(options)
|
35
|
+
end
|
36
|
+
|
37
|
+
def datagrid_integer_filter(attribute_or_filter, options = {})
|
38
|
+
text_field get_attribute(attribute_or_filter), options
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_attribute(attribute_or_filter)
|
42
|
+
attribute_or_filter.is_a?(Symbol) ? attribute_or_filter : attribute_or_filter.name
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_filter(attribute_or_filter)
|
46
|
+
attribute_or_filter.is_a?(Symbol) ? object.class.filter_by_name(attribute_or_filter) : attribute_or_filter
|
47
|
+
end
|
48
|
+
|
49
|
+
def datagrid_filter_class(klass)
|
50
|
+
klass.to_s.split("::").last.underscore
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
ActionView::Helpers::FormBuilder.send(:include, Datagrid::FormBuilder)
|
56
|
+
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require "action_view"
|
2
|
+
|
3
|
+
module Datagrid
|
4
|
+
module Helper
|
5
|
+
|
6
|
+
def datagrid_format_value(column, asset)
|
7
|
+
value = column.value(asset)
|
8
|
+
if column.options[:url]
|
9
|
+
link_to(value, column.options[:url].call(asset))
|
10
|
+
else
|
11
|
+
case column.format
|
12
|
+
when :url
|
13
|
+
link_to(column.label ? asset.send(column.label) : I18n.t("datagrid.table.url_label", :default => "URL"), value)
|
14
|
+
else
|
15
|
+
value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def datagrid_table(report, *args)
|
21
|
+
options = args.extract_options!
|
22
|
+
html = options[:html] || {}
|
23
|
+
html[:class] ||= "datagrid"
|
24
|
+
paginate = options[:paginate] || {}
|
25
|
+
paginate[:page] ||= params[:page]
|
26
|
+
assets = report.assets.paginate(paginate)
|
27
|
+
content_tag(:table, html) do
|
28
|
+
table = content_tag(:tr, datagrid_header(report, options))
|
29
|
+
table << datagrid_rows(report.columns, assets, options)
|
30
|
+
table
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def datagrid_header(grid, options)
|
37
|
+
header = empty_string
|
38
|
+
grid.columns.each do |column|
|
39
|
+
data = column.header.html_safe
|
40
|
+
if column.order
|
41
|
+
data << datagrid_order_for(grid, column)
|
42
|
+
end
|
43
|
+
header << content_tag(:th, data)
|
44
|
+
end
|
45
|
+
header
|
46
|
+
end
|
47
|
+
|
48
|
+
def datagrid_rows(columns, assets, options)
|
49
|
+
rows = empty_string
|
50
|
+
assets.each do |asset|
|
51
|
+
rows << content_tag(:tr, :class => cycle("odd", "even")) do
|
52
|
+
html = empty_string
|
53
|
+
columns.each do |column|
|
54
|
+
html << content_tag(:td, datagrid_format_value(column, asset))
|
55
|
+
end
|
56
|
+
html
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
rows
|
61
|
+
end
|
62
|
+
|
63
|
+
def datagrid_order_for(grid, column)
|
64
|
+
content_tag(:div, :class => "order") do
|
65
|
+
link_to(
|
66
|
+
I18n.t("datagrid.table.order.asc", :default => "ASC"), url_for(grid.param_name => grid.attributes.merge(:order => column.name))
|
67
|
+
) + " " +
|
68
|
+
link_to(I18n.t("datagrid.table.order.desc", :default => "DESC"), url_for(grid.param_name => grid.attributes.merge(:order => column.name, :reverse => true )))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def empty_string
|
73
|
+
res = ""
|
74
|
+
res.respond_to?(:html_safe) ? res.html_safe : res
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
::ActionView::Base.send(:include, ::Datagrid::Helper)
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
shared_examples_for "Datagrid" do
|
2
|
+
describe "as Datagrid" do
|
3
|
+
|
4
|
+
it "should have at least one entry if assets" do
|
5
|
+
subject.assets.should_not be_empty
|
6
|
+
end
|
7
|
+
|
8
|
+
its(:data) {should_not be_empty}
|
9
|
+
|
10
|
+
described_class.columns.each do |column|
|
11
|
+
describe "column ##{column.name}" do
|
12
|
+
|
13
|
+
it "should has value in #data_hash" do
|
14
|
+
subject.data_hash.first.should have_key(column.name)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should support order" do
|
18
|
+
subject.order = column.name
|
19
|
+
subject.assets.first.should_not be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should support reverse order" do
|
23
|
+
subject.reverse = true
|
24
|
+
subject.assets.first.should_not be_nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
described_class.filters.each do |filter|
|
31
|
+
describe "filter ##{filter.name}" do
|
32
|
+
|
33
|
+
let(:filter_value) do
|
34
|
+
|
35
|
+
case Datagrid::Filters::FILTER_TYPES.invert[filter.class]
|
36
|
+
when :default, :string
|
37
|
+
"text"
|
38
|
+
when :date
|
39
|
+
1.day.ago
|
40
|
+
when :eboolean
|
41
|
+
Datagrid::Filters::BooleanEnumFilter::YES
|
42
|
+
when :boolean
|
43
|
+
true
|
44
|
+
when :integer
|
45
|
+
1
|
46
|
+
when :enum
|
47
|
+
select = filter.select
|
48
|
+
select = select.call(subject) if select.respond_to?(:call)
|
49
|
+
select.first.try(:last)
|
50
|
+
else
|
51
|
+
raise "unknown filter type: #{filter.class}"
|
52
|
+
end.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
before(:each) do
|
56
|
+
subject.attributes = {filter.name => filter_value}
|
57
|
+
subject.send(filter.name).should_not be_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should be supported" do
|
61
|
+
subject.assets.should_not be_nil
|
62
|
+
#TODO: better matcher.
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
class MyFormBuilder
|
5
|
+
include Datagrid::FormBuilder
|
6
|
+
end
|
7
|
+
|
8
|
+
class MyTemplate
|
9
|
+
include ActionView::Helpers::FormHelper
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
describe Datagrid::FormBuilder do
|
14
|
+
|
15
|
+
let(:template) { ActionView::Base.new}
|
16
|
+
let(:grid) { SimpleReport.new }
|
17
|
+
let(:view) { ActionView::Helpers::FormBuilder.new(:report, grid, template, {}, Proc.new {|f| })}
|
18
|
+
subject { view }
|
19
|
+
|
20
|
+
|
21
|
+
describe ".datagrid_filter" do
|
22
|
+
|
23
|
+
subject { view.datagrid_filter(_filter)}
|
24
|
+
context "with default filter type" do
|
25
|
+
let(:_filter) { :name }
|
26
|
+
it { should equal_to_dom(
|
27
|
+
'<input class="name default_filter" id="report_name" name="report[name]" size="30" type="text"/>'
|
28
|
+
)}
|
29
|
+
end
|
30
|
+
context "with integer filter type" do
|
31
|
+
let(:_filter) { :group_id }
|
32
|
+
it { should equal_to_dom(
|
33
|
+
'<input class="group_id integer_filter" id="report_group_id" name="report[group_id]" size="30" type="text"/>'
|
34
|
+
)}
|
35
|
+
end
|
36
|
+
context "with enum filter type" do
|
37
|
+
let(:_filter) { :category }
|
38
|
+
it { should equal_to_dom(
|
39
|
+
'<select class="category enum_filter" id="report_category" name="report[category][]"><option value=""></option>
|
40
|
+
<option value="first">first</option>
|
41
|
+
<option value="second">second</option></select>'
|
42
|
+
)}
|
43
|
+
context "when first option is selected" do
|
44
|
+
before(:each) do
|
45
|
+
grid.category = "first"
|
46
|
+
end
|
47
|
+
it { should equal_to_dom(
|
48
|
+
'<select class="category enum_filter" id="report_category" name="report[category][]"><option value=""></option>
|
49
|
+
<option value="first" selected="true">first</option>
|
50
|
+
<option value="second">second</option></select>'
|
51
|
+
)}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "with eboolean filter type" do
|
56
|
+
let(:_filter) { :disabled }
|
57
|
+
it { should equal_to_dom(
|
58
|
+
'<select class="disabled boolean_enum_filter" id="report_disabled" name="report[disabled][]"><option value=""></option>
|
59
|
+
<option value="NO">NO</option>
|
60
|
+
<option value="YES">YES</option></select>'
|
61
|
+
)}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require "will_paginate"
|
3
|
+
require "active_support/core_ext/hash"
|
4
|
+
require "active_support/core_ext/object"
|
5
|
+
|
6
|
+
describe Datagrid::Helper do
|
7
|
+
subject {ActionView::Base.new}
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
subject.stub!(:params).and_return({})
|
11
|
+
subject.stub(:url_for) do |options|
|
12
|
+
options.to_param
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:group) { Group.create!(:name => "Pop") }
|
18
|
+
let!(:entry) { Entry.create!(
|
19
|
+
:group => group, :name => "Star", :disabled => false, :confirmed => false, :category => "first"
|
20
|
+
) }
|
21
|
+
let(:grid) { SimpleReport.new }
|
22
|
+
|
23
|
+
describe ".report_table" do
|
24
|
+
before(:each) do
|
25
|
+
subject.stub!(:datagrid_order_for).and_return(subject.content_tag(:div, "", :class => "order"))
|
26
|
+
end
|
27
|
+
it "should return data table html" do
|
28
|
+
subject.datagrid_table(grid).should equal_to_dom(
|
29
|
+
'<table class="datagrid">
|
30
|
+
<tr>
|
31
|
+
<th>Group<div class="order"></div>
|
32
|
+
</th>
|
33
|
+
<th>Name<div class="order"></div>
|
34
|
+
</th>
|
35
|
+
</tr>
|
36
|
+
|
37
|
+
<tr class="odd">
|
38
|
+
<td>Pop</td>
|
39
|
+
<td>Star</td>
|
40
|
+
</tr>
|
41
|
+
</table>')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
end
|