datatables 1.0.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.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in datatables.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2004-2011 Caseproof, LLC
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.
21
+
@@ -0,0 +1,134 @@
1
+ = Datatables
2
+
3
+ Datatables is a Rails 3 plugin that enables the easy creation of dynamic datatable views on top of any ActiveRecord model. Datatables provides a simple helper that can be utilized in the view to automatically display a dynamic view on top of a model. Datatables handles the entire front end and backend support to do this.
4
+
5
+ = Installation
6
+
7
+ To install datatables to your rails project, follow these simple steps:
8
+
9
+ ==== 1. Make sure your project is using the 'jquery-rails' plugin.
10
+ ==== 2. Add the following line to your Gemfile:
11
+ gem 'datatables', :git => 'git://github.com/Caseproof/datatables.git'
12
+ ==== 3. Run <b>bundle install</b>
13
+ ==== 4. Run <b>rails generate datatables:install</b>
14
+ ==== 5. Add the following lines to your layout file after <b>javascript_include_tag :defaults</b>
15
+ <%= stylesheet_link_tag "datatable_page" %>
16
+ <%= stylesheet_link_tag "datatable_table" %>
17
+ <%= javascript_include_tag 'jquery.dataTables.min' %>
18
+ ==== 6. Add the Datatable helper mixin to your ApplicationHelper (<b>app/helpers/application_helper.rb</b>) like so:
19
+ module ApplicationHelper
20
+ include Datatables::Helpers
21
+ end
22
+
23
+ = Example
24
+
25
+ With datatables it's easy to add rich datatables to your views that correspond with your active record models.
26
+
27
+ There is a lovely helper that you can use to render your datatable that takes in a whole host of options. Unfortunately, there's still quite a bit of work to do with these options to handle every scenario but here's what it support so far:
28
+
29
+ == Database table:
30
+
31
+ The following examples will use the following database table:
32
+
33
+ mysql> desc companies;
34
+ +-------------+--------------+------+-----+---------+----------------+
35
+ | Field | Type | Null | Key | Default | Extra |
36
+ +-------------+--------------+------+-----+---------+----------------+
37
+ | id | int(11) | NO | PRI | NULL | auto_increment |
38
+ | name | varchar(255) | YES | MUL | NULL | |
39
+ | slug | varchar(255) | YES | MUL | NULL | |
40
+ | domain | varchar(255) | YES | MUL | NULL | |
41
+ | category_id | int(11) | NO | MUL | NULL | |
42
+ | created_at | datetime | YES | | NULL | |
43
+ | updated_at | datetime | YES | | NULL | |
44
+ +-------------+--------------+------+-----+---------+----------------+
45
+
46
+
47
+ == Standard datatable
48
+
49
+ The first argument to the datatable helper is the name of the model or an array of model & scope. The second argument is a hash of column names & options.
50
+
51
+ <%= datatable('company', { :name => { :type => 'string',
52
+ :label => 'Name',
53
+ :width => '20%' },
54
+ :slug => { :type => 'string',
55
+ :label => 'Slug',
56
+ :width => '15%' },
57
+ :domain => { :type => 'string',
58
+ :label => 'Domain',
59
+ :width => '15%' }
60
+ }) %>
61
+
62
+ == Datatable with a link to edit
63
+
64
+ Note that the edit link path utilizes the :id field in the database table to automatically insert the correct id on a row by row basis:
65
+
66
+ <%= datatable('company', { :id => { :type => 'hidden' },
67
+ :name => { :type => 'link',
68
+ :label => 'Name',
69
+ :link => edit_admin_company_path(:id),
70
+ :replace => 'id',
71
+ :width => '50%' },
72
+ :slug => { :type => 'string',
73
+ :label => 'Slug',
74
+ :width => '25%' },
75
+ :domain => { :type => 'string',
76
+ :label => 'Domain',
77
+ :width => '25%' }
78
+ }) %>
79
+
80
+ == Scoped datatable (now we're using the 'dot_org_domains' that can be found in the Company model):
81
+
82
+ <%= datatable(['company','dot_org_domains'], { :id => { :type => 'hidden' },
83
+ :name => { :type => 'link',
84
+ :label => 'Name',
85
+ :link => edit_admin_company_path(:id),
86
+ :replace => 'id',
87
+ :width => '50%' },
88
+ :slug => { :type => 'string',
89
+ :label => 'Slug',
90
+ :width => '25%' },
91
+ :domain => { :type => 'string',
92
+ :label => 'Domain',
93
+ :width => '25%' }
94
+ }) %>
95
+
96
+ == Association columns
97
+ <%= datatable(['company','dot_org_domains'], { :id => { :type => 'hidden' },
98
+ :name => { :type => 'link',
99
+ :label => 'Name',
100
+ :link => edit_admin_company_path(:id),
101
+ :replace => 'id',
102
+ :width => '40%' },
103
+ :slug => { :type => 'string',
104
+ :label => 'Slug',
105
+ :width => '20%' },
106
+ :domain => { :type => 'string',
107
+ :label => 'Domain',
108
+ :width => '20%' },
109
+ :category_name => { :type => 'string',
110
+ :label => 'Category',
111
+ :column => 'categories.name',
112
+ :width => '20%',
113
+ :class => 'center' }
114
+
115
+ }) %>
116
+
117
+ = Why use this plugin?
118
+
119
+ It is the easiest way to integrate dynamic datatables using the jQuery datatables plugin into your rails app.
120
+
121
+ = Version history
122
+
123
+ - Version 1.0.0
124
+ - First Release
125
+
126
+ = Contribute
127
+
128
+ If you've got some ideas for datatables let me know and, even better, if you've made some enhancements to the code send me a pull request!
129
+
130
+ = Support
131
+
132
+ Bug report? Faulty/incomplete documentation? Feature request? Please post an issue on 'http://github.com/Caseproof/metafy/issues'. If its urgent, please contact me from my website at 'http://blairwilliams.com/contact'
133
+
134
+ Copyright (c) 2004-2011 Caseproof, LLC, released under the MIT license
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,19 @@
1
+ class DatatablesController < ApplicationController
2
+ #before_filter :authenticate_user!
3
+ #load_and_authorize_resource
4
+ respond_to :json
5
+
6
+ def dataset
7
+ begin
8
+ model_class = params[:model].classify.constantize
9
+ rescue NameError
10
+ render :text => 'Model Not Found', :layout => false
11
+ return
12
+ end
13
+
14
+ output = model_class.datatable(params)
15
+
16
+ # Render Encoded JSON
17
+ render :text => output.to_json, :layout => false
18
+ end
19
+ end
@@ -0,0 +1,163 @@
1
+ <% unless @pptions[:selectable].nil? or !@pptions[:selectable] -%>
2
+ <div id="rd_buttons">
3
+ <a id="rd_clear_selections" class="rd_disabled">Clear Selections</a>
4
+ <% unless @pptions[:selectable_actions].nil? -%>
5
+ <% @pptions[:selectable_actions].each do |sa| %>
6
+ &nbsp;&nbsp;<%= sa %>
7
+ <% end -%>
8
+ <% end -%>
9
+ </div>
10
+ <% end -%>
11
+ <table cellpadding="0" cellspacing="0" border="0" class="display" id="rdatatable">
12
+ <thead>
13
+ <tr>
14
+ <% @columns.each_pair do |key,col| -%>
15
+ <th width="<%= col[:width] %>"><%= col[:label] || key.to_s.humanize %></th>
16
+ <% end -%>
17
+ </tr>
18
+ </thead>
19
+ <tbody>
20
+ <tr>
21
+ <td colspan="<%= @columns.length.to_s %>" class="dataTables_empty">Loading...</td>
22
+ </tr>
23
+ </tbody>
24
+ <tfoot>
25
+ <tr>
26
+ <% @columns.each_pair do |key,col| -%>
27
+ <th><input type="text" name="search_<%= key.to_s %>" value="Search <%= col[:label] || key.to_s.humanize %>" class="search_init" rel="<%= @columns.keys.index(key) %>" /></th>
28
+ <% end -%>
29
+ </tr>
30
+ </tfoot>
31
+ </table>
32
+
33
+ <%= javascript_tag :defer => 'defer' do -%>
34
+ var asInitVals = new Array();
35
+
36
+ <% unless @pptions[:selectable].nil? or !@pptions[:selectable] -%>
37
+ var gaiSelected = [];
38
+
39
+ function rd_toggle_buttons() {
40
+ if( gaiSelected.length > 0 )
41
+ {
42
+ jQuery("#rd_buttons a").removeClass('rd_disabled');
43
+ jQuery("#rd_buttons a").addClass('rd_enabled');
44
+ }
45
+ else
46
+ {
47
+ jQuery("#rd_buttons a").removeClass('rd_enabled');
48
+ jQuery("#rd_buttons a").addClass('rd_disabled');
49
+ }
50
+ }
51
+ <% end -%>
52
+
53
+ function rd_bulk_edit_users() {
54
+ if( jQuery('#rd_bulk_edit_users').hasClass('rd_enabled') )
55
+ {
56
+ jQuery.prettyPhoto.open('/admin/projects/<%= @id %>/edit_users/' + gaiSelected + '?iframe=true&width=90%&height=90%');
57
+ }
58
+ }
59
+
60
+ jQuery(document).ready(function() {
61
+ var oTable = jQuery('#rdatatable').dataTable( {
62
+
63
+ /* Custom Options */
64
+ <% @jsoptions.each_pair do |k,v| -%>
65
+ "<%= k %>": <%= v %>,
66
+ <% end -%>
67
+
68
+ /* Visible / Hidden columns & Formatting */
69
+ "aoColumnDefs": [
70
+ <% @columns.each_pair do |name,attrs| -%>
71
+ <% if attrs[:type] == 'link' -%>
72
+ /* <%= name.to_s %> */ {
73
+ "fnRender": function ( oObj ) {
74
+ return '<a href="' + "<%= attrs[:link] %>".replace( /<%= attrs[:replace] %>/i, oObj.aData[<%= @columns.keys.index( attrs[:replace].to_sym ) %>] ) + '">' + oObj.aData[<%= @columns.keys.index( name.to_sym ) %>] + '</a>';
75
+ },
76
+ "aTargets": [ <%= @columns.keys.index( name ) %> ]
77
+ }<%= "," unless @columns.keys.last == name %>
78
+ <% elsif attrs[:type] == 'hidden' -%>
79
+ /* <%= name.to_s %> */ { "bVisible": false, "aTargets": [ <%= @columns.keys.index( name ) %> ] }<%= "," unless @columns.keys.last == name %>
80
+ <% else -%>
81
+ /* <%= name.to_s %> */ { "aTargets": [ <%= @columns.keys.index( name ) %> ] }<%= "," unless @columns.keys.last == name %>
82
+ <% end -%>
83
+ <% end -%>
84
+ ],
85
+
86
+ <% unless @pptions[:selectable].nil? or !@pptions[:selectable] -%>
87
+ "fnRowCallback": function( nRow, aData, iDisplayIndex ) {
88
+ if ( jQuery.inArray(aData[0], gaiSelected) != -1 ) {
89
+ jQuery(nRow).addClass('row_selected');
90
+ }
91
+ return nRow;
92
+ },
93
+ <% end -%>
94
+
95
+ /* Boilerplate Options */
96
+ "sPaginationType": "full_numbers",
97
+ "aLengthMenu": [[10, 25, 50, 100, 500], [10, 25, 50, 100, 500]],
98
+ "bProcessing": true,
99
+ "bServerSide": true,
100
+ "sAjaxSource": '/rdtable/<%= @modelname %><%= "/#{@col_str}" %><%= @scope.nil? ? '' : "/#{@scope}" %><%= @id.nil? ? '' : "/#{@id}" %>'
101
+ } );
102
+
103
+ <% unless @pptions[:selectable].nil? or !@pptions[:selectable] -%>
104
+ jQuery('#rd_clear_selections').click( function () {
105
+ gaiSelected = [];
106
+ jQuery("#rdatatable tbody tr").removeClass('row_selected');
107
+ rd_toggle_buttons();
108
+ });
109
+
110
+ /* Click event handler */
111
+ jQuery('#rdatatable tbody tr').live('click', function () {
112
+ var aData = oTable.fnGetData( this );
113
+ var iId = aData[0];
114
+
115
+ if ( jQuery.inArray(iId, gaiSelected) == -1 )
116
+ {
117
+ gaiSelected[gaiSelected.length++] = iId;
118
+ }
119
+ else
120
+ {
121
+ gaiSelected = jQuery.grep(gaiSelected, function(value) {
122
+ return value != iId;
123
+ } );
124
+ }
125
+
126
+ jQuery(this).toggleClass('row_selected');
127
+ rd_toggle_buttons();
128
+ } );
129
+ <% end -%>
130
+
131
+ jQuery("tfoot input").keyup( function () {
132
+ /* Filter on the column (the index) of this element */
133
+ oTable.fnFilter( this.value, jQuery(this).attr('rel') );
134
+ gaiSelected = []; // clear the selection on filter
135
+ rd_toggle_buttons();
136
+ } );
137
+
138
+ /*
139
+ * Support functions to provide a little bit of 'user friendlyness' to the textboxes in
140
+ * the footer
141
+ */
142
+ jQuery("tfoot input").each( function (i) {
143
+ asInitVals[i] = this.value;
144
+ } );
145
+
146
+ jQuery("tfoot input").focus( function () {
147
+ if ( this.className == "search_init" )
148
+ {
149
+ this.className = "";
150
+ this.value = "";
151
+ }
152
+ } );
153
+
154
+ jQuery("tfoot input").blur( function (i) {
155
+ if ( this.value == "" )
156
+ {
157
+ this.className = "search_init";
158
+ this.value = asInitVals[jQuery("tfoot input").index(this)];
159
+ }
160
+ } );
161
+
162
+ } );
163
+ <% end -%>
@@ -0,0 +1,5 @@
1
+ Rails.application.routes.draw do
2
+ match 'rdtable/:model/:columns' => 'datatables#dataset', :as => 'datatable'
3
+ match 'rdtable/:model/:columns/:scope' => 'datatables#dataset', :as => 'datatable'
4
+ match 'rdtable/:model/:columns/:scope/:id' => 'datatables#dataset', :as => 'datatable'
5
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "datatables"
6
+ s.version = "1.0.0"
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Blair Williams","Brandon Toone"]
9
+ s.email = ["blair@caseproof.com","btoone@gmail.com"]
10
+ s.homepage = "http://blairwilliams.com/ruby-on-rails/datatables"
11
+ s.summary = %q{Datatables is a Rails 3 plugin that enables the easy creation of dynamic datatable views on top of any ActiveRecord model}
12
+ s.description = %q{Datatables is a Rails 3 plugin that enables the easy creation of dynamic datatable views on top of any ActiveRecord model. Datatables provides a simple helper that can be utilized in the view to automatically display a dynamic view on top of a model. Datatables handles the entire front end and backend support to do this. }
13
+
14
+ s.add_dependency('rails','>= 3.0.3')
15
+ s.add_dependency('jquery-rails')
16
+
17
+ s.license = 'MIT'
18
+
19
+ s.rubyforge_project = "datatables"
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 = ["app","config","lib"]
25
+ end
@@ -0,0 +1,5 @@
1
+ module Datatables
2
+ require 'datatables/engine' if defined?(Rails) && Rails::VERSION::MAJOR == 3
3
+ require 'datatables/activerecord_methods' if defined?(Rails) && Rails::VERSION::MAJOR == 3
4
+ require 'datatables/datatable_helpers' if defined?(Rails) && Rails::VERSION::MAJOR == 3
5
+ end
@@ -0,0 +1,130 @@
1
+ # Adds the datatable method in ActiveRecord::Base, which sets up the backend for the datatable javascript
2
+ #
3
+ class << ActiveRecord::Base
4
+ def datatable( params )
5
+ curr_model = self
6
+ table_name = curr_model.table_name
7
+
8
+ if curr_model.metafied?
9
+ metas = curr_model.metafied_attrs || []
10
+ else
11
+ metas = []
12
+ end
13
+
14
+ sql_opts = { :select => [], :limit => "", :order => "", :joins => [], :conditions => "" }
15
+ columns = []
16
+ full_columns = []
17
+
18
+ dscope = params[:scope] || nil
19
+ dcols = params[:columns].split(',')
20
+ did = params[:id]
21
+
22
+ dcols.each do |col|
23
+ col = col.split(':')
24
+ if col.length > 1 # association (already assumed we're joined within a scope)
25
+ col_fmt = "`#{col[1].to_s}`.`#{col[2].to_s}`"
26
+ elsif metas.include? col.first.to_sym
27
+ col_fmt = "`m_#{col.first.to_s}`.`meta_value`"
28
+ else
29
+ col_fmt = "`#{table_name.to_s}`.`#{col.first.to_s}`"
30
+ end
31
+
32
+ columns.push col.first.to_s
33
+ full_columns.push col_fmt
34
+ sql_opts[:select].push "#{col_fmt} as #{col.first.to_s}"
35
+ end
36
+
37
+ # Paging
38
+ if params['iDisplayStart'] and params['iDisplayLength'] != '-1'
39
+ sql_opts[:limit] = "#{params['iDisplayStart']},#{params['iDisplayLength']}"
40
+ end
41
+
42
+ # Ordering
43
+ if params['iSortCol_0']
44
+ orders = []
45
+ i = 0
46
+ while i < params['iSortingCols'].to_i do
47
+ if params['bSortable_' + params['iSortCol_' + i.to_s]] == "true"
48
+ # Make sure the order works for metafied columns and normal columns
49
+ col = full_columns[ params['iSortCol_' + i.to_s].to_i ]
50
+ orders.push( "#{col} #{params['sSortDir_' + i.to_s]}" )
51
+ end
52
+ i += 1
53
+ end
54
+ sql_opts[:order] = orders.join(", ") unless orders.empty?
55
+ end
56
+
57
+ # Searching
58
+ search_str = ""
59
+ searches = []
60
+ if params['sSearch'] and !params['sSearch'].empty?
61
+ full_columns.each_with_index do |col,i|
62
+ searches.push( "#{col} LIKE '%#{params['sSearch']}%'" )
63
+ end
64
+ search_str = searches.join(' OR ') unless searches.empty?
65
+ end
66
+
67
+ # Filtering
68
+ filter_str = ""
69
+ filters = []
70
+ full_columns.each_with_index do |col,i|
71
+ if params['bSearchable_' + i.to_s] == "true" and !params['sSearch_' + i.to_s].empty?
72
+ filters.push( "#{col} LIKE '%#{params['sSearch_' + i.to_s]}%'" )
73
+ end
74
+ end
75
+ filter_str = filters.join(' AND ') unless filters.empty?
76
+
77
+ # Pull Searching & Filtering into where
78
+ if !searches.empty? and !filters.empty?
79
+ sql_opts[:conditions] = "(#{search_str}) AND #{filter_str}"
80
+ elsif !searches.empty?
81
+ sql_opts[:conditions] = search_str
82
+ elsif !filters.empty?
83
+ sql_opts[:conditions] = filter_str
84
+ end
85
+
86
+ # Query
87
+ if dscope.nil?
88
+ # Scope model
89
+ records = curr_model.select( sql_opts[:select] ).limit( sql_opts[:limit] ).order( sql_opts[:order] ).where( sql_opts[:conditions] ).joins(sql_opts[:joins])
90
+ # Records Found
91
+ filtered_total = curr_model.count( :select => "*", :conditions => sql_opts[:conditions], :joins => sql_opts[:joins] )
92
+ # Total Found
93
+ total = curr_model.count( :select => "*" )
94
+ elsif params[:id].nil?
95
+ # Scope model
96
+ records = curr_model.send( dscope.to_s ).select( sql_opts[:select] ).limit( sql_opts[:limit] ).order( sql_opts[:order] ).where( sql_opts[:conditions] ).joins(sql_opts[:joins])
97
+ # Records Found
98
+ filtered_total = curr_model.send( dscope.to_s ).count( :select => "*", :conditions => sql_opts[:conditions], :joins => sql_opts[:joins] )
99
+ # Total Found
100
+ total = curr_model.send( dscope.to_s ).count( :select => "*" )
101
+ else
102
+ # Scope model
103
+ records = curr_model.send( dscope.to_s, params[:id] ).select( sql_opts[:select] ).limit( sql_opts[:limit] ).order( sql_opts[:order] ).where( sql_opts[:conditions] ).joins(sql_opts[:joins])
104
+ # Records Found
105
+ filtered_total = curr_model.send( dscope.to_s, params[:id] ).count( :select => "*", :conditions => sql_opts[:conditions], :joins => sql_opts[:joins] )
106
+ # Total Found
107
+ total = curr_model.send( dscope.to_s, params[:id] ).count( :select => "*" )
108
+ end
109
+
110
+ # Build Output Data Structure
111
+ output = { "sEcho" => params['sEcho'],
112
+ "iTotalRecords" => total,
113
+ "iTotalDisplayRecords" => filtered_total,
114
+ "aaData" => [] }
115
+
116
+ records.each do |a_row|
117
+ row = []
118
+ columns.each_with_index do |col,i|
119
+ if col == "version" # Special output formatting for version column
120
+ row.push( a_row[ col ]=="0" ? '-' : a_row[ col ] )
121
+ elsif col != ' ' # General Output
122
+ row.push( a_row[ col ])
123
+ end
124
+ end
125
+ output['aaData'].push(row)
126
+ end
127
+
128
+ output
129
+ end
130
+ end