Is there a simple and reliable way to determine the URL of the currently-executing JavaScript file (inside a web page)?
My only thought on this is to scan the DOM for all the script src attributes to find how the current file was referenced and then figure out the absolute URL by applying it to document.location. Anyone have other ideas, is there some super-easy method I completely overlooked?
UPDATE: Script elements accessed via the DOM already have a src property which contains the full URL. I don't know how ubiquitous/standard that is, but alternatively you can use getAttribute("src") which will return whatever raw attribute value is in the [X]HTML.
Put this in the js file that needs to know it's own url.
Fully Qualified (eg http://www.example.com/js/main.js):
var scriptSource = (function(scripts) {
var scripts = document.getElementsByTagName('script'),
script = scripts[scripts.length - 1];
if (script.getAttribute.length !== undefined) {
return script.src
}
return script.getAttribute('src', -1)
}());
Or
As it appears in source (eg /js/main.js):
var scriptSource = (function() {
var scripts = document.getElementsByTagName('script'),
script = scripts[scripts.length - 1];
if (script.getAttribute.length !== undefined) {
return script.getAttribute('src')
}
return script.getAttribute('src', 2)
}());
See http://www.glennjones.net/Post/809/getAttributehrefbug.htm for explanation of the getAttribute parameter being used (it's an IE bug).
For recent browsers, you can use document.currentScript to get this information.
var mySource = document.currentScript.src;
The upside is that it's more reliable for scripts that are loaded asynchronously. The downside is that it's not, as best I know, universally supported. It should work on Chrome >= 29, FireFox >= 4, Opera >= 16. Like many useful things, it doesn't seem to work in IE.
When I need to get a script path, I check to see if document.currentScript is defined, and, if not, use the method described in the accepted answer.
if (document.currentScript) {
mySource = document.currentScript.src;
} else {
// code omitted for brevity
}
https://developer.mozilla.org/en-US/docs/Web/API/document.currentScript
As it appears in source (e.g. /js/main.js), this is cross-browser:
var scriptSource = (function()
{
var scripts = document.getElementsByTagName('script'),
script = scripts[scripts.length - 1];
//No need to perform the same test we do for the Fully Qualified
return script.getAttribute('src', 2); //this works in all browser even in FF/Chrome/Safari
}());
Fully Qualified (e.g. http://www.example.com/js/main.js):
After some tests it seems hard to get the fully qualified one in a cross-browser way. The solution suggested by Crescent Fresh does not work in IE8 to get the fully qualified, even if it works in IE7
This method work with defer, async and lazy loading
Since you know the filename of your script, and if it will be unique
/* see
* http://stackoverflow.com/questions/984510/what-is-my-script-src-url/984656#984656
* http://www.glennjones.net/Post/809/getAttributehrefbug.htm
*
* iterate all script to find script with right filename
* this work with async and defer (but your script MUST have a unique filemane)
* mozilla support document.currentScript and we use it, if is set
*
* this will not work with local script loaded by jQuery.getScript(),
* since there is no script tag added into the dom. the script is only evaluated in global space.
* http://api.jquery.com/jQuery.getScript/
*
* to fix this odd, you can add a reference in meta ( meta[name=srcipt][content=url] )
* when you load the script
*/
var scriptFilename = 'jquery.plugins.template.js'; // don't forget to set the filename
var scriptUrl = (function() {
if (document.currentScript) { // support defer & async (mozilla only)
return document.currentScript.src;
} else {
var ls,s;
var getSrc = function (ls, attr) {
var i, l = ls.length, nf, s;
for (i = 0; i < l; i++) {
s = null;
if (ls[i].getAttribute.length !== undefined) {
s = ls[i].getAttribute(attr, 2);
}
if (!s) continue; // tag with no src
nf = s;
nf = nf.split('?')[0].split('/').pop(); // get script filename
if (nf === scriptFilename) {
return s;
}
}
};
ls = document.getElementsByTagName('script');
s = getSrc(ls, 'src');
if ( !s ) { // search reference of script loaded by jQuery.getScript() in meta[name=srcipt][content=url]
ls = document.getElementsByTagName('meta');
s = getSrc(ls, 'content');
}
if ( s ) return s;
}
return '';
})();
var scriptPath = scriptUrl.substring(0, scriptUrl.lastIndexOf('/'))+"/";
a jquery plugin template with it:
https://github.com/mkdgs/mkdgs-snippet/blob/master/javascript/jquery.plugins.template.js
note: this will not work with local script loaded by jQuery.getScript(), since there is no script tag added into the dom. the script is only evaluated in global space.
http://api.jquery.com/jQuery.getScript/
to fix it you can do something like:
function loadScript(url,callback) {
if ( $('[src="'+url+'"]').length ) return true; // is already loaded
// make a reference of the loaded script
if ( $('meta[content="'+url+'"]', $("head")).length ) return true; // is already loaded
var meta = document.createElement('meta');
meta.content = url;
meta.name = 'script';
$("head").append(meta);
return $.ajax({
cache: true,
url: u,
dataType: 'script',
async: false,
success : function (script) {
try {
if ( typeof callback == 'function' ) callback();
} catch (error) {
//console.log(error);
}
}
});
}
If this is a strictly client solution, yours sounds pretty good.
If you are writing code on the server, you could probably just populate a div/hidden field/(insert your fave HTML element here) with the fully resolved URL to the script, and pick that up with your javascript on the clientside.
You may want to have a look at https://addons.mozilla.org/en-US/firefox/addon/10345 if you're interested in learning which functions (and thus which file) are executing on a page you don't control.
If you're interested in figuring out which of your scripts is executing, then there are a number of ways. With Firebug you could console.log() the information. Even just putting alert statements in your code (while annoying) can help debug in a low-tech way. You could also raise errors and catch them, then process using properties of the error (see: https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error)
However, why would this be important? If the script is causing errors already then it's easy enough to determine where the error is occurring. If it's not about errors at all, then what's the advantage in knowing which file it comes from?
Related
I need to find out if html document inside iframe contains some occurance of word wysiwyg (purpose: to check if it is wysiwyg editor).
What I have tried:
iframes = $('iframe').filter(
function () {
var result = this.id.match(/wysiwyg/i);
if (!result)
result = this.className.match(/wysiwyg/i);
if (!result)
{
var success = this.innerHTML.match(/wysiwyg/i);
if ( success && success.length )
return this;
}
return result;
});
using JQuery.
The problem here is that innerHTML is empty. I thought, contentDocument could contain innerhtml, but this is not the case. I try to do case insensitive search, where the word wysiwyg can be in the middle of any element. Originally I tried to find a tag with href value or img tag with src value but I found that is too much complicated and the word could be used in other parts of the html document and I would miss it. I don't know where the word could be, it can be anywhere in the iframe -> html document.
Your suggestion?
Note:
Permissions here are not problem, they are granted by Firefox webextentions API - which is not subject of the question.
If permissions are not the problem, you should be able to access the iframe HTML by doing the following:
$('#specificIframe').contents().find('#thingToFind');
jQuery .contents()
You may use .contents() and jQuery( ":contains(text)" ) plus the load event to check if the iframe contains the string.
In order to test if the id contains the string you may refer to attributeContains selector.
$(function () {
$('iframe[id*="wysiwyg"]').on('load', function (e) {
var iframes = $(this).contents().find(':contains("wysiwyg")');
});
});
As guest271314 has mentioned, you are not currently using .contentDocument in your code.
You could change your code as follows:
iframes = $('iframe').filter(function() {
var result = this.id.match(/wysiwyg/i);
if (!result){
result = this.className.match(/wysiwyg/i);
}
if (!result) {
var success = this.contentDocument.querySelector('html').innerHTML.match(/wysiwyg/i);
if ( success && success.length ){
return this;
}
}
return result;
});
From MDN's <iframe> page:
From the DOM iframe element, scripts can get access to the window object of the included HTML page via the contentWindow property. The contentDocument property refers to the document element inside the iframe (this is equivalent to contentWindow.document), but is not supported by Internet Explorer versions before IE8.
However, if this is the same issue you were asking about in a prior, now deleted, question, this will, not solve your actual problem. The actual problem appeared to be that your (nearly identical) code was executing prior to the <iframe> you are looking for being inserted into the DOM by other JavaScript on the page. Your code, of course, can not find it when it is not, yet, in the DOM. Your code in that question was verified to find the <iframe> desired if it existed in the DOM in the state that the DOM was once the page scripts finished setting up the DOM. Prior to that code, ckeditor_new/ckeditor.js, executing, what exists on the page is:
<script src="ckeditor_new/ckeditor.js"></script>
<textarea id="FCKeditor1" name="FCKeditor1" rows="8" cols="60"></textarea>
<script>CKEDITOR.replace( 'FCKeditor1', {customConfig: '/ckeditor_new/config.js'});</script>
The page script hides that <textarea> and inserts a <div> containing the <iframe> in which you are interested (about 15kB of inserted HTML).
You will need to delay looking for the existence of that <iframe> until after that other JavaScript inserts it into the DOM.
While there may be better ways to perform this delay (e.g. watching for the insert, etc.), it could be something as simple as:
setTimeout(findIframeAndDoAllTasksNeedingIt,150);
If still not found, you could retry looking for the <iframe> a limited number of times after additional delays.
I thought this could be solution:
iframes = $('iframe').filter(
function () {
var result = this.id.match(/wysiwyg/i);
if (!result)
result = this.className.match(/wysiwyg/i);
if (!result)
{
if (!this.contentDocument)
return null;
var success = this.contentDocument.head.innerHTML.match(/wysiwyg|editor|editable/i);
if ( success && success.length )
return this;
success = this.contentDocument.body.innerHTML.match(/wysiwyg|editor|editable/i);
if ( success && success.length )
return this;
}
return result;
});
edit: bugfix
I tried improvement which enables to use this code for almost all WYSIWYG editors, except TINYMCE which is kind of strange behaviour. There are found some frames with different id than that one containing "mce". Maybe we will find solution later.
iframes = $('iframe').filter(
function () {
var result = this.id.match(/wysiwyg/i);
if (!result)
result = this.className.match(/editable|wysiwyg|editor/i);
if (!result)
result = this.id.match(/mce/i);
if (!result)
{
if (!this.contentDocument)
return null;
var success = this.contentDocument.head.innerHTML.match(/editable|wysiwyg|editor|tinymce/i);
if ( success && success.length )
return this;
success = this.contentDocument.body.innerHTML.match(/editable|wysiwyg|editor|tinymce/i);
if ( success && success.length )
return this;
}
return result;
});
I love this code (I use it in my program which adds css styles to WYSIWYG editors - quite usable).
I'm using IE Edge's emulator mode to test some work and one of the project I work on requires IE8. The emulator is pretty useful to debug some stuff that the original IE8 is doing a good job at blackboxing. I'm trying to find a way around this bug since Microsoft isn't willing to fix it.
The problem is that IE8 emulator hangs on SVG image load. I'm currently using this SVG fallback library which works great on the real IE8 but I was wondering if there is a way to modify events or object prototypes using Javascript to change the behavior of the browsers before it tries to load SVG images when parsing HTML? Is there such a way to solve this issue or should I just live with this bug? I have this dirty workaround which does the trick but I'm hoping to find a more proactive solution.
var fixMySVG = setInterval(function () {
var elements = document.getElementsByTagName('img');
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
element.src = element.src.replace(/^(.+)(\.svg)(\?.)*$/ig, '$1.' + 'png' + '$3');
}
if (document.readyState == 'complete') {
clearInterval(fixMySVG);
}
}, 100);
There is no error, the image is just stuck in an 'uninitialized' state (so I cannot use the onerror event). I'm also unaware of any onbeforeoload event I could use.
Is using interval the only solution?
Edit
I realize there is no perfect solution but to solve basic <img> and backgroundImage style, using interval seems to do an good job without performance hit. On top of that fall back images seems to load faster. I updated my SVG fallback to use interval instead of using onload events which solve both IE8 emulator and the real IE8.
It's a really odd bug, since there is no older-version emulation mode in Edge, just mobile one and user-agent string emulation, which will just allow you "to debug errors caused by browser sniffing", but in no way it is related to some feature non-support.
Using your fallback is one out of many options but there is no "clean" way to do this. On top of that it will not solve SVG images using <object>, <iframe> or <embded> elements, nor inline <svg> elements.
So this doesn't point directly to your issue, which should be fixed by IE team since it's a bug in their browser, but just for the hack, here is a way to change the src of an image before the fetching of the original one starts.
Disclaimer
Once again, this is a hack and should not be used in any production nor development site maybe just for an edge debugging case like yours and for experimentation but that's all !
Note : this will work in modern browsers, including Edge with IE8 user-string Emulation set, but not in the original IE8.
Before the dump
This code should be called in the <head> of your document, preferably at the top-most, since everything that is called before it will be called twice.
Read the comments.
<script id="replaceSrcBeforeLoading">
// We need to set an id to the script tag
// This way we can avoid executing it in a loop
(function replaceSrcBeforeLoading(oldSrc, newSrc) {
// first stop the loading of the document
if ('stop' in window) window.stop();
// IE didn't implemented window.stop();
else if ('execCommand' in document) document.execCommand("Stop");
// clear the document
document.removeChild(document.documentElement);
// the function to rewrite our actual page from the xhr response
var parseResp = function(resp) {
// create a new HTML doc
var doc = document.implementation.createHTMLDocument(document.title);
// set its innerHTML to the response
doc.documentElement.innerHTML = resp;
// search for the image you want to modify
// you may need to tweak it to search for multiple images, or even other elements
var img = doc.documentElement.querySelector('img[src*="' + oldSrc + '"]');
// change its src
img.src = newSrc;
// remove this script so it's not executed in a loop
var thisScript = doc.getElementById('replaceSrcBeforeLoading');
thisScript.parentNode.removeChild(thisScript);
// clone the fetched document
var clone = doc.documentElement.cloneNode(true);
// append it to the original one
document.appendChild(clone);
// search for all script elements
// we need to create new script element in order to get them executed
var scripts = Array.prototype.slice.call(clone.querySelectorAll('script'));
for (var i = 0; i < scripts.length; i++) {
var old = scripts[i];
var script = document.createElement('script');
if (old.src) {
script.src = old.src;
}
if (old.innerHTML) {
script.innerHTML = old.innerHTML;
}
old.parentNode.replaceChild(script, old);
}
}
// the request to fetch our current doc
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState == 4 && (this.status == 200 || this.status == 0)) {
var resp = this.responseText || this.response;
parseResp(resp);
}
};
xhr.open('GET', location.href);
xhr.send();
})('oldSrc.svg',
'newSrc.svg');
</script>
And a live example which won't work with the IE8 UA string since plnkr.co just doesn't allow this browser on his website :-/
I'm trying to build a browser bookmarklet that lets a user save a quotation (block of text) - and with the press of the bookmarklet - will save the quote, along with a timestamp and URL to their profile inside a web app.
You can see the code I've written below, but running into a few problems.
Wrapping the code nicely to work in a bookmarklet.
Obviously using the $post may be heavy given some pages will require it to be added by the bookmarklet.
Any ideas on how to proceed?
You can see where I'm at currently here http://jsfiddle.net/Rh7zx/1/
(function() {
function getSelectionHtml() {
var html = "";
if (typeof window.getSelection != "undefined") {
var sel = window.getSelection();
if (sel.rangeCount) {
var container = document.createElement("div");
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
html = container.innerHTML;
}
} else if (typeof document.selection != "undefined") {
if (document.selection.type == "Text") {
html = document.selection.createRange().htmlText;
}
}
return html;
}
function saveToBiblio() {
var url = window.location;
var dateSaved = new Date();
var selectedText = getSelectionHtml();
console.log(url + dateSaved + selectedText);
/*
do the $post here
*/
}
)();
1) Wrapping the code nicely to work in a bookmarklet.
A bookmarklet is just JavaScript code encoded into a URL using the javascript: pseudo-protocol. There are several sites and tools that will take your code and turn it into a bookmarklet. The fundamental thing is to make it so that when the code is run, the thing you want to do occurs. Your code currently defines two functions, but doesn't call either of them. You'd want to call the relevant one. It's also generally best to wrap your bookmarklet code in a scoping function so you don't add to the page's global namespace (because of the possibility of conflicts):
(function() {
// Your code here
)();
Some of the bookmarklet makers may have an option to do that for you, but doing it yourself is trivial.
2) Obviously using the $post may not work cross browser.
That will work fine cross-browser, but won't work in pages that don't have jQuery loaded, and loading jQuery into the page is probably overkill. I'd use XMLHttpRequest directly.
With IE7 and IE8, I discovered one could avoid memory leaks in a "single page app" that was doing frequent JSONP calls by adding a script element to the head and simply changing the src attribute. Each time the src attribute was changed, it would immediately load and run the script. This is no longer working in IE9 or IE10. Using JQuery's .ajax() or manually where the previous script node is removed from the head, and a new one added (which works fine in FF and Chrome) causes memory to leak in IE.
Here is the basic code I use to submit the JSONP - Jquery and other libraries seem to leak memory, I'm wondering if I can avoid it at all with ie9 and ie10...
// Some statics used by JSONP calls (below)... uuid is used to prevent getting stale cached results, it forces a new "get" every time by changing the url
Testing123.uuid = 0;
Testing123.head = document.getElementsByTagName('head')[0];
//-----------------------------------------------------
// mainurl is the url we are going to, callbackFuncName is the callback function, parameters must be a string with zero or more parameters already encoded
// formatted as "&parm1=value1&parm2=value2" as it is being tacked onto a GET url...
Testing123.debugJSONP = false; // set to true to see stuff in console
Testing123.initiateJSONP = function (mainurl, callbackFuncName, parameters) {
var url = mainurl + "?callback=" + callbackFuncName + "&uuid=" + (Testing123.uuid++);
var script;
url += parameters; // add optional parameters.
// Now, let's make the JSONP call happen. One way for IE 8 and below, another way for FF, Chrome, etc.
if (Testing123.isIE) {
// ***** NOTE *****
// This tests for ALL ie versions, but ie9 and ie10 will only display one interation...
// If you add && Testing123.browserVersionNumber < 9.0 to the if above, then the iterations will work, but
// memory usage will go up dramatically if run for a while...
// ***** NOTE ******
// For IE, we create the script node just once, and then set its src attribute to run again...
// ***** This seems now to fail in ie9 and ie10
var addToDOM = 0;
script = document.getElementById('JSONP');
if (!script) {
if (Testing123.debugJSONP) Testing123.logToOutput("initiateJSONP with IE: creating script element with id JSONP");
script = document.createElement('script');
script.id = 'JSONP';
script.type = 'text/javascript';
script.charset = 'utf-8';
addToDOM = 1;
}
if (Testing123.debugJSONP) Testing123.logToOutput("initiateJSONP with IE: setting script element's src to " + url);
script.setAttribute("src", url);
//script.src = url;
if (addToDOM) // Only do this the first time we create it...
{
if (Testing123.debugJSONP) Testing123.logToOutput("initiateJSONP with IE: appending script element with id JSONP to head");
Testing123.head.appendChild(script);
}
} else {
//First lets clean up the DOM from the last call, if there was one...
var tmp;
while (tmp = document.getElementById('JSONP')) {
if (Testing123.debugJSONP) Testing123.logToOutput("initiateJSONP non IE: found a JSONP element by id... asking parent to remove it and deleting its properties.");
tmp.parentNode.removeChild(tmp);
// not working in IE 7/8/9
for (var prop in tmp) {
//if (Testing123.debugJSONP) Testing123.logToOutput("initiateJSONP non IE: deleting prop: [" + prop + "] from the element found.");
delete tmp[prop];
}
tmp = null;
}
Does anyone have a solution to this issue? Here is a jsfiddle with a little test application and all the code:
http://jsfiddle.net/bbct/9RqZ6/
Thanks in advance for any advice / insight.
I am working on a javascript that sequentially loads a list of other external javascript.
The code I have so far:
function loadJavascript(url){
var js = document.createElement("script");
js.setAttribute("type", "text/javascript");
js.setAttribute("src", url);
if(typeof js!="undefined"){
document.getElementsByTagName("head")[0].appendChild(js)
}
}
loadJavascript("Jquery.js");
loadJavascript("second.js");
loadJavascript("third.js");
The problem I ran into is that sometimes the other js files loads before the Jquery file completes its loading. This gives me some errors.
Is it possible to make it so that the next JS file is only initiated when the previous file is finished loading.
Thanks in advance
Sure there is, but there's entire libraries written around doing this. Stop reinventing the wheel and use something that already works. Try out yepnope.js or if you're using Modernizr it's already available as Modernizr.load
loadJavascript("Jquery.js");
$(function(){
$.getScript('second.js', function(data, textStatus){
$.getScript('third.js', function(data, textStatus){
console.log("loaded");
});
});
}
Also, consider using the Google or Microsoft CDN for the jQuery, it will save you bandwidth and hopefully your visitors will already have it cached.
Actually, it's not necessary to load jquery within a js function. But if you insist, you can callback to make sure other js loaded after jquery.
Still, I recommend you load jquery just before </body> then use $.getScript to load other .js
You could do a check to see if jQuery is loaded, not the best way to do it, but if you really have to wait until jQuery is loaded before loading the other scripts, this is how I would do it, by checking for $ :
loadJavascript("Jquery.js");
T=0;
CheckIfLoaded();
function CheckIfLoaded() {
if (typeof $ == 'undefined') {
if (T <= 3000) {
alert("jQuery not loaded within 3 sec");
} else {
T=T+200;
setTimeout(CheckIfLoaded, 200);
} else {
loadJavascript("second.js");
loadJavascript("third.js");
}
}
In technical terms: Browsers have a funny way of deciding I which order to execute/eval dynamically loaded JS, so after suffering the same pain and checking a lot of posts, libraries, plugins, etc. I came up with this solution, self contained, small, no jquery needed, IE friendly, etc. The code is extensively commented:
lazyLoader = {
load: function (scripts) {
// The queue for the scripts to be loaded
lazyLoader.queue = scripts;
lazyLoader.pendingScripts = [];
// There will always be a script in the document, at least this very same script...
// ...this script will be used to identify available properties, thus assess correct way to proceed
var firstScript = document.scripts[0];
// We will loop thru the scripts on the queue
for (i = 0; i < lazyLoader.queue.length; ++i) {
// Evaluates if the async property is used by the browser
if ('async' in firstScript ) {
// Since src has to be defined after onreadystate change for IE, we organize all "element" steps together...
var element = document.createElement("script");
element.type = "text/javascript"
//... two more line of code than necessary but we add order and clarity
// Define async as false, thus the scripts order will be respected
element.async = false;
element.src = lazyLoader.queue[i];
document.head.appendChild(element);
}
// Somebody who hates developers invented IE, so we deal with it as follows:
// ... In IE<11 script objects (and other objects) have a property called readyState...
// ... check the script object has said property (readyState) ...
// ... if true, Bingo! We have and IE!
else if (firstScript.readyState) {
// How it works: IE will load the script even if not injected to the DOM...
// ... we create an event listener, we then inject the scripts in sequential order
// Create an script element
var element = document.createElement("script");
element.type = "text/javascript"
// Add the scripts from the queue to the pending list in order
lazyLoader.pendingScripts.push(element)
// Set an event listener for the script element
element.onreadystatechange = function() {
var pending;
// When the next script on the pending list has loaded proceed
if (lazyLoader.pendingScripts[0].readyState == "loaded" || lazyLoader.pendingScripts[0].readyState == "complete" ) {
// Remove the script we just loaded from the pending list
pending = lazyLoader.pendingScripts.shift()
// Clear the listener
element.onreadystatechange = null;
// Inject the script to the DOM, we don't use appendChild as it might break on IE
firstScript.parentNode.insertBefore(pending, firstScript);
}
}
// Once we have set the listener we set the script object's src
element.src = lazyLoader.queue[i];
}
}
}
}
Of course you can also use the minified version:
smallLoader={load:function(d){smallLoader.b=d;smallLoader.a=[];var b=document.scripts[0];for(i=0;i<smallLoader.b.length;++i)if("async"in b){var a=document.createElement("script");a.type="text/javascript";a.async=!1;a.src=smallLoader.b[i];document.head.appendChild(a)}else b.readyState&&(a=document.createElement("script"),a.type="text/javascript",smallLoader.a.push(a),a.onreadystatechange=function(){var c;if("loaded"==smallLoader.a[0].readyState||"complete"==smallLoader.a[0].readyState)c=smallLoader.a.shift(),
a.onreadystatechange=null,b.parentNode.insertBefore(c,b)},a.src=smallLoader.b[i])}};