253 votes

JavaScript/jquery pour télécharger un fichier via POST avec des données JSON

J'ai un jquery-basées sur une seule page webapp. Il communique avec un service web RESTful via des appels AJAX.

Je suis en train de réaliser ce qui suit:

  1. Soumettre un POST qui contient les données JSON à un RESTE de l'url.
  2. Si la demande spécifie une réponse JSON, puis JSON est retourné.
  3. Si la demande spécifie un fichier PDF/XLS/etc réponse, puis télécharger le binaire est retourné.

J'ai 1 & 2 de travail maintenant, et le client jquery application affiche les données renvoyées dans la page web en créant des éléments du DOM basés sur les données JSON. J'ai aussi #3 de la web-service point de vue, le sens qu'il permettra de créer et de retour d'un fichier binaire, si, étant donné la bonne JSON paramètres. Mais je ne suis pas sûr de la meilleure façon de traiter avec le n ° 3 dans le client du code javascript.

Est-il possible d'obtenir un fichier téléchargeable de retour d'un appel ajax comme ça? Comment puis-je obtenir le navigateur pour télécharger et enregistrer le fichier?

$.ajax({
    type: "POST",
    url: "/services/test",
    contentType: "application/json",
    data: JSON.stringify({category: 42, sort: 3, type: "pdf"}),
    dataType: "json",
    success: function(json, status){
        if (status != "success") {
            log("Error loading data");
            return;
        }
        log("Data loaded!");
    },
    error: function(result, status, err) {
        log("Error loading data");
        return;
    }
});

Le serveur répond avec les en-têtes suivants:

Content-Disposition:attachment; filename=export-1282022272283.pdf
Content-Length:5120
Content-Type:application/pdf
Server:Jetty(6.1.11)

Une autre idée est de générer le fichier PDF et de le stocker sur le serveur et retour JSON qui comprend une URL vers le fichier. Ensuite, émettre un autre appel en ajax gestionnaire de succès à faire quelque chose comme ce qui suit:

success: function(json,status) {
    window.location.href = json.url;
}

Mais cela signifie que j'aurais besoin de faire plus d'un appel vers le serveur, et mon serveur serait nécessaire de construire fichiers à télécharger, de les stocker quelque part, puis périodiquement au nettoyage de la zone de stockage.

Il doit y avoir un moyen plus simple de faire cela. Des idées?


EDIT: Après avoir examiné la documentation de $.ajax, je vois que la réponse de type de données ne peut être que celui de l' xml, html, script, json, jsonp, text, donc je suppose que il n'y a aucun moyen de télécharger directement un fichier à l'aide d'une requête ajax, à moins que je incorporer le fichier binaire en utilisant les Données de schéma d'URI, comme suggéré dans le @VinayC réponse (qui n'est pas quelque chose que je veux faire).

Donc je suppose que mes options sont les suivantes:

  1. Ne pas utiliser ajax et au lieu de soumettre un formulaire post et intégrer mes données JSON dans le formulaire de valeurs. Serait probablement besoin de retoucher avec des iframes.

  2. Ne pas utiliser ajax et au lieu de convertir mes données JSON dans une chaîne de requête pour construire une requête GET standard et de l'ensemble de la fenêtre.emplacement.href cette adresse URL. Pouvez avoir besoin d'utiliser de l'événement.preventDefault() dans mon gestionnaire de clic pour garder navigateur à partir de la modification de l'URL de l'application.

  3. Utiliser mon autre idée ci-dessus, mais amélioré avec des suggestions de l' @naikus réponse. Soumettre une requête AJAX avec un certain nombre de paramètres qui permet web-service, savoir ce qui est appelé via un appel ajax. Si le service web est appelée à partir d'un appel ajax, il suffit de retourner JSON avec une URL de la ressource généré. Si la ressource est appelée directement, puis retourner le fichier binaire.

Plus j'y pense, plus j'aime la dernière option. De cette façon je peux obtenir des informations à propos de la demande (temps de production, la taille des fichiers, des messages d'erreur, etc.) et je peux agir sur cette information avant de commencer le téléchargement. L'inconvénient, c'est extra de gestion de fichiers sur le serveur.

Toutes les autres façons de l'accomplir? Les avantages/inconvénients de ces méthodes, je devrais être au courant?

170voto

SamStephens Points 2913

La solution de letronje ne fonctionne que pour des pages très simples. document.body.innerHTML += prend le texte HTML du corps, ajoute le code HTML iframe et définit le innerHTML de la page sur cette chaîne. Cela effacera toutes les liaisons d'événements de votre page, entre autres choses. Créez un élément et utilisez appendChild place.

 $.post('/create_binary_file.php', postData, function(retData) {
  var iframe = document.createElement("iframe");
  iframe.setAttribute("src", retData.url);
  iframe.setAttribute("style", "display: none");
  document.body.appendChild(iframe);
}); 
 

Ou en utilisant jQuery

 $.post('/create_binary_file.php', postData, function(retData) {
  $("body").append("<iframe src='" + retData.url+ "' style='display: none;' ></iframe>");
}); 
 

61voto

letronje Points 3228

Une fois que le fichier binaire a été généré sur le serveur, en supposant qu'il existe une URL accessible publiquement au fichier généré, un iframe masqué peut être utilisé pour effectuer le travail sans utiliser de redirection.

Voici comment cela peut être fait:

 $.post('/create_binary_file.php', postData, function(retData){
  var binUrl = retData.url;
  document.body.innerHTML += "<iframe src='" + binUrl + "' style='display: none;' ></iframe>"
}); 
 

52voto

JoshBerke Points 34238

J'ai été jouer avec une autre option qui utilise des gouttes. J'ai réussi à obtenir de télécharger des documents de texte, et j'ai téléchargé les fichiers PDF (Cependant ils sont endommagés).

À l'aide de la goutte de l'API de votre pouvez effectuer les opérations suivantes:

$.post(/*...*/,function (result)
{
    var blob=new Blob([result]);
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download="myFileName.txt";
    link.click();

});

C'est IE 10+, Chrome 8+, FF 4+. Voir https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL

Il ne téléchargez le fichier dans Chrome, Firefox et Opera. Il utilise un téléchargement de l'attribut de la balise d'ancrage pour forcer le navigateur à télécharger.

17voto

amersk Points 61

Je sais que ce genre de vieux, mais je pense que je suis venu avec une solution plus élégante. J'ai eu exactement le même problème. Le problème que j'ai avec les solutions suggérées que tous les fichiers sont sauvegardés sur le serveur, mais je ne voulais pas enregistrer les fichiers sur le serveur, car il introduit d'autres problèmes (sécurité: le fichier peut alors être consulté par les utilisateurs non authentifiés, de nettoyage: quand et comment voulez-vous vous débarrasser des fichiers). Et comme vous, mes données complexes, imbriquées objets JSON qui serait difficile à mettre en forme.

Ce que j'ai fait a été de créer deux fonctions de serveur. La première a validé les données. Si il y a une erreur, il serait renvoyé. Si ce n'était pas une erreur, j'ai rentré tous les paramètres sérialisé/encodée en base64 de la chaîne. Ensuite, sur le client, j'ai un formulaire qui n'a qu'un seul cachés d'entrée et de postes de à une deuxième fonction de serveur. J'ai mis le caché d'entrée de la chaîne base64 et soumettre le format. La deuxième fonction de serveur décode/désérialise les paramètres et génère le fichier. La forme peut soumettre à une nouvelle fenêtre ou un iframe sur la page et le fichier va s'ouvrir.

Il y a un peu plus de travail, et peut-être un peu plus de traitement, mais dans l'ensemble, je me sentais beaucoup mieux avec cette solution.

Le Code est en C#/MVC

    public JsonResult Validate(int reportId, string format, ReportParamModel[] parameters)
    {
        // TODO: do validation

        if (valid)
        {
            GenerateParams generateParams = new GenerateParams(reportId, format, parameters);

            string data = new EntityBase64Converter<GenerateParams>().ToBase64(generateParams);

            return Json(new { State = "Success", Data = data });
        }

        return Json(new { State = "Error", Data = "Error message" });
    }

    public ActionResult Generate(string data)
    {
        GenerateParams generateParams = new EntityBase64Converter<GenerateParams>().ToEntity(data);

        // TODO: Generate file

        return File(bytes, mimeType);
    }

sur le client

    function generate(reportId, format, parameters)
    {
        var data = {
            reportId: reportId,
            format: format,
            params: params
        };

        $.ajax(
        {
            url: "/Validate",
            type: 'POST',
            data: JSON.stringify(data),
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            success: generateComplete
        });
    }

    function generateComplete(result)
    {
        if (result.State == "Success")
        {
            // this could/should already be set in the HTML
            formGenerate.action = "/Generate";
            formGenerate.target = iframeFile;

            hidData = result.Data;
            formGenerate.submit();
        }
        else
            // TODO: display error messages
    }

8voto

VinayC Points 23947

En bref, il n'y a pas de moyen plus simple. Vous avez besoin de faire une autre demande de serveur pour afficher les fichiers PDF. Al, si, il y a peu de solutions de rechange, mais ils ne sont pas parfaits et ne fonctionnera pas sur tous les navigateurs:

  1. Regardez les données de schéma d'URI. Si des données binaires est petit, alors vous pouvez peut-être utiliser javascript pour ouvrir la fenêtre de transfert de données dans l'URI.
  2. Windows/IE seule solution serait d'avoir .NET de contrôle ou FileSystemObject pour enregistrer les données sur le système de fichiers local et de l'ouvrir à partir de là.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X