gitter 1.1.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/.gitignore +5 -0
- data/.travis.yml +6 -0
- data/Gemfile +14 -0
- data/Guardfile +21 -0
- data/License +20 -0
- data/Rakefile +9 -0
- data/Readme.markdown +159 -0
- data/assets/images/sort_asc.gif +0 -0
- data/assets/images/sort_desc.gif +0 -0
- data/gitter.gemspec +28 -0
- data/lib/gitter.rb +13 -0
- data/lib/gitter/axis.rb +48 -0
- data/lib/gitter/base.rb +149 -0
- data/lib/gitter/breadcrumbs.rb +44 -0
- data/lib/gitter/cell.rb +11 -0
- data/lib/gitter/column.rb +142 -0
- data/lib/gitter/columns.rb +110 -0
- data/lib/gitter/controller.rb +19 -0
- data/lib/gitter/csv.rb +9 -0
- data/lib/gitter/driver.rb +26 -0
- data/lib/gitter/drivers/abstract_driver.rb +36 -0
- data/lib/gitter/drivers/active_record_driver.rb +84 -0
- data/lib/gitter/facet.rb +95 -0
- data/lib/gitter/filters.rb +4 -0
- data/lib/gitter/filters/abstract_filter.rb +94 -0
- data/lib/gitter/filters/block_filter.rb +16 -0
- data/lib/gitter/filters/column_filter.rb +50 -0
- data/lib/gitter/filters/select_filter.rb +43 -0
- data/lib/gitter/grid.rb +23 -0
- data/lib/gitter/header.rb +44 -0
- data/lib/gitter/helpers.rb +44 -0
- data/lib/gitter/i18n.rb +11 -0
- data/lib/gitter/model.rb +40 -0
- data/lib/gitter/pivot.rb +107 -0
- data/lib/gitter/pivot_grid.rb +23 -0
- data/lib/gitter/railtie.rb +8 -0
- data/lib/gitter/table.rb +149 -0
- data/lib/gitter/utils.rb +12 -0
- data/lib/gitter/version.rb +3 -0
- data/spec/breadcrumbs_spec.rb +24 -0
- data/spec/column_filter_spec.rb +80 -0
- data/spec/column_spec.rb +159 -0
- data/spec/facets_spec.rb +75 -0
- data/spec/grid_spec.rb +92 -0
- data/spec/helper_spec.rb +8 -0
- data/spec/i18n_spec.rb +39 -0
- data/spec/inputs_spec.rb +0 -0
- data/spec/locales/de.yml +10 -0
- data/spec/locales/en.yml +10 -0
- data/spec/range_filter_spec.rb +32 -0
- data/spec/scope_filter_spec.rb +12 -0
- data/spec/select_filter_spec.rb +22 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/database.rb +24 -0
- data/spec/support/person_grid.rb +71 -0
- metadata +152 -0
data/lib/gitter/facet.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'gitter/utils'
|
2
|
+
|
3
|
+
module Gitter
|
4
|
+
|
5
|
+
class FacetData
|
6
|
+
include Utils
|
7
|
+
|
8
|
+
attr_reader :facet, :raw_value, :count
|
9
|
+
delegate :grid, :name, to: :facet
|
10
|
+
delegate :h, to: :grid
|
11
|
+
|
12
|
+
def initialize facet, raw_value, label, count
|
13
|
+
@facet, @raw_value, @label, @count = facet, raw_value, label, count
|
14
|
+
end
|
15
|
+
|
16
|
+
def value
|
17
|
+
@value ||= facet.format raw_value
|
18
|
+
end
|
19
|
+
|
20
|
+
def params
|
21
|
+
@params ||= grid.scoped_params name => raw_value
|
22
|
+
end
|
23
|
+
|
24
|
+
def selected?
|
25
|
+
@selected ||= facet.selected_value == raw_value.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
def label
|
29
|
+
#@label || grid.translate(:facets, raw_value) || '-'
|
30
|
+
@label || raw_value || '-'
|
31
|
+
end
|
32
|
+
|
33
|
+
def link
|
34
|
+
@link ||= begin
|
35
|
+
p = grid.params.dup
|
36
|
+
p.delete name
|
37
|
+
p[name] = raw_value if raw_value.present?
|
38
|
+
p = grid.scoped_params p
|
39
|
+
p[:page] = 1
|
40
|
+
|
41
|
+
value_class = selected? ? 'facet_value_selected' : 'selected'
|
42
|
+
value_tag = h.content_tag :span, label, class: value_class
|
43
|
+
value_tag = h.link_to value_tag, url_for(p)
|
44
|
+
|
45
|
+
if selected? or not facet.selected?
|
46
|
+
count_tag = h.content_tag :span, "(#{count})", class: 'facet_count'
|
47
|
+
#count_tag = h.link_to count_tag, url_for(p.merge(show: true))
|
48
|
+
else
|
49
|
+
count_tag = ''
|
50
|
+
end
|
51
|
+
|
52
|
+
h.content_tag :span, (value_tag + count_tag), {class: 'facet_entry'}, false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_s
|
57
|
+
"#{name}:#{value}(#{raw_value})=#{count}"
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
class Facet
|
63
|
+
attr_reader :filter
|
64
|
+
delegate :grid, :name, :selected_value, :selected?, :format, to: :filter
|
65
|
+
|
66
|
+
def initialize filter
|
67
|
+
@filter = filter
|
68
|
+
end
|
69
|
+
|
70
|
+
def label
|
71
|
+
filter.label || grid.translate(:facets, name)
|
72
|
+
end
|
73
|
+
|
74
|
+
def params_for_any
|
75
|
+
grid.scoped_params grid.params.reject{|k,v| k == name}
|
76
|
+
end
|
77
|
+
|
78
|
+
def selected_data opts = {}
|
79
|
+
data(opts).detect{|d|d.selected?}
|
80
|
+
end
|
81
|
+
|
82
|
+
def data opts = {}
|
83
|
+
values_to_counts = filter.counts
|
84
|
+
values = opts[:include_zeros] ? filter.distinct_values(grid.driver) : values_to_counts.keys
|
85
|
+
values.map do |value,label|
|
86
|
+
FacetData.new self, value, label, (values_to_counts[value]||0)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_s
|
91
|
+
"#{self.class}(#{name},label=#{label})"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
class AbstractFilter
|
2
|
+
|
3
|
+
attr_reader :grid, :name, :input_options, :input_tag, :formatter, :order, :label
|
4
|
+
|
5
|
+
def initialize grid, name, opts = {}
|
6
|
+
@grid, @name = grid, name
|
7
|
+
@label = opts.delete(:label){grid.translate(:filters, name)}
|
8
|
+
@input_options = opts.delete(:input){nil}
|
9
|
+
@input_tag = opts.delete(:input_tag){nil}
|
10
|
+
@include_blank = opts.delete(:include_blank){false}
|
11
|
+
|
12
|
+
@formatter = opts.delete(:formatter){nil}
|
13
|
+
@param_scoped = opts.delete(:param_scoped){true}
|
14
|
+
@order = opts.delete(:order){nil}
|
15
|
+
|
16
|
+
# replace shortcut
|
17
|
+
if coll = opts.delete(:input_collection){nil}
|
18
|
+
(@input_options||={})[:collection] = coll
|
19
|
+
end
|
20
|
+
@opts = opts
|
21
|
+
end
|
22
|
+
|
23
|
+
def input?
|
24
|
+
@input_options || @input_tag
|
25
|
+
end
|
26
|
+
|
27
|
+
def include_blank?
|
28
|
+
@include_blank
|
29
|
+
end
|
30
|
+
|
31
|
+
def param_scoped?
|
32
|
+
@param_scoped
|
33
|
+
end
|
34
|
+
|
35
|
+
def selected_value
|
36
|
+
@selected_value ||= grid.filter_value name
|
37
|
+
end
|
38
|
+
|
39
|
+
def selected?
|
40
|
+
selected_value.present?
|
41
|
+
end
|
42
|
+
|
43
|
+
def counts driver = grid.filtered_driver
|
44
|
+
{ true => apply(driver.unordered).count }
|
45
|
+
end
|
46
|
+
|
47
|
+
def distinct_values driver = nil
|
48
|
+
[ true, false ]
|
49
|
+
end
|
50
|
+
|
51
|
+
def input_tag
|
52
|
+
return '' unless input?
|
53
|
+
|
54
|
+
@input_tag ||= if col = collection
|
55
|
+
col = [''] + col if include_blank? && col.size > 1
|
56
|
+
select_tag col
|
57
|
+
else
|
58
|
+
text_field_tag
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def text_field_tag
|
63
|
+
@text_field_tag ||= h.text_field_tag scoped_name, grid.params[name.intern], :class => 'grid'
|
64
|
+
end
|
65
|
+
|
66
|
+
def select_tag collection
|
67
|
+
h.select_tag scoped_name, h.options_for_select(collection, grid.params[name.intern]), :class => 'grid'
|
68
|
+
end
|
69
|
+
|
70
|
+
def format value
|
71
|
+
case formatter
|
72
|
+
when Hash then formatter[value]
|
73
|
+
when Proc then formatter.call value
|
74
|
+
else value;
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def scoped_name
|
80
|
+
"#{grid.key}[#{name}]"
|
81
|
+
end
|
82
|
+
|
83
|
+
def collection
|
84
|
+
input_options.respond_to?(:[]) && input_options[:collection]
|
85
|
+
end
|
86
|
+
|
87
|
+
def h
|
88
|
+
@h ||= grid.h
|
89
|
+
end
|
90
|
+
|
91
|
+
def sort_hash hash
|
92
|
+
hash.keys.sort.inject({}){|memo,k|memo[k] = hash[k]; memo}
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Gitter
|
2
|
+
|
3
|
+
class BlockFilter < AbstractFilter
|
4
|
+
|
5
|
+
def initialize grid, name, options ={}, &block
|
6
|
+
raise ArgumentError, "no block given" unless block
|
7
|
+
@block = block
|
8
|
+
super grid, name, options
|
9
|
+
end
|
10
|
+
|
11
|
+
def apply driver, value = nil
|
12
|
+
driver.new @block.call(driver.scope, value)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Gitter
|
2
|
+
|
3
|
+
class ColumnFilter < AbstractFilter
|
4
|
+
|
5
|
+
attr_reader :columns, :values
|
6
|
+
|
7
|
+
def initialize grid, name, opts = {}
|
8
|
+
@columns = [opts[:column]||opts[:columns]||name].flatten
|
9
|
+
@values = opts[:values]
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def apply driver, value = nil
|
14
|
+
return driver if value.blank?
|
15
|
+
|
16
|
+
attr_values = columns.inject({}){|memo,column| memo[column] = value; memo}
|
17
|
+
driver.where attr_values, @opts
|
18
|
+
end
|
19
|
+
|
20
|
+
def counts driver = grid.filtered_driver
|
21
|
+
if columns.size == 1
|
22
|
+
#sort_hash ordered(driver).group(columns.first).count
|
23
|
+
sort_hash driver.unordered.group(columns.first).count
|
24
|
+
else
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def distinct_values driver = grid.filtered_driver
|
30
|
+
if columns.size == 1
|
31
|
+
values || driver.unordered.distinct_values(columns.first).sort
|
32
|
+
else
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def ordered driver
|
39
|
+
order_attr = case order
|
40
|
+
when true then columns.first
|
41
|
+
when String, Symbol then order
|
42
|
+
else nil;
|
43
|
+
end
|
44
|
+
s = driver.unordered
|
45
|
+
s = s.order(order_attr) if order_attr
|
46
|
+
s
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Gitter
|
2
|
+
|
3
|
+
class SelectFilter < AbstractFilter
|
4
|
+
|
5
|
+
attr_reader :filters, :values
|
6
|
+
|
7
|
+
def initialize grid, name, filters, opts = {}
|
8
|
+
super grid, name, opts
|
9
|
+
@filters = filters.inject({}){|memo,filter| memo[filter.name] = filter; memo}
|
10
|
+
@values = opts[:values]
|
11
|
+
if @input_options == true
|
12
|
+
@input_options = {}
|
13
|
+
@input_options[:collection] = @filters.keys
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def apply driver, value
|
18
|
+
if filter = @filters[value]
|
19
|
+
filter.apply driver
|
20
|
+
else
|
21
|
+
driver
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def counts driver = nil
|
26
|
+
values_names = Hash === values ? values.keys : values
|
27
|
+
@filters.values.inject({}) do |memo,filter|
|
28
|
+
if !values_names || values_names.include?(filter.name)
|
29
|
+
count = filter.counts[true]
|
30
|
+
memo[filter.name] = count if count > 0
|
31
|
+
end
|
32
|
+
memo
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# returns map { :name => label }
|
37
|
+
def distinct_values driver = nil
|
38
|
+
@distinct_values ||= values || @filters.values.inject({}){|memo, filter| memo[filter.name] = filter.label; memo }
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
data/lib/gitter/grid.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'gitter/driver'
|
2
|
+
require 'gitter/base'
|
3
|
+
require 'gitter/columns'
|
4
|
+
require 'gitter/breadcrumbs'
|
5
|
+
require 'gitter/csv'
|
6
|
+
require 'gitter/i18n'
|
7
|
+
require 'gitter/helpers'
|
8
|
+
require 'gitter/model'
|
9
|
+
|
10
|
+
module Gitter
|
11
|
+
|
12
|
+
class Grid
|
13
|
+
include Gitter::Base
|
14
|
+
include Gitter::Driver
|
15
|
+
include Gitter::Columns
|
16
|
+
include Gitter::Breadcrumbs
|
17
|
+
include Gitter::CSV
|
18
|
+
include Gitter::I18n
|
19
|
+
include Gitter::Helpers
|
20
|
+
include Gitter::Model
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Gitter
|
2
|
+
|
3
|
+
class Header
|
4
|
+
def self.blank
|
5
|
+
new :blank, false
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :grid, :content, :html_options, :column
|
9
|
+
|
10
|
+
def initialize grid, *args
|
11
|
+
opts = args.extract_options!
|
12
|
+
raise ArgumentError, 'too many arguments' if args.size > 1
|
13
|
+
@grid = grid
|
14
|
+
@content = args.first
|
15
|
+
@column = opts.delete(:column){nil}
|
16
|
+
@html_options = opts
|
17
|
+
end
|
18
|
+
|
19
|
+
def label
|
20
|
+
@label ||= case content
|
21
|
+
when false then ''
|
22
|
+
when Symbol then grid.translate(:headers, name)
|
23
|
+
else content
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def link *args
|
28
|
+
if column
|
29
|
+
column.link label, *args
|
30
|
+
else
|
31
|
+
label
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def name
|
36
|
+
@name ||= Symbol === content ? content : 'n/a'
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
"Header(#{name},html_options=#{html_options},#{column ? ',col':''},label=#{label})"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Gitter
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
def name
|
5
|
+
@name ||= self.class.name.underscore
|
6
|
+
end
|
7
|
+
|
8
|
+
# used to scope params of requests
|
9
|
+
def key
|
10
|
+
@key ||= :grid#name.intern
|
11
|
+
end
|
12
|
+
|
13
|
+
def scoped_params params
|
14
|
+
{ key => params }
|
15
|
+
end
|
16
|
+
|
17
|
+
def mark *keys
|
18
|
+
{ mark: keys.map{|k|filter_value k}.select{|v|v.present?} }
|
19
|
+
end
|
20
|
+
|
21
|
+
def highlight text, *keys
|
22
|
+
h.highlight "#{text}", mark(*keys)[:mark]
|
23
|
+
end
|
24
|
+
|
25
|
+
def h
|
26
|
+
@decorator.h
|
27
|
+
end
|
28
|
+
|
29
|
+
def decorate *args
|
30
|
+
@decorator.decorate
|
31
|
+
end
|
32
|
+
|
33
|
+
def input_tags
|
34
|
+
@input_tags ||= begin
|
35
|
+
res = {}
|
36
|
+
filters.each do |filter|
|
37
|
+
res[filter.name] = filter.input_tag if filter.input?
|
38
|
+
end
|
39
|
+
res
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
data/lib/gitter/i18n.rb
ADDED
data/lib/gitter/model.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Gitter
|
2
|
+
module Model
|
3
|
+
|
4
|
+
def self.included base
|
5
|
+
base.send :attr_reader, :model
|
6
|
+
end
|
7
|
+
|
8
|
+
# evaluate data (string or proc) in context of grid
|
9
|
+
def eval data, model = nil
|
10
|
+
instance_variable_set :"@model", model
|
11
|
+
attributes.each do |name,block|
|
12
|
+
instance_variable_set :"@#{name}", instance_eval(&block)
|
13
|
+
end
|
14
|
+
|
15
|
+
res = instance_eval &data
|
16
|
+
|
17
|
+
remove_instance_variable :"@model"
|
18
|
+
attributes.each do |name,block|
|
19
|
+
remove_instance_variable :"@#{name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
res
|
23
|
+
end
|
24
|
+
|
25
|
+
def decorate *args
|
26
|
+
@decorator.decorate *args
|
27
|
+
end
|
28
|
+
|
29
|
+
def attribute name, &block
|
30
|
+
self.class.send :attr_reader, name
|
31
|
+
attributes[name] = block or raise ArgumentError, "missing block for model #{model}"
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def attributes
|
36
|
+
@attributes||={}
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|