I've got a server side route I'm using to download a file. This is called from a client side button click and everything is working fine. However, once the button has been clicked once it will not work again until another route is loaded and you go back. How can I code it so that the button can be clicked multiple times and the server side route be fired each time?
My button code looks like this...
'click #view_document_download': function (event, tmpl) {
Router.go('/download_document/' + this._id);
}
And my server side route looks like this...
Router.route('/download_document/:_id', function () {
//Get the file record to download
var file = files.findOne({_id: this.params._id});
//Function to take a cfs file and return a base64 string
var getBase64Data = function(file2, callback) {
var readStream = file2.createReadStream();
var buffer = [];
readStream.on('data', function(chunk) {
buffer.push(chunk);
});
readStream.on('error', function(err) {
callback(err, null);
});
readStream.on('end', function() {
callback(null, buffer.concat()[0].toString('base64'));
});
};
//Wrap it to make it sync
var getBase64DataSync = Meteor.wrapAsync(getBase64Data);
//Get the base64 string
var base64str = getBase64DataSync(file);
//Get the buffer from the string
var buffer = new Buffer(base64str, 'base64');
//Create the headers
var headers = {
'Content-type': file.original.type,
'Content-Disposition': 'attachment; filename=' + file.original.name
};
this.response.writeHead(200, headers);
this.response.end(buffer, 'binary');
}, { where: 'server' });
use a element instead of js 'click' event
page html
page js in server
Router.route("/download_document/:fileId", function(){
var file = files.findOne({_id: this.params.fileId});
var contentFile = //file text
let headers = {
'Content-Type': 'text/plain',
'Content-Disposition': "attachment; filename=file.txt"
};
this.response.writeHead(200, headers);
this.response.end(contentFile);
},
{where: "server", name: "download"}
);
Maybe you should just return an Object from your Server via a method and form it to a file on the client side? if possible..
To create a file on the client side is really simple, and you don't have to deal with Routers at this point.
function outputFile(filename, data) {
var blob = new Blob([data], {type: 'text/plain'}); // !note file type..
if(window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, filename);
}
else{
var elem = window.document.createElement('a');
elem.href = window.URL.createObjectURL(blob);
elem.download = filename;
document.body.appendChild(elem)
elem.click();
document.body.removeChild(elem);
}
}
function getContentAndOutputFile() {
var content = document.getElementById('content').value;
outputFile('file.txt', content);
}
<input id="content" value="test content"/>
<button onClick="getContentAndOutputFile()">Create File</button>
Related
I have an ASP MVC5 API that generates an excel.xlsx and returns it in a FileContentResult. All in memory, as I can not save the file to the server disk. It works ok if I access the url directly.
I have an AngularJS application that needs to pass a huge Json to the API and receive the generated .xlsx file.
I'm trying the following:
Controller:
public async Task<FileContentResult> Excel([FromBody]GetGeneralFilterVM operationHistoryFilter = null)
{
var ListaOperazioni = await GetListaOperazioniData(operationHistoryFilter);
var Totals = await GetExcelTotalsData(operationHistoryFilter);
var excelExport = new ExcelExportEntity(new object[]
{
ListaOperazioni,
Totals,
});
var preFile = excelExport.DoExcel();
var arraybits = preFile;
var file = File(arraybits, "application/vnd.ms-excel", "OperationHistory.xlsx");
return file;
}
Angular:
$scope.exportExcel = () => {
$.ajax({
cache: false,
url: appPath + "controller/Excel",
data: filter,
success: function (response) {
var file = new Blob([response], { type: "application/vnd.ms-excel" });
var fileName = "excelFeliz.xlsx";
saveAs(file, fileName);
},
error: function (ajaxContext) {
alert('Export error: ' + ajaxContext.responseText);
}
});
}
This will even download a file, but when trying to open it is corrupted.
My insistence on AJAX is because of the GetGeneralFilterVM that I am getting in the controller, it contains sub objects with many properties would be very complicated to put this as parameters in the url.
I also have no way to generate and return a url to download, because I can not save the file to the server disk.
Any idea?
Change the mime type to "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" instead of " "application/vnd.ms-excel"
After much searching I found a way, this one works 100%. Instead of using AJAX, which seems to have problems with blob, I used an XMLHttpRequest call. Note: The controller has not been changed.
getExportExcel: function (filter) {
var json_upload = "operationHistoryFilter=" + JSON.stringify(filter);
var url = appPath + "OperationHistoryReport/ExcelGeneral";
var fileName = "excel.xlsx"
var request = new XMLHttpRequest();
request.open('POST', url, true);
request.setRequestHeader('Content-Type', 'application/json');
request.responseType = 'blob';
request.onload = function (e) {
if (this.status === 200) {
var blob = this.response;
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, fileName);
}
else {
var downloadLink = window.document.createElement('a');
var contentTypeHeader = request.getResponseHeader("Content-Type");
downloadLink.href = window.URL.createObjectURL(new Blob([blob], { type: contentTypeHeader }));
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
}
}
};
request.send(JSON.stringify(filter));
}
Is there any way to generate a csv file and download it in the client side using meteor? I would the user to be able to click a button and then a file would be downloaded without leaving the current page.
Template.analytics.events({
'click .csv-export' (event, template) {
const json2csv = require('json2csv');
let fields = ['timestamp', 'user', 'category', 'action'];
let data = Analytics.find({}).fetch();
try {
let result = json2csv({data: data, fields: fields});
var filename = 'test.csv';
var headers = {
'Content-Type': 'text/csv',
'Content-Disposition': "attachment; filename=" + filename
};
this.response.writeHead(200, headers);
this.response.end(result);
} catch (err) {
console.error(err);
}
}
});
Thanks to #Jankapunkt I was able to solve this.
Here's the answer, all the code is inside Template.onRendered.
// DOWNLOAD CSV
this.csvFile = null,
makeTextFile = function() {
var csv = json2csv(Analytics.find({}).fetch(), true, false);
var data = new Blob([csv], {
type: 'text/csv'
});
if (this.csvFile !== null) {
window.URL.revokeObjectURL(this.csvFile);
}
this.csvFile = window.URL.createObjectURL(data);
return this.csvFile;
};
let csvExport = document.getElementById('csv-export');
csvExport.addEventListener('click', function() {
let downloadLink = makeTextFile();
window.open(downloadLink, '_blank');
}, false);
//END DOWNLOAD CSV
I'm trying to do the following:
The user fill a form and send it in .JSON to the server
With the form, the server generate some .CSV files and put them all together in a .ZIP file.
The server send the .ZIP file and the user download it.
After some research I have wrote this code:
My Controller:
[HttpPost]
[Route("routeToMyAPI")]
public HttpResponseMessage Process(Form form)
{
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(<streamToMyGeneratedZipFile>)
};
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = "fileName.zip"
};
return result;
}
My Service:
angular.module('app')
.factory('MyService', function ($http) {
return {
Process: function (form) {
var req = $http.post('rest/example/process', form);
return req;
}
};
});
My Controller:
this.submit = function () {
var Form = {};
var formPost = MyService.Process(Form);
formPost.then(function (data) {
var a = document.createElement('a');
var blob = new Blob([data], { 'type': "application/octet-stream" });
a.href = URL.createObjectURL(blob);
a.download = "fileName.zip";
a.click();
}, function (error) {
alert('An error occured !');
});
};
I have parts 1 & 2 working, but I don't have find the way to call my ASP API to download the .ZIP file.
When I call the submit method of my Controller, I have a fileName.zip who is downloaded on my PC but when I try to open it Windows says to me that it's not a valid archive.
What did I do wrong ? I'm a rookie in angularjs and ASP so any help will be welcomed.
Thanks in advance.
Several issues with your code:
After ZipArchive does its work, the position of the stream will be at the end. So you must reset it to the beginning like this before sending it:
zipStream.Position = 0;
Since you're setting the content type and file name of the file on the server already, just parse it on the client side.
var headers = data.headers(); //$http resolves to data, status, headers, config
var regex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var match = regex.exec(headers["content-disposition"]);
var fileName = match[1] || "myZipFile.zip";
fileName = fileName.replace(/\"/g, ""); //replace trailing and leading slashes
var contentType = headers["content-type"] || "application/octet-stream";
IE will not allow you to open blobs directly. You must use msSaveOrOpenBlob or msSaveBlob.
var blob = new Blob([data.data], { type: contentType });
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, fileName);
} else {
var a = document.createElement('a');
var objectUrl = URL.createObjectURL(blob);
a.href = URL.createObjectURL(blob);
a.download = match[1];
a.click();
}
One last thing: the previous code won't work on firefox because firefox doesn't support clic(). Thanks to this thread this can be solved by adding this little snippet of code:
HTMLElement.prototype.click = function() {
var evt = this.ownerDocument.createEvent('MouseEvents');
evt.initMouseEvent('click', true, true, this.ownerDocument.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
this.dispatchEvent(evt);
}
I cannot seem to find any documentation that will explain how I can get the filename and filepath of an uploaded collectionFS image into my meteor method.
I am able to get the image URL on the client side no problem using helpers, but I cannot seem to figure out how I can send the filename and filepath of the attached image to my method.
Method JS
Meteor.methods({
addQuote: function(data) {
check(data, Object);
var attachments = [];
var html = html;
// need to get the filename and filepath from collectionFS
// I would then have the data go here
attachments.push({filename: , filePath: });
this.unblock();
var email = {
from: data.contactEmail,
to: Meteor.settings.contactForm.emailTo,
subject: Meteor.settings.contactForm.quoteSubject,
html: html,
attachmentOptions: attachments
};
EmailAtt.send(email);
}
});
Controller JS
function ($scope, $reactive, $meteor) {
$reactive(this).attach($scope);
this.user = {};
this.helpers({
images: () => {
return Images.find({});
}
});
this.subscribe('images');
this.addNewSubscriber = function() {
// Uploads the Image to Collection
if(File.length > 0) {
Images.insert(this.user.contactAttachment);
console.log(this.user.contactAttachment);
}
// This is the variable I use to push to my method
// I image I need to push the filename and filepath also
// I am unsure how to access that information in the controller.
var data = ({
contactEmail: this.user.contactEmail,
contactName: this.user.contactName,
contactPhone: this.user.contactPhone,
contactMessage: this.user.contactMessage
});
// This will push the data to my meteor method "addQuote"
$meteor.call('addQuote', data).then(
function(data){
// Show Success
},
function(err) {
// Show Error
}
);
};
You can use the insert callback to get this informations:
Images.insert(fsFile, function (error, fileObj)
{
if (error) console.log(error);
else
{
console.log(fileObj);
//Use fileObj.url({brokenIsFine: true}); to get the url
}
});
I have a API that returns image and want to display the image on the browser. I am using iron:router package. On the client side user click on a link which is a basically a server side iron:route. The route makes call to API and should display the response of API on the browser.
client js : -
Template.images.events({
'click .image': function (event, template) {
event.preventDefault();
var docId = $(event.target).attr('data-docId');
var imageType = "raw";
var param = {"docId":docId,"imageType":imageType};
params = 'width=' + window.innerWidth;
params += ', height=' + window.innerHeight;
params += ', top=0, left=0'
params += ', fullscreen=yes';
var win = window.open("/Image/?param=" + encodeURIComponent(Base64.encode(JSON.stringify(param))), "_blank", params);
}
});
Iron:route : -
Router.route('/checkImage', function () {
var decoded = Base64.decode(decodeURIComponent(this.params.query.param));
var param = JSON.parse(decoded);
var docId = param.docId;
var content="";
Meteor.call('imageApi', docId, imageType, function (error, result) {
if (error) {
content = "";
} else
content = new Buffer(result);
});
if (content == "") {
this.response.writeHeader('200', {
'Content-Type': 'image/jpeg',
'Content-Disposition': "inline",
'Access-Control-Allow-Origin': '*'
});
this.response.write('<html><body><p>No content for image found.</p></body></html>');
this.response.end();
}
else {
this.response.writeHeader('200', {
'Content-Type': 'image/jpeg'
'Content-Disposition': 'inline; filename=image.jpg'
});
this.response.write(content);
this.response.end();
}
}, {where: 'server'});
Server method : -
imageApi: function (docId, imageType) {
var url = "API url with the paramters ";
var response;
try{
response = HTTP.call('GET', url, {
headers: {"Content-Type": "image/jpeg"},
responseType: "buffer"
});
}catch (error) {
logger.error("imageApi - Exception in image API " + error);
return false;
}
if (response.statusCode == 200) {
return new Uint8Array(response.content);
}
else {
logger.error"imageApi - Response issue: " + response.statusCode);
return "";
}
return "";
}
I am not able to display the image data on the browser. Do you think something is wrong in this approach or else if there is another way to render image.