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.
Files changed (56) hide show
  1. data/.gitignore +5 -0
  2. data/.travis.yml +6 -0
  3. data/Gemfile +14 -0
  4. data/Guardfile +21 -0
  5. data/License +20 -0
  6. data/Rakefile +9 -0
  7. data/Readme.markdown +159 -0
  8. data/assets/images/sort_asc.gif +0 -0
  9. data/assets/images/sort_desc.gif +0 -0
  10. data/gitter.gemspec +28 -0
  11. data/lib/gitter.rb +13 -0
  12. data/lib/gitter/axis.rb +48 -0
  13. data/lib/gitter/base.rb +149 -0
  14. data/lib/gitter/breadcrumbs.rb +44 -0
  15. data/lib/gitter/cell.rb +11 -0
  16. data/lib/gitter/column.rb +142 -0
  17. data/lib/gitter/columns.rb +110 -0
  18. data/lib/gitter/controller.rb +19 -0
  19. data/lib/gitter/csv.rb +9 -0
  20. data/lib/gitter/driver.rb +26 -0
  21. data/lib/gitter/drivers/abstract_driver.rb +36 -0
  22. data/lib/gitter/drivers/active_record_driver.rb +84 -0
  23. data/lib/gitter/facet.rb +95 -0
  24. data/lib/gitter/filters.rb +4 -0
  25. data/lib/gitter/filters/abstract_filter.rb +94 -0
  26. data/lib/gitter/filters/block_filter.rb +16 -0
  27. data/lib/gitter/filters/column_filter.rb +50 -0
  28. data/lib/gitter/filters/select_filter.rb +43 -0
  29. data/lib/gitter/grid.rb +23 -0
  30. data/lib/gitter/header.rb +44 -0
  31. data/lib/gitter/helpers.rb +44 -0
  32. data/lib/gitter/i18n.rb +11 -0
  33. data/lib/gitter/model.rb +40 -0
  34. data/lib/gitter/pivot.rb +107 -0
  35. data/lib/gitter/pivot_grid.rb +23 -0
  36. data/lib/gitter/railtie.rb +8 -0
  37. data/lib/gitter/table.rb +149 -0
  38. data/lib/gitter/utils.rb +12 -0
  39. data/lib/gitter/version.rb +3 -0
  40. data/spec/breadcrumbs_spec.rb +24 -0
  41. data/spec/column_filter_spec.rb +80 -0
  42. data/spec/column_spec.rb +159 -0
  43. data/spec/facets_spec.rb +75 -0
  44. data/spec/grid_spec.rb +92 -0
  45. data/spec/helper_spec.rb +8 -0
  46. data/spec/i18n_spec.rb +39 -0
  47. data/spec/inputs_spec.rb +0 -0
  48. data/spec/locales/de.yml +10 -0
  49. data/spec/locales/en.yml +10 -0
  50. data/spec/range_filter_spec.rb +32 -0
  51. data/spec/scope_filter_spec.rb +12 -0
  52. data/spec/select_filter_spec.rb +22 -0
  53. data/spec/spec_helper.rb +31 -0
  54. data/spec/support/database.rb +24 -0
  55. data/spec/support/person_grid.rb +71 -0
  56. metadata +152 -0
@@ -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,4 @@
1
+ require 'gitter/filters/abstract_filter'
2
+ require 'gitter/filters/block_filter'
3
+ require 'gitter/filters/select_filter'
4
+ require 'gitter/filters/column_filter'
@@ -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
@@ -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
@@ -0,0 +1,11 @@
1
+ require 'i18n'
2
+
3
+ module Gitter
4
+ module I18n
5
+
6
+ def translate prefix, key
7
+ ::I18n.translate "gitter.#{name}.#{prefix}.#{key}", default: key.to_s.humanize
8
+ end
9
+
10
+ end
11
+ end
@@ -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