Background:
I want to make an "app" that uses JavaScript/HTML only and can be opened by a browser directly from the filesystem. This app must be able to read data from another file. I'll then use JS to parse it and render pages. As a simplified example, imagine I have a CSV file (download here):
Mark Rodgers,[email protected],Accounting
[...]
Melissa Jones,[email protected],CEO
I want to be able to read the file using JS and use data in it to generate my page.
What I've accomplished so far:
Demo (right-click -> "Save As" to save HTML to your computer). It's also available on jsfiddle in semi-broken fashion (layout is broken, but it should still be functionally correct).
Simply drag and drop the CSV text file into the drag and drop box, or select the text file using the file menu, and JavaScript will read, parse the file and populate the table.
This relies on the FileReader API; most of the heavy lifting is done by this function:
function handleFileSelect(evt) {
evt.stopPropagation();
evt.preventDefault();
var files = evt.target.files || evt.dataTransfer.files; // FileList object.
var file = files[0];
// this creates the FileReader and reads stuff as text
var fr = new FileReader();
fr.onload = parse;
fr.readAsText(file);
// this is the function that actually parses the file
// and populates the table
function parse()
{
var table = document.getElementById('emps');
var employees = fr.result.split('\n'); var c = 0;
for (var i in employees)
{
var employee = employees[i].split(',');
if (employee.length == 3)
{
var row = document.createElement('tr');
row.innerHTML = "<td>" + employee.join("</td><td>") + "</td>";
table.appendChild(row);
c++;
}
}
document.getElementById('result').innerHTML = '<span>Added ' + c + ' employees from file: ' + file.name + '</span>';
}
}
This is almost OK, but it inconveniences the user into manually loading a file. Ideally it should be able to load it automatically, but for security reasons no browser will allow that... yet.
Solution Requirements:
Must work offline; ie: it can't rely on any online service. This also includes HTTP servers running on the local machine. The idea is to have this run on any computer with just a browser installed.
Must work when the page is opened using the file:///
protocol (ie: a HTML page on the hard drive).
Should not rely on third party add ons (eg: Flash, Java, shudders ActiveX). I'm pretty sure these probably wouldn't work anyways if the page is in file:///
It must be able to accept arbitrary data. This rules out loading a file in a well-behaved format that's ready for consumption like JSON.
If it works on either (ideally both) Firefox or Chrome it's fine. It's also OK to rely on experimental APIs
I know what the file name is beforehand, so it could be coded in the HTML itself. Any solution that enables me to read a file from disk is fine, it doesn't have to use the FileReader API.
So if there's a clever hack to load a file into a page that's fine too (maybe load it into an invisible iframe and have JS retrieve the contents); that's OK too.
Here is the code I used for Firefox, which is not portable, but works:
As OP commented, enablePrivilege()
has been deprecated, this should be considered usable. But as my Firefox using previous profile still work with my code, so I dig a little into the prefs.js
(as about:config
is hiding these settings,) And here is the settings you need you get it work.
user_pref("capability.principal.codebase.p0.granted", "UniversalXPConnect");
user_pref("capability.principal.codebase.p0.id", "file://"); // path to the html file.
user_pref("capability.principal.codebase.p0.subjectName", "");
And here goes the code:
var File = function(file) {
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var ios = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
if (!File.baseURI) {
File.baseURI = ios.newURI(location.href.substring(0, location.href.lastIndexOf('/')+1), null, null);
File.baseFolder = File.baseURI.QueryInterface(Components.interfaces.nsIFileURL).file.path;
}
var URL = ios.newURI(file, null, File.baseURI);
this.fptr = URL.QueryInterface(Components.interfaces.nsIFileURL).file;
}
File.prototype = {
write: function(data) {
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
.createInstance(Components.interfaces.nsIFileOutputStream);
foStream.init(this.fptr, 0x02 | 0x08 | 0x20, 0666, 0);
var converter = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
.createInstance(Components.interfaces.nsIConverterOutputStream);
converter.init(foStream, null, 0, 0);
converter.writeString(data);
converter.close();
},
read: function() {
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
var cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
.createInstance(Components.interfaces.nsIConverterInputStream);
fstream.init(this.fptr, -1, 0, 0);
cstream.init(fstream, null, 0, 0);
var data = "";
// let (str = {}) { // use this only when using javascript 1.8
var str = {};
cstream.readString(0xffffffff, str);
data = str.value;
// }
cstream.close();
return data;
}
};