4

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>
Community
  • 1
  • 1
John
  • 235
  • 4
  • 10
  • `updateDiv` function is not defined at `html` at global attribute event when `document` is loaded. – guest271314 Dec 12 '16 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 ` – guest271314 Dec 12 '16 at 22:45
  • 1
    jQuery parses the HTML and searches for ` – Barmar Dec 12 '16 at 22:50
  • OP is attempting to _not_ use jQuery. How does linked Question address attaching `click` event to dynamic `` elements appended to `document`? – guest271314 Dec 12 '16 at 22:50
  • @guest271314 It doesn't. I don't see anything about that in the question. Binding event handlers to dynamic elements is addressed in http://stackoverflow.com/questions/203198/event-binding-on-dynamically-created-elements. It's mostly jQuery solutions, but there are some plain-JS answers. – Barmar Dec 12 '16 at 22:55
  • Using the link provided by @Barmar I was able to get something working but only by splitting my html and javascript into separate documents and loading them separately. It's not the best solution but it's definitely better than not working or requiring jquery. – John Dec 12 '16 at 22:56
  • @John You have edited the original Question. What is your current Question? How jQuery's `.load()` method works internally? Or, how to produce the same affect as `jQuery.load()` without using jQuery and using single `.html` document which includes ` – guest271314 Dec 12 '16 at 22:56
  • @Barmar OP edited the original Question between last comment and present, as Question was being reopened. Not entirely sure what Question is now? – guest271314 Dec 12 '16 at 22:58
  • 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 ` – Barmar Dec 12 '16 at 23:00
  • @guest271314 - both, really. But it seems that jquery is parsing the html and treating scripts specially so that piece can be considered answered. – John Dec 12 '16 at 23:00

1 Answers1

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

Community
  • 1
  • 1
guest271314
  • 1
  • 15
  • 104
  • 177
  • 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 Dec 12 '16 at 23:15
  • @John Am hesitant to delve into "good practice", "bad practice" estimations; fwiw, see http://stackoverflow.com/questions/36388227/when-did-using-global-event-handler-attributes-within-html-become-considered-ba . Is current Question resolved? – guest271314 Dec 12 '16 at 23:18
  • 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: Play and Play – John Dec 12 '16 at 23:47
  • 1
    @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])`. – guest271314 Dec 13 '16 at 00:07