0

I'm trying to do a simple file upload in my HTML and Javascript.

The way that I've set it up is by having an <input type="file" id="file-upload-input"/> with a loadFile function subscribed to its onChange event. (I have this input invisible just because it looks bad.)

I then have a <button/> with the function handleBrowseClick subscribed to its onclick event.

Both of the above controls seem to work fine: when I click on my <button/>, handleBrowseClick is called, which clicks my <input type="file">, causing the file dialog to open. However, an issue occurs once I select a file and press "open" in the dialog (which triggers the <input type="file">'s onChange event, thus calling my loadFile function).

As you can see in the Javascript, at this point to try to read the file by getting my <input type="file">, accessing its files property, and trying to get the first element of that array so that I can read from it with the FileReader (which works fine).

However, no matter what file I select, the files array is always empty (and thus my alert prints out "# Files Selected: 0")! After extensive debugging and narrowing the problem, I discovered that in handleBrowseClick(), if I remove my debugging code that slightly modifies the body of the HTML, then the file upload suddenly works perfectly! (The alert prints out "# Files Selected: 1".)

So here's my question: why would a line that

  • only modifies the body of the HTML (but doesn't affect any of the controls)
  • is irrelevant to the file upload functions

prevent my file upload from working?

function handleBrowseClick()
{
    document.getElementById("upload-file-input").click();
    // if I comment out this line, then the file upload works
    document.body.innerHTML += "<br/>Browsing...";
}
function loadFile()
{
    var elem = document.getElementById("upload-file-input");
    var files = elem.files;
    alert("# Files Selected: " + files.length);
    var file = files[0];
    if (!file) return;
    
    var reader = new FileReader();
    reader.onload = function()
    {
	// do stuff with reader.result
	// example:
	document.getElementById("file-content").innerHTML = reader.result;
    };
    reader.readAsText(file);
}
<!DOCTYPE html>
<html>
  <head>
    <script src="course_chart.js"></script>
  </head>
  <body>
    <input type="file" accept=".txt" id="upload-file-input" style="display: none" onChange="javascript:loadFile()"/>
    <button onclick="javascript:handleBrowseClick()" id="choose-file-button">Load File</button>
    <div id="file-content"></div>
  </body>
</html>

5
  • The problem exists in Chrome, but not in IE, apparently. Commented Jul 15, 2015 at 22:52
  • If you put the "Browsing..." text inside the file-content DIV, the problem is gone too... Commented Jul 15, 2015 at 22:56
  • @m69 The "Browsing..." text is an example. I actually have some debugging stuff that can't be static, and needs to be dynamically generated by (and inserted into the HTML by) the Javascript. Commented Jul 15, 2015 at 22:59
  • @m69 Also, my question is not about how to fix the issue (I stated in my question that I already figured that out), but is instead about why the issue exists. Commented Jul 15, 2015 at 23:05
  • I meant, if you change the body's innerHTML, the problem exists, if you change a DIV's innerHTML, the problem is gone. Just trying to add some info to help find the cause. Commented Jul 15, 2015 at 23:43

1 Answer 1

4

Using innerHTML += interprets the HTML, it's not only adding some text, it's removing everything and recreating the whole HTML. See https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML:

For that reason, it is recommended you not use innerHTML when inserting plain text; instead, use node.textContent. This doesn't interpret the passed content as HTML, but instead inserts it as raw text.

So in your case the input element before you call is not the same as the one after. You can see in the jsfiddle below, comparing the node before your call and after, when you use innerHTML += you'll see it's not the same element, even if the HTML is the same.

http://jsfiddle.net/6nja0v6b/1/

EDIT

The quote in original answer can lead to confusion, while it describes what innerHTML does, the context is not the one of the question. A more precise description can be found here: https://dvcs.w3.org/hg/innerhtml/raw-file/tip/index.html#widl-Element-innerHTML. Especially:

element . innerHTML [ = value ]

Returns a fragment of HTML or XML that represents the element's contents.

Can be set, to replace the contents of the element with nodes parsed from the given string.

And

On setting, these steps must be run:

Let fragment be the result of invoking the fragment parsing algorithm with the new value as markup, and the context object as the context element. Replace all with fragment within the context object.

This can be illustrated quite simply by adding an eventListener on a node, then calling innerHTML to insert exactly the same HTML on that node. While the document will look the same, behavior is not consistent, since innerHTML recreates the node structure, the node before innerHTML call isn't the same as after. See snippet:

var container = document.querySelector("#container");
var testElem = document.querySelector("#testElement");
var replaceBtn = document.querySelector("#replaceHtml");
var compareBtn = document.querySelector("#compare");

//We add the listener on original testElem
testElement.addEventListener('click', function(e) {
  alert("eventListener is working");
})

//After using innerHTML, event listener won't work and compare will be false
replaceBtn.onclick = function() {
  //This doesn't change the HTML, but replaces the node
  container.innerHTML = container.innerHTML;
}

//Use this to compare original testElement to new one. After innerHTML is used, it's not the same node.
compare.onclick = function() {
  alert("Is testElem \=\=\= document.querySelector(\"testElement\"): " + (testElem === document.querySelector("#testElement")));
}
#testElement {
  width: 100px;
  height: 100px;
  background-color: blue;
}
<div id="container">
  <div id="testElement"></div>
</div>
<button id="replaceHtml">Replace HTML</button>
<button id="compare">Compare nodes</button>

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

2 Comments

The context for your quote is incorrect... what it's saying is For that reason ["that reason" being security vulnerabilities in cross-site scripting], it is recommended that you not use innerHTML.... Although your answer makes sense, I do really want to see some evidence.
you're right about the context, but 'This doesn't interpret the passed content as HTML, but instead inserts it as raw text.' seemed the simpler way to describe what innerHTML does. I've added clarification.

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.