In an AngularJS web app, I have this block of JS code:
var _loop = function _loop(file) {
// For each file a reader (to read the base64 URL)
// and a promise (to track and merge results and errors)
var promise = new Promise(function (resolve, reject) {
var reader = new FileReader();
reader.addEventListener('load', function (event) {
var type = void 0;
var name = file.name;
// Try to find the MIME type of the file.
var match = mimeTypeMatcher.exec(file.type);
if (match) {
type = match[1]; // The first part in the MIME, "image" in image/png
} else {
type = file.type;
}
// If it's an image, try to find its size
if (type === 'image') {
var data = {
src: reader.result,
name: name,
type: type,
height: 0,
width: 0
};
var image = new Image();
image.addEventListener('error', function (error) {
reject(error);
});
image.addEventListener('load', function () {
data.height = image.height;
data.width = image.width;
resolve(data);
});
image.src = data.src;
} else if (type) {
// Not an image, but has a type
resolve({
src: reader.result,
name: name,
type: type
});
} else {
// No type found, resolve with the URL only
resolve(reader.result);
}
});
reader.addEventListener('error', function (error) {
reject(error);
});
reader.addEventListener('abort', function (error) {
reject('Aborted');
});
reader.readAsDataURL(file);
});
promises.push(promise);
};
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = files[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var file = _step.value;
_loop(file);
} } catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
and in Internet Explorer 11 in the last block:
for (var _iterator = files[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var file = _step.value;
_loop(file);
}
I get this error:
the object does not support the property or the 'jscomp_symbol_iterator0' method
I read that IE doesn't support Symbol. So, I tried to rewrite the code into:
for (var _iterator = files.length, _step; !(_iteratorNormalCompletion = (_step = _iterator[_iterator++]).done); _iteratorNormalCompletion = true) {
var file = _step.value;
_loop(file);
}
But I have this error:
Can not read property "done" of undefined
So, I know that it is not supported by IE. But is there an alternative to write that loop in another way, supported by IE?
Before changing the code, if everything works in Chrome and if you use compilation for your project, you might consider to add at the top of your entry point
import 'core-js'
At the moment core-js polyfill library is the easiest way to make Cross Browser Support
Related
I hope this is simple. This is a react app where the user can click a button to download a file. When the button is clicked I make a server call to get a secure S3 bucket URL. I'm returning the URL and using window.open() to open the file. This works.
Instead of opening the file I would like to just download it. How do I do this?
handleDownloadResumeFile(event) {
event.preventDefault();
const url = event.target.value;
Meteor.call('getResumeUrl', url, function(error, result) {
if (error) {
console.log(error);
}
if (result && result.url) {
window.open(result.url);
}
});
}
Use saveAs(url, fileName') function from this, it works for me
https://github.com/eligrey/FileSaver.js
/* FileSaver.js
* A saveAs() FileSaver implementation.
* 1.3.8
* 2018-03-22 14:03:47
*
* By Eli Grey, https://eligrey.com
* License: MIT
* See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
*/
/*global self */
/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
/*! #source http://purl.eligrey.com/github/FileSaver.js/blob/master/src/FileSaver.js */
var saveAs = saveAs || (function(view) {
"use strict";
// IE <10 is explicitly unsupported
if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
return;
}
var
doc = view.document
// only get URL when necessary in case Blob.js hasn't overridden it yet
, get_URL = function() {
return view.URL || view.webkitURL || view;
}
, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
, can_use_save_link = "download" in save_link
, click = function(node) {
var event = new MouseEvent("click");
node.dispatchEvent(event);
}
, is_safari = /constructor/i.test(view.HTMLElement) || view.safari
, is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent)
, setImmediate = view.setImmediate || view.setTimeout
, throw_outside = function(ex) {
setImmediate(function() {
throw ex;
}, 0);
}
, force_saveable_type = "application/octet-stream"
// the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to
, arbitrary_revoke_timeout = 1000 * 40 // in ms
, revoke = function(file) {
var revoker = function() {
if (typeof file === "string") { // file is an object URL
get_URL().revokeObjectURL(file);
} else { // file is a File
file.remove();
}
};
setTimeout(revoker, arbitrary_revoke_timeout);
}
, dispatch = function(filesaver, event_types, event) {
event_types = [].concat(event_types);
var i = event_types.length;
while (i--) {
var listener = filesaver["on" + event_types[i]];
if (typeof listener === "function") {
try {
listener.call(filesaver, event || filesaver);
} catch (ex) {
throw_outside(ex);
}
}
}
}
, auto_bom = function(blob) {
// prepend BOM for UTF-8 XML and text/* types (including HTML)
// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type});
}
return blob;
}
, FileSaver = function(blob, name, no_auto_bom) {
if (!no_auto_bom) {
blob = auto_bom(blob);
}
// First try a.download, then web filesystem, then object URLs
var
filesaver = this
, type = blob.type
, force = type === force_saveable_type
, object_url
, dispatch_all = function() {
dispatch(filesaver, "writestart progress write writeend".split(" "));
}
// on any filesys errors revert to saving with object URLs
, fs_error = function() {
if ((is_chrome_ios || (force && is_safari)) && view.FileReader) {
// Safari doesn't allow downloading of blob urls
var reader = new FileReader();
reader.onloadend = function() {
var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;');
var popup = view.open(url, '_blank');
if(!popup) view.location.href = url;
url=undefined; // release reference before dispatching
filesaver.readyState = filesaver.DONE;
dispatch_all();
};
reader.readAsDataURL(blob);
filesaver.readyState = filesaver.INIT;
return;
}
// don't create more object URLs than needed
if (!object_url) {
object_url = get_URL().createObjectURL(blob);
}
if (force) {
view.location.href = object_url;
} else {
var opened = view.open(object_url, "_blank");
if (!opened) {
// Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html
view.location.href = object_url;
}
}
filesaver.readyState = filesaver.DONE;
dispatch_all();
revoke(object_url);
}
;
filesaver.readyState = filesaver.INIT;
if (can_use_save_link) {
object_url = get_URL().createObjectURL(blob);
setImmediate(function() {
save_link.href = object_url;
save_link.download = name;
click(save_link);
dispatch_all();
revoke(object_url);
filesaver.readyState = filesaver.DONE;
}, 0);
return;
}
fs_error();
}
, FS_proto = FileSaver.prototype
, saveAs = function(blob, name, no_auto_bom) {
return new FileSaver(blob, name || blob.name || "download", no_auto_bom);
}
;
// IE 10+ (native saveAs)
if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
return function(blob, name, no_auto_bom) {
name = name || blob.name || "download";
if (!no_auto_bom) {
blob = auto_bom(blob);
}
return navigator.msSaveOrOpenBlob(blob, name);
};
}
// todo: detect chrome extensions & packaged apps
//save_link.target = "_blank";
FS_proto.abort = function(){};
FS_proto.readyState = FS_proto.INIT = 0;
FS_proto.WRITING = 1;
FS_proto.DONE = 2;
FS_proto.error =
FS_proto.onwritestart =
FS_proto.onprogress =
FS_proto.onwrite =
FS_proto.onabort =
FS_proto.onerror =
FS_proto.onwriteend =
null;
return saveAs;
}(
typeof self !== "undefined" && self
|| typeof window !== "undefined" && window
|| this
));
I've implemented this file reader into my project.
I would like to make this return a promise when it finishes with the file reading, but I don't know how to propagate the promise from there.
class MyClass {
constructor() {}
public start(file) {
parseFile(file);
}
private parseFile(file) {
let fileSize = file.size;
let chunkSize = 10000;
let offset = 0;
let self = this;
let readBlock = null;
// How do I get this success function to return a promise to the user?
let success = function() { return new Promise...? };
let onLoadHandler = function(evt) {
if (evt.target.error == null) {
offset += evt.target.result.length;
chunkReadCallback(evt.target.result);
} else {
chunkErrorCallback(evt.target.error);
return;
}
if (offset >= fileSize) {
success(file);
return;
}
readBlock(offset, chunkSize, file);
}
readBlock = function(_offset, length, _file) {
let r = new FileReader();
let blob = _file.slice(_offset, length + _offset);
r.onload = onLoadHandler;
r.readAsText(blob);
}
readBlock(offset, chunkSize, file);
}
}
Today it works like this:
let x = new MyClass();
x.start(file);
And I would like it to be like this instead:
let x = new MyClass();
x.start(file).then(() => { console.log('done') });
Where do I put my return Promise so that the user can handle the promise?
Thanks!
The following should turn readFile into a promise:
private parseFile(file,chunkSize,offset) {
let fileSize = file.size;
let self = this;
readBlock = function (_offset, length, _file) {
return new Promise(
function(resolve,reject){
let r = new FileReader();
let blob = _file.slice(_offset, length + _offset);
//https://developer.mozilla.org/en-US/docs/Web/API/FileReader/onload
r.onload = function(e){
if(e.target.error!==null){
reject(e.target.error);
}
else{
resolve(e.target.result)
}
};
//https://developer.mozilla.org/en-US/docs/Web/API/FileReader/onerror
r.onerror = function(err){
reject(err);
}
r.readAsText(blob);
}
)
}
return readBlock(offset, chunkSize, file);
}
You can have the caller define what the block size is and when to read the next block.
An example how to use this function:
x.parseFile(file,file.size,0)
.then(
function(textData){
console.log(textData);
}
);
//read in chunks of 1000
function readInChunks(file,chunkSize=1000,offset=0,data=""){
return x.parseFile(file,chunkSize,offset)
.then(
function(textData){
if(offset+chunkSize>=file.size){
return data+textData;
}
console.log("get next chunk");
//recursively call itself
return readInChunks(file,chunkSize,offset+chunkSize,data+textData);
}
)
}
//call read in chunks
readInChunks(file,/* optional, defaults to 1000 */500)
.then(
function(textData){
console.log("got data:",textData);
}
)
Our application that utilizes IndexedDB to track tallies of data over periods of time. In using the app for a number of months, it seems that there is some sort of storage leak causing a relatively small amount of data to require 1.6 GB of storage on the device?
The app has a feature to download its full data as a ZIP file, and that file is under 50KB. So there must be a long-term leak somewhere. What we're trying now is to download the data, clear storage, and re-import to track down where this leak might be.
However, looking at the following code that interacts with IndexedDB, it's not obvious to me where that leak may be:
// Initializing the Database
if(!dbengine){
dbengine = this;
var request = window.indexedDB.open("DB", 1);
request.onerror = function(event) {
throw new Error("Error opening indexedDb")
};
// This creates the schema the first time around
request.onupgradeneeded = (event) => {
event.target.result.createObjectStore("db", { keyPath: 'id' });
};
request.onsuccess = (event) => {
dbengine.db = event.target.result;
var dbReadyEvent = new CustomEvent('db-ready', {});
document.dispatchEvent(dbReadyEvent);
dbengine.db.onerror = function(event) {
throw new Error("Database error: " + event.target.errorCode);
};
dbengine.db.onabort = function(event) {
throw new Error("Database aborted: " + event.target.error);
// This is the error that is being called for us - QuotaExceededError
};
};
}// if !dbengine
var initialTransaction = dbengine.db.transaction(['db'], 'readwrite');
initialTransaction.onabort = (event) => {
var error = event.target.error; // DOMError
alert(error.name);
} // onabort
initialTransaction.objectStore('db').openCursor().onsuccess = (event) => {
var cursor = event.target.result;
if(cursor) {
this.encryption.loadIdentity((success) => {
if(success) {
this.loadDb(() => {
document.dispatchEvent(new CustomEvent('api-ready', {}));
setInterval(this.syncDb.bind(this), this.syncInterval);
this.syncDb();
});
} else {
document.dispatchEvent(new CustomEvent('api-fail', {}));
}
});
} else {
initialTransaction.objectStore('db').add({
/* [...] */
});
document.dispatchEvent(new CustomEvent('db-created', {}));
} // if/else cursor
}; // objectStore
loadDb(callback) {
this._tallies = [];
this._collections = [];
var dbStore = dbengine.db.transaction(['db'], 'readwrite').objectStore('db');
dbStore.get(1).onsuccess = (event) => {
var result = event.target.result;
var tallies = result.tallies;
if(result.tallies.length !== 0) {
tallies = this._encryption.decrypt(result.tallies);
}
tallies.forEach(((t) => {
/* [...] */
}));
var collections = result.collections;
if(collections.length !== 0) {
collections = this._encryption.decrypt(result.collections);
}
collections.forEach(((c) => {
/* [...] */
}));
callback()
}
} // loadDb
logout() {
var dbStore = dbengine.db.transaction(['db'], 'readwrite').objectStore('db');
var request = dbStore.get(1);
request.onsuccess = function(e) {
delete e.currentTarget.result.identity;
dbStore.put(e.currentTarget.result);
redirectTo('welcome');
};
} // logout
syncDb() {
var tallyDataString = JSON.stringify(this.tallies);
var collectionDataString = JSON.stringify(this.collections);
var encryptedTallyDataString = this._encryption.encrypt(tallyDataString);
var encryptedCollectionDataString = this._encryption.encrypt(collectionDataString);
var dbStore = dbengine.db.transaction(['db'], 'readwrite').objectStore('db');
dbStore.get(1).onsuccess = (event) => {
var data = event.target.result;
if(data.tallies !== encryptedTallyDataString) {
data.tallies = encryptedTallyDataString;
dbStore.put(data);
}
if(data.collections !== encryptedCollectionDataString) {
data.collections = encryptedCollectionDataString;
dbStore.put(data);
}
var syncEvent = new CustomEvent('sync', {});
document.dispatchEvent(syncEvent);
} // onsuccess
dbStore.get(1).onabort = (event) => {
var error = event.target.error; // DOMError
// if (error.name == 'QuotaExceededError') {
// alert('Sync failed. Quota exceeded.')
// }
alert(error.name);
} // onabort
} // syncDb
loadIdentity(callback) {
var dbStore = dbengine.db.transaction(['db'], 'readwrite').objectStore('db');
dbStore.get(1).onsuccess = (event) => {
var result = event.target.result;
if(!result.identity || !result.identity.publicKey || !result.identity.privateKey) {
if(window.location.pathname !== "/app/welcome/") {
redirectTo('welcome');
}
}
this.identity = {
/* [...] */
};
var solid = false;
if(result.identity.publicKey && result.identity.privateKey) {
solid = true;
}
callback(solid);
};
} // loadIdentity
setIdentity(identity, username) {
var dbStore = dbengine.db.transaction(['db'], 'readwrite').objectStore('db');
var getRequest = dbStore.get(1);
getRequest.onsuccess = (event) => {
var result = event.target.result;
result.identity = {
/* [...] */
};
this.identity = identity;
var request = dbStore.put(result);
} // getRequest
} // setIdentity
I am currently struggling with cordova and AWS. I am about to download multiple items. Currently it is working successfully, but the array I build up is filled in a random order. Is there anyway I can keep track of the file being downloaded?
function GetFilesFromAWS(filesToGet) {
AWS.config.update();
AWS.config.region = REGION
var fileDown = [];
var fileDwnCnt = 0;
var filecnt = 0;
var fileDownNames = [];
var fileDownNameCnt = 0;
for (var file in filesToGet) {
if (filesToGet.length != 0) {
if (filesToGet[file].indexOf('.') != -1) {
fileDownNames[fileDownNameCnt] = filesToGet[file];
var s3bucket = new AWS.S3();
s3bucket.getObject({ Bucket: BUCKET, Key: filesToGet[file] }, function (err, data) {
if (err)// an error occurred
{
console.log(err, err.stack);
}
else {// successful response
fileDown[fileDwnCnt] = data.Body.buffer; //convert the data to a buffer
fileDwnCnt++;
if (fileDwnCnt == filecnt - 1) {
SaveFile(fileDown, fileDownNames)
}
}
});
fileDownNameCnt++;
filecnt++;
}
} else {
navigator.notification.alert("Documents Are up to date.");
}
}
And the code to saving files:
function SaveFile(fileDownloaded, fileName) {
var files = fileDownloaded;
var fileNames = fileName;
fileCnt = 0;
var fileNamesCnt = 0;
var message = true;
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fileSys) {
//The folder is created if doesn't exist
fileSys.root.getDirectory("Documentation", { create: true }, function (directory) {
for (var file in files) {
directory.getFile(fileNames[file].substring(fileNames[file].lastIndexOf('/')), { create: true }, function (file) {
file.createWriter(function (writer) {
writer.onwrite = function (evt) {
if (message) {
navigator.notification.alert("All Documents are updated");
message = false;
}
};
writer.onwriteend = function (evt) {
};
writer.write(files[fileCnt]);
fileCnt++;
});
}, resolveOnError);
}
}, resolveOnError);
}, resolveOnError);
}
What I want to achive is using the return value of the "previewfile" function as an execution indicator for the "readfiles" function. But this needs to be after the "image.onload" part has been executed, since there I need returnThis to be set to true.
I've researched several things on Google and Stackoverflow concerning this problem and callbacks / deferred objects in general, but I cannot wrap my head around how to applicate that in this situation.
I have the following constellation in my Image uploading section:
function previewfile(file, tests, acceptedTypes, holder) {
var returnThis = false;
if (tests.filereader === true && acceptedTypes[file.type] === true) {
var reader = new FileReader();
reader.onload = function (event) {
var image = new Image();
image.onload = function() {
var testimage = new Image();
testimage.src = $(this).attr('src');
var widthOfImage = testimage.width;
var heightOfImage = testimage.height;
if (!checkImageDimensions(widthOfImage, heightOfImage)) {
// do stuff
} else {
returnThis = true;
}
};
image.src = event.target.result;
holder.appendChild(image);
};
reader.readAsDataURL(file);
} else {
// do other stuff
}
return returnThis;
}
function readfiles(files, tests, acceptedTypes, holder, progress) {
var uploadNow = previewfile(files[0], tests, acceptedTypes, holder);
if (uploadNow === true) {
// do stuff
}
} else {
// do other stuff
}
}
I would go with something like this
function readfiles(files, tests, acceptedTypes, holder, progress) {
previewfile(files[0], tests, acceptedTypes, holder, function(value){
if (uploadNow === true){
// do stuff
}
else {
// do other stuff
}
});
}
function previewfile(file, tests, acceptedTypes, holder, callback) {
...
callback(returnValue); //instead of return
}
As previewfile() relies on asynchronous activity, it is itself effectively asynchronous. As such, it can't reliably return a value, but it can return a promise.
As others have pointed out, previewfile() can be written to accept a callback, which would avoid the need for a promise. However, if you want a promise solution, here is one (certainly not the only one).
function previewfile(file, tests, acceptedTypes, holder) {
if(tests.filereader === true && acceptedTypes[file.type] === true) {
var reader = new FileReader(),
image;
var promise_a = $.Deferred(function(dfrd) {
reader.onload = function(event) {
image.attr('src', event.target.result).appendTo(holder);
dfrd.resolve();
};
reader.onerror = function() {
dfrd.reject('fileReader error');
};
}).promise();
var promise_b = $.Deferred(function(dfrd) {
image = $("<img/>").on('load', function() {
var widthOfImage = image.width;
var heightOfImage = image.height;
if (checkImageDimensions(widthOfImage, heightOfImage)) {
dfrd.resolve();
} else {
//do stuff
dfrd.reject('image loaded but dimensions did not check out');
}
}).error(function() {
dfrd.reject('image did not load');
});
}).promise();
reader.readAsDataURL(file);
return $.when(promise_a, promise_b);
} else {
// do other stuff
// Also return a promise here, even if no async is involved.
}
}
readfiles() can now be written as follows :
function readfiles(files, tests, acceptedTypes, holder, progress) {
return previewfile(files[0], tests, acceptedTypes, holder).then(function() {
// do stuff
}).then(null, function(reason) {
console.log(reason);// or display to the user in the DOM.
// do other stuff
});
}
The benefit of a promise-based solution is maybe not so much in handling success as managing errors. Note how a single handler reports several different types of error.
With the help of FelixKling and kallehj, this is the working solution (with callback):
// important
function previewfile(file, tests, acceptedTypes, holder, callback) {
var returnThis = false;
if (tests.filereader === true && acceptedTypes[file.type] === true) {
var reader = new FileReader();
reader.onload = function (event) {
var image = new Image();
image.onload = function() {
var testimage = new Image();
testimage.src = $(this).attr('src');
var widthOfImage = testimage.width;
var heightOfImage = testimage.height;
if (!checkImageDimensions(widthOfImage, heightOfImage)) {
// do stuff
} else {
returnThis = true;
}
callback(returnThis); // important
};
image.src = event.target.result;
holder.appendChild(image);
};
reader.readAsDataURL(file);
} else {
callback(returnThis); // important
}
}
function readfiles(files, tests, acceptedTypes, holder, progress) {
// important
previewfile(files[0], tests, acceptedTypes, holder, function (uploadNow) {
if (uploadNow === true) {
// do stuff
}
} else {
// do other stuff
}
}
});