Recommended method to locate the current script?
Asked Answered
L

9

12

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:

  1. 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...
  2. 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.
  3. 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)
  4. document.write: not elegant, but seems to do the job.

  5. [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.
  6. [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.
Lactobacillus answered 13/11, 2010 at 11:4 Comment(0)
L
0

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.

Lactobacillus answered 6/5, 2011 at 6:21 Comment(0)
O
6

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

Oxus answered 23/9, 2015 at 12:40 Comment(6)
Thanks! Unfortunately not supported by all major browsers.Lactobacillus
@Lactobacillus You can polyfill it or use a pre-existing polyfill, but you're right.Oxus
I did a quick search but couldn't find a polyfill that works with IE11.Lactobacillus
There are some like github.com/samyk/jiagra or github.com/JamesMGreene/document.currentScript. But I've never tested them because I don't care about IE at all, so I can't say if they work/are securedOxus
Just in case someone working with Nuxt comes up to such message, just bundle a pollyfill from pollyfill.io with a document.currentScript & it will solve the problem. This answer has helped me to indentify the pollyfill name.Lepine
@Lepine - You sir are a blimmin legend. Exactly what I needed, have been trying to resolve a production bug for over a month! Thank you!Chapatti
D
5

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.

Dambro answered 13/11, 2010 at 11:13 Comment(2)
well, "heavy" compared to a direct getElementById. But I get your point, not a big deal.Lactobacillus
Works cross-browser if no async is used in the <script> that load by src. Also this code can be hidden in the widget or script itself. And the <script> can be removed after insertion of HTML component.Foulard
C
4

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>
Cohlier answered 10/6, 2011 at 11:18 Comment(3)
I am concerned with browser support (querySelector), but apart from this it looks very interesting! Give me some time to fully understand it ;-). Another concern I have is that querySelector could return another script on the same page, and the current script will never be executed.Lactobacillus
The querySelector is in all modern browsers and IE10 caniuse.com/#search=queryFoulard
Explanation is missing. How does Function(script.innerHTML) not evaluate to null? There is no return.Foulard
M
1

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.

Mickelson answered 13/11, 2010 at 11:26 Comment(4)
What's wrong is that document.write() only "works" when the page is still loading. If you perform a document.write() in a script you append to the page after onload (...or something, not sure of the exact inflection point), the effect is as though the current page were blown away just prior to that write, with the result being a page containing only the code that was just written into the document.Long
Well, I do prefer the other way. I have not encountered what you describe. In most browsers under the circumstances that I have seen it will stop loading other stuff until your javascript is loaded - which is why it slows down the load time.Mickelson
Another concern I have with document.write: can elements included this way be manipulated by DOM methods, just like standard elements? Or are there any restrictions?Lactobacillus
@Lactobacillus Once elements are added, assuming they are properly formed, they can be accessed just like elements that were there from the beginning. One thing to remember, though, if you are using a library like jQuery, is that if you bind actions to a particular css class, say, and then you add an element of that class, it won't having the binding.Mickelson
C
1

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.

Chirk answered 13/11, 2010 at 12:53 Comment(6)
I use self-executing functions. Deleting the script is not an option as the widget includes interaction. I thought about using class, but then grabbing the element is not as straightforward as with an id. I like the idea of checking the src or another attribute of the script tag, this could help identify the right one.Lactobacillus
@Christophe: I don't mean actually deleting the script itself. Once the script has already been loaded and executed, it's safe to remove the script element that loaded it (of course, after adding the div that will contain the dynamically generated code).Chirk
I wrote "another attribute of the script tag", but then realized there are not many to choose from... Well, maybe I could make up a type, like Microsoft did for the jQuery templating plugin (text/x-jquery-tmpl).Lactobacillus
@Christophe: You can get and set whatever attribute name you want in JavaScript and it won't affect the validity of the HTML.Chirk
Thanks for the comments. Not sure I understand everything, but I have added two other options to my initial post (5 and 6).Lactobacillus
Apparently, JSLint would prefer (function () { /* ... */ }()); to (function () { /* ... */ })();, for reasons I'm not aware of.Tricyclic
L
0

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.

Lactobacillus answered 6/5, 2011 at 6:21 Comment(0)
I
0

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; 
      }

 }
Indian answered 10/5, 2017 at 15:29 Comment(0)
H
0

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
    }
 }
Helios answered 10/8, 2017 at 17:40 Comment(0)
W
0

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'])
}
Weald answered 11/3, 2020 at 17:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.