Progress callback not updating during progress - javascript

I have written some code that reads a local text file and compresses it into a zip file using JSZip library.
I want to provide the user a progress bar as shown in this demo: https://stuk.github.io/jszip/documentation/examples/downloader.html
zip.generateAsync()method has a callback method but what happens is the file fully compresses then it send all the ticks at once at the end (check console output in code below using large enough file to cause some delay).
I can't understand what is blocking the callbacks until the process finishes. Can anyone help?
Here is my code:
var statusEl = document.getElementById("status");
const fileSelector = document.getElementById("file-selector");
fileSelector.addEventListener("change", (event) => {
readFile(event);
});
function readFile(event) {
statusEl.innerHTML = "Loading file";
const fileList = event.target.files;
console.log(fileList[0]);
const reader = new FileReader();
reader.addEventListener("load", (event) => {
statusEl.innerHTML = "Compressing file";
zipFile(event.target.result);
});
reader.readAsArrayBuffer(fileList[0]);
}
function zipFile(result) {
var zip = new JSZip();
zip.file("myfile.txt", result);
zip
.generateAsync(
{
type: "blob",
compression: "DEFLATE",
compressionOptions: {
level: 6
}
},
updateCallback
)
.then(function (content) {
saveAs(content, "example.zip");
});
}
function updateCallback(metaData) {
statusEl.innerHTML = metaData.percent.toFixed(2) + " %";
console.log(metaData.percent.toFixed(2) + " %");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.5.0/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.2/FileSaver.min.js"></script>
<label for="avatar">Choose a file:</label>
<input type="file"
id="file-selector" name="file-selector" accept=".txt">
<div id="status"></div>

JavaScript execution and page rendering are done in the same execution thread, which means that while your code is executing the browser will not be redrawing the page.
What you need to do is use the setTimeout(). Using these, there will be "spaces" in between your code execution in which the browser will get a chance to redraw the page.
UPDATED
You dont need to use FileReader, you can get the file from "onChange" on file selector.
added "streamFiles: true" into generateAsync options.
So, try this (It would be nice if your file is at least 8MB to clearly see the percentage):
var statusEl = document.getElementById("status");
const fileSelector = document.getElementById("file-selector");
fileSelector.onchange = function() {
var zip = new JSZip();
zip.file("myfile.txt", this.files[0]);
statusEl.innerHTML = "Compressing file";
zip.generateAsync({
type: "blob",
compression: "DEFLATE",
streamFiles: true,
compressionOptions: {
level: 6
}
},
updateCallback
)
.then(function(content) {
saveAs(content, "example.zip");
});
};
function updateCallback(metaData) {
const percent = metaData.percent;
setTimeout(function() {
//console.log(percent);
statusEl.textContent = percent.toFixed(2) + " %";
}, 10);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.5.0/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.2/FileSaver.min.js"></script>
<label for="avatar">Choose a file:</label>
<input type="file"
id="file-selector" name="file-selector" accept=".txt">
<div id="status"></div>

I'm afraid you need a Worker.
var statusEl = document.getElementById("status");
const fileSelector = document.getElementById("file-selector");
fileSelector.addEventListener("change", (event) => {
var blob = new Blob([
document.querySelector('#worker1').textContent
], { type: "text/javascript" })
var worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = function(e) {
statusEl.innerHTML = e.data;
console.log(e.data)
}
worker.postMessage(event.target.files[0]);
});
function readFile(event) {
statusEl.innerHTML = "Loading file";
const fileList = event.target.files;
console.log("qui", fileList[0]);
const reader = new FileReader();
reader.addEventListener("load", (event) => {
statusEl.innerHTML = "Compressing file";
console.log(event.target.result instanceof ArrayBuffer)
zipFile(event.target.result);
});
reader.readAsArrayBuffer(fileList[0]);
}
function zipFile(result) {
var zip = new JSZip();
zip.file("myfile.txt", result);
zip
.generateAsync(
{
type: "blob",
compression: "DEFLATE",
compressionOptions: {
level: 6
}
},
updateCallback
)
.then(function (content) {
saveAs(content, "example.zip");
});
}
function updateCallback(metaData) {
statusEl.innerHTML = metaData.percent.toFixed(2) + " %";
console.log(metaData.percent.toFixed(2) + " %");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.2/FileSaver.min.js"></script>
<label for="avatar">Choose a file:</label>
<input type="file"
id="file-selector" name="file-selector">
<div id="status"></div>
<script id="worker1" type="javascript/worker">
importScripts("https://cdnjs.cloudflare.com/ajax/libs/jszip/3.5.0/jszip.min.js");
function readFile(file) {
const reader = new FileReader();
reader.addEventListener("load", (event) => {
zipFile(event.target.result);
});
reader.readAsArrayBuffer(file);
}
function zipFile(result) {
var zip = new JSZip();
zip.file("myfile.txt", result);
zip
.generateAsync(
{
type: "blob",
compression: "DEFLATE",
compressionOptions: {
level: 6
}
},
metaData => self.postMessage(metaData.percent.toFixed(2) + " %")
)
.then(function (content) {
saveAs(content, "example.zip");
});
}
self.onmessage = function(e) {
readFile(e.data);
};
</script>
I tested it with a 70MB file and more or less it works. There is still an initial delay probably related to the read file time, if you find a solution to pipe the content from FileReader to JSZip...

Related

Vue.js read blob as object/string to app-variable

I have a vue app with
data: function() {
return {
modules: [],
...
And method:
methods: {
submitted: function(){
...
axios({method: 'get',
url: 'http://' + document.location.host + '/api/execute?commands=' + encodeURI(JSON.stringify(commands)),
responseType: "blob"}).then(response => {
if (response.headers['content-type'] === 'image/jpeg'){
this.modules[current]['results']['image']['visible'] = true
this.modules[current]['results']['text']['visible'] = false
const url = window.URL.createObjectURL(response.data)
this.modules[current]['results']['image']['value'] = url
}
else{
this.modules[current]['results']['image']['visible'] = false
this.modules[current]['results']['text']['visible'] = true
var reader = new FileReader();
reader.onload = function(e) {
// reader.result contains the contents of blob as a typed array
console.log(e)
this.modules[current]['results']['text']['value'] = reader.result
}
reader.readAsArrayBuffer(response.data);
}
...
This should request some data from server and show it as text or image depending on server response.
*v-for module in modules earlier*
<div class="resultText" :visibility=module.results.text.visible>
{{ module.results.text.value }}
</div>
<div class="resultImage" :visibility=module.results.image.visible>
<img :src=module.results.image.value>
</div>
But the method gives js error when text returned: TypeError: this.modules is undefined
The error in this string:
this.modules[current]['results']['text']['value'] = reader.result
With images it works fine.
With self.modules it gives same: TypeError: self.modules is undefined
Solved the problem with:
response.data.text().then(text => {
this.modules[current]['results']['text']['value'] = text
});

Multiple file upload using Laravel & Vue JS

so I've been trying to upload multiple image file using Vue JS with Laravel at server side.
My template vue
<input type="file" id = "file" ref="file" v-on:change="onImageChange" multiple />
My Javascript code
<script>
export default {
data(){
return{
product: {},
image: '',
}
},
created() {
let uri = `/api/product/edit/${this.$route.params.id}`;
this.axios.get(uri).then((response) => {
this.product = response.data;
});
},
methods:{
onImageChange(e){
let files = e.target.files || e.dataTransfer.files;
if (!files.length)
return;
this.createImage(files[0]);
},
createImage(file){
let reader = new FileReader();
let vm = this;
reader.onload = (e) => {
vm.image = e.target.result;
};
reader.readAsDataURL(file);
},
replaceByDefault(e) {
e.target.src = this.src='/uploads/products/default_image.jpg';
},
saveImage(e){
e.preventDefault()
var file = document.getElementById('file').files;
let formData = new FormData;
formData.append('productId', this.product.id)
formData.append('file', file[0])
axios.post('/api/product/image/add', formData, {
headers: {'Content-Type': 'multipart/form-data'}
}).then((response) => {
this.$router.push({name: 'view',params: { id: this.product.id }});
});
}
}
}
</script>
I saw somewhere the internet that in vue you can use looping the formData.append but how do i catch the data in the server side. Here is my ProductController
$saveImage = new Gallery;
$saveImage->product_id = $request->productId;
$file = request()->file('file');
$file_name = time().$file->getClientOriginalName();
$path = $imgUpload = Image::make($file)->save(public_path('/uploads/products/' . $file_name));
$saveImage->path = '/uploads/products/'.$file_name;
$saveImage->status = 1;
$saveImage->save();
return "success";
Thank you very much guys!
you can use request()->file('file') to get files. but you have to add some changes in your vue source when you are trying to send an array of files.
Vue
let formData = new FormData;
formData.append('productId', this.product.id)
// append files
for(let i=0; i<file.length; i++){
formData.append('file[]', file[i])
}
useing file[] instead of file will generate an array of files in request payload.
then in laravel side of code you can use request()->file('file') to get that array of files. but if you want just one of them (for example: first one) you can use request()->file('file.0') to get that file.

is it possible to create a variable for an endpoint response in javascript?

I have a function that depending on the button pressed, hits a different endpoint. However, these endpoints would have the same response, and return similar data. Is there a way to keep things DRY here instead of copying and pasting the response in an if else? Function below for reference.
$scope.getModelRunJson = function (xOrY) {
if(xOrY) {
Runs.getData({id: $scope.runId}).then(
function (response) {
$scope.runData = response.data.data[0].runData;
console.log($scope.modelRunData);
let blob = new Blob([$scope.runData], {type: 'json'});
let downloadLink = angular.element('<a></a>');
downloadLink.attr('href', window.URL.createObjectURL(blob));
downloadLink.attr('download', 'modelRun.json');
downloadLink[0].click();
}
)
}
else {
Audit.getData({id: $scope.runId}).then(
function (response) {
$scope.runData = response.data.data[0].runData;
console.log($scope.runData);
let blob = new Blob([$scope.runData], {type: 'json'});
let downloadLink = angular.element('<a></a>');
downloadLink.attr('href', window.URL.createObjectURL(blob));
downloadLink.attr('download', 'modelRun.json');
downloadLink[0].click();
}
)
}
};
Something like this. Runs and Audit both implement getData
$scope.getModelRunJson = function(xOrY) {
const xy = (xOrY) ? Runs : Audit;
xy.getData({
id: $scope.runId
}).then(
function(response) {
$scope.runData = response.data.data[0].runData;
console.log($scope.modelRunData);
let blob = new Blob([$scope.runData], {
type: 'json'
});
let downloadLink = angular.element('<a></a>');
downloadLink.attr('href', window.URL.createObjectURL(blob));
downloadLink.attr('download', 'modelRun.json');
downloadLink[0].click();
}
)
};

How to call a function inside a vue npm package?

What I'm trying to do is to display and modify the images that the car has "in my case", so I used the vue-upload-multiple-image package to save the images and went well, but when I call back these images to the same package I got stuck.
I convert the images that has been stored to base64 now what I want is the list of images go to specific function inside that package, so it will display the images when I try to update the car.
This is the function I want to call:
createImage(file) {
let reader = new FileReader()
let formData = new FormData()
formData.append('file', file)
reader.onload = e => {
let dataURI = e.target.result
if (dataURI) {
if (!this.images.length) {
this.images.push({
name: file.name,
path: dataURI,
highlight: 1,
default: 1,
})
this.currentIndexImage = 0
} else {
this.images.push({
name: file.name,
path: dataURI,
highlight: 0,
default: 0,
})
}
this.$emit(
'upload-success',
formData,
this.images.length - 1,
this.images,
)
}
}
reader.readAsDataURL(file)
},
The Function inside this file
I tried to console.log the function normally it outputs undefined,
I think of props but how it gonna help me.
mounted(){
console.log(this.createImage);
What I want is just to call this function inside my editcar component and sent to it the converter images.
Thank you for helping me and read the this far.
I Found the solution of the problem: in the doc there is dataImages prop. I use it like this:
<div class="form-group m-form__group">
<vue-upload-multiple-image
#upload-success="uploadImageSuccess"
#before-remove="beforeRemove"
#edit-image="editImage"
:dataImages="images"
></vue-upload-multiple-image>
</div>
And it must the images base64 so here is the function the the data.
data() {
return {
images :[],
}
},
mounted() {
this.ConvertImages();
},
This is methods:
methods: {
ConvertImages() {
let images = this.car.images
let image = this.images
for (var i = 0; i < images.length; i++) {
this.toDataURL(images[i].path, function(dataURL) {
image.push({
path: dataURL,
})
})
}
},
toDataURL(url, callback) {
var xhr = new XMLHttpRequest()
xhr.onload = function() {
var reader = new FileReader()
reader.onloadend = function() {
callback(reader.result)
}
reader.readAsDataURL(xhr.response)
}
xhr.open('GET', url)
xhr.responseType = 'blob'
xhr.send()
},
}, //END OF METHODS

Upload video with edge slingshot meteor

I try to upload a video on my s3 bucket with edge:slingshot package, It's work fine with image file but with video file I have this error : "Error: Failed to upload file to cloud storage [ - 0] trace : Meteor.makeErrorType/errorClass#http://localhost:3000/packages/meteor.js?9730f4ff059088b3f7f14c0672d155218a1802d4:525:15
getError#http://localhost:3000/packages/edgee_slingshot.js?4c5b8e7dc4cae9d464984ead4903ef4beaac99f5:388:1
Slingshot.Upload/<.transfer/<#http://localhost:3000/packages/edgee_slingshot.js?4c5b8e7dc4cae9d464984ead4903ef4beaac99f5:407:18"
my template event :
Template.upload_vid.events({
'change input[type="file"]': function ( event, template ) {
var file = event.currentTarget.files[0];
Modules.client.uploadToAmazonS3( { file: file, template: template, type :"video" } );
}});
my template :
<template name="upload_vid">
<input type="file" name="file" id="file_vid">
<label for="file_vid">download</label>
</template>
my function to define slingshot (slingshot.js on server side only):
Slingshot.fileRestrictions( "uploadVidToAmazonS3", {
allowedFileTypes: [ "video/webm", "video/mp4", "video/mov", "video/3gp"],
maxSize: 50 * 1024 * 1024
});
Slingshot.createDirective( "uploadVidToAmazonS3", Slingshot.S3Storage, {
bucket: my_bucket,
acl: "public-read",
authorize: function () {
var user = Meteor.userId();
if(user){
return true;
} else {
return false;
}
},
key: function ( file ) {
var user = Meteor.users.findOne( this.userId );
return user.emails[0].address + "/video_profil/" + file.name;
}
});
and function for use Slingshot and make upload (work fine with image) :
let template;
let _uploadPicsToAmazon = ...
let _uploadVidToAmazon = ( file, type ) => {
var uploader = new Slingshot.Upload( "uploadVidToAmazonS3" );
uploader.send( file, ( error, url ) => {
if ( error ) {
console.log( error, "warning" );
} else {
console.log("success");
}
});
};
let upload = ( options ) => {
template = options.template;
let file = options.file;
let type = options.type;
if(type == "img"){
_uploadPicsToAmazon( file, type );
}else{
_uploadVidToAmazon( file, type );
}
};
Modules.client.uploadToAmazonS3 = upload;
anyone have a solution? thank you beforehand.

Categories