I have been working on a complex project for the past few weeks when I encountered this bizarre bug. I have since isolated my problem in the code below, which is the smallest possible valid HTML and JS that reproduces it:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Chat</title>
<script type="text/javascript">
//<![CDATA[
function refresh()
{
var old = document.getElementById("conversation").innerHTML;
var message = '<img/>';
if(old != message) {
alert("Old:\n" + old);
alert("New:\n" + message);
//alert("Refreshed!");
document.getElementById("conversation").innerHTML = message;
}
}
//]]>
</script>
</head>
<body onload="refresh()">
<div id="conversation"></div>
<script type="text/javascript">var myVar = setInterval(function(){refresh();},1000);</script>
</body>
</html>
Every second, the function refresh() checks to see if the code in the div is different than the stored string. If it is different, it replaces the div's contents with the string. However, each time it checks, it sees the contents and the stored string are different. Although the string is <img/>, the innerHTML is returned as <img>. I figured out that for any self-closing tag, it automatically removes the slash. For a tag that isn't supposed to be self-closing (like <i>), it automatically splits it into two tags (like <i></i>). For any other tag or text, it does nothing.
I don't really understand why the div's contents are changing at all. If anyone can explain why, I would appreciated it. If someone can even provide a possible solution, I would be grateful.
That's because the browser is using an HTML parser, not an XML parser for the page, that's why it will remove the closing slash.
I'm a relative novice myself but I think what Matt Ball is referring to is the fact that you are creating a string when you should be creating a new DOM element So one simple change to your function would be:
var convo = document.getElementById("conversation");
function refresh() {
var old = convo.innerHTML;
var message = document.createElement("IMG");
if (old != message) {
alert("Old:\n" + old);
alert("New:\n" + message);
//alert("Refreshed!");
convo.innerHTML = message;
}
}
Here is a FIDDLE
Note: I added the global convo variable to reduce keystrokes and use document.createElemet instead of innerHTML to add the new image element to the DOM. You can then call upon that image element inside or outside your function using the firstChild method like this:
convo.firstChild;
Related
I would like to globally redefine a createElement method, but unfortunately it only works in a main document, and is ignored in iframes. Let's take this example code:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script>
old_createElement = document.createElement;
document.createElement = function(el_type) {
new_el = old_createElement.call(this, el_type);
new_el.style.color="red";
return new_el;
};
</script>
</head>
<body bgcolor="white">
<iframe id="iframe1"></iframe>
<script type="text/javascript">
window.setTimeout(function(){
iframe_el = document.getElementById("iframe1").contentDocument.createElement("div");
iframe_el.innerHTML = 'inside iframe';
document.getElementById("iframe1").contentDocument.body.appendChild(iframe_el);
},50);
no_iframe_el=document.createElement('div');
no_iframe_el.innerHTML = 'outside of iframe';
document.body.appendChild(no_iframe_el);
</script>
</body>
</html>
When i open in in a browser, the element created in a main document has red color, as expected, but the one in the iframe is black.
The problem is that I only have control on the script contained in the HEAD section of the document. In other words, i don't know how many iframes there will be later on in the HTML source, or how they will be names, or if they are added via user's Javascript.
My question is: how can i change the method globally, so all elements created in iframes also use this new style?
Thanks a lot!
Each frame has it's own separate Javascript context. If you want to change that frame's context, you have to do it specifically for that frame.
In your specific example, each frame has its own document object so it should be no surprise that each document has its own .createElement property.
You cannot generically change things in a way that will affect all frames. And, in fact if it's a cross-origin frame, you can't change it at all.
This is a piece of code that links to a javascript file that generates some fake tweets, along with some self made comments that attempt to explain to myself what is happening. I was wondering what the purpose of $body.html(''); was. It seems like it just clears the contents of the body, which is already empty except for the javascript that is present. Wouldn't this also clear the actual script that inside the body as well? i.e., why doesn't the whole script just vanish when we reach that line. I'm guessing that the function is executed in its entirety before the body is cleared? Just looking for a little illumination, I guess, on function execution.
<!DOCTYPE html>
<html>
<head>
<script src="jquery.js"></script>
<script src="data_generator.js"></script>
</head>
<body>
<script>
$(document).ready(function(){ // calls function only after dom is loaded
var $body = $('body'); // selects html body tag, stores in $body
$body.html(''); // clears body?
var index = streams.home.length - 1; // sets index to length of streams array
while(index >= 0){
var tweet = streams.home[index]; // gets a tweet string
var $tweet = $('<div></div>'); // $tweet is a div element
$tweet.text('#' + tweet.user + ': ' + tweet.message); // add formatted tweet to div
$tweet.appendTo($body); // add tweet to body
index -= 1; // rinse, repeat
}
});
</script>
</body>
</html>
EDIT: To be clear, I didn't write the code, only the comments. I am just trying to break it down and understand each line.
When the Javascript is run the function context is stored in memory, and closed-over variables are stored in the context. They don't go away. Globals are attached to window. They don't go away either.
I think it is a better idea to append the script to the header (where all the other scripts reside) than to the body.
If you want to completely eliminate the script that is being called, you could use:
<script>
(function foo(){
var b=function moo(){
var c=document.getElementsByTagName('script');
alert(document.body.innerHTML);
c[0].parentElement.removeChild(c[0]);
alert(document.body.innerHTML);
}
var a=setTimeout(b,1000);
b=null;
})();
foo=null;
</script>
Keep in mind, that will COMPLETELY remove any functionality of the script and any reference to it in the DOM.
Basically, because the script has already loaded, you can't just remove it from the DOM.
Is there really any reason to get rid of it, now that it's already loaded...???
I have a function to grab a video title from a YouTube json callback, which works - however I'm having issues inserting the variable into an element.
Here is the feed I'm grabbing:
<script type="text/javascript" src="http://gdata.youtube.com/feeds/api/videos/2WNrx2jq184?v=2&alt=json-in-script&callback=youtubeFeedCallback"></script>
The javascript function I'm using:
function youtubeFeedCallback(data) {
var info = data.entry.title.$t;
document.write(info);
}
This works fine, but I'd like to insert it into a div with the ID "box".
Usually I would use the following (and add it to the function - and remove the document.write):
var box = document.getElementById('box');
box.innerHTML = info;
I just cannot get this to work though. What would be the correct way to achieve what I'm trying to do? Thanks
http://jsfiddle.net/b3VYT/
Either make sure that the script is below the element or wrap your code in a document.ready callback so that it is not run until after the DOM is loaded.
http://jsfiddle.net/b3VYT/1
You need to make sure that the element that you are using is declared prior to your script executing:
<div id='test'></div>
<script>
function youtubeFeedCallback(data) {
document.getElementById('test').innerHTML = data.entry.title.$t;
}
</script>
Example
I am new to Javascript, and I am trying to get this function to work, but what ever I do I can't get anonymous functions to work, when I switch to the normal function it works. I know that I can live without anonymous functions but it's really annoying me.
Example:
In the HTML file:
<!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>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
</head>
<script type="text/javascript" src="ch10_2.js"> </script>
<body>
Go Searching
</body>
</html>
In the JavaScript file:
var s_link = document.getElementById("search_link");
s_link.onclick = function() {
var is_sure = window.confirm("Are you sure you want to leave?");
if (!is_sure) {
window.alert("OK. You can stay here.");
return false;
}
};
There are several problems here, each on its own would cause this script to fail:
Script tag is an illegal position in the document - between the <body> and <head> tags. It must be inside one of those.
Script tries to access a variable named 's_link' which should point to the link. For it to reference the link, you need to fetch the element using something like getElementById() or other DOM traversal methods [Edit - I see you've added that line after posting the question].
If the script is ran before the the element (link) is rendered (as it is now), it would not affect the element since it does not exist in the document yet. Either wrap it in a function that runs on document load, or place the script after the element in the document.
The link with the id search_link doesn't exist at the time the script runs, so it can't be fetched with getElementById or similar. You need to delay the execution of the code (e.g. by wrapping it in a function that executes onload or just moving the <script> element to after the link is parsed (just before </body> is often recommended).
It works for me.
Suggestion: Make sure to put your code at the end of the page just before </body>.
You should tell your script to execute after the page has loaded. To do this just wrap the code as shown below:
window.onload = function() {
// code you had before
}
The reason for this is simple. The script is currently being executed before the page is loaded. As such the document.getElementById() request is actually failing. It has nothing to do with your function being anonymous.
Just as Mr. Dorward pointed out, your script (at ch10_2.js) is executed before de DOM is ready.
The thing is, to attach an event to an element said element must exist but as javascript code is executed as soon as it is loaded the rest of the page is usually still loading. Therefore, the element you want to attach an event to does not exist yet.
I recommend this article about how jQuery (a javascript library) deals with this: http://www.learningjquery.com/2006/09/introducing-document-ready.
And here is a jsbin very simple example: http://jsbin.com/eziwu5/edit
There's a div called "Content":
<div id="content"></div>
It should be filled with data from a PHP file, by AJAX, including a <script> tag. However, the script inside this tag is not being executed.
<div id="content"><!-- After AJAX loads the stuff that goes here -->
<script type="text/javascript">
//code
</script>
<!-- More stuff that DOES work here -->
</div>
I used this code, it is working fine
var arr = MyDiv.getElementsByTagName('script')
for (var n = 0; n < arr.length; n++)
eval(arr[n].innerHTML)//run script inside div
JavaScript inserted as DOM text will not execute. However, you can use the dynamic script pattern to accomplish your goal. The basic idea is to move the script that you want to execute into an external file and create a script tag when you get your Ajax response. You then set the src attribute of your script tag and voila, it loads and executes the external script.
This other StackOverflow post may also be helpful to you: Can scripts be inserted with innerHTML?.
If you load a script block within your div via Ajax like this:
<div id="content">
<script type="text/javascript">
function myFunction() {
//do something
}
myFunction();
</script>
</div>
... it simply updates the DOM of your page, myFunction() does not necessarily get called.
You can use an Ajax callback method such as the one in jQuery's ajax() method to define what to execute when the request finishes.
What you are doing is different from loading a page with JavaScript included in it from the get-go (which does get executed).
An example of how to used the success callback and error callback after fetching some content:
$.ajax({
type: 'GET',
url: 'response.php',
timeout: 2000,
success: function(data) {
$("#content").html(data);
myFunction();
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert("error retrieving content");
}
Another quick and dirty way is to use eval() to execute any script code that you've inserted as DOM text if you don't want to use jQuery or other library.
Here is the script that will evaluates all script tags in the text.
function evalJSFromHtml(html) {
var newElement = document.createElement('div');
newElement.innerHTML = html;
var scripts = newElement.getElementsByTagName("script");
for (var i = 0; i < scripts.length; ++i) {
var script = scripts[i];
eval(script.innerHTML);
}
}
Just call this function after you receive your HTML from server. Be warned: using eval can be dangerous.
Demo:
http://plnkr.co/edit/LA7OPkRfAtgOhwcAnLrl?p=preview
This 'just works' for me using jQuery, provided you don't try to append a subset the XHR-returned HTML to the document. (See this bug report showing the problem with jQuery.)
Here is an example showing it working:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>test_1.4</title>
<script type="text/javascript" charset="utf-8" src="jquery.1.4.2.js"></script>
<script type="text/javascript" charset="utf-8">
var snippet = "<div><span id='a'>JS did not run<\/span><script type='text/javascript'>" +
"$('#a').html('Hooray! JS ran!');" +
"<\/script><\/div>";
$(function(){
$('#replaceable').replaceWith($(snippet));
});
</script>
</head>
<body>
<div id="replaceable">I'm going away.</div>
</body>
</html>
Here is the equivalent of the above: http://jsfiddle.net/2CTLH/
Here is a function you can use to parse AJAX responses, especially if you use minifiedjs and want it to execute the returned Javascript or just want to parse the scripts without adding them to the DOM, it handles exception errors as well. I used this code in php4sack library and it is useful outside of the library.
function parseScript(_source) {
var source = _source;
var scripts = new Array();
// Strip out tags
while(source.toLowerCase().indexOf("<script") > -1 || source.toLowerCase().indexOf("</script") > -1) {
var s = source.toLowerCase().indexOf("<script");
var s_e = source.indexOf(">", s);
var e = source.toLowerCase().indexOf("</script", s);
var e_e = source.indexOf(">", e);
// Add to scripts array
scripts.push(source.substring(s_e+1, e));
// Strip from source
source = source.substring(0, s) + source.substring(e_e+1);
}
// Loop through every script collected and eval it
for(var i=0; i<scripts.length; i++) {
try {
if (scripts[i] != '')
{
try { //IE
execScript(scripts[i]);
}
catch(ex) //Firefox
{
window.eval(scripts[i]);
}
}
}
catch(e) {
// do what you want here when a script fails
// window.alert('Script failed to run - '+scripts[i]);
if (e instanceof SyntaxError) console.log (e.message+' - '+scripts[i]);
}
}
// Return the cleaned source
return source;
}
If you are injecting something that needs the script tag, you may get an uncaught syntax error and say illegal token. To avoid this, be sure to escape the forward slashes in your closing script tag(s). ie;
var output += '<\/script>';
Same goes for any closing tags, such as a form tag.
This worked for me by calling eval on each script content from ajax .done :
$.ajax({}).done(function (data) {
$('div#content script').each(function (index, element) { eval(element.innerHTML);
})
Note: I didn't write parameters to $.ajax which you have to adjust
according to your ajax.
I had a similiar post here, addEventListener load on ajax load WITHOUT jquery
How I solved it was to insert calls to functions within my stateChange function. The page I had setup was 3 buttons that would load 3 different pages into the contentArea. Because I had to know which button was being pressed to load page 1, 2 or 3, I could easily use if/else statements to determine which page is being loaded and then which function to run. What I was trying to do was register different button listeners that would only work when the specific page was loaded because of element IDs..
so...
if (page1 is being loaded, pageload = 1)
run function registerListeners1
then the same for page 2 or 3.
My conclusion is HTML doesn't allows NESTED SCRIPT tags. If you are using javascript for injecting HTML code that include script tags inside is not going to work because the javascript goes in a script tag too. You can test it with the next code and you will be that it's not going to work. The use case is you are calling a service with AJAX or similar, you are getting HTML and you want to inject it in the HTML DOM straight forward. If the injected HTML code has inside SCRIPT tags is not going to work.
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"></head><body></body><script>document.getElementsByTagName("body")[0].innerHTML = "<script>console.log('hi there')</script>\n<div>hello world</div>\n"</script></html>
you can put your script inside an iframe using the srcdoc attribute
example:
<iframe frameborder="0" srcdoc="
<script type='text/javascript'>
func();
</script>
</iframe>
Another thing to do is to load the page with a script such as:
<div id="content" onmouseover='myFunction();$(this).prop( 'onmouseover', null );'>
<script type="text/javascript">
function myFunction() {
//do something
}
myFunction();
</script>
</div>
This will load the page, then run the script and remove the event handler when the function has been run. This will not run immediately after an ajax load, but if you are waiting for the user to enter the div element, this will work just fine.
PS. Requires Jquery