dynatable_builder 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,8 @@
1
+ require 'dynatable_builder/version'
2
+ require 'dynatable_builder/search'
3
+ require 'dynatable_builder/builder'
4
+ require 'dynatable_builder/table'
5
+ require 'dynatable_builder/engine' if defined?(Rails)
6
+
7
+ module DynatableBuilder
8
+ end
@@ -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,9 @@
1
+ require 'dynatable_builder/view_helpers'
2
+
3
+ module DynatableBuilder
4
+ class Engine < Rails::Engine
5
+ initializer 'dynatable_builder.view_helpers' do
6
+ ActionView::Base.send :include, DynatableBuilder::ViewHelpers
7
+ end
8
+ end
9
+ 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,3 @@
1
+ module DynatableBuilder
2
+ VERSION = '0.0.1'
3
+ 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