My my requirement is to either display(new tab)/download/embed a PDF in my angular js app on form submit/post.
I do not want the server to return a unique identifier of the generated PDF and than use $window service to open a new window with it's url pointing to a server-side endpoint which returns PDf based on unique identifier. Because I need to generate the pdf on the fly (no storing in file system).
Similar question to this one AngularJS: Display blob (.pdf) in an angular app But it is not working for me.
My controller
angular.module('EvaluationResultsModule').controller('CA_EvaluationResultsCtrl',
[ '$scope', 'EvaluationResultsService', '$sce', function($scope, EvaluationResultsService, $sce) {
$scope.showPDF = function() {
$scope.result = CA_EvaluationResultsService.getEvalutaionResultPDF($scope.evaluationResults);
$scope.result.$promise.then(function(data) {
var file = new Blob([data], {
type : 'application/pdf'
});
var fileURL = URL.createObjectURL(file);
$scope.pdfContent = $sce.trustAsResourceUrl(fileURL);
});
}
} ]);
My Service
angular.module('EvaluationResultsModule').factory('EvaluationResultsService', function($resource) {
return $resource('./api/ca/evaluationResults/:dest', {}, {
getEvalutaionResultPDF : {
method : 'GET',
params : {
dest : "getPDF"
},
responseType : 'arraybuffer',
}
});
});
Rest Controller Method
#RequestMapping(value = "/getPDF", method = RequestMethod.GET)
public byte[] getEvalutaionResultPDF() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Generate PDF using Jasper
Map<String, Object> model = new HashMap<String, Object>();
List<User> usersList = null; //populated from Service layer;
JRBeanCollectionDataSource beanColDataSource = new JRBeanCollectionDataSource(usersList);
JasperPrint jasperPrint = jasperPrint = JasperFillManager.fillReport(this.getClass().getClassLoader().getResourceAsStream("A4.jasper"), model, beanColDataSource);
JasperExportManager.exportReportToPdfStream(jasperPrint, baos);
return baos.toByteArray();
}
My response logged in console
response: Object {data: ArrayBuffer, status: 200, headers: function, config: Object, statusText: "OK"}config: Objectdata: ArrayBufferbyteLength: (...)__proto__: ArrayBufferbyteLength: [Exception: TypeError: Method ArrayBuffer.prototype.byteLength called on incompatible receiver #<ArrayBuffer>]get byteLength: function byteLength() { [native code] }constructor: function ArrayBuffer() { [native code] }slice: function slice() { [native code] }__proto__: Objectheaders: function (name) {resource: Resourcestatus: 200statusText: "OK"__proto__: Object
I use this code and it works for me:
REST Controller:
#RequestMapping(value = "/api/reports/pdf", method = RequestMethod.GET)
#Timed
public #ResponseBody byte[] getOpenedEventsInPdf(HttpServletResponse response) {
response.setHeader("Content-Disposition", "inline; filename=file.pdf");
response.setContentType("application/pdf");
// get file in bytearray from my custom service in backend
byte[] file = jasperReportsService.getOpenedEventsReport(ReportFormat.PDF);
return file;
}
JS/Angular Controller;
$scope.getPdf = function(){
$http.get('/api/reports/pdf', {responseType: 'arraybuffer'})
.success(function (data) {
var file = new Blob([data], {type: 'application/pdf'});
var fileURL = URL.createObjectURL(file);
window.open(fileURL);
});
}
HTML fragment:
<a ng-click="getPdf()">Show PDF</a>
For "Browser Compatibility" given code is working properly :
Get the byte array data from beck-end controller side and generate
file on js controller side :
Beck-end controller
#RequestMapping(value = "/getPDF", method = RequestMethod.GET)
public byte[] getEvalutaionResultPDF() {
byte[] data = //get byte Array from back-end service
return data;
}
JS Service
var getPdfFile = function(){
return $http.get("getPDF", {responseType: 'arraybuffer'});
};
JS controller
$scope.pdfFile = function() {
service.getPdfFile().then(function (data) {
//for browser compatibility
var ieEDGE = navigator.userAgent.match(/Edge/g);
var ie = navigator.userAgent.match(/.NET/g); // IE 11+
var oldIE = navigator.userAgent.match(/MSIE/g);
var name = "file";
var blob = new window.Blob([data.data], { type: 'application/pdf' });
if (ie || oldIE || ieEDGE) {
var fileName = name+'.pdf';
window.navigator.msSaveBlob(blob, fileName);
}
else {
var file = new Blob([ data.data ], {
type : 'application/pdf'
});
var fileURL = URL.createObjectURL(file);
var a = document.createElement('a');
a.href = fileURL;
a.target = '_blank';
a.download = name+'.pdf';
document.body.appendChild(a);
a.click();
}
},
function(error) {
//error
});
};
In the following link, you should find the answer :
AngularJS Display PDF (byte[]) received from Spring(#RestController) + jasper report
In this link you find how display pdf in a iframe using angularjs.
The pdf is received from a API rest using spring and jasper report.
Related
I am trying to send an object and array in the same time to an API, but i got an error
This is the object (from input boxes)
var vacation = {
Vac_Main_Key: $scope.vackey, Vac_Main_Code: $scope.code, Gender_Type: $scope.gen, CareeService_Flag: $scope.career, Vac_Duration: $scope.vduration,
Duration_Flag: $scope.vflag}
This is the array (from multiple check boxes)
$scope.selectedcontract = function (con)
{
if (con.details == true) {
$scope.vacationcontracts.push({ VacMKey: $scope.vackey, WType: con.sys_key });
console.log($scope.vacationcontracts);
}
else if (con.details == false) {
$scope.vacationcontracts.splice(con, 1);
}
}
The save button
var promisePost = vacationsetupSrv.save(vacation, $scope.vacationcontracts);
promisePost.then(function () {}
The angularjs service:
var vacationsetup = angular.module("vacationsetup", [])
.service("vacationsetupSrv", function ($http) {
var urlBase = "/VacationSetupAPI/api";
this.save = function (url, vacation,vacationcontracts) {
return $http({
method: "POST",
url: urlBase + '/' + url,
data: vacation, vacationcontracts,
async: false,
})
};
i got this error http://localhost/VacationSetupAPI/api/[object%20Object]
Any help, Thanks in advance
Try
this.save = function (vacation,vacationcontracts) {
You are sending object as 1st argument and then assigning it to the url as string, that’s wrong.
vacationsetupSrv.save(vacation, $scope.vacationcontracts);
OR
Try
vacationsetupSrv.save(“”,vacation, $scope.vacationcontracts);
I'm trying to build the back and front end parts of files uploader in an app built with jhipster 4.0.0, with angularjs.
How can I proceed ? Jhipster is actually giving acess for creating blob type columns with the entities builder , but isn't it a bad idea to store image in the database?
So, how can I build that file uploader ?
Another option you can consider if you don't want to save the file/image as a blob to the database is to create a separate file upload/retrieval service (ours was using MongoDB gridFs as we were dealing with large files) and only send the file id (or file path) of the successfully uploaded file when you save/update the entity.
You can use ng-file-upload to help manage this, note that below code is using angularJS version 1.
<form name="editForm" role="form" novalidate ng-submit="save(userUploadedFile)" show-validation>
<div class="form-group">
<div ng-show="entity.image == null">
<label class="control-label" for="field_file">Image</label>
<input type="file" ngf-select ng-model="userUploadedFile" name="file" id="field_file"
ngf-max-size="8MB" ng-disabled="disableFile" ngf-change="upload($files)" ngf-accept="'image/*'"/>
<button type="button" ng-click="removeUserUploadedFile()" ng-show="userUploadedFile">Remove</button>
</div>
<div ng-show="editForm.file.$invalid">
<p class="help-block" ng-show="editForm.file.$error.maxSize">Error! Image exceeds 8MB file limit</p>
</div>
<my-server-file file-id="entity.image" on-delete="onRemoveServerFile()"/>
</div>
<!-- include submit button etc -->
</form>
The my-server-file directive:
angular.module('myApp')
.directive('myServerFile', function(UrlService, FileService, $log) {
return {
restrict: 'E',
scope: {
fileId: "=fileId",
callbackOnDelete: "&onDelete"
},
template : "<div ng-if='fileId'>" +
"<a ng-if='fileId' ng-href='{{serverFilePath}}' target='_blank' download='{{fileName}}'>{{fileName}}</a>" +
"<button type='button' ng-click='deleteServerFile()' ng-show='fileId'>Remove</button>" +
"</div>",
controller: ['$scope',
function($scope) {
$scope.getFile = function(fileId) {
if(fileId){
$scope.serverFilePath = UrlService.getContextPath() + '/api/file/' + fileId;
FileService.getFileMetaData(fileId).then(function(file){
$scope.fileName = file.name;
});
}
}
$scope.deleteServerFile = function(){
FileService.deleteFile($scope.fileId).then(function() {
$scope.callbackOnDelete();
});
}
}
],
link: function(scope, iElement, iAttrs, ctrl) {
scope.$watch('fileId', function(value) {
scope.getFile(value);
})
}
}
})
Your controller will need to upload the file before it saves.
'use strict';
angular.module('myApp').controller('myAppController',
['$scope', '$stateParams', '$uibModalInstance', 'entity', 'UrlService', 'Upload', '$timeout', 'MyEntity'
function($scope, $stateParams, $uibModalInstance, entity, UrlService, Upload, $timeout, MyEntity) {
$scope.entity = entity;
$scope.load = function(id) {
MyEntity.get({id : id}, function(result) {
$scope.entity = result;
});
};
$scope.onRemoveServerFile = function() {
//Need to remove the file reference from the entity.
$scope.entity.image = null;
if($scope.entity.id){
MyEntity.update($scope.entity);
}
}
$scope.removeUserUploadedFile = function() {
$scope.userUploadedFile = null;
}
var uploadFile = function(file){
file.upload = Upload.upload({
url: UrlService.getContextPath() + '/api/file',
file: file
});
file.upload.then(function (response) {
$timeout(function () {
file.result = response.data;
$scope.entity.image = file.result.fileId;
saveEntity();
});
}, function (response) {
if (response.status > 0)
$scope.errorMsg = response.status + ': ' + response.data;
}, function (evt) {
// Math.min is to fix IE which reports 200% sometimes
file.progress = Math.min(100, parseInt(100.0 * evt.loaded / evt.total));
});
}
var onSaveSuccess = function (result) {
$scope.isSaving = false;
$scope.$emit('myApp:entityUpdate', result);
$uibModalInstance.close(result);
};
var onSaveError = function (result) {
$scope.isSaving = false;
};
var saveEntity = function() {
$scope.isSaving = true;
if ($scope.entity.id != null) {
MyEntity.update($scope.entity, onSaveSuccess, onSaveError);
} else {
MyEntity.save($scope.entity, onSaveSuccess, onSaveError);
}
};
$scope.save = function (file) {
if(file != null){
uploadFile(file);
}
else {
saveEntity();
}
};
}]);
This does introduce some drawbacks though, when you delete the entity you will need to delete the file separately.
Create a simple FileSerivce to interact with the backend.
angular.module('myApp')
.factory('FileService', function ($http) {
return {
getFile: function(fileId) {
return $http.get('api/file/' + fileId).then(function (response) {
return response.data;
});
},
getFileMetaData: function(fileId) {
return $http.get('api/file/' + fileId + '/metaData').then(function (response) {
return response.data;
});
},
deleteFile: function(fileId) {
return $http.delete('api/file/' + fileId);
}
}
});
And finally your REST controller, here we are delegating to a custom wrapper for GridFs but you could just save it to the file system and return the full path as the id.
#RestController
#RequestMapping("/api/file")
public class FileController {
private final Logger log = LoggerFactory.getLogger(FileController.class);
#Inject
private GridFsService gridFsService;
#RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public Map<String, String> upload(#RequestParam("file") MultipartFile file)
throws IOException {
GridFSFile storeFile = null;
if (!file.isEmpty()) {
storeFile = gridFsService.storeFile(file);
}
Map<String, String> map = new HashMap<>();
map.put("fileId", storeFile.getId().toString());
return map;
}
#RequestMapping(value="/{fileId}", method = RequestMethod.GET)
#ResponseBody
public ResponseEntity<InputStreamResource> getFile(
#PathVariable String fileId) throws IOException {
return gridFsService.getFile(fileId).map(f -> new ResponseEntity<> (
new InputStreamResource(f.getInputStream()),
HeaderUtil.createFileContentLengthAndContentTypeHeaders(f.getLength(), f.getContentType()),
HttpStatus.OK
)).orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
#RequestMapping(value="/{fileId}/metaData",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<Map<String, String>> getFileMetaData(
#PathVariable String fileId) throws IOException {
Optional<GridFSDBFile> optFile = gridFsService.getFile(fileId);
if(!optFile.isPresent()){
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
GridFSDBFile file = optFile.get();
Map<String, String> map = new HashMap<>();
map.put("name", file.getFilename());
map.put("contentType", file.getContentType());
map.put("size", String.valueOf(FileUtils.byteCountToDisplaySize(file.getLength())));
return ResponseEntity.ok()
.body(map);
}
#RequestMapping(value="/{fileId}", method = RequestMethod.DELETE)
public void deleteFile(
#PathVariable String fileId) throws IOException {
log.debug("REST request to delete File : {}", fileId);
gridFsService.deleteFile(fileId);
}
}
I am try to collect data and images from local server (Acquia Dev Desktop) using this Angular Js code
controller
var app = angular.module('App', []);
app.controller('Ctrl', function($scope, $http) {
$scope.images = [];
$http({
method : "GET",
url : 'http://docroot.com.dd:8083/catalogue/11/images/json'
}).then(function mySucces(response) {
$scope.images = response.data;
}, function myError(response) {
$scope.images = response.statusText;
});
});
Json
[{"image":" <a href=\"http:\/\/docroot.com.dd:8083\/sites\/docroot.com.dd\/files\/catalogues\/2016-09\/images\/Pty%20Prs.compressedjpg_Page1.jpg\">Property Press.compressedjpg_Page1.jpg<\/a>"}]
// i got out put like this :
<a href=\"http:\/\/docroot.com.dd:8083\/sites\/docroot.com.dd\/files\/catalogues\/2016-09\/images\/Pty%20Prs.compressedjpg_Page1.jpg\">Property Press.compressedjpg_Page1.jpg<\/a>
i need to collect only image url instead of the whole link ,
Well, sending HTML elements in a JSON doesn't seem good to but, anyway if you cannot change it...
For my part, I would parse the html string with the built-in XML parser.
Here is the code taken from this answer
//XML parser
var parseXml;
if (typeof window.DOMParser != "undefined") {
parseXml = function(xmlStr) {
//should work with any recent browser
return ( new window.DOMParser()).parseFromString(xmlStr, "text/xml");
};
} else if (typeof window.ActiveXObject != "undefined" &&
new window.ActiveXObject("Microsoft.XMLDOM")) {
//This part is intended to very old browsers
parseXml = function(xmlStr) {
var xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async = "false";
xmlDoc.loadXML(xmlStr);
return xmlDoc;
};
} else {
throw new Error("No XML parser found");
}
//Your code
var jsonContent= [{"image":" <a href=\"http:\/\/docroot.com.dd:8083\/sites\/docroot.com.dd\/files\/catalogues\/2016-09\/images\/Pty%20Prs.compressedjpg_Page1.jpg\">Property Press.compressedjpg_Page1.jpg<\/a>"}];
var elem = jsonContent[0].image;
var link = parseXml(elem);
try {
document.getElementById("out").innerHTML = link.documentElement.getAttribute("href");
} catch (e) {
alert(e);
}
<span id="out" />
I already did some research about this issue, but I don't find any problems in the code. If I save the file to disk it looks fine.
The document could not be loaded...
The javascript blob object has bytes.
Response code is 200.
Maybe somebody finds a coding issue?
The html:
<div data-ng-show="vouchercontent">
<embed ng-src="{{vouchercontent}}" style="width:400px;height:700px;"></embed>
</div>
Angular Controller:
$scope.vouchercontent = undefined;
$scope.generateVoucher = function() {
var self = this;
generateVoucherService.generateVoucher($scope.voucherdata).then(function(result) {
var file = new Blob([result], {type: 'application/pdf' });
var fileURL = window.URL.createObjectURL(file);
$scope.vouchercontent = $sce.trustAsResourceUrl(fileURL);
}, function(error) {
alert(error);
});};
Angular Service:
generateVoucher : function(data){
return $http.post('rest/generatevoucher/generate', data, {responseType: 'arraybuffer'})
.then(function(response){
return response.data;
}, function (response) {
return $q.reject(response.data);
});
}
Response in the service:
Response in the controller:
How can I get a 'progress' event from my AngularJS $http POST request that is uploading an image? Is it possible to do this client-side, or do I need the server to report the progress as it receives the data?
Using pure angular:
function upload(data) {
var formData = new FormData();
Object.keys(data).forEach(function(key){formData.append(key, data[key]);});
var defer = $q.defer();
$http({
method: 'POST',
data: formData,
url: <url>,
headers: {'Content-Type': undefined},
uploadEventHandlers: { progress: function(e) {
defer.notify(e.loaded * 100 / e.total);
}}
}).then(defer.resolve.bind(defer), defer.reject.bind(defer));
return defer.promise;
}
and somewhere else ...
// file is a JS File object
upload({avatar:file}).then(function(responce){
console.log('success :) ', response);
}, function(){
console.log('failed :(');
}, function(progress){
console.log('uploading: ' + Math.floor(progress) + '%');
});
You can also use the simple/lightweight angular-file-upload directive that takes care of these stuff.
It supports drag&drop, file progress/abort and file upload for non-HTML5 browsers with FileAPI flash shim
<div ng-controller="MyCtrl">
<input type="file" ng-file-select="onFileSelect($files)" multiple>
</div>
JS:
//inject angular file upload directive.
angular.module('myApp', ['angularFileUpload']);
var MyCtrl = [ '$scope', '$upload', function($scope, $upload) {
$scope.onFileSelect = function($files) {
//$files: an array of files selected, each file has name, size, and type.
for (var i = 0; i < $files.length; i++) {
var $file = $files[i];
$upload.upload({
url: 'my/upload/url',
file: $file,
progress: function(e){}
}).then(function(data, status, headers, config) {
// file is uploaded successfully
console.log(data);
});
}
}
}];
I don't think $http.post() can be used for this.
As for client-side, it should work with an HTML5 browser, but you'll probably have to create your own XMLHttpRequest object and onprogress listener. See AngularJS: tracking status of each file being uploaded simultaneously for ideas.
I don't think Angular has something built-in to handle uploads.
I think your best bet is to use something like jQuery File Upload. An idea for a solution would to create a Service that returns {progress:0} as default and then inside itself, implements the jQuery File Upload's progress update callback, which then simply keeps updating the progress. Thanks to Angular's binding, the upload progress would be in sync.
angular.module('myApp.services', [])
.factory('Uploader', function() {
var uploaderService = {};
var status = { progress: 0 };
uploaderService.upload = function(inputEl) {
inputEl.fileupload({
/* ... */
progressall: function (e, data) {
status.progress = parseInt(data.loaded / data.total * 100, 10);
}
});
};
return uploaderService;
});
Here is another solution:
window.XMLHttpRequest = (function (orig) {
if (orig) {
var intercept = [],
result = function () {
var r = new orig();
if (r.upload) {
$(r).on(
'abort error load loadend loadstart progress',
function (e) {
$(document).trigger('upload.XHR', e);
}
);
}
if (intercept.length) {
intercept[0].push({
request:r
});
}
return r;
};
result.grab = function (f) {
intercept.unshift([]);
f();
return intercept.shift();
};
return result;
}
return function () {
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
throw new Error("This browser does not support XMLHttpRequest.");
};
}(window.XMLHttpRequest));
Notes:
AngularJS currently stores a reference to window.XMLHttpRequest as private XHR variable, then uses it like this: new XHR(). I doubt this will ever change, so the shim-like code above should work just fine.
Mozilla has some extensions: XMLHttpRequest accepts optional arguments. The code above does not handle this, but AngularJS does not use these extensions anyway.
One of possible uses (if you want to show all current requests, and maybe implement some "Cancel" button):
$(document).on('upload.XHR', function (_e, e) {
switch (e.type) {
// do your thing here
}
});
Another possible use:
var list = window.XMLHttpRequest.grab(function () {
// start one or more $http requests here, or put some code
// here that indirectly (but synchronously) starts requests
$http.get(...);
couchDoc.save();
couchDoc.attach(blob, 'filename.ext');
// etc
});
list[0].request.upload.addEventListener(...);
Or, you can combine both approaches with some modifications to the code above.
you can use this where Im using simple angular function to upload file and $scope.progressBar variable to check the progress of uploading...
$scope.functionName = function(files) {
var file = files[0];
$scope.upload = $upload.upload({
url: 'url',
method: 'POST',
withCredentials: true,
data: {type:'uploadzip'},
file: file, // or list of files ($files) for html5 only
}).progress(function(evt) {
console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
$scope.progressBar = parseInt(100.0 * evt.loaded / evt.total);
}).success(function(data, status, headers, config) {
console.log('upload succesfully...')
}).error(function(err) {
console.log(err.stack);
})
}