dynatable_builder 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +17 -0
- data/app/assets/javascripts/dynatable_builder.js +34 -0
- data/lib/dynatable_builder.rb +8 -0
- data/lib/dynatable_builder/builder.rb +104 -0
- data/lib/dynatable_builder/engine.rb +9 -0
- data/lib/dynatable_builder/search.rb +63 -0
- data/lib/dynatable_builder/table.rb +74 -0
- data/lib/dynatable_builder/version.rb +3 -0
- data/lib/dynatable_builder/view_helpers.rb +86 -0
- data/lib/generators/dynatable_generator.rb +39 -0
- data/lib/generators/templates/index.html.erb +18 -0
- data/lib/generators/templates/index.html.haml +14 -0
- data/lib/generators/templates/table.rb +35 -0
- data/vendor/assets/javascripts/jquery.dynatable.js +1733 -0
- data/vendor/assets/stylesheets/jquery.dynatable.css +72 -0
- metadata +200 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 13f04ec467ddd3c7e2c423f116f0251ffa29539d
|
4
|
+
data.tar.gz: eec1f0cc9d7c3c6df995c94d83ff2a64e80d5976
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: aae04a0ddb21748cf46e4051d4453fa61e3d4fb1c350151b8b581a85b2efd88acff7b463b9e0ccc3fcdf6a546da8f2e73f611d8f5c55f994403566a0d0ca3ade
|
7
|
+
data.tar.gz: d081ec36eba4928ecf5c902d03bcb51a9ab708245a16bc23b75fc0eee5c56923ba7ea7bbc0f6d46bd73425d1fa0ab9fdd59cfaa5e10fba31d2a3824ab00ff40d
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2014 YOURNAME
|
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
@@ -0,0 +1,17 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'DynatableBuilder'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
Bundler::GemHelper.install_tasks
|
@@ -0,0 +1,34 @@
|
|
1
|
+
function DynatableBuilder(element) {
|
2
|
+
this.table = $(element);
|
3
|
+
this.sortDirection = this.table.data('order') || 'asc';
|
4
|
+
this.sortColumn = this.table.data('sort');
|
5
|
+
this.source = this.table.data('source') || document.URL;
|
6
|
+
};
|
7
|
+
|
8
|
+
DynatableBuilder.SORT_DIRECTIONS = { 'asc': '1', 'desc': '-1' };
|
9
|
+
|
10
|
+
DynatableBuilder.prototype.initialize = function() {
|
11
|
+
this.table.dynatable({
|
12
|
+
dataset: {
|
13
|
+
ajax: true,
|
14
|
+
ajaxUrl: this.source,
|
15
|
+
ajaxOnLoad: true,
|
16
|
+
records: [],
|
17
|
+
sorts: this.defaultSorts()
|
18
|
+
}
|
19
|
+
});
|
20
|
+
};
|
21
|
+
|
22
|
+
DynatableBuilder.prototype.defaultSorts = function() {
|
23
|
+
var sorts = {};
|
24
|
+
if (this.sortColumn) {
|
25
|
+
sorts[this.sortColumn] = DynatableBuilder.SORT_DIRECTIONS[this.sortDirection];
|
26
|
+
}
|
27
|
+
return sorts;
|
28
|
+
};
|
29
|
+
|
30
|
+
$(function() {
|
31
|
+
$('.dynatable').each(function() {
|
32
|
+
new DynatableBuilder(this).initialize();
|
33
|
+
});
|
34
|
+
});
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module DynatableBuilder
|
2
|
+
class Builder
|
3
|
+
attr_reader :columns, :includes, :joins, :defined_scope, :search, :view
|
4
|
+
delegate :total_count, to: :collection
|
5
|
+
|
6
|
+
def initialize(view, defined_scope, opts = {})
|
7
|
+
@view = view
|
8
|
+
@defined_scope = defined_scope
|
9
|
+
|
10
|
+
(opts[:helpers] || []).each { |h| add_helper(h) }
|
11
|
+
|
12
|
+
[:columns, :includes, :joins, :search].each do |arg|
|
13
|
+
instance_variable_set "@#{arg}", opts[arg]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def collection
|
18
|
+
@collection ||= fetch_collection
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_builder
|
22
|
+
Jbuilder.new do |json|
|
23
|
+
json.records do
|
24
|
+
if columns
|
25
|
+
json.array! collection do |object|
|
26
|
+
if columns.respond_to?(:call)
|
27
|
+
generate_object_nodes(json, object)
|
28
|
+
else
|
29
|
+
json.extract!(object, *columns)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
else
|
33
|
+
json.array! collection
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
json.queryRecordCount total_count
|
38
|
+
json.totalRecordCount total_count
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def as_json
|
43
|
+
to_builder.attributes!
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_json
|
47
|
+
to_builder.target!
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_helper(helper = nil, &block)
|
51
|
+
helper = block if block_given?
|
52
|
+
if helper.respond_to?(:call)
|
53
|
+
extend Module.new(&helper)
|
54
|
+
else
|
55
|
+
extend helper
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def respond_to?(method, include_private = false)
|
60
|
+
view.respond_to?(method, include_private) || super
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def generate_object_nodes(json, object)
|
66
|
+
instance_exec(json, object, &columns)
|
67
|
+
end
|
68
|
+
|
69
|
+
def sorts
|
70
|
+
view.params[:sorts] && view.params[:sorts].map do |k, v|
|
71
|
+
"#{k} #{v.to_i < 0 ? 'desc' : 'asc'}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def search_query
|
76
|
+
view.params.key?(:queries) && view.params[:queries][:search]
|
77
|
+
end
|
78
|
+
|
79
|
+
def fetch_collection
|
80
|
+
scope = base_scope
|
81
|
+
scope = scope.joins(joins) if joins.present?
|
82
|
+
scope = scope.includes(includes) if includes.present?
|
83
|
+
scope = Search.new(scope, search).perform(search_query) if search_query
|
84
|
+
scope = scope.order(sorts) if sorts.present?
|
85
|
+
scope.page(view.params[:page]).per(view.params[:perPage])
|
86
|
+
end
|
87
|
+
|
88
|
+
def base_scope
|
89
|
+
if defined_scope.respond_to?(:call)
|
90
|
+
instance_eval &defined_scope
|
91
|
+
else
|
92
|
+
defined_scope
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def method_missing(method, *args, &block)
|
97
|
+
if view.respond_to?(method)
|
98
|
+
view.send(method, *args, &block)
|
99
|
+
else
|
100
|
+
super
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module DynatableBuilder
|
2
|
+
class Search
|
3
|
+
attr_reader :defined_search, :scope
|
4
|
+
|
5
|
+
def initialize(scope, defined_search)
|
6
|
+
@scope = scope
|
7
|
+
@defined_search = defined_search
|
8
|
+
end
|
9
|
+
|
10
|
+
def perform(search_term)
|
11
|
+
if defined_search.present?
|
12
|
+
if defined_search.respond_to?(:call) # is it a proc?
|
13
|
+
defined_search.call(scope, search_term)
|
14
|
+
else
|
15
|
+
query_with_column_list(search_term)
|
16
|
+
end
|
17
|
+
elsif scope.respond_to?(:search)
|
18
|
+
scope.search(search_term)
|
19
|
+
else
|
20
|
+
fail NotDefined, 'You need to either define self.search ' \
|
21
|
+
'on your model or provide a search block in your table.'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def table_name(model)
|
28
|
+
model.to_s.camelize.constantize.table_name
|
29
|
+
rescue NameError => e
|
30
|
+
raise ModelNotFound, "Couldn't find model called #{model.to_s.camelize}: #{e}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def search_columns
|
34
|
+
defined_search.map do |column|
|
35
|
+
next column unless column.respond_to?(:keys)
|
36
|
+
model, col_name = column.first
|
37
|
+
table = table_name(model)
|
38
|
+
|
39
|
+
if col_name.respond_to?(:first) # value is array?
|
40
|
+
col_name.map { |c| "#{table}.#{c}" }
|
41
|
+
else
|
42
|
+
"#{table}.#{col_name}"
|
43
|
+
end
|
44
|
+
end.flatten
|
45
|
+
end
|
46
|
+
|
47
|
+
def query_with_column_list(search_term)
|
48
|
+
sql, values = [], []
|
49
|
+
|
50
|
+
search_columns.each do |column|
|
51
|
+
search_term.downcase.split.each do |word|
|
52
|
+
sql << "lower(#{column}) like ?"
|
53
|
+
values << "%#{word}%"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
scope.where(sql.join(' OR '), *values)
|
58
|
+
end
|
59
|
+
|
60
|
+
class ModelNotFound < StandardError; end
|
61
|
+
class NotDefined < StandardError; end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'jbuilder'
|
2
|
+
|
3
|
+
module DynatableBuilder
|
4
|
+
class Table
|
5
|
+
attr_reader :view, :builder
|
6
|
+
|
7
|
+
delegate :collection, :total_count, :to_builder, to: :builder
|
8
|
+
|
9
|
+
class_attribute :defined_columns, :defined_includes, :defined_joins,
|
10
|
+
:defined_scope, :defined_search, :defined_helpers
|
11
|
+
|
12
|
+
def initialize(view)
|
13
|
+
@view = view
|
14
|
+
@builder = Builder.new(@view, self.class.base_scope, {
|
15
|
+
columns: defined_columns,
|
16
|
+
includes: defined_includes,
|
17
|
+
joins: defined_joins,
|
18
|
+
search: defined_search,
|
19
|
+
helpers: defined_helpers
|
20
|
+
})
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_json(opts = {})
|
24
|
+
builder.to_json
|
25
|
+
end
|
26
|
+
|
27
|
+
def as_json(opts = {})
|
28
|
+
builder.as_json
|
29
|
+
end
|
30
|
+
|
31
|
+
class << self
|
32
|
+
def scope(model_name = nil, &block)
|
33
|
+
self.defined_scope = block_given? ? block : model_name
|
34
|
+
end
|
35
|
+
|
36
|
+
def columns(*cols, &block)
|
37
|
+
if block_given?
|
38
|
+
self.defined_columns = block
|
39
|
+
else
|
40
|
+
(self.defined_columns ||= []) << cols
|
41
|
+
self.defined_columns.flatten!
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def search(*cols, &block)
|
46
|
+
if block_given?
|
47
|
+
self.defined_search = block
|
48
|
+
else
|
49
|
+
(self.defined_search ||= []) << cols
|
50
|
+
self.defined_search.flatten!
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def includes(*args)
|
55
|
+
(self.defined_includes ||= []) << args
|
56
|
+
self.defined_includes.flatten!
|
57
|
+
end
|
58
|
+
|
59
|
+
def joins(*args)
|
60
|
+
(self.defined_joins ||= []) << args
|
61
|
+
self.defined_joins.flatten!
|
62
|
+
end
|
63
|
+
|
64
|
+
def helpers(*mod_or_mods, &block)
|
65
|
+
(self.defined_helpers ||= []) << (block || mod_or_mods)
|
66
|
+
self.defined_helpers.flatten!
|
67
|
+
end
|
68
|
+
|
69
|
+
def base_scope
|
70
|
+
defined_scope.presence || name.gsub(/Table/, '').singularize.constantize
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module DynatableBuilder
|
2
|
+
module ViewHelpers
|
3
|
+
class Builder
|
4
|
+
include ActionView::Context
|
5
|
+
include ActionView::Helpers::TagHelper
|
6
|
+
|
7
|
+
attr_reader :view, :columns, :table_opts
|
8
|
+
|
9
|
+
def initialize(view, opts = {})
|
10
|
+
@view = view
|
11
|
+
@columns = []
|
12
|
+
@table_opts = opts
|
13
|
+
end
|
14
|
+
|
15
|
+
def column(title, opts = {})
|
16
|
+
col_opts, dyna_opts = { title: title }, {}
|
17
|
+
col_opts.merge! opts.except(:model, :attribute, :sort)
|
18
|
+
|
19
|
+
dyna_opts[:dynatable_column] = opts[:attribute] || title.underscore.tr(' ', '_')
|
20
|
+
|
21
|
+
if opts[:sort] == false
|
22
|
+
dyna_opts[:dynatable_no_sort] = true
|
23
|
+
else
|
24
|
+
dyna_opts[:dynatable_sorts] = opts[:sort] || dyna_opts[:dynatable_column].dup
|
25
|
+
|
26
|
+
if model_name = opts[:model] || table_opts[:model]
|
27
|
+
dyna_opts[:dynatable_sorts].prepend "#{table_name(model_name)}."
|
28
|
+
end
|
29
|
+
|
30
|
+
if direction = opts[:default_sort]
|
31
|
+
table_opts[:sort] = dyna_opts[:dynatable_sorts]
|
32
|
+
table_opts[:order] = direction
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
columns << col_opts.merge(data: dyna_opts)
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def evaluate(&block)
|
41
|
+
with_output_buffer { instance_eval(&block) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def render
|
45
|
+
# Without an id, multiple dynatables will conflict
|
46
|
+
table_opts[:id] ||= "dynatable#{rand(1..100)}"
|
47
|
+
table_opts[:class] = [table_opts[:class], 'dynatable'].compact.join(' ')
|
48
|
+
(table_opts[:data] ||= {}).merge! table_opts.slice(:source, :sort, :order)
|
49
|
+
|
50
|
+
content_tag(:table, table_opts.except(:model, :source, :sort, :order)) do
|
51
|
+
content_tag(:thead) do
|
52
|
+
columns.map do |column_opts|
|
53
|
+
content_tag(:th, column_opts[:title], column_opts.except(:title))
|
54
|
+
end.join('').html_safe
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def respond_to?(method, include_private = false)
|
60
|
+
view.respond_to?(method, include_private) || super
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def method_missing(method, *args, &block)
|
66
|
+
if view.respond_to?(method)
|
67
|
+
view.send(method, *args, &block)
|
68
|
+
else
|
69
|
+
super
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def table_name(model_name)
|
74
|
+
model_name.to_s.camelize.constantize.table_name
|
75
|
+
rescue NameError
|
76
|
+
raise "Couldn't find a model named #{model_name}. Maybe you mispelled it?"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def dynatable(opts = {}, &block)
|
81
|
+
builder = DynatableBuilder::ViewHelpers::Builder.new(self, opts)
|
82
|
+
builder.evaluate &block
|
83
|
+
builder.render
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class DynatableGenerator < Rails::Generators::Base
|
2
|
+
source_root File.expand_path('../templates', __FILE__)
|
3
|
+
argument :model_name, type: :string
|
4
|
+
|
5
|
+
def create_table_file
|
6
|
+
template 'table.rb', "app/tables/#{plural_instance_name}_table.rb"
|
7
|
+
end
|
8
|
+
|
9
|
+
def create_view
|
10
|
+
app = ::Rails.application
|
11
|
+
ext = app.config.generators.options[:rails][:template_engine] || :erb
|
12
|
+
template "index.html.#{ext}", "app/views/#{plural_instance_name}/index.html.#{ext}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def notify_controller_info
|
16
|
+
puts <<-EOS
|
17
|
+
\nChange your index route in app/controllers/#{plural_instance_name}_controller.rb to the following:
|
18
|
+
|
19
|
+
def index
|
20
|
+
respond_to do |format|
|
21
|
+
format.html
|
22
|
+
format.json { render json: #{model.name.pluralize}Table.new(view_context).to_builder.target! }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
EOS
|
26
|
+
end
|
27
|
+
|
28
|
+
def model
|
29
|
+
model_name.camelize.constantize
|
30
|
+
end
|
31
|
+
|
32
|
+
def model_instance_name
|
33
|
+
model_name.underscore
|
34
|
+
end
|
35
|
+
|
36
|
+
def plural_instance_name
|
37
|
+
model_instance_name.pluralize
|
38
|
+
end
|
39
|
+
end
|