Как экспортировать все строки из Datatables с помощью Ajax?

Я использую новую функцию в Datatables: "кнопки экспорта HTML5". Я загружаю данные с помощью Ajax.

https://datatables.net/extensions/buttons/examples/html5/simple.html

Проблема заключается в том, что он экспортирует только отображаемую страницу.

Я экспортирую вот так:

buttons: [
    {
        extend: 'pdfHtml5',
        text: 'PDF',
        exportOptions: {
            "columns": ':visible',
        }
    },
]

Как я могу экспортировать все строки?

Ответы

Ответ 1

В соответствии с Документация DataTables нет способа экспортировать все строки, когда вы используете серверную сторону:

Особое примечание при обработке на стороне сервера: при использовании DataTables в режиме обработки на стороне сервера (serverSide) selector-modifier оказывает очень малое влияние на выбранные строки, поскольку вся обработка (заказы, поиск и т.д.) выполняется на сервер. Поэтому единственными строками, которые существуют на стороне клиента, являются те, которые показаны в таблице в любой момент времени, и селектор может выбирать только те строки, которые находятся на текущей странице.

Я работал над этим, добавив параметр "ВСЕ" в меню длины и обучая конечных пользователей отображать все записи перед экспортом PDF (или XLS):

var table = $('#example').DataTable({
    serverSide: true,
    ajax: "/your_ajax_url/",
    lengthMenu: [[25, 100, -1], [25, 100, "All"]],
    pageLength: 25,
    buttons: [
        {
            extend: 'excel',
            text: '<span class="fa fa-file-excel-o"></span> Excel Export',
            exportOptions: {
                modifier: {
                    search: 'applied',
                    order: 'applied'
                }
            }
        }
    ],
    // other options
});

Ответ 2

Вам нужно сообщить функции AJAX, чтобы получить все данные, затем выполнить экспорт, но отменить фактическую ничью, чтобы все эти данные не загружались в DOM. Полные данные все еще будут сохраняться в памяти для API DataTables, поэтому вам нужно обновить его до того, как оно было до экспорта.

var oldExportAction = function (self, e, dt, button, config) {
    if (button[0].className.indexOf('buttons-excel') >= 0) {
        if ($.fn.dataTable.ext.buttons.excelHtml5.available(dt, config)) {
            $.fn.dataTable.ext.buttons.excelHtml5.action.call(self, e, dt, button, config);
        }
        else {
            $.fn.dataTable.ext.buttons.excelFlash.action.call(self, e, dt, button, config);
        }
    } else if (button[0].className.indexOf('buttons-print') >= 0) {
        $.fn.dataTable.ext.buttons.print.action(e, dt, button, config);
    }
};

var newExportAction = function (e, dt, button, config) {
    var self = this;
    var oldStart = dt.settings()[0]._iDisplayStart;

    dt.one('preXhr', function (e, s, data) {
        // Just this once, load all data from the server...
        data.start = 0;
        data.length = 2147483647;

        dt.one('preDraw', function (e, settings) {
            // Call the original action function 
            oldExportAction(self, e, dt, button, config);

            dt.one('preXhr', function (e, s, data) {
                // DataTables thinks the first item displayed is index 0, but we're not drawing that.
                // Set the property to what it was before exporting.
                settings._iDisplayStart = oldStart;
                data.start = oldStart;
            });

            // Reload the grid with the original page. Otherwise, API functions like table.cell(this) don't work properly.
            setTimeout(dt.ajax.reload, 0);

            // Prevent rendering of the full data to the DOM
            return false;
        });
    });

    // Requery the server with the new one-time export settings
    dt.ajax.reload();
};

и

    buttons: [
        {
            extend: 'excel',
            action: newExportAction
        },

Ответ 3

Да, это вполне возможно сделать. Внутри DataTables имеет функцию, называемую button.exportData(). Когда вы нажимаете кнопку, эта функция вызывается и возвращает текущее содержимое страницы. Вы можете перезаписать эту функцию, чтобы она извлекала все результаты на стороне сервера на основе текущих фильтров. И называя тот же url используемый для ajax pagination.

Вы перезаписываете его перед инициализацией своей таблицы. Код выглядит следующим образом:

$(document).ready(function() {

    jQuery.fn.DataTable.Api.register( 'buttons.exportData()', function ( options ) {
            if ( this.context.length ) {
                var jsonResult = $.ajax({
                    url: 'myServerSide.json?page=all',
                    data: {search: $(#search).val()},
                    success: function (result) {
                        //Do nothing
                    },
                    async: false
                });

                return {body: jsonResult.responseJSON.data, header: $("#myTable thead tr th").map(function() { return this.innerHTML; }).get()};
            }
        } );

    $("#myTable ").DataTable(
        {
            "dom": 'lBrtip',
            "pageLength": 5, 
            "buttons": ['csv','print', 'excel', 'pdf'],
            "processing": true,
            "serverSide": true,
            "ajax": {
                "url": "myServerSide.json",
                "type": 'GET',
                "data": {search: $(#search).val()} 
            }
        }
});

Ответ 4

Это определение кнопки работало для меня в прокрученной таблице (вместо пейджинга):

{
  text: 'PDF',
  action: function(e, dt, button, config) {
    dt.one('preXhr', function(e, s, data) {
      data.length = -1;
    }).one('draw', function(e, settings, json, xhr) {
      var pdfButtonConfig = $.fn.DataTable.ext.buttons.pdfHtml5;
      var addOptions = { exportOptions: { "columns" : ":visible" }};

      $.extend(true,pdfButtonConfig,addOptions);
      pdfButtonConfig.action(e, dt, button, pdfButtonConfig);
    }).draw();
  }
}

Ответ 5

Я знаю, что это старый вопрос, однако для тех, кто борется с этим, здесь мое решение.

Переменные

var downloading = false,
    downloadTimestamp = null;

Определение кнопки загрузки:

buttons: [{
    text: '<span class="glyphicon glyphicon-save-file" aria-hidden="true"></span>',
    titleAttr: 'CSV',
    className: 'downloadCSV',
    action: function(e, dt, node, config) {
        if (downloading === false) { //if download is in progress, do nothing, else
            node.attr('disabled', 'disabled'); //disable download button to prevent multi-click, probably some sort of *busy* indicator is a good idea

            downloading = true; //set downloading status to *true*

            dt.ajax.reload(); //re-run *DataTables* AJAX query with current filter and sort applied
        }
    }
}]

Определение Ajax:

ajax: {
    url: ajaxURL,
    type: 'POST',
    data: function(data) {
        data.timestamp = new Date().getTime(); //add timestamp to data to be sent, it going to be useful when retrieving produced file server-side

        downloadTimestamp = data.timestamp; //save timestamp in local variable for use with GET request when retrieving produced file client-side

        if (downloading === true) { //if download button was clicked
            data.download = true; //tell server to prepare data for download
            downloading = data.draw; //set which *DataTable* draw is actually a request to produce file for download
        }

        return { data: JSON.stringify(data) }; //pass data to server for processing
    }
}

'preDrawCallback':

preDrawCallback: function(settings) {
    if (settings.iDraw === downloading) { //if returned *DataTable* draw matches file request draw value
        downloading = false; //set downloading flag to false

        $('.downloadCSV').removeAttr('disabled'); //enable download button

        window.location.href = ajaxURL + '?' + $.param({ ts: downloadTimestamp }); //navigate to AJAX URL with timestamp as parameter to trigger file download. Or You can have hidden IFrame and set its *src* attribute to the address above.

        return false; //as it is file request, table should not be re-drawn
    }
}

на стороне сервера:

if (download == false), тогда сервер выполняет SELECT столбцы FROM tables WHERE rowNumber BETWEEN firstRow AND lastRow и выводит результат для нормального отображения в DataTable.

if (download == true), тогда сервер выполняет таблицы SELECT FROM FROM и сохраняет все строки, отформатированные как CSV файл (или любой другой формат файла в зависимости от того, что может создать серверная среда) на стороне сервера для последующего поиска GET.

Ниже приведен код ASP JScript, который я использовал на стороне сервера:

    var timestamp = Number(Request.QueryString('ts')), //if it a GET request, get timestamp
        tableData = {
            draw: data.draw,
            recordsTotal: 100, //some number static or dynamic
            recordsFiltered: 10, //some number static or dynamic
            data: []
        };
        jsonData = String(Request.Form('data')), //if it POST request, get data sent by *DataTable* AJAX
        data = jsonData === 'undefined' || jsonData.length === 0 ? null : JSON.parse(jsonData); //do some error checking (optional)

    if(!isNaN(timestamp)) { //check timestamp is valid
        var csvTextKey = 'download-' + timestamp, //this is where timestamp value is used (can be any other unique value)
            csvText = Session(csvTextKey); //obtain saved CSV text from local server-side storage

        if(typeof csvText === 'undefined') { //if CSV text does not exist in local storage, return nothing (or throw error is You wish)
            Response.End();
        }

        //if CSV exists:
        Response.ContentType = 'text/csv'; //set response mime type
        Response.AddHeader('Content-Disposition', 'attachment; filename=test.csv'); //add header to tell browser that content should be downloaded as file and not displayed

        Response.Write(csvText); //send all content to browser

        Response.End(); //stop further server-side code execution
    }

    //if timestamp is not valid then we assume this is POST request, hence data should be either prepared for display or stored for file creation

    if(typeof data !== 'object' || data === null) { //do some more clever error checking
        throw 'data is not an object or is null';
    }

        var recordset = data.download === true ? sqlConnection.Execute('SELECT * FROM #FinalTable') : Utilities.prepAndRunSQLQuery('SELECT * FROM #FinalTable WHERE rowId BETWEEN ? AND ?', [data.start, data.start + data.length], //execute SELECT either for display or for file creation
            headerRow = [],
            sqlHeaderRow = [],
            exportData = [];; 

        if(data.download === true) { //create CSV file (or any other file)
            if(!Array.isArray(data.columns)) {
                throw 'data.columns is not an array';
            }

            for(var i = 0, dataColumnsCount = data.columns.length; i < dataColumnsCount; ++i) {
                var dataColumn = data.columns[i], //get columns data object sent by client
                    title = dataColumn.title, //this is custom property set on client-side (not shown in code above)
                    sqlColumnName = typeof dataColumn.data === 'string' ? dataColumn.data : (typeof dataColumn.data.display === 'string' ? dataColumn.data.display : dataColumn.data['_']); //set SQL table column name variable

                if(typeof title === 'string' && typeof sqlColumnName === 'string' && columnNames.indexOf(sqlColumnName) > -1) { //some more error checking
                    headerRow.push(title);
                    sqlHeaderRow.push(sqlColumnName);
                }
            }

            exportData.push('"' + headerRow.join('","') + '"'); //add table header row to in CSV file format
        }

        while(recordset.EOF === false) { //iterate through recordset
            if(data.download === true) { //if download flag is set build string containing CSV content
                var row = [];

                for(var i = 0, count = sqlHeaderRow.length; i < count; ++i) {
                    row.push(String(recordset.Fields(sqlHeaderRow[i]).Value).replace('"', '""'));
                }

                exportData.push('"' + row.join('","') + '"');
            }

            else { //else format data for display
                var row = {};

                for(var i = 1, fieldsCount = recordset.Fields.Count; i < fieldsCount; ++i) {
                    var field = recordset.Fields(i),
                        name = field.Name,
                        value = field.Value;

                    row[name] = value;
                }

                tableData.data.push(row);
            }

            recordset.MoveNext();
        }

if(data.download === true) { //save CSV content in server-side storage
    Session('download-' + data.timestamp) = exportData.join('\r\n'); //this is where timestamp value is used (can be any other unique value)
}

Response.Write(JSON.stringify(tableData)); //return data for display, if download flag is set, tableData.data = []

Ответ 6

Большое спасибо пользователю "kevinpo". Он дал способ, как все записи из jquery datatable можно загружать как Excel, когда обработка на стороне сервера включена. Основываясь на его ответе, здесь я реализовал полную функциональность экспорта (copy, excel, csv, pdf, print) для обработки на стороне сервера.

внутри $(document).ready() определите нижеприведенную функцию & вызывайте эту функцию для action каждой кнопки экспорта, как показано ниже:

/* For Export Buttons available inside jquery-datatable "server side processing" - Start
- due to "server side processing" jquery datatble does not support all data to be exported
- below function makes the datatable to export all records when "server side processing" is on */

function newexportaction(e, dt, button, config) {
    var self = this;
    var oldStart = dt.settings()[0]._iDisplayStart;
    dt.one('preXhr', function (e, s, data) {
        // Just this once, load all data from the server...
        data.start = 0;
        data.length = 2147483647;
        dt.one('preDraw', function (e, settings) {
            // Call the original action function
            if (button[0].className.indexOf('buttons-copy') >= 0) {
                $.fn.dataTable.ext.buttons.copyHtml5.action.call(self, e, dt, button, config);
            } else if (button[0].className.indexOf('buttons-excel') >= 0) {
                $.fn.dataTable.ext.buttons.excelHtml5.available(dt, config) ?
                    $.fn.dataTable.ext.buttons.excelHtml5.action.call(self, e, dt, button, config) :
                    $.fn.dataTable.ext.buttons.excelFlash.action.call(self, e, dt, button, config);
            } else if (button[0].className.indexOf('buttons-csv') >= 0) {
                $.fn.dataTable.ext.buttons.csvHtml5.available(dt, config) ?
                    $.fn.dataTable.ext.buttons.csvHtml5.action.call(self, e, dt, button, config) :
                    $.fn.dataTable.ext.buttons.csvFlash.action.call(self, e, dt, button, config);
            } else if (button[0].className.indexOf('buttons-pdf') >= 0) {
                $.fn.dataTable.ext.buttons.pdfHtml5.available(dt, config) ?
                    $.fn.dataTable.ext.buttons.pdfHtml5.action.call(self, e, dt, button, config) :
                    $.fn.dataTable.ext.buttons.pdfFlash.action.call(self, e, dt, button, config);
            } else if (button[0].className.indexOf('buttons-print') >= 0) {
                $.fn.dataTable.ext.buttons.print.action(e, dt, button, config);
            }
            dt.one('preXhr', function (e, s, data) {
                // DataTables thinks the first item displayed is index 0, but we're not drawing that.
                // Set the property to what it was before exporting.
                settings._iDisplayStart = oldStart;
                data.start = oldStart;
            });
            // Reload the grid with the original page. Otherwise, API functions like table.cell(this) don't work properly.
            setTimeout(dt.ajax.reload, 0);
            // Prevent rendering of the full data to the DOM
            return false;
        });
    });
    // Requery the server with the new one-time export settings
    dt.ajax.reload();
};
//For Export Buttons available inside jquery-datatable "server side processing" - End

А для кнопок определите, как показано ниже

"buttons": [
                           {
                               "extend": 'copy',
                               "text": '<i class="fa fa-files-o" style="color: green;"></i>',
                               "titleAttr": 'Copy',                               
                               "action": newexportaction
                           },
                           {
                               "extend": 'excel',
                               "text": '<i class="fa fa-file-excel-o" style="color: green;"></i>',
                               "titleAttr": 'Excel',                               
                               "action": newexportaction
                           },
                           {
                               "extend": 'csv',
                               "text": '<i class="fa fa-file-text-o" style="color: green;"></i>',
                               "titleAttr": 'CSV',                               
                               "action": newexportaction
                           },
                           {
                               "extend": 'pdf',
                               "text": '<i class="fa fa-file-pdf-o" style="color: green;"></i>',
                               "titleAttr": 'PDF',                               
                               "action": newexportaction
                           },
                           {
                                "extend": 'print',
                                "text": '<i class="fa fa-print" style="color: green;"></i>',
                                "titleAttr": 'Print',                                
                                "action": newexportaction
                           }
],

Ответ 7

Ответ Selcuk будет работать абсолютно нормально, если мы сможем зафиксировать значение "Все" заранее. Предположим, что количество строк хранится в переменной row_count. Тогда

var row_count = $("#row_count").val();
var table = $('#example').DataTable({
    serverSide: true,
    ajax: "/your_ajax_url/",
    lengthMenu: [[25, 100, row_count], [25, 100, "All"]],
    pageLength: 25,
    buttons: [
        {
            extend: 'excel',
            text: '<span class="fa fa-file-excel-o"></span> Excel Export',
            exportOptions: {
                modifier: {
                    search: 'applied',
                    order: 'applied'
                }
            }
        }
    ],
    // other options
}); 

Ответ 8

Я использую Datatables Version: 1.10.15 и получил @kevenpo ответ на работу. Мне пришлось немного изменить его, чтобы обрабатывать наши параметры на стороне сервера, но это был единственный камень преткновения. Я изменил его строку: data.length = 2147483647; до data.params[2]= -1;, потому что мы сохранили наши параметры на стороне сервера в подпараллеле params. Я еще не тестировал его с очень большим набором данных, чтобы узнать, что такое производительность, но это очень умное решение.

Ответ 9

Если вы используете Laravel Framework, вы можете использовать это...

$.fn.DataTable.Api.register( 'buttons.exportData()', function( options ) {
  if(this.context.length) {

    var src_keyword = $('.dataTables_filter input').val();

    var items = [];

    $.ajax({
      url: "server_side_url",
      success: function (result) {

        var k = 1;
        $.each(result.data, function(key, value) {

          var item = [];

          item.push(k);
          item.push(value.username);
          item.push(value.email);
          item.push(value.created_at);
          item.push(value.status);

          // filter search with regex
          arr = $.map(item, function (value) {
            var search = new RegExp(src_keyword, "gi");
            if(value.toString().match(search)) return value;
            return null;
          });

          if(!src_keyword || (src_keyword && arr.length)) {
            items.push(item);
            k++;
          }
        });
      },
      async: false
    });

    return {
      body: items, 
      // skip actions header
      header: $("#user_table thead tr th").map(function() { 
        if(this.innerHTML!='Actions')
          return this.innerHTML; 
      }).get()
    };
  }
});

var user_table = $('#user_table').DataTable({
  dom: 'Bfrtip',
  buttons: [
  'copy', 'csv', 'excel', 'pdf', 'print'
  ],
  "oSearch": {"bSmart": false},
  processing: true,
  serverSide: true,
  ajax: {
    url: "server_side_url",
    type: 'GET',
    data: function (d) {
      d.status = "";
    }
  },
  columns: [
  {data: 'DT_RowIndex', name: 'DT_RowIndex'},
  {data: 'username', name: 'username'},
  {data: 'email', name: 'email'},
  {data: 'created_at', name: 'created_at'},
  {data: 'status', name: 'status'},
  {data: 'actions', name: 'actions', orderable: false, searchable: false},
  ],
});

Ответ 10

@diogenesgg ответ хорош!

но я проверил $.fn.DataTable.Api.register не поддерживает Promise

Итак, я сначала получил данные.

    const {data} = await selectDailyConnectStatistics({
      page: 1,
      limit: 99999999
    }))
    excelDatas = data.list

    $("#table").DataTable().button('.buttons-excel').trigger();

Второй триггер для экспорта в Excel.

  let excelDatas = []
  $.fn.DataTable.Api.register('buttons.exportData()', function(options) {
    if (this.context.length ) {
      return {
        body: _.map(excelDatas, v=> [v.data, ...]), 
        header: ['colum header name', ...]
      }
    }
  });

Ответ 11

Просто хотел опубликовать фактический ответ для людей, борющихся с этим.

Если вы экспортируете с помощью кнопки excel, вы можете использовать свойство кнопки customizeData для форматирования данных, которые будут превосходить момент до его экспорта.

Я использовал это, чтобы сделать синхронный вызов api на моем сервере, чтобы получить данные, вернуть их, массировать, а затем позволить ему продолжить его путь. Код ниже.

                           {
                extend: 'excel',
                customizeData: function (p)
                {
                    //get the params for the last datatables ajax call
                    var params = JSON.parse(options.dataTable.ajax.params());
                    //flag to tell the server to ignore paging info and get everything that matches the filter
                    params.export = true;
                    UC.Api(options.api.read.getHook(), params, function (data)
                    {
                        p.body = new Array();
                        $.each(data.data, function (i, d)
                        {
                            var item = [d.serial, UC.FormatDateToLocal(d.meta.Date), d.transmission.title, d.transmission.type, d.transmission.information];
                            p.body.push(item);
                        });
                    }, null, { async: false });
                }
            },