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/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2 do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+/)*(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
|
9
|
+
# Rails example
|
10
|
+
watch(%r{^spec/.+_spec\.rb$})
|
11
|
+
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
12
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
13
|
+
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
14
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
15
|
+
watch('spec/spec_helper.rb') { "spec" }
|
16
|
+
watch('config/routes.rb') { "spec/routing" }
|
17
|
+
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
18
|
+
# Capybara request specs
|
19
|
+
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
|
20
|
+
end
|
21
|
+
|
data/License
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Thomas Sonntag
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/Readme.markdown
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
# gitter
|
2
|
+
|
3
|
+
Ruby library for Rails which enables you to create
|
4
|
+
data grids, i.e table like data with customizable
|
5
|
+
|
6
|
+
* Filters
|
7
|
+
* Sortables columns
|
8
|
+
* Faceted search
|
9
|
+
* Localization
|
10
|
+
|
11
|
+
## Data Grids
|
12
|
+
|
13
|
+
In order to define a grid you need to provide:
|
14
|
+
|
15
|
+
* a scope which returns the objects for the grid's rows
|
16
|
+
* filters that will be used to filter the rows
|
17
|
+
* columns to be displayed
|
18
|
+
|
19
|
+
Example:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
class ArticleGrid << Gitter::Grid
|
23
|
+
|
24
|
+
### First define the source for your data
|
25
|
+
# helpers are accessible by #h
|
26
|
+
scope do
|
27
|
+
Article.where(:owner => h.current_user)
|
28
|
+
end
|
29
|
+
|
30
|
+
### Then you may define filters
|
31
|
+
|
32
|
+
# filter by attribute
|
33
|
+
filter :name
|
34
|
+
|
35
|
+
# filter by multiple columns: filters by :name OR :description
|
36
|
+
filter :search, :columns => [:name, :description]
|
37
|
+
|
38
|
+
# filter by named scope
|
39
|
+
filter :topsellers, :scope => :topsellers
|
40
|
+
|
41
|
+
# customized filter
|
42
|
+
filter :on_stock, do |scope|
|
43
|
+
scope.where('stock > 0')
|
44
|
+
end
|
45
|
+
|
46
|
+
filter :out_of_stock do |scope|
|
47
|
+
scope.where(:stock => 0)
|
48
|
+
end
|
49
|
+
|
50
|
+
# select from given filters
|
51
|
+
filter :availability, :select => [:on_stock, :out_of_stock]
|
52
|
+
|
53
|
+
# add to facets
|
54
|
+
filter :category, :facet => true
|
55
|
+
|
56
|
+
# select among named scopes
|
57
|
+
filter :price_range, :scopes => [:niceprice, :regular]
|
58
|
+
|
59
|
+
# you can provide 'search' like attributes
|
60
|
+
filter :search, :ignore_case => true, :exact => false
|
61
|
+
|
62
|
+
# The former can be abbreviated by
|
63
|
+
search :search
|
64
|
+
|
65
|
+
### Define your data grid
|
66
|
+
|
67
|
+
# show an attribute
|
68
|
+
column :article_no
|
69
|
+
|
70
|
+
# provide a hardcoded header (i18n support also available)
|
71
|
+
column :description, :header => 'Details'
|
72
|
+
|
73
|
+
# make the column sortable
|
74
|
+
column :name, :sort => true
|
75
|
+
|
76
|
+
# customize your data cell
|
77
|
+
column :price, :sort => true do
|
78
|
+
"#{price/100.floor},#{price%100} USD"
|
79
|
+
end
|
80
|
+
|
81
|
+
# helpers are accessible via #h
|
82
|
+
column :details, :header => false do
|
83
|
+
h.link_to 'details', h.edit_article_path(self)
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
[More about filters](https://github.com/tracksun/gitter/wiki/Filters)
|
90
|
+
|
91
|
+
[More about columns](https://github.com/tracksun/gitter/wiki/Columns)
|
92
|
+
|
93
|
+
|
94
|
+
#Rendering your grid
|
95
|
+
|
96
|
+
For the most common use case -- your controller -- you simply do:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
def index
|
100
|
+
@grid = ArticleGrid.new(self)
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
Render you grid:
|
105
|
+
|
106
|
+
```haml
|
107
|
+
%table
|
108
|
+
%tr
|
109
|
+
- @grid.headers.each do |header|
|
110
|
+
%th = header
|
111
|
+
|
112
|
+
- @grid.rows.each do |row|
|
113
|
+
%tr
|
114
|
+
- row.each do |cell|
|
115
|
+
%th = cell
|
116
|
+
```
|
117
|
+
[More about grids](https://github.com/tracksun/gitter/wiki/Grids)
|
118
|
+
|
119
|
+
# Facets
|
120
|
+
|
121
|
+
Render your facets:
|
122
|
+
|
123
|
+
```haml
|
124
|
+
%ul
|
125
|
+
- @grid.facets do |facet|
|
126
|
+
%li
|
127
|
+
= facet.label
|
128
|
+
%ul
|
129
|
+
- facet.data.each do |data|
|
130
|
+
= data.value
|
131
|
+
= link_to "(#{data.count})", data.link
|
132
|
+
|
133
|
+
```
|
134
|
+
[More about facets](https://github.com/tracksun/gitter/wiki/Facets)
|
135
|
+
|
136
|
+
# Breadcrumbs
|
137
|
+
|
138
|
+
Render your breadcrumbs:
|
139
|
+
|
140
|
+
```haml
|
141
|
+
@grid.render_breadcrumbs
|
142
|
+
```
|
143
|
+
|
144
|
+
[More about inputs](https://github.com/tracksun/gitter/wiki/Inputs)
|
145
|
+
|
146
|
+
|
147
|
+
# ORM Support
|
148
|
+
|
149
|
+
* ActiveRecord
|
150
|
+
* others: Help or suggestions are welcome
|
151
|
+
|
152
|
+
|
153
|
+
# Credits
|
154
|
+
|
155
|
+
API inspired by [datagrid](https://github.com/bogdan/datagrid)
|
156
|
+
|
157
|
+
# License
|
158
|
+
|
159
|
+
Gitter is released under the MIT license
|
Binary file
|
Binary file
|
data/gitter.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "gitter/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "gitter"
|
7
|
+
s.version = Gitter::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Thomas Sommtag"]
|
10
|
+
s.email = ["git@sonntagsbox.de"]
|
11
|
+
s.homepage = "http://github.com/tracksun/gitter"
|
12
|
+
s.summary = %q{Ruby gem to define searches, facets and data grids for Rails applications}
|
13
|
+
s.description = <<-EOS
|
14
|
+
To be used within Rails applications.
|
15
|
+
Helps you to define searches with filters and facets
|
16
|
+
and data tables with sortable columns and filters.
|
17
|
+
EOS
|
18
|
+
|
19
|
+
s.rubyforge_project = "gitter"
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split("\n")
|
22
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
23
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
24
|
+
s.require_paths = %w(lib assets)
|
25
|
+
s.add_dependency 'activesupport', '>=3.2'
|
26
|
+
s.add_dependency 'activerecord', '>=3.2'
|
27
|
+
s.add_dependency 'artdeco', '>=1.2'
|
28
|
+
end
|
data/lib/gitter.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module Gitter
|
2
|
+
|
3
|
+
autoload :Version, 'gitter/version'
|
4
|
+
autoload :Grid, 'gitter/grid'
|
5
|
+
autoload :PivotGrid, 'gitter/pivot_grid'
|
6
|
+
autoload :Helper, 'gitter/helper'
|
7
|
+
autoload :Controller, 'gitter/controller'
|
8
|
+
autoload :Table, 'gitter/table'
|
9
|
+
|
10
|
+
class ConfigurationError < StandardError
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
data/lib/gitter/axis.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Gitter
|
2
|
+
|
3
|
+
class Axis
|
4
|
+
|
5
|
+
attr_reader :grid, :name, :attr
|
6
|
+
|
7
|
+
def initialize grid, name, opts = {}
|
8
|
+
@grid, @name = grid, name
|
9
|
+
@attr = opts.delete(:column){name}
|
10
|
+
only = opts.delete(:only){nil}
|
11
|
+
case only
|
12
|
+
when Hash
|
13
|
+
@only_data, @titles= only.keys, only
|
14
|
+
else
|
15
|
+
@only_data, @titles = only, nil
|
16
|
+
end
|
17
|
+
@except = opts.delete(:except){[]}
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def data
|
22
|
+
data = case attr
|
23
|
+
when Symbol,String
|
24
|
+
grid.scope.select(attr).uniq.map(&:"#{attr}").sort
|
25
|
+
else
|
26
|
+
attr
|
27
|
+
end
|
28
|
+
|
29
|
+
data = ((data + @only_data) & @only_data).uniq if @only_data
|
30
|
+
data = data - @except
|
31
|
+
end
|
32
|
+
|
33
|
+
def titles
|
34
|
+
if @titles
|
35
|
+
data.map{|d|@titles[d]}
|
36
|
+
else
|
37
|
+
data
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def data_titles
|
42
|
+
res = {}
|
43
|
+
data.each{|d| res[d] = @titles ? @titles[d] : d}
|
44
|
+
res
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
data/lib/gitter/base.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'artdeco'
|
3
|
+
require 'gitter/filters.rb'
|
4
|
+
require 'gitter/facet.rb'
|
5
|
+
|
6
|
+
module Gitter
|
7
|
+
|
8
|
+
module Base
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def grid &grid
|
13
|
+
if grid
|
14
|
+
@grid = grid
|
15
|
+
else
|
16
|
+
@grid or raise ArgumentError, 'undefined grid'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :params, :decorator, :options, :values
|
22
|
+
|
23
|
+
def initialize *args
|
24
|
+
opts = args.extract_options!
|
25
|
+
@decorator = Artdeco::Decorator.new *args, opts
|
26
|
+
|
27
|
+
@params = @decorator.params.fetch(key){{}}.symbolize_keys
|
28
|
+
|
29
|
+
@filters, @values, @facets = {}, {}, {}
|
30
|
+
scope = opts.delete(:scope){nil}
|
31
|
+
@options = opts.dup
|
32
|
+
|
33
|
+
instance_eval &self.class.grid
|
34
|
+
|
35
|
+
@scope = scope || @scope
|
36
|
+
|
37
|
+
@params.each do |name, value|
|
38
|
+
if filter = @filters[name]
|
39
|
+
@values[name] = value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def filter_value filter_name
|
45
|
+
@values[filter_name]
|
46
|
+
end
|
47
|
+
|
48
|
+
def filters
|
49
|
+
@filters.values
|
50
|
+
end
|
51
|
+
|
52
|
+
def filter_for name
|
53
|
+
@filters[name]
|
54
|
+
end
|
55
|
+
|
56
|
+
def label name
|
57
|
+
filter_for(name).label
|
58
|
+
end
|
59
|
+
|
60
|
+
def driver
|
61
|
+
@driver ||= begin
|
62
|
+
scope = Proc === @scope ? instance_eval(&@scope) : @scope
|
63
|
+
create_driver scope
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def filtered_driver
|
68
|
+
@filter_driver ||= begin
|
69
|
+
d = driver
|
70
|
+
@values.each do |name, value|
|
71
|
+
d = @filters[name].apply d, value
|
72
|
+
end
|
73
|
+
d
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def scope &scope
|
78
|
+
if scope
|
79
|
+
@scope = scope
|
80
|
+
else
|
81
|
+
filtered_driver.scope
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def filter *args, &block
|
86
|
+
opts = args.extract_options!
|
87
|
+
raise ConfigurationError, 'only zero or one argument allowed' if args.size > 1
|
88
|
+
name = args.first
|
89
|
+
|
90
|
+
filter = case
|
91
|
+
when opts.delete(:range)
|
92
|
+
raise ConfigurationError, "no block allowed for range filter #{name}" if block
|
93
|
+
return range_filter name, opts # return is required
|
94
|
+
when block
|
95
|
+
BlockFilter.new self, name, opts, &block
|
96
|
+
when select = opts.delete(:select)
|
97
|
+
if opts[:facet] && opts[:facet] != true
|
98
|
+
opts.merge! values: opts[:facet]
|
99
|
+
end
|
100
|
+
filters = [select].flatten.map{|name| @filters[name] || scope_filter(name)}
|
101
|
+
SelectFilter.new self, name, filters, opts
|
102
|
+
when s = opts.delete(:scope)
|
103
|
+
scope_filter( s == true ? name : s, opts )
|
104
|
+
else
|
105
|
+
if opts[:facet] && opts[:facet] != true
|
106
|
+
opts.merge! values: opts[:facet]
|
107
|
+
end
|
108
|
+
ColumnFilter.new self, name, opts
|
109
|
+
end
|
110
|
+
|
111
|
+
@facets[name] = Facet.new(filter) if opts[:facet]
|
112
|
+
@filters[name] = filter
|
113
|
+
end
|
114
|
+
|
115
|
+
# shortcut for filter name, { exact: false, ignore_case: true, strip_blank: true }.merge(options)
|
116
|
+
def search name, opts = {}
|
117
|
+
filter name, { exact: false, ignore_case: true, strip_blank: true }.merge(opts)
|
118
|
+
end
|
119
|
+
|
120
|
+
def facets
|
121
|
+
@_facets_ ||= @facets.values
|
122
|
+
end
|
123
|
+
|
124
|
+
def facet name
|
125
|
+
@facets[name]
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def range_filter name, opts
|
131
|
+
column = opts.delete(:column){name}
|
132
|
+
|
133
|
+
filter opts.delete(:from){:"from_#{name}"}, opts do |scope, value|
|
134
|
+
create_driver(scope).greater_or_equal(column, value).scope
|
135
|
+
end
|
136
|
+
|
137
|
+
filter opts.delete(:to){:"to_#{name}"}, opts do |scope, value|
|
138
|
+
create_driver(scope).less_or_equal(column, value).scope
|
139
|
+
end
|
140
|
+
|
141
|
+
filter name, :column => column
|
142
|
+
end
|
143
|
+
|
144
|
+
def scope_filter name, opts = {}
|
145
|
+
BlockFilter.new(self,name, opts){|scope| create_driver(scope).named_scope(name).scope}
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|