vendredi 12 décembre 2014

Comment exécuter un export EXCEL en arrrière plan ?


Il est possible en Javascript d'exécuter des tâches en "arrière plan".

Il faut être conscient que cette "possibilité" offerte est limitée pour différentes raisons techniques.

Les raisons sortent du cadre de cet article - par exemple le nombre de tâches Javascript pouvant être exécutées en "arrière plan" dépend notamment de votre navigateur.

Ce qui nous intéresse dans cet article est la possibilité de générer un fichier EXCEL sans "bloquer" votre site / application Web.

Ayons à l'esprit que l'exécution de plusieurs tâches peuvent ralentir fortement (voire "bloquer") votre site / application.

Une réflexion en amont est primordiale pour user (et non abuser) des ressources mises à disposition.

Cette fonctionnalité peut s'avérer fort utile notamment si vous devez exporter un volume important de données.


Générer en arrière plan le fichier EXCEL

  • Nous allons dans un premier temps initialiser le traitement (dans <ma_page.php>)

<script>
var tag = 0;
var worker = null;
</script>

La variable <tag> va nous servir à "décrire" le fichier EXCEL.

La variable <worker> est la tâche (ou "worker" au sens Javascript) qui aura pour responsabilité de générer notre fichier EXCEL.

  • Ajoutons un bouton <image> pour que l'utilisateur puisse générer le fichier EXCEL après  un click sur ce bouton (dans <ma_page.php>)

=> excel.png

<img id="img_excel" src="images/excel.png" onclick="generateExcel()" 
style="cursor: pointer;" title="Exportez vos données dans un fichier EXCEL !" />
&nbsp;&nbsp;<span style="position: relative; top: -8px;"><u>export</u></span>
<a id='trigger_download' style='visibility: hidden' href='http://www.monsite.com/mon_fichier.xlsx' target='_blank'>*</a>

La fonction Javascript generateExcel() permet de démarrer le traitement en "arrière plan".

L'élément <trigger_download> permettra le téléchargement du fichier EXCEL généré ("mon_fichier.xlsx") via le navigateur.

  • Examinons la préparation de l'export EXCEL (dans <ma_page.php>)

function generateExcel() {
tag = Math.floor((Math.random()*1000)+1);

var img_excel = document.getElementById("img_excel");
img_excel.src = "images/mini_spinner.gif";
img_excel.onclick = "";

      // un message pour l'utilisateur :  "Votre fichier EXCEL est en cours de génération...!"

setTimeout(function(){ backgroundTask(); }, 2000);
}

=> mini_spinner.gif


Nous avertissons l'utilisateur que le fichier EXCEL est en cours de génération - au bout de quelques secondes (dans notre exemple 2 secondes, le temps que le message s'affiche et soit lu) nous démarrerons l'exécution du traitement (fonction Javascript backgroundTask()).

A noter qu'il n'est plus possible de lancer un nouvel export EXCEL tant que l'export en cours n'est pas terminé ("mini_spinner.gif").

La variable <tag> est renseignée avec une valeur aléatoire pour obtenir une certaine "unicité" dans la description du fichier EXCEL.

  • Le démarrage de l'export EXCEL (dans <ma_page.php>)

function backgroundTask() {
worker = new Worker("excelWorker.js");

worker.onmessage = function (event) {
var erreur = event.data.indexOf("Erreur");

if (erreur != -1)
// message d'erreur
else {
// message fichier EXCEL généré

setTimeout(function(){ download_excel(); }, 2000);
}
};

worker.postMessage({'data1': data1, 'data2': data2, ... ,'tag': tag });
}

Nous pouvons envoyer au "worker" des messages et il peut en recevoir.

--> La fonction <onmessage> gére les messages reçus - ce qui permet de déterminer dans notre exemple si le fichier EXCEL a été ou pas généré (valeur de la variable <erreur>).

Si cela est le cas nous pouvons le télécharger (fonction download_excel()) via le navigateur.

function download_excel() {
if (worker != null)
worker.terminate();

var trigger_download = document.getElementById('trigger_download');
trigger_download.click();

var img_excel = document.getElementById("img_excel");
img_excel.src = "images/excel.png";

var fct = "generateExcel()"; 
img_excel.onclick = new Function(fct);
}

A noter l'utilisation de la fonction <worker.terminate()> pour stopper la tâche.

--> La fonction <postMessage> gére les messages envoyés - pour ce qui nous concerne ce sont les données nécessaires au traitement de génération du fichier EXCEL ("data1", "data2", ..."tag" est une donnée qui sera stockée comme propriété dans le fichier EXCEL).

Le corps du traitement est spécifié dans le fichier Javascript <excelWorker.js>.

  • L'export EXCEL (excelWorker.js)

Le fichier <excelWorker.js> :

importScripts("XMLHttpRequest.js");

onmessage = function(event) {
var arg = event.data;

try {
var xhr = creatXMLHTTPRequest();

xhr.open("GET",
"http://www.monsite.com/generation_excel.php?data1="
+ arg.data1 + "&data2=" + arg.data2 ...
+ "&tag=" + arg.tag, true);

xhr.send(null);

xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (xhr.responseText.indexOf("0") != -1)
postMessage("Fichier EXCEL g\351n\351r\351 !");
else
postMessage("Erreur lors la g\351n\351ration du fichier EXCEL !");
}
}
};

} catch (e) {
postMessage("Erreur lors la g\351n\351ration du fichier EXCEL : "
+ e.message);
}
};

Le fichier <XMLHttpRequest.js>  (déclaration objet AJAX) :

function creatXMLHTTPRequest() {
try {
xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch(e) {
try {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch(e2) {
xmlHttp = false;
}
}
if (!xmlHttp && typeof XMLHttpRequest != 'undefined') {
xmlHttp = new XMLHttpRequest();
}
return xmlHttp;
}

La page PHP <generation_excel.php> contient le code pour générer le fichier EXCEL :

<?php
header('Content-Type: text/xml; charset=ISO-8859-1');

header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past

echo "<?xml version=\"1.0\"?>\n";

ini_set("max_execution_time", 'time_limit');

$error = 0;

// votre code pour générer le fichier EXCEL

// description (propriété) du fichier  = $_GET["tag"]

// si pas d'erreur : $_SESSION["tag"] = $_GET["tag"] -> utile pour garder une trace dans la session

// si erreur alors $erreur  = 1

echo "<reponse>\n";

echo "<erreur>" . $error . "</erreur>\n";

echo "</reponse>\n";
?>


Autre moyen de détecter que le fichier EXCEL a été généré !

Le traitement peut être long et pendant ce temps vous pouvez passer d'une page à une autre dans votre site / application Web.

Vous pouvez ainsi perdre le "contexte" applicatif qui a lancé la génération du fichier EXCEL.

Comment savoir, quelque soit le contexte, que le fichier a été généré ?

Dans votre page "principale" PHP :

var interval;

function verifyIfTaskIsFinished(tag) {
interval = self.setInterval(function(){ taskWorkerIsFinished(tag); }, 20000);
}

Dans notre exemple nous fixons l'interval de "surveillance" à 20 secondes.

Nous vous conseillons d'estimer le temps nécessaire maximum pour générer votre fichier EXCEL.

Cette valeur pourra servir de base pour définir l'interval.

...

<?php
echo "<script>";
echo "verifyIfTaskIsFinished('" . $_SESSION["tag"] . "');";
echo "</script>";
?>

Le fait d'avoir "stocker" la valeur de la variable <tag> en session est bien pratique.

La fonction <taskWorkerIsFinished()> :

var docXMLOp = null;

function taskWorkerIsFinished(tag) {
var xhr = null;

if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else if (window.ActiveXObject) {
try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {
alert("Votre navigateur ne supporte pas les objets Microsoft.XMLHTTP...");
return;
}
}
} else { 
alert("Votre navigateur ne supporte pas les objets XMLHTTPRequest...");
return;
}

xhr.onreadystatechange = function() {
process_taskWorkerIsFinished(xhr);
};

xhr.open("GET",
"http://www.monsite.com//taskWorkerIsFinished.php?tag="
+ tag, true);

xhr.send(null);
}

function process_taskWorkerIsFinished(xhr) {
if (xhr.readyState == 4 && (xhr.status == 200)) {
docXMLOp = xhr.responseXML;

if (docXMLOp != null) {
var reponse = docXMLOp.getElementsByTagName("reponse");

if (reponse.item(0).firstChild.data == "ok") {
window.clearInterval(interval);
download_excel();
}
}
}
}

La page <taskWorkerIsFinished.php> :

<?php
header('Content-Type: text/xml; charset=ISO-8859-1');

header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past

echo "<?xml version=\"1.0\"?>\n";

$reponse = "";

$fichier = "mon_fichier.xlsx";

if (file_exists($fichier))
{
// récupérer la description du fichier

       ....

$current_tag = description;

if ($_GET["tag"] == $current_tag)
$reponse = "ok";
else
$reponse = "tag incorrect";
}
else
$reponse = "not found";

echo "<reponse>" . $reponse . "</reponse>\n";
?>

Aucun commentaire:

Enregistrer un commentaire