I have a Chrome Packaged App that's a text editor. I've created a File Save dialog using FileSaver.js but I have a problem.
User Writes some text
User clicks Save and saves file as 'myfile.txt' (I set the default as 'file.txt')
User writes some more text
User clicks Save again, and expects to see 'myfile.txt' in the dialog, but they just see 'file.txt'
So is there any way around this? Ideally I'd be able to work out what that filename was so I can save it in chrome's local storage along with the file they've been editing so that it remembers next time they start up.
I imagine I can't get the full path because of security problems - but is there a way to get just the filename? Is there some non-standard thing in Chrome Packaged Apps that would allow this?
I imagine I can't get the full path because of security problems
Yes you can, and you just have to write file.fullPath to have it (as a string) when you're saving a file. Here is a simple example to save and get the path of a file:
function saveFile () {
chrome.fileSystem.chooseEntry({
type: "saveFile",
suggestedName: "file.txt"
},
function(savedFile) {
// The code to save your file, here you can use savedFile.fullPath
});
}
Related
I am not an HTML/JavaScript developer. I am having to modify some legacy code written by someone who has left.
We have a Python app which acts as a local server with an HTML/JavaScript front end that can be viewed in a browser.
The Python creates a temporary cache file. I would like to give the user the option to save a copy of this temp file to a location of their choice or at least download it to the downloads directory (Windows & Linux)
I've tried adapting some of the ideas from here: https://www.delftstack.com/howto/javascript/javascript-download/
E.g.
const saveAnalysisBtn = document.getElementById("saveAnalysisBtn");
saveAnalysisBtn.addEventListener('click', saveAnalysis);
function saveAnalysis(evt) {
function download(filename) {
var element = document.createElement('a');
// hardcode temp file name just for POC
element.setAttribute('href','file://C:\\tmp\\my_temp_cache.db');
element.setAttribute('download', filename);
document.body.appendChild(element);
element.click();
//document.body.removeChild(element);
}
var filename = "output.txt";
console.log(`Call Download`);
download(filename);
}
In Firefox this gives a security error:
Security Error: Content at
http://127.0.0.1:5000/replay/fapi_15_6_udi.bin may not load or link to
file:///C:/tmp/my_temp_cache.db
Which isn't terribly surprising. (Edge & Chrome give similar errors)
Is there a way to do this? Can be in HTML or JavaScript or Python (though I would like user to see evidence of download taking place in the browser).
Maybe I'm not understanding, but it looks like we're talking about just copying a file from one local location to a user specified location. The file you want to copy is on the machine the user is using? Couldn't you just provide the location in the web page and then just go there in a file explorer, finder, or command line tool to copy it however you want? It would solve the security issue.
But if you're required to create a link, you could create a download process that zips the file up to make a file like "my_temp_cache_db.zip" (or whatever compression tool/extension works best for you), and then provide the link for that. Zip files work through browsers better than some other types of files, and the user just has to unzip it wherever it ended up.
If that's not ideal, you could create a download process that makes a copy of the file and just changes the extension to something like "txt". The user downloads that file and then has to rename it to have the right extension.
How to (If possible) make a function inside a chrome extension that rewrites a file?
Files in directory : manifest.json background.js ls.txt (background.js obviously always running when chrome is open, in the background)
Example: a function that writes text to a file when the user makes any request
chrome.webRequest.onBeforeRequest.addListener(details => {
const anytext = "anytext"
const filedir = "ls.txt"
//how do I delete everything from ls.txt and write "anytext" to ls.txt, simply, how do I overwrite ls.txt?
},{ urls: ["<all_urls>"] },['requestBody']);
Boring backstory: I've tried looking for this online, but every result either shows me how to download a file or how to read it, maybe there is no way to modify a file? But then how do I save personal configurations between computer reboots if I can't save them to local files? And for anyone asking, this is definitely not why I'm making the plugin, I used this code as an example and not to make a plugin that writes text to a file every time a user makes a request.
I've converted an existing web application (HTML5, JS, CSS, etc.) into a Windows UWP app so that (hopefully) I can distribute it via the Windows Store to Surface Hubs so it can run offline. Everything is working fine, except PDF viewing. If I open a PDF in a new window, the Edge-based browser window simply crashes. If I open an IFRAME and load PDFJS into it, that also crashes. What I'd really like to do is just hand off the PDF to the operating system so the user can view it in whatever PDF viewer they have installed.
I've found some windows-specific Javascript APIs that seem promising, but I cannot get them to work. For example:
Windows.System.Launcher.launchUriAsync(
new Windows.Foundation.Uri(
"file:///"+
Windows.ApplicationModel.Package.current.installedLocation.path
.replace(/\//g,"/")+"/app/"+url)).then(function(success) {
if (!success) {
That generates a file:// URL that I can copy into Edge and it shows the PDF, so I know the URL stuff is right. However, in the application it does nothing.
If I pass an https:// URL into that launchUriAsync function, that works. So it appears that function just doesn't like file:// URLs.
I also tried this:
Windows.ApplicationModel.Package.current.installedLocation.getFileAsync(url).then(
function(file) { Windows.System.Launcher.launchFileAsync(file) })
That didn't work either. Again, no error. It just didn't do anything.
Any ideas of other things I could try?
-- Update --
See the accepted answer. Here is the code I ended up using. (Note that all my files are in a subfolder called "app"):
if (location.href.match(/^ms-appx:/)) {
url = url.replace(/\?.+/, "");
Windows.ApplicationModel.Package.current.installedLocation.getFileAsync(("app/" + url).replace(/\//g,"\\")).then(
function (file) {
var fn = performance.now()+url.replace(/^.+\./, ".");
file.copyAsync(Windows.Storage.ApplicationData.current.temporaryFolder,
fn).then(
function (file2) {
Windows.System.Launcher.launchFileAsync(file2)
})
});
return;
}
Turns out you have to turn the / into \ or it won't find the file. And copyAsync refuses to overwrite, so I just use performance.now to ensure I always use a new file name. (In my application, the source file names of the PDFs are auto-generated anyway.) If you wanted to keep the filename, you'd have to add a bunch of code to check whether it's already there, etc.
LaunchFileAsync is the right API to use here. You can't launch a file directly from the install directory because it is protected. You need to copy it first to a location that is accessible for the other app (e.g. your PDF viewer). Use StorageFile.CopyAsync to make a copy in the desired location.
Official SDK sample: https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/AssociationLaunching
I just thought I'd add a variation on this answer, which combines some details from above with this info about saving a blob as a file in a JavaScript app. My case is that I have a BLOB that represents the data for an epub file, and because of the UWP content security policy, it's not possible simply to force a click on a URL created from the BLOB (that "simple" method is explicitly blocked in UWP, even though it works in Edge). Here is the code that worked for me:
// Copy BLOB to downloads folder and launch from there in Edge
// First create an empty file in the folder
Windows.Storage.DownloadsFolder.createFileAsync(filename,
Windows.Storage.CreationCollisionOption.generateUniqueName).then(
function (file) {
// Open the returned dummy file in order to copy the data to it
file.openAsync(Windows.Storage.FileAccessMode.readWrite).then(function (output) {
// Get the InputStream stream from the blob object
var input = blob.msDetachStream();
// Copy the stream from the blob to the File stream
Windows.Storage.Streams.RandomAccessStream.copyAsync(input, output).then(
function () {
output.flushAsync().done(function () {
input.close();
output.close();
Windows.System.Launcher.launchFileAsync(file);
});
});
});
});
Note that CreationCollisionOption.generateUniqueName handles the file renaming automatically, so I don't need to fiddle with performance.now() as in the answer above.
Just to add that one of the things that's so difficult about UWP app development, especially in JavaScript, is how hard it is to find coherent information. It took me hours and hours to put the above together from snippets and post replies, following false paths and incomplete MS documentation.
You will want to use the PDF APIs https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/PdfDocument/js
https://github.com/Microsoft/Windows-universal-samples/blob/master/Samples/PdfDocument/js/js/scenario1-render.js
Are you simply just trying to render a PDF file?
I have a file structure on a web page, and look for a solution for the following scenario:
The chosen file should be downloaded in browser cache and opened (if it's an excel document, open with excel, etc.).
Now when the user changes the file, it should be detected and the file should be uploaded again.
Is this even possible with JavaScript?
If yes, where do I store the file (temporary internet folder?) and how do I detect the changes?
The only way for this to work you would need to have the user select the downloaded file, and then check for modification.
HTML
<label for="excelFile">Select the excel file: </label><input type="file" id="excelFile" />
JS
//Change event to detect when the user has selected a file
document.querySelector("#excelFile").addEventListener("change",function(e){
//get the selected file
var file = this.files[0];
//get the last modified date
var lastModified = file.lastModified;
//check lastModified against stored lastModified
//this assumes you store the last mod in localStorage
if(localStorage['excelLastMod'] < lastModified){
//It has modified update last mod
localStorage['excelLastMod'] = lastModified;
//do upload
}
});
If you know your user is using Chrome you can use Chrome's FileSystem api
The way you describe it: No, that is not possible in JavaScript.
It sounds like you want an FTP client.
When the user changes the file, it should be detected and the file should be uploaded again.
That is not possible due to JS having almost no access to the file system.
The only way you can access a file at all is by requesting the user to select one, see:
How to open a local disk file with Javascript?
So the most you could do would be:
File is downloaded.
Based on browser & settings, file may be opened automatically, or not.
User is presented with a file selection dialog that they can use when they are done editing.
Compare selected file to file on server and upload if changed.
After downloading a file, you have no control over it.
For applications that have a protocol registered (such a steam://, for example), you might be able to request the URL being opened in a program, but that would require an if per file type/program.
Detecting file changes is not at all possible (because you have no access to the file), and uploading again requires the user to select the file manually, using a file dialog.
Thanks for your help and ideas. I saw a software (https://www.group-office.com/) which includes this function so there has to be way to do it.
New Idea, using chrome filesystem api (#Siguza already said it):
Create file from servers database on users local filesystem with filesystem api
open file locally (should work with filesystem:http://www.example.com/persistent/info.txt, or?)
poll last changes of file every x seconds
if change detected, upload file back to servers database
I saw some problems with excel locking the files Check if file has changed using HTML5 File API
but except of that this should work, shouldn't it?
Requirements for a closed system: Firefox, client side code only, HTML, CSS and JavaScript/Jquery but no other open source libraries.
Need to save a complete web page. The built in functionality works great, except that I need to set the file name dynamically.
Currently, the built in Save as mechanism populates the file name (in the save as dialog) with the html title attribute.
However, I need the file name to be dynamically populated each time. (i.e. File1, File2, File3) - in other words I need to set the file name on each save via some code.
How do I do this leveraging the browser or writing it all myself?
Thank You!
EDIT
Is there an event that notices when save as is clicked and change the title right then?
Worst case, can I implement my own save as dialog?
EDIT 2
I see the command to save as can be called in IE document.execCommand('SaveAs',), is there an equivalent in FF? If I open the save as dialog via java script, I assume right then I would change the title?
You cannot do this. It is not scriptable. The file dialog is part of the OS that the browser hooks into.
Try using the new html5 download attribute:
Click here
If filename is sent in response header, browser will prefer that. But if not, browser will use filename specified in download attribute.
You can even make a script to automatically display the dialog:
//create a element
var a = document.createElement('a');
a.appendChild(document.createTextNode('Click here'));
a.href = 'http://some/url';
a.download = 'filename';
//Put filename in clipboard. If download filename is ignored, user can simply paste it
var aux = document.createElement('input');
aux.setAttribute('value', a.download);
document.body.appendChild(aux);
aux.select();
document.execCommand("copy");
document.body.removeChild(aux);
document.body.insertBefore(a, document.body.firstElementChild);
a.dispatchEvent(new MouseEvent('click'));
//Optionally you can remove the link as well (uncomment line bellow)
//document.body.removeChild(a);
Perhaps using an Auto Increment (AI) field in the DB which you are saving the file names to would do the trick.
Create a field for the file names <filename> in your DB table.
Define it as Not Null (NN) and as Auto Increment (AI) - this will give each new
record a value in this field as a running number.
In your PHP file, right before saving, create a new record in the DB table and then
pull out the auto-created field <filename> to a variable
<$newname>.
Save the file with PHP as you would, with <$newname>
followed by the file extension.
You mentioned this is closed system, though if you could set a local MySQL server and then this would work. (Also this might help others who don't happen to use a closed system, heh...)