Passer des paramètres aux fichiers JavaScript

163

Souvent, j'aurai un fichier JavaScript que je souhaite utiliser et qui nécessite la définition de certaines variables dans ma page Web.

Donc, le code est quelque chose comme ceci:

<script type="text/javascript" src="file.js"></script>
<script type="text/javascript">
   var obj1 = "somevalue";
</script>

Mais ce que je veux faire, c'est:

<script type="text/javascript" 
     src="file.js?obj1=somevalue&obj2=someothervalue"></script>

J'ai essayé différentes méthodes et la meilleure à ce jour est d'analyser la chaîne de requête comme ceci:

var scriptSrc = document.getElementById("myscript").src.toLowerCase();

Et puis recherchez mes valeurs.

Je me demande s'il existe une autre façon de faire cela sans créer de fonction pour analyser ma chaîne.

Connaissez-vous tous d'autres méthodes?

RaduM
la source
9
C'est une question en double ( stackoverflow.com/questions/1017424/… ) et à mon avis une conception malavisée. Il est beaucoup plus simple d'utiliser votre première solution de variable (d'accord), ou de laisser le script définir une fonction qui est ensuite appelée avec les variables (mieux).
Matthew Flaschen
Je recherche une solution mise à jour qui tire parti de la nouvelle fonctionnalité ECMAScript 6 ... le cas échéant
Chef_Code
L'utilisation de tout script interne déclenchera des rapports CSP si le site est HTTPS et si CSP est entièrement activé. La raison donnée dans le document CSP est que l'autorisation d'éléments internes peut faciliter l'injection.
David Spector

Réponses:

207

Je recommanderais de ne pas utiliser de variables globales si possible. Utilisez un espace de noms et une POO pour transmettre vos arguments à un objet.

Ce code appartient au file.js:

var MYLIBRARY = MYLIBRARY || (function(){
    var _args = {}; // private

    return {
        init : function(Args) {
            _args = Args;
            // some other initialising
        },
        helloWorld : function() {
            alert('Hello World! -' + _args[0]);
        }
    };
}());

Et dans votre fichier html:

<script type="text/javascript" src="file.js"></script>
<script type="text/javascript">
   MYLIBRARY.init(["somevalue", 1, "controlId"]);
   MYLIBRARY.helloWorld();
</script>
Naeem Sarfraz
la source
4
@Naeem - file.js est-il chargé à 100% du temps lorsque MYLIBRARY.init (["somevalue", 1, "controlId"]); est exécuté? Peut-être avons-nous besoin d'un document prêt à le mettre?
Darius.V
2
1) Si MYLIBRARY existe, utilisez-la ou définissez un nouvel objet. L'intention est de créer un espace de noms global, normalement j'utiliserai des sous-espaces de noms afin que cette ligne évite de redéfinir l'espace de noms de niveau supérieur. 2) Oui, cela a créé un singleton.
Naeem Sarfraz
1
J'ai fait un exemple de cette idée: http://plnkr.co/edit/iE0Vr7sszfqrrDIsR8Wi?p=preview
robe007
1
Que signifie cette expression? var MYLIBRARY = MYLIBRARY || (function(){}());? Pourquoi devrait-il MYLIBRARYêtre défini sur une valeur booléenne?
Alex
1
@Alexander, voir mon commentaire ci-dessus stackoverflow.com/questions/2190801/…
Naeem Sarfraz
79

Vous pouvez transmettre des paramètres avec des attributs arbitraires. Cela fonctionne dans tous les navigateurs récents.

<script type="text/javascript" data-my_var_1="some_val_1" data-my_var_2="some_val_2" src="/js/somefile.js"></script>

Dans somefile.js, vous pouvez obtenir les valeurs des variables transmises de cette façon:

........

var this_js_script = $('script[src*=somefile]'); // or better regexp to get the file name..

var my_var_1 = this_js_script.attr('data-my_var_1');   
if (typeof my_var_1 === "undefined" ) {
   var my_var_1 = 'some_default_value';
}
alert(my_var_1); // to view the variable value

var my_var_2 = this_js_script.attr('data-my_var_2');   
if (typeof my_var_2 === "undefined" ) {
   var my_var_2 = 'some_default_value';
}
alert(my_var_2); // to view the variable value

...etc...

Vlado
la source
1
Très belle solution, cela fonctionne même avec l'attribut async.
ViRuSTriNiTy
2
vieux mais pour ceux qui recherchent sur Google: c'est génial si vous devez contourner un CSP (politique de sécurité du contenu). Nous utilisons de nombreux scripts dans lesquels nous transmettons des chaînes déterminées à partir de ressources linguistiques et de clés API, etc. dans des fichiers JS.
monkeySeeMonkeyDo
2
Notez que vous pouvez également utiliser document.currentScript, voir caniuse.com/#feat=document-currentscript pour la compatibilité (ou utiliser un polyfill)
Yves M.
La $(..)syntaxe est jquery, n'oubliez pas de l'inclure.
Timo le
48

Une autre idée que j'ai rencontrée était d'assigner un idà l' <script>élément et de passer les arguments en tant data-*qu'attributs. La <script>balise résultante ressemblerait à ceci:

<script id="helper" data-name="helper" src="helper.js"></script>

Le script peut ensuite utiliser l'ID pour se localiser et analyser les arguments par programme. Compte tenu de la <script>balise précédente , le nom pourrait être récupéré comme ceci:

var name = document.getElementById("helper").getAttribute("data-name");

Nous obtenons name=helper

Nalini Wanjale
la source
4
Si vous ne voulez pas dépendre de l'attribut id, vous pouvez également faire en sorte que votre helper.jsscript parcoure toutes les scriptbalises de la page, en recherchant celle avec scr="helper.js", puis en extrayant data-name.
jvannistelrooy
1
@jvannistelrooy Je n'ai pas essayé mais je pense que l'on devrait pouvoir accéder au script via jquery et un sélecteur intelligent comme celui-ci: var this_js_script = $('script[src*=somefile]');(copié d'en haut )
LosManos
19

Consultez cette URL. Cela fonctionne parfaitement pour l'exigence.

http://feather.elektrum.org/book/src.html

Merci beaucoup à l'auteur. Pour une référence rapide, j'ai collé la logique principale ci-dessous:

var scripts = document.getElementsByTagName('script');
var myScript = scripts[ scripts.length - 1 ];

var queryString = myScript.src.replace(/^[^\?]+\??/,'');

var params = parseQuery( queryString );

function parseQuery ( query ) {
  var Params = new Object ();
  if ( ! query ) return Params; // return empty object
  var Pairs = query.split(/[;&]/);
  for ( var i = 0; i < Pairs.length; i++ ) {
    var KeyVal = Pairs[i].split('=');
    if ( ! KeyVal || KeyVal.length != 2 ) continue;
    var key = unescape( KeyVal[0] );
    var val = unescape( KeyVal[1] );
    val = val.replace(/\+/g, ' ');
    Params[key] = val;
  }
  return Params;
}
user378221
la source
1
J'ai aimé cette solution, mais je l'ai légèrement modifiée, pour utiliser l'identifiant de fragment, afin qu'elle ne casse pas les caches: var frag = myScript.src.replace (/ ^ [^ \ #] + \ #? /, '') ;
Mark Nottingham
Oh, et j'ai trouvé que mettre JSON là-dedans fonctionne, tant que vous le codez url.
Mark Nottingham
@ user378221 - êtes-vous sûr que cela obtiendra toujours le bon fichier? Je veux dire que cela suppose que ce fichier est le dernier. Que faire si plus de balises de script sont chargées avant que cela ne s'exécute? Je ne sais pas si cela est possible, mais je suppose que le document n'attend pas la fin de l'exécution du code pour charger d'autres balises?
Darius.V
Mieux vaut utiliser Utilisez decodeURI () ou decodeURIComponent () au lieu de unescape car cette fonction est obsolète.
Steve Childs
@ Darius.V Cela fonctionne pour les scripts non asynchrones. Je préfère utiliser l' option 1 avec l'option 5 comme solution de secours .
Luc
14

Vous utilisez des variables globales :-D.

Comme ça:

<script type="text/javascript">
   var obj1 = "somevalue";
   var obj2 = "someothervalue";
</script>
<script type="text/javascript" src="file.js"></script">

Le code JavaScript dans 'file.js' peut accéder à obj1et obj2sans problème.

EDIT Je veux juste ajouter que si 'file.js' veut vérifier si obj1et obj2a même été déclaré, vous pouvez utiliser la fonction suivante.

function IsDefined($Name) {
    return (window[$Name] != undefined);
}

J'espère que cela t'aides.

NawaMan
la source
Cela jettera une erreur non référencée dans IE. Le corps préféré de cette fonction est return typeof window[$Name] !== 'undefined';. Veuillez noter que vous passeriez à cette fonction une chaîne contenant le nom de la variable. Il est probablement plus facile de vérifier simplement si la variable existe dans le code appelant (cela ne peut pas être implémenté en tant que fonction) via if (typeof var !== 'undefined').
Anthony DiSanti
2
Oh, n'utilisez jamais IE. Désolé.
NawaMan
Oui, mais selon mon expérience avec JavaScript, j'utiliserais des variables globales uniquement pour les espaces de noms de modules et exposerais autant que nécessaire (comme le code d'initialisation, certaines propriétés), comme indiqué ici . Il est trop facile d'obtenir des conflits de noms dans des projets complexes ... passer des heures de votre temps et découvrir ensuite que vous avez déjà utilisé la variable ailleurs dans le projet.
Matt
Vous pouvez omettre le var.
Timo le
12

Voici une preuve de concept très précipitée.

Je suis sûr qu'il y a au moins 2 endroits où il peut y avoir des améliorations, et je suis également sûr que cela ne survivrait pas longtemps à l'état sauvage. Tout commentaire pour le rendre plus présentable ou utilisable est le bienvenu.

La clé définit un identifiant pour votre élément de script. Le seul problème est que cela signifie que vous ne pouvez appeler le script qu'une seule fois, car il recherche cet ID pour extraire la chaîne de requête. Cela pourrait être corrigé si, à la place, le script effectue une boucle sur tous les éléments de requête pour voir si l'un d'entre eux pointe vers lui, et si tel est le cas, utilise la dernière instance d'un tel élément de script. Quoi qu'il en soit, avec le code:

Script appelé:

window.onload = function() {
//Notice that both possible parameters are pre-defined.
//Which is probably not required if using proper object notation
//in query string, or if variable-variables are possible in js.
var header;
var text;

//script gets the src attribute based on ID of page's script element:
var requestURL = document.getElementById("myScript").getAttribute("src");

//next use substring() to get querystring part of src
var queryString = requestURL.substring(requestURL.indexOf("?") + 1, requestURL.length);

//Next split the querystring into array
var params = queryString.split("&");

//Next loop through params
for(var i = 0; i < params.length; i++){
 var name  = params[i].substring(0,params[i].indexOf("="));
 var value = params[i].substring(params[i].indexOf("=") + 1, params[i].length);

    //Test if value is a number. If not, wrap value with quotes:
    if(isNaN(parseInt(value))) {
  params[i] = params[i].replace(value, "'" + value + "'");
 }

    // Finally, use eval to set values of pre-defined variables:
 eval(params[i]);
}

//Output to test that it worked:
document.getElementById("docTitle").innerHTML = header;
document.getElementById("docText").innerHTML = text;
};

Script appelé via la page suivante:

<script id="myScript" type="text/javascript" 
        src="test.js?header=Test Page&text=This Works"></script>

<h1 id="docTitle"></h1>
<p id="docText"></p>
Anthony
la source
donc eval définira en fait la valeur comme variable globale ... plutôt cool.
rodmjay
1
eval is evil run away
Barry Chapman
@barrychapman - oh man, 2010. Je suis surpris qu'il n'y ait pas eu 10 autres drapeaux rouges là-dedans.
Anthony
9

pourrait être très simple

par exemple

<script src="js/myscript.js?id=123"></script>
<script>
    var queryString = $("script[src*='js/myscript.js']").attr('src').split('?')[1];
</script>

Vous pouvez ensuite convertir la chaîne de requête en json comme ci-dessous

var json = $.parseJSON('{"' 
            + queryString.replace(/&/g, '","').replace(/=/g, '":"') 
            + '"}');

et puis peut utiliser comme

console.log(json.id);
Tofeeq
la source
6

Cela peut être facilement fait si vous utilisez un framework Javascript comme jQuery. Ainsi,

var x = $('script:first').attr('src'); //Fetch the source in the first script tag
var params = x.split('?')[1]; //Get the params

Vous pouvez maintenant utiliser ces paramètres en les fractionnant en tant que paramètres variables.

Le même processus peut être fait sans aucun cadre, mais prendra quelques lignes de code supplémentaires.

GeekTantra
la source
2
Il existe également un plugin jQuery appelé parseQuery qui rend cela encore plus facile: plugins.jquery.com/project/parseQuery
Jimmy Cuadra
@JimmyCuadra - malheureusement, le lien que vous avez fourni est rompu. Mais j'ai trouvé une approche similaire dans jQuery lui-même: jQuery.param ()
Matt
1

Eh bien, vous pourriez avoir le fichier javascript en cours de construction par l'un des langages de script, en injectant vos variables dans le fichier à chaque demande. Vous devrez dire à votre serveur Web de ne pas distribuer de fichiers js de manière statique (l'utilisation de mod_rewrite suffirait).

Sachez cependant que vous perdez toute mise en cache de ces fichiers js car ils sont constamment modifiés.

Au revoir.

aefxx
la source
1
Je ne comprends pas pourquoi cela a été rejeté. C'est une solution parfaitement valable faisant appel à la logique côté serveur. Autant que je vois, l'OP ne l'exige pas pour être une solution entièrement javascript.
Anoyz
@aefxx - Vous avez raison. À l'intérieur du <script>bloc, vous pouvez utiliser quelque chose comme celui MyModule.Initialize( '<%: Model.SomeProperty%>', '<%: Model.SomeOtherProperty%>', ...);qui est rendu sur le serveur. Attention: <%:échappe la valeur, tandis que <%=envoie la valeur sans échappement. MyModuleserait un espace de noms (c'est-à-dire une variable à l'intérieur du fichier .js qui est auto-invoquée, qui expose la fonction Initialize). J'ai voté pour votre réponse, car c'est une contribution utile.
Matt
Remarque: Le concept, comment créer un module qui contient une propriété exposée est décrit plus en détail ici sur cette page.
Matt
1

Bonne question et réponses créatives mais ma suggestion est de paramétrer vos méthodes et cela devrait résoudre tous vos problèmes sans aucune astuce.

si vous avez une fonction:

function A()
{
    var val = external_value_from_query_string_or_global_param;
}

vous pouvez changer cela en:

function B(function_param)
{
    var val = function_param;
}

Je pense que c'est l'approche la plus naturelle, vous n'avez pas besoin de créer de documentation supplémentaire sur les `` paramètres de fichier '' et vous recevez la même chose. Ceci est particulièrement utile si vous autorisez d'autres développeurs à utiliser votre fichier js.

Dzida
la source
0

Non, vous ne pouvez pas vraiment faire cela en ajoutant des variables à la partie chaîne de requête de l'URL du fichier JS. Si c'est écrit la partie de code pour analyser la chaîne qui vous dérange, peut-être une autre façon serait de json encoder vos variables et de les mettre dans quelque chose comme l'attribut rel de la balise? Je ne sais pas à quel point cela est valable en termes de validation HTML, si c'est quelque chose qui vous inquiète beaucoup. Ensuite, il vous suffit de trouver l'attribut rel du script, puis de json_decode.

par exemple

<script type='text/javascript' src='file.js' rel='{"myvar":"somevalue","anothervar":"anothervalue"}'></script>
Dal Hundal
la source
2
Vous pouvez le faire avec la fausse chaîne de requête. Ce n'est tout simplement pas une bonne idée. Mais non plus. L'attribut rel est destiné à spécifier un type de relation de lien, et non à entasser des données aléatoires dans une balise de script.
Matthew Flaschen
1
^^ d'accord. Je ne vois aucun problème en le laissant tel quel et en utilisant l'approche <script> var obj1 = "somevalue" </script>, c'est la meilleure façon, et je crois - la bonne façon.
Dal Hundal
0

Ce n'est pas du HTML valide (je ne pense pas) mais cela semble fonctionner si vous créez un attribut personnalisé pour la balise de script dans votre page Web :

<script id="myScript" myCustomAttribute="some value" ....>

Accédez ensuite à l'attribut personnalisé dans le javascript:

var myVar = document.getElementById( "myScript" ).getAttribute( "myCustomAttribute" );

Je ne sais pas si c'est meilleur ou pire que l'analyse de la chaîne source du script.

Cor
la source
0

Si vous avez besoin d'un moyen qui passe la vérification CSP (qui interdit unsafe-inline), vous devez utiliser la méthode nonce pour ajouter une valeur unique à la fois au script et à la directive CSP ou écrire vos valeurs dans le html et les relire.

Méthode Nonce pour express.js:

const uuidv4 = require('uuid/v4')

app.use(function (req, res, next) {
  res.locals.nonce = uuidv4()
  next()
})

app.use(csp({
  directives: {
    scriptSrc: [
      "'self'",
      (req, res) => `'nonce-${res.locals.nonce}'`  // 'nonce-614d9122-d5b0-4760-aecf-3a5d17cf0ac9'
    ]
  }
}))

app.use(function (req, res) {
  res.end(`<script nonce="${res.locals.nonce}">alert(1 + 1);</script>`)
})

ou écrivez des valeurs dans la méthode html. dans ce cas en utilisant Jquery:

<div id="account" data-email="{{user.email}}"></div>
...


$(document).ready(() => {
    globalThis.EMAIL = $('#account').data('email');
}
David Dehghan
la source
0

Bien que cette question ait été posée il y a quelque temps, elle est toujours d'actualité. Ce n'est pas une approche triviale utilisant des paramètres de fichier de script, mais j'avais déjà des cas d'utilisation extrêmes où cette méthode était la plus adaptée.

Je suis tombé sur ce post pour trouver une meilleure solution que ce que j'avais écrit il y a quelque temps, avec l'espoir de trouver peut-être une fonctionnalité native ou quelque chose de similaire.
Je partagerai ma solution, jusqu'à ce qu'une meilleure soit mise en œuvre. Cela fonctionne sur la plupart des navigateurs modernes, peut-être même sur les plus anciens, je n'ai pas essayé.

Toutes les solutions ci-dessus, sont basées sur le fait qu'il doit être injecté avec une balise SCRIPT prédéfinie et bien marquée et s'appuyer entièrement sur l'implémentation HTML. Mais que se passe-t-il si le script est injecté dynamiquement, ou pire encore, que se passe-t-il si vous écrivez une bibliothèque, qui sera utilisée dans une variété de sites Web?
Dans ces cas et dans d'autres, toutes les réponses ci-dessus ne sont pas suffisantes et deviennent même trop compliquées.

Tout d'abord, essayons de comprendre ce que nous devons accomplir ici. Tout ce que nous devons faire est d'obtenir l'URL du script lui-même, à partir de là, c'est un jeu d'enfant.

Il existe en fait une astuce intéressante pour obtenir l'URL du script à partir du script lui-même. L'une des fonctionnalités de la Errorclasse native est la possibilité de fournir une trace de pile de «l'emplacement problématique», y compris la trace exacte du fichier jusqu'au dernier appel. Pour ce faire, j'utiliserai la propriété stack de l'instance Error, qui une fois créée, donnera la trace complète de la pile.

Voici comment fonctionne la magie:

// The pattern to split each row in the stack trace string
const STACK_TRACE_SPLIT_PATTERN = /(?:Error)?\n(?:\s*at\s+)?/;
// For browsers, like Chrome, IE, Edge and more.
const STACK_TRACE_ROW_PATTERN1 = /^.+?\s\((.+?):\d+:\d+\)$/;
// For browsers, like Firefox, Safari, some variants of Chrome and maybe other browsers.
const STACK_TRACE_ROW_PATTERN2 = /^(?:.*?@)?(.*?):\d+(?::\d+)?$/;

const getFileParams = () => {
    const stack = new Error().stack;
    const row = stack.split(STACK_TRACE_SPLIT_PATTERN, 2)[1];
    const [, url] = row.match(STACK_TRACE_ROW_PATTERN1) || row.match(STACK_TRACE_ROW_PATTERN2) || [];
    if (!url) {
        console.warn("Something went wrong. You should debug it and find out why.");
        return;
    }
    try {
        const urlObj = new URL(url);
        return urlObj.searchParams; // This feature doesn't exists in IE, in this case you should use urlObj.search and handle the query parsing by yourself.
    } catch (e) {
        console.warn(`The URL '${url}' is not valid.`);
    }
}

Maintenant, dans tous les cas d'appel de script, comme dans le cas OP:

<script type="text/javascript" src="file.js?obj1=somevalue&obj2=someothervalue"></script>

Dans le file.jsscript, vous pouvez maintenant faire:

const params = getFileParams();
console.log(params.get('obj2'));
// Prints: someothervalue

Cela fonctionnera également avec RequireJS et d'autres scripts de fichiers injectés dynamiquement.

Slavik Meltser
la source