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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/CODE_OF_CONDUCT.md +13 -0
  5. data/Gemfile +8 -0
  6. data/Guardfile +24 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +134 -0
  9. data/Rakefile +1 -0
  10. data/app/table/assets/css/font-awesome.min.css +4 -0
  11. data/app/table/assets/css/main.scss +66 -0
  12. data/app/table/assets/fonts/FontAwesome.otf +0 -0
  13. data/app/table/assets/fonts/fontawesome-webfont.eot +0 -0
  14. data/app/table/assets/fonts/fontawesome-webfont.svg +655 -0
  15. data/app/table/assets/fonts/fontawesome-webfont.ttf +0 -0
  16. data/app/table/assets/fonts/fontawesome-webfont.woff +0 -0
  17. data/app/table/assets/fonts/fontawesome-webfont.woff2 +0 -0
  18. data/app/table/config/dependencies.rb +1 -0
  19. data/app/table/config/initializers/boot.rb +10 -0
  20. data/app/table/config/routes.rb +1 -0
  21. data/app/table/controllers/columns_controller.rb +101 -0
  22. data/app/table/controllers/footers_controller.rb +19 -0
  23. data/app/table/controllers/headers_controller.rb +98 -0
  24. data/app/table/controllers/main_controller.rb +135 -0
  25. data/app/table/tasks/count_task.rb +15 -0
  26. data/app/table/views/columns/column_head.html +4 -0
  27. data/app/table/views/columns/filter_head.html +36 -0
  28. data/app/table/views/columns/index.html +17 -0
  29. data/app/table/views/footers/index.html +14 -0
  30. data/app/table/views/headers/index.html +62 -0
  31. data/app/table/views/headers/modal.html +34 -0
  32. data/app/table/views/main/body.html +26 -0
  33. data/app/table/views/main/footers.html +1 -0
  34. data/app/table/views/main/index.html +7 -0
  35. data/app/table/views/wrapper/index.html +10 -0
  36. data/lib/volt/table.rb +18 -0
  37. data/lib/volt/table/version.rb +5 -0
  38. data/spec/dummy/.gitignore +9 -0
  39. data/spec/dummy/README.md +4 -0
  40. data/spec/dummy/app/main/assets/css/app.css.scss +1 -0
  41. data/spec/dummy/app/main/config/dependencies.rb +13 -0
  42. data/spec/dummy/app/main/config/initializers/boot.rb +10 -0
  43. data/spec/dummy/app/main/config/routes.rb +14 -0
  44. data/spec/dummy/app/main/controllers/main_controller.rb +67 -0
  45. data/spec/dummy/app/main/lib/seed_db.rb +32 -0
  46. data/spec/dummy/app/main/models/user.rb +15 -0
  47. data/spec/dummy/app/main/views/main/about.html +16 -0
  48. data/spec/dummy/app/main/views/main/index.html +25 -0
  49. data/spec/dummy/app/main/views/main/main.html +29 -0
  50. data/spec/dummy/config.ru +4 -0
  51. data/spec/dummy/config/app.rb +137 -0
  52. data/spec/dummy/config/base/index.html +15 -0
  53. data/spec/dummy/config/initializers/boot.rb +4 -0
  54. data/spec/factories.rb +31 -0
  55. data/spec/integration/table_integration_spec.rb +171 -0
  56. data/spec/spec_helper.rb +19 -0
  57. data/volt-table.gemspec +46 -0
  58. metadata +399 -0
@@ -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,4 @@
1
+ <:Body>
2
+ <th e-click="sort(attrs.sort_name)" class="header {{ header_sort_klass(attrs.sort_name) }}">
3
+ {{ attrs.label }}
4
+ </th>
@@ -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">&gt;</option>
18
+ <option value="$gte">&gt;=</option>
19
+ <option value="$lt">&lt;</option>
20
+ <option value="$lte">&lt;=</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>