5

While converting a script to not require jQuery, I've discovered that if I load my content (a partial html page with html and javascript) via XMLHttpRequest, the javascript in the partial page does not work. But if I load the partial using jQuery.load, it does work.

I've tried digging through jQuery's load function to see if it's doing anything special and nothing jumped out at me. I've been banging my head against the wall and searching for an answer for a couple of days now to no avail.

What am I doing wrong/how can I make it work like it does when loaded with jQuery.load?

EDIT

I got the XMLHttpRequest method to work by splitting out out my javascript from the html in the fragment and loading the javascript using the suggested technique here: https://stackoverflow.com/a/11695198/362958. However, that still does not provide an explanation of why jQuery.load works. Is jQuery umtimately parsing the HTML and doing the same thing for any scripts it finds within the content it loads?

I've set up a plunker (https://plnkr.co/edit/wE9RuULx251C5ARnUbCh) with the following code that demonstrates the issue. Note: once you load the fragment with jQuery, it will continue to work and you'll have to restart the plunk for the XMLHttpRequest method to fail again.

index.html:

<!DOCTYPE html>
<html>

  <head>
    <script data-require="jquery@*" data-semver="3.0.0" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>

  <body>

  <h3>Buttons</h3>  
    <div>
      <input type="button" value="Load with XMLHttpRequest" onclick="loadXMLDoc('ajaxContentDiv', 'fragmentToLoad.html');">  (Links do not work if loaded this way... Script from fragmentToLoad.html not loaded in DOM?) <br/><br/>
      <input type="button" value="Load with JQuery" onclick="jQuery('#ajaxContentDiv').load('fragmentToLoad.html');">  (Links will work if loaded this way)
    </div>
<br/>
<br/>
<br/>
<div id="ajaxContentDiv">Content will load here...</div>

  </body>

</html>

script.js:

function loadXMLDoc(targetDivName, url) {

  var xmlhttp = new XMLHttpRequest();
  xmlhttp.open("GET", url, true);
  xmlhttp.onreadystatechange = function () {
      if (xmlhttp.readyState == XMLHttpRequest.DONE) {
          if (xmlhttp.status == 200) {
              document.getElementById(targetDivName).innerHTML = xmlhttp.responseText;
          }
      }
  };
  xmlhttp.send();
}

fragmentToLoad.html:

<div id="divToBeUpdated">
  <span id="stringBox">String here</span>
</div>
<br/>
<h3>Links</h3>
<div>
  <a href="#" onclick="updateDiv('Hello World 1');">Link 1</a><br>
  <a href="#" onclick="updateDiv('Hello World 2');">Link 2</a><br>
  <a href="#" onclick="updateDiv('Hello World 3');">Link 3</a><br>
</div>

<script>
  function updateDiv(string){
    var stringBox = document.getElementById('stringBox');
    stringBox.innerHTML = string;
  }
</script>
10
  • updateDiv function is not defined at html at global attribute event when document is loaded. Commented Dec 12, 2016 at 22:08
  • 1
    @Barmar OP's Question is slightly more complicated than linked Question and Answers. That is, separating html elements within fragmentToLoad.html that are not <script> from <script> element within fragmentToLoad.html; also, OP needs to attach click event listener to each a element, which linked Question does not address. From perspective here, the linked Question is not an "exact duplicate". Commented Dec 12, 2016 at 22:45
  • 1
    jQuery parses the HTML and searches for <script> tags, and emulates them itself because assigning to .innerHTML doesn't execute scripts. Commented Dec 12, 2016 at 22:50
  • OP is attempting to not use jQuery. How does linked Question address attaching click event to dynamic <a> elements appended to document? Commented Dec 12, 2016 at 22:50
  • 3
    The reason nothing in the .load() function jumped out at you is that it's done in the .html() function. .load() is simply a shortcut that performs an AJAX call and then uses .html() on the result. Finding the actual code that performs the <script> emulation can be difficult, because it's buried way down. Commented Dec 12, 2016 at 23:00

1 Answer 1

3

You can use single .html file, and you are on the correct track by splitting the html content - though you can also split the html content of a single file, rather than requesting two files. @Barmar explains the functionality of jQuery's .load() method at this comment.

script.js

function loadXMLDoc(targetDivName, url) {

  var xmlhttp = new XMLHttpRequest();
  xmlhttp.open("GET", url, true);
  xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState == XMLHttpRequest.DONE) {
      if (xmlhttp.status == 200) {
        // create a `div` elemenent, append response to `div` element
        // get specific elements by `id`, append `script` element to `document.body`
        var content = document.createElement("div");
        content.innerHTML = xmlhttp.responseText
        var div = content.querySelector("#htmlContent");
        var contentScript = content.querySelector("#contentScript");
        var script = document.createElement("script");
        script.textContent = contentScript.textContent;
        document.getElementById(targetDivName).innerHTML = div.innerHTML;
        document.body.appendChild(script);
      }
    }
  };
  xmlhttp.send();
}

fragmentToLoad.html

<div id="htmlContent">
  <div id="divToBeUpdated">
    <span id="stringBox">String here</span>
  </div>
  <br/>
  <h3>Links</h3>
  <div class="links">
    <a href="#">Link 1</a>
    <br>
    <a href="#">Link 2</a>
    <br>
    <a href="#">Link 3</a>
    <br>
  </div>
</div>
<script type="text/javascript" id="contentScript">
  function updateDiv(string) {
    var stringBox = document.getElementById('stringBox');
    stringBox.innerHTML = string;
  }
  // attach `click` event to `.link a` elements here
  var links = document.querySelectorAll(".links a");
  for (var i = 0; i < links.length; i++) {
    (function(link, i) {
      console.log(i)
      link.addEventListener("click", function(e) {
        e.preventDefault();
        updateDiv("Hello World " + i)
      })
    })(links[i], i)
  }
</script>

plnkr https://plnkr.co/edit/7fLtGRSV7WlH2enLbwSW?p=preview

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for the example and pointing out that the content can still live in a single document by using selectors. In my final code, I was still able to use onclick="updateDiv(.... instead of event handlers attached to links.
@John Am hesitant to delve into "good practice", "bad practice" estimations; fwiw, see stackoverflow.com/questions/36388227/… . Is current Question resolved?
Good point. HTML/Javascript is not my native homeland so I've lost touch with some of the best practices these days. The only problem I have with the method you proposed is that my actual code will be setting a src element on an audio tag and displaying the title of the audio to be played in a div. How would you convert these: <a href="#" onclick="playAudio('/AudioFile321.mp3','Audio File 321');">Play</a> and <a href="#" onclick="playAudio('/HelloWorld123.mp3','Hello World 123');">Play</a>
@John You can store the values in an array var arr = [['/AudioFile321.mp3','Audio File 321'], ['/HelloWorld123.mp3','Hello World 123']], pass the element of array at i to immediately invoked function expression within for loop then utilize Function.prototype.apply() at call to updateDiv within event handler (function(link, array) {link.addEventListener("click", function(e) {e.preventDefault();updateDiv.apply(null, array)})})(links[i], arr[i]).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.