volt-table 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +8 -0
- data/Guardfile +24 -0
- data/LICENSE.txt +22 -0
- data/README.md +134 -0
- data/Rakefile +1 -0
- data/app/table/assets/css/font-awesome.min.css +4 -0
- data/app/table/assets/css/main.scss +66 -0
- data/app/table/assets/fonts/FontAwesome.otf +0 -0
- data/app/table/assets/fonts/fontawesome-webfont.eot +0 -0
- data/app/table/assets/fonts/fontawesome-webfont.svg +655 -0
- data/app/table/assets/fonts/fontawesome-webfont.ttf +0 -0
- data/app/table/assets/fonts/fontawesome-webfont.woff +0 -0
- data/app/table/assets/fonts/fontawesome-webfont.woff2 +0 -0
- data/app/table/config/dependencies.rb +1 -0
- data/app/table/config/initializers/boot.rb +10 -0
- data/app/table/config/routes.rb +1 -0
- data/app/table/controllers/columns_controller.rb +101 -0
- data/app/table/controllers/footers_controller.rb +19 -0
- data/app/table/controllers/headers_controller.rb +98 -0
- data/app/table/controllers/main_controller.rb +135 -0
- data/app/table/tasks/count_task.rb +15 -0
- data/app/table/views/columns/column_head.html +4 -0
- data/app/table/views/columns/filter_head.html +36 -0
- data/app/table/views/columns/index.html +17 -0
- data/app/table/views/footers/index.html +14 -0
- data/app/table/views/headers/index.html +62 -0
- data/app/table/views/headers/modal.html +34 -0
- data/app/table/views/main/body.html +26 -0
- data/app/table/views/main/footers.html +1 -0
- data/app/table/views/main/index.html +7 -0
- data/app/table/views/wrapper/index.html +10 -0
- data/lib/volt/table.rb +18 -0
- data/lib/volt/table/version.rb +5 -0
- data/spec/dummy/.gitignore +9 -0
- data/spec/dummy/README.md +4 -0
- data/spec/dummy/app/main/assets/css/app.css.scss +1 -0
- data/spec/dummy/app/main/config/dependencies.rb +13 -0
- data/spec/dummy/app/main/config/initializers/boot.rb +10 -0
- data/spec/dummy/app/main/config/routes.rb +14 -0
- data/spec/dummy/app/main/controllers/main_controller.rb +67 -0
- data/spec/dummy/app/main/lib/seed_db.rb +32 -0
- data/spec/dummy/app/main/models/user.rb +15 -0
- data/spec/dummy/app/main/views/main/about.html +16 -0
- data/spec/dummy/app/main/views/main/index.html +25 -0
- data/spec/dummy/app/main/views/main/main.html +29 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/app.rb +137 -0
- data/spec/dummy/config/base/index.html +15 -0
- data/spec/dummy/config/initializers/boot.rb +4 -0
- data/spec/factories.rb +31 -0
- data/spec/integration/table_integration_spec.rb +171 -0
- data/spec/spec_helper.rb +19 -0
- data/volt-table.gemspec +46 -0
- metadata +399 -0
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
# Component dependencies
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# Place any code you want to run when the component is included on the client
|
2
|
+
# or server.
|
3
|
+
|
4
|
+
# To include code only on the client use:
|
5
|
+
# if RUBY_PLATFORM == 'opal'
|
6
|
+
#
|
7
|
+
# To include code only on the server, use:
|
8
|
+
# unless RUBY_PLATFORM == 'opal'
|
9
|
+
# ^^ this will not send compile in code in the conditional to the client.
|
10
|
+
# ^^ this include code required in the conditional.
|
@@ -0,0 +1 @@
|
|
1
|
+
# Component routes
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Table
|
2
|
+
class ColumnsController < Volt::ModelController
|
3
|
+
reactive_accessor :options
|
4
|
+
reactive_accessor :values
|
5
|
+
|
6
|
+
##############
|
7
|
+
# Actions
|
8
|
+
##############
|
9
|
+
|
10
|
+
def index
|
11
|
+
page._column_filt = []
|
12
|
+
end
|
13
|
+
|
14
|
+
##############
|
15
|
+
# Callbacks
|
16
|
+
##############
|
17
|
+
|
18
|
+
##############
|
19
|
+
# Data Sources
|
20
|
+
##############
|
21
|
+
|
22
|
+
##############
|
23
|
+
# Events
|
24
|
+
##############
|
25
|
+
def sort(field)
|
26
|
+
if field
|
27
|
+
toggle_sort_direction(field)
|
28
|
+
params._sort_field = field
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def toggle_sort_direction(field)
|
33
|
+
if params._sort_field == field
|
34
|
+
if params._sort_direction.to_i == 1
|
35
|
+
params._sort_direction = -1
|
36
|
+
else
|
37
|
+
params._sort_direction = 1
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def apply_filters(item, event)
|
43
|
+
page._column_filt = page._column_filt.reject { |h| item.to_s.include? h._col }
|
44
|
+
unless options == nil || values == nil || options == '' || values == ''
|
45
|
+
page._column_filt << {col: "#{item}", option: "#{options}", value: "#{values}" }
|
46
|
+
end
|
47
|
+
`$(#{event.target}).closest('.dropdown').removeClass('open')`
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def reset(item, event)
|
52
|
+
options = ""
|
53
|
+
values = ""
|
54
|
+
if page._column_filt.any? { |x| x._col == item.to_s }
|
55
|
+
page._column_filt = page._column_filt.reject { |h| item.to_s.include? h._col }
|
56
|
+
end
|
57
|
+
`$(#{event.target}).closest('.dropdown').removeClass('open')`
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
|
61
|
+
##############
|
62
|
+
# Display
|
63
|
+
##############
|
64
|
+
def header_sort_klass(label)
|
65
|
+
if params._sort_field == label
|
66
|
+
params._sort_direction.to_i == 1 ? 'headerSortDown' : 'headerSortUp'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def sort_direction(reverse)
|
71
|
+
if reverse
|
72
|
+
params._sort_direction.to_i == 1 ? 'down' : 'up'
|
73
|
+
else
|
74
|
+
params._sort_direction.to_i == 1 ? 'up' : 'down'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def filter_label(filter)
|
79
|
+
if page._column_filt.any? { |x| x._col == filter.to_s }
|
80
|
+
index = page._column_filt.index {|x| x._col == filter.to_s }
|
81
|
+
if page._column_filt[index]._option.to_s == '$ne'
|
82
|
+
"!= #{page._column_filt[index]._value}"
|
83
|
+
elsif page._column_filt[index]._option == '$gt'
|
84
|
+
"> #{page._column_filt[index]._value}"
|
85
|
+
elsif page._column_filt[index]._option == '$lt'
|
86
|
+
"< #{page._column_filt[index]._value}"
|
87
|
+
elsif page._column_filt[index]._option == '$lte'
|
88
|
+
"<= #{page._column_filt[index]._value}"
|
89
|
+
elsif page._column_filt[index]._option == '$gte'
|
90
|
+
">= #{page._column_filt[index]._value}"
|
91
|
+
elsif page._column_filt[index]._option == '$eq'
|
92
|
+
"= #{page._column_filt[index]._value}"
|
93
|
+
else
|
94
|
+
""
|
95
|
+
end
|
96
|
+
else
|
97
|
+
""
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Table
|
2
|
+
class FootersController < Volt::ModelController
|
3
|
+
|
4
|
+
def start_offset
|
5
|
+
(((params._page || 1).to_i - 1) * params._per_page.to_i)
|
6
|
+
end
|
7
|
+
|
8
|
+
def last_item
|
9
|
+
range = start_offset + params._per_page.to_i
|
10
|
+
attrs.table_size.then do |size|
|
11
|
+
if range >= size
|
12
|
+
size
|
13
|
+
else
|
14
|
+
range
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Table
|
2
|
+
class HeadersController < Volt::ModelController
|
3
|
+
reactive_accessor :search_term
|
4
|
+
reactive_accessor :options
|
5
|
+
reactive_accessor :values
|
6
|
+
before_action :set_default_options
|
7
|
+
|
8
|
+
##################
|
9
|
+
# Actions
|
10
|
+
##################
|
11
|
+
def index
|
12
|
+
self.search_term = params._query
|
13
|
+
end
|
14
|
+
|
15
|
+
def modal
|
16
|
+
self.options = []
|
17
|
+
self.values = []
|
18
|
+
end
|
19
|
+
|
20
|
+
######################
|
21
|
+
# callbacks
|
22
|
+
######################
|
23
|
+
def set_default_options
|
24
|
+
params._per_page ||= 10
|
25
|
+
end
|
26
|
+
|
27
|
+
######################
|
28
|
+
# Data Sources
|
29
|
+
######################
|
30
|
+
|
31
|
+
def size
|
32
|
+
params._per_page.to_i
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_table_size(size)
|
36
|
+
params._per_page = size
|
37
|
+
end
|
38
|
+
|
39
|
+
####################
|
40
|
+
# Events
|
41
|
+
####################
|
42
|
+
def toggle_popover
|
43
|
+
if `$('#popover').css('display') == 'none'`
|
44
|
+
`$('#popover').css('display', 'block')`
|
45
|
+
else
|
46
|
+
`$('#popover').css('display', 'none')`
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def apply_filters
|
51
|
+
page._column_filt = []
|
52
|
+
options.each_with_index do |opt, i|
|
53
|
+
unless options[i] == nil || values[i] == nil
|
54
|
+
page._column_filt << {col: "#{search_fields[i]['field']}", option: "#{options[i]}", value: "#{values[i]}" }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
`$('#sortModal').modal('hide');`
|
58
|
+
end
|
59
|
+
|
60
|
+
def search_fields
|
61
|
+
search_fields = []
|
62
|
+
page._table._columns.each do |col|
|
63
|
+
unless col._search_field == nil
|
64
|
+
search_fields << {title: col._title, field: col._field_name, search: col._search_field}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
search_fields
|
68
|
+
end
|
69
|
+
|
70
|
+
def search
|
71
|
+
unless @last_hit.nil? || search_term.length == 1
|
72
|
+
if (Time.now - @last_hit) >= 0.5
|
73
|
+
params._query = search_term
|
74
|
+
else
|
75
|
+
timeout unless @timeout
|
76
|
+
end
|
77
|
+
end
|
78
|
+
@last_hit = Time.now
|
79
|
+
end
|
80
|
+
|
81
|
+
def timeout
|
82
|
+
unless @timeout
|
83
|
+
@timeout = true
|
84
|
+
`setTimeout(function(){#{timeout_search}}, 500)`
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def timeout_search
|
89
|
+
@timeout = false
|
90
|
+
search
|
91
|
+
end
|
92
|
+
|
93
|
+
def clear_filters
|
94
|
+
page._column_filt = []
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module Table
|
2
|
+
class MainController < Volt::ModelController
|
3
|
+
|
4
|
+
def body
|
5
|
+
params._per_page ||= 10
|
6
|
+
params._sort_direction ||= 1
|
7
|
+
page._column_filt ||= []
|
8
|
+
end
|
9
|
+
|
10
|
+
def trigger_row_click(item_id)
|
11
|
+
# event = page._table._default_e_click
|
12
|
+
# event ||=
|
13
|
+
# trigger('row_click', item_id)
|
14
|
+
end
|
15
|
+
|
16
|
+
def td_click(item_id, col_index)
|
17
|
+
event = page._table._columns[col_index]._click_event
|
18
|
+
event ||= page._table._default_click_event
|
19
|
+
trigger(event, item_id, col_index)
|
20
|
+
end
|
21
|
+
|
22
|
+
def start_offset
|
23
|
+
(((params._page || 1).to_i - 1) * params._per_page.to_i)
|
24
|
+
end
|
25
|
+
|
26
|
+
def searched_data
|
27
|
+
if params._query && !params._query.empty?
|
28
|
+
query = build_query
|
29
|
+
else
|
30
|
+
query = column_filters
|
31
|
+
end
|
32
|
+
attrs.source.where(query)
|
33
|
+
end
|
34
|
+
|
35
|
+
def build_query
|
36
|
+
if params._query
|
37
|
+
ands = []
|
38
|
+
# split at commas to get my array of queries (commas are AND)
|
39
|
+
and_pieces = params._query.split(',').map(&:strip)
|
40
|
+
and_pieces.each_with_index do |piece, i|
|
41
|
+
if piece =~ /\|/
|
42
|
+
or_query = []
|
43
|
+
or_pieces = piece.split('|').map(&:strip)
|
44
|
+
or_pieces.each_with_index do |p, i|
|
45
|
+
if /:/.match(p)
|
46
|
+
or_query.push(field_match(p)).flatten!
|
47
|
+
else
|
48
|
+
or_query.push(any_match(p)).flatten!
|
49
|
+
end
|
50
|
+
end
|
51
|
+
ands << {'$or' => or_query}
|
52
|
+
elsif /:/.match(piece) # if part of the query is a specific field search
|
53
|
+
ands << field_match(piece)
|
54
|
+
else
|
55
|
+
ands << {'$or' => any_match(piece)}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
ands << column_filters if column_filters
|
59
|
+
{'$and' => ands}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def field_match(query)
|
64
|
+
clean_string = query.split(':').map(&:strip)
|
65
|
+
if search_fields.has_key?(clean_string[0])
|
66
|
+
{search_fields[clean_string[0]] => {"$regex"=>clean_string[1], "$options"=>"i"}}
|
67
|
+
else
|
68
|
+
{}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def any_match(query)
|
73
|
+
match = []
|
74
|
+
search_fields.values.each do |field|
|
75
|
+
match << {field => { '$regex' => "#{query}", '$options' => 'i' }}
|
76
|
+
end
|
77
|
+
match
|
78
|
+
end
|
79
|
+
|
80
|
+
def column_filters
|
81
|
+
ands = []
|
82
|
+
page._column_filt.each do |filter|
|
83
|
+
ands << {filter._col => {"#{filter._option}" => "#{filter._value}"}}
|
84
|
+
end
|
85
|
+
ands unless ands.empty?
|
86
|
+
end
|
87
|
+
|
88
|
+
def search_fields
|
89
|
+
fields = {}
|
90
|
+
page._table._columns.each do |col|
|
91
|
+
unless col._search_field == nil
|
92
|
+
fields[col._search_field] = col._field_name
|
93
|
+
end
|
94
|
+
end
|
95
|
+
fields
|
96
|
+
end
|
97
|
+
|
98
|
+
def ordered_data
|
99
|
+
if params._sort_direction
|
100
|
+
searched_data.order(params._sort_field => params._sort_direction.to_i)
|
101
|
+
else
|
102
|
+
searched_data
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def current_page
|
107
|
+
per_page = params._per_page.to_i
|
108
|
+
per_page = 10 unless per_page > 0
|
109
|
+
ordered_data.skip(start_offset).limit(per_page)
|
110
|
+
end
|
111
|
+
|
112
|
+
def total_size
|
113
|
+
# TODO: volt-mongo loads the entire collection into memory for counts as of 9-7-15
|
114
|
+
#attrs.total_size || 500 #attrs.source.count
|
115
|
+
query = {}
|
116
|
+
attrs.source.path.then do |path|
|
117
|
+
CountTask.count(path[0], query)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def table_size
|
122
|
+
# TODO: volt-mongo loads the entire collection into memory for counts as of 9-7-15
|
123
|
+
# searched_data.count
|
124
|
+
if params._query && !params._query.empty?
|
125
|
+
query = build_query
|
126
|
+
else
|
127
|
+
query = column_filters
|
128
|
+
end
|
129
|
+
attrs.source.path.then do |path|
|
130
|
+
CountTask.count(path[0], query)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CountTask < Volt::Task
|
2
|
+
def count(data, filter)
|
3
|
+
puts "Volt::DataStore.fetch.db[#{data.to_s}].find(#{filter})"
|
4
|
+
res = Volt::DataStore.fetch.db[data.to_s].find(filter).count
|
5
|
+
puts res
|
6
|
+
res
|
7
|
+
end
|
8
|
+
|
9
|
+
def total_count(data, filter)
|
10
|
+
puts "Volt::DataStore.fetch.db[#{data.to_s}].find(#{filter})"
|
11
|
+
res = Volt::DataStore.fetch.db[data.to_s].find(filter).count
|
12
|
+
puts res
|
13
|
+
res
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<:Body>
|
2
|
+
<th>
|
3
|
+
<div class="filter-label dropdown">
|
4
|
+
{{ if page._column_filt.any? { |x| x._col == attrs.field_name.to_s } }}
|
5
|
+
<span data-toggle="dropdown">{{ filter_label(attrs.field_name) }}</span>
|
6
|
+
{{ else }}
|
7
|
+
<span class="fa fa-filter muted" data-toggle="dropdown"></span>
|
8
|
+
{{ end }}
|
9
|
+
<ul class="dropdown-menu">
|
10
|
+
<form e-submit="apply_filters(attrs.field_name, event)">
|
11
|
+
<li style="padding: 5px;">Filter {{ attrs.label }}</li>
|
12
|
+
<li style="padding: 5px;">
|
13
|
+
<select value="{{options}}" class="form-control">
|
14
|
+
<option value=""></option>
|
15
|
+
<option value="$eq">=</option>
|
16
|
+
<option value="$ne">!=</option>
|
17
|
+
<option value="$gt">></option>
|
18
|
+
<option value="$gte">>=</option>
|
19
|
+
<option value="$lt"><</option>
|
20
|
+
<option value="$lte"><=</option>
|
21
|
+
</select>
|
22
|
+
</li>
|
23
|
+
<li style="padding: 5px;">
|
24
|
+
<input type="text" class="form-control" value="{{ values }}" label=false />
|
25
|
+
</li>
|
26
|
+
<li role="separator" class="divider"></li>
|
27
|
+
<li style="padding: 5px;">
|
28
|
+
<button type="reset" e-click="reset(attrs.field_name, event)" class="btn btn-default btn-block">Reset</button>
|
29
|
+
</li>
|
30
|
+
<li style="padding: 5px;">
|
31
|
+
<button type="submit" class="btn btn-default btn-block">Apply</button>
|
32
|
+
</li>
|
33
|
+
</form>
|
34
|
+
</ul>
|
35
|
+
</div>
|
36
|
+
</th>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<:Body>
|
2
|
+
<thead>
|
3
|
+
<tr>
|
4
|
+
{{ page._table._columns.each do |label| }}
|
5
|
+
{{ if label._shown }}
|
6
|
+
<:column_head label="{{ label._title }}" field_name="{{ label._field_name }}" sort_name="{{ label._sort_name }}" sort_reverse="{{ label._sort_reverse }}"/>
|
7
|
+
{{ end }}
|
8
|
+
{{ end }}
|
9
|
+
</tr>
|
10
|
+
<tr>
|
11
|
+
{{ page._table._columns.each do |label| }}
|
12
|
+
{{ if label._shown }}
|
13
|
+
<:filter_head label="{{ label._title }}" field_name="{{ label._field_name }}" sort_name="{{ label._sort_name }}" sort_reverse="{{ label._sort_reverse }}"/>
|
14
|
+
{{ end }}
|
15
|
+
{{ end }}
|
16
|
+
</tr>
|
17
|
+
</thead>
|