Comment faire attendre l'exécution du script jusqu'à ce que jquery soit chargé

106

J'ai un problème où la page se charge si rapidement, que jquery n'a pas fini de se charger avant d'être appelé par un script ultérieur. Existe-t-il un moyen de vérifier l'existence de jquery et s'il n'existe pas, attendez un moment, puis réessayez?


En réponse aux réponses / commentaires ci-dessous, je publie une partie du balisage.

La situation ... asp.net masterpage et childpage.

Dans la masterpage, j'ai une référence à jquery. Ensuite, dans la page de contenu, j'ai une référence au script spécifique à la page. Lorsque le script spécifique à la page est en cours de chargement, il se plaint que "$ n'est pas défini".

J'ai mis des alertes à plusieurs endroits dans le balisage pour voir l'ordre dans lequel les choses se déclenchaient et j'ai confirmé qu'il se déclenche dans cet ordre:

  1. En-tête de page maître.
  2. Bloc de contenu 1 de la page enfant (situé à l'intérieur de l'en-tête de la page maître, mais après l'appel des scripts de la page maître).
  3. Bloc de contenu de la page enfant 2.

Voici le balisage en haut de la page principale:

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="SiteMaster" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>Reporting Portal</title>
    <link href="~/Styles/site.css" rel="stylesheet" type="text/css" />
    <link href="~/Styles/red/red.css" rel="stylesheet" type="text/css" />
    <script type="text/Scripts" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script> 
    <script type="text/Scripts" language="javascript" src="../Scripts/jquery.dropdownPlain.js"></script>
    <script type="text/Scripts" language="javascript" src="../Scripts/facebox.js"></script>
    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
    <asp:ContentPlaceHolder ID="head" runat="server">
    </asp:ContentPlaceHolder>
</head>

Ensuite, dans le corps de la page maître, il y a un ContentPlaceHolder supplémentaire:

 <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
                </asp:ContentPlaceHolder>

Dans la page enfant, cela ressemble à ceci:

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Dashboard.aspx.cs" Inherits="Data.Dashboard" %>
<%@ Register src="../userControls/ucDropdownMenu.ascx" tagname="ucDropdownMenu" tagprefix="uc1" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
    <link rel="stylesheet" type="text/css" href="../Styles/paserMap.css" />
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
***CONTENT HERE***
    <script src="../Scripts/Dashboard.js" type="text/javascript"></script>
</asp:Content>

Voici le contenu du fichier "../Script/Dashboard.js":

    $(document).ready(function () {

    $('.tgl:first').show(); // Show the first div

    //Description: East panel parent tab navigation
    $('.tabNav label').click(function () {
        $('.tabNav li').removeClass('active')
        $(this).parent().addClass('active');

        var index = $(this).parent('li').index();
        var divToggle = $('.ui-layout-content').children('div.tgl');

        //hide all subToggle divs
        divToggle.hide();
        divToggle.eq(index).show();
    });

});
Amanda Kitson
la source
4
utilisez-vous $(document).ready(function(){...});?
rownage le
Si vous chargez des scripts avec de simples <script src="...">balises, cela ne peut pas arriver. Utilisez-vous quelque chose comme RequireJS ou LABjs?
Pointy le
exécutez-vous le script suivant dans le $(document).ready()ou $(window).load()? Pouvons-nous voir un exemple?
John Hartsock
1
@AmandaMyer maintenant, ignorez toutes les suggestions sur l'utilisation du document prêt, puisque vous le faites déjà, mais je parie que c'est le type MIME qui les empêche de se charger de manière synchrone
Sander
2
Essayez de changer type="text/Scripts"à type="text/javascript", ou se débarrasser de tout cela ensemble , il est pas nécessaire plus, se débarrasser aussi du language="javascript. Autant commencer à essayer des choses.
Jack le

Réponses:

11

Éditer

Pourriez-vous essayer le type correct pour vos balises de script? Je vois que vous utilisez text/Scripts, qui n'est pas le bon type MIME pour javascript.

Utilisez ceci:

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script> 
<script type="text/javascript" src="../Scripts/jquery.dropdownPlain.js"></script>
<script type="text/javascript" src="../Scripts/facebox.js"></script>

terminer la modification

ou vous pouvez jeter un œil à require.js qui est un chargeur pour votre code javascript.

en fonction de votre projet, cela pourrait cependant être un peu exagéré

Ponceuse
la source
205

En retard à la fête, et similaire à la question de Briguy37, mais pour référence future, j'utilise la méthode suivante et transmets les fonctions que je veux différer jusqu'à ce que jQuery soit chargé:

function defer(method) {
    if (window.jQuery) {
        method();
    } else {
        setTimeout(function() { defer(method) }, 50);
    }
}

Il appellera récursivement la méthode defer toutes les 50 ms jusqu'à ce qu'il window.jQueryexiste, moment où il sort et appellemethod()

Un exemple avec une fonction anonyme:

defer(function () {
    alert("jQuery is now loaded");
});
Darbio
la source
2
Cela n'a pas fonctionné pour moi. À l'intérieur de la méthode, $ n'a pas été défini ... je ne sais pas encore pourquoi.
Aaron Lifshin
C'était une bonne solution pour faire fonctionner du code personnalisé dans un site conçu par quelqu'un d'autre dans Adobe Muse.
Buggabill
@gidim Ce ne sera pas le cas, car c'est une minuterie et non une boucle. Mais il continuera à fonctionner tant que l'utilisateur reste sur la page.
Micros
Il serait préférable d'étudier l'ordre de chargement des scripts. J'ai trouvé que si la scriptbalise qui charge jQuery avait un deferattribut, cela causerait le problème en ne se chargeant que plus tard, malgré l'ordre défini par le code des scripts.
ADTC
19

vous pouvez utiliser l'attribut defer pour charger le script à la fin.

<script type='text/javascript' src='myscript.js' defer='defer'></script>

mais normalement charger votre script dans le bon ordre devrait faire l'affaire, alors assurez-vous de placer l'inclusion jquery avant votre propre script

Si votre code se trouve dans la page et non dans un fichier js séparé, vous ne devez donc exécuter votre script qu'une fois le document prêt et l'encapsulation de votre code comme celle-ci devrait également fonctionner:

$(function(){
//here goes your code
});
Malko
la source
C'est ok tant que vous avez une seule dépendance
Guillaume Massé
17

Encore une autre façon de le faire, bien que la méthode de report de Darbio soit plus flexible.

(function() {
  var nTimer = setInterval(function() {
    if (window.jQuery) {
      // Do something with jQuery
      clearInterval(nTimer);
    }
  }, 100);
})();
thdoan
la source
16

le moyen le plus simple et le plus sûr est d'utiliser quelque chose comme ceci:

var waitForJQuery = setInterval(function () {
    if (typeof $ != 'undefined') {

        // place your code here.

        clearInterval(waitForJQuery);
    }
}, 10);
moisrex
la source
2
Solution Genius. Merci
Diego Fortes
13

Vous pouvez essayer l'événement onload. Il s'est déclenché lorsque tous les scripts ont été chargés:

window.onload = function () {
   //jquery ready for use here
}

Mais gardez à l'esprit que vous pouvez remplacer d'autres scripts où window.onload utilise.

Nigrimmist
la source
cela fonctionne mais vous verrez probablement un flash de contenu non stylé si votre jquery change l'apparence de la page
Matthew Lock
1
Vous pouvez ajouter un écouteur d'événements pour éviter de surcharger d'autres scripts: stackoverflow.com/a/15564394/32453 FWIW '
rogerdpack
@rogerdpack d'accord, c'est une meilleure solution.
Nigrimmist
10

J'ai trouvé que la solution suggérée ne fonctionnait qu'en tenant compte du code asynchrone. Voici la version qui fonctionnerait dans les deux cas:

document.addEventListener('DOMContentLoaded', function load() {
    if (!window.jQuery) return setTimeout(load, 50);
    //your synchronous or asynchronous jQuery-related code
}, false);
Annarfych
la source
Merci, vérifier une fois en charge et uniquement en tant que démarrage de secours en boucle toutes les 50 ms est bien meilleur que de simplement le laisser en boucle avec setInterval comme la réponse la plus votée. Dans le meilleur des cas, vous avez un délai de 0 ms, au lieu du cas moyen de 25 ms de la première réponse.
Luc
Agréable et élégant - j'aime la façon dont vous transmettez la loadfonction, mais vous la réutilisez également setTimeout.
Nemesarial
6

C'est un problème courant, imaginez que vous utilisez un moteur de création de modèles PHP sympa, donc vous avez votre mise en page de base:

HEADER
BODY ==> dynamic CONTENT/PAGE
FOOTER

Et bien sûr, vous lisez quelque part, il vaut mieux charger Javascript en bas de page, pour que votre contenu dynamique ne sache pas qui est jQuery (ou le $).

Vous avez aussi lu quelque part que le petit Javascript en ligne est bon, alors imaginez que vous avez besoin de jQuery dans une page, baboom, $ n'est pas défini (.. encore ^^).

J'adore la solution fournie par Facebook

window.fbAsyncInit = function() { alert('FB is ready !'); }

Donc, en tant que programmeur paresseux (je devrais dire un bon programmeur ^^), vous pouvez utiliser un équivalent (dans votre page):

window.jqReady = function() {}

Et ajoutez en bas de votre mise en page, après jQuery, incluez

if (window.hasOwnProperty('jqReady')) $(function() {window.jqReady();});
Thomas Decaux
la source
Merci! C'est exactement le cas auquel je suis confronté.
KumZ
Ou déplacez la charge jquery vers le haut :) stackoverflow.com/a/11012073/32453
rogerdpack
5

Plutôt que d '«attendre» (ce qui est généralement fait en utilisant setTimeout), vous pouvez également utiliser la définition de l'objet jQuery dans la fenêtre elle-même comme un hook pour exécuter votre code qui en dépend. Ceci est réalisable grâce à une définition de propriété, définie à l'aide de Object.defineProperty.

(function(){
  var _jQuery;
  Object.defineProperty(window, 'jQuery', {
    get: function() { return _jQuery; },
    set: function($) {
      _jQuery = $;

      // put code or call to function that uses jQuery here

    }
  });
})();
Protecteur un
la source
cela serait vaguement utile si jQuery définissait réellement une propriété de fenêtre, mais cela ne semble pas le cas?
Jim
Il ne définit pas vraiment une propriété, mais il définit window.jQueryune valeur, en définissant la jQueryvariable dans la portée globale (ie window). Cela déclenchera la fonction de définition de propriété sur l'objet de fenêtre défini dans le code.
Protector one
Cela ne fonctionne pas si la propriété jQuery est définie avant l'exécution de ce code; c'est-à-dire si votre jquery.js différé se charge avant que ce code ne soit chargé. Une solution consiste à placer votre jquery.js différé avant </body>au lieu d'avant</head>
user36388
@user: exécutez simplement le code avant de charger jQuery (différé ou non). Peu importe où vous le chargez, juste que mon fragment de code précède.
Protector one
1
Ceci est très utile si vous pouvez garantir que les scripts qui l'utilisent viennent avant l'inclusion de jQuery dans votre document. Aucune des inefficacités ou scénarios de race-case des approches en boucle ci-dessus non plus.
RedYetiCo
4

Utilisation:

$(document).ready(function() {
    // put all your jQuery goodness in here.
});

Consultez ceci pour plus d'informations: http://www.learningjquery.com/2006/09/introducing-document-ready

Remarque: cela devrait fonctionner tant que l'importation de script pour votre bibliothèque JQuery est au-dessus de cet appel.

Mettre à jour:

Si, pour une raison quelconque, votre code ne se charge pas de manière synchrone (ce que je n'ai jamais rencontré, mais apparemment peut-être possible à partir du commentaire ci-dessous ne devrait pas se produire), vous pouvez le coder comme suit.

function yourFunctionToRun(){
    //Your JQuery goodness here
}

function runYourFunctionWhenJQueryIsLoaded() {
    if (window.$){
        //possibly some other JQuery checks to make sure that everything is loaded here

        yourFunctionToRun();
    } else {
        setTimeout(runYourFunctionWhenJQueryIsLoaded, 50);
    }
}

runYourFunctionWhenJQueryIsLoaded();
Briguy37
la source
6
cette attente est jusqu'à ce que le dom soit prêt, pas jusqu'à ce que jquery soit chargé. il a probablement 2 fichiers de script, 1 à partir d'un CDN (jquery de google et un plugin localement) où le 1 est chargé plus tôt que l'autre .... votre code ne résout pas cela, si le plugin est chargé plus rapidement que jquery lui
Sander
2
Je ne suis pas d'accord avec vous @Sander, cela devrait fonctionner car le chargement est synchrone
malko
vous avez raison, mon hypothèse selon laquelle ils sont chargés de manière asynchrone s'ils proviennent d'un domaine différent est complètement faux! excusez-moi pour ça
Sander
Cela lèvera une exception si jquery n'est pas disponible, le faisant ne jamais entrer dans l'instruction else qu'il semble. Je suppose que ça devrait être un essai ou quelque chose.
Sam Stoelinga
@SamStoelinga: Merci, cela devrait maintenant être corrigé.
Briguy37
3

Je n'aime pas trop les trucs d'intervalle. Quand je veux différer jquery, ou quoi que ce soit en fait, ça se passe généralement comme ça.

Commencer avec:

<html>
 <head>
  <script>var $d=[];var $=(n)=>{$d.push(n)}</script>
 </head>

Ensuite:

 <body>
  <div id="thediv"></div>

  <script>
    $(function(){
       $('#thediv').html('thecode');
    });
  </script>

  <script src="http://code.jquery.com/jquery-3.2.1.min.js" type="text/javascript"></script>

Puis finalement:

  <script>for(var f in $d){$d[f]();}</script>
 </body>
<html>

Ou la version la moins époustouflante:

<script>var def=[];function defer(n){def.push(n)}</script>
<script>
defer(function(){
   $('#thediv').html('thecode');
});
</script>
<script src="http://code.jquery.com/jquery-3.2.1.min.js" type="text/javascript"></script>
<script>for(var f in def){def[f]();}</script>

Et dans le cas de l'async, vous pouvez exécuter les fonctions poussées sur jquery onload.

<script async onload="for(var f in def){def[f]();}" 
src="jquery.min.js" type="text/javascript"></script>

Alternativement:

function loadscript(src, callback){
  var script = document.createElement('script');
  script.src = src
  script.async = true;
  script.onload = callback;
  document.body.appendChild(script);
};
loadscript("jquery.min", function(){for(var f in def){def[f]();}});
Simon
la source
3

Développons defer()Dario pour être plus réutilisable.

function defer(toWaitFor, method) {
    if (window[toWaitFor]) {
        method();
    } else {
        setTimeout(function () { defer(toWaitFor, method) }, 50);
    }
}

Qui est alors exécuté:

function waitFor() {
    defer('jQuery', () => {console.log('jq done')});
    defer('utag', () => {console.log('utag done')});
}
mewc
la source
2

Je ne pense pas que ce soit votre problème. Le chargement du script est synchrone par défaut, donc à moins que vous n'utilisiez l' deferattribut ou que vous ne chargiez jQuery lui-même via une autre requête AJAX, votre problème ressemble probablement plus à un 404. Pouvez-vous montrer votre balisage et nous faire savoir si vous voyez quelque chose de suspect dans Firebug ou inspecteur Web?

jmar777
la source
1

Vérifie ça:

https://jsfiddle.net/neohunter/ey2pqt5z/

Cela créera un faux objet jQuery, qui vous permettra d'utiliser les méthodes onload de jquery, et elles seront exécutées dès que jquery sera chargé.

Ce n'est pas parfait.

// This have to be on <HEAD> preferibly inline
var delayed_jquery = [];
jQuery = function() {
  if (typeof arguments[0] == "function") {
    jQuery(document).ready(arguments[0]);
  } else {
    return {
      ready: function(fn) {
        console.log("registering function");
        delayed_jquery.push(fn);
      }
    }
  }
};
$ = jQuery;
var waitForLoad = function() {
  if (typeof jQuery.fn != "undefined") {
    console.log("jquery loaded!!!");
    for (k in delayed_jquery) {
      delayed_jquery[k]();
    }
  } else {
    console.log("jquery not loaded..");
    window.setTimeout(waitForLoad, 500);
  }
};
window.setTimeout(waitForLoad, 500);
// end



// now lets use jQuery (the fake version)
jQuery(document).ready(function() {
  alert('Jquery now exists!');
});

jQuery(function() {
  alert('Jquery now exists, this is using an alternative call');
})

// And lets load the real jquery after 3 seconds..
window.setTimeout(function() {
  var newscript = document.createElement('script');
  newscript.type = 'text/javascript';
  newscript.async = true;
  newscript.src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js';
  (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(newscript);
}, 3000);

Arnold Roa
la source
c'est une solution sans tracas pour enregistrer les fonctions de document prêt merci
zanedev
0

Une note tangentielle sur les approches ici qui utilisent la charge setTimeoutou setInterval. Dans ces cas, il est possible que lorsque votre vérification s'exécute à nouveau, le DOM soit déjà chargé et l' DOMContentLoadedévénement du navigateur ait été déclenché, vous ne pouvez donc pas détecter cet événement de manière fiable en utilisant ces approches. Ce que j'ai trouvé, c'est que jQuery readyfonctionne toujours, vous pouvez donc intégrer votre

jQuery(document).ready(function ($) { ... }

à l'intérieur de votre setTimeoutou setIntervalet tout devrait fonctionner normalement.

Richard J
la source