diff --git a/README.md b/README.md index 3c7d5151..ba2f1480 100644 --- a/README.md +++ b/README.md @@ -12,64 +12,23 @@ Other features include aggregate (total/average) footer rows, bulk actions, show This gem includes the jQuery DataTables assets. +For use with any Rails 3, 4, 5 application already using Twitter Bootstrap 3. Works with postgres, mysql, sqlite3 and arrays. -## Live Demo - -Click here for a [Live Demo](https://effective-datatables-demo.herokuapp.com/). - -See [effective_datatables_demo](https://github.com/code-and-effect/effective_datatables_demo) for a working rails website example. - -## effective_datatables 4.0 - -This is the 4.0 series of effective_datatables. - -This requires Twitter Bootstrap 4 and Rails 5.1+ - -Please check out [Effective Datatables 3.x](https://github.com/code-and-effect/effective_datatables/tree/bootstrap3) for more information using this gem with Bootstrap 3. - -# Contents - -* [Getting Started](#getting-started) -* [Quick Start](#quick-start) -* [Usage](#usage) -* [DSL](#dsl) - * [attributes](#attributes) - * [collection](#collection) - * [datatable](#datatable) - * [col](#col) - * [val](#val) - * [bulk_actions_col](#bulk_actions_col) - * [actions_col](#actions_col) - * [length](#length) - * [order](#order) - * [reorder](#reorder) - * [aggregate](#aggregate) - * [filters](#filters) - * [scope](#scope) - * [filter](#filter) - * [bulk_actions](#bulk_actions) - * [bulk_action](#bulk_action) - * [bulk_action](#bulk_action_divider) - * [bulk_download](#bulk_download) - * [bulk_action_content](#bulk_action_content) - * [charts](#charts) - * [Extras](#extras) - * [Advanced Search and Sort](#advanced-search-and-sort) -* [Addtional Functionality](#additional-functionality) - * [Checking for Empty collection](#checking-for-empty-collection) - * [Override javascript options](#override-javascript-options) - * [Get access to the raw results](#get-access-to-the-raw-results) - * [Authorization](#authorization) -* [License](#license) -* [Contributing](#contributing) +## Bootstrap3 + +This is the `bootstrap3` branch of effective_datatables which supports Twitter Bootstrap 3. + +All published effective_datatables 3.x gems will support Twitter Bootstrap 3 and SimpleForm. + +For Bootstrap 4 please see the master branch and/or effective_datatables 4.x gems. # Getting Started ```ruby gem 'haml-rails' # or try using gem 'hamlit-rails' -gem 'effective_datatables' +gem 'effective_datatables', '~> 3.0' ``` Run the bundle command to install it: @@ -86,8 +45,6 @@ rails generate effective_datatables:install The generator will install an initializer which describes all configuration options. -Make sure you have [Twitter Bootstrap 4](https://github.com/twbs/bootstrap-rubygem) installed. - Require the javascript on the asset pipeline by adding the following to your application.js: ```ruby @@ -173,7 +130,7 @@ class PostsDatatable < Effective::Datatable # Everything in the filters block ends up in a single form # The form is submitted by datatables javascript as an AJAX post filters do - # Scopes are rendered as a single radio button form field (works well with effective_bootstrap gem) + # Scopes are rendered as a single radio button form field (works well with effective_form_inputs gem) # The scopes only work when your collection is an ActiveRecord class, and they must exist on the model # The current scope is automatically applied by effective_datatables to your collection # You don't have to consider the current scope when writing your collection block @@ -237,10 +194,10 @@ class PostsDatatable < Effective::Datatable col :post_category, action: :edit if attributes[:user_id].nil? # Show all users, otherwise this table is meant for one user only - col :user, search: User.authors.all - end + col :user, search: { collection: User.authors } - col 'user.first_name' # Using the joined syntax + col 'user.first_name' # Using the joined syntax + end if can?(:index, Comment) col :comments @@ -280,11 +237,10 @@ class PostsDatatable < Effective::Datatable # Puts links to show/edit/destroy actions, if authorized to those actions. # Use the actions_col block to add additional actions - actions_col + actions_col(edit: false) do |post| + glyphicon_to('print', print_post_path(post), title: 'Print') + end - # actions_col(edit: false) do |post| - # dropdown_link_to('Approve', approve_post_path(post) data: { method: :post, confirm: "Approve #{post}?"}) - # end end end @@ -348,7 +304,7 @@ When initialized with a Hash, that hash is available throughout the entire datat You can call the attributes from within the datatable as `attributes` or within a partial/view as `@datatable.attributes`. -These attributes are serialized and stored in an encrypted data attribute. Objects won't work. Keep it simple. +These attributes are serialized and stored in an encrypted cookie. Objects won't work. Keep it simple. Attributes cannot be changed by search, filter, or state in any way. They're guaranteed to be the same as when first initialized. @@ -451,7 +407,7 @@ Sometimes it's handy to call `.reorder(nil)` on a scope. The `datatable do ... end` block configures a table of data. -Initialize the datatable in your controller or view, `@datatable = PostsDatatable.new(self)`, and render it in your view `<%= render_datatable(@datatable) %>` +Initialize the datatable in your controller or view, `@datatable = PostsDatatable.new`, and render it in your view `<%= render_datatable(@datatable) %>` ### col @@ -466,7 +422,7 @@ It accepts one optional block used to format the value after any search or sorti The following options are available: ```ruby -action: :show|:edit|false # Render as a link to this action. edit -> show by default +action: :show|:edit|false # :resource and relation columns only. generate links to this action. edit -> show by default as: :string|:integer|etc # Sets the type of column initializing defaults for search, sort and format col_class: 'col-green' # Sets the html class to use on this column's td and th label: 'My label' # The label for this column @@ -478,9 +434,7 @@ responsive: 10000 # Controls how columns collapse https://datatables.ne search: false search: :string search: { as: :string, fuzzy: true } -search: User.all -search: { as: :select, collection: User.all, multiple: true, include_null: 'All Users' } -search: { collection: { 'All Books' => Book.all, 'All Shirts' => Shirt.all}, polymorphic: true } +search: { as: :select, collection: User.all, multiple: true } sort: true|false # Should this column be orderable. true by default sql_column: 'posts.rating' # The sql column to search/sort on. Only needed when doing custom selects or tricky joins. @@ -493,14 +447,14 @@ It is auto-detected from an ActiveRecord collection's SQL datatype, and set to ` Valid options for `:as` are as follows: -`:boolean`, `:currency`, `:datetime`, `:date`, `:decimal`, `:duration`, `:email`, `:float`, `:integer`, `:percent`, `:price`, `:resource`, `:string`, `:text` +`:boolean`, `:currency`, `:datetime`, `:date`, `:decimal`, `:duration`, `:email`, `:float`, `:integer`, `:percentage`, `:price`, `:resource`, `:string`, `:text` These settings are loosely based on the regular datatypes, with some custom effective types thrown in: - `:currency` expects the underlying datatype to be a Float. - `:duration` expects the underlying datatype to be an Integer representing the number of minutes. 120 == 2 hours - `:email` expects the underlying datatype to be a String -- `:percent` expects the underlying datatype to be an Integer * 1000. 50000 == 50%. 50125 == 50.125%. +- `:percentage` expects the underlying datatype to be an Integer or a Float. 75 == 0.75 == 75% - `:price` expects the underlying datatype to be an Integer representing the number of cents. 5000 == $50.00 - `:resource` can be used for an Array based collection which includes an ActiveRecord object @@ -567,13 +521,11 @@ The authorization method is configured via the `config/initializers/effective_da There are just a few options: ```ruby -btn_class: 'btn-sm btn-outline-primary' show: true|false edit: true|false destroy: true|false visible: true|false -actions_partial: :dropleft -inline: true|false +actions_partial: :glyphicons ``` Each object is checked individually for authorization. @@ -584,18 +536,14 @@ It's all very complicated. If you just want to override this entire column with your own actions implementation, you can pass `actions_col partial: 'my_partial'` and roll your own. -Otherwise, use the following block syntax to add additional actions. This helper comes from `effective_bootstrap` gem. +Otherwise, use the following block syntax to add additional actions. This helper comes from `effective_form_inputs` gem. ```ruby actions_col do |post| - dropdown_link_to('Approve', approve_post_path(post) data: { method: :post, confirm: "Approve #{post}?"}) + glyphicon_to('print', print_post_path(post), title: 'Print') end ``` -Any `data-remote` actions will be hijacked and performed as inline ajax by datatables. - -If you'd like to opt-out of this behavior, use `actions_col(inline: false)` or add `data-inline: false` to your action link. - ### length Sets the default number of rows per page. Valid lengths are `5`, `10`, `25`, `50`, `100`, `250`, `500`, `:all` @@ -618,24 +566,6 @@ When not specified, effective_datatables will sort by the first defined column. order :created_at, :asc|:desc ``` -### reorder - -Enables drag-and-drop row re-ordering. - -Only works with ActiveRecord collections. - -The underlying field must be an Integer, and it's assumed to be a sequential list of unique numbers. - -When a drag and drop is completed, a POST request is made to the datatables#reorder action that will update the indexes. - -Both zero and one based lists will work. - -```ruby -reorder :position -``` - -Using `reorder` will sort the collection by this field and disable all other column sorting. - ### aggregate The `aggregate` command inserts a row in the table's `tfoot`. @@ -679,7 +609,7 @@ Creates a single form with fields for each `filter` and a single radio input fie The form is submitted by an AJAX POST action, or, in some advanced circumstances (see Dynamic Columns below) as a regular POST or even GET. -Initialize the datatable in your controller or view, `@datatable = PostsDatatable.new(self)`, and render its filters anywhere with `<%= render_datatable_filters(@datatable) %>`. +Initialize the datatable in your controller or view, `@datatable = PostsDatatable.new`, and render its filters anywhere with `<%= render_datatable_filters(@datatable) %>`. ### scope @@ -723,7 +653,7 @@ filters do filter :end_date, nil, parse: -> { |term| Time.zone.local(term).end_of_day } filter :user, current_user, as: :select, collection: User.all filter :year, 2018, as: :select, collection: [2018, 2017], label: false, include_blank: false - filter :year_group, '2018', as: :select, grouped: true, collection: { 'Years' => [['2017', 2017], ['2018', 2018]], 'Months' => [['January', 1], ['February', 2]] } + filter :reports, '2018', as: :grouped_select, collection: [['Years', [['2017', '2017'], ['2018', '2018']]], ['Months', [['January', '1'], ['February', '2']]]], group_method: :last end ``` @@ -744,13 +674,13 @@ end The filter command has the following options: ```ruby -as: :select|:date|:boolean # Passed to form +as: :select|:date|:boolean # Passed to SimpleForm label: 'My label' # Label for this form field parse: -> { |term| term.to_i } # Parse the incoming term (string) into whatever datatype -required: true|false # Passed to form +required: true|false # Passed to SimpleForm ``` -Any other option given will be yielded to EffectiveBootstrap as options. +Any other option given will be yielded to SimpleForm as `input_html` options. ## bulk_actions @@ -804,6 +734,8 @@ or if using [effective_resources](https://github.com/code-and-effect/effective_r ```ruby include Effective::CrudController + +collection_action :bulk_approve ``` and in your model @@ -933,7 +865,7 @@ If you just want to render a datatable and nothing else, there is a quick way to ```ruby class PostsController < ApplicationController def index - render_datatable_index PostsDatatable.new(self) + render_datatable_index PostsDatatable.new end end ``` @@ -1005,11 +937,9 @@ datatable do duration = (hours.to_i * 60) + minutes.to_i collection.select! { |row| row[index] == duration } # Must return an Array of Arrays - end.sort do |collection, direction, column, index| - if direction == :asc - collection.sort! { |a, b| a[index] <=> b[index] } - else - collection.sort! { |a, b| b[index] <=> a[index] } + end.sort do |collection, term, column, index| + collection.sort! do |x, y| + x[index] <=> y[index] end end end @@ -1196,11 +1126,11 @@ rescue_from Effective::AccessDenied do |exception| end ``` -# License +## License MIT License. Copyright [Code and Effect Inc.](http://www.codeandeffect.com/) -# Contributing +## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) @@ -1208,3 +1138,4 @@ MIT License. Copyright [Code and Effect Inc.](http://www.codeandeffect.com/) 4. Push to the branch (`git push origin my-new-feature`) 5. Bonus points for test coverage 6. Create new Pull Request + diff --git a/app/assets/config/effective_datatables_manifest.js b/app/assets/config/effective_datatables_manifest.js new file mode 100644 index 00000000..8a42cf13 --- /dev/null +++ b/app/assets/config/effective_datatables_manifest.js @@ -0,0 +1,3 @@ +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css +//= link_tree ../images diff --git a/app/assets/images/dataTables/sort-down.svg b/app/assets/images/dataTables/sort-down.svg deleted file mode 100644 index 18b1a33c..00000000 --- a/app/assets/images/dataTables/sort-down.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/assets/images/dataTables/sort-up.svg b/app/assets/images/dataTables/sort-up.svg deleted file mode 100644 index eb539976..00000000 --- a/app/assets/images/dataTables/sort-up.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/assets/images/dataTables/sort.svg b/app/assets/images/dataTables/sort.svg deleted file mode 100644 index a1a78d49..00000000 --- a/app/assets/images/dataTables/sort.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/assets/images/dataTables/sort_asc.png b/app/assets/images/dataTables/sort_asc.png new file mode 100644 index 00000000..e1ba61a8 Binary files /dev/null and b/app/assets/images/dataTables/sort_asc.png differ diff --git a/app/assets/images/dataTables/sort_both.png b/app/assets/images/dataTables/sort_both.png new file mode 100644 index 00000000..af5bc7c5 Binary files /dev/null and b/app/assets/images/dataTables/sort_both.png differ diff --git a/app/assets/images/dataTables/sort_desc.png b/app/assets/images/dataTables/sort_desc.png new file mode 100644 index 00000000..0e156deb Binary files /dev/null and b/app/assets/images/dataTables/sort_desc.png differ diff --git a/app/assets/javascripts/dataTables/buttons/buttons.bootstrap4.js b/app/assets/javascripts/dataTables/buttons/buttons.bootstrap.js similarity index 72% rename from app/assets/javascripts/dataTables/buttons/buttons.bootstrap4.js rename to app/assets/javascripts/dataTables/buttons/buttons.bootstrap.js index f2dedd9b..994ab89a 100755 --- a/app/assets/javascripts/dataTables/buttons/buttons.bootstrap4.js +++ b/app/assets/javascripts/dataTables/buttons/buttons.bootstrap.js @@ -5,7 +5,7 @@ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD - define( ['jquery', 'datatables.net-bs4', 'datatables.net-buttons'], function ( $ ) { + define( ['jquery', 'datatables.net-bs', 'datatables.net-buttons'], function ( $ ) { return factory( $, window, document ); } ); } @@ -17,7 +17,7 @@ } if ( ! $ || ! $.fn.dataTable ) { - $ = require('datatables.net-bs4')(root, $).$; + $ = require('datatables.net-bs')(root, $).$; } if ( ! $.fn.dataTable.Buttons ) { @@ -35,29 +35,36 @@ 'use strict'; var DataTable = $.fn.dataTable; + $.extend( true, DataTable.Buttons.defaults, { dom: { container: { className: 'dt-buttons btn-group' }, button: { - className: 'btn btn-secondary' + className: 'btn btn-default' }, collection: { - tag: 'div', + tag: 'ul', className: 'dt-button-collection dropdown-menu', button: { - tag: 'a', - className: 'dt-button dropdown-item', + tag: 'li', + className: 'dt-button', active: 'active', disabled: 'disabled' + }, + buttonLiner: { + tag: 'a', + className: '' } } } } ); -DataTable.ext.buttons.collection.className += ' dropdown-toggle'; -DataTable.ext.buttons.collection.rightAlignClassName = 'dropdown-menu-right'; +DataTable.ext.buttons.collection.text = function ( dt ) { + return dt.i18n('buttons.collection', 'Collection '); +}; + return DataTable.Buttons; })); diff --git a/app/assets/javascripts/dataTables/buttons/buttons.colVis.js b/app/assets/javascripts/dataTables/buttons/buttons.colVis.js index d5ec8de8..920d6f69 100755 --- a/app/assets/javascripts/dataTables/buttons/buttons.colVis.js +++ b/app/assets/javascripts/dataTables/buttons/buttons.colVis.js @@ -108,7 +108,6 @@ $.extend( DataTable.ext.buttons, { }, init: function ( dt, button, conf ) { var that = this; - button.attr( 'data-cv-idx', conf.columns ); dt .on( 'column-visibility.dt'+conf.namespace, function (e, settings) { @@ -123,17 +122,14 @@ $.extend( DataTable.ext.buttons, { return; } - conf.columns = $.inArray( conf.columns, details.mapping ); - button.attr( 'data-cv-idx', conf.columns ); - - // Reorder buttons for new table order - button - .parent() - .children('[data-cv-idx]') - .sort( function (a, b) { - return (a.getAttribute('data-cv-idx')*1) - (b.getAttribute('data-cv-idx')*1); - } ) - .appendTo(button.parent()); + if ( typeof conf.columns === 'number' ) { + conf.columns = details.mapping[ conf.columns ]; + } + + var col = dt.column( conf.columns ); + + that.text( conf._columnText( dt, conf ) ); + that.active( col.visible() ); } ); this.active( dt.column( conf.columns ).visible() ); @@ -153,9 +149,7 @@ $.extend( DataTable.ext.buttons, { var title = dt.settings()[0].aoColumns[ idx ].sTitle .replace(/\n/g," ") // remove new lines .replace(//gi, " ") // replace line breaks with spaces - .replace(//g, "") // remove select tags, including options text - .replace(//g, "") // strip HTML comments - .replace(/<.*?>/g, "") // strip HTML + .replace( /<.*?>/g, "" ) // strip HTML .replace(/^\s+|\s+$/g,""); // trim return conf.columnText ? diff --git a/app/assets/javascripts/dataTables/buttons/buttons.html5.js b/app/assets/javascripts/dataTables/buttons/buttons.html5.js index b7f3835e..f340086d 100755 --- a/app/assets/javascripts/dataTables/buttons/buttons.html5.js +++ b/app/assets/javascripts/dataTables/buttons/buttons.html5.js @@ -48,20 +48,6 @@ function _pdfMake () { return pdfmake || window.pdfMake; } -DataTable.Buttons.pdfMake = function (_) { - if ( ! _ ) { - return _pdfMake(); - } - pdfmake = m_ake; -} - -DataTable.Buttons.jszip = function (_) { - if ( ! _ ) { - return _jsZip(); - } - jszip = _; -} - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * FileSaver.js dependency @@ -436,9 +422,6 @@ function _addToZip( zip, obj ) { // Return namespace attributes to being as such str = str.replace( /_dt_b_namespace_token_/g, ':' ); - - // Remove testing name space that IE puts into the space preserve attr - str = str.replace( /xmlns:NS[\d]+="" NS[\d]+:/g, '' ); } // Safari, IE and Edge will put empty name space attributes onto @@ -522,11 +505,11 @@ function _excelColWidth( data, col ) { // Max width rather than having potentially massive column widths if ( max > 40 ) { - return 54; // 40 * 1.35 + return 52; // 40 * 1.3 } } - max *= 1.35; + max *= 1.3; // And a min width return max > 6 ? max : 6; @@ -567,9 +550,8 @@ var excelStrings = { ''+ ''+ ''+ - ''+ + ''+ ''+ - ''+ '', "xl/worksheets/sheet1.xml": @@ -620,9 +602,7 @@ var excelStrings = { ''+ ''+ ''+ - ''+ // Excel appears to use this as a dotted background regardless of values but - ''+ // to be valid to the schema, use a patternFill - ''+ + ''+ // Excel appears to use this as a dotted background regardless of values ''+ ''+ ''+ @@ -831,7 +811,7 @@ DataTable.ext.buttons.copyHtml5 = { } if ( config.customize ) { - output = config.customize( output, config, dt ); + output = config.customize( output, config ); } var textarea = $('