datatables 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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