Recommended method to locate the current script? - javascript

I am writing a script that needs to add DOM elements to the page, at the place where the script is located (widget-like approach).
What is the best way to do this?
Here are the techniques I am considering:
Include an element with an id="Locator" right above the script. Issues:
I don't like the extra markup
If I reuse the widget in the page, several elements will have the same "Locator" id. I was thinking about adding a line in the script to remove the id once used, but still...
Add an id to the script. Issues:
even though it seems to work, the id attribute is not valid for the script element
same issue as above, several elements will have the same id if I reuse the script in the page.
Use getElementsByTagName("script") and pick the last element. This has worked for me so far, it just seems a little heavy and I am not sure if it is reliable (thinking about deferred scripts)
document.write: not elegant, but seems to do the job.
[Edit] Based on the reply from idealmachine, I am thinking about one more option:
Include in the script tag an attribute, for example goal="tabify".
Use getElementsByTagName("script") to get all the scripts.
Loop through the scripts and check the goal="tabify" attribute to find my script.
Remove the goal attribute in case there's another widget in the page.
[Edit] Another idea, also inspired by the replies so far:
Use getElementsByTagName("script") to get all the scripts.
Loop through the scripts and check innerHTML to find my script.
At the end of the script, remove the script tag in case there's another widget in the page.

Out of the box : document.currentScript (not supported by IE)

I've worked for OnlyWire which provides, as their main service, a widget to put on your site.
We use the var scripts = document.getElementsByTagName("script"); var thisScript = scripts[scripts.length - 1]; trick and it seems to work pretty well. Then we use thisScript.parentNode.insertBefore(ga, thisScript); to insert whatever we want before it, in the DOM tree.
I'm not sure I understand why you consider this a "heavy" solution... it doesn't involve iteration, it's a pure cross-browser solution which integrates perfectly.

This works with multiple copies of same code on page as well as with dynamically inserted code:
<script type="text/javascript" class="to-run">
(function(self){
if (self == window) {
var script = document.querySelector('script.to-run');
script.className = '';
Function(script.innerHTML).call(script);
} else {
// Do real stuff here. self refers to current script element.
console.log(1, self);
}
})(this);
</script>

Either document.write or picking the last script element will work for synchronously loaded scripts in the majority of web pages. However, there are some options I can think of that you did not consider to allow for async loading:
Adding a div with class="Locator" before the script. HTML classes has the advantage that duplicates are not invalid. Of course, to handle the multiple widget case, you will want to change the element's class name when done adding the HTML elements so you do not add them twice. (Note that it is also possible for an element to be a member of multiple classes; it is a space-separated list.)
Checking the src of each script element can ensure that tracking code (e.g. Google Analytics legacy tracking code) and other scripts loaded at the very end of the page will not prevent your script from working properly when async loading is used. Again, to handle the multiple widget case, you may need to remove the script elements when done with them (i.e. when the desired code has been added to the page).
One final comment I will make (although you may already be aware of this) is that when coding a widget, you need to declare all your variables using var and enclose all your code within: (JSLint can help check this)
(function(){
...
})();
This has been called a "self-executing function" and will ensure that variables used in your script do not interfere with the rest of the Web page.

Whether you drop a <script> tag in or a <div class="mywidget">, you're adding something to the markup. Personally, I prefer the latter as the script itself is only added once. Too many scripts in the page body can slow down the page load time.
But if you need to add the script tag where the widget is going to be, I don't see what's wrong with using document.write() to place a div.

I just found another method that seems to answer my question:
How to access parent Iframe from javascript
Embedding the script in an iframe allows to locate it anytime, as the script always keeps a reference to its own window.
I vote this the best approach, as it'll always work no matter how many times you add the script to the page (think widget). You're welcome to comment.
What pushed me to consider iframes in the first place was an experiment I did to build a Google gadget.

In many cases this work well (hud.js is the name of the scipt):
var jsscript = document.getElementsByTagName("script");
for (var i = 0; i < jsscript.length; i++) {
var pattern = /hud.js/i;
if ( pattern.test( jsscript[i].getAttribute("src") ) )
{
var parser = document.createElement('a');
parser.href = jsscript[i].getAttribute("src");
host = parser.host;
}
}

Also you can add individual script's name inside them.
either inside some js-script
dataset['my_prefix_name'] = 'someScriptName'
or inside HTML - in the <script> tag
data-my_prefix_name='someScriptName'
and next search appropriate one by looping over document.scripts array:
... function(){
for (var i = 0, n = document.scripts.length; i < n; i++) {
var prefix = document.scripts[i].dataset['my_prefix_name']
if (prefix == 'whatYouNeed')
return prefix
}
}

I haven't had access to internet explorer since forever, but this should work pretty much everywhere:
<script src="script.js"
data-count="30"
data-headline="My headline"
onload="uniqueFunctionName(this)"
defer
></script>
and inside script.js:
window.uniqueFunctionName = function (currentScript) {
var dataset = currentScript.dataset
console.log(dataset['count'])
console.log(dataset['headline'])
}

Related

JavaScript: register a custom script type and define a handler?

HTML elements with a unique custom type attribute are ignored by the browser. Sometimes these are used by template engines. How do I define what happens when such a script element is loaded/created? (either while loading the page or when inserted dynamically)
In other words, does an onCreateElement event of some sorts exist in the DOM?
I could quite easily iterate through all script elements with attribute type=text/mycustomtype when the DOM loads using for instance the querySelector and then parse them with a function. This however does not work when a new script element is created and appended programatically. Is this currently possible?
var d = document.createElement('script')
d.setAttribute('type', 'text/mycustomtype')
d.innerHTML = 'define foo = 1;' // some code in some custom language
document.body.appendChild(d)
In this case, nothing will happen because the browser will ignore this unknown type. Can I somehow define a handler function for this? Thanks in advance.
I recently saw python script running in a browser using brython. View source and I see:
<script type="text/python3">
from interpreter import Interpreter
# open REPL in textarea with id "code"
Interpreter("code")
</script>
I wanted to know how this is possible. I googled for the answer, and found the dreaded stackoverflow question that's exactly what I want - with no answers.
I found a good answer in: Everything I Know About The Script Tag. It's exactly as you predicted when you say iterate through all script elements.
Their example looks like this:
<script type="text/emerald">
make a social network
but for cats
</script>
<script>
var codez = document.querySelectorAll('script[type="text/emerald"]');
for (var i=0; i < codez.length; i++)
runEmeraldCode(codez[i].innerHTML);
</script>

Remove dynamic script tag on src match

I have a web page which is working fine on my local machine, but when it is moved to the server, an ad script from google overwrites page image src.
I want to find that particular script tag with that src and remove it. I know the url, but can not add an id or modify the DOM. Please help.
for eg:
<script src="one.js"><script>
<script src="two.js"><script>
<script src="three.js"><script>
so if
var scripts = document.getElementsByTagName ( "script" );
var l = scripts.length;
for ( var i = 0; i < l; ++ i ) {
if ( scripts[i].src ="one.js") {
//remove that particular script tag with one.js
}
}
As Paul stated in the comments, removing an already loaded script wont do any change. But, for the sake of it:
$('script[src*="one.js"]').remove();
with jQuery (as this question is tagged).
Edit: for more info about attribute selectors (like *="string") have read here
Not quite an answer per say, but still i have a gut feeling that i should post this workaround.
So if I were you, maybe I'd try to trap the script by creating the fake set of elements that the script is meddling with and then verify if the respective data has changed ad then delete that div.
Better explained step by step.
Introduce a fake div with similar Dom before our concerned div. Now if there are two divs with same Id then that script shall target the first div with that id.
You check if the content of our Trojan div is changed, if yes then remove it from the Dom.
Now execute your script.
Also as a counter measure just remove all the events that are delegated to that div. If the rogue script is jquery based, even better, since you can remove the respective event and bind your own, using the .off() function. As shown below..
$("#xyz").off().on('click', function(){});

AngularJS - inject trusted code from loaded content

I've hooked up a lazy loader in Angular. It pulls in full templates and extracts key information from that full template in order to populate a partial. This full page template has script tags which load in and then register with the existing app. All of this works fine. My problem is that I'd like to remove the only use of jQuery in this approach.
The root issue is that the JS inside of something.js doesn't execute when using $element.html(), but it does execute when using $.html(), despite the script tag being placed in the DOM in both approaches.
Working code, including lazy loader and post-bootstrap registration of lazy-loaded JS:
$http.get("/path/to/file.html").success(function(response) {
// response is a full HTML page including <doctype>
var partial = getOnlyWhatWeNeed(response);
// partial is now something like: '<script type="text/javascript" src="/path/to/something.js"></script><div ng-controller="somethingCtrl">{{something}}</div>'
// i'd like the following to not rely on full jQuery.
$("#stage").html(partial);
$("#stage").html($compile(partial)($scope)); // it is necessary to do it once before compile so that the <script> tags get dropped in and executed prior to compilation.
});
I've tried what seems like the logical translation:
$element.html($compile(partial)($scope));
and the DOM is created properly, but the JS inside of the loaded <script> tag doesn't actually execute. My research suggested this was an $sce issue, so I tried:
$element.html($compile($sce.trustAsHtml(partial)($scope));
but i get the same result. the DOM is fine, but the JS doesn't actually execute and so I get undefined controller issues.
I've tried playing with $sce.JS and $sce.RESOURCE_URL but the docs didnt elaborate much so I'm not sure I know whether or not what I'm trying is even right.
I've also tried $element[0].innerHTML but I get the same result as $element.html().
Preemptive disclaimer: I can trust the incoming HTML/JS. I know it's inadvisable. This isn't my baby and it is much more complicated than I explained so please try to stay on topic so other people in this position may not have as hard of a time as I am :)
The $http.get happens in a provider, and the $element.html happens in a directive. I consolidated them to remove noise from the problem.
Jquery will find any script tags and evaluate them (either a direct eval or appending them to the head for linked scripts) when calling html(), see this answer. I'm assuming angular's jquery lite doesn't do this. You would need to effectively replicate what jquery is doing and look for script tags in the html you are appending.
Something like this (although I haven't tested it):
$http.get("/path/to/file.html").success(function(response) {
// response is a full HTML page including <doctype>
var partial = getOnlyWhatWeNeed(response);
// partial is now something like: '<script type="text/javascript" src="/path/to/something.js"></script><div ng-controller="somethingCtrl">{{something}}</div>'
var d = document.createElement('div');
d.innerHTML = partial;
var scripts = d.getElementsByTagName('script');
for (var i = 0; i < scripts.length; i++) {
document.head.appendChild(scripts[0]);
}
$("#stage").html($compile(partial)($scope)); // it is necessary to do it once before compile so that the <script> tags get dropped in and executed prior to compilation.
});
This is far from an ideal solution as it gives you no guarantee of when things are loaded and doesn't really handle dependencies across scripts. If you can control the templates it would be simpler to remove the scripts from them and load them independently.

Is it possible to reliably insert a HTML element at script's location?

I'm writing a Javascript file which will be a component in a webpage. I'd like it to be simple to use - just reference the script file in your page, and it is there. To that end however there is a complication - where should the HTML go that the Javascript generates? One approach would be to require a placeholder element in the page with a fixed ID or class or something. But that's an extra requirement. It would be better if the HTML was generated at the location that the script is placed (or, at the start of body, if the script is placed in head). Also, for extra customizability, if the fixed ID was found, the HTML would be placed inside that placeholder.
So I'm wondering - how do I detect my script's location in the page? And how do I place HTML there? document.write() comes to mind, but that is documented as being pretty unreliable. Also it doesn't help if the script is in the head. Not to mention what happens if my script is loaded dynamically via some AJAX call, but I suppose that can be left as an unsupported scenario.
I am doing that with this code...
// This is for Firefox only at the moment.
var thisScriptElement = document.currentScript,
// Generic `a` element for exploiting its ability to return `pathname`.
a = document.createElement('a');
if ( ! thisScriptElement) {
// Iterate backwards, to look for our script.
var scriptElements = document.body.getElementsByTagName('script'),
i = scriptElements.length;
while (i--) {
if ( ! scriptElements[i].src) {
continue;
}
a.href = scriptElements[i].src;
if (a.pathname.replace(/^.*\//, '') == 'name-of-your-js-code.js') {
thisScriptElement = scriptElements[i];
break;
}
}
}
Then, to add your element, it's simple as...
currentScript.parentNode.insertBefore(newElement, currentScript);
I simply add a script element anywhere (and multiple times if necessary) in the body element to include it...
<script type="text/javascript" src="somewhere/name-of-your-js-code.js?"></script>
Ensure the code runs as is, not in DOM ready or window's load event.
Basically, we first check for document.currentScript, which is Firefox only but still useful (if it becomes standardised and/or other browsers implement it, it should be most reliable and fastest).
Then I create a generic a element to exploit some of its functionality, such as extracting the path portion of the href.
I then iterate backwards over the script elements (because in parse order the last script element should be the currently executing script), comparing the filename to what we know ours is called. You may be able to skip this, but I am doing this to be safe.
document.write is very reliable if used as you indicate (a default SharePoint 2010 page uses it 6 times). If placed in the head, it will write content to immediately after the body element. The trick is to build a single string of HTML and write it in one go, don't write snippets of half-formed HTML.
An alternative is to use document.getElementsByTagName('script') while the document is loading and assume the the last one is the current script element. Then you can look at the parent and if it's the head, use the load or DOM ready event to add your elements after the body. Otherwise, just add it before or after the script element as appropriate.

Javascript execution order

I want to give a static javascript block of code to a html template designer, which can be:
either inline or external or both
used once or more in the html template
and each block can determine its position in the template relative to the other javascript code blocks.
An example could be image banners served using javascript. I give code to template designer who places it in two places, once for a horizontal banner in the header and once for a vertical banner. The same code runs in both blocks but knowing their positions can determine if to serve a horizontal or a vertical image banner.
Make sense?
Another example: Say you have the same 2 javascript tags in a web page calling an external script on a server. Can the server and/or scripts determine which javascript tag it belongs to?
NOTE: Can we say this is a challenge? I know that I can avoid this puzzle very easily but I come across this on a regular basis.
JavaScript code can locate all <script> elements on the page and it can probably examine the attributes and the content to check from which element it came from. But that's probably not what you want.
What you want is a piece of JavaScript which replaces tags on the page with ad banners. The usual solution is to add a special element, say a IMG, for this and give that IMG an id or a class or maybe even a custom attribute (like adtype="vertical") and then use JavaScript to locate these elements and replace the content by changing the src attribute.
For example, using jQuery, you can should your images like so:
<img src="empty.gif" width="..." height="..." class="ad" adtype="..." />
Then you can locate each image with
$('img.ad')
[EDIT] Well, the server obviously knows which script belongs into which script tag because it inserts the script. So this is a no-brainer.
If the script wants to find out where it is in the DOM, add something which it can use to identify itself, say:
<script>var id= '329573485745';
Then you can walk all script tags and check which one contains the value of the variable id.
If you call an external script, then you can do the same but you must add the ID to the script tag as you emit the HTML:
<script id="329573485745" src="..." />
Then the external script can examine the DOM and lookup the element with this id. You will want to use an UUID for this, btw.
This way, a piece of JS can locate the script tag which added itself to the page.
Best thing would probably be to make an insert once function, and then have him insert only the function call where needed.
Like this:
timescalled=0
function buildad(){
var toinsert="" //Code to generate the desired piece of HTML
document.write(toinsert)
timescalled+=1 //So you can tell how many times the function have been called
}
Now a script block calling the function can simply be inserted wherever a banner is needed
<script type="text/javascript">buildad()</script>
Thanks for the tips everyone but I'll be answering my own question.
I figured out several ways of accomplishing the task and I give you the one which works nicely and is easy to understand.
The following chunk of code relies on outputting dummy divs and jQuery.
<script>
// Unique identifier for all dummy divs
var rnd1="_0xDEFEC8ED_";
// Unique identifier for this dummy div
var rnd2=Math.floor(Math.random()*999999);
// The dummy div
var d="<div class='"+rnd1+" "+rnd2+"'></div>";
// Script which :
// Calculates index of THIS dummy div
// Total dummy divs
// Outputs to dummy div for debugging
var f1="<script>$(document).ready(function(){";
var f2="var i=$('."+rnd1+"').index($('."+rnd2+"'))+1;";
var f3="var t=$('."+rnd1+"').length;";
var f4="$('."+rnd2+"').html(i+' / '+t);";
var f5="});<\/script>";
document.write(d+f1+f2+f3+f4+f5);
</script>
Why not not just place the function call on the page instead of the entire code block? This way you can pass in a parameter to tell it what type of advertisement is needed?
BuildAd('Tower');
BuildAd('Banner');
Javascript itself has no clue of it's position in a page. You have to target a control on the page to get it's location.
I don't think it is possible for JavaScript code to know where it was loaded from. It certainly doesn't run at the point it is found, since execution isn't directly tied to the loading process (code usually runs after the whole DOM is loaded). In fact, in the case of externals, it doesn't even make sense, since only one copy of the code will be loaded no matter how many times it is encountered.
It shouldn't be the same code for each banner - there will be a parameter passed to whatever is serving the image banner which will specify the intended size.
Can you give a specific example of what you need this for?
To edit for your recent example: The simple answer is no. I could help you approach the problem from a different direction if you post details of your problem
The term "static block of code" leaves a lot of room for interpretation.
Inline scripts (e.g., ones that rely on document.write and so must be parsed and executed during the HTML parsing phase) cannot tell where they are in the DOM at runtime. You have to tell them (as in one of the first answers you got).
I think you'll probably find that you need to change your approach.
A common way to keep code and markup separate (which is useful when providing tools to HTML designers who aren't coders) is to have them use a script tag like so:
<script defer async type='text/javascript' src='pagestuff.js'></script>
...which then triggers itself when the page is loaded (using window.onload if necessary, but there are several techniques for being triggered earlier than that, which you want because window.onload doesn't trigger until the images have all loaded).
That script then looks for markers in the markup and manipulates the page accordingly. For instance (this example uses Prototype, but you can do the same with raw JavaScript, jQuery, Closure, etc.):
document.observe("dom:loaded", initPage);
function initPage() {
var verticals = $$('div.vertical');
/* ...do something with the array of "vertical" divs in `verticals`,
such as: */
var index;
for (index = 0; index < verticals.length; ++index) {
vertical.update("I'm vertical #" + index);
}
}
The designers can then have blocks on the page that are filled in by code which they flag up in a way that's normal for them (classes or attributes, etc.). The code figures out what it should do based on the classes/attributes of the blocks it finds when it runs.

Categories

Resources