3

I use PhpSpreadsheet for export mysql data in this way:

Page 1:

button trigger window.open to page with script for export

window.open('/export.php?data.., '_self');

Page 2 (export.php):

Whole system for export and php://output

ob_end_clean();
ob_start();
$objWriter->save('php://output');            
exit;

Can I somehow understand if the export page has finished? I need this for trigger a overlay.

What i have tried?

Looking in stackoverflow I tried this solution but it didn't work:

overlay.style.display = "block";
let myPopup =  window.open('/export.php?data.., '_self');
myPopup.addEventListener('load', () => {
   console.log('load'); //just for debug
   overlay.style.display = "none";
}, false);
Simone Rossaini
  • 8,115
  • 1
  • 13
  • 34

2 Answers2

1

This will execute an ajax request which will result in a download without refreshing the page with jQuery

<button id='download'>Download Spreadsheet</button>
<form method='get' action='/export.php?' id='hiddenForm'>
  <input type='hidden' name='foo' value='bar' />
  <input type='hidden' name='foo2' value='bar2' />
</form>


$(document).on('click', '#download', function (e) {
    e.preventDefault();
    $('#hiddenForm').submit();
});

Make sure your PHP outputs the correct content type

if (file_exists($file)) {
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="'.basename($file).'"');
    header('Expires: 0');
    header('Cache-Control: must-revalidate');
    header('Pragma: public');
    header('Content-Length: ' . filesize($file));
    readfile($file);
    exit;
}

Another Option

Based on this post, there isn't an api available to truly detect the loading of a javascript window object across all different browsers. This method uses a defer callback and postMessage approach to acommodate most modern browsers.

function defer (callback) {
    var channel = new MessageChannel();
    channel.port1.onmessage = function (e) {
        callback();
    };
    channel.port2.postMessage(null);
}

var awaitLoad = function (win, cb){
    var wasCalled = false;
    function unloadListener(){
        if (wasCalled)
            return;
        wasCalled = true;
        win.removeEventListener("unload", unloadListener);
        win.removeEventListener("pagehide", unloadListener);
        // Firefox keeps window event listeners for multiple page loads
        defer(function (){
            win.document.readyState;
            // IE sometimes throws security error if not accessed 2 times
            if (win.document.readyState === "loading")
                win.addEventListener("load", function loadListener(){
                    win.removeEventListener("load", loadListener);
                    cb();
                });
            else
                cb();
        });
    };
    win.addEventListener("unload", unloadListener);
    win.addEventListener("pagehide", unloadListener);
    // Safari does not support unload
}


w = window.open();
w.location.href="/export.php?data=foo";
awaitLoad(w, function (){
   console.log('got it')
});
Kinglish
  • 23,358
  • 3
  • 22
  • 43
  • That method only work if i manually close the window and don't use `_self`, do you know a method for close window after `$objWriter->save('php://output');` because i try to use `ob_clean();` and `echo "";` for close it but nothing work. – Simone Rossaini Jun 21 '21 at 06:55
  • Initially I was focused on just working with an open window and detection of load completion. Now I am asking myself why this method? Why not an async fetch or ajax request? – Kinglish Jun 21 '21 at 07:03
  • It was actually a method of having a loader in the meantime that I was tweaking things, and ajax request will allow do use `php://output` ? – Simone Rossaini Jun 21 '21 at 07:04
  • I updated my answer with another option for ajax download from php://ouput without page reload – Kinglish Jun 21 '21 at 07:20
  • I try your method but when press the button all data after `/export.php?` it's deleted – Simone Rossaini Jun 21 '21 at 07:25
  • ok, updated with a much simpler approach. define form fields in a hidden form and submit through jquery. PHP output headers also included, but you will need to customize those – Kinglish Jun 21 '21 at 07:36
  • With `php://output` doesn't exist a `$file` and how that can resolve the main problem? how i can detect the finish of script? because if you use form it's same as used `open window`. – Simone Rossaini Jun 21 '21 at 07:45
0

I will give +1 to @Kinglish answer because awaitLoad function can help, in my case i used ajax instead with this method Link answer

What is it about?

  • Create an Ajax call with json type to export page and use done to catch JSON like:

    overlay.style.display = "block"; //active overlay
    $.ajax({
      url: "/export.php",
      type: "GET",
      dataType: 'json',
      data: {
        data: data
      }
    }).done(function(data) {
      var $a = $("<a>");
      $a.attr("href", data.file);
      $("body").append($a);
      $a.attr("download", "nameoffile.xls");
      $a[0].click();
      $a.remove();
      overlay.style.display = "none"; //deactive overlay
    });
    
  • In PHP page catch php://output with ob_get_contents then return json like:

    $xlsData = ob_get_contents();
    ob_end_clean();
    $response = array(
      'op' => 'ok',
      'nomefile' => 'nameofile',
      'file' => "data:application/vnd.ms-excel;base64,".base64_encode($xlsData)
    );
    die(json_encode($response));
    
    
  • The last step is create, click and remove a fake link.

Simone Rossaini
  • 8,115
  • 1
  • 13
  • 34