Complex HTML output to PDF in ASP.NET C# - javascript

A page in our asp.net C# project is producing the following output:
Sample real output
The charts which I am using here are "Highcharts" (in the rows too, as shown in the image).
The requirement is to export this to PDF.
The above output is being produced in a div.
The following is what I have tried:
HTML part:
<input type="button" id="cmdExport" value="Export" style="width:70px" />
JS part:
<script type="text/javascript">
const toDataURL = url => fetch(url)
.then(response => response.blob())
.then(blob => new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onloadend = () => resolve(reader.result)
reader.onerror = reject
reader.readAsDataURL(blob)
}));
$('#cmdExport').click(function() {
var charts = Highcharts.charts,
exportUrl = 'https://export.highcharts.com/',
doc = new jsPDF(),
//doc = new jsPDF('p', 'pt', 'a4'),
pageHeight = doc.internal.pageSize.getHeight(),
ajaxCalls = [],
promises = [],
yDocPos = 0,
k = 0,
chart,
imgUrl,
i,
j;
for (i = 0; i < charts.length; i++) {
chart = charts[i];
ajaxCalls.push($.ajax({
type: 'post',
url: exportUrl,
data: {
options: JSON.stringify(chart.userOptions),
type: 'image/png',
async: true
}
}));
}
$.when.apply(null, ajaxCalls).done(function() {
for (j = 0; j < arguments.length; j++) {
imgUrl = exportUrl + arguments[j][0];
promises[j] = toDataURL(imgUrl);
}
Promise.all(promises).then((values) => {
values.forEach((value, index) => {
var page = doc.internal.getCurrentPageInfo();
if (yDocPos > pageHeight - 150) {
doc.addPage();
yDocPos = 25;
k = 0;
} else {
yDocPos = 25 + k * 140;
}
doc.setFontSize(30);
doc.text(50, yDocPos, 'jspdf title for Chart' + (index + 1));
yDocPos += 15;
doc.addImage(value, 'PNG', 20, yDocPos);
k++;
});
doc.save('charts.pdf');
});
});
});
</script>
The above code is giving error
"Access to XMLHttpRequest at 'https://export.highcharts.com/' from origin 'http://localhost:1042' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."
Is there any other way to fulfill this requirement?
Any help on this would be appreciated.

Related

javascript fetch an image and create base64

I'm using CloudFlare service workers and I want to fetch an image and then generate a base64 representation of it.
So something like this:
const res = await fetch('https://cdn.cnn.com/cnnnext/dam/assets/211010073527-tyson-fury-exlarge-169.jpg')
const blob = await res.blob();
console.log(blob)
console.log(btoa(blob))
this of course doesn't work, any ideas how to get this resolved?
complete worker script with cloudflare using btao
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
const imageUrl =
'https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/' +
'Cat_playing_with_a_lizard.jpg/1200px-Cat_playing_with_a_lizard.jpg';
function base64Encode (buf) {
let string = '';
(new Uint8Array(buf)).forEach(
(byte) => { string += String.fromCharCode(byte) }
)
return btoa(string)
}
function base64Decode (string) {
string = atob(string);
const
length = string.length,
buf = new ArrayBuffer(length),
bufView = new Uint8Array(buf);
for (var i = 0; i < length; i++) { bufView[i] = string.charCodeAt(i) }
return buf
}
async function handleRequest(request) {
const
upstreamResponse = await fetch(imageUrl),
text = base64Encode(await upstreamResponse.arrayBuffer()),
bin = base64Decode(text);
return new Response(bin, {status: 200, headers: {
'Content-type': upstreamResponse.headers.get('Content-type')
}})
}
can refer this discussion as well

Azure blob storage multiple simultaneous uploads with progress

I'm trying to upload multiple files simultaneously to Azure BLOB storage from JavaScript. I'm not sure how it's handling the parallelism, but I'm trying to have separate progress bars for each file/upload/promise.
Now the progress function gets called but gives only "loadedBytes" I need a way to know which progress bar to update.. One person suggested onload give an identifier, it does not seem to have an onload event. When I use the code below, the index is always the last one in the loop.
try {
console.log("Uploading files…");
var inputElement = document.getElementById('fileSelector');
const promises = [];
for (var fileIndex = 0; fileIndex < inputElement.files.length; fileIndex++) {
const file = inputElement.files[fileIndex];
var thisToken = await this.Instance.invokeMethodAsync('jsGetSASToken', file.name);
var containerURL = new azblob.ContainerURL(thisToken, azblob.StorageURL.newPipeline(new azblob.AnonymousCredential));
const blockBlobURL = azblob.BlockBlobURL.fromContainerURL(containerURL, file.name);
var blobUploadOptions = {
blockSize: 4 * 1024 * 1024, // 4MB block size
parallelism: 20, // 20 concurrency
metadata: { 'testindex': fileIndex.toString() },
progress: function (ev) {
var percentdone = ((ev.loadedBytes / file.size) * 100);
// Jumps around because loadedBytes is different for each upload
document.getElementById('percentdone-' + fileIndex).innerHTML = percentdone.toFixed(2) + "%";
// fileIndex is always the last item in the loop
}
};
promises.push(
azblob.uploadBrowserDataToBlockBlob(
azblob.Aborter.none,
file,
blockBlobURL,
blobUploadOptions
)
);
}
await Promise.all(promises);
console.log('Done.');
} catch (error) {
console.log("File Upload Error");
console.log(error);
}
Seems this issue is caused by fileIndex. I use file.name as the identifier, everything works as excepted. Try the code below:
<html>
<body>
<button id="select-button">Select and upload files</button>
<input type="file" id="file-input" multiple style="display: none;" />
<div id="showProgress"></div>
<p><b>Status:</b></p>
<p id="status" style="height:160px; width: 593px; overflow: scroll;" />
</body>
<script src="./azure-storage-blob.js" charset="utf-8"></script>
<script>
const selectButton = document.getElementById("select-button");
const fileInput = document.getElementById("file-input");
const status = document.getElementById("status");
const reportStatus = message => {
status.innerHTML += `${message}<br/>`;
status.scrollTop = status.scrollHeight;
}
const accountName = "storage account";
const sasString = "sas token";
const containerName = "container";
const containerURL = new azblob.ContainerURL(
`https://${accountName}.blob.core.windows.net/${containerName}?${sasString}`,
azblob.StorageURL.newPipeline(new azblob.AnonymousCredential));
const uploadFiles = async () => {
try {
reportStatus("Uploading files...");
const promises = [];
for (var fileIndex = 0; fileIndex < fileInput.files.length; fileIndex++) {
const file = fileInput.files[fileIndex];
const blockBlobURL = azblob.BlockBlobURL.fromContainerURL(containerURL, file.name);
document.getElementById('showProgress').innerHTML += file.name +":<div id='progress-"+ file.name +"'></div>"
var blobUploadOptions = {
blockSize: 4 * 1024 * 1024, // 4MB block size
parallelism: 20, // 20 concurrency
metadata: { 'testindex': fileIndex.toString() },
progress: function (ev) {
var percentdone = ((ev.loadedBytes / file.size) * 100);
var progessItem = document.getElementById('progress-' + file.name);
progessItem.innerHTML = percentdone.toFixed(2) + "%";
}
};
var promise = azblob.uploadBrowserDataToBlockBlob(
azblob.Aborter.none, file, blockBlobURL,blobUploadOptions);
promise.then((result)=>{
var progessItem = document.getElementById('progress-' + file.name);
progessItem.innerHTML += " file link"
});
promises.push(promise);
}
await Promise.all(promises);
reportStatus("Done.");
} catch (error) {
console.log(error)
}
}
selectButton.addEventListener("click", () => fileInput.click());
fileInput.addEventListener("change", uploadFiles);
</script>
</html>
Result:
Update Result:

Convert Image URI to File or Blob

I am able to get the image URI using Ionic Image Picker plugin:
this.imagePicker.getPictures(options).then((results) => {
for (var i = 0; i < results.length; i++) {
//below logs: 'Image URI: file:///Users/josh.0/Library/Developer/CoreSimulator/Devices/CC0FFBD2-EADF-4489-8F22-7948E0EFD261/data/Containers/Data/Application/2BC3C571-61B7-4EFF-A4D1-4D1F99F04EBC/tmp/cdv_photo_013.jpg'
console.log('Image URI: ' + results[i]);
}
}, (err) => { });
I need to get this image as a File, so I can upload it. I've tried doing the following, but it doesn't work:
this.imagePicker.getPictures(options).then((results) => {
for (var i = 0; i < results.length; i++) {
console.log('Image URI: ' + results[i]);
let base64Image = "data:image/jpeg;base64," + results[i];
fetch(base64Image)
.then(res => res.blob())
.then(blob => {
//Doesnt fire
console.log("Got blob")
const file = new File([blob], "image.png")
})
}
}, (err) => { });
How do I convert an image URI to file?
Ionic really needs to improve their documentation. The documentation on the plugin in the docs is absolutely pathetic. Took me hours to figure this out on my own. Anyway, here it is:
getImage() {
const options = {
maximumImagesCount: 1,
width: 800,
height: 800,
quality: 100,
outputType: 1 //Set output type to 1 to get base64img
};
this.imagePicker.getPictures(options).then((results) => {
var files: File[] = [];
for (var i = 0; i < results.length; i++) {
console.log('Image URI: ' + results[i]);
let blob = this.getBlob(results[i], ".jpg")
const file = new File([blob], "image.jpg")
//Do something with file like upload
}
}, (err) => { });
}
private getBlob(b64Data:string, contentType:string, sliceSize:number= 512) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
let byteCharacters = atob(b64Data);
let byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
let slice = byteCharacters.slice(offset, offset + sliceSize);
let byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
let byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
let blob = new Blob(byteArrays, {type: contentType});
return blob;
}
You need to get the file from the directory with AJAX. I use AngularJS and below is what I used to get the Blob. You can pretty much convert this to whatever JS framework/library you're using.
AngularJS method:
$http.get(file_uri, {'responseType':'blob'}).then(successMethod, failMethod);

Attempting to upload image with fetch api and C# web API

I have inherited the following C# code to upload an image, and I am attempting to try to call the endpoint using the fetch api.
Error I keep receiving:
{"Invalid 'HttpContent' instance provided. It does not have a content type header starting with 'multipart/'.\r\nParameter name: content"}
Any idea what I am doing wrong with calling this endpoint? I have included the C#/web api code, and the javascript code beneath that. Thanks for any help you can provide.
[Route( "coverpicture" )]
public virtual Task<HttpResponseMessage> Post()
{
HttpContext.Current.Response.ContentType = "application/json";
var formatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
var personId = CurrentUser.PersonId;
if ( !Request.Content.IsMimeMultipartContent() )
{
throw new HttpResponseException( HttpStatusCode.UnsupportedMediaType );
}
var task = Request.Content.ReadAsMultipartAsync().ContinueWith( t =>
{
var parts = t.Result.Contents;
var content = parts.First( x => x.Headers.ContentDisposition.Name.Contains( "CoverPicture" ) );
if ( content == null )
{
var resp = new HttpResponseMessage( HttpStatusCode.NotAcceptable )
{
Content = new ObjectContent<string>( "No ContentDisposition named CoverPicture.", formatter )
};
return resp;
}
using ( var imgStream = content.ReadAsStreamAsync().Result )
using ( var photos = new PhotosProvider() )
{
var photo = photos.CreateOrUpdateCoverPicture( personId, imgStream );
var resp = new HttpResponseMessage( HttpStatusCode.OK )
{
Content = new ObjectContent<Photo>( photo, formatter )
};
return resp;
}
} );
return task;
}
Javascript code:
let data = new FormData()
for (var x = 0; x < files.length; x++){
data.append("file" + x, files[x]);
}
fetch(url, {
method: 'POST',
mode: 'cors',
contentType: false,
processData: false,
body: JSON.stringify({}),
data: data
})
.then(parseResponse)
.then(resolve)
.catch(reject);
I think you should try as follows...
let data = new FormData()
for (var x = 0; x < files.length; x++){
data.append("file" + x, files[x]);
}
fetch(url, {
method: 'POST',
mode: 'cors',
body: data
})
.then(parseResponse)
.then(resolve)
.catch(reject);

How to get image width and height in a web-worker?

Here's my code:
// process-image.js (web-worker)
self.addEventListener('message', ev => {
const {id,file} = ev.data;
const reader = new FileReader();
reader.onload = ev => {
const imageFile = new Image(); // <-- error is here
imageFile.src = ev.target.result;
imageFile.onload = () => {
const fit = makeFit(imageFile);
self.postMessage({
id,
file: {
src: ev.target.result,
width: fit.width,
height: fit.height,
}
})
}
};
reader.readAsDataURL(file);
});
This was working fine in the main UI thread, but apparently I don't have access to Image inside of a web-worker. The specific error is:
Uncaught ReferenceError: Image is not defined
at FileReader.reader.onload (process-image.js:12)
Is there another way get the width and height of an image?
I'd like to support as many file types as possible, but just JPG is good enough for now if there's some sort of library that can do this and will run in a web-worker.
Here's the relevant bit from the UI thread:
// some-react-component.js
componentDidMount() {
this.worker = new ImageWorker();
this.worker.addEventListener('message', ev => {
const {id, file} = ev.data;
this.setState(({files}) => {
files = files.update(id, img => ({
...img,
...file,
}))
const withWidths = files.filter(f => f.width);
const avgImgWidth = withWidths.reduce((acc, img) => acc + img.width, 0) / withWidths.size;
return {files, avgImgWidth};
});
});
}
onDrop = ev => {
ev.preventDefault();
Array.prototype.forEach.call(ev.dataTransfer.files, file => {
const id = shortid();
this.worker.postMessage({id, file});
const img = {
id,
width: null,
height: null,
src: null,
name: file.name,
}
this.setState(({files}) => ({files: files.set(id, img)}));
});
}
Only thing worth noting here is that id is just a random UUID, and file is a File. I'm passing the whole thing to the web-worker so that I can do all the processing there.
I think there might be a simpler solution:
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/createImageBitmap
I managed to get the size without using FileReader at all:
http://jsfiddle.net/hL1Ljkmv/2/
var response = `self.addEventListener('message', async ev => {
const {id,file} = ev.data;
console.log('received data');
let image = await self.createImageBitmap(file);
console.log(image);
});`;
const blob = new Blob([response], {type: 'application/javascript'});
const worker = new Worker(URL.createObjectURL(blob));
const input = document.querySelector('#file-input');
input.addEventListener('change', (e) => {
worker.postMessage({file: input.files[0], id: 1})
});
I managed to get the width and height of most JPEGs by following this spec.
self.addEventListener('message', ev => {
const {id, file} = ev.data;
const reader = new FileReader();
reader.onload = ev => {
const view = new DataView(reader.result);
let offset = 0;
let header = view.getUint16(offset, false);
if(header !== 0xFFD8) {
throw new Error(`Not a JPEG`);
}
offset += 2;
for(; ;) {
header = view.getUint16(offset, false);
offset += 2;
let length = view.getUint16(offset, false);
if(header === 0xFFC0) break;
offset += length;
}
const height = view.getUint16(offset + 3);
const width = view.getUint16(offset + 5);
const fit = makeFit({width, height});
self.postMessage({
id,
file: {
src: URL.createObjectURL(file),
width: fit.width,
height: fit.height,
}
})
};
reader.readAsArrayBuffer(file);
});
This code is flakey as heck, but surprisingly it works. I'm still looking for a more robust solution.

Categories