How to pass parameters to a Script tag?
Asked Answered
P

15

143

I read the tutorial DIY widgets - How to embed your site on another site for XSS Widgets by Dr. Nic.

I'm looking for a way to pass parameters to the script tag. For example, to make the following work:

<script src="http://path/to/widget.js?param_a=1&amp;param_b=3"></script>

Is there a way to do this?


Two interesting links:

Photogram answered 13/3, 2011 at 21:5 Comment(1)
P
18

Got it. Kind of a hack, but it works pretty nice:

var params = document.body.getElementsByTagName('script');
query = params[0].classList;
var param_a = query[0];
var param_b = query[1];
var param_c = query[2];

I pass the params in the script tag as classes:

<script src="http://path.to/widget.js" class="2 5 4"></script>

This article helped a lot.

Photogram answered 13/3, 2011 at 23:12 Comment(3)
The article is awesome!Physiotherapy
Classes should be used to styling element not to pass parameters.Reedy
@Reedy Yeah and HTML should contain content only without styling, except we have to precisely order our DIV tags for float and column layouts because CSS can't actually do the job, and we're somehow OK with that. Hack away, I say. But you can use "data-" attributes if you want to avoid conflict or you're the type to lose sleep over it.Alysaalyse
F
192

I apologise for replying to a super old question but after spending an hour wrestling with the above solutions I opted for simpler stuff.

<script src=".." one="1" two="2"></script>

Inside above script:

document.currentScript.getAttribute('one'); // 1
document.currentScript.getAttribute('two'); // 2

Much easier than jQuery or URL parsing.

You might need the polyfill for document.currentScript from @Yared Rodriguez's answer for IE:

document.currentScript = document.currentScript || (function() {
  var scripts = document.getElementsByTagName('script');
  return scripts[scripts.length - 1];
})();
Fulgor answered 15/9, 2015 at 15:26 Comment(12)
Note that currentScript does not work in IE. DetailsSchick
I did add an polyfill for IE in the answer. Does the polyfill not work?Fulgor
It's also not compliant with HTML5.Ruth
Do you have a source for that this says its ok.Fulgor
the polyfill does not work anymore, document.currentScript is read only and cannot be assigned if it already exist. maybe a different check like if(!document.currentScript) { *** the polyfill function *** } would work betterLated
Does it become HTML5 compliant if you rename one and two to data-one and data-two?Tetradymite
Doesn't answer original question: doesn't use path/to/widget.js.Complemental
My apologies David, I always make my answers generic so that others can use the solution without the initial implementation details adding to issues. In my example above you can see src="..." This is meant to signify, ANY src, doesn't matter :)Fulgor
@RichardFox thanks for the answer, but just had to comment that "Doesn't answer original question: doesn't use path/to/widget.js" is probably the funniest thing I've read all week! Can't believe you replied as nicely as you have done.Numerology
@Lated a true polyfill should check for existence before polyfilling. Just wrap it in something like: if(!document.currentScript) {...}Alexandria
if ".currentScript" doesn't work, you can instead just give an ID to the script tag, and access the data via "document.getElementByID(...).getAttribute(...)"Convenance
I use this pattern: <script src=".." params='{"id":1, "url":"", "readonly":true}'></script> with JSON.parse(document.currentScript.getAttribute('params'))Passkey
T
79

It's better to Use feature in html5 5 data Attributes

<script src="http://path.to/widget.js" data-width="200" data-height="200">
</script>

Inside the script file http://path.to/widget.js you can get the paremeters in that way:

<script>
function getSyncScriptParams() {
         var scripts = document.getElementsByTagName('script');
         var lastScript = scripts[scripts.length-1];
         var scriptName = lastScript;
         return {
             width : scriptName.getAttribute('data-width'),
             height : scriptName.getAttribute('data-height')
         };
 }
</script>
Twinflower answered 21/1, 2014 at 9:18 Comment(6)
What if the id="myAwesomeWigretNo1" is not alwas like that but myAwesomeWigretNo2, or myAwesomeWigretNo681 ??? How can I fix this to accept a real variable?Universality
For what it's worth, there are other ways to reference the script instead, such as document.currentScript (only works for scripts not in their own file) and putting an id on the <script> tag and then finding it by that.Cankerworm
And what if the ID was changed by some other programmer in your team that had no clue that you use this id ?Twinflower
If you load scripts asynchronously then using an ID is the only reliable option I would recommend. Thanks @Cankerworm ... Also, any programmer should refrain from refactoring a code without a proper sprint plan overviewed by someone who has a clue of what's going on ;)Mckee
Sure you can demand a strong Ethics from Developers, but it wont work on the long run... People forget stuff.... In a Good Software Design Each Block Shod be isolated, awesome-pixels.tumblr.com/post/101930002170/… en.wikipedia.org/wiki/SOLID_(object-oriented_design) So I don't argue that it's a good vs bad solution it just more complex to maintain ID's vs not to maintain :-) Is you don't plan to load the script Async I see no reason to load using IDTwinflower
For async you can use this technique: #12821453Twinflower
M
22

Another way is to use meta tags. Whatever data is supposed to be passed to your JavaScript can be assigned like this:

<meta name="yourdata" content="whatever" />
<meta name="moredata" content="more of this" />

The data can then be pulled from the meta tags like this (best done in a DOMContentLoaded event handler):

var data1 = document.getElementsByName('yourdata')[0].content;
var data2 = document.getElementsByName('moredata')[0].content;

Absolutely no hassle with jQuery or the likes, no hacks and workarounds necessary, and works with any HTML version that supports meta tags...

Muchness answered 31/8, 2017 at 8:11 Comment(3)
I thought this method was by far the easiest method them all. It works like a charm and gives me exactly what I need. Great question and Fantastic solution!Tarlatan
I think requiring a line before <script> for each parameter is clumsy. We need a new standards feature.Complemental
This has the advantage that this seems to be the only reliable way to pass parameters to <script type="module"> with a <meta name> derived from import.meta.url. document.currentScript is not available in modules. document.body.getElementsByTagName('script').pop() cannot guarantee to access the current module in an asynchronous world either. import.meta.url is absolute while <script src> may be anything. Hardcoding some ID might conflict with other modules using the same technique.Knowall
P
18

Got it. Kind of a hack, but it works pretty nice:

var params = document.body.getElementsByTagName('script');
query = params[0].classList;
var param_a = query[0];
var param_b = query[1];
var param_c = query[2];

I pass the params in the script tag as classes:

<script src="http://path.to/widget.js" class="2 5 4"></script>

This article helped a lot.

Photogram answered 13/3, 2011 at 23:12 Comment(3)
The article is awesome!Physiotherapy
Classes should be used to styling element not to pass parameters.Reedy
@Reedy Yeah and HTML should contain content only without styling, except we have to precisely order our DIV tags for float and column layouts because CSS can't actually do the job, and we're somehow OK with that. Hack away, I say. But you can use "data-" attributes if you want to avoid conflict or you're the type to lose sleep over it.Alysaalyse
S
13

JQuery has a way to pass parameters from HTML to javascript:

Put this in the myhtml.html file:

<!-- Import javascript -->
<script src="//code.jquery.com/jquery-1.11.2.min.js"></script>
<!-- Invoke a different javascript file called subscript.js -->
<script id="myscript" src="subscript.js" video_filename="foobar.mp4">/script>

In the same directory make a subscript.js file and put this in there:

//Use jquery to look up the tag with the id of 'myscript' above.  Get 
//the attribute called video_filename, stuff it into variable filename.
var filename = $('#myscript').attr("video_filename");

//print filename out to screen.
document.write(filename);

Analyze Result:

Loading the myhtml.html page has 'foobar.mp4' print to screen. The variable called video_filename was passed from html to javascript. Javascript printed it to screen, and it appeared as embedded into the html in the parent.

jsfiddle proof that the above works:

http://jsfiddle.net/xqr77dLt/

Spillar answered 25/1, 2015 at 20:12 Comment(0)
Y
10

Create an attribute that contains a list of the parameters, like so:

<script src="http://path/to/widget.js" data-params="1, 3"></script>

Then, in your JavaScript, get the parameters as an array:

var script = document.currentScript || 
/*Polyfill*/ Array.prototype.slice.call(document.getElementsByTagName('script')).pop();

var params = (script.getAttribute('data-params') || '').split(/, */);

params[0]; // -> 1
params[1]; // -> 3
Yang answered 10/3, 2019 at 18:57 Comment(2)
What are the downsides to this approach?Terbia
@Terbia there are no real downsides to this approach and there is a polyfill included for better browser support. The only downside I see here is not being able to include commas in parameters, which isn't usually a problem, and you can compensate for that by changing the delimiter to a character you aren't planning on using.Yang
M
8

If you are using jquery you might want to consider their data method.

I have used something similar to what you are trying in your response but like this:

<script src="http://path.to/widget.js" param_a = "2" param_b = "5" param_c = "4">
</script>

You could also create a function that lets you grab the GET params directly (this is what I frequently use):

function $_GET(q,s) {
    s = s || window.location.search;
    var re = new RegExp('&'+q+'=([^&]*)','i');
    return (s=s.replace(/^\?/,'&').match(re)) ? s=s[1] : s='';
}

// Grab the GET param
var param_a = $_GET('param_a');
Miff answered 13/3, 2011 at 23:30 Comment(0)
B
6

it is a very old thread, I know but this might help too if somebody gets here once they search for a solution.

Basically I used the document.currentScript to get the element from where my code is running and I filter using the name of the variable I am looking for. I did it extending currentScript with a method called "get", so we will be able to fetch the value inside that script by using:

document.currentScript.get('get_variable_name');

In this way we can use standard URI to retrieve the variables without adding special attributes.

This is the final code

document.currentScript.get = function(variable) {
    if(variable=(new RegExp('[?&]'+encodeURIComponent(variable)+'=([^&]*)')).exec(this.src))
    return decodeURIComponent(variable[1]);
};

I was forgetting about IE :) It could not be that easier... Well I did not mention that document.currentScript is a HTML5 property. It has not been included for different versions of IE (I tested until IE11, and it was not there yet). For IE compatibility, I added this portion to the code:

document.currentScript = document.currentScript || (function() {
  var scripts = document.getElementsByTagName('script');
  return scripts[scripts.length - 1];
})();

What we are doing here is to define some alternative code for IE, which returns the current script object, which is required in the solution to extract parameters from the src property. This is not the perfect solution for IE since there are some limitations; If the script is loaded asynchronously. Newer browsers should include ".currentScript" property.

I hope it helps.

Blakemore answered 15/5, 2015 at 5:30 Comment(0)
C
5

Thanks to the jQuery, a simple HTML5 compliant solution is to create an extra HTML tag, like div, to store the data.

HTML:

<div id='dataDiv' data-arg1='content1' data-arg2='content2'>
  <button id='clickButton'>Click me</button>
</div>

JavaScript:

$(document).ready(function() {
    var fetchData = $("#dataDiv").data('arg1') + 
                    $("#dataDiv").data('arg2') ;

    $('#clickButton').click(function() {
      console.log(fetchData);
    })
});

Live demo with the code above: http://codepen.io/anon/pen/KzzNmQ?editors=1011#0

On the live demo, one can see the data from HTML5 data-* attributes to be concatenated and printed to the log.

Source: https://api.jquery.com/data/

Chink answered 9/3, 2016 at 0:44 Comment(2)
Went through some of the other solutions, but this was the one that worked the best and also is pretty simple to implement. thanks!Emolument
This should be the accepted solution, as it allows you to reference the script by ID and is valid HTML5.Solemnize
I
4

I wanted solutions with as much support of old browsers as possible. Otherwise I'd say either the currentScript or the data attributes method would be most stylish.

This is the only of these methods not brought up here yet. Particularly, if for some reason you have great amounts of data, then the best option might be:

localStorage

/* On the original page, you add an inline JS Script.
 * If you only have one datum you don't need JSON:
 * localStorage.setItem('datum', 'Information here.');
 * But for many parameters, JSON makes things easier: */
var data = {'data1': 'I got a lot of data.',
            'data2': 'More of my data.',
            'data3': 'Even more data.'};
localStorage.setItem('data', JSON.stringify(data));

/* External target JS Script, where your data is needed: */
var data = JSON.parse(localStorage.getItem('data'));
console.log(data['data1']);

localStorage has full modern browser support, and surprisingly good support of older browsers too, back to IE 8, Firefox 3,5 and Safari 4 [eleven years back] among others.

If you don't have a lot of data, but still want extensive browser support, maybe the best option is:

Meta tags [by Robidu]

/* HTML: */
<meta name="yourData" content="Your data is here" />

/* JS: */
var data1 = document.getElementsByName('yourData')[0].content;

The flaw of this, is that the correct place to put meta tags [up until HTML 4] is in the head tag, and you might not want this data up there. To avoid that, or putting meta tags in body, you could use a:

Hidden paragraph

/* HTML: */
<p hidden id="yourData">Your data is here</p>

/* JS: */
var yourData = document.getElementById('yourData').innerHTML;

For even more browser support, you could use a CSS class instead of the hidden attribute:

/* CSS: */
.hidden {
   display: none;
}

/* HTML: */
<p class="hidden" id="yourData">Your data is here</p>
Interloper answered 14/9, 2019 at 0:8 Comment(3)
Requiring a line for each argument is clumsy, however the args are stored.Complemental
That's true David. I added JSON to the first method, making it more efficient for many arguments.Interloper
In my answer I show an easy way if you can do without the argument names.Complemental
C
4

This is the Solution for jQuery 3.4

<script src="./js/util.js" data-m="myParam"></script>
$(document).ready(function () {
    var m = $('script[data-m][data-m!=null]').attr('data-m');
})
Cormick answered 16/7, 2020 at 5:8 Comment(1)
Except jQuery is effectively obsolete and has been for quite a while, besides the original question doesn't ask for a jQuery solution. You solution would better be written using document.querySelector().getAttribute() -- which was already mentioned in other answers. (not that it's the "best" way, just pointing out that a jQuery solution is not really necessary.Cowslip
B
3

Put the values you need someplace where the other script can retrieve them, like a hidden input, and then pull those values from their container when you initialize your new script. You could even put all your params as a JSON string into one hidden field.

Blistery answered 13/3, 2011 at 21:7 Comment(2)
Thanks for answering. The params are actually input by the users (who embeds this script tag in their code). What then?Photogram
The only thing I can think of if you don't have control over both client/server scripts is to take the users input and use it to generate a specific .js file with their input embedded in it, and that's assuming you are in control of gathering and processing their input.Blistery
C
3

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']. This assumes synchronous script calling.

Complemental answered 26/6, 2021 at 15:9 Comment(1)
The prolem with currentScript, though, is that it is only available while the document is loading. If dcoument.readyState is set to complete, it doesn't point anywhere. In contrast to this meta tags don't make your JS dependent on being executed synchronously (thus works with both defer and async attributes) and can be read from virtually every JS incorporated into your page. If you want to achieve the same with data attributes, you have quite a rigmarole ahead of you to even retrieve the data.Muchness
K
1

Parameters for <script type="module">

I had the problem to pass parameters to some JS script loaded as a module.

There are two variants, based on the ideas of @Robidy and @Fom.

In modules, document.currentScript is not available. Also I do not want to rely on document.scripts[document.scripts.length-1] when it comes to possibly asynchronously executing modules.

@Robidy: parameters from <meta name="script.js" content>

While using <meta> for this seems clumsy on the first sight, it starts to have some benefits:

  • You can also pass (possibly sparsely filled) tabular data.
  • The name can be derived from the module's name.
<meta name="module.js" content="https://cdn.skypack.dev/octokit" data-0="https://cdn.skypack.dev/[email protected]"/>
<script type="module" src="module.js"></script>
  • content is placed at the first array position:
    • meta[N][0] in the example
  • data-M gets additional tabular parameters put at the M+1th position of the array:
    • metas[N][M+1] in the example
  • N is the Nth meta, where 0 is the first <meta>
  • data-X where X is not a positive integer, it is ignored

I needed this, as the documented way to load Octokit failed. So I created a loader module which allows to add fallback URLs.
(example use)

It does not support SRI, though.

Here is the beginning of the code of such a module.js:

const me = import.meta.url?.split('/').pop() || 'module.js';
const metas = Array
  .from(document.getElementsByName(me))
  .filter(_ => _.nodeName === 'META')
  .map(_ => [_.attributes.content.value].concat(Array.from(_.attributes)
    .map(_ => ([_.name.split('-'), _.value]))
    .filter(([name,value]) => name.length===2 && name[0] ==='data' && name[1] === `${name[1]||0}`)
    .reduce((a,[[data,nr],value]) => (a[nr|0]=value, a), [])                                                                                
    ));

I have no idea what happens if import.meta is not available.

For the case this is no syntax error, above code uses some hardcoded value as fallback (here 'module.js'), named after the default name of a module of course.

Perhaps a better thing would be to throw in this case.

The variable metas (from the example) then is an array of possibly sparsely filled arrays with the <meta> rows' content, data-0, data-1, .. and so on.

<meta name="module.js" content="https://cdn.skypack.dev/octokit" data-0="https://cdn.skypack.dev/[email protected]"/>

gives

[['https://cdn.skypack.dev/octokit','https://cdn.skypack.dev/[email protected]']]

and

<meta name="module.js" content="https://cdn.skypack.dev/octokit" data-2="https://cdn.skypack.dev/[email protected]"/>

gives (note the tripple comma):

[['https://cdn.skypack.dev/octokit',,,'https://cdn.skypack.dev/[email protected]']]

@Fom: JSON from hidden <div>

To parse hidden <div> for JSON, as suggested by @Fom, might look like:

const me = import.meta.url?.split('/').pop() || 'module.js';
const jsons = Array
  .from(document.getElementsByName(me))
  .filter(_ => _.nodeName === 'DIV')
  .map(_ => JSON.parse(_.innerText));

This hands out an array of the JSONs found in all <div name> elements named after the module. (You might need to wait until all DOM is loaded.)

The automatically derived name:

If you use something like

<script type="module" src="/path/to/module.js#abc"></script>

the line

const me = import.meta.url?.split('/').pop();

sets me to module.js#abc, so you need to give name like following:

<meta name="module.js#abc" content="parameter"/>
<div name="module.js#abc">{"a":"b"}</div>
<script type="module" src="/path/to/module.js#abc"></script>

Note that JS apparently allows arbitrary strings in name.

Final note

This all works similar for normal <script> without <script type="module">, if you replace import.meta.url with document?.currentScript.src

To change the code such, that it works for normal scripts, modules and WebWorkers, and create some meaningful script which can be executed as any of these, is left as an exercise for the reader.

Knowall answered 16/6, 2023 at 20:22 Comment(1)
The point is, you can easily wrap up in a closure that provides an API by harvesting every meta tag that you are coming across (document.getElementByTagName(), then iterate over the list), then return some sort of API for easy access. If done right, you can even access comma(or whatever)-separated lists into the meta tags and check them for individual items without further ado.Muchness
F
1

data attributes

If you combine all the information here you arrive at a best practice answer. Note that this way everything will be a string.

<script src="index.js" data-foo="bar" data-foo-bar="1"></script>

index.js

// Can also destructure here if you prefer
const args = document.currentScript.dataset;
// args.foo is "bar", args.fooBar is "1"

URL params

Should you for some reason prefer to use URL params, here is what I got for that. It has some syntax disadvantages. Also, unfortunately everything is still a string.

<script src="index.js?foo=bar&foo-bar=1"></script>

index.js

const args = Object.fromEntries(new URL(document.currentScript.src).searchParams);
// args.foo is "bar", args.foo-bar is "1"

See Also

Falmouth answered 27/9, 2023 at 16:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.