Using a local Apache web server for testing, with Chrome (33) and a very basic piece of code
function onInitFs(fs) {
fs.root.getFile("productinfo.xml", {}, function(fileEntry) {
fileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function(e) {
.
.
.
};
reader.readAsText(file);
}, errorHandler);
}, errorHandler);
}
window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);
No matter where I put the file (productinfo.xml), I get:
A requested file or directory could not be found at the time an operation was processed
My root directory is C:\xampp\htdocs so putting productinfo.xml there should work?
As the comments pointed out, you're going to want to make an AJAX call - you don't obtain the file without grabbing it from the server. I'm not certain if you are going to just stick with making the AJAX call everytime. However, working with the HTML5-File system can keep you from re-grabbing the XML every-time.
The code/my answer below is to grab the file locally when it exists and grab it from the server when it doesn't exist locally your code would look like the following (or something very similar - I copy and pasted a lot of working code and tried to abstract some components):
Function call to get the xml file,
whether it's locally or from the server, see code below - make sure fs is initialized before making the following call, this is done by setting it to a global variable in your onInitFs called in the request File system function
getFile("productinfo.xml",function(textDataFromFile){
console.log("some callback function"}
//your ... code should be handled here
);
the AJAX call to get your file from the server
function obtainFileFromServer(filename,callback){
var xhr2 = new XMLHttpRequest();
xhr2.onload = function(e){
writeToFile(filename,xhr2.response,callback);
}
xhr2.open('GET', "path/to/"+filename, true);
xhr2.send();
}
Reading from the HTML5-File system.
function getFile(filename,callback){
fs.root.getFile(filename, {create:false}, function(fileEntry) {
fileEntry.file(function(file) {
var errorHandler2 = function(e){
switch(e.name){
case "NotFoundError":
//if the productinfo.xml is not found, then go get it from the server
//In you're callback you'll want to also recall the getFile
obtainFileFromServer(function(){getFile(filename,callback);});
break;
default:
console.log(e);
break;
}
}
var reader = new FileReader();
reader.onloadend = function(e) {
callback(this.result);
};
reader.readAsText(file);
}, errorHandler2);
}, errorHandler);
}
Writing to HTML5 File-system
There are two ways you can look at the write method (writeToFile()), in the event you are replacing an old file, and the new one happens to be shorter, you'll want to truncate it before writing (you can only truncate immediately after opening the file - hence why it happens first). That appears to be outside of the scope of this question. I'm including the code to truncate, but not including the logic for figuring out whether or not you need to re-download the sample/if it is old.
function writeToFile(filename,data,callback){
fs.root.getFile(filename, {create: true}, function(fileEntry) {
fileEntry.createWriter(function(writer) {
writer.onwriteend = function(e) {
//we've truncated the file, now write the data
writer.onwriteend = function(e){
callback();
}
var blob = new Blob([data]);
writer.write(blob);
};
writer.truncate(0);
}, errorHandler);
}, errorHandler);
}
Related
I implemented image file upload to an AWS s3 bucket with dropzone. It creates a pre-signed url and ultimately sends the image file to s3. I cannot get this to work in combination with the client side image resizing dropzone provides.
For s3 upload to work properly, I had to override sending, as explained here https://github.com/enyo/dropzone/issues/590. Otherwise, it would send form data and I would get the image wrapped in ------WebKitFormBoundary...
This, from the link above is working:
sending: function(file, xhr) {
var _send = xhr.send;
xhr.send = function() {
_send.call(xhr, file);
};
}
However, I get problems when I try to combine the above approach with dropzone's client side image resize. As per the dropzone documentation, I specified:
resizeWidth: 384,
resizeHeight: 384,
resizeMethod: 'contain',
resizeQuality: 1.0,
Debugging the code, the resize functionality is called with the above settings. However, the file that arrives at sending is still the original file which has not been resized. I could not make out where the resized image would be stored.
I tried to change sending as follows:
sending (file, xhr) {
var _send = xhr.send;
this.options.transformFile.call(this, file, function (done) {
console.log('done', done)
xhr.send = function () {
_send.call(xhr, done);
}
}
However, the result from the call to transformFile is a blob and, while the result looks resized, it is also wrapped as a form.
In summary, can I get the combination of resize with plain image upload to work, somehow? Is the resized image stored in a suitable place? Can my override of sending be changed to get this to function?
I just created an account to answer this since I've been trying to do the same thing for the last hours.
I was on the same track as what you did, except I removed the resizeWidth and resizeHeight params to prevent double processing, and called resizeImage instead.
After messing with logs for a while it hit me, it was a race condition! The function sending returned before xhr got modified. Specifically, in createThumbnail there is a FileReader that executes async. Your result was resized and wrapped because the xhr did not get changed at all, so it just executed the normal code path.
Thus, just calling the resizing function in xhr.send does the trick! 🎉🥳
Here is my working code:
sending: function(file, xhr) {
var _send = xhr.send;
var _this = this
xhr.send = function () {
_this.resizeImage(file, 500, 500, 'contain', function (done) {
_send.call(xhr, done);
})
}
}
I asked the same on github and pkl's solution (https://github.com/enyo/dropzone/issues/1840#issuecomment-538643878) worked for me:
xhr.send = formData => {
// get the resized file in formData.get("file") vs file
_send.call(xhr, formData.get("file"));
};
also, joshmaines posted (https://github.com/enyo/dropzone/issues/1840#issuecomment-545590304):
You could also override the process queue and implementing it in the
thumbnail function instead: this.on('thumbnail', function(file, thumb)
{ file.thumbnail = thumb; this.processQueue(); });
Here is the function:
this.saveObj = function(o, finished)
{
root.getDirectory("object", {create: true}, function(directoryEntry)
{
directoryEntry.getFile("object.json", {create: true}, function(fileEntry)
{
fileEntry.createWriter(function(fileWriter)
{
fileWriter.onwriteend = function(e)
{
finished(fileEntry);
};
fileWriter.onerror = errorHandler;
var blob = new Blob([JSON.stringify(o)], {type: "json"});
fileWriter.write(blob);
}, errorHandler);
}, errorHandler);
}, errorHandler);
};
Now when I save an object everything works fine. Lets say I save {"id":1} my file content would be {"id":1}. Now I edit the object with o = {}; and save it again, my file content suddenly is {} "id":1 }.
It just overwrites the old content, but doesn't clean it. Do I have to delete the file before writing it or is there something I'm missing?
For as far as I understand the write method will write the supplied content to a position. To me this implies that the existing content is untouched unless you are overwriting parts. So I'm going to say yes, delete the file and save a new one.
source
According to the Mozilla documentation using only { create: true} :
The existing file or directory is removed and replaced with a new one,
then the successCallback is called with a FileSystemFileEntry or a
FileSystemDirectoryEntry, as appropriate.
Tested in Chrome 72 this seems to be the case.
This does not work as the file seems to be persist. The file will be overwritten (first bytes) but the size will remain the same. So this is a bug in at least Chrome 72.
Source
I have been looking for a way to initiate a file download of data that only exists within the code executing on the user's browser (as opposed to initiating a download from the server), and indicate the file name in the process.
A search of StackOverflow turns up a number of promising posts that don't quite do what I need.
This answer is not IE-compatible and doesn't allow specifying a file name anyway.
This question provides a few ways to initiate downloads, but the answers that allow specifying a file name are not IE compatible and require user interaction, while other answers don't allow specifying a file name.
Is there a way to initiate a file download from JavaScript that:
Downloads client-side data
Does not require the user to manually initiate the download
Allows specifying the filename of the downloaded file
After much searching around and trying approaches from various places, I was able to come up with the following.
The tryAnchorDownload() approach should work on modern versions of FireFox and Chrome, and is a cleaned-up version of code provided in this forum post.
The trySaveAsDownload() approach should theoretically work on any major modern browser, though Safari may not respect the specified file name. It requires the FileSaver.js library and some browsers may need the Blob.js polyfill.
You can call the main function here like this:
initiateFileDownload(bytes, "myFile.txt");
where bytes is a Uint8Array of the file's bytes.
function byteArrayToBase64(bytes) {
var chArray = Array.prototype.map.call(bytes,
function (byte) { return String.fromCharCode(byte); });
return window.btoa(chArray.join(""));
}
var octetStreamMimeType = "application/octet-stream";
function tryAnchorDownload(fileBytes, fileName) {
var aElement = document.createElement("a"),
event;
if ("download" in aElement) {
aElement.setAttribute("download", fileName);
aElement.href = "data:" + octetStreamMimeType +
";base64," + byteArrayToBase64(fileBytes);
document.body.appendChild(aElement);
event = document.createEvent("MouseEvents");
event.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0,
false, false, false, false, 0, null);
aElement.dispatchEvent(event);
document.body.removeChild(aElement);
return true;
}
return false;
}
function trySaveAsDownload(fileBytes, fileName) {
var blob;
if (window.saveAs) {
blob = new Blob([fileBytes], { type: octetStreamMimeType });
saveAs(blob, fileName);
return true;
}
return false;
}
// fileBytes is a Uint8Array
function initiateFileDownload(fileBytes, fileName) {
return tryAnchorDownload(fileBytes, fileName) ||
trySaveAsDownload(fileBytes, fileName);
}
A more thorough (and probably more efficient) alternative to the byteArrayToBase64() function above can be found on MDN.
I'm using HTML5 FileWriter API to save the state of my webapp. I have bit of JS that periodically calls FileWriter.write to do that (so , over time, the write method is called several times). By default FileWriter API use an 'append' approach to writing files which does not suits my needs since I wan't to overwrite the file content.
I first tried this:
this._writer.seek(0);
this._writer.write(content);
This is not working when you are writing a text shorter than the file content. I then tried this:
this._writer.truncate(0);
this._writer.write(content);
This code is supposed to clear the file and then write my new content but I'm getting the following error when write method is called:
Uncaught InvalidStateError: An operation that depends on state cached in an interface object was made but the state had changed since it was read from disk.
Odd thing: when I debug the code (with a breakpoint), the error does not occur, as if FileWriter.truncate was an asynchronous method...
I am stuck here, any ideas?
I am using Chrome 30.0.1599.69
Here is a correct code that won't waste 500ms on waiting
fileWriter.onwriteend = function() {
if (fileWriter.length === 0) {
//fileWriter has been reset, write file
fileWriter.write(blob);
} else {
//file has been overwritten with blob
//use callback or resolve promise
}
};
fileWriter.truncate(0);
You can truncate and then write with two different FileWriter objects.
fileEntry.createWriter(function (fileWriter) {
fileWriter.truncate(0);
}, errorHandler);
fileEntry.createWriter(function (fileWriter) {
var blob = new Blob(["New text"], { type: 'text/plain' });
fileWriter.write(blob);
}, errorHandler);
If you want to always override it, you can use this method
function save(path,data){
window.resolveLocalFileSystemURL(dataDirectory, function(dir){
dir.getFile(path, {create:true}, function(file){
file.createWriter(function(fileWriter){
fileWriter.seek(0);
fileWriter.truncate(0);
var blob = new Blob([data], {type:'text/plain'});
fileWriter.write(blob);
}, function(e){
console.log(e);
});
});
});
};
A workaround is the following code:
this._writer.truncate(0);
window.setTimeout(function(){
this._writer.write(content);
}.bind(this),500)
This simply wait 500 milliseconds before writing. Not great but it works...
This is the simplest way how i use to delete the content of a file with syncFileSystem in my Chrome App.
Two createWriter, the first one truncates then the second one overwrites with nothing (you can change with your new value) :
file.createWriter((fileWriter)=>fileWriter.truncate(0));
file.createWriter((fileWriter)=> {
fileWriter.onwriteend = function() {
console.log('New Actions!');
};
var blob = new Blob([''], {type: 'text/plain'});
fileWriter.write(blob);
});
Chrome implements the file interface as described here http://www.html5rocks.com/en/tutorials/file/filesystem/, just adding the webkit prefix. The documentation covers several aspects of the interface, but what are the simplest steps, for example, to prompt the user with a file saving dialog, or to tell him that the file has been saved somewhere? For example, let's say we want to save some text data for the user.
I'm mainly referring to lines of code as a metric of simplicity, but within the 80 characters per line (and common sense). I'm also referring to Chrome 26.
This is what i found. Naturally, it's use is quite limited, and it is better to refer to the main article linked above
function error(e) { console.log(e); };
webkitRequestFileSystem(TEMPORARY, Math.pow(2, 10), function(fs) {
fs.root.getFile( 'exported.txt', {create:true}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
fileWriter.onwriteend = function() {
alert('content saved to '+fileEntry.fullPath);
};
var blob = new Blob(['Lorem Ipsum'], {type: 'text/plain'});
fileWriter.write(blob);
});
}, error);
}, error);