File loading from a user's operating system has to be triggered by some form of user-event, save for getting a file object from a canvas element (this can be generated directly).
As this excerpt from MDN states:
File objects may be obtained from a FileList object returned as a result of a user selecting files using the element, from a drag and drop operation's DataTransfer object, or from the mozGetAsFile() API on an HTMLCanvasElement.
Allowing a web page the ability to load any file from a user's computer, without the user's intervention or expressed permission, would be a dangerous thing indeed. However, depending on what you can do with your file (or the host environment) you do have some options.
Load file via AJAX/XHTTP
I include source code that I've been using via the file://
protocol, hence the reason for downloading jQuery locally, rather than using any hotlink equivalent. In order to test this you just need to download jQuery and save it in the same directory as this HTML file, and obviously either create a textfile.txt
or change the code to point to another file.
NOTE: The file path should be kept within your web root and accessible from your web server under the same host, unless you are running the code from the file://
protocol locally. If you are running over the file://
protocol, you can load a file from anywhere on your machine as long as your browser has permissions to access said file, and you remain accessing via file://
.
There is one large difference in loading a file this way (or any of the other methods I mention on the page), to the way you are loading a file in your question. Loading a file via a file selector dialog is sourcing the file from the user's own computer; whereas loading a file via AJAX (or via a script/iframe tag) is sourcing it from the server. Whether or not this defeats your objective would be down to what exactly you are using it for. Never-the-less loading text files is best done via this method, below you will find some other secondary methods that do have their benefits, but also a number of downsides.
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Load a text file</title>
<script src="jquery.js"></script>
<script>
jQuery
.ajax({
url: 'textfile.txt',
dataType: 'text'
})
.error(function(){
console.warn('An error occurred whilst loading the file', arguments);
})
.done(function(res){
jQuery.each(res.split(/\r?\n/g), function(i, v){
jQuery('#output').append('<p>' + v + '</p>');
});
})
;
</script>
</head>
<body>
<h1>Fileload</h1>
<div id="output"></div>
</body>
</html>
Load file via script tag
This option requires that you modify your text file to be wrapped by a small bit of JavaScript, this will then allow you to import its content using an ordinary script tag. Unlike AJAX this is not subject to cross-origin blocking i.e. stackoverflow.com can request the file from gamedev.stackexchange.com. This method also has the benefit of having quite a small footprint.
NOTE: AJAX requests can now reliably get around the cross-origin problems when implementing CORS. However this does usually involve having some control of the server-side.
The downsides to this method are that you have to modify your text files, your text files are parsed as JavaScript (meaning certain higher-order characters can upset older parsers), and you are abusing Function.toString()
. If you aren't happy about using comments for a multiline hack you can quite easily modify your text file to implement multiline strings the proper way using string concatenation instead. But once you do that you are getting rather far away from loading anything like a plain text file.
contents of your text file
(typeof loadfile != 'undefined') && loadfile(function(){/*
Place your text file contents here.
We are abusing the JavaScript comment facility
to achieve multiline strings without too much
modification to your original text file.
*/});
script in your main page
<script>
/**
* This function is responsible for receiving the function
* sent by your text file
*/
function loadfile(content){
var text = String(content && content.toString()),
p1 = text.indexOf('/*')+2,
p2 = text.lastIndexOf('*/')
;
console.log(text.substring(p1, p2));
};
</script>
<script src="your-text-file.txt.js"></script>
Load file via an iframe
This option is pretty much what used to be used — if no server-side was available — prior to the prevalence of AJAX. It doesn't really have any benefits over AJAX save for the fact that, if using polling to keep an eye on when the iframe is ready, the code to manage the iframe is smaller and more cross-browser reliable. With AJAX it is generally best to rely on a library (like jQuery) to handle the differences between different environments.
However, that said AJAX gives you a whole lot more ability, and much more interactive feedback as to what has happened when (with better support for events). Relying on polling can be slower/unreliable, but it is your only option with iframes because not all browsers support triggering the correct events, especially when dealing with unexpected data-types.
Here is a script I used to use that I've tidied and updated, just to illustrate the point. This version relies on Object.create
so you will need a polyfill for that if you want to use it with older browsers; but as I've stated a number of times... make use of AJAX instead, if you can, if you can't then perhaps your approach needs a rethink.
/**
* Textfile
*
* Allows the loading of a textfile via an iframe or script tag.
*
* Loading via script tag:
*
* textfile.load({
* type: 'script',
* path: 'textfile.txt.js'
* })
* .fail(function(){
* console.log('an error occured!');
* })
* .done(function(res){
* console.log('file loaded', res);
* })
* ;
*
* loading via iframe:
*
* textfile.load({
* type: 'iframe',
* path: 'textfile.txt'
* })
* .fail(function(){
* console.log('an error occured!');
* })
* .done(function(res){
* console.log('file loaded', res);
* })
* ;
*
* NOTE: When loading via a script tag, your text file must be in the
* following format:
*/
(typeof textfile != 'undefined') && textfile.injected('NAME OF YOUR FILE', function(){/*
This is example text content
you can have as many lines as
you want.
*/});
/**
* Once your text file is wrapped with the above the textfile code
* will be notified that the file has been loaded, and it will also
* allow you to have multiline text more easily in JavaScript.
*
* <NAME OF YOUR FILE> should be replaced with your filename to load
* for example textfile.txt.js
*/
var textfile = (function(){
var tf = {
shared: {},
create: function(config){
return tf.prep.apply(Object.create(tf), arguments);
},
prep: function(config){
this.config = config;
this.tag = !this.config.inline
? document.documentElement
: document.scripts[document.scripts.length-1]
;
this.tid = setTimeout(this.bind(this.timeout), this.config.timeout || 5 * 1000);
this.iid = setInterval(this.bind(this.polling), 100);
this.loader = this.config.type === 'script'
? this.script()
: this.frame()
;
this.loader.src = this.config.path;
!this.config.inline
? this.tag.appendChild(this.loader)
: this.tag.parentNode && this.tag.parentNode.insertBefore(this.loader, null)
;
return this;
},
script: function(element){
if ( element ) { return element; }
element = document.createElement('script');
element.type = 'text/javascript';
return element;
},
frame: function(element){
if ( element ) { return element; }
element = document.createElement('iframe');
element.style.position = 'fixed';
element.style.right = '100%';
element.style.bottom = '100%';
element.style.width = '1em';
element.style.height = '1em';
return element;
},
tidyup: function(){
this.loader && this.loader.parentNode.removeChild(this.loader);
return this;
},
loaded: function(res){
this.trigger('done', res);
this.tidyup();
return this;
},
failed: function(){
this.trigger('fail');
this.tidyup();
return this;
},
on: function(name, listener){
!this.listeners && (this.listeners = {});
!this.listeners[name] && (this.listeners[name] = []);
this.listeners[name].push(listener);
return this;
},
off: function(name, listener){
if ( this.listeners && this.listeners[name] ) {
for ( var a=this.listeners[name], i=a.length-1; i>=0; i-- ) {
if ( a[i] === listener ) {
this.listeners[name].splice(i, 1);
}
}
}
return this;
},
trigger: function(name, data, context){
if ( this.listeners && this.listeners[name] ) {
for ( var i=0, a=this.listeners[name], l=a.length; i<l; i++ ) {
if ( a[i] && a[i].call ) {
a[i].call( context || this, data );
}
}
}
return this;
},
bind: function(method, args, context){
!context && args && !args.length && (context = args);
!context && (context = this);
args && (args = Array.prototype.slice.call(args));
return function(){
return (args
? method.apply(context, args.concat(Array.prototype.slice.call(arguments)))
: method.apply(context, arguments)
);
};
},
fail: function(listener){ return this.on('fail', listener); },
done: function(listener){ return this.on('done', listener); },
timeout: function(){
clearInterval(this.iid);
this.failed();
},
polling: function(){
var obj, text, ex;
if ( this.config.type === 'iframe' ) {
try { text = ((obj=this.loader.contentWindow) && obj.document.documentElement.textContent) ||
((obj=this.loader.contentWindow) && obj.document.documentElement.innerText) ||
((obj=this.loader.contentDocument) && obj.documentElement.textContent) ||
((obj=this.loader.contentDocument) && obj.documentElement.innerText);
} catch (ex) {}
}
else {
text = this.loader.textContent ||
this.loader.text ||
this.loader.innerHTML ||
this.shared.loaded && this.shared.loaded[this.config.path]
;
}
if ( text && text.split ) {
this.loaded(text);
clearInterval(this.tid);
clearInterval(this.iid);
}
},
injected: function(script, content){
var text = String(content && content.toString()),
p1 = text.indexOf('/*')+2,
p2 = text.lastIndexOf('*/')
;
!this.shared.loaded && (this.shared.loaded={});
this.shared.loaded[script] = text.substring(p1, p2);
},
load: function(config){
return config && config.split
? tf.create({ path: config })
: tf.create(config)
;
}
};
return tf;
})();