Generate a designed PDF report from a web application - javascript

I want to generate a PDF report from a web application. The PDF should contain charts (pie, bar), tables, different fonts and colors.
The server-side of the application is Java, the client-side is AngularJS (and of course CSS3 and HTML).
Two main options:
The client side will pass some parameters to the server, and the server will generate the PDF report, using a Java package. Then the report will be sent back to the client as a downloaded file.
The client will generate the report, using a JS package that converts HTML and CSS to PDF.
In the Java world, I've found for example iText and JFreeChart, like here. The problem here is that the design of charts look bad in the example, and I don't know if it can be changed to be designed by the style-guide I have (a design that can be done easily with CSS).
In the JS world, I've found for example html2canvas and pdfMake, like here. The problem here is that I'm not sure the conversion from HTML to canvas and then to PDF will work good in an Angular application. And I'm not sure it converts well complicated DOM elements, like charts in svg or canvas elements.
Do you have any experience with these packages? Do you know other recommended packages for this task, client or server?

Want to share my solution... I chose a client-side solution.
I started with jsPDF, but had some problems. For example, it was hard to convert tables with the style I want.
I chose pdfMake for the PDF generation, html2canvas for taking screenshots of complicated designed components, and canvg for conversion of d3js charts (svg charts) to canvas (pdfMake can add canvas as image to the document).
I wrote a function that gets the CSS class of the HTML root of the part I want to convert to PDF (remember it's a single-page application), and also gets a meta data of which HTML nodes (again, by their CSS classes) should be added to the PDF (and what type is the node - table/text/image/svg).
Then, with DOM traversing, I walked through the elements I want to add to the PDF, and handled each one by its type. Part of the code (the traversing and the switch-case by type):
$(htmlRootSelector).contents().each(function processNodes(index, element) {
var classMeta = getMetaByClass(element.className);
if (!classMeta) {
$(element).contents().each(processNodes);
return;
}
var pdfObj = {};
pdfObj.width = classMeta.width || angular.undefined;
pdfObj.height = classMeta.height || angular.undefined;
pdfObj.style = classMeta.style || angular.undefined;
pdfObj.pageBreak = classMeta.pageBreak || angular.undefined;
switch (classMeta.type) {
case 'text':
pdfObj.text = element.innerText;
pdfDefinition.content.push(pdfObj);
break;
case 'table':
var tableArray = [];
var headerArray = [];
var headers = $(element).find('th');
var rows = $(element).find('tr');
$.each(headers, function (i, header) {
headerArray.push({text: header.innerHTML, style: classMeta.style + '-header'});
});
tableArray.push(headerArray);
$.each(rows, function (i, row) {
var rowArray = [];
var cells = $(row).find('td');
if (cells.length) {
$.each(cells, function (j, cell) {
rowArray.push(i % 2 === 1 ? {text: cell.innerText, style: classMeta.style + '-odd-row'} : cell.innerText);
});
tableArray.push(rowArray);
}
});
pdfObj.table = {
widths: $.map(headers, function (d, i) {
return i === 0 ? 80 : '*';
}),
body: tableArray
};
pdfDefinition.content.push(pdfObj);
break;
case 'image':
html2CanvasCount++;
htmlToCanvas(element, pdfObj);
pdfDefinition.content.push(pdfObj);
break;
case 'svg':
svgToCanvas(element, pdfObj);
pdfDefinition.content.push(pdfObj);
break;
default:
break;
}
$(element).contents().each(processNodes);
});
This is the solution in general.
Hope it will help someone.

Related

SAPUI5/ OPENUI5 javascript view HTML element render

I am new to SAPUI5/ OPENUI5 I have created a javascript view which basically imports several graphs and chart library and like a utility, it creates multiple charts and graphs and tables with icons
below is the code for the same
below is Js (sap ui JS view which creates graph)
jQuery.sap.require("some.graph.lib");
jQuery.sap.require("some.util.lib");
jQuery.sap.require("some.other.imports");
(function(){
sas.hc.ui.core.mvc.JSView.extend(" reports.dashboard.BaseDashboard.prototype.addCrossTab", {
metadata : {
properties : {
chartRequired:false,
tableDataRequired:false
},
},
rb: sap.ui.getCore().getLibraryResourceBundle("sas.csb.common"),
reports.dashboard.BaseDashboard.prototype.createGraphics = function (data) {
//This Function creates barchart, line charts and cross tab tables
return this.mainContentPane;
};
//Some other code to create and render graph
})();
I am creating another view in which I am importing above view and making ajax call so that I can create graphs and tables and send it as base64 encoded string to REST API to save into a PDF at the server side.
jQuery.sap.require("sas.cscommon.util.JSONUtil");
jQuery.sap.require({modName: "reports.dashboard.MeasureDashboard", type: "view"});
(function(){
var rb = sap.ui.getCore().getLibraryResourceBundle("sas.csb.backtesting");
//some other method
sas.csb.backtesting.ModelList.prototype.downloadModelReport = function(event) {
var id = "sample";
var oView = new reports.dashboard.MeasureDashboard(id, {
viewName: "reports.dashboard.MeasureDashboard",
chartrequired: true
} );
//Calling REST API to get data
oView.createGraphics(data);
}
How to convert this javascript sapui5 oView Object without displaying it to the user into the HTML element so that I can have the canvas and other graphics to convert into a base 64 encoded string and pass it to REST API to save at server end ?
I want to access the canvas html element from the view object without rendering the view on screen.
I know there is a renderer attached with sapui5 control and RenderManager in sapui5 but its not helping.
Sadly the methods for getting the HTML directly were deprecated. But you can create a temporary Element which is not in DOM.
var div = document.createElement("div");
var rm = sap.ui.getCore().createRenderManager();
rm.render(oView, div);
rm.destroy();
var sHTML = div.innerHTML;

Solution to map different excel files to db

I have to map a lot of different files with different structures to a db. There is a lot of different tables in those xlsx so I thought about schemeless noSQL approach, but I'm quite newbie in this field.
It should be a microservice with client interface for choosing tables/cells for parsing xlsx files. I do not have strict technology; it could be JAVA, GROOVY, Python or even a JavaScript engine.
Do you know any working solution for doing it?
Here is example xlsx (but I've got also other files, also in xls format): http://stat.gov.pl/download/gfx/portalinformacyjny/pl/defaultaktualnosci/5502/11/13/1/wyniki_finansowe_podmiotow_gospodarczych_1-6m_2015.xlsx
The work you have to do is called ETL (Extract Transform Load). You need to either find a good ETL software (here is a discussion about open source ETL) or to script your own solution in a language you are used with.
The advantage of a ready made GUI software is that you just have to drag and drop data but if you have some custom logic or semi structured data like in your xlsx example, you have limited support.
The advantage of writing your own script is you have all the freedom you need.
I have done some ETL work and I used successfully Groovy for writing my own solution with custom logic and so on, and in terms of GUI I used Altova Mapforce when I had to import some exotic file types.
If you decide to write your own solution you have to:
Convert all data to an easy to load format. In your case you have to convert each xls or xlsx tab to CSV with a naming convention.
Load your files in your chosen language for transforming
Do your logic to put data in a desirable format
Save it in a database (SQL or noSQL)
Maybe you should try Google Sheets to display excel and Google Apps Script (https://developers.google.com/apps-script/overview) to write custom add-on for parsing data to JSON.
Spreadsheet Service (https://developers.google.com/apps-script/reference/spreadsheet/) has plenty methods to access data in sheets.
Next you can send this JSON over API (https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app) or put directly into database (https://developers.google.com/apps-script/guides/jdbc).
Maybe isn't clean, but fast solution.
I had a project that done work almost the same as your problem but it seem easier as I had a fixed structure of xlsx files.
For xlsx parsing, I had experiment with Python and Openpyxl and had no struggle while working with them, they are simple, fast and easy to use.
For database, I recommend using MongoDB, you can deal with documents and collections in MongoDB just as simple as working with JSON objects or a set of JSON objects. PyMongo is the best and recommended way to work with MongoDB from Python I think.
The problem is you have different files with different structures. I cannot recommend anything deeper on this without viewing your data. But you should find the general structure of them or you have to figure out the way to classify them into common sets, each set will be parsed using appropriate algorithm.
Javascript solution, as xlsx2csv (you can make export anywhere):
var def = "1.xlsx";
if (WScript.Arguments.length>0) def = WScript.Arguments(0);
var col = [];
var objShell = new ActiveXObject( "Shell.Application" );
var fs = new ActiveXObject("Scripting.FileSystemObject");
function flush(){
WScript.Echo(col.join(';'));
}
function import_xlsx(file) {
var strZipFile = file; // '"1.xlsx" 'name of zip file
var outFolder = "."; // 'destination folder of unzipped files (must exist)
var pwd =WScript.ScriptFullName.replace( WScript.ScriptName, "");
var i,j,k;
var strXlsFile = strZipFile;
var strZipFile = strXlsFile.replace( ".xlsx",".zip").replace( ".XLSX",".zip");
fs.CopyFile (strXlsFile,strZipFile, true);
var objSource = objShell.NameSpace(pwd+strZipFile).Items();
var objTarget = objShell.NameSpace(pwd+outFolder);
for (i=0;i<objSource.Count;i++)
if (objSource.item(i).Name == "xl"){
if (fs.FolderExists("xl")) fs.DeleteFolder("xl");
objTarget.CopyHere(objSource.item(i), 256);
}
var xml = new ActiveXObject("Msxml2.DOMDocument.6.0");
xml.load("xl\\sharedStrings.xml");
var sel = xml.selectNodes("/*/*/*") ;
var vol = [];
for(i=0;i<sel.length;i++) vol.push(sel[i].text);
xml.load ("xl\\worksheets\\sheet1.xml");
ret = "";
var line = xml.selectNodes("/*/*/*");
var li, line2 = 0, line3=0, row;
for (li = 0; li< line.length; li++){
if (line[li].nodeName == "row")
for (row=0;row<line[li].childNodes.length;row++){
r = line[li].childNodes[row].selectSingleNode("#r").text;
line2 = eval(r.replace(r.substring(0,1),""));
if (line2 != line3) {
line3 = line2;
if (line3 != 0) {
//flush -------------------------- line3
flush();
for (i=0;i<col.length;i++) col[i]="";
}
}
try{
t = line[li].childNodes[row].selectSingleNode("#t").text;
//i = instr("ABCDEFGHIJKLMNOPQRSTUVWXYZ", left(r,1))
i = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ").indexOf(r.charAt(0));
while (i > col.length) col.push("");
if (t == "s"){
t = eval(line[li].childNodes[row].firstChild.text)
col[i] = vol[t];
} else col[i] = line[li].childNodes[row].firstChild.text;
} catch(e) {};
}
flush();
}
if (fs.FolderExists("xl")) fs.DeleteFolder("xl");
if (fs.FileExists(strZipFile)) fs.DeleteFile(strZipFile);
}
import_xlsx(def);

How can I download an image and display it in a NativeScript application?

I'm creating a NativeScript application that is supposed to work on both Android and iOS. I need to display some images that are in a S3 bucket.
As I want to show some progress indicator while the image is being downloaded I think I should download the image locally instead of just setting the source property of the Image component. What is the best thing to do?
After a little bit more research I found this sample https://github.com/telerik/nativescript-sample-cuteness/blob/master/nativescript-sample-cuteness/ and it has plenty of images downloaded from the Internet.
What I used is a module called image-cache that solves exactly this problem.
Here is what I used more precisely:
var imageSourceModule = require("image-source");
var imageCache = require("ui/image-cache");
var cache = new imageCache.Cache();
var defaultImageSource = imageSourceModule.fromFile("~/app/res/loading.gif");
var defaultNotFoundImageSource = imageSourceModule.fromFile("~/app/res/no-image.png");
cache.invalid = defaultNotFoundImageSource;
cache.placeholder = defaultImageSource;
cache.maxRequests = 5;
function displayImage(viewModel, url, propertyName) {
var source = cache.get(url);
propertyName = propertyName || "image";
if (source) {
viewModel.set(propertyName, source);
} else {
viewModel.set(propertyName, defaultImageSource);
cache.push({
key: url,
url: url,
completed: function (result, key) {
if (key === url) {
viewModel.set(propertyName, result);
}
}
});
}
}
If there is a better solution, I would be happy to learn about it.
There is plugin available called nativescript-web-image-cache. As per their npm page:
A minimalistic NativeScript plugin that wraps just the caching
functionality of SDWebImageCache library for iOS and Facebook Fresco
for android. Supports local Images.
You can also check if image is loading, e.g. Get the reference to the WebImage view by using angular template variable references and #ViewChild decorator and check the isLoading property (same as that of NativeScript Image isLoading property).

Can mxGraph export graphs as PDFs?

I am working on a project that uses mxGraph where I am required to export a high resolution output in PDF for a service process diagram. I've tried recreating the graph using JGraphX, the Java Swing client and exporting that to a PDF, but the result is not close to what the browser displays.
There's no PDF export in JavaScript on the client, does mxGraph have any explicit support for PDF generation from JavaScript?
I'll explain the case of a client initiated request, where the diagram is displayed on the browser when the request is made. This is the standard case, mxGraph transmits an XML representation of the graph using custom graphics primitives and these are received on the server and decoded either by the Java or .NET back-ends.
The reason for the need for the graph being displayed is there are certain text measurements that are hard to recreate outside of a browser environment.
On the client side you need to create the required immediate XML using, say, the diagrameditor.html example as a guide:
var exportImage = function(editor)
{
var graph = editor.graph;
var scale = graph.view.scale;
var bounds = graph.getGraphBounds();
// New image export
var xmlDoc = mxUtils.createXmlDocument();
var root = xmlDoc.createElement('output');
xmlDoc.appendChild(root);
// Renders graph. Offset will be multiplied with state's scale when painting state.
var xmlCanvas = new mxXmlCanvas2D(root);
xmlCanvas.translate(Math.floor(1 / scale - bounds.x), Math.floor(1 / scale - bounds.y));
xmlCanvas.scale(scale);
var imgExport = new mxImageExport();
imgExport.drawState(graph.getView().getState(graph.model.root), xmlCanvas);
// Puts request data together
var w = Math.ceil(bounds.width * scale + 2);
var h = Math.ceil(bounds.height * scale + 2);
var xml = mxUtils.getXml(root);
// Requests image if request is valid
if (w > 0 && h > 0)
{
var name = 'export.png';
var format = 'png';
var bg = '&bg=#FFFFFF';
new mxXmlRequest(editor.urlImage, 'filename=' + name + '&format=' + format +
bg + '&w=' + w + '&h=' + h + '&xml=' + encodeURIComponent(xml)).
simulate(document, '_blank');
}
};
Where editor.urlImage is the URL of the image generating servlet, in the case for a Java back-end.
On the server-side, in the case of Java, look at the java/examples/com/mxgraph/examples/web/ExportServlet.java. That looks at the "format" parameter passed up, and if 'pdf', the writePdf() method is invoked.
That method creates an PdfWriter and renders the graphics primitives to a Java Swing Graphics2D using the Java favoured part of mxGraph.
This example writes the PDF result directly to the outstream of the servlet repsonse in this line:
PdfWriter writer = PdfWriter.getInstance(document, response.getOutputStream());
You could map the output to any stream.
Note that you need to setup iText to map every font you need in the PDF. This isn't always ideal for a large number of fonts. It's worth testing a few cases of the export to see if the output is good enough for your requirements. We're currently researching the use of PhantomJS for the export. If the Java export isn't good enough, please post another question regarding using PhantomJS and I'll detail the process for that.
iText is provided as an example PDF library to use, it's easier since it's under an open source library. It's possibly not the best suited library, we didn't find it easy to work with for this specific scenario. You might also want to investigate other Java PDF generation libraries.
Also note that the .NET back-end only supports raster image generation in dotnet/aspnet/Export.ashx, there's no known open source PDF library to supply as an example there.
Full vector solution:
Set mxClient.NO_FO = true;
Export SVG to PDF by svg2pdf.js
Write all DIV's text to pdf by jsPDF
Example:
let pdf = new jsPDF('p', 'pt', 'a4', false, false);
mxClient.NO_FO = true;
let graph = Draw(drawdiv, false);
let svgEl = drawdiv.children[1];
//draw svg:
svg2pdf(svgEl, pdf, {
xOffset: pdfPageDefaultOffsetX,
yOffset: pdfOffsetY,
scale: divToPdfRatio
});
//draw text:
for (let child of drawdiv.children) {
if (child.tagName === 'DIV') {
let splitText = pdf.splitTextToSize(child.innerText, Math.ceil((childSizes.width) * divToPdfRatio));
pdf.text(pdfPageDefaultOffsetX + (child.offsetLeft * divToPdfRatio), textPositionTop, splitText, {
align: child.style.textAlign,
lineHeightFactor: 1,
});
}
}
pdf.save('Test.pdf');

Caching a background in Windows Metro App

I'm working on a WinJS Windows Metro application and on one of my pages I'm getting a URL to an image to display as a background. I can get that working just fine by using url(the URL of the image) and setting that as the style.backgroundImage.
I need to use that same image on a linked page, but that means I have to make another HTTP request, which I'm trying to avoid. I looked into alternatives and found LocalFolder as an option. The only issue is I don't know how to access the file and set it as a background.
Is that the right way to go about caching data to reduce webcalls?
Here's the code I'm using:
function saveBackground(url) {
localFolder.createFileAsync("background.jpg", Windows.Storage.CreationCollisionOption.replaceExisting).then(function (newFile) {
var uri = Windows.Foundation.Uri(url);
var downloader = new Windows.Networking.BackgroundTransfer.BackgroundDownloader();
var promise = downloader.createDownload(uri, newFile);
promise.startAsync().then(function () {
//set background here.
var wrapper = document.getElementById("wrapper").style;
localFolder.getFileAsync("background.jpg").then(function (image) {
console.log(image.path);
var path = image.path.split("");
var newLocation = [];
//This is just to make the backslashes work out for the url()
for (var i = 0; i < path.length; i++) {
if (path[i] != '\\') {
newLocation.push(path[i]);
} else {
newLocation.push('\\\\');
}
}
console.log(newLocation);
var newPath = newLocation.join("");
var target = "url(" + newPath + ")";
wrapper.backgroundImage = target;
console.log(wrapper.backgroundImage);
wrapper.backgroundSize = "cover";
});
});
});
}
It depends on which kind of image you want to transfer and how many of these. If there is only one image and not an heavy one (<5Mo approximately) I suggest you to use WinJS.xhr which allows you to download datas and more important it downloads the data as soon as its called.
The BackgroundTransfer should be used for big datas such as videos, musics, large images.
Concerning the caching of your image yes you can do it of course with the local folder (and you should do it this way).
You should take a look to this series of article made by David Catuhe which are really great
http://blogs.msdn.com/b/eternalcoding/archive/2012/06/15/how-to-cook-a-complete-windows-8-application-with-html5-css3-and-javascript-in-a-week-day-0.aspx
Hope this help.

Categories