I include myscript.js in the file http://site1.com/index.html like this:
<script src=http://site2.com/myscript.js></script>
Inside "myscript.js", I want to get access to the URL "http://site2.com/myscript.js". I'd like to have something like this:
function getScriptURL() {
// something here
return s
}
alert(getScriptURL());
Which would alert "http://site2.com/myscript.js" if called from the index.html mentioned above.
From http://feather.elektrum.org/book/src.html:
var scripts = document.getElementsByTagName('script');
var index = scripts.length - 1;
var myScript = scripts[index];
The variable myScript now has the script dom element. You can get the src url by using myScript.src.
Note that this needs to execute as part of the initial evaluation of the script. If you want to not pollute the Javascript namespace you can do something like:
var getScriptURL = (function() {
var scripts = document.getElementsByTagName('script');
var index = scripts.length - 1;
var myScript = scripts[index];
return function() { return myScript.src; };
})();
You can add id attribute to your script tag (even if it is inside a head tag):
<script id="myscripttag" src="http://site2.com/myscript.js"></script>
and then access to its src as follows:
document.getElementById("myscripttag").src
of course id value should be the same for every document that includes your script, but I don't think it is a big inconvenience for you.
Everything except IE supports
document.currentScript
Simple and straightforward solution that work very well :
If it not IE you can use document.currentScript
For IE you can do document.querySelector('script[src*="myscript.js"]')
so :
function getScriptURL(){
var script = document.currentScript || document.querySelector('script[src*="myscript.js"]')
return script.src
}
update
In a module script, you can use:
import.meta.url
as describe in mdn
I wrote a class to find get the path of scripts that works with delayed loading and async script tags.
I had some template files that were relative to my scripts so instead of hard coding them I made created the class to do create the paths automatically. The full source is here on github.
A while ago I had use arguments.callee to try and do something similar but I recently read on the MDN that it is not allowed in strict mode.
function ScriptPath() {
var scriptPath = '';
try {
//Throw an error to generate a stack trace
throw new Error();
}
catch(e) {
//Split the stack trace into each line
var stackLines = e.stack.split('\n');
var callerIndex = 0;
//Now walk though each line until we find a path reference
for(var i in stackLines){
if(!stackLines[i].match(/http[s]?:\/\//)) continue;
//We skipped all the lines with out an http so we now have a script reference
//This one is the class constructor, the next is the getScriptPath() call
//The one after that is the user code requesting the path info (so offset by 2)
callerIndex = Number(i) + 2;
break;
}
//Now parse the string for each section we want to return
pathParts = stackLines[callerIndex].match(/((http[s]?:\/\/.+\/)([^\/]+\.js)):/);
}
this.fullPath = function() {
return pathParts[1];
};
this.path = function() {
return pathParts[2];
};
this.file = function() {
return pathParts[3];
};
this.fileNoExt = function() {
var parts = this.file().split('.');
parts.length = parts.length != 1 ? parts.length - 1 : 1;
return parts.join('.');
};
}
if you have a chance to use jQuery, the code would look like this:
$('script[src$="/myscript.js"]').attr('src');
Following code lets you find the script element with given name
var scripts = document.getElementsByTagName( 'script' );
var len = scripts.length
for(var i =0; i < len; i++) {
if(scripts[i].src.search("<your JS file name") > 0 && scripts[i].src.lastIndexOf("/") >= 0) {
absoluteAddr = scripts[i].src.substring(0, scripts[i].src.lastIndexOf("/") + 1);
break;
}
}
document.currentScript.src
will return the URL of the current Script URL.
Note: If you have loaded the script with type Module then use
import.meta.url
for more import.meta & currentScript.src
Some necromancy, but here's a function that tries a few methods
function getScriptPath (hint) {
if ( typeof document === "object" &&
typeof document.currentScript === 'object' &&
document.currentScript && // null detect
typeof document.currentScript.src==='string' &&
document.currentScript.src.length > 0) {
return document.currentScript.src;
}
let here = new Error();
if (!here.stack) {
try { throw here;} catch (e) {here=e;}
}
if (here.stack) {
const stacklines = here.stack.split('\n');
console.log("parsing:",stacklines);
let result,ok=false;
stacklines.some(function(line){
if (ok) {
const httpSplit=line.split(':/');
const linetext = httpSplit.length===1?line.split(':')[0]:httpSplit[0]+':/'+( httpSplit.slice(1).join(':/').split(':')[0]);
const chop = linetext.split('at ');
if (chop.length>1) {
result = chop[1];
if ( result[0]!=='<') {
console.log("selected script from stack line:",line);
return true;
}
result=undefined;
}
return false;
}
ok = line.indexOf("getScriptPath")>0;
return false;
});
return result;
}
if ( hint && typeof document === "object") {
const script = document.querySelector('script[src="'+hint+'"]');
return script && script.src && script.src.length && script.src;
}
}
console.log("this script is at:",getScriptPath ())
Can't you use location.href or location.host and then append the script name?
Related
This is my code (it's a bookmarklet)
javascript:(function(){
a=document.createElement('script');
a.setAttribute('src','//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js');
document.body.appendChild(a);
data='[["#txtapplicantlname","agrawal","text"],["#txtapplicantfname","aayush","text"],["#txtfather","Ranjan","text"],["#txtmother","Neelam","text"],["#txtPincode","452010","text"],["#txtPhone","2147483647","text"],["#txtEmail","aayush#mail.com","text"]]';
for(a=$.parseJSON(data),b=a.length-1;0<=b;b--){
c=a[b];
if (c[2] == 'text') {
console.log(c);
$(c[0]).val(c[1]);
}
}
})();
It used to work fine until I inserted the if statement, then it broke. The console doesn't give me any errors and I have googled a lot for javascript string comparison errors and found nothing useful.
I tried to use equals and compareTo and ended up with console errors and nothing working.
Uncaught TypeError: Cannot call method 'equals' of undefined fillform.php:1
Uncaught TypeError: Cannot call method 'compareTo' of undefined
Help is highly appreciated.
Note: The variables are named like that for a reason, it being that it was initially compiled with Google closure compiler and the if statement is being edited in.
There are several things wrong with this code; string comparison is not one of them.
1) You aren't waiting for the asynchronously loaded script to complete. This code should pretty much always fail because $.parseJSON() isn't available. In fact, once I fixed that problem, this code works fine for me:
(function(){
a=document.createElement('script');
a.setAttribute('src','//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js');
var afterJqueryLoad = function() {
data='[["#txtapplicantlname","agrawal","text"],["#txtapplicantfname","aayush","text"],["#txtfather","Ranjan","text"],["#txtmother","Neelam","text"],["#txtPincode","452010","text"],["#txtPhone","2147483647","text"],["#txtEmail","aayush#mail.com","text"]]';
for(a=$.parseJSON(data),b=a.length-1;0<=b;b--){
c=a[b];
if (c[2] == 'text') {
console.log(c);
$(c[0]).val(c[1]);
}
}
};
var jqueryReady = false;
a.onreadystatechange= function () {
if((this.readyState == 'complete' || this.readyState == 'loaded') && !jqueryReady) {
jqueryReady = true;
afterJqueryLoad();
}
};
a.onload = function() {
if(!jqueryReady) {
jqueryReady = true;
afterJqueryLoad();
}
};
document.body.appendChild(a);
})();
2) Use better var names (a, b, and c are not good var names).
3) Use var to scope vars correctly. Right now your code is shadowing globals and stomping on vars even within the same scope; the a var, for example, would stomp on your script elem var. (You should still change the var names per (2) but using var is not optional; you must always do this to scope vars correctly.)
4) Use spaces to help readability; your for line is unnecessarily difficult to read having no spaces.
All together now:
(function(){
var jqueryScriptElem = document.createElement('script');
jqueryScriptElem.setAttribute('src', '//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js');
var afterJqueryLoad = function() {
var data = '[["#txtapplicantlname","agrawal","text"],["#txtapplicantfname","aayush","text"],["#txtfather","Ranjan","text"],["#txtmother","Neelam","text"],["#txtPincode","452010","text"],["#txtPhone","2147483647","text"],["#txtEmail","aayush#mail.com","text"]]',
dataParsed = $.parseJSON(data);
for(var dataItemIndex = dataParsed.length - 1; 0 <= dataItemIndex; dataItemIndex--) {
var dataItem = dataParsed[dataItemIndex];
if (dataItem[2] == 'text') {
console.log(dataItem);
$(dataItem[0]).val(dataItem[1]);
}
}
};
var jqueryReady = false;
jqueryScriptElem.onreadystatechange = function () {
if((this.readyState == 'complete' || this.readyState == 'loaded') && !jqueryReady) {
jqueryReady = true;
afterJqueryLoad();
}
};
jqueryScriptElem.onload = function() {
if(!jqueryReady) {
jqueryReady = true;
afterJqueryLoad();
}
};
document.body.appendChild(jqueryScriptElem);
})();
<head>
<link rel="stylesheet" type="text/css" href="mystyle.css">
<script src="myscript.js"></script>
</head>
I'd like to have a function that checks if a resource is included in the head
checkIfHeaderHas('myscript.js'); // would return true
checkIfHeaderHas('mystyle.css'); // would return true
checkIfHeaderHas('mybla.css'); // would return false
But I'm wondering how I can go about searching the head for a filename? (in the 'src' if it's javascript, or in the 'href' if it's a css)
I made a small function that does what you want. It loops through all <link> and <script> elements until it finds a script with that name. If it doesn't, it returns false.
function checkIfIncluded(file) {
var links = document.getElementsByTagName("link");
for(var i = 0; i < links.length; i++) {
if (links[i].href.substr(-file.length) == file)
return true;
}
var scripts = document.getElementsByTagName("script");
for(var i = 0; i < scripts.length; i++) {
if (scripts[i].src.substr(-file.length) == file)
return true;
}
return false;
}
console.log(checkIfIncluded("mystyle.css"));
console.log(checkIfIncluded("myscript.js"));
console.log(checkIfIncluded("mybla.css"));
Live example
Note that this will not only find resources in the <head>, but in thew whole document. If you really need to look inside the head, tell me and we'll figure something else.
If you're using jQuery you could maybe do something like:
var checkIfHeaderHas = function(fileName) {
// Start with CSS.
$.each($("header link"), function() {
if ($(this).attr("href").toLowerCase() === fileName.toLowerCase())
return true;
});
// Then JavaScript.
$.each($("header script"), function() {
if ($(this).attr("src").toLowerCase() === fileName.toLowerCase())
return true;
});
// Default response.
return false;
}
Apologies for anything that's not quite right. I'm knocking this out from my phone, and didn't have time to test.
Using the .length in jquery would work for this. (Not tested!)
Just check to see if a element exists with the href attribute set to your CSS file's URL:
if (!$("link[href='/path/to.css']").length){
alert('not loaded');
}else{
alert('loaded!');
}
Here is a working (tested) solution:
function checkIfHeaderHas(name) {
const checks = [
{attr: 'href', items: $("head link")},
{attr: 'src', items: $("head script")}
// add any other if needed...
];
let res = false;
if (typeof name === 'string') {
const lc = name.toLowerCase();
for (let i = checks.length - 1; i >= 0; i--) {
$.each(checks[i].items, function () {
if (($(this).attr(checks[i].attr) || '').toLowerCase() === lc) {
res = true;
return false; // exit the loop as soon as found
}
})
}
}
return res;
}
How to write this JavaScript code without eval?
var typeOfString = eval("typeof " + that.modules[modName].varName);
if (typeOfString !== "undefined") {
doSomething();
}
The point is that the name of the var that I want to check for is in a string.
Maybe it is simple but I don't know how.
Edit: Thank you for the very interesting answers so far. I will follow your suggestions and integrate this into my code and do some testing and report. Could take a while.
Edit2: I had another look at the could and maybe itis better I show you a bigger picture. I am greatful for the experts to explain so beautiful, it is better with more code:
MYNAMESPACE.Loader = ( function() {
function C() {
this.modules = {};
this.required = {};
this.waitCount = 0;
this.appendUrl = '';
this.docHead = document.getElementsByTagName('head')[0];
}
function insert() {
var that = this;
//insert all script tags to the head now!
//loop over all modules:
for (var modName in this.required) {
if(this.required.hasOwnProperty(modName)){
if (this.required[modName] === 'required') {
this.required[modName] = 'loading';
this.waitCount = this.waitCount + 1;
this.insertModule(modName);
}
}
}
//now poll until everything is loaded or
//until timout
this.intervalId = 0;
var checkFunction = function() {
if (that.waitCount === 0) {
clearInterval(that.intervalId);
that.onSuccess();
return;
}
for (var modName in that.required) {
if(that.required.hasOwnProperty(modName)){
if (that.required[modName] === 'loading') {
var typeOfString = eval("typeof " + that.modules[modName].varName);
if (typeOfString !== "undefined") {
//module is loaded!
that.required[modName] = 'ok';
that.waitCount = that.waitCount - 1;
if (that.waitCount === 0) {
clearInterval(that.intervalId);
that.onSuccess();
return;
}
}
}
}
}
};
//execute the function twice a second to check if all is loaded:
this.intervalId = setInterval(checkFunction, 500);
//further execution will be in checkFunction,
//so nothing left to do here
}
C.prototype.insert = insert;
//there are more functions here...
return C;
}());
var myLoader = new MYNAMESPACE.Loader();
//some more lines here...
myLoader.insert();
Edit3:
I am planning to put this in the global namespace in variable MYNAMESPACE.loadCheck, for simplicity, so the result would be, combining from the different answers and comments:
if (MYNAMESPACE.loadCheck.modules[modName].varName in window) {
doSomething();
}
Of course I will have to update the Loader class where ever "varName" is mentioned.
in JS every variable is a property, if you have no idea whose property it is, it's a window property, so I suppose, in your case, this could work:
var typeOFString = typeof window[that.modules[modName].varName]
if (typeOFString !== "undefined") {
doSomething();
}
Since you are only testing for the existence of the item, you can use in rather than typeof.
So for global variables as per ZJR's answer, you can look for them on the window object:
if (that.modules[modName].varName in window) {
...
}
If you need to look for local variables there's no way to do that without eval. But this would be a sign of a serious misdesign further up the line.
I have a function that strips the youtube id off a url. I then want to use this function 10 time per page (in the wordpress loop).
The function works great when I feed it the url within my function script tags, but when I start a new set of script tags within the loop, it does not work.
I need to know how I can use my function without declaring it all first.
So this is the code I have in the header:
<script type="text/javascript">
$(document).ready(function() {
var getList = function(url, gkey){
var returned = null;
if (url.indexOf("?") != -1){
var list = url.split("?")[1].split("&"),
gets = [];
for (var ind in list){
var kv = list[ind].split("=");
if (kv.length>0)
gets[kv[0]] = kv[1];
}
returned = gets;
if (typeof gkey != "undefined")
if (typeof gets[gkey] != "undefined")
returned = gets[gkey];
}
return returned;
};
// THIS WORKS
alert(getList('http://www.youtube.com/watch?v=dm4J5dAUnR4', "v"));
});
But when I try use this somewhere else on the page, it doesnt work.
<script type="text/javascript">
$(document).ready(function() {
alert(getList('http://www.youtube.com/watch?v=dm4J5dAUnR4', "v"));
};
</script>
Firebug gives me getList is not defined which makes sense, because its not. Am I able to 'globally' declare this function?
You have two options, add it to the window object to make it global:
window.getList = function(url, gkey){
// etc...
}
or move it from inside the document ready event handler into the global scope:
$(document).ready(function() {
alert(getList('http://www.youtube.com/watch?v=dm4J5dAUnR4', "v"));
});
var getList = function(url, gkey){
var returned = null;
if (url.indexOf("?") != -1){
var list = url.split("?")[1].split("&"),
gets = [];
for (var ind in list){
var kv = list[ind].split("=");
if (kv.length>0)
gets[kv[0]] = kv[1];
}
returned = gets;
if (typeof gkey != "undefined")
if (typeof gets[gkey] != "undefined")
returned = gets[gkey];
}
return returned;
};
You might also want to read this question about using var functionName = function () {} vs function functionName() {}, and this article about variable scope.
Yet another option is to hang the function off the jQuery object itself. That way you avoid polluting the global name space any further:
jQuery.getlist = function getlist(url, gkey) {
// ...
}
Then you can get at it with "$.getlist(url, key)"
declare getList() outside the ready() function..
var getList = function(url, gkey){
var returned = null;
if (url.indexOf("?") !=
....
....
...
};
Now the getList will work anywhere in the code:
$(document).ready( function() {
alert(getList('http://www.youtube.com/watch?v=dm4J5dAUnR4', "v"));
});
The problem was, scope of the getList(.) function.
You can simply add your function in the $.fn variable:
(function ($) {
$.fn.getList = function() {
// ...
};
}(jQuery));
Example usage:
$().getList();
This is what you would typically do while creating a Basic Plugin for jQuery.
Just define it as a regular function at the top of your script:
<script type="text/javascript">
function getlist(url, gkey){
...
}
</script>
To declare it as a global function, just get rid of all the jQuery specific bits. Something like this:
function getList(url, gkey) {
var returned = null;
if (url.indexOf("?") != -1){
var list = url.split("?")[1].split("&"), gets = [];
for (var ind in list){
var kv = list[ind].split("=");
if (kv.length>0) {
gets[kv[0]] = kv[1];
}
}
returned = gets;
if (typeof gkey != "undefined") {
if (typeof gets[gkey] != "undefined") {
returned = gets[gkey];
}
}
return returned;
}
And then you should be able to call it from anywhere.
I need to detect and eval the Javascript code contained in a string.
The following code works, but it only evaluates the first <script>...</script> it founds.
function executeJs(html) {
var scriptFragment = "<script(.+?)>(.+?)<\/script>";
match = new RegExp(scriptFragment, "im");
var matches = html.match(match);
if (matches.length >= 2) {
eval(matches[2]);
}
}
I wonder if there is a method that allows me to iterate and execute all Javascript fragments.
The reason it only takes the first one is because you're missing the g flag. Try this:
function executeJs(html) {
var scriptFragment = '<script(.*?)>(.+?)<\/script>';
var re = new RegExp(scriptFragment, 'gim'), match;
while ((match = re.exec(html)) != null) {
eval(match[2]);
}
}
executeJs('<script>alert("hello")</script>abc<script>alert("world")</script>');
Here is some code that does the same thing in a slightly different way. You can pass the string to the function and it will eval all the script tags and return the cleaned source(without script). There is also a slight difference in the way IE handles it, that is handled in the code as well, you may adapt it to your requirements. Also, the evaluated code has the global context. Hope it helps.
function parseScript(_source)
{
var source = _source;
var scripts = new Array();
// Strip out tags
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);
// Add to scripts array
scripts.push(source.substring(s_e+1, e));
// Strip from source
source = source.substring(0, s) + source.substring(e_e+1);
}
// Loop through every script collected and eval it
for(var i=0; i<scripts.length; i++)
{
try
{
//eval(scripts[i]);
if(window.execScript)
{
window.execScript(scripts[i]); // IE
}
else
{
window.setTimeout(scripts[i],0); // Changed this from eval() to setTimeout() to get it in Global scope
}
}
catch(ex)
{
// do what you want here when a script fails
alert("Javascript Handler failed interpretation. Even I am wondering why(?)");
}
}
// Return the cleaned source
return source;
}
Blixt should be right...
You may also take a look at prototype's String.evalScripts function.
http://api.prototypejs.org/language/string.html#evalscripts-instance_method