34

I know that client-side Javascript cannot write data to the local filesystem, for obvious security reasons.

The only way to save data locally with Javascript seems to be with cookies, localStorage, or allow the user to download a file (with a "Save..." dialog box or to the browser's default Download folder).

But is it possible, in the very specific case when the file is accessed locally with an URL like file:///D:/test/index.html (and not through internet) to write data locally ? (without any server language, and even without any server at all: just local browsing of a HTML file)

For example, would it be possible, by clicking on SAVE here:

  <div contenteditable="true" style="height:200px;">Content editable - edit me and save!</div>
  <button>Save</button>

...that such a HTML file (accessed through file:///D:/test/index.html) is overwritten with its new content ? (i.e. the local HTML file should be updated when SAVE is pressed).


enter image description here


TL;DR: Is this possible to SAVE a file thanks to Javascript, when the HTML page is accessed locally?

Note: I want to be able to silently save, not propose a Download/Save dialog box in which the user has to choose where to download, then "Are you sure to want to overwrite" etc.


EDIT : Why this question? I'm doing an in-browser notepad that I can run locally without any server (no Apache, no PHP). I need to be able to save easily without having to deal with Dialog Box "Where do you want to download the file?" and having to always re-browse to the same folder to overwrite the currently-being-edited file. I would like a simple UX like in any notepad program: CTRL+S done, the current file is saved! (example: MS Word doesn't ask to browse where you want to save the file each time you do "Save": CTRL+S, done!)

Basj
  • 41,386
  • 99
  • 383
  • 673
  • 2
    I believe the only way you can do that is to submit the contents to some server-side processing (e.g. PHP, Python, etc.) and have the local scripts update the file. – Jacob Ewing Nov 27 '14 at 19:56
  • 1
    @JacobEwing and without sever-side processing? I mean *local* browsing of the HTML file, i.e. the browser opens c:\myproject\index.html with no Apache server / no PHP server – Basj Nov 27 '14 at 21:32
  • 1
    If you execute the script in the browser (even loaded from your local machine), then there is no way to tell it to modify local files (It's the browser that prevents this, not the server). You may be able to execute it with other software, in which case, I don't know what restrictions remain in place. http://stackoverflow.com/questions/2941411/executing-javascript-without-a-browser has a little more information that may help. – Jacob Ewing Nov 27 '14 at 22:14
  • see also [Javascript save data to file system (with user prompt)](http://stackoverflow.com/q/15212240/1176601) or [Javascript: Create and save file](http://stackoverflow.com/q/13405129/1176601) – Aprillion Nov 30 '14 at 00:27
  • you can alter the behavior of the save/open box to just save without asking, or memorize an app to handle a specific mime. or use an HTA file or "node+webkit" to get direct file-system access. – dandavis Dec 03 '14 at 04:14
  • The closest solution to my question seems to be : FileSystem API (see www.html5rocks.com/en/tutorials/file/filesystem/) but unfortunately, it seems to be dead and only Chrome has it... – Basj Dec 03 '14 at 09:59
  • @dandavis what do you mean by HTA or node+webkit ? Do you mean https://github.com/rogerwang/node-webkit ? Then the client needs to have it installed on his machine to be able to use it, right ? (so few users have it) – Basj Dec 03 '14 at 10:00
  • Why don't you do this: when the button is clicked make an ajax request to php and have php store file. Would that accomplish what you are asking? – www139 Dec 03 '14 at 13:59
  • Maybe java applet. Or flash? I haven't ever done flash though. – www139 Dec 03 '14 at 14:00
  • @www139 I have already implemented a solution with ajax (php on server side), it works, but in order to do a light-version (without server) I would like to be able to do it locally without server – Basj Dec 03 '14 at 19:52
  • 1
    @Basj: re: node-webkit, it produces something that runs without installing nore-webkit itself on the endpoint. HTA is ready to ship for windows, and you can use FSO to read/write files, as long as the file has an tag. there's also chrome packaged apps and firefox apps. and cordova. – dandavis Dec 06 '14 at 00:04
  • what is your ultimate goal? – www139 Dec 06 '14 at 15:41
  • @www139 : to be able to do a HTML/JS notepad that I can run locally without any server (no apache, no php). I need to be able to save easily without having to deal with Dialog Box "Where do you want to download the file?" – Basj Dec 06 '14 at 16:02
  • You might want to post that in your question. – www139 Dec 06 '14 at 16:10
  • @www139 I added that at the end of question – Basj Dec 06 '14 at 16:20
  • Have you considered using vbscript, i beleive if you use it in a HTA you can access the file system – Michael Smith Dec 06 '14 at 21:28
  • 7 years later, any other solutions? I have the exact same problem. Need to be able to save a file locally silently (even just on the same location of the html file). Will be purely local - just by running the html file and call javascript under the hood. I also think that using localStorage/indexedDB is NOT a good solution since it can be cleared by the user anytime. Help! – kzaiwo Feb 24 '22 at 09:17
  • 1
    @kzaiwo I just started a bounty to see if there are new ways. – Basj Feb 24 '22 at 09:35
  • HTAs really are great for local / personal tools written using HTML\CSS\JS. This was mentioned 7 years ago too, but judging by the answers below, I'm not sure if it was ever covered in more detail. They do have their own shortcomings of course, but because they exist outside of the usual browser security sandbox, and can access the filesystem, they should be considered. – Gian Singh Sarao Mar 02 '22 at 01:15
  • @GianSinghSarao After these years, I don't remember what is "HTA". "HTML applications" or something else? – Basj Mar 02 '22 at 09:08
  • @Basj Yes, HTML applications. They're a windows only solution, because it uses windows built-in html rendering stack, so it loses some points there. But it gains points because its basically unrestricted in modifying files on your system. I don't think it can be launched from a browser tab or server easily, but because simply double-clicking it in explorer opens it, it should be fine I think. See [my example of an HTA](https://giansinghsarao.github.io/String-Encrypter/) if you're curious about it, but to be honest it doesn't really showcase any of the filesystem access stuff, just HTAs. – Gian Singh Sarao Mar 02 '22 at 14:58

13 Answers13

30

You can just use the Blob function:

function save() {
  var htmlContent = ["your-content-here"];
  var bl = new Blob(htmlContent, {type: "text/html"});
  var a = document.createElement("a");
  a.href = URL.createObjectURL(bl);
  a.download = "your-download-name-here.html";
  a.hidden = true;
  document.body.appendChild(a);
  a.innerHTML = "something random - nobody will see this, it doesn't matter what you put here";
  a.click();
}

and your file will save.

Awesomeness01
  • 2,321
  • 1
  • 18
  • 17
9

The canonical answer, from the W3C File API Standard:

User agents should provide an API exposed to script that exposes the features above. The user is notified by UI anytime interaction with the file system takes place, giving the user full ability to cancel or abort the transaction. The user is notified of any file selections, and can cancel these. No invocations to these APIs occur silently without user intervention.

Basically, because of security settings, any time you download a file, the browser will make sure the user actually wants to save the file. Browsers don't really differentiate JavaScript on your computer and JavaScript from a web server. The only difference is how the browser accesses the file, so storing the page locally will not make a difference.

Workarounds: However, you could just store the innerHTML of the <div> in a cookie. When the user gets back, you can load it back from the cookie. Although it isn't exactly saving the file to the user's computer, it should have the same effect as overwriting the file. When the user gets back, they will see what they entered the last time. The disadvantage is that, if the user clears their website data, their information will be lost. Since ignoring a user's request to clear local storage is also a security problem, there really is no way around it.

However, you could also do the following:

  • Use a Java applet
  • Use some other kind of applet
  • Create a desktop (non-Web based) application
  • Just remember to save the file when you clear your website data. You can create an alert that pops up and reminds you, or even opens the save window for you, when you exit the page.

Using cookies: You can use JavaScript cookies on a local page. Just put this in a file and open it in your browser:

<!DOCTYPE html>
<html>

<head>
</head>

<body>
  <p id="timesVisited"></p>
  <script type="text/javascript">
    var timesVisited = parseInt(document.cookie.split("=")[1]);
    if (isNaN(timesVisited)) timesVisited = 0;
    timesVisited++;
    document.cookie = "timesVisited=" + timesVisited;
    document.getElementById("timesVisited").innerHTML = "You ran this snippet " + timesVisited + " times.";
  </script>
</body>

</html>
James Westman
  • 2,680
  • 1
  • 15
  • 20
  • Ok, thanks, that's what I thought (impossible because of security reasons). If it was possible, I would have made for personal use a simple local notepad in HTML : http://gget.it/59b78rl1/NFBF33C.HTML (of course with some more features, thanks to Javascript). Since it's impossible to SAVE in one click, this project is simply impossible :( – Basj Nov 30 '14 at 21:53
  • @Basj You could still achieve the same effect with cookies, though. You would just have to be careful not to clear its web data, or you would lose the notes. There may also be a size limit, but it probably isn't much of a problem for a simple notepad. – James Westman Nov 30 '14 at 21:57
  • @Basj You could also use local storage. (much more space available) – James Westman Nov 30 '14 at 22:01
  • Up to now, I use `localStorage` indeed. But the fact of loosing all my data, and having to remember to export the notepad (with some Download file feature) if I clean the history makes it practically uninteresting and useless... unfortunately... – Basj Nov 30 '14 at 22:06
  • @Basj There are plenty of other options, I will add them to my answer. – James Westman Nov 30 '14 at 22:09
  • Yes @kittycat3141, I want to add my own specific functionality, see my project here http://bigpicture.bi/demo . It is fully working as an online-service (with server side language of course, etc.), but I wanted to try to release it as well as a 100% offline tool : that's why I would need to be able to "SAVE" locally. Any idea ? – Basj Nov 30 '14 at 22:43
  • @Basj If you want to create an offline tool as well, you might want to consider a Java app or something. Plus, this way you can put images and other resources in the same file. ZIP files and stuff are a bit messy for users to deal with. – James Westman Nov 30 '14 at 22:48
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/65917/discussion-between-basj-and-kittycat3141). – Basj Nov 30 '14 at 22:48
  • you can't use cookies or localStorage from a local html page viewed in any common (and unmodified) browser... – dandavis Dec 06 '14 at 00:01
  • @Basj I added a canonical answer (so no, it definitely is not possible with pure JavaScript) – James Westman Dec 06 '14 at 14:36
  • OK OK, i take back my comment, things have changed recently. Just goes to show that web "knowledge" is transient and fading. It pays to keep up and i try, but this update slipped past my attention's goalie. tested in FF and CH, and it works on current versions of both. i am not crazy: https://bugzilla.mozilla.org/show_bug.cgi?id=507361 – dandavis Dec 06 '14 at 23:27
8

Chromium's File System Access API (introduced in 2019)

There's a relatively new, non-standard File System Access API (not to be confused with the earlier File and Directory Entries API or the File System API). It looks like it was introduced in 2019/2020 in Chromium/Chrome, and doesn't have support in Firefox or Safari.

When using this API, a locally opened page can open/save other local files and use the files' data in the page. It does require initial permission to save, but while the user is on the page, subsequent saves of specific files do so 'silently'. A user can also grant permission to a specific directory, in which subsequent reads and writes to that directory don't require approval. Approval is needed again after the user closes all the tabs to the web page and reopens the page.

You can read more about this newish API at https://web.dev/file-system-access/. It's meant to be used to make more powerful web applications.

A few things to note about it:

  • By default, it requires a secure context to run. Running it on https, localhost, or through file:// should work.

  • You can get a file handle from dragging and dropping a file by using DataTransferItem.getAsFileSystemHandle

  • Initially reading or saving a file requires user approval and can only be initiated via a user interaction. After that, subsequent reads and saves don't need approval, until the site is opened again.

    enter image description here

  • Handles to files can be saved in the page (so if you were editing local file '/path/to/file.txt', and reload the page, it would be able to have a reference to the file). They can't seemingly be stringified, so are stored through something like IndexedDB (see this answer for more info). Using stored handles to read/write requires user interaction and user approval.

Here are some simple examples. They don't seem to run in a cross-domain iframe, so you probably need to save them as an html file and open them up in Chrome/Chromium.

Opening and Saving, with Drag and Drop (no external libraries):

<body>
<div><button id="open">Open</button><button id="save">Save</button></div>
<textarea id="editor" rows=10 cols=40></textarea>
<script>
let openButton = document.getElementById('open');
let saveButton = document.getElementById('save');
let editor = document.getElementById('editor');
let fileHandle;
async function openFile() {
  try {
    [fileHandle] = await window.showOpenFilePicker();
    await restoreFromFile(fileHandle);
  } catch (e) {
    // might be user canceled
  }
}
async function restoreFromFile() {
  let file = await fileHandle.getFile();
  let text = await file.text();
  editor.value = text;
}
async function saveFile() {
  var saveValue = editor.value;
  if (!fileHandle) {
    try {
      fileHandle = await window.showSaveFilePicker();
    } catch (e) {
      // might be user canceled
    }
  }
  if (!fileHandle || !await verifyPermissions(fileHandle)) {
    return;
  }
  let writableStream = await fileHandle.createWritable();
  await writableStream.write(saveValue);
  await writableStream.close();
}

async function verifyPermissions(handle) {
  if (await handle.queryPermission({ mode: 'readwrite' }) === 'granted') {
    return true;
  }
  if (await handle.requestPermission({ mode: 'readwrite' }) === 'granted') {
    return true;
  }
  return false;
}
document.body.addEventListener('dragover', function (e) {
  e.preventDefault();
});
document.body.addEventListener('drop', async function (e) {
  e.preventDefault();
  for (const item of e.dataTransfer.items) {
    if (item.kind === 'file') {
      let entry = await item.getAsFileSystemHandle();
      if (entry.kind === 'file') {
        fileHandle = entry;
        restoreFromFile();
      } else if (entry.kind === 'directory') {
        // handle directory
      }
    }
  }
});
openButton.addEventListener('click', openFile);
saveButton.addEventListener('click', saveFile);
</script>
</body>

Storing and Retrieving a File Handle using idb-keyval:

Storing file handles can be tricky, since they can't be unstringified, though apparently they can be used with IndexedDB and mostly with history.state. For this example we'll use idb-keyval to access IndexedDB to store a file handle. To see it work, open or save a file, and then reload the page and press the 'Restore' button. This example uses some code from https://stackoverflow.com/a/65938910/.

<body>
<script src="https://unpkg.com/idb-keyval@6.1.0/dist/umd.js"></script>
<div><button id="restore" style="display:none">Restore</button><button id="open">Open</button><button id="save">Save</button></div>
<textarea id="editor" rows=10 cols=40></textarea>
<script>
let restoreButton = document.getElementById('restore');
let openButton = document.getElementById('open');
let saveButton = document.getElementById('save');
let editor = document.getElementById('editor');
let fileHandle;
async function openFile() {
  try {
    [fileHandle] = await window.showOpenFilePicker();
    await restoreFromFile(fileHandle);
  } catch (e) {
    // might be user canceled
  }
}
async function restoreFromFile() {
  let file = await fileHandle.getFile();
  let text = await file.text();
  await idbKeyval.set('file', fileHandle);
  editor.value = text;  
  restoreButton.style.display = 'none';
}
async function saveFile() {
  var saveValue = editor.value;
  if (!fileHandle) {
    try {
      fileHandle = await window.showSaveFilePicker();
      await idbKeyval.set('file', fileHandle);
    } catch (e) {
      // might be user canceled
    }
  }
  if (!fileHandle || !await verifyPermissions(fileHandle)) {
    return;
  }
  let writableStream = await fileHandle.createWritable();
  await writableStream.write(saveValue);
  await writableStream.close();
  restoreButton.style.display = 'none';
}

async function verifyPermissions(handle) {
  if (await handle.queryPermission({ mode: 'readwrite' }) === 'granted') {
    return true;
  }
  if (await handle.requestPermission({ mode: 'readwrite' }) === 'granted') {
    return true;
  }
  return false;
}
async function init() {
  var previousFileHandle = await idbKeyval.get('file');
  if (previousFileHandle) {
    restoreButton.style.display = 'inline-block';
    restoreButton.addEventListener('click', async function (e) {
      if (await verifyPermissions(previousFileHandle)) {
        fileHandle = previousFileHandle;
        await restoreFromFile();
      }
    });
  }
  document.body.addEventListener('dragover', function (e) {
    e.preventDefault();
  });
  document.body.addEventListener('drop', async function (e) {
    e.preventDefault();
    for (const item of e.dataTransfer.items) {
      console.log(item);
      if (item.kind === 'file') {
        let entry = await item.getAsFileSystemHandle();
        if (entry.kind === 'file') {
          fileHandle = entry;
          restoreFromFile();
        } else if (entry.kind === 'directory') {
          // handle directory
        }
      }
    }
  });
  openButton.addEventListener('click', openFile);
  saveButton.addEventListener('click', saveFile);
}
init();
</script>
</body>

Additional Notes

Firefox and Safari support seems to be unlikely, at least in the near term. See https://github.com/mozilla/standards-positions/issues/154 and https://lists.webkit.org/pipermail/webkit-dev/2020-August/031362.html

Steve
  • 10,435
  • 15
  • 21
  • 1
    Wonderful solution @Steve! Last thing, would you know if it's possible to open a file with a drag and drop onto the browser window? This would make it a totally perfect solution to create in-browser text editors, etc. – Basj Feb 28 '22 at 10:16
  • 2
    @Basj, it should be possible via [`DataTransferItem.getAsFileSystemHandle`](https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/getAsFileSystemHandle) (and also mentioned [here](https://web.dev/file-system-access/#drag-and-drop-integration)). I'll update my answer when I have a chance to test it out. – Steve Feb 28 '22 at 15:59
3

Yes, it's possible.

In your example, you are already using ContentEditable and most of tutorials for that attribute have some sort of localStrorage example, ie. http://www.html5tuts.co.uk/demos/localstorage/

On page load, script should check localStorage for data and if true, populate element. Any changes in content could be saved in localStorage when clicking save button (or automatically, in linked example, using blur and focus). Additionally you can use this snippet to check weather user is online or offline and based on state modify your logic:

// check if online/offline
// http://www.kirupa.com/html5/check_if_internet_connection_exists_in_javascript.htm
function doesConnectionExist() {
    var xhr = new XMLHttpRequest();
    var file = "http://www.yoursite.com/somefile.png";
    var randomNum = Math.round(Math.random() * 10000);

    xhr.open('HEAD', file + "?rand=" + randomNum, false);

    try {
        xhr.send();

        if (xhr.status >= 200 && xhr.status < 304) {
            return true;
        } else {
            return false;
        }
    } catch (e) {
        return false;
    }
}

EDIT: More advance version of localStorage is Mozilla localForage which allows storing other types of data besides strings.

Teo Dragovic
  • 3,438
  • 20
  • 34
  • I already tested with `localStorage` but modifications are lost when you clear browser history... So it's not a real solution for the purpose of doing a HTML/JS notepad that I can run locally without any server (no apache, no php). – Basj Dec 06 '14 at 16:21
  • 1
    @Basj Ok, I now see your comments on kittycat3141 answer. As explained there, any browser storage technique (cookies, localStorage or appcache) will work offline but won't persis if user decides to clear cache and browsing data. Maybe you should consider integrating 3rd party storage service into your app and use it to store data files? Both Dropbox (https://www.dropbox.com/developers/dropins ) and Google Drive (https://developers.google.com/drive/web/) have pretty nice API you could use. – Teo Dragovic Dec 06 '14 at 17:08
3

You could save files, and make it persistent using the FileSystem-API and webkit. You would have to use a chrome browser and it is not a standards technology but I think it does exactly what you want it to do. Here is a great tutorial to show how to do just that http://www.noupe.com/design/html5-filesystem-api-create-files-store-locally-using-javascript-webkit.html

And to show that its on topic it starts off showing you how to make the file save persistent...

window.webkitRequestFileSystem(window.PERSISTENT , 1024*1024, SaveDatFileBro);
RadleyMith
  • 1,313
  • 11
  • 22
  • 1
    With this technique and API, can `file:///D:/test/index.html` opened in Chrome write to `file:///D:/test/test.txt`? – Basj Feb 24 '22 at 09:40
1

Convert your HTML content to a data uri string, and set as href attribute of an anchor element. Don't forget to specify a filename as download attribute.

Here's a simple example:

<a>click to download</a>
<script>
    var anchor = document.querySelector('a');
    anchor.setAttribute('download', 'example.html');
    anchor.setAttribute('href', 'data:text/html;charset=UTF-8,<p>asdf</p>');
</script>

Just try it in your browser, no server required.

Leo
  • 13,428
  • 5
  • 43
  • 61
  • 1
    Thanks @LeoDeng, but I want to be able to silently save, not propose a Download dialog box in which the user has to choose where to download. Any other idea? – Basj Nov 30 '14 at 09:19
  • That depends on user's browser settings. If you are going to bypass it, so far as I know it's not possible. – Leo Nov 30 '14 at 10:51
  • 1
    Just solved a minor problem with encoding, so it is worth mentioning here. just before html in data url, add `escape('\xEF\xBB\xBF')`, so it really becomes UTF-8, and language specific characters (Turkish in my case) will display ok. – Erdogan Kurtur Aug 27 '15 at 08:36
1

Have a look into this :) Download File Using Javascript/jQuery there should be everything you need. If you still need help or it's not the solution you need, tell me ;)

Community
  • 1
  • 1
Tom-Oliver Heidel
  • 1,011
  • 9
  • 25
  • Thanks, but it's not exactly what I need. I was looking for something *without* having a Download dialog box being displayed. For the specific reason, see my last comments on kittycat3141's answer. – Basj Nov 30 '14 at 22:46
1

Yes, it is possible. Proof by example:

TiddlyFox: allows modification of local files via an add-on. (source code) (extension page):

TiddlyFox is an extension for Mozilla Firefox that enables TiddlyWiki to save changes directly to the file system.

Todo.html: An HTML file that saves edits to itself. Currently, it only works in Internet Explorer and you have to confirm some security dialogs when first opening the file. (source code) (functional demo).

Steps to confirm todo.html actually saves changes to itself locally:

  1. Save todo.html to local harddrive
  2. Open with Internet Explorer. Accept all the security dialogs.
  3. Type command todo add TEST (todo.html emulates the command-line interface of todo.txt-CLI)
  4. Inspect todo.html file for addition of 'TEST'

Caveats: there is no cross-platform method. I'm not sure how much longer these methods will exist. When I first started my todo.html project, there was a jQuery plugin called twFile that allowed cross-browser loading/saving of local files using four different methods (ActiveX, Mozilla XUL, Java applet, Java Live Connect). Except for ActiveX, browsers have disallowed all these methods due to security concerns.

Leftium
  • 16,497
  • 6
  • 64
  • 99
1

If you are fine with your code running outside of the scope of your default browser, and you are fine with windows only support, HTAs meet the silently save without prompts requirement easily.

The below code doesn't use many HTA specific features but it does still use microsoft specific stuff like ActiveXObject("Scripting.FileSystemObject").

<html>

<head>
  <title>Simple Notepad</title>
  <meta http-equiv="X-UA-Compatible" content="IE=9">
  <script>
    document.addEventListener('keydown', function (event) {
      if (event.ctrlKey) {
        if (event.key == 's') {
          var FSo = new ActiveXObject("Scripting.FileSystemObject");
          //see https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/opentextfile-method
          var thisFile = FSo.OpenTextFile(window.location.pathname, 2, true, -1);
          thisFile.Write(document.getElementsByTagName("html")[0].outerHTML);
          thisFile.Close();
          // Comment out the below alert to get truly silent saving.
          alert('Saved Successfully');
          if (event.preventDefault) event.preventDefault();
          return false;
        }
      }
    }, false);
  </script>

</head>

<body contentEditable="true">
  <h1>Press <kbd>CTRL + S</kbd> To Save</h1>
</body>

</html>

It also isn't a very rich editing experience but that can be fixed with some more buttons or keyboard shortcuts I think. Like CTRL + B to embolden selected text. It doesn't have any safety checks as of yet, but binding an event handler to beforeunload should prevent any data loss caused by accidentally closing the program.

HTA's do have other disadvantages too. They don't support ES6 (though transpiling is an option).

Although it is a bit dated, If you're not trying to use modern web features, I think you'll agree that it is very functional and usable.

Edit I forgot to mention, but HTAs have to be saved with the .hta file extension for mshta.exe to be registered as their file type handler. Which is needed so that you can double click it in windows explorer to open it easily.

See also

Introduction to HTML Applications on MSDN

HTML Applications reference on MSDN

  • Just to clarify, this doesn't run in a sandbox/restricted environment like a browser tab, so there should be no prompts about it attempting to use `ActiveXObjects`, `FileSystemObject`, etc. – Gian Singh Sarao Mar 02 '22 at 16:12
  • 1
    HTAs cannot be rendered via MS Edge. They use the MSHTML rendering engine (same one used by IE) and are, therefore, limited to the capabilities of IE 11. The code above, however, will run in IE 5 document mode because it lacks any `DOCTYPE` or `X-UA-Compatible` declaration. [Add appropriate declarations to run in IE 9, 10, or 11 mode](https://stackoverflow.com/a/70358044/15764378). – LesFerch Mar 03 '22 at 01:51
  • But the question is looking for a method to access the local file system _from the sandboxed environment of a web page_. An HTA doesn't do that. It's just another method to write a local app that just happens to use HTML/CSS as it's GUI. – LesFerch Mar 03 '22 at 02:03
  • Yep. Sorry. My Mistake. HTAs cannot use MSEdge (even with registry hacks). I've edited it now. For some reason I thought that mshta.exe allowed choosing the engine you want to use, like how cscript.exe can, where you can use a progId on the command line, or modify the registry to modify the default. – Gian Singh Sarao Mar 05 '22 at 05:03
0

I think it's important to clarify the difference between server and client in this context.

Client/server is a program relationship in which one program (the client) requests a service or resource from another program (the server).

Source: http://searchnetworking.techtarget.com/definition/client-server

I'm not sure you'll find too many advanced applications that don't have at least one server/client relationship. It is somewhat misleading to ask to achieve this without any server, because any time your program speaks to another program, it is a client/server relationship, with the requester being the client and the response coming from the server. This is even if you are working locally. When you want to do something outside of the scope of the browser, you need a hook in a server.

Now, that does not mean you can't achieve this without a server-side specific language. For example, this solution uses NodeJS for the server. WinJS has WinJS.xhr, which uses XmlHttpRequest to serve data to the server.

AJAX seeks to offer the same sort of functions. The point here is that whether you have a program or there is some sort of hook pre-built, when you issue a command like "save file" and the file actually gets saved, there is a program on the other side that is parsing it, whether it's a server-side language or something else, meaning you can't possibly have something like this function without a server to receive the request.

Community
  • 1
  • 1
smcjones
  • 5,490
  • 1
  • 23
  • 39
0

Just use https://github.com/firebase/firepadSee it in action
This doesn’t need a server on your computer, it will reach out and save the data remotely.

Kirk Strobeck
  • 17,984
  • 20
  • 75
  • 114
0

Use jsPDF -> https://github.com/MrRio/jsPDF

<div id="content">
     <h3>Hello, this is a H3 tag</h3>
    <p>a pararaph</p>
</div>
<div id="editor"></div>
<button id="cmd">generate PDF</button>

Javascript

  var doc = new jsPDF();
  var specialElementHandlers = {
      '#editor': function (element, renderer) {
          return true;
      }
  };

  $('#cmd').click(function () {
      doc.fromHTML($('#content').html(), 15, 15, {
          'width': 170,
              'elementHandlers': specialElementHandlers
      });
      doc.save('sample-file.pdf');
  });
Brayan Pastor
  • 874
  • 7
  • 12
0

This is an example for those who want to know how to use the localStorage.

<div id="divInput" contenteditable="true" style="height:200px;border: 2px solid blue">
  Content editable - edit me and save!
</div>
<button onclick="onSave()">Save</button>
<button onclick="onLoad()">Load</button>
<script>
  config = {
    localStorageItemName: "demo",
    datetimeFormat: {
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      hour12: false,
      minute: '2-digit',
      second: '2-digit'
    }
  }

  function Now() {
    return new Date().toLocaleString("zh-TW", config.datetimeFormat)
  }

  const errMap = {
    IsEmptyError: new Error('is empty'),
    LengthError: new Error('length = 0')
  }

  /**
   * @param {string} itemName
   * @return {Object}
   * */
  function getLocalStorageItem(itemName) {
    const dbDataString = localStorage.getItem(itemName)
    if (dbDataString === null) {
      throw errMap.IsEmptyError
    }
    const db = JSON.parse(dbDataString)
    if (Object.keys(db).length === 0) {
      throw errMap.LengthError
    }
    return db
  }

  function onSave() {
    const inputValue = document.querySelector(`#divInput`).textContent
    try {
      const db = getLocalStorageItem(config.localStorageItemName)
      db.msg = inputValue
      db.lastModTime = Now()
      localStorage.setItem(config.localStorageItemName, JSON.stringify(db))
      console.log("save OK!")
    } catch (err) {
      switch (err) {
        case errMap.IsEmptyError:
          console.info("new localStorageItemName")
          localStorage.setItem(config.localStorageItemName,
            JSON.stringify({msg: inputValue, createTime: Now()})
          )
          break
        /*
        case ...
          break
        */
        default:
          console.error(err.message)
      }
    }
  }

  function onLoad(e) {
    try {
      const db = getLocalStorageItem(config.localStorageItemName)
      console.log("load")
      document.querySelector(`#divInput`).textContent = db.msg
    } catch (err) {
      return
    }
  }
  (()=>{
    window.onload = () => (
      onLoad()
    )
  })()
</script>

It is written in pure javascript with no dependencies.

enter image description here

Carson
  • 6,105
  • 2
  • 37
  • 45