export html to excel/pdf with angularjs and servlet(spring mvc)

As we konw, the XHR request that angularjs uses cannot init a download.

iFrame version

One way to achieve is thru an invisible iframe and assign the url to the iframe for download.

The basic idea is to construct a iframe on the fly and append a html FORM in it and then submit the form to server in javascript. On the server side, return the stream with ‘Content-disposition’ header.

Angularjs code

just pass in fileType(here is pdf/xls) in your ng-click or from some other delegations

	$scope.exportReport = function (fileType) {
		var printIframe = angular.element("<iframe class='hidden'>");
		var formElement = angular.element("<form>");
		formElement.attr("action", "/scrc/rest/trace/export/" + fileType);
		formElement.attr("method", "post");
		var contentElement = angular.element("<input>").attr("type", "hidden").attr("name",
				"domContent").val(angular.element('.report-outer-wrapper').html());
		//build file name
		var fileName = $scope.reportData.mpid + '_' + $scope.reportData.periodDate.replace(' ', '_') + '_' + $scope.reportData.viewType;
		var fileNameElement = angular.element("<input>").attr("type", "hidden").attr("name",
				"fileName").val(fileName);
		formElement.append(contentElement);
		formElement.append(fileNameElement);
		printIframe.append(formElement);
		angular.element('body').append(printIframe);
		formElement.submit();
	};

Java code in Spring controller

The buildExportContent() is just to put the style and content into a html’s head and body and return a String.

    @RequestMapping(value = "/export/{fileType}", method = RequestMethod.POST)
    @ResponseBody
    public void handleExport(@PathVariable("fileType") String fileType, @RequestParam("domContent") String domContent,
        @RequestParam("fileName") String fileName, HttpServletResponse response) throws IOException, DocumentException
    {
        OutputStream out = response.getOutputStream();
        String finalFileName = fileName + "." + fileType;
        response.setHeader("Content-disposition", "attachment; filename=" + finalFileName);
        String styles = "";
        if ("pdf".equalsIgnoreCase(fileType))
        {
            String bootstrapStylePath = context.getRealPath("/css/bootstrap/bootstrap.min.css");
            styles = Files.toString(new File(bootstrapStylePath), StandardCharsets.UTF_8);
            //we need xhtml here.
            String exportContent = reportService.buildExportContent(domContent, styles).replaceAll("<br>", "<br/>");
            ITextRenderer renderer = new ITextRenderer();
            renderer.setDocumentFromString(exportContent);
            renderer.layout();
            renderer.createPDF(out);
        }
        else if ("xls".equalsIgnoreCase(fileType))
        {
            styles = " table{border:1px solid gray !important}; " + " td, th {border:1px solid gray !important}; ";
            //we need xhtml here.
            String exportContent = reportService.buildExportContent(domContent, styles).replaceAll("<br>", "<br/>");
            out.write(exportContent.getBytes(StandardCharsets.UTF_8));
        }
        out.close();

    }

Blob version

another way is to use Blob and generate the url with browser. however only IE>10 support this. WHat a sad story.

<a download="content.txt" ng-href="{{ url }}">download

in your controller:

var content = 'file content';
var blob = new Blob([ content ], { type : 'text/plain' });
$scope.url = (window.URL || window.webkitURL).createObjectURL( blob );

in order to enable the URL:

app = angular.module(...);
app.config(['$compileProvider',
    function ($compileProvider) {
        $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|file|blob):/);
});

Please note that

Each time you call createObjectURL(), a new object URL is created, even if you’ve already created one for the same object. Each of these must be released by calling URL.revokeObjectURL() when you no longer need them. Browsers will release these automatically when the document is unloaded; however, for optimal performance and memory usage, if there are safe times when you can explicitly unload them, you should do so.

Source: MDN

Another blob version

Pure html Form version

Bassically use a HTML form with ng-submit, on return add ‘Content-disposition’ header so that the page would not re-direct but just a file download.

code in Angular:

I did some customization to the form element so that the content of the report could be posted to the server side for the flying saucer to render and process to pdf file.

	$scope.printExcel = function (e) {
		var form = angular.element(e.target);
		form.attr("action", "/scrc/rest/trace/pdf");
		var contentElement = angular.element("<input>").attr("type", "hidden").attr("name",
				"printContent").val(angular.element('.report-outer-wrapper').html());
		form.append(contentElement);
		form.submit();
	};

HTML:

                       <form name="printPdfForm" method="POST" ng-submit="printPdf($event)">
                           <button type="submit" class="print-button">
                              <img src="/scrc/images/icon_pdf.gif" border="0" alt="download pdf"/>
                           </button>
                        </form>

Code in Spring controller.

The excel version is basically just return to the client the same content with a Content-disposition header which is for the origin server to suggest a default filename if the user requests that the content is saved to a file. It would be nice if we could do this in the client side with javascipt so that we can save a trip(Please notify me if you know how to do it.).

    @RequestMapping(value = "/pdf", method = RequestMethod.POST)
    @ResponseBody
    public void handlePdf(@RequestParam("printContent") String printContent, HttpServletResponse response) throws Exception
    {
        String fileName = "result.pdf";
        response.setHeader("Content-disposition", "attachment; filename=" + fileName);
        OutputStream out = response.getOutputStream();

        String content = printContent.replaceAll("<br>", "<br/>");
        ITextRenderer renderer = new ITextRenderer();
        renderer.setDocumentFromString(getPdfHtml(content));
        renderer.layout();

        renderer.createPDF(out);
        out.close();
    }

    private String getPdfHtml(String content) throws Exception
    {
        StringBuilder sb = new StringBuilder();
        sb.append("<html>");
        sb.append("<head><style language='text/css'>");
        //if performance is bad we could ignore this big css
        String rcStylePath = context.getRealPath("/css/bootstrap/bootstrap.min.css");
        sb.append(Files.toString(new File(rcStylePath), StandardCharsets.UTF_8));
        sb.append("</style></head>");
        sb.append("<body>");
        sb.append(content);

        sb.append("</body>");
        sb.append("</html>");

        return sb.toString();
    }

    @RequestMapping(value = "/excel", method = RequestMethod.POST)
    @ResponseBody
    public void handleExcel(@RequestParam("printContent") String printContent, HttpServletResponse response) throws IOException
    {
        String fileName = "result.xls";
        response.setHeader("Content-disposition", "attachment; filename=" + fileName);
        OutputStream out = response.getOutputStream();

        out.write(getExcelHtml(printContent).getBytes(StandardCharsets.UTF_8));
        out.close();
    }

    private String getExcelHtml(String content) throws IOException
    {
        StringBuilder sb = new StringBuilder();
        sb.append("<html>");
        sb.append("<head><style language='text/css'>");

        sb.append(" table{border:1px solid gray !important}; ");
        sb.append(" td, th {border:1px solid gray !important}; ");
        sb.append("</style></head>");
        sb.append("<body>");
        sb.append(content);

        sb.append("</body>");
        sb.append("</html>");

        return sb.toString();
    }

For some IE8 issue, look at my other POST

Advertisements

12 comments

  1. Pingback: A fun day with IE8 | Life in USA
  2. Neha · November 18, 2015

    Hi
    Need your help regarding export html(angularjs) to pdf in java.
    Please provide your contact email.

    Thanks,
    Neha

    • LEON · November 18, 2015

      What problem do you have?

  3. Neha · November 18, 2015

    Thanks for the prompt reply
    I am using second method for export to pdf
    1. I am getting error as end tag is not defined for . When the call is made to server
    2. Where is the variable context declared in the code.

    Thanks

    • Neha · November 18, 2015

      Correcting the line
      I am getting error as end tag is not defined for . When the call is made to server

      • Neha · November 18, 2015

        I am getting error as end tag is not defined for “”input . When the call is made to server

    • LEON · November 18, 2015

      I am not sure what you meant for the 1st question.
      For the 2nd one, the contenxt is just the SevletContent that we use to grab some css resource so that the pdf could look the same as what it is in the browser. Just inject it in your controller:
      @Autowired
      ServletContext context;

  4. Neha · November 22, 2015

    Thank you so much for the solution, I am able to generate with the code.
    Please forgive me for silly questions since I am not much aware about this API. One more thing I want to ask is regarding css.As I can apply one external css but How to apply mutliple external css in this code.

    • LEON · November 22, 2015

      If you want more style, just add more style tag to that generated html string. It is the same way you put css into your html. The only difference is typically you pass the html to browser but here you pass to the iText engine for rendering.
      PS: you do not want add too many styles to the html for pdf since the performance might not be that good.

  5. manish · December 2, 2015

    hi Leon,
    i am using iFrame version of the code , but when i submit the request it am 415 error unsupported media type.

    Please help

    Thanks,
    manish

    • LEON · December 3, 2015

      this is kind of vague… What Browser do you have? what Spring version do you use?

  6. Pingback: print all data in paginated table/grid | Life in USA

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s