-1

Premise

  1. I am not much expert in this, but I have superficial familiarity with HTML, CSS, vanilla JS.
  2. I am creating for let say for myself an application in HTML 5, CSS 3, vanilla JS ECMA script 6, so with NO use of frameworks as jQuery or else: just vanilla JS, and I also don't care about older browser that are not HTML 5 ES6 CSS 3 compliant, like IE.
  3. The application runs as simple HTML/CSS/JS file on the local computer only: so no remote server is involved but it is a portable app I store and use on my computer or on a pen drive or I send to other people as my family to show things to them.
  4. I know about limits imposed to HTML/JS to open files on its own and about the need for user interaction by the use of input type="file" html element... but nevertheless might be there is another way I am not familiar with yet, if I'm lucky...

Situation

  1. I have an already working html file, let's call it Manager.html. It contains a table that is populated with a list of files, listed as:
  • File 1.db
  • File 2.db
  • File 3.db
  • and much much more of them...

Also, each file is a URL, for instance:

<a href="file 1.db">file 1.db</a>

If I have to, I can change their extension to *.json, *.csv, *.tsv, *.db, *.txt, or any other that could work as long as it is a data related extension for files containing text only, no problem at all for that.

  1. I have an already working html file that acts as a viewer let's call it Viewer.html: it loads data from those other db files by using at the moment a canonical input type="file". It loads the content of the chosen database and displays it into the table handled by Manager.html and the relative js that handles the loading process and the process to populate the table.

  2. The db file used is a custom kind of "csv"-like-file (coma separated value format file) customized by myself: it contains basically text only content and uses normal \CR\LF to separate records and the pipe symbol "|" (no double quotes) instead of commas, for separating fields. For instance:

    1. text of field 1|text of field 2|text of field 3|text of field n
    2. text of field 1|text of field 2|text of field 3|text of field n
    3. text of field 1|text of field 2|text of field 3|text of field n
    4. and more records ...
  3. The content of the db files is utf8 text and it is not limited in quantity, so:

  • a db file could be of any size: from few Bytes or KB and few records or even hundreds or thousands of KB and hundreds or even thousands of records: so can be present any number of records.
  • a record could be any length, with a fixed number of fields, corresponding to the number of fields of the target html table in Viewer.html
  • a field can contain text of any length as well

At the moment everything works well per se with the input type="file" implementation, but I want to implement a different feature and improve my user experience because at the moment I have to:

  1. open Viewer.html
  2. here I have to click on the input type="file" control to open the "open file window"
  3. from the "open file window" I have to select the File n.db file I wish to load in Viewer.html to open it and populate the table into Viewer.html.

All this is of course super tedious.


So what I want is to be able to:

Of course directly from Manager.html, that holds the table with the main list of all File n.db I want just to:

  • click on the url of the File n.db file I want to open, listed inside the table in the file Manager.html. And with that just one click I want the javascript to:
  1. open the Viewer.html
  2. pass to Viewer.html as parameter the File n.db file to be processed
  3. Viewer.html opens it by its own, process it, and shows its content into its table.

In other words I'm looking for a function that can do something similar to:

Pseudo code:

open(Viewer.html, File n.db)

will also work as well something similar to:

<database src="MyDBsubDir/MyDBfile.db"></database>

Would be great if it works with standard db files as csv, tsv, json, db, txt, and so on, containing just Unicode text in it.


It is relevant/Crucial

  1. The use of the Manager.html to list all File n.db files to click on for opening.
  2. The use of just one common Viewer.html, so it will be easier to maintain and in case of need could be updated just once for debugging or to implement new features in case of need.
  3. Handle an unlimited number of File n.db files (or with other extensions)

Questions

  1. Is it possible for the user (typically myself or family or friends) which is showing Manager.html click on a link of the file and pass its href value as parameter to the other file Viewer.html to be processed and shown here?
  2. If Yes, how do I implement a function that does something like that in vanilla JS?

Basically the function activated on mouse click on the link will have to get the text content of the file n.db file under the href attribute of the same clicked link, and will "inject" / "fuse" on the fly such a content with the Viewer.html itself that will provide the correct formatting of it as html table so can be shown on the browser as a normal html page instead of just text.


Notice that

As already said: the solution I'm looking for, if any, must be compatible with only HTML 5, ES6 compliant browsers (so I really don't care about IE and similar others, which are dead for me). I repeat: all has to work on a "local" machine (Windows, Linux, MAC, Android... and more), no server of any kind has to be involved.

The ideal solution would be a fetch() like function if it worked on local files, but unfortunately it doesn't, as far as I know.

Also it doesn't work either to compile by JavaScript a input type=file with the file because of course it is not allowed for security reasons.


Ideal behavior

IMHO the best way to go to solve this limit once and for all without putting in jeopardy the security of the local system, would be to implement in all browsers a standard behavior that asks to the user the authorization to access to app directory and its sub-dirs, similarly as when the browsers ask the user for authorization to use the microphone. This will allow the user to decide if a local app is authorized to access to its own directory. This authorization has to be a per-session authorization: so it is given every time the app is opened into the browsers.

willy wonka
  • 1,440
  • 1
  • 18
  • 31
  • 3
    "I know about limits imposed to html/js to open files on its own and about the need for user interaction by the use of input type="file" html element... but nevertheless might be there is another way I am not familiar with yet, if I'm lucky..." — No. The limits you know about are the limits. – Quentin Aug 01 '22 at 15:47
  • A simple way around the problem is to run a light web server locally, and make requests through that. – Pointy Aug 01 '22 at 15:49
  • @Quentin, at the moment I think you're right: but hope never dies... Thanks anyway. – willy wonka Aug 01 '22 at 16:03
  • @willywonka some time ago there was a popular tool designed to do exactly what you're trying to do. Back then, there were browser APIs (in at least some browsers) that did allow file access, but that was a while ago. [Link to Wikipedia (Tiddly Wiki)](https://en.wikipedia.org/wiki/TiddlyWiki) – Pointy Aug 01 '22 at 16:09
  • @Pointy, thank you for the suggestion but as I specified into the Original question I might want the app to be portable as I could use it not only on my own local machine but even with family or friends. So it is not possible to setup and use a web server every time: too complex solution. Also if other people want to use that app and asks me to send it to them to I can't ask to them to implement complex things as to install configure and run a web server on their own machines: the solution must be a click and run solution, KISS (Keep It Simple Stupid) like approach. Sorry, but thanks anyway. – willy wonka Aug 07 '22 at 09:10
  • @willywonka TiddlyWiki does not require a web server, but it may require a browser plugin. – Pointy Aug 07 '22 at 11:19
  • @Pointy, about TiddlyWiki / browser plugin: no thanks, too complex and also I already have my app that is almost perfect for my needs: I just need to implement this last feature/solution into my own app to make it work for the specific needs I have. I had a look on TiddlyWiki but it is so far away from my needs. But thank you for your time: I appreciate your attempt to help me. – willy wonka Aug 07 '22 at 11:30
  • 1
    Just out of curiosity: what kind of data is inside of `file N.db`? *"I can change the extension to *.json"* why would you change to JSON something that (not explained in your question) might not be a JSON format at all? – Roko C. Buljan Aug 07 '22 at 11:41
  • @RokoC.Buljan, Hi and thank you for your time. The *file n.db* files contain just *text* data (could be for instance cooking receipts or even other things) in each db file there is a whole category so even hundreds of records: we share them with each other with this "strange" way. We share this way because browsers are universal viewers on all hardware platforms even on tablets: I know there are other ways to do that but we get used to it. – willy wonka Aug 07 '22 at 11:49
  • I don't understand again, what kind of data. Really text? like i.e: `Lorem Ipsum.` and that's all? Why not provide a sample of your data and code? Please read: [mcve] - so we can get a better grasp on what you have - as a minimal clue-code. – Roko C. Buljan Aug 07 '22 at 11:51
  • @RokoC.Buljan I already have indeed: the data into the *.db files are organized as: field 1|field 2|field 3. As simple as that. I can load it using an input type file. but I want something simpler as click and show solution, so the problem is to fetch the *.db file content to send it to the Viewer.html with just one click. At the moment I have to open the viewer and load in here the db file, which is tedious. So what I want is to click on its link and open it into the Viewer.html because the viewer formats the *.db content and puts it into a well organized and filterable - sortable html table – willy wonka Aug 07 '22 at 11:57
  • Why not create a setup.sh file that installs NodeJS? And a start.sh that starts your webserver? You could tell your friends to run those and you're done, no need to explain more than that. The start.sh would simply run `npm start` which runs an `index.js` that spins up a webserver and loads automatically the browser at i.e: localhost:3000 with your app. That app can finally read files from the disk. Or even better, you could create an Electron App: https://www.electronjs.org/ – Roko C. Buljan Aug 07 '22 at 12:03
  • @RokoC.Buljan I would like to implement that behavior because I'm relatively good in computer science but others of my family or friends I share to aren't and they have different kind of hardware so I need an "universal" solution to make them able to use the shared data with just a click and run solution: browsers have an ubiquitary presence in all systems nowadays even in tablets and smartphones: not all of them have a computer with windows to run Word or excel or access, not they want to struggle with complex situations just to make a dinner. – willy wonka Aug 07 '22 at 12:04
  • @RokoC.Buljan I don't want to install a server on my system just to prepare a dinner, and believe me they don't want too. It is just enough to have this app working on the browser. It just needs some fine tuning to optimize the user experience and make it usable in shorter timings. Nevertheless thank you for your time and proposals. – willy wonka Aug 07 '22 at 12:09
  • *"It just needs some fine tuning"*? Fine tuning of what kind? What you're talking about is the tip of the iceberg, and I'm afraid that *"just some fine tuning"* is the part you don't see. – Roko C. Buljan Aug 07 '22 at 12:11
  • @RokoC.Buljan this is why I'm studying: to find a different solution, if any, to improve it. but I suppose it is more difficult than I thought. – willy wonka Aug 07 '22 at 12:13
  • 1
    It's not difficult, it's impossible (the `file:///` way.). Imagine a browser, an app, website, whatever, lying here on my screen and having read/write access to my machine. Read: https://stackoverflow.com/questions/18586921/how-to-launch-html-using-chrome-at-allow-file-access-from-files-mode . If such flaw was discovered it would be a serious vulnerability issue. – Roko C. Buljan Aug 07 '22 at 12:15
  • Well, if all the data are going to be copied to other people's machine anyway, and those are just plain text, why not store them as JS strings and use some variables to refer to them? Or, if they contain no special characters, put them in hidden html elements and then access the contents? – qrsngky Aug 07 '22 at 13:39
  • @qrsngky because there could be a lot of different **file n.db** populated of data and there is no point to hide the data into html elements and then access their content because the data must be shown to me or other users that use the app. Also the **Viewer.html** on the table that shows the data got from the db file has filtering and sorting features already implemented which works more or less as ms excel. – willy wonka Aug 07 '22 at 16:40

2 Answers2

0

There are alternatives.

Key factor #1 is : "If I have to, I can change their extension to *.json, or any other that could work, no problem at all for that.". This matters because you are open to using a file format that browsers are still happy to open locally.

Key factor #2 is : "Of course directly from Manager.html, that holds the table with the main list of all File n.db". This matters because browsers are not and should not be allowed to read your local drive without your intervention, but a list or urls gets us pretty far.

The first solution, and the simplest one from a technical perspective, is to save your database files as html files that already look the way you want them to. You just open them when you click on the link. You basically don't have a viewer anymore since your files are directly viewable.

If the thought of adding markup all throughout your data does not appeal, another possible solution is to define your data as a plain object, and have a little javascript function to render it on the page. Again, no viewer needed since the files render themselves.

Here is a rough example of a database.html

<head>
    <style>
        .hidden-frame {
            display: none;
        }
    </style>
    <script>
        // Your plain object data is here
        const data = [
            ['text of field 1', 'text of field 2', 'text of field 3', 'text of field n'],
            ['text of field 1', 'text of field 2', 'text of field 3', 'text of field n'],
            ['text of field 1', 'text of field 2', 'text of field 3', 'text of field n']
        ];
    </script>
</head>
<body>
    This is the data...
    <div id="viewer"></div>
    <script>
        //Your vanilla javascript render function is here
        document.getElementById('viewer').innerHTML = data.map(r => `<div>${r.join(', ')}</div>`).join('');
    </script>
</body>

If the idea of having a render function in your database is also problematic, you could leverage an iframe and get the data from the database through that and render it in the main html file.

Here is a rough example. The following manager.html has just one hardcoded "db file example" named filedb_example.html instead of a list because I am lazy. Your actual list can be as fancy as you want with the user experience there. You don't need a framework for that. Also, here again, no separate viewer.

<head>
    <style>
        .hidden-frame {
            display: none;
        }
    </style>
    <script>
        // This is the event handler that receives the data from the loaded db file
        const eh = (e) => {
            // The render function is in the main html file now
            document.getElementById('viewer').innerHTML = e.data.map(r => `<div>${r.join(', ')}</div>`).join('');
        }
        // We register the handler to receive messages from the db files when they load
        window.addEventListener('message', eh);
    </script>
</head>
<body>
    This is the manager, and bellow is the data I loaded from the db file.
    <div id="viewer">Nothing loaded yet!</div>
    <iframe class="hidden-frame" src="file:///c:/work/js/filedb_example.html" id="loader"></iframe>
</body>

Your html pseudo databases now look something like this filedb_example.html :

<head>
    <script>
        // This is where the actual data is set.
        const data = [
            ['text of field 1', 'text of field 2', 'text of field 3', 'text of field n'],
            ['text of field 1', 'text of field 2', 'text of field 3', 'text of field n'],
            ['text of field 1', 'text of field 2', 'text of field 3', 'text of field n']
        ];
        // As soon as it is loaded, it sends the data to the top frame
        window.top.postMessage(data, "*");
    </script>
</head>
<body>
    This is a db file and is not meant to be loaded directly :P
</body>

If you want to keep your Manager.html and Viewer.html separate, you can create a dedicated Viewer.html and simply use a query parameter to pass to it the file you want to view like, but you will probably need to url-encode your file path and use front slashes. Example link file:///Viewer.html?file_db=c%3A%2Fwork%2Fjs%2Ffiledb_example.html

A rough viewer would be something like this :

<head>
    <style>
        .hidden-frame {
            display: none;
        }
    </style>
    <script>
        // A function to extract the query parameters
        const params = new Proxy(new URLSearchParams(window.location.search), {
          get: (usp, p) => usp.get(p),
        });        

        // The same rendered
        const eh = (e) => {
            document.getElementById('viewer').innerHTML = e.data.map(r => `<div>${r.join(', ')}</div>`).join('');
        }
        window.addEventListener('message', eh);
    </script>
</head>
<body>
    This is the viewer, and below the data from the file I loaded.
    <div id="viewer"></div>
    <iframe class="hidden-frame" id="loader"></iframe>
    <script>
        // Now that the viewer is loaded, we load the database file
        document.getElementById('loader').src = `file:///${params.file_db}`;
    </script>
</body>

So there you go, options.

M. Gallant
  • 236
  • 2
  • 5
  • Hi, the first code block is wrong: it returns an error **e is not defined**. I think you might want to correct it as **document.getElementById('viewer').innerHTML = data.map(r => `
    ${r.join(', ')}
    `).join('');**
    – willy wonka Aug 15 '22 at 16:02
  • Thank you for you attempt: unfortunately you are right when you say that the thought of adding markup all throughout my data does not appeal me... This is true if it is not database related markup. Even adding JS to each of all the database files does not sound a good and clean solution. Unfortunately this is not what I'm looking for. – willy wonka Aug 15 '22 at 17:06
  • Nevertheless, even if they are not ideal these answers gave me some hints and maybe I can find a way myself... so thank you so much for them. – willy wonka Aug 16 '22 at 20:24
-1

You can check the FileSystemDirectoryEntry API in javascript it allows your app to access a local directory here is a link to the mozilla documentation page https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryEntry

Here is an example on how to use this there are a lot of drawbacks as this is stil experimental features and chrome does not let you access the api if you open the file from the file explorer instead of serving it, to make this work you will have to launch chrome with this flag --allow-file-access-from-files or serve the file with something like live server

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="explorer">
    </div>
    <button id="open">Sync</button>
    <div id="content"></div>
    <script>
        const $ = (s, e = document) => e.querySelector(s);
        const e = (e) => document.createElement(e);
        const getDb = async () => {
            const dir = await window.showDirectoryPicker();
            let it = dir.entries();
            let entry = await it.next();
            while (entry.done === false) {
                const [fileName, fileHandle] = entry.value;
                const fileInAppDir = await rootAppDir.getFileHandle(fileName, { "create": true });

                const fileData = await fileHandle.getFile();
                const writable = await fileInAppDir.createWritable();

                await writable.write(fileData);
                await writable.close();

                $('#explorer').appendChild(e('div')).innerText = fileName;
                entry = await it.next();
            }
        }

        const getAppFiles = async () => {
            for await (const handle of rootAppDir.values()) {
                $('#explorer').appendChild(e('div')).innerText = handle.name;
            }
        }

        const showFileContents = async (fileName) => {
            const fileHandle = await rootAppDir.getFileHandle(fileName);
            const file = await fileHandle.getFile();
            const reader = new FileReader();
            reader.onload = function (event) {
                $('#content').innerText = event.target.result;
            };
            reader.readAsText(file);
        }

        let rootAppDir;

        const main = async () => {
            rootAppDir = await navigator.storage.getDirectory();
            await getAppFiles();

            $('#explorer').addEventListener('click', async (e) => {
                const fileName = e.target.innerText;
                await showFileContents(fileName);
            });

            $('#open').addEventListener('click', getDb);
        }

        main();
    </script>
</body>

</html>

cesium133
  • 67
  • 1
  • 4
  • @willywonka when the standards prevent people from accessing the file system, what else do you expect? – qrsngky Aug 07 '22 at 13:32
  • Thank you for your time: Isn't that approach a Non-standard and Deprecated? Nevertheless how do I apply your proposal to my question? Can you make a very simple example? Thank you in advance. – willy wonka Aug 08 '22 at 12:15
  • **@qrsngky** right, but the problem is that if it is Experimental | non standard | deprecated | or similar things, such feature doesn't work on all browser and also can be dropped at any moment... than I must start again to adapt my app to new things hopping they are standard... which is ugly too. Also being non standard I must implement different code to target different browsers as webkit- moz- and ms- js vendor prefixes that make me to repeat code and make my source awful and less maintainable... which is also bad... I in fact asked ES6 standard into my OP – – willy wonka Aug 15 '22 at 14:07
  • Thank you for your time. In this a approach I am worried about browser support of **showDirectoryPicker()** and related functions, which are not standard: at the moment it seems that about 70% of the browsers don't support this possibility. Nevertheless if they did this way would be doable IMHO. I have also implemented something similar to experiment with it just in Chrome, but I won't use it in the final app: at the moment too much browsers don't support it as said in https://developer.mozilla.org/en-US/docs/Web/API/Window/showDirectoryPicker#browser_compatibility is an experimental tech. – willy wonka Aug 15 '22 at 15:09
  • So I'm afraid is needed another and more standard approach. Thank you any way. – willy wonka Aug 15 '22 at 15:09