diff --git a/.gitignore b/.gitignore index 9af112d6..061912ed 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ Gemfile.lock log/*.log pkg/ tmp/ +.rubocop-* diff --git a/README.md b/README.md index ed4da007..162ab7aa 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Please check out [Effective Datatables 3.x](https://github.com/code-and-effect/e * [bulk_actions_col](#bulk_actions_col) * [actions_col](#actions_col) * [length](#length) + * [length_menu](#length_menu) * [order](#order) * [reorder](#reorder) * [aggregate](#aggregate) @@ -50,6 +51,7 @@ Please check out [Effective Datatables 3.x](https://github.com/code-and-effect/e * [filters](#filters) * [scope](#scope) * [filter](#filter) + * [filter_date_range](#filter_date_range) * [bulk_actions](#bulk_actions) * [bulk_action](#bulk_action) * [bulk_action](#bulk_action_divider) @@ -227,7 +229,7 @@ class PostsDatatable < Effective::Datatable # The user's selected filters, search, sort, length, column visibility and pagination settings are saved between visits # on a per-table basis and can be Reset with a button datatable do - length 25 # 5, 10, 25, 50, 100, 500, :all + length 25 # 5, 10, 25, 50, 100, 500 order :updated_at, :desc # Renders a column of checkboxes to select items for any bulk_actions @@ -613,7 +615,7 @@ MyApp::UsersTable.new(namespace: :my_app) ## length -Sets the default number of rows per page. Valid lengths are `5`, `10`, `25`, `50`, `100`, `250`, `500`, `:all` +Sets the default number of rows per page. When not specified, effective_datatables uses the default as per the `config/initializers/effective_datatables.rb` or 25. @@ -621,6 +623,18 @@ When not specified, effective_datatables uses the default as per the `config/ini length 100 ``` +## length_menu + +You can specify the length menu values in `config/initializers/effective_datatables.rb` or per datatable. + +To override the default, add the method to your datatable + +``` +def length_menu + [5, 10, 25, 50] +end +``` + ## order Sets the default order of table rows. The first argument is the column, the second the direction. @@ -791,6 +805,24 @@ required: true|false # Passed to form Any other option given will be yielded to EffectiveBootstrap as options. +## filter_date_range + +There is also a special date range filter built in. To use: + +```ruby +filters do + filter_date_range +end + +collection do + Thing.where(updated_at: date_range) +end +``` + +This method creates 3 filters, `filters[:date_range]`, `filters[:start_date]` and `filters[:end_date]` and presents a rough Prev/Next month and year navigation. Do not have any columns named the same as these. + +You can pass a default into `filter_date_range`, one of `:current_month`, `:current_year`, `:month`, `:year` and `:custom`. + ## bulk_actions Creates a single dropdown menu with a link to each action, download or content. @@ -1112,6 +1144,27 @@ Above we have `resources :things` for the 7 crud actions. And we add two more me Your datatable should now have New, Show, Edit, Approve and Reject buttons. Click them for inline functionality. +## Adding an Ajax member action + +To render a member action with an inline datatable: + +- Create a "cool_things.html" template and a "_cool_things.html" partial file. Need both. + +- The links must be inside an `actions_col` or a `col(:thing, col_class: 'col-actions')` for the javascript to work. + +- The action itself just needs to be `data-remote=true`. Try `link_to('Show Cool Things', thing_cool_things_path(thing), 'data-remote': true)` + +Make sure the route and permissions are working: + +``` +resources :things do + get :cool_things, on: :member +``` + +and `can?(:cool_things, Thing)` + +Good luck. + ## Troubleshooting Inline If things aren't working, try the following: @@ -1292,7 +1345,7 @@ class TimeEntriesPerClientReport < Effective::Datatable end datatable do - length :all + length 50 col :client diff --git a/app/assets/javascripts/dataTables/UPGRADE.md b/app/assets/javascripts/dataTables/UPGRADE.md new file mode 100644 index 00000000..ba33d80b --- /dev/null +++ b/app/assets/javascripts/dataTables/UPGRADE.md @@ -0,0 +1,17 @@ +# Upgrade + +To upgrade the datatables.net source code: + +Visit https://datatables.net/download/ + +Step 1: Choose Bootstrap4 +Step 2: + Packages: Just DataTables + Extensions: Buttons, Column visibility, HTML5 export, Print view, Responsive, RowReorder +Step 3: Download do not minify or concatenate +Step 4: Replace existing javascript and css with downloaded + +After replacing the existing JS/CSS: + +- Consider the buttons.colVis.js and add back the custom sorted functionality +- Consider the stylesheets/effective_datatables/overrides.bootstrap4.scss diff --git a/app/assets/javascripts/dataTables/buttons/buttons.bootstrap4.js b/app/assets/javascripts/dataTables/buttons/buttons.bootstrap4.js old mode 100755 new mode 100644 index f2dedd9b..11f43be0 --- a/app/assets/javascripts/dataTables/buttons/buttons.bootstrap4.js +++ b/app/assets/javascripts/dataTables/buttons/buttons.bootstrap4.js @@ -1,5 +1,5 @@ /*! Bootstrap integration for DataTables' Buttons - * ©2016 SpryMedia Ltd - datatables.net/license + * © SpryMedia Ltd - datatables.net/license */ (function( factory ){ @@ -11,21 +11,37 @@ } else if ( typeof exports === 'object' ) { // CommonJS - module.exports = function (root, $) { - if ( ! root ) { - root = window; - } - - if ( ! $ || ! $.fn.dataTable ) { - $ = require('datatables.net-bs4')(root, $).$; + var jq = require('jquery'); + var cjsRequires = function (root, $) { + if ( ! $.fn.dataTable ) { + require('datatables.net-bs4')(root, $); } if ( ! $.fn.dataTable.Buttons ) { require('datatables.net-buttons')(root, $); } - - return factory( $, root, root.document ); }; + + if (typeof window === 'undefined') { + module.exports = function (root, $) { + if ( ! root ) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window; + } + + if ( ! $ ) { + $ = jq( root ); + } + + cjsRequires( root, $ ); + return factory( $, root, root.document ); + }; + } + else { + cjsRequires( window, jq ); + module.exports = factory( jq, window, window.document ); + } } else { // Browser @@ -35,29 +51,67 @@ 'use strict'; var DataTable = $.fn.dataTable; -$.extend( true, DataTable.Buttons.defaults, { + + +$.extend(true, DataTable.Buttons.defaults, { dom: { container: { - className: 'dt-buttons btn-group' + className: 'dt-buttons btn-group flex-wrap' }, button: { - className: 'btn btn-secondary' + className: 'btn btn-secondary', + active: 'active' }, collection: { - tag: 'div', - className: 'dt-button-collection dropdown-menu', + action: { + dropHtml: '' + }, + container: { + tag: 'div', + className: 'dropdown-menu dropdown-menu-right dt-button-collection' + }, + closeButton: false, button: { tag: 'a', className: 'dt-button dropdown-item', - active: 'active', - disabled: 'disabled' + active: 'dt-button-active', + disabled: 'disabled', + spacer: { + className: 'dropdown-divider', + tag: 'hr' + } + } + }, + split: { + action: { + tag: 'a', + className: 'btn btn-secondary dt-button-split-drop-button', + closeButton: false + }, + dropdown: { + tag: 'button', + dropHtml: '', + className: + 'btn btn-secondary dt-button-split-drop dropdown-toggle dropdown-toggle-split', + closeButton: false, + align: 'split-left', + splitAlignClass: 'dt-button-split-left' + }, + wrapper: { + tag: 'div', + className: 'dt-button-split btn-group', + closeButton: false } } + }, + buttonCreated: function (config, button) { + return config.buttons ? $('
').append(button) : button; } -} ); +}); DataTable.ext.buttons.collection.className += ' dropdown-toggle'; DataTable.ext.buttons.collection.rightAlignClassName = 'dropdown-menu-right'; -return DataTable.Buttons; + +return DataTable; })); diff --git a/app/assets/javascripts/dataTables/buttons/buttons.colVis.js b/app/assets/javascripts/dataTables/buttons/buttons.colVis.js old mode 100755 new mode 100644 index d5ec8de8..19db9dd9 --- a/app/assets/javascripts/dataTables/buttons/buttons.colVis.js +++ b/app/assets/javascripts/dataTables/buttons/buttons.colVis.js @@ -1,6 +1,6 @@ /*! * Column visibility buttons for Buttons and DataTables. - * 2016 SpryMedia Ltd - datatables.net/license + * © SpryMedia Ltd - datatables.net/license */ (function( factory ){ @@ -12,21 +12,37 @@ } else if ( typeof exports === 'object' ) { // CommonJS - module.exports = function (root, $) { - if ( ! root ) { - root = window; - } - - if ( ! $ || ! $.fn.dataTable ) { - $ = require('datatables.net')(root, $).$; + var jq = require('jquery'); + var cjsRequires = function (root, $) { + if ( ! $.fn.dataTable ) { + require('datatables.net')(root, $); } if ( ! $.fn.dataTable.Buttons ) { require('datatables.net-buttons')(root, $); } - - return factory( $, root, root.document ); }; + + if (typeof window === 'undefined') { + module.exports = function (root, $) { + if ( ! root ) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window; + } + + if ( ! $ ) { + $ = jq( root ); + } + + cjsRequires( root, $ ); + return factory( $, root, root.document ); + }; + } + else { + cjsRequires( window, jq ); + module.exports = factory( jq, window, window.document ); + } } else { // Browser @@ -37,38 +53,68 @@ var DataTable = $.fn.dataTable; -$.extend( DataTable.ext.buttons, { + +$.extend(DataTable.ext.buttons, { // A collection of column visibility buttons - colvis: function ( dt, conf ) { - return { + colvis: function (dt, conf) { + var node = null; + var buttonConf = { extend: 'collection', - text: function ( dt ) { - return dt.i18n( 'buttons.colvis', 'Column visibility' ); + init: function (dt, n) { + node = n; + }, + text: function (dt) { + return dt.i18n('buttons.colvis', 'Column visibility'); }, className: 'buttons-colvis', - buttons: [ { - extend: 'columnsToggle', - columns: conf.columns, - columnText: conf.columnText - } ] + closeButton: false, + buttons: [ + { + extend: 'columnsToggle', + columns: conf.columns, + columnText: conf.columnText + } + ] }; + + // Rebuild the collection with the new column structure if columns are reordered + dt.on('column-reorder.dt' + conf.namespace, function (e, settings, details) { + dt.button(null, dt.button(null, node).node()).collectionRebuild([ + { + extend: 'columnsToggle', + columns: conf.columns, + columnText: conf.columnText + } + ]); + }); + + return buttonConf; }, // Selected columns with individual buttons - toggle column visibility - columnsToggle: function ( dt, conf ) { - var columns = dt.columns( conf.columns ).indexes().map( function ( idx ) { - return { - extend: 'columnToggle', - columns: idx, - columnText: conf.columnText - }; - } ).toArray(); - - return columns; + columnsToggle: function (dt, conf) { + var columns = dt + .columns(conf.columns) + .indexes() + .map(function (idx) { + return { + extend: 'columnToggle', + columns: idx, + columnText: conf.columnText + }; + }) + + var sorted = columns.sort(function (a, b) { + var a = dt.settings()[0].aoColumns[a.columns].sTitle; + var b = dt.settings()[0].aoColumns[b.columns].sTitle; + return a.localeCompare(b) + }).toArray(); + + return sorted; }, // Single button to toggle column visibility - columnToggle: function ( dt, conf ) { + columnToggle: function (dt, conf) { return { extend: 'columnVisibility', columns: conf.columns, @@ -77,15 +123,19 @@ $.extend( DataTable.ext.buttons, { }, // Selected columns with individual buttons - set column visibility - columnsVisibility: function ( dt, conf ) { - var columns = dt.columns( conf.columns ).indexes().map( function ( idx ) { - return { - extend: 'columnVisibility', - columns: idx, - visibility: conf.visibility, - columnText: conf.columnText - }; - } ).toArray(); + columnsVisibility: function (dt, conf) { + var columns = dt + .columns(conf.columns) + .indexes() + .map(function (idx) { + return { + extend: 'columnVisibility', + columns: idx, + visibility: conf.visibility, + columnText: conf.columnText + }; + }) + .toArray(); return columns; }, @@ -93,111 +143,113 @@ $.extend( DataTable.ext.buttons, { // Single button to set column visibility columnVisibility: { columns: undefined, // column selector - text: function ( dt, button, conf ) { - return conf._columnText( dt, conf ); + text: function (dt, button, conf) { + return conf._columnText(dt, conf); }, className: 'buttons-columnVisibility', - action: function ( e, dt, button, conf ) { - var col = dt.columns( conf.columns ); + action: function (e, dt, button, conf) { + var col = dt.columns(conf.columns); var curr = col.visible(); - col.visible( conf.visibility !== undefined ? - conf.visibility : - ! (curr.length ? curr[0] : false ) + col.visible( + conf.visibility !== undefined ? conf.visibility : !(curr.length ? curr[0] : false) ); }, - init: function ( dt, button, conf ) { + 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) { - if ( ! settings.bDestroying && settings.nTable == dt.settings()[0].nTable ) { - that.active( dt.column( conf.columns ).visible() ); - } - } ) - .on( 'column-reorder.dt'+conf.namespace, function (e, settings, details) { - // Don't rename buttons based on column name if the button - // controls more than one column! - if ( dt.columns( conf.columns ).count() !== 1 ) { - 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()); - } ); - - this.active( dt.column( conf.columns ).visible() ); + button.attr('data-cv-idx', conf.columns); + + dt.on('column-visibility.dt' + conf.namespace, function (e, settings) { + if (!settings.bDestroying && settings.nTable == dt.settings()[0].nTable) { + that.active(dt.column(conf.columns).visible()); + } + }).on('column-reorder.dt' + conf.namespace, function (e, settings, details) { + // Button has been removed from the DOM + if (conf.destroying) { + return; + } + + if (dt.columns(conf.columns).count() !== 1) { + return; + } + + // This button controls the same column index but the text for the column has + // changed + that.text(conf._columnText(dt, conf)); + + // Since its a different column, we need to check its visibility + that.active(dt.column(conf.columns).visible()); + }); + + this.active(dt.column(conf.columns).visible()); }, - destroy: function ( dt, button, conf ) { - dt - .off( 'column-visibility.dt'+conf.namespace ) - .off( 'column-reorder.dt'+conf.namespace ); + destroy: function (dt, button, conf) { + dt.off('column-visibility.dt' + conf.namespace).off( + 'column-reorder.dt' + conf.namespace + ); }, - _columnText: function ( dt, conf ) { + _columnText: function (dt, conf) { // Use DataTables' internal data structure until this is presented // is a public API. The other option is to use // `$( column(col).node() ).text()` but the node might not have been // populated when Buttons is constructed. - var idx = dt.column( conf.columns ).index(); - 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(/^\s+|\s+$/g,""); // trim - - return conf.columnText ? - conf.columnText( dt, idx, title ) : - title; + var idx = dt.column(conf.columns).index(); + var title = dt.settings()[0].aoColumns[idx].sTitle; + + if (!title) { + title = dt.column(idx).header().innerHTML; + } + + title = title + .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(/^\s+|\s+$/g, ''); // trim + + return conf.columnText ? conf.columnText(dt, idx, title) : title; } }, - colvisRestore: { className: 'buttons-colvisRestore', - text: function ( dt ) { - return dt.i18n( 'buttons.colvisRestore', 'Restore visibility' ); + text: function (dt) { + return dt.i18n('buttons.colvisRestore', 'Restore visibility'); }, - init: function ( dt, button, conf ) { - conf._visOriginal = dt.columns().indexes().map( function ( idx ) { - return dt.column( idx ).visible(); - } ).toArray(); + init: function (dt, button, conf) { + conf._visOriginal = dt + .columns() + .indexes() + .map(function (idx) { + return dt.column(idx).visible(); + }) + .toArray(); }, - action: function ( e, dt, button, conf ) { - dt.columns().every( function ( i ) { + action: function (e, dt, button, conf) { + dt.columns().every(function (i) { // Take into account that ColReorder might have disrupted our // indexes - var idx = dt.colReorder && dt.colReorder.transpose ? - dt.colReorder.transpose( i, 'toOriginal' ) : - i; + var idx = + dt.colReorder && dt.colReorder.transpose + ? dt.colReorder.transpose(i, 'toOriginal') + : i; - this.visible( conf._visOriginal[ idx ] ); - } ); + this.visible(conf._visOriginal[idx]); + }); } }, - colvisGroup: { className: 'buttons-colvisGroup', - action: function ( e, dt, button, conf ) { - dt.columns( conf.show ).visible( true, false ); - dt.columns( conf.hide ).visible( false, false ); + action: function (e, dt, button, conf) { + dt.columns(conf.show).visible(true, false); + dt.columns(conf.hide).visible(false, false); dt.columns.adjust(); }, @@ -206,8 +258,8 @@ $.extend( DataTable.ext.buttons, { hide: [] } -} ); +}); -return DataTable.Buttons; +return DataTable; })); diff --git a/app/assets/javascripts/dataTables/buttons/buttons.html5.js b/app/assets/javascripts/dataTables/buttons/buttons.html5.js old mode 100755 new mode 100644 index b7f3835e..8f2881cc --- a/app/assets/javascripts/dataTables/buttons/buttons.html5.js +++ b/app/assets/javascripts/dataTables/buttons/buttons.html5.js @@ -1,6 +1,6 @@ /*! * HTML5 export buttons for Buttons and DataTables. - * 2016 SpryMedia Ltd - datatables.net/license + * © SpryMedia Ltd - datatables.net/license * * FileSaver.js (1.3.3) - MIT license * Copyright © 2016 Eli Grey - http://eligrey.com @@ -15,21 +15,37 @@ } else if ( typeof exports === 'object' ) { // CommonJS - module.exports = function (root, $, jszip, pdfmake) { - if ( ! root ) { - root = window; - } - - if ( ! $ || ! $.fn.dataTable ) { - $ = require('datatables.net')(root, $).$; + var jq = require('jquery'); + var cjsRequires = function (root, $) { + if ( ! $.fn.dataTable ) { + require('datatables.net')(root, $); } if ( ! $.fn.dataTable.Buttons ) { require('datatables.net-buttons')(root, $); } - - return factory( $, root, root.document, jszip, pdfmake ); }; + + if (typeof window === 'undefined') { + module.exports = function (root, $, jszip, pdfmake) { + if ( ! root ) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window; + } + + if ( ! $ ) { + $ = jq( root ); + } + + cjsRequires( root, $ ); + return factory( $, root, root.document, jszip, pdfmake ); + }; + } + else { + cjsRequires( window, jq ); + module.exports = factory( jq, window, window.document ); + } } else { // Browser @@ -39,29 +55,33 @@ 'use strict'; var DataTable = $.fn.dataTable; + + // Allow the constructor to pass in JSZip and PDFMake from external requires. // Otherwise, use globally defined variables, if they are available. -function _jsZip () { - return jszip || window.JSZip; +var useJszip; +var usePdfmake; + +function _jsZip() { + return useJszip || window.JSZip; } -function _pdfMake () { - return pdfmake || window.pdfMake; +function _pdfMake() { + return usePdfmake || window.pdfMake; } DataTable.Buttons.pdfMake = function (_) { - if ( ! _ ) { + if (!_) { return _pdfMake(); } - pdfmake = m_ake; -} + usePdfmake = _; +}; DataTable.Buttons.jszip = function (_) { - if ( ! _ ) { + if (!_) { return _jsZip(); } - jszip = _; -} - + useJszip = _; +}; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * FileSaver.js dependency @@ -69,50 +89,55 @@ DataTable.Buttons.jszip = function (_) { /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ -var _saveAs = (function(view) { - "use strict"; +var _saveAs = (function (view) { + 'use strict'; // IE <10 is explicitly unsupported - if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) { + if ( + typeof view === 'undefined' || + (typeof navigator !== 'undefined' && /MSIE [1-9]\./.test(navigator.userAgent)) + ) { return; } - var - doc = view.document - // only get URL when necessary in case Blob.js hasn't overridden it yet - , get_URL = function() { + var doc = view.document, + // only get URL when necessary in case Blob.js hasn't overridden it yet + get_URL = function () { return view.URL || view.webkitURL || view; - } - , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") - , can_use_save_link = "download" in save_link - , click = function(node) { - var event = new MouseEvent("click"); + }, + save_link = doc.createElementNS('http://www.w3.org/1999/xhtml', 'a'), + can_use_save_link = 'download' in save_link, + click = function (node) { + var event = new MouseEvent('click'); node.dispatchEvent(event); - } - , is_safari = /constructor/i.test(view.HTMLElement) || view.safari - , is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent) - , throw_outside = function(ex) { - (view.setImmediate || view.setTimeout)(function() { + }, + is_safari = /constructor/i.test(view.HTMLElement) || view.safari, + is_chrome_ios = /CriOS\/[\d]+/.test(navigator.userAgent), + throw_outside = function (ex) { + (view.setImmediate || view.setTimeout)(function () { throw ex; }, 0); - } - , force_saveable_type = "application/octet-stream" + }, + force_saveable_type = 'application/octet-stream', // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to - , arbitrary_revoke_timeout = 1000 * 40 // in ms - , revoke = function(file) { - var revoker = function() { - if (typeof file === "string") { // file is an object URL + arbitrary_revoke_timeout = 1000 * 40, // in ms + revoke = function (file) { + var revoker = function () { + if (typeof file === 'string') { + // file is an object URL get_URL().revokeObjectURL(file); - } else { // file is a File + } + else { + // file is a File file.remove(); } }; setTimeout(revoker, arbitrary_revoke_timeout); - } - , dispatch = function(filesaver, event_types, event) { + }, + dispatch = function (filesaver, event_types, event) { event_types = [].concat(event_types); var i = event_types.length; while (i--) { - var listener = filesaver["on" + event_types[i]]; - if (typeof listener === "function") { + var listener = filesaver['on' + event_types[i]]; + if (typeof listener === 'function') { try { listener.call(filesaver, event || filesaver); } catch (ex) { @@ -120,38 +145,43 @@ var _saveAs = (function(view) { } } } - } - , auto_bom = function(blob) { + }, + auto_bom = function (blob) { // prepend BOM for UTF-8 XML and text/* types (including HTML) // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF - if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { - return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type}); + if ( + /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test( + blob.type + ) + ) { + return new Blob([String.fromCharCode(0xfeff), blob], { type: blob.type }); } return blob; - } - , FileSaver = function(blob, name, no_auto_bom) { + }, + FileSaver = function (blob, name, no_auto_bom) { if (!no_auto_bom) { blob = auto_bom(blob); } // First try a.download, then web filesystem, then object URLs - var - filesaver = this - , type = blob.type - , force = type === force_saveable_type - , object_url - , dispatch_all = function() { - dispatch(filesaver, "writestart progress write writeend".split(" ")); - } + var filesaver = this, + type = blob.type, + force = type === force_saveable_type, + object_url, + dispatch_all = function () { + dispatch(filesaver, 'writestart progress write writeend'.split(' ')); + }, // on any filesys errors revert to saving with object URLs - , fs_error = function() { + fs_error = function () { if ((is_chrome_ios || (force && is_safari)) && view.FileReader) { // Safari doesn't allow downloading of blob urls var reader = new FileReader(); - reader.onloadend = function() { - var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;'); + reader.onloadend = function () { + var url = is_chrome_ios + ? reader.result + : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;'); var popup = view.open(url, '_blank'); - if(!popup) view.location.href = url; - url=undefined; // release reference before dispatching + if (!popup) view.location.href = url; + url = undefined; // release reference before dispatching filesaver.readyState = filesaver.DONE; dispatch_all(); }; @@ -165,8 +195,9 @@ var _saveAs = (function(view) { } if (force) { view.location.href = object_url; - } else { - var opened = view.open(object_url, "_blank"); + } + else { + var opened = view.open(object_url, '_blank'); if (!opened) { // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html view.location.href = object_url; @@ -175,13 +206,12 @@ var _saveAs = (function(view) { filesaver.readyState = filesaver.DONE; dispatch_all(); revoke(object_url); - } - ; + }; filesaver.readyState = filesaver.INIT; if (can_use_save_link) { object_url = get_URL().createObjectURL(blob); - setTimeout(function() { + setTimeout(function () { save_link.href = object_url; save_link.download = name; click(save_link); @@ -193,16 +223,15 @@ var _saveAs = (function(view) { } fs_error(); - } - , FS_proto = FileSaver.prototype - , saveAs = function(blob, name, no_auto_bom) { - return new FileSaver(blob, name || blob.name || "download", no_auto_bom); - } - ; + }, + FS_proto = FileSaver.prototype, + saveAs = function (blob, name, no_auto_bom) { + return new FileSaver(blob, name || blob.name || 'download', no_auto_bom); + }; // IE 10+ (native saveAs) - if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { - return function(blob, name, no_auto_bom) { - name = name || blob.name || "download"; + if (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob) { + return function (blob, name, no_auto_bom) { + name = name || blob.name || 'download'; if (!no_auto_bom) { blob = auto_bom(blob); @@ -211,33 +240,31 @@ var _saveAs = (function(view) { }; } - FS_proto.abort = function(){}; + FS_proto.abort = function () {}; FS_proto.readyState = FS_proto.INIT = 0; FS_proto.WRITING = 1; FS_proto.DONE = 2; FS_proto.error = - FS_proto.onwritestart = - FS_proto.onprogress = - FS_proto.onwrite = - FS_proto.onabort = - FS_proto.onerror = - FS_proto.onwriteend = - null; + FS_proto.onwritestart = + FS_proto.onprogress = + FS_proto.onwrite = + FS_proto.onabort = + FS_proto.onerror = + FS_proto.onwriteend = + null; return saveAs; -}( - typeof self !== "undefined" && self - || typeof window !== "undefined" && window - || this.content -)); - +})( + (typeof self !== 'undefined' && self) || + (typeof window !== 'undefined' && window) || + this.content +); // Expose file saver on the DataTables API. Can't attach to `DataTables.Buttons` // since this file can be loaded before Button's core! DataTable.fileSave = _saveAs; - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Local (private) functions */ @@ -247,11 +274,10 @@ DataTable.fileSave = _saveAs; * * @param {object} config Button configuration */ -var _sheetname = function ( config ) -{ +var _sheetname = function (config) { var sheetName = 'Sheet1'; - if ( config.sheetName ) { + if (config.sheetName) { sheetName = config.sheetName.replace(/[\[\]\*\/\\\?\:]/g, ''); } @@ -264,13 +290,8 @@ var _sheetname = function ( config ) * @param {object} config Button configuration * @return {string} Newline character */ -var _newLine = function ( config ) -{ - return config.newline ? - config.newline : - navigator.userAgent.match(/Windows/) ? - '\r\n' : - '\n'; +var _newLine = function (config) { + return config.newline ? config.newline : navigator.userAgent.match(/Windows/) ? '\r\n' : '\n'; }; /** @@ -281,44 +302,41 @@ var _newLine = function ( config ) * @param {object} config Button configuration * @return {object} The data to export */ -var _exportData = function ( dt, config ) -{ - var newLine = _newLine( config ); - var data = dt.buttons.exportData( config.exportOptions ); +var _exportData = function (dt, config) { + var newLine = _newLine(config); + var data = dt.buttons.exportData(config.exportOptions); var boundary = config.fieldBoundary; var separator = config.fieldSeparator; - var reBoundary = new RegExp( boundary, 'g' ); - var escapeChar = config.escapeChar !== undefined ? - config.escapeChar : - '\\'; - var join = function ( a ) { + var reBoundary = new RegExp(boundary, 'g'); + var escapeChar = config.escapeChar !== undefined ? config.escapeChar : '\\'; + var join = function (a) { var s = ''; // If there is a field boundary, then we might need to escape it in // the source data - for ( var i=0, ien=a.length ; i 0 ) { + for (var i = 0, ien = a.length; i < ien; i++) { + if (i > 0) { s += separator; } - s += boundary ? - boundary + ('' + a[i]).replace( reBoundary, escapeChar+boundary ) + boundary : - a[i]; + s += boundary + ? boundary + ('' + a[i]).replace(reBoundary, escapeChar + boundary) + boundary + : a[i]; } return s; }; - var header = config.header ? join( data.header )+newLine : ''; - var footer = config.footer && data.footer ? newLine+join( data.footer ) : ''; + var header = config.header ? join(data.header) + newLine : ''; + var footer = config.footer && data.footer ? newLine + join(data.footer) : ''; var body = []; - for ( var i=0, ien=data.body.length ; i 1 && version[1]*1 < 603.1 ) { + var version = navigator.userAgent.match(/AppleWebKit\/(\d+\.\d+)/); + if (version && version.length > 1 && version[1] * 1 < 603.1) { return true; } @@ -352,14 +370,14 @@ var _isDuffSafari = function () * @param {int} n Column number * @return {string} Column letter(s) name */ -function createCellPos( n ){ +function createCellPos(n) { var ordA = 'A'.charCodeAt(0); var ordZ = 'Z'.charCodeAt(0); var len = ordZ - ordA + 1; - var s = ""; + var s = ''; - while( n >= 0 ) { - s = String.fromCharCode(n % len + ordA) + s; + while (n >= 0) { + s = String.fromCharCode((n % len) + ordA) + s; n = Math.floor(n / len) - 1; } @@ -369,8 +387,7 @@ function createCellPos( n ){ try { var _serialiser = new XMLSerializer(); var _ieExcel; -} -catch (t) {} +} catch (t) {} /** * Recursively add XML files from an object's structure to a ZIP file. This @@ -380,24 +397,28 @@ catch (t) {} * @param {JSZip} zip ZIP package * @param {object} obj Object to add (recursive) */ -function _addToZip( zip, obj ) { - if ( _ieExcel === undefined ) { +function _addToZip(zip, obj) { + if (_ieExcel === undefined) { // Detect if we are dealing with IE's _awful_ serialiser by seeing if it // drop attributes - _ieExcel = _serialiser - .serializeToString( - $.parseXML( excelStrings['xl/worksheets/sheet1.xml'] ) - ) - .indexOf( 'xmlns:r' ) === -1; + _ieExcel = + _serialiser + .serializeToString( + new window.DOMParser().parseFromString( + excelStrings['xl/worksheets/sheet1.xml'], + 'text/xml' + ) + ) + .indexOf('xmlns:r') === -1; } - $.each( obj, function ( name, val ) { - if ( $.isPlainObject( val ) ) { - var newDir = zip.folder( name ); - _addToZip( newDir, val ); + $.each(obj, function (name, val) { + if ($.isPlainObject(val)) { + var newDir = zip.folder(name); + _addToZip(newDir, val); } else { - if ( _ieExcel ) { + if (_ieExcel) { // IE's XML serialiser will drop some name space attributes from // from the root node, so we need to save them. Do this by // replacing the namespace nodes with a regular attribute that @@ -407,47 +428,49 @@ function _addToZip( zip, obj ) { var i, ien; var attrs = []; - for ( i=worksheet.attributes.length-1 ; i>=0 ; i-- ) { + for (i = worksheet.attributes.length - 1; i >= 0; i--) { var attrName = worksheet.attributes[i].nodeName; var attrValue = worksheet.attributes[i].nodeValue; - if ( attrName.indexOf( ':' ) !== -1 ) { - attrs.push( { name: attrName, value: attrValue } ); + if (attrName.indexOf(':') !== -1) { + attrs.push({ name: attrName, value: attrValue }); - worksheet.removeAttribute( attrName ); + worksheet.removeAttribute(attrName); } } - for ( i=0, ien=attrs.length ; i]*?) xmlns=""([^<>]*?)>/g, '<$1 $2>' ); + str = str.replace(/<([^<>]*?) xmlns=""([^<>]*?)>/g, '<$1 $2>'); - zip.file( name, str ); + zip.file(name, str); } - } ); + }); } /** @@ -460,22 +483,22 @@ function _addToZip( zip, obj ) { * (child nodes) and `text` (text content) * @return {node} Created node */ -function _createNode( doc, nodeName, opts ) { - var tempNode = doc.createElement( nodeName ); +function _createNode(doc, nodeName, opts) { + var tempNode = doc.createElement(nodeName); - if ( opts ) { - if ( opts.attr ) { - $(tempNode).attr( opts.attr ); + if (opts) { + if (opts.attr) { + $(tempNode).attr(opts.attr); } - if ( opts.children ) { - $.each( opts.children, function ( key, value ) { - tempNode.appendChild( value ); - } ); + if (opts.children) { + $.each(opts.children, function (key, value) { + tempNode.appendChild(value); + }); } - if ( opts.text !== null && opts.text !== undefined ) { - tempNode.appendChild( doc.createTextNode( opts.text ) ); + if (opts.text !== null && opts.text !== undefined) { + tempNode.appendChild(doc.createTextNode(opts.text)); } } @@ -488,27 +511,25 @@ function _createNode( doc, nodeName, opts ) { * @param {int} col Column index * @return {int} Column width */ -function _excelColWidth( data, col ) { +function _excelColWidth(data, col) { var max = data.header[col].length; var len, lineSplit, str; - if ( data.footer && data.footer[col].length > max ) { + if (data.footer && data.footer[col].length > max) { max = data.footer[col].length; } - for ( var i=0, ien=data.body.length ; i max ) { + if (len > max) { max = len; } // Max width rather than having potentially massive column widths - if ( max > 40 ) { + if (max > 40) { return 54; // 40 * 1.35 } } @@ -534,233 +555,234 @@ function _excelColWidth( data, col ) { // Excel - Pre-defined strings to build a basic XLSX file var excelStrings = { - "_rels/.rels": - ''+ - ''+ - ''+ + '_rels/.rels': + '' + + '' + + '' + '', - "xl/_rels/workbook.xml.rels": - ''+ - ''+ - ''+ - ''+ + 'xl/_rels/workbook.xml.rels': + '' + + '' + + '' + + '' + '', - "[Content_Types].xml": - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ + '[Content_Types].xml': + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + '', - "xl/workbook.xml": - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ + 'xl/workbook.xml': + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + '', - "xl/worksheets/sheet1.xml": - ''+ - ''+ - ''+ - ''+ + 'xl/worksheets/sheet1.xml': + '' + + '' + + '' + + '' + '', - "xl/styles.xml": - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ // Excel appears to use this as a dotted background regardless of values but - ''+ // to be valid to the schema, use a patternFill - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ + 'xl/styles.xml': + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + // Excel appears to use this as a dotted background regardless of values but + '' + // to be valid to the schema, use a patternFill + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + '' }; // Note we could use 3 `for` loops for the styles, but when gzipped there is @@ -771,21 +793,50 @@ var excelStrings = { // Ref: section 3.8.30 - built in formatters in open spreadsheet // https://www.ecma-international.org/news/TC45_current_work/Office%20Open%20XML%20Part%204%20-%20Markup%20Language%20Reference.pdf var _excelSpecials = [ - { match: /^\-?\d+\.\d%$/, style: 60, fmt: function (d) { return d/100; } }, // Precent with d.p. - { match: /^\-?\d+\.?\d*%$/, style: 56, fmt: function (d) { return d/100; } }, // Percent - { match: /^\-?\$[\d,]+.?\d*$/, style: 57 }, // Dollars - { match: /^\-?£[\d,]+.?\d*$/, style: 58 }, // Pounds - { match: /^\-?€[\d,]+.?\d*$/, style: 59 }, // Euros - { match: /^\-?\d+$/, style: 65 }, // Numbers without thousand separators - { match: /^\-?\d+\.\d{2}$/, style: 66 }, // Numbers 2 d.p. without thousands separators - { match: /^\([\d,]+\)$/, style: 61, fmt: function (d) { return -1 * d.replace(/[\(\)]/g, ''); } }, // Negative numbers indicated by brackets - { match: /^\([\d,]+\.\d{2}\)$/, style: 62, fmt: function (d) { return -1 * d.replace(/[\(\)]/g, ''); } }, // Negative numbers indicated by brackets - 2d.p. - { match: /^\-?[\d,]+$/, style: 63 }, // Numbers with thousand separators - { match: /^\-?[\d,]+\.\d{2}$/, style: 64 } // Numbers with 2 d.p. and thousands separators + { + match: /^\-?\d+\.\d%$/, + style: 60, + fmt: function (d) { + return d / 100; + } + }, // Percent with d.p. + { + match: /^\-?\d+\.?\d*%$/, + style: 56, + fmt: function (d) { + return d / 100; + } + }, // Percent + { match: /^\-?\$[\d,]+.?\d*$/, style: 57 }, // Dollars + { match: /^\-?£[\d,]+.?\d*$/, style: 58 }, // Pounds + { match: /^\-?€[\d,]+.?\d*$/, style: 59 }, // Euros + { match: /^\-?\d+$/, style: 65 }, // Numbers without thousand separators + { match: /^\-?\d+\.\d{2}$/, style: 66 }, // Numbers 2 d.p. without thousands separators + { + match: /^\([\d,]+\)$/, + style: 61, + fmt: function (d) { + return -1 * d.replace(/[\(\)]/g, ''); + } + }, // Negative numbers indicated by brackets + { + match: /^\([\d,]+\.\d{2}\)$/, + style: 62, + fmt: function (d) { + return -1 * d.replace(/[\(\)]/g, ''); + } + }, // Negative numbers indicated by brackets - 2d.p. + { match: /^\-?[\d,]+$/, style: 63 }, // Numbers with thousand separators + { match: /^\-?[\d,]+\.\d{2}$/, style: 64 }, + { + match: /^[\d]{4}\-[01][\d]\-[0123][\d]$/, + style: 67, + fmt: function (d) { + return Math.round(25569 + Date.parse(d) / (86400 * 1000)); + } + } //Date yyyy-mm-dd ]; - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Buttons */ @@ -796,83 +847,87 @@ var _excelSpecials = [ DataTable.ext.buttons.copyHtml5 = { className: 'buttons-copy buttons-html5', - text: function ( dt ) { - return dt.i18n( 'buttons.copy', 'Copy' ); + text: function (dt) { + return dt.i18n('buttons.copy', 'Copy'); }, - action: function ( e, dt, button, config ) { - this.processing( true ); + action: function (e, dt, button, config) { + this.processing(true); var that = this; - var exportData = _exportData( dt, config ); - var info = dt.buttons.exportInfo( config ); + var exportData = _exportData(dt, config); + var info = dt.buttons.exportInfo(config); var newline = _newLine(config); var output = exportData.str; - var hiddenDiv = $('
') - .css( { - height: 1, - width: 1, - overflow: 'hidden', - position: 'fixed', - top: 0, - left: 0 - } ); - - if ( info.title ) { + var hiddenDiv = $('
').css({ + height: 1, + width: 1, + overflow: 'hidden', + position: 'fixed', + top: 0, + left: 0 + }); + + if (info.title) { output = info.title + newline + newline + output; } - if ( info.messageTop ) { + if (info.messageTop) { output = info.messageTop + newline + newline + output; } - if ( info.messageBottom ) { + if (info.messageBottom) { output = output + newline + newline + info.messageBottom; } - if ( config.customize ) { - output = config.customize( output, config, dt ); + if (config.customize) { + output = config.customize(output, config, dt); } - var textarea = $('