web-dev-qa-db-fra.com

Comment exporter toutes les lignes de Datatables en utilisant Ajax?

J'utilise une nouvelle fonctionnalité dans Datatables: "boutons d'exportation HTML5". Je charge des données avec Ajax. 

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

Le problème est qu’il n’exporte que la page actuellement affichée.

J'exporte comme ceci:

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

Comment puis-je exporter toutes les lignes?

18
Fox

Selon la documentation DataTables il n’existe aucun moyen d’exporter toutes les lignes lorsque vous utilisez le côté serveur:

Remarque spéciale sur le traitement côté serveur: lors de l’utilisation de DataTables en mode de traitement côté serveur (serverSide), le selector-modifier a très peu d’effet sur les lignes sélectionnées car tous les traitements (commande, recherche, etc.) sont effectués sur le serveur. Par conséquent, les seules lignes qui existent côté client sont celles affichées dans le tableau à la fois, et le sélecteur ne peut sélectionner que les lignes qui figurent sur la page en cours.

J'ai résolu ce problème en ajoutant un paramètre 'ALL' au menu de longueur et en formant les utilisateurs finaux à afficher tous les enregistrements avant de procéder à une exportation PDF (ou 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
});
19
Selcuk

Vous devez indiquer à la fonction AJAX d'obtenir toutes les données, puis exporter, mais annuler le tirage proprement dit, afin que toutes ces données ne soient pas chargées dans le DOM. Cependant, les données complètes existeront toujours en mémoire pour l'API DataTables. Vous devez donc l'actualiser de la même manière qu'avant l'exportation.

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();
};

et:

    buttons: [
        {
            extend: 'Excel',
            action: newExportAction
        },
18
kevinpo

Cette définition de bouton a fonctionné pour moi dans un tableau déroulant (au lieu de pagination): 

{
  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();
  }
}

Cela forcera le DataTable à demander toutes les lignes pour le filtrage actuel pour une demande. Ensuite, il appelle directement l'action souhaitée du bouton Exporter. La variable addOptions peut être utilisée pour modifier la configuration standard du bouton d'exportation.

Vous pouvez toutefois rencontrer des problèmes si vous avez beaucoup de lignes car elles sont toutes chargées dans le DOM.

4
haui

Je sais que c’est une vieille question, mais pour ceux qui ont des problèmes, voici ma solution.

Variables:

var downloading = false,
    downloadTimestamp = null;

Définition du bouton de téléchargement:

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
        }
    }
}]

Définition Ajax:

ajax: {
    url: ajaxURL,
    type: 'POST',
    data: function(data) {
        data.timestamp = new Date().getTime(); //add timestamp to data to be sent, it's 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
    }
}

fonction '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
    }
}

Du côté serveur:

if (download == false) , le serveur exécute SELECT les colonnes dans les tables WHERE rowNumber ENTRE firstRow ET lastRow et génère le résultat pour un affichage normal dans DataTable .

if (download == true) , le serveur exécute SELECT les colonnes dans les tables et stocke toutes les lignes au format CSV (ou tout autre format de fichier en fonction de ce que votre environnement de serveur est capable de produire) server- côté pour une récupération ultérieure par requête GET.

Voici le code ASP JScript que j'ai utilisé côté serveur:

    var timestamp = Number(Request.QueryString('ts')), //if it's 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's 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 = []

Oui, il est tout à fait possible que cela fonctionne . En interne, DataTables a une fonction appelée buttons.exportData (). Lorsque vous appuyez sur un bouton, cette fonction est appelée et renvoie le contenu de la page en cours. Vous pouvez écraser cette fonction afin d’obtenir tous les résultats côté serveur en fonction des filtres actuels. Et en appelant la même URL utilisée pour la pagination ajax. 

Vous écrasez-le avant d'initialiser votre table. Le code est comme suit:

$(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()} 
            }
        }
});
3
diogenesgg

La réponse de Selcuk fonctionnera parfaitement si nous pouvons corriger la valeur de "Tout" à l'avance ... .. Supposons que le nombre de lignes soit stocké dans la variable row_count ensuite 

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
}); 
0

J'utilise Datatables Version: 1.10.15 et la réponse de @ kevenpo est au travail. J'ai dû le modifier un peu pour gérer nos paramètres côté serveur, mais c'était le seul obstacle. J'ai changé sa ligne: data.length = 2147483647; en data.params[2]= -1; car nous avons stocké nos paramètres côté serveur dans un sous-tableau params. Je ne l'ai pas encore testé avec un très grand ensemble de données pour voir quelle est la performance, mais c'est une solution très intelligente. 

0
Robyn Wyrick