I have an index.html file that displays names:
<div id="names">
<div>John</div>
<div>Henry</div>
<div>Jason</div>
</div>
I also have a Javascript file that gets the names from somewhere (internet/database/wherever):
class NamesDatabase {
getNames() {
let names = [];
// fill the names list
return names;
}
}
I'd like to use the NamesDatabase.js in a index.js file, in this way:
document.addEventListener("DOMContentLoaded", function(){
let database = new NamesDatabase();
let names = database.getNames();
let namesDiv = document.getElementById("names");
names.forEach((name) => {
// add a new child to namesDiv
});
}
I tried using
<script type="module" src="NamesDatabase.js"></script>
to import it inside index.html, however I got the access blocked cors error.
What is the preferred way of going about this problem, without using a webserver such as Express?
Premise
I am not much expert in this, but I have superficial familiarity with HTML, CSS, vanilla JS.
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.
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.
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
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:
file 1.db
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.
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.
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:
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
and more records ...
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:
open Viewer.html
here I have to click on the input type="file" control to open the "open file window"
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:
open the Viewer.html
pass to Viewer.html as parameter the File n.db file to be processed
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
The use of the Manager.html to list all File n.db files to click on for opening.
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.
Handle an unlimited number of File n.db files (or with other extensions)
Questions
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?
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.
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.
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>
I got problem loading dynamic JS in my script, so here the case, I have a plan to build android app with local webview something like webView.loadUrl("file:///android_asset/filename.html");
Everything work fine since its only html file, but the problem came when I need to read local js contain array data that need to read by other js file.
To make it clear,
I have 100++ data_1.js data_2.js etc who contain something like
data = [{
no1:"xxx",
no1:"xxx",
no3:"xxx",
..
}]
Those data will be read by one of my js script and display it on html file, basically I only need 1 data each time the page open, so its like when I open file://.../folder/file.html?no=1 it will only need to read data_1.js, file://.../folder/file.html?no=2 it will only need to read data_2.js
This is what I try to do.
#1
Using getScript("assets/data/data_"+number+".js");
This work when we use local server, when I access via localhost/folder/file.html?no=1 its work, but when I access file://..../folder/file.html?no=1 it not load because cors block
#2
Using ajax, result same with #1
#3
Add all data_x.js directly in file.html
<script src="folder/data_1.js"></script>
<script src="folder/data_2.js"></script>
<script src="folder/data_3.js"></script>
Its work when we access file://..../folder/file.html?no=1 but the page load very slow because need to include and load whole data_x.js (more than 100++)
Is there any other way to solve this problem?
Note: I don't want to connect to any server because I want the apps can be access offline so all data will be included inside the apps
You can make use of URLSearchParam in combination with createElement, which is similar to your solution #3 but only loads the file from parameter id.
So you can also make use of: webView.loadUrl("file:///android_asset/filename.html?id=N");
<html>
<body>
<script>
const urlParams = new URLSearchParams(window.location.search),
id = urlParams.get('id');
if (id != null) {
const script = document.createElement("script");
script.src = `folder/data_${id}.js`;
script.onload = function() {
// do something ...?
}
document.body.appendChild(script);
}
</script>
</body>
</html>
EDIT:
At least at desktop it works fine.
In my project we're using TYPO3. We're getting some data from the backend and they're assigned as follows in the html page itself.
var items = {};
items.item1 = {settings.item1};
items.item2 = {settings.item2};
items.item3 = {settings.item3};
and then the values are being assigned to the buttons. Those values will be sent back to the JS when an action has triggered. The {settings.item*} comes from the TYPO3 backend. What I wanted to know is how can I add the above code block in a separate JS file rather than adding it in the HTML page. When I tried adding it directly, it doesn't work as the {settings.item*} comes from TYPO3
Thanks
You have to pick up your settings from the HTML since this is what TYPO3 will render for you. but you could rather make use of the data-attributes of HTML, e.g.
You could also render the whole {settings} array as a JSON string into a data-attribute and pick that up with your JavaScript.
You can use javascript on html file (templates , partial , layouts )
You need to add javascript code between the
Syntax:
<![CDATA[ javascript code ]]> TYPO3 Code <![CDATA[ javascript code ]]>
<script type="text/javascript">
<![CDATA[
{
var items = {};
items.item1 = ]]>{settings.item1}<![CDATA[;
items.item2 = ]]>{settings.item2}<![CDATA[;
items.item3 = ]]>{settings.item3}<![CDATA[;
]]>
</script>
Thanks
I hope it helps !!
Can you define the context more precisely? Where are the settings defined, inside of TypoScript, Flexform, PHP (Extensionmanager)?
If you have settings defined in TypoScript you can use:
page.inlineSettings {
setting1 = Hello
setting2 = GoOnTop
}
To make them available in JavaScript as:
TYPO3.settings = {"TS":{"setting1":"Hello","setting2":"GoOnTop"}};
See: https://docs.typo3.org/typo3cms/TyposcriptReference/Setup/Page/Index.html#inlinesettings
Perhaps this will be removed in future versions as it's purpose is usage via ExtJS. Also it's not possible to use stdWrap and such.
Another, more flexible, way is using
page.jsInline {
10 = TEXT
10.stdWrap.dataWrap = var pageId = {TSFE:id};
}
This allows you to use full TypoScript like TEXT, dataWrap, etc.
See: https://docs.typo3.org/typo3cms/TyposcriptReference/Setup/Page/Index.html#jsinline
I wouldn't write JavaScript from PHP just for some configuration. I would store them in data-attributes of a DOM element and grab it via JavaScript. Maybe this is also an option for you.
I have a following Kohana setup:
All my files are placed under 'public_html/koh'
My js files are placed under 'public_html/koh/media/js/'
I use html::script helper to include those javascript files which generates me following html code:
<script type="text/javascript" src="/koh/media/js/site.js"></script>
In my js I access one of controllers like 'json/getsomething' (which is http://localhost/koh/json/getsomething).
It works OK as long as I'm staying in top of the controller:
http://localhost/koh/home
When I go to 'http://localhost/koh/home/index' it renders the same page of course but 'json/getsomething' is not accessible from Javascript anymore.
How can I solve this problem?
Include Javascript using absolute path?
Create a variable in js like var fullPath = 'http://localhost/koh/'?
What is the best practice to do it?
Leonti
That's how I did it.
I made a function url_base which would correspond to kohana's url::base and so it would switch around when I moved from localhost and to production.
View template:
<script type="text/javascript">
function url_base() { return "<?php echo url::base();?>"; }
</script>
And then in config.php:
if(IN_PRODUCTION) {
$config['site_domain'] = '/';
}
else {
//if in localhost redirect to localhost/mysite
//instead of just localhost
$config['site_domain'] = '/mysite/';
}
(A bit late but hope still useful) Here's another way I use to better organize my js-serverside vars.
I put somewhere in beginning some basic variables - eg. in "before()" function:
$this->template->appconf = array(
'url_base' => url::base(),
'l' => substr(I18n::$lang, 0, 2),
);
Now I can add whenever I need any extra variable:
$this->template->appconf['page_key'] = 'product_page';
And finally in template this cleaniness:
<script type="text/javascript">
var appconf = <?php echo json_encode($appconf); ?>;
</script>
Use like this:
<script type="text/javascript">
console.log(appconf.url_base); // "/mysite/"
</script>