I have 2 elements on a page
an input type="file" and a button
when the button is clicked, I want to check if the file selected on the input element still exists or not. Let's just say the file gets deleted or renamed after after being selected and before the button was clicked.
Is there a way to do this? Even just a simple alert code whether it exists or not would be helpful.. thank you
You can use the URL.createObjectURL method which will create a direct pointer to your file on the disk.
Then to check whether it has been deleted/renamed or not, you can simply try to fetch it (either through the fetch API, or through XHR).
let url;
inp.onchange = e => {
url = URL.createObjectURL(inp.files[0]);
btn.disabled = false;
}
btn.onclick = e => {
fetch(url)
.then((r) => console.log("File still exists"))
.catch(e => console.log("File has been removed or renamed"));
}
<input type="file" id="inp">
<button disabled id="btn">check if deleted</button>
ES5 version : (with a lot of quirks to handle... only tested in FF Safari and chrome)
var url;
inp.onchange = function(e) {
url = URL.createObjectURL(inp.files[0]);
btn.disabled = false;
}
btn.onclick = function(e) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url); // cache trick for Safari
xhr.responseType = 'blob';
var headers = { // Safari uses the cache...
"Pragma": "no-cache",
"Cache-Control": "no-store, no-cache, must-revalidate, post-check=0, pre-check=0",
"Expires": 0,
"Last-Modified": new Date(0), // January 1, 1970
"If-Modified-Since": new Date(0)
};
for (var k in headers) {
xhr.setRequestHeader(k, headers[k]);
}
xhr.onload = function(e) {
if (xhr.response.size) {
console.log("File still exists\n");
} else { // chrome fires the load event
console.log("File has been removed or renamed (load event)\n");
}
};
xhr.onerror = function(e) { // sometimes it fires an error
console.log("File has been removed or renamed (error event)\n");
};
try {
xhr.send();
} catch (e) { // sometimes it throws in FF
console.log("File has been removed or renamed (caught)\n");
}
}
<input type="file" id="inp">
<button disabled id="btn">check if deleted</button>
And fiddles for Safari which doesn't fetch BlobURIs from stacksnippetĀ®'s null-origined iframes :
ES6, ES5
Extending on #Kaiido answer: loading from URL like that will load the entire file. If the file is huge it will take very long time and/or will cause the browser to consume a lot of RAM. Tested on chrome with 8GB file - browser used around 8GB memory when using fetch. When using XHR it seemed not to eat up memory but the request took very long to complete. One possible workaround - check onprogress event of the XHR:
xhr.onprogress = function (e) {
if (e.loaded > 0) {
xhr.abort();
console.log("File still exists");
}
}
Related
I am making a form where the user uploads local files onto the TinyMCE editor to which upon form submission they will be sent to the Flask server to store the image files. I have automatic_uploads set to false for this and I'm also using a custom image_upload_handler.
My problem is that if one of the images is too big, say it exceeds 1MB, then I want to reject that and not submit the form (but also not reset everything the user has already written).
Handler and init functions
// Taken from the TinyMCE v6 docs example, but I added image size checking.
const example_image_upload_handler = (blobInfo, progress) => new Promise((resolve, reject) => {
// Reject any images uploaded that are too big.
var image_size = blobInfo.blob().size; // size in bytes
var image_name = blobInfo.blob().filename;
var max_size = 1048576
if( image_size > max_size ){
reject("File too large! Max 1MB");
return; // Why isn't this the end of it?
}
const xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.open('POST', '/upload_image');
xhr.upload.onprogress = (e) => {
progress(e.loaded / e.total * 100);
};
xhr.onload = () => {
if (xhr.status === 403) {
reject({ message: 'HTTP Error: ' + xhr.status, remove: true });
return;
}
if (xhr.status < 200 || xhr.status >= 300) {
reject('HTTP Error: ' + xhr.status);
return;
}
const json = JSON.parse(xhr.responseText);
if (!JSON || typeof json.location != 'string') {
reject('Invalid JSON: ' + xhr.responseText);
return;
}
resolve(json.location);
};
xhr.onerror = () => {
reject('Image upload failed due to a XHR Transport error. Code: ' + xhr.status);
};
const formData = new FormData();
formData.append('file', blobInfo.blob(), blobInfo.filename());
xhr.send(formData);
});
tinymce.init({
selector: '#content',
height: 500,
plugins: 'advlist auto-link lists link image charmap preview anchor page break wordcount',
toolbar_mode: 'floating',
automatic_uploads: false,
images_upload_handler: example_image_upload_handler,
});
Using uploadImages() to submit a form
document.addEventListener("DOMContentLoaded", function() {
const postSubmitButton = document.querySelector(".submit-post");
// Called when form button is submitted.
postSubmitButton.addEventListener("click", function() {
tinymce.activeEditor.uploadImages()
.then(() => {
document.forms[0].submit(); // I want to submit this only if the
}); // Promise is not rejected.
});
});
If a normal-sized image is posted it submits fine and the image is correctly uploaded to the server.
If, however, a larger-sized image is posted the form is still submitted even when the Promise gets rejected and returned. Then the image is stored as base64. I have no idea how to stop that.
I am entirely new to how Promises work and relatively new to Javascript so any help on this would be greatly appreciated.
Solved, but in a tacky way.
I used a global boolean variable to track whether the file size is okay or not as a condition to submit the form.
I tried to open file with
window.open("file:///D:/Hello.txt");
The browser does not allow opening a local file this way, probably for security reasons. I want to use the file's data in the client side. How can I read local file in JavaScript?
Here's an example using FileReader:
function readSingleFile(e) {
var file = e.target.files[0];
if (!file) {
return;
}
var reader = new FileReader();
reader.onload = function(e) {
var contents = e.target.result;
displayContents(contents);
};
reader.readAsText(file);
}
function displayContents(contents) {
var element = document.getElementById('file-content');
element.textContent = contents;
}
document.getElementById('file-input')
.addEventListener('change', readSingleFile, false);
<input type="file" id="file-input" />
<h3>Contents of the file:</h3>
<pre id="file-content"></pre>
Specs
http://dev.w3.org/2006/webapi/FileAPI/
Browser compatibility
IE 10+
Firefox 3.6+
Chrome 13+
Safari 6.1+
http://caniuse.com/#feat=fileapi
The HTML5 fileReader facility does allow you to process local files, but these MUST be selected by the user, you cannot go rooting about the users disk looking for files.
I currently use this with development versions of Chrome (6.x). I don't know what other browsers support it.
Because I have no life and I want those 4 reputation points so I can show my love to (upvote answers by) people who are actually good at coding I've shared my adaptation of Paolo Moretti's code. Just use openFile(function to be executed with file contents as first parameter).
function dispFile(contents) {
document.getElementById('contents').innerHTML=contents
}
function clickElem(elem) {
// Thx user1601638 on Stack Overflow (6/6/2018 - https://stackoverflow.com/questions/13405129/javascript-create-and-save-file )
var eventMouse = document.createEvent("MouseEvents")
eventMouse.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
elem.dispatchEvent(eventMouse)
}
function openFile(func) {
readFile = function(e) {
var file = e.target.files[0];
if (!file) {
return;
}
var reader = new FileReader();
reader.onload = function(e) {
var contents = e.target.result;
fileInput.func(contents)
document.body.removeChild(fileInput)
}
reader.readAsText(file)
}
fileInput = document.createElement("input")
fileInput.type='file'
fileInput.style.display='none'
fileInput.onchange=readFile
fileInput.func=func
document.body.appendChild(fileInput)
clickElem(fileInput)
}
Click the button then choose a file to see its contents displayed below.
<button onclick="openFile(dispFile)">Open a file</button>
<pre id="contents"></pre>
Try
function readFile(file) {
return new Promise((resolve, reject) => {
let fr = new FileReader();
fr.onload = x=> resolve(fr.result);
fr.readAsText(file);
})}
but user need to take action to choose file
function readFile(file) {
return new Promise((resolve, reject) => {
let fr = new FileReader();
fr.onload = x=> resolve(fr.result);
fr.readAsText(file);
})}
async function read(input) {
msg.innerText = await readFile(input.files[0]);
}
<input type="file" onchange="read(this)"/>
<h3>Content:</h3><pre id="msg"></pre>
Others here have given quite elaborate code for this. Perhaps more elaborate code was needed at that time, I don't know. Anyway, I upvoted one of them, but here is a very much simplified version that works the same:
function openFile() {
document.getElementById('inp').click();
}
function readFile(e) {
var file = e.target.files[0];
if (!file) return;
var reader = new FileReader();
reader.onload = function(e) {
document.getElementById('contents').innerHTML = e.target.result;
}
reader.readAsText(file)
}
Click the button then choose a file to see its contents displayed below.
<button onclick="openFile()">Open a file</button>
<input id="inp" type='file' style="visibility:hidden;" onchange="readFile(event)" />
<pre id="contents"></pre>
Consider reformatting your file into javascript.
Then you can simply load it using good old...
<script src="thefileIwantToLoad.js" defer></script>
Here is how to do it in typescript if Blob is good enough (no need to convert to ByteArray/String via FileReader for many use-cases)
function readFile({
fileInput,
}: {
fileInput: HTMLInputElement;
}): Promise<ArrayLike<Blob>> {
return new Promise((resolve, reject) => {
fileInput.addEventListener("change", () => {
resolve(fileInput.files);
});
});
}
here is how to do it in vanilla javascript
function readFile({
fileInput,
}) {
return new Promise((resolve, reject) => {
fileInput.addEventListener("change", () => {
resolve(fileInput.files);
});
});
}
It is not related to "security reasons" . And it does not matter if it local or file on network drive.
The solution for Windows OS could be IIS - Internet Information Services
and this is some details :
To open file in browser with Java Script window.open() , the file should be available on WEB server.
By creating Virtual Directory on your IIS that mapped to any physical drive you should be able to open files.
The virtual directory will have some http: address.
So instead of window.open("file:///D:/Hello.txt");
You will write window.open("http://iis-server/MY_VIRTUAL_DRIVE_D/Hello.txt");
The xmlhttp request method is not valid for the files on local disk because the browser security does not allow us to do so.But we can override the browser security by creating a shortcut->right click->properties In target "... browser location path.exe" append --allow-file-access-from-files.This is tested on chrome,however care should be taken that all browser windows should be closed and the code should be run from the browser opened via this shortcut.
You can't. New browsers like Firefox, Safari etc. block the 'file' protocol. It will only work on old browsers.
You'll have to upload the files you want.
Javascript cannot typically access local files in new browsers but the XMLHttpRequest object can be used to read files. So it is actually Ajax (and not Javascript) which is reading the file.
If you want to read the file abc.txt, you can write the code as:
var txt = '';
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function(){
if(xmlhttp.status == 200 && xmlhttp.readyState == 4){
txt = xmlhttp.responseText;
}
};
xmlhttp.open("GET","abc.txt",true);
xmlhttp.send();
Now txt contains the contents of the file abc.txt.
The thing I want to build is that by clicking a button I want to trigger the print of a PDF file, but without opening it.
+-----------+
| Print PDF |
+-----------+
^ Click *---------> printPdf(pdfUrl)
The way how I first tried it is to use an iframe:
var $iframe = null;
// This is supposed to fix the onload bug on IE, but it's not fired
window.printIframeOnLoad = function() {
if (!$iframe.attr("src")) { return; }
var PDF = $iframe.get(0);
PDF.focus();
try {
// This doesn't work on IE anyways
PDF.contentWindow.print();
// I think on IE we can do something like this:
// PDF.document.execCommand("print", false, null);
} catch (e) {
// If we can't print it, we just open it in the current window
window.location = url;
}
};
function printPdf(url) {
if ($iframe) {
$iframe.remove();
}
$iframe = $('<iframe>', {
class: "hide",
id: "idPdf",
// Supposed to be a fix for IE
onload: "window.printIframeOnLoad()",
src: url
});
$("body").prepend($iframe);
}
This works on Safari (desktop & iOS) and Chrome (can we generalize it maybe to webkit?).
On Firefox, PDF.contentWindow.print() ends with a permission denied error (even the pdf is loaded from the same domain).
On IE (11), the onload handler is just not working.
Now, my question is: is there another better way to print the pdf without visually opening it to the user?
The cross browser thing is critical here. We should support as many browsers as possible.
What's the best way to achieve this? Is my start a good one? How to complete it?
We are now in 2016 and I feel like this is still a pain to implement across the browsers.
UPDATE: This link details an elegant solution that involves editing the page properties for the first page and adding an action on Page Open. Works across all browsers (as browsers will execute the JavaScript placed in the actions section). Requires Adobe Acrobat Pro.
It seems 2016 brings no new advancements to the printing problem. Had a similar issue and to make the printing cross-browser I solved it using PDF.JS but had to make a one-liner addition to the source (they ask you to build upon it in anyways).
The idea:
Download the pre-built stable release from https://mozilla.github.io/pdf.js/getting_started/#download and add the "build" and "web" folders to the project.
The viewer.html file is what renders out PDFs with a rich interface and contains print functionality. I added a link in that file to my own JavaScript that simply triggers window.print() after a delay.
The link added to viewer:
<script src="viewer.js"></script>
<!-- this autoPrint.js was added below viewer.js -->
<script src="autoPrint.js"></script>
</head>
The autoPrint.js javascript:
(function () {
function printWhenReady() {
if (PDFViewerApplication.initialized) {
window.print();
}
else {
window.setTimeout(printWhenReady, 3000);
}
};
printWhenReady();
})();
I could then put calls to viewer.html?file= in the src of an iframe and hide it. Had to use visibility, not display styles because of Firefox:
<iframe src="web/viewer.html?file=abcde.pdf" style="visibility: hidden">
The result: the print dialog showed after a short delay with the PDF being hidden from the user.
Tested in Chrome, IE, Firefox.
After spending the past couple of hours trying to figure this one out and lots of searching here is what I have determined...
The HTML5 Web API spec for Printing indicates that one of the printing steps must fire beforeprint, a simple event (an event that is non-cancelable), to the window object of the Document being printed (as well as any nested browsing contexts, this relates to iframes) to allow for changes to the Document prior to printing. This step is internal to the browser and not something you'll be able to adjust. During this process, the browser's print dialog sometimes shows a preview of the file (Chrome does this)...so if your goal is to never display the file to the viewer you might be stuck.
The closest to achieving this I came was by creating an index.html file which has a button containing data-* attributes which provided context. Change the path/filename.ext in the data-print-resource-uri attribute to a local file of your own.
<!DOCTYPE html>
<html>
<head>
<title>Express</title>
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body>
<h1>Express</h1>
<p>Welcome to Express</p>
<button name="printFile" id="printFile" data-print-resource-uri="/binary/paycheckStub.pdf" data-print-resource-type="application/pdf">Print File</button>
<iframe name="printf" id="printf" frameborder="0"></iframe>
<script src="/javascripts/print.js"></script>
</body>
</html>
Then in the print.js file, I tried a few things, but never quite got it working (leaving different things I had played with in the comments).
// Reference vars
var printButton = document.getElementById('printFile');
var printFrame = document.getElementById('printf');
// onClick handler
printButton.onclick = function(evt) {
console.log('evt: ', evt);
printBlob('printf', printButton.getAttribute('data-print-resource-uri'), printButton.getAttribute('data-print-resource-type'));
}
// Fetch the file from the server
function getFile( fileUri, fileType, callback ) {
var xhr = new XMLHttpRequest();
xhr.open('GET', fileUri);
xhr.responseType = 'blob';
xhr.onload = function(e) {
// Success
if( 200 === this.status ) {
// Store as a Blob
var blob = new Blob([this.response], {type: fileType});
// Hang a URL to it
blob = URL.createObjectURL(blob);
callback(blob);
} else {
console.log('Error Status: ', this.status);
}
};
xhr.send();
}
function printBlob(printFrame, fileUri, fileType) {
// Debugging
console.log('inside of printBlob');
console.log('file URI: ', fileUri);
console.log('file TYPE: ', fileType);
// Get the file
getFile( fileUri, fileType, function(data) {
loadAndPrint(printFrame, data, fileType);
});
}
function loadAndPrint(printFrame, file, type) {
// Debugging
console.log('printFrame: ', printFrame);
console.log('file: ', file);
window.frames[printFrame].src = file;
window.frames[printFrame].print();
/*
// Setup the print window content
var windowContent = '<!DOCTYPE html>';
windowContent += '<html>'
windowContent += '<head><title>Print canvas</title></head>';
windowContent += '<body>'
windowContent += '<embed src="' + file + '" type="' + type + '">';
windowContent += '</body>';
windowContent += '</html>';
// Setup the print window
var printWin = window.open('','','width=340,height=260');
printWin.document.open();
printWin.document.write(windowContent);
printWin.document.close();
printWin.focus();
printWin.print();
printWin.close();
*/
}
I think that if you can get it working properly using the Blob might work the best in the cross-browser method you wanted.
I found a few references about this topic which might be helpful:
How to send a pdf file directly to the printer using JavaScript?
https://www.w3.org/TR/html5/webappapis.html#printing
https://developer.mozilla.org/en-US/docs/Web/Guide/Printing#Print_an_external_page_without_opening_it
Printing a web page using just url and without opening new window?
I will post here the modified functions of the OP functional on IE 11
printPdf: function (url) {
$('#mainLoading').show();
let iframe = $('#idPdf');
if (iframe) {
iframe.remove();
}
iframe = $('<iframe>', {
style: "display:none",
id: "idPdf"
});
$("body").prepend(iframe);
$('#idPdf').on("load", function(){
utilities.printIframeOnLoad()
})
utilities.getAsyncBuffer(url, function(response){
let path = utilities.getPdfLocalPath(response);
$('#idPdf').attr('src', path);
})
},
printIframeOnLoad: function () {
let iframe = $('#idPdf');
if (!iframe.attr("src")) { return; }
var pdf = iframe.get(0);
pdf.focus();
$('#mainLoading').hide();
pdf.contentWindow.print();
},
getPdfLocalPath: function (data) {
var filename = "Application_" + utilities.uuidv4() + ".pdf";
var blob = new Blob([data], { type: 'application/pdf' });
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, filename);
return filename;
}
else {
let url = window.URL || window.webkitURL;
let href = url.createObjectURL(blob);
return href;
}
},
getAsyncBuffer: function (uriPath, callback) {
var req = new XMLHttpRequest();
req.open("GET", uriPath, true);
req.responseType = "blob";
req.onreadystatechange = function () {
if (req.readyState === 4 && req.status === 200) {
callback(req.response);
}
};
req.send();
}
I am uploading a file via ajax request, by simply splitting them in to chunks.
The problem is progress event, Firefox for some reason doesn't want to fire that event, here is my code (most of the unnecessary code is removed)
//slice file
if(file.mozSlice){
chunk = file.mozSlice(startByte, endByte);
}else if(file.slice){
chunk = file.slice(startByte, endByte);
}else{
chunk = file;
isLast = true;
}
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', function(e){
console.log('progress');
}, false);
xhr.upload.addEventListener('error', function(e){
console.log("upload error!");
});
xhr.onreadystatechange = function(e){
if(this.readyState == 4 && this.status == 200){
//this chunk has bee uploaded, proceed with the next one...
}
}
xhr.open('POST', "", true);
xhr.setRequestHeader('Cache-Control', 'no-cache');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');//header
xhr.setRequestHeader('Content-Type', 'application/octet-stream');//generic stream header
xhr.send(chunk);
I'm sure i haven't made any big mistakes since chrome works without any problems, so there must be some Firefox related issue.
for Chrome:
xhr.upload.addEventListener('progress', function(e) {
console.log('progress');
}, false);
for Firefox:
xhr.addEventListener('progress', function(e) {
console.log('progress');
}, false);
I checked my implementation I'm adding the progress event after I call xhr.open, maybe that fixes it?
Try the 2nd code sample here: https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest/Using_XMLHttpRequest#Monitoring_progress does that work?
I understand that you can set HTTP request headers very easily when making AJAX calls in JavaScript.
However is it also possible to set custom HTTP request headers when inserting an iframe into a page via script?
<iframe src="someURL"> <!-- is there any place to set headers in this? -->
You can make the request in javascript, setting any headers you'd like. Then you can URL.createObjectURL(), to get something suitable for the src of the iframe.
var xhr = new XMLHttpRequest();
xhr.open('GET', 'page.html');
xhr.onreadystatechange = handler;
xhr.responseType = 'blob';
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
xhr.send();
function handler() {
if (this.readyState === this.DONE) {
if (this.status === 200) {
// this.response is a Blob, because we set responseType above
var data_url = URL.createObjectURL(this.response);
document.querySelector('#output-frame-id').src = data_url;
} else {
console.error('no pdf :(');
}
}
}
The MIME type of the response is preserved. So if you get an html response, the html will show in the iframe. If you requested a pdf, the browser pdf viewer will kick in for the iframe.
If this is part of a long-lived client-side app, you may want to use URL.revokeObjectURL() to avoid memory leaks.
The object URLs are also pretty interesting. They're of the form blob:https://your.domain/1e8def13-3817-4eab-ad8a-160923995170. You can actually open them in a new tab and see the response, and they're discarded when the context that created them is closed.
Here's a full example: https://github.com/courajs/pdf-poc
No, you can't. However you could set the iframe source to some kind of preload script, which uses AJAX to fetch the actual page with all the headers you want.
As #FellowMD answer is not working on modern browsers due to the depreciation of createObjectURL, I used the same approach but using iframe srcDoc attribute.
Retrieve the content to display in the iframe using XMLHttpRequest or any other method
Set the srcdoc parameter of the iframe
Please find below a React example (I know it is overkill):
import React, {useEffect, useState} from 'react';
function App() {
const [content, setContent] = useState('');
useEffect(() => {
// Fetch the content using the method of your choice
const fetchedContent = '<h1>Some HTML</h1>';
setContent(fetchedContent);
}, []);
return (
<div className="App">
<iframe sandbox id="inlineFrameExample"
title="Inline Frame Example"
width="300"
height="200"
srcDoc={content}>
</iframe>
</div>
);
}
export default App;
Srcdoc is now supported on most browsers. It seems that Edge was a bit late to implement it: https://caniuse.com/#feat=iframe-srcdoc
It turns out that URL.createObjectURL() is deprecated in Chrome 71
(see https://developers.google.com/web/updates/2018/10/chrome-71-deps-rems)
Building on #Niet the dark Absol and #FellowMD's excellent answers, here's how to load a file into an iframe, if you need to pass in authentication headers. (You can't just set the src attribute to the URL):
$scope.load() {
var iframe = #angular.element("#reportViewer");
var url = "http://your.url.com/path/etc";
var token = "your-long-auth-token";
var headers = [['Authorization', 'Bearer ' + token]];
$scope.populateIframe(iframe, url, headers);
}
$scope.populateIframe = function (iframe, url, headers) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onreadystatechange = handler;
xhr.responseType = 'document';
headers.forEach(function (header) {
xhr.setRequestHeader(header[0], header[1]);
});
xhr.send();
function handler() {
if (this.readyState === this.DONE) {
if (this.status === 200) {
var content = iframe[0].contentWindow ||
iframe[0].contentDocument.document ||
iframe[0].contentDocument;
content.document.open();
content.document.write(this.response.documentElement.innerHTML);
content.document.close();
} else {
iframe.attr('srcdoc', '<html><head></head><body>Error loading page.</body></html>');
}
}
}
}
and shoutout to courajs: https://github.com/courajs/pdf-poc/blob/master/script.js