AngularJS - File Download through AJAX - java

I created an angular js program for downloading a file from the server here follows the code
HTML Code
<a download="fullList.csv" ng-href="{{ fullListUrl }}" type="button" class="btn btn-success btn-xs exec-batch" ng-click="exportCSVBulk(batchExec)">
<span class="glyphicon glyphicon-ok"></span> EXPORT AS CSV
</a>
AngularJS Controller
$scope.exportCSVBulk=function(){
var page = "../importExportService/exportBulkCSV/"+batchExec.id;
$http.get(page).success(function(response) {
$scope.fullListUrl = 'data:text/csv;charset=utf-8,' + escape(response);
});
}
Here what i am doing is when a user click on the EXPORT AS CSV link the function exportCSVBulk fires and from that function the url value (fullListUrl) sets. But this is an ajax request, so when a user click on the link the url, the response time become little bit long which results the url will not redirected properly. Is it possible to fix this problem? or is there is any alternative way to fix this?

I have faced the similar issue for downloading files such as .pdf, .xls, .xlsx etc through Ajax.
Its a fact that we cant download files through Ajax, even though i came up with a solution which downloads files through Ajax like.
You can use jquery.fileDownload - A jQuery File Download Plugin for Ajax like, feature rich file downloads.
Demo Working
Server Side
I am using Spring at the server side
#RequestMapping(value = "exportXLS", method = RequestMethod.POST, produces = APP_JSON)
#ResponseBody
public void getCSV(final HttpServletResponse response, #RequestParam(value = "empId", required = true) final String empId) throws IOException, Exception
{
final byte[] csv = ExportXLSUtil.getFileBytes(empId); // get the file bytes
final OutputStream output = getOutputStream(response);
response.setHeader("Content-Disposition", "attachment; filename=documents_" + new DateTime() + ".xls");
response.setContentType(CONTENT_TYPE);
response.setContentLength(csv.length);
write(output, csv);
}
Client Side
At the client side, I am using AngularJS
$downloadXLS = function(id)
{
$.fileDownload('/user/exportXLS',
{
httpMethod : "POST",
data : {
empId : id
}
}).done(function(e, response)
{
// success
}).fail(function(e, response)
{
// failure
});
}
Download Link - jquery.fileDownload.js

I created a more angular way solution. The server has to provide content-type and content-disposition if you want to sync with server info, although you could add type and download properties manually.
vm.export = function () {
//PopUps.showLoading()
$http.get(Url).then(function (result) {
//PopUps.hideLoading()
var headers = result.headers()
var blob = new Blob([result.data], { type: headers['content-type'] })
var windowUrl = (window.URL || window.webkitURL)
var downloadUrl = windowUrl.createObjectURL(blob)
var anchor = document.createElement("a")
anchor.href = downloadUrl
var fileNamePattern = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
anchor.download = fileNamePattern.exec(headers['content-disposition'])[1]
document.body.appendChild(anchor)
anchor.click()
windowUrl.revokeObjectURL(blob)
})
}

Related

Spring RestTemplate upload binary file

Image
I want to write a client code to consume an API. The API is expecting a text file. When I select the binary file option in the postman tool and select any text file from my local it worked. how to implement this in spring ?. I have tried MULTIPART_FORM_DATA but no luck.
If You mean file
#RestController
public class FileContentController {
#RequestMapping(value="/up", method = RequestMethod.POST)
public ResponseEntity<?> upload(#RequestParam("file") MultipartFile file)
throws IOException {
String contentType=file.getContentType());
InputStream i=file.getInputStream();
return new ResponseEntity<>(HttpStatus.OK);
}
return null;
}
also spring boot has multi part confs, you should enable it and set size and tempdir
,In Earlier version spring boot need to add:
spring.servlet.multipart.max-file-size=128KB
spring.servlet.multipart.max-request-size=128KB
spring.servlet.multipart.enabled=true
spring.servlet.multipart.location=${java.io.tmpdir}
However in your client code you should not set content-type application/json in your header post request
simple fetch should be such
const input = document.getElementById('uploadInput');
const data = new FormData();
data.append('file', input.files[0]);
var resp = await fetch('upload/', {
method: 'POST',
body: data
});
if (!resp.ok) {
throw new Error(`HTTP error! status: ${resp.status}`);
}
if (resp.ok) {
await this.images();
}

some binary data is displayed on the browser when trying to open a PDF file from URL

I am working on java application. I want to open a pdf file in the webpage and should open in both IE and chrome.I have investigated a lot and tried using JSoup API.Below is the complete flow of html to java controller to get the pdf file and load on the web page. Issue is when the URL i'm pointing is an PDF, it is displaying some binary format on the UI. Other than PDF if i'm pointing to pptx or xslx with the same code shown in the java controller below, it is opening that files. Any advice how to open a PDF files from the URL in a browser using java and angularjs?
html code:
<div class="tab-content" ng-controller="myPDFController" style="max-height: 600px;">
<div ng-bind-html="trustedHtml" style="width: 100%; height: 600px;"></div>
</div>
js code:
app.controller('myPDFController', function ($scope, MyService) {
MyService.getPDFContentFromURL().then(
function (response) {
$scope.content = response;
$scope.trustedHtml = $sce.trustAsHtml($scope.content);
},
function (errResponse) {
});
});
//service call
myServiceFactory.getPDFContentFromURL = function(){
var deferred = $q.defer();
var repUrl = appURL+'/pdfFiles'+'/getFileFromURL.form';
$http.get(repUrl).then(
function (response) {
deferred.resolve(response.data);
},
function(errResponse){
}
);
return deferred.promise;
}
Spring controller:
#RequestMapping(value = "/getFileFromURL", method = RequestMethod.GET, produces="text/plain")
public #ResponseBody String getHtmlStringContent() throws Exception {
//Document doc = Jsoup.connect("https://abc.xyz.com/chks/content/myPowerPPT.pptx").ignoreContentType(true).get(); //working
Document doc = Jsoup.connect("https://abc.xyz.com/chks/content/myFirst.pdf").ignoreContentType(true).get(); //opening the pdf with binary data on the UI
String htmlString = doc.toString();
return htmlString;
}
First of all, you need to set the responsetype to arraybuffer. This is required if you want to create a blob of your data.So your code will look like this:
$http.get('/postUrlHere',{myParams}, {responseType:'arraybuffer'})
.success(function (response) {
var file = new Blob([response], {type: 'application/pdf'});
var fileURL = URL.createObjectURL(file);
});
The next part is, you need to use the $sce service to make angular trust your url. This can be done in this way:
$scope.content = $sce.trustAsResourceUrl(fileURL);
Do not forget to inject the $sce service.
If this is all done you can now embed your pdf:
<embed ng-src="{{content}}" style="width:200px;height:200px;"></embed>

Render thumbnail in Angular4

I am trying to render an image which I got from a Java service as InputStream, re-send it through NodeJS Express server and finally render it in Angular4
Here's what I do:
Java Jersey service:
#GET
#Path("thumbnail")
#ApiOperation(
value = "Gets document preview",
notes = "Gets document preview"
)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Preview of the document")
})
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces("image/png")
public Response getDocThumbnail(
#ApiParam(value = "Entity UUID", required = true) #FormDataParam("uuid") String uuid
) throws RepositoryException, UnknowException, WebserviceException, PathNotFoundException, DatabaseException, AutomationException, AccessDeniedException, ConversionException, IOException {
RawDocument rawDocument = docCtrl.getDocThumbnail(uuid);
return Response
.ok(rawDocument.getInputStream(), "image/png")
.header("Content-Disposition", "attachment; filename=\" " + rawDocument.getName() + "\"")
.build();
}
the controller looks like:
public RawDocument getDocThumbnail(String uuid) throws IOException, AccessDeniedException, PathNotFoundException, WebserviceException, RepositoryException, DatabaseException, ConversionException, AutomationException, UnknowException {
return new RawDocument(
okmWebSrv.getOkmService().getThumbnail(uuid, ThumbnailType.THUMBNAIL_LIGHTBOX),
"whatever"
);
}
Basically it's call to OpenKM SDK to retreive document's thumbnail
This Java endpoint is called from NodeJS Express 4.15 that is pre-processing some requests for this Java backend.
Here's what I do:
...compose request options...
const vedica_res = await rp(options);
let buffered = new Buffer(vedica_res, 'binary');
res.writeHead(200, {
'Content-Type': 'image/png',
'Content-disposition': 'attachment;filename=' + 'thumb.png',
'Content-Length': buffered.length
});
return res.end(buffered, 'binary');
Finally with Angular4 being the initiator of this roundtrip I am trying to render the image like so:
this.rest.send('http://localhost:4200/vedica/api/document/thumbnail', RequestMethod.Get,
{uuid: '19516ea1-657e-4b21-8564-0cb87f29b064'}, true).subscribe(img => {
// this.preview = img
var urlCreator = window.URL;
var url = urlCreator.createObjectURL(img);
this.thumb.nativeElement.src = url;
})
The 'img' received is a Blob {size: 81515, type: "image/png"}. Console shows no errors but renders no image in the <img #thumb/> tag. But I can see that it sets the src=blob:http%3A//localhost%3A3000/4cf847d5-5af3-4c5a-acbc-0201e60efdb7 for it. Image just has a broken image icon.
When I try to read a cached response in a new tab, its accessible but renders nothing again.
Can you point out what I'm doing wrong? Have tried a lot, but no luck.
I think the problem is not the stream is closed early, the problem I think will be in the way is downloaded, take a look here:
https://docs.openkm.com/kcenter/view/sdk4j-1.1/document-samples.html#getContent
From the server side ( inde middle between OpenKM and your user interface ) the problem usualy is:
//response.setContentLength(is.available()); // Cause a bug, because at this point InputStream still has not its real size.
And you should use
response.setContentLength(new Long(doc.getActualVersion().getSize()).intValue());
resolved this by replacing request-promise with bare request package for making this request to the java BE and piping reply right into the wrapping response of the angular FE:
let reply = request(options);
reply.pipe(res);

Multipart request giving issues when received in rest service

I am trying to upload a file using multipart request through angularjs and receive the content on my Rest service. I am putting up this question here after trying several helps for last 4 days and tiring myself to utmost level. I would appreciate if you can fine tune my approach or suggest another approach (I am open to any suggestions which may work as I am out of ideas now).
Just a pointer, I have tried writing a servlet to read the multipart request sent through angularjs and I got the parts correctly. But I am still putting the angular code here for your reference as I am not better on both angular and rest.
Following is the html extract for file upload:
<div>
<input type="file" data-file-upload multiple/>
<ul>
<li data-ng-repeat="file in files">{{file.name}}</li>
</ul>
</div>
Followingis the angularjs directive code extract:
.directive('fileUpload', function () {
return {
scope: true, //create a new scope
link: function (scope, el, attrs) {
el.bind('change', function (event) {
var files = event.target.files;
//iterate files since 'multiple' may be specified on the element
for (var i = 0;i<files.length;i++) {
//emit event upward
scope.$emit("fileSelected", { file: files[i] });
}
});
}
};
})
Following is the angularjs controller code extract
//a simple model to bind to and send to the server
$scope.model = {
name: "test",
comments: "TC"
};
//an array of files selected
$scope.files = [];
//listen for the file selected event
$scope.$on("fileSelected", function (event, args) {
$scope.$apply(function () {
//add the file object to the scope's files collection
$scope.files.push(args.file);
});
});
//the save method
$scope.save = function() {
$http({
method: 'POST',
url: "/services/testApp/settings/api/vsp/save",
headers: { 'Content-Type': undefined },
transformRequest: function (data) {
var formData = new FormData();
formData.append("model", angular.toJson(data.model));
for (var i = 0; i < data.files.length; i++) {
formData.append("file" , data.files[i]);
}
return formData;
},
data: { model: $scope.model, files: $scope.files }
}).
success(function (data, status, headers, config) {
alert("success!");
}).
error(function (data, status, headers, config) {
alert("failed!");
});
};
And here is my rest service code:
#Path("/vsp")
public class SampleService{
#Path("/save")
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
public void saveProfile(#FormParam("model") String theXml,
#FormParam("file") List<File> files) throws ServletException, IOException {
final String response = "theXML: " + theXml + " and " + files.size() + " file(s) received";
System.out.println(response);
}
}
And here is the response:
theXML: {"name":"test","comments":"TC"} and 1 file(s) received
The problem is that the content of the file is coming in path and I am not able to get the input stream to read the file. I even tried using
new ByteArrayInputStream(files.get(0).getPath().getBytes())
If the content is text (like txt or csv) it works, but if the content is any other file like xls etc, the retrieved content is corrupt and unusable. Also tried using Jeresy api, but with same result. Am I missing anything obvious? Any help is appreciated.
I came across a few links, but none worked for me. So finally, I had to write a servlet to read the multipart request and added the files and the request parameters as request attributes. Once the request attributes are set, I forwarded the request to my Rest service.
Just for the record, if the multipart request is read once to extract the parts, the request will not have the parts in the forwarded servlet. So I had to set them as request attributes before forwarding.
Here is the servlet code:
public class UploadServlet extends HttpServlet {
#Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// process only if its multipart content
RequestContext reqContext = new ServletRequestContext(request);
if (ServletFileUpload.isMultipartContent(reqContext)) {
try {
List<FileItem> multiparts = new ServletFileUpload(
new DiskFileItemFactory()).parseRequest(request);
ArrayList<FileItem> fileList = new ArrayList<FileItem>();
request.setAttribute("files", fileList);
for (FileItem item : multiparts) {
if (!item.isFormField()) {
fileList.add(item);
} else {
request.setAttribute(item.getFieldName(),
item.getString());
}
}
request.setAttribute("message", "success");
} catch (Exception ex) {
request.setAttribute("message", "fail"
+ ex);
}
} else {
request.setAttribute("message",
"notMultipart");
}
System.out.println(request.getRequestURI().substring(request.getRequestURI().indexOf("upload")+6));
String forwardUri = "/api" + request.getRequestURI().substring(request.getRequestURI().indexOf("upload")+6);
request.getRequestDispatcher(forwardUri)
.forward(request, response);
}
}
Any request starting with /upload/<rest api path> will be received by the servlet and once the attributes are set, they will be forwarded to /api/<rest api path>.
In the rest api, I used the following code to retrieve the parameters.
#Path("/vsp")
public class SampleService{
#Path("/save")
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
public void saveProfile(#Context HttpServletRequest request,
#Context HttpServletResponse response) throws Exception {
// getting the uploaded files
ArrayList<FileItem> items = (ArrayList<FileItem>)request.getAttribute("files");
FileItem item = items.get(0);
String name = new File(item.getName()).getName();
item.write( new File("C:" + File.separator + name));
// getting the data
String modelString = (String)request.getAttribute("model");
// Getting JSON from model string
JSONObject obj = JSONObject.parse(modelString);
String responseString = "model.name: " + obj.get("name") + " and " + items.size() + " file(s) received";
System.out.println(responseString);
}
}

AJAX: Send file to Servlet and open response in new window

in my application, users can edit an ODF file via WebODF (http://webodf.org/). On save, i want to send the edited file to a servlet, have it convert to PDF via ODFDOM (http://code.google.com/p/xdocreport/wiki/ODFDOMConverterPDFViaIText) and open in a new window.
Currently i am trying to do this via AJAX. Everything works fine up to the point where i try to open the received PDF file.
My Javascript:
function showPDF(pServletUrl)
{
var successCallback = function(pData)
{
var mimetype = "application/vnd.oasis.opendocument.text";
var blob = new Blob([pData.buffer], {type: mimetype});
var formData = new FormData();
formData.append("file", blob, "test.odt");
jQuery.ajax({
type: "POST",
url: pServletUrl,
async: false,
data: formData,
processData: false,
contentType: false,
success: function(pSuccessData)
{
window.open(pSuccessData);
},
error: function(pErrorData)
{
console.log(pErrorData);
}
});
}
var errorCallback = function(data)
{
console.log(error);
}
_canvas.odfContainer().createByteArray(successCallback, errorCallback);
}
My servlet:
public void handleRequest(HttpServletRequest pRequest, HttpServletResponse pResponse) throws ServletException, IOException
{
BufferedInputStream tBufferedInput = null;
BufferedOutputStream tBufferedOutput = null;
try
{
List<FileItem> tItems = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(pRequest);
for (FileItem tItem : tItems)
{
if (!tItem.isFormField())
{
String tFieldname = tItem.getFieldName();
String tFilename = FilenameUtils.getName(tItem.getName());
InputStream tFilecontent = tItem.getInputStream();
if("file".equals(tFieldname))
{
tBufferedInput = new BufferedInputStream(tFilecontent);
pResponse.reset();
pResponse.setHeader("Content-Type", "application/pdf");
pResponse.setHeader("Content-Disposition", "inline; filename=\"" + "test.pdf" + "\"");
tBufferedOutput = new BufferedOutputStream(pResponse.getOutputStream(), 10240);
this.getOdtAsPdf(tBufferedInput, tBufferedOutput);
tBufferedOutput.flush();
}
}
}
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
try
{
tBufferedInput.close();
tBufferedOutput.close();
}
catch(Exception e)
{
}
}
}
private void getOdtAsPdf(InputStream pInputStream, OutputStream pOutputStream) throws Exception
{
OdfDocument tOdfDocument = OdfDocument.loadDocument(pInputStream);
PdfOptions tPdfOptions = PdfOptions.create();
PdfConverter.getInstance().convert(tOdfDocument, pOutputStream, tPdfOptions);
}
It seems like Javascript wants to parse the recieved PDF file as a URL and (obviously) fails doing so. Is there a way to just open the file in a new window or do i have to find another way to do this?
You can't open the file using Ajax. This is a security restriction fo javascript. You have a few workarounds:
use a plugin which gives a Ajax type experience but opens a file in a new window.more details here
have a form which is submitted to a new window. <form target=_blank /> this will cause a new window to open thus not changing the contents of your current page.
Another option (not so neat) is to store the file in session and in the response of your AJAX, pass the id. Then using Javascript make a call using window.open('downloadurl?id') which will send the response of your PDF file.
You can make use an embed tag to display your blob after you make an ajax call.
Use createObjectUrl method to get url from blob and then display your pdf.

Categories

Resources