Friday, May 31, 2013

innerHTML Property in HTML Elements

With this property, we can get or set the inner HTML/Contents of any elements. We have the following HTML ::

<div id='myDiv'></div>

Now if we write the following script, it successfully inserts a new HTML inside the above DIV. And this works on all versions of browsers.

<script> 
var p = document.getElementById("myDiv");
// Set the innerHTML
p.innerHTML = "<p>This is a nice day</p>";
</script>

 

Problem starts when we try to insert <link> or <style> elements through this innerHTML property. However, <link> tag should appear inside the <head> tag. So, let's re-write our program as shown below ..

<html>
<head id='header'>
<script> 
function load_style()
{
  var p = document.getElementById("header");
  var t = p.innerHTML;
  p.innerHTML =  "<link href='style.css' rel='stylesheet' >" + t;
}
</script>
</head>

<body>
<div id="mydiv">This is a sample DIV. Click the button to make this DIV Red
<input type="button" onclick="load_style()" value="Load Style">
</div>

</body>
</html>

 



The <head> element contains a script which inserts a new <link> tag inside the <head> and as a result, a stylesheet is loaded at run time. Check that the DIV contents change to red when the "Load Style" button is clicked. This works perfectly on all browsers but shows error on IE. Similar error occurs in IE when we try to insert new HTML inside any <tbody> element. For IE, "The innerHTML property is read-only on the col, colGroup, frameSet, html, head, style, table, tBody, tFoot, tHead, title, and tr objects" - ;MSDN. This has been done for security puposes. So, we need to use DOM methods to insert this new <link> tag inside the <head> element. Check the new implementation below ::

<script> 
function load_style()
{
  var p = document.getElementById("header");
 
  /* Create New link element */
  var t = document.createElement("link");

  /* Set href and rel property */
  t.setAttribute('href','style.css');
  t.setAttribute('rel','stylesheet');

  /* Append it to head tag */
  p.appendChild( t );
}
</script>


The above solution works on all browsers including IE7 and IE8. This dynamically loads a stylesheet and applies it.

Let's load some Javascripts using innerHTML property. Try the following function :

<script>
function load_script()
{
  var p = document.getElementById("header");
  var t = p.innerHTML;
  p.innerHTML =  "<script>alert('This is a nice day');</script>" + t;
}
</script>


The above function does not work because of malformed Javascript due to the existence of "</script>" word used inside the function body. Browser thinks that the script ends there. So the rest part " + t }" are assumed to be HTML and printed on screen. So, we would correct that first. Check out the implementation below ::

<html>
<head id='header'>
<script> 
function load_script()
{
  // Get the ID of <head>
  var p = document.getElementById("header");
  var t = p.innerHTML;
  p.innerHTML =  "<script>alert('This is a nice day');</scri" + "pt>" + t;
}
</script>
</head>

<body>
<div id="mydiv">This is a sample DIV. Click the button to load a script <br />
<input type="button" onclick="load_script()" value="Load Script">
</div>
</body>
</html>


Notice how the "</script>" has been written as "</scri" + "pt>" to tell JavaScript engine that this is just a string. The above code successfully inserts a new <script> node on all browser [Except IE] but does not execute the script. IF we insert the <script> tag in any other elements like DIV or P, it would have worked in IE also, but the code would still behave as a plain text.

However, dynamically adding a script to DIV or P etc elements does not make the new script work on any browser. However in IE, we can see a different bahaviour if we use the keyword "defer" on <script> tag.

<html>
<head id='header'>
<script> 
function load_script()
{
  // Get the ID of DIV
  var p = document.getElementById("mydiv");
  var t = p.innerHTML;

  // We use defer keyword
  p.innerHTML =  "<script defer>alert('This is a nice day');</scri" + "pt>" + t;
}
</script>
</head>

<body>
<div id="mydiv">This is a sample DIV. Click the button to load a script <br />
<input type="button" onclick="load_script()" value="Load Script">
</div>
</body>
</html>


The above code works on IE only. As soon as we click on the 'Load Script' button, a script tag with alert() function call is inserted in the "mydiv" DIV element and the script is executed.The keyword "defer" means the loading of JS is deferred.

So, we need to use DOM methods to insert a script.

<script>
function load_script()
{
  // Get the ID of head
  var p = document.getElementById("header");
 
  // Create new script element
  var scr = document.createElement("script");
 
  // Define Code Body
  var code = "function d(){ var sum = 0; "   +
     "  for(var i=1; i<=10; i++ )  sum += i;" +  

     "  alert('The Sum is : ' + sum );} d();" ;
        
  // Create a TEXT node
  var c = document.createTextNode(code);

  // Append the code to script
  scr.appendChild( c );

  // Append the script to header
  p.appendChild( scr );

}
 
</script> 

The above code successfully inserts a script at runtime inside the <head> tag. The script contains definition of a user-defined function d() which calculates sum of first 10 natural numbers and shows the sum on screen. And then we call the function d(). As a result, when we click on the "Load Script" button, the new script is appended to <head> element, the d() function is evaluated and run; so we see a message "The Sum is : 55" popping up on screen.

innerHTML property treats the inserted javascript statements as plain text. However, we can use eval() function to make those statements back to Javascript. Check the example below ..

<script> 
function load_script()
{
  eval( document.getElementById("mydiv").innerHTML  );
}
</script>

<div id="mydiv">function sum(a,b){alert (a+b);}</div>
<input type="button" onclick="load_script()" value="Load Script">
<input type='button' value = 'Get Sum' onclick='sum(1,2)'>


The purpose is, when the "Load Script" button is clicked, the content of the DIV is parsed and evaluated and as a result function sum() is registered, hence when then "Get Sum" button is clicked, the sum(1,2) function should get executed. But problem is this does not work as eval() maintains the scope for the code body it parses. Hence, the sum() function is registered inside the load_script() function only, hence not accessible form outside the parent function load_script(). So, if we change the DIV content as shown below, a function is registered in global scope and can be accessed from anywhere.

<div id="mydiv">sum = function(a,b){alert (a+b);}</div>

Here, we create a sum() function in the global scope. Hence the "Get Sum" button now works and shows us a message containing sum of 1 and 2.

We can receive block of Javascript statements in Ajax Response, put those statement texts in any DIV or span or other elements through innerHTML property of that element and then use eval() method to parse and evaluate them to do some automated job.


Check my next article "Dynamic JS Insertion" where I have discussed some more on this.

No comments: