I have a CGI application that had been working correctly for a long time, but recently broke, apparently because of a change in rules for where js scripts get loaded from. I'm using the solution from this answer to load js code conditionally from an external source. My version of the code looks like this:
function load_external_js(s) {
// usage: load_external_js({src:"http://foo.com/bar.js",
// element:"head",type:"text/javascript"});
// ... defaults (the values shown above) are provided for element and type
// http://stackoverflow.com/a/15521523/1142217
// src can be a url or a filename
var js = document.createElement("script");
js.src = s.src;
js.type = (typeof s.type === 'undefined') ? 'text/javascript' : s.type;
var element = (typeof s.element === 'undefined') ? 'head' : s.element;
var e = document.getElementsByTagName(element)[0];
e.appendChild(js);
// BUG -- no error handling if src doesn't exist
}
The issue I'm running into seems to be related to how either the browser or the server resolves relative file paths in this context. What I was doing was this:
load_external_js({src:"foo.js"});
This used to load foo.js from the same directory where the calling js script lived, which was something like /var/www/html/js/3.0.4. But recently this behavior has changed, and the file is searched for in the directory /usr/lib/cgi-bin/myapp, presumably because the html is generated by a CGI script. I can hardcode the directory like this:
load_external_js({src:"/js/3.0.4/mathjax_config.js"});
But this is ugly, and I would have to have some mechanism for setting the version number. Is there some way in pure js to do this so that the script is loaded from the same directory as the one in which the calling script lives? Googling has turned up lots of answers involving node.js or jquery, but I'm not using those.
It's difficult to test this without replicating the setup you're working with, but in my experience this should work well:
var load_external_js = (function () {
var base = document.currentScript.src;
var a = document.createElement('a');
a.href = base.split('/').slice(0, -1).join('/') + '/';
var resolve = function (src) {
var a = this.cloneNode();
a.href += src;
return a.href;
}.bind(a);
return function (s) {
// usage: load_external_js({src:'http://foo.com/bar.js',
// element:'head',type:'text/javascript'});
// ... defaults (the values shown above) are provided for element and type
// http://stackoverflow.com/a/15521523/1142217
// src can be a url or a filename
var js = document.createElement('script');
js.src = resolve(s.src); // relative to <script> this is closure is in
js.type = (typeof s.type === 'undefined') ? 'text/javascript' : s.type;
var element = (typeof s.element === 'undefined') ? 'head' : s.element;
var e = document.getElementsByTagName(element)[0];
e.appendChild(js);
};
})();
If you have access to html markup too, try to use <base> element or simply use document.baseURI and construct full path yourself.
browser: Chrome
environment: grails app localhost
I'm running a grails app on local host (which i know there's an issue with pdf.js and local file system) and instead of using a file: url which i know would fail i'm passing in a typed javascript array and it's still failing. To be correct it's not telling me anything but "Warning: Setting up fake worker." and then it does nothing.
this.base64ToBinary = function(dataURI) {
var BASE64_MARKER = ';base64,';
var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
var base64 = dataURI.substring(base64Index);
var raw = window.atob(base64);
var rawLength = raw.length;
var array = new Uint8Array(new ArrayBuffer(rawLength));
for(i = 0; i < rawLength; i++) {
array[i] = raw.charCodeAt(i);
}
return array;
};
PDFJS.disableWorker = true; // due to CORS
// I convert some base64 data to binary data here which comes back correctly
var data = utilities.base64ToBinary(result);
PDFJS.getDocument(data).then(function (pdf) {
//nothing console logs or reaches here
console.log(pdf);
}).catch(function(error){
//no error message is logged either
console.log("Error occurred", error);
});
I'm wondering if I just don't have it set up correctly? Can I use this library purely on the client side by just including pdf.js or do I need to include viewer.js too? and also i noticed compatibility file... the set up isn't very clear and this example works FIDDLE and mine doesn't and I'm not understanding the difference. Also if I use the url supplied in that example it also says the same thing.
I get to answer my own question:
the documentation isn't clear at all. If you don't define PDFJS.workerSrc to point to the correct pdf.worker.js file than in pdf.js it tries to figure out what the correct src path is to the file and load it.
Their method however is pretty sketchy for doing this:
if (!PDFJS.workerSrc && typeof document !== 'undefined') {
// workerSrc is not set -- using last script url to define default location
PDFJS.workerSrc = (function () {
'use strict';
var scriptTagContainer = document.body ||
document.getElementsByTagName('head')[0];
var pdfjsSrc = scriptTagContainer.lastChild.src;
return pdfjsSrc && pdfjsSrc.replace(/\.js$/i, '.worker.js');
})();
}
They only grab the last script tag in the head and assume that that is the right src to load the file instead of searching all the script tags for the src that contains "pdf.js" and using that as the correct one.
Instead they should just make it clear and require that you do in fact point PDFJS.workerSrc = "(your path)/pdf.worker.js"
Here is the short answer : define PDFJS.workerSrc at the begining of your code.
PDFJS.workerSrc = "(your path)/pdf.worker.js"
see the exemple on the documentation : https://mozilla.github.io/pdf.js/examples/#interactive-examples
In my HTML file I have linked to the JS with:
src="myscript.js?config=true"
Can my JS directly read the value of this var like this?
alert (config);
This does not work, and the FireFox Error Console says "config is not defined". How do I read the vars passed via the src attribute in the JS file? Is it this simple?
<script>
var config=true;
</script>
<script src="myscript.js"></script>
You can't pass variables to JS the way you tried. SCRIPT tag does not create a Window object (which has a query string), and it is not server side code.
Yes, you can, but you need to know the exact script file name in the script :
var libFileName = 'myscript.js',
scripts = document.head.getElementsByTagName("script"),
i, j, src, parts, basePath, options = {};
for (i = 0; i < scripts.length; i++) {
src = scripts[i].src;
if (src.indexOf(libFileName) != -1) {
parts = src.split('?');
basePath = parts[0].replace(libFileName, '');
if (parts[1]) {
var opt = parts[1].split('&');
for (j = opt.length-1; j >= 0; --j) {
var pair = opt[j].split('=');
options[pair[0]] = pair[1];
}
}
break;
}
}
You have now an 'options' variable which has the arguments passed. I didn't test it, I changed it a little from http://code.google.com/p/canvas-text/source/browse/trunk/canvas.text.js where it works.
You might have seen this done, but really the JS file is being preprocessed server side using PHP or some other language first. The server side code will print/echo the javascript with the variables set. I've seen a scripted ad service do this before, and it made me look into seeing if it can be done with plain ol' js, but it can't.
You need to use Javascript to find the src attribute of the script and parse the variables after the '?'. Using the Prototype.js framework, it looks something like this:
var js = /myscript\.js(\?.*)?$/; // regex to match .js
var jsfile = $$('head script[src]').findAll(function(s) {
return s.src.match(js);
}).each(function(s) {
var path = s.src.replace(js, ''),
includes = s.src.match(/\?.*([a-z,]*)/);
config = (includes ? includes[1].split('=');
alert(config[1]); // should alert "true" ??
});
My Javascript/RegEx skills are rusty, but that's the general idea. Ripped straight from the scriptaculous.js file!
Your script can however locate its own script node and examine the src attribute and extract whatever information you like.
var scripts = document.getElementsByTagName ('script');
for (var s, i = scripts.length; i && (s = scripts[--i]);) {
if ((s = s.getAttribute ('src')) && (s = s.match (/^(.*)myscript.js(\?\s*(.+))?\s*/))) {
alert ("Parameter string : '" + s[3] + "'");
break;
}
}
Whether or not this SHOULD be done, is a fair question, but if you want to do it, http://feather.elektrum.org/book/src.html really shows how. Assuming your browser blocks when rendering script tags (currently true, but may not be future proof), the script in question is always the last script on the page up to that point.
Then using some framework and plugin like jQuery and http://plugins.jquery.com/project/parseQuery this becomes pretty trivial. Surprised there's not a plugin for it yet.
Somewhat related is John Resig's degrading script tags, but that runs code AFTER the external script, not as part of the initialization: http://ejohn.org/blog/degrading-script-tags/
Credits: Passing parameters to JavaScript files , Passing parameters to JavaScript files
Using global variables is not a so clean or safe solution, instead you can use the data-X attributes, it is cleaner and safer:
<script type="text/javascript" data-parameter_1="value_1" ... src="/js/myfile.js"></script>
From myfile.js you can access the data parameters, for instance with jQuery:
var parameter1 = $('script[src*="myfile.js"]').data('parameter_1');
Obviously "myfile.is" and "parameter_1" have to match in the 2 sources ;)
You can do that with a single line code:
new URL($('script').filter((a, b, c) => b.src.includes('myScript.js'))[0].src).searchParams.get("config")
It's simpler if you pass arguments without names, just like function calls.
In HTML:
<script src="abc.js" data-args="a,b"></script>
Then, in JavaScript:
const args=document.currentScript.dataset.args.split(',');
Now args contains the array ['a','b'].
I have this function:
function parseScript(_source) {
var source = _source;
var scripts = new Array();
while(source.indexOf("<script") > -1 || source.indexOf("</script") > -1) {
var s = source.indexOf("<script");
var s_e = source.indexOf(">", s);
var e = source.indexOf("</script", s);
var e_e = source.indexOf(">", e);
scripts.push(source.substring(s_e+1, e));
source = source.substring(0, s) + source.substring(e_e+1);
}
for(var i=0; i<scripts.length; i++) {
try {
eval(scripts[i]);
}
catch(ex) {
}
}
return source;
}
It parses and execute Javascript wonderfully except the when in <script type='text/javascript' src='scripts/gen_validatorv31.js'></script> the src file never gets executed.
The parser can only evaluate inline scripts in the file you have opened. To evaluate external scripts you would have to find their sources, probably using something like:
var scripts = source.match(/<script[^>]*src=[^>]*>/g);
if (scripts) {
for (var i = 0; i < scripts.length; i++) {
src = scripts[i].match(/src=("([^"]*)"|'([^']*)')/);
src = src[2] || src[3];
if (src) {
addScriptTag(src);
}
}
}
else console.log('no external scripts found');
where addScriptTag is described in this answer. addScriptTag adds the script to the head, if possible. It will need to be adapted if you need to add script to the body.
However... why do this? It is slow and messy to parse an entire HTML/Javascript page to get the scripts; for instance you might end up loading the same scripts twice or loading two scripts that don't work well together. Also the scripts may not work if inserted at a different point in the head or body. With AJAX you should only be loading the particular elements you need. Usually this means loading bits of data or HTML to be added to the page. If you have long scripts that are not needed at the beginning but might be needed later then it might be justified to dynamically add new scripts to the page. But in many cases probably better to load all needed scripts at the beginning. And if you really need to switch pages completely then isn't it better to use the old-fashioned method of linking to another page?
Is it possible from a Javascript code to find out "where" it came from?
I needed this to provide scripts that could run folder-agnostic, e.g.:
http://web1/service-a/script.js
http://web2/some-folder/service-b/script.js
And they're linked in:
http://web1/page1.html
http://web2/page2.html
The file script.js is identical in both locations but I would like them to be able to find out where it originates from so it can call the right web service methods (script.js from service-a should call service-a.method while script.js that is served from service-b should call service-b.method)
Is there a way to find out where script.js came from without using any server-side code?
Well, it's kind of a hack, you could grab all the <script> tags in the document, look at which one has the file name of the file, and do the logic from there.
document.getElementsByTagName('script'); is pretty much all you need. The rest is basic JS.
Even more interesting than looping through all of the returned elements (although that's probably safest), is that we can simply only look at the last element returned by the call above, as Javascript guarantees that must be the <script> tag we're in at the time of it being parsed (with the exception of deferred scripts). So this code:
var all_scripts = document.getElementsByTagName('script');
var current_script = scripts[all_scripts.length-1];
alert(current_script.src);
Will alert the source of the script tag used to include the current Javascript file.
You can analyze source of the html where script.js is included for tag and retrieve path of the script.js from there. Include next function in script.js and use it to retrieve the path.
function getPath() {
var path = null;
var scriptTags = document.getElementsByTagName("script");
for (var i = 0; i < scriptTags.length; i++) {
var scriptTagSrc = scriptTags.item(i).src;
if (scriptTagSrc && scriptTagSrc.indexOf("script.js") !== -1) {
path = scriptTagSrc;
break;
}
}
return path;
}