Monday, June 03, 2013

Dynamic Javascript Insertion

We can create dynamic script element using the simple DOM methods. We have discussed how setting element's innerHTML property fails to execute the inserted scripts at runtime. Let us check out a simple DOM method to load an external js file. Changing "src" property of any <script> element on the runtime does not work on all browses. This has been discussed at the end of this article.

<html>
<head id='header'>
<script> 
function load_script()
{
  // Get the ID of head
  var p = document.getElementById("header");
 
  // Create new script element
  var scr = document.createElement("script");
 
  // Define src
  scr.setAttribute("src","sample.js");
 
  // Append the script to header
  p.appendChild( scr );

}
</script>
</head>

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


The function load_script() dynamically inserts a <script> element inside the header section, and then setting up "src" property and appending the new <script> to <header> element loads and executes the external js file 'sample.js'.

Using this method we can dynamically call some server pages and get the feedback in native JavaScript code and get it executed.

We have already seen in my previous article that inserting a piece of JavaScript code in a DIV using DIV's innerHTML property does not work. To get some JS code executed, we either need to call eval() method or use the general DOM method to create Script element itself. Let's check out an Ajax example where the feedback is received in native Javascript and we are following the DOM methods to create new script element on the document.

<script>
function call_ajax()
{
 // XHR Object
 var xhr = new XMLHttpRequest();

 // CallBack on Success
 xhr.onreadystatechange = function(){
 
  if(xhr.readyState == 4 && xhr.status == 200)
  {
    // Create new script element
    var scr = document.createElement("script");

    // Create a TEXT node with Ajax Response
    var c = document.createTextNode( xhr.responseText );

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

    // Append the script to document body
    document.body.appendChild( scr );
  }

 };

 // REQUEST to find_city.php with param zip=10001
 xhr.open("GET",'find_city.php?zip=10001', true);
 xhr.send();
}
</script>

<input type='button' value='Load Script thru Ajax' onclick='call_ajax()'>

<span id='city'></span>


In the above example we are sending a call to find_city.php with a zip parameter. Now, find_city.php searches a DB with given zip code, and returns the result in a native JS (in text format) as shown below.

search_result = 'New York, NY'; document.getElementById('city').innerHTML = search_result; 

Now, our JS code create a Script element with the above Ajax response and appends it to the document body. As a result, the above function gets executed which changes the content of the span (id=city). Check the screenshot below.




The screenshot shows that the ajax response text has been successfully inserted in the span element after clicking the button.

Now, suppose, the ajax response is a function call like this ::

put_search_result ( 'New York, NY', 'city_div') ;

the function "put_search_result" should have been defined prior to making the ajax call. However, we can make the function name dynamic and vary between function calls as shown in example below.

<script>
function call_ajax()
{

 // Define random no.
 var random = Math.floor( Math.random() * 10000 );

 // Define the dynamic function name
 var call_back_fn_nm = "call_back_" + random;

 // Define an anonymous function with body
 var func = function(response){ document.getElementById('city').innerHTML = response;  } ;

 // Link up dynamic function name & body
 eval(call_back_fn_nm + " = func");


 //// AJAX SECTION 
 var xhr = new XMLHttpRequest();

 xhr.onreadystatechange = function(){
 
  if(xhr.readyState == 4 && xhr.status == 200)
  {
    var p = document.getElementById('script_div');
    // Below line is IMPORTANT

    eval( xhr.responseText );
  }

 };

 xhr.open("GET",'find_city.php?zip=10001&callback=' + call_back_fn_nm, true);
 xhr.send();
}
</script>


The above program has some significant and interesting points to notice :

i. We dynamically create a function name like "call_back_1234" and assign it to variable "call_back_fn_nm". We want to fire call_back_1234() after we receive the ajax response. Suppose that 1234 was generated as a random nunber.
ii. Secondly we define an anonymous function and store it in variable func.
iii. Most important statement is call to eval(call_back_fn_nm + " = func"); which is finally evaluated to "call_back_1234 = func" which assigns the anonymous function to a variable call_back_1234 in global scope. This means we can always call call_back_1234(). We can't make a statement like "
call_back_1234 = func" and execute it without the help of eval().
iv. Next we call the server page find_city.zip with zipcode and callback function name as parameters like this ::
    find_city.php?zip=10001&callback=call_back_1234
v. The server simply returns the response in text format :: call_back_1234( 'New York, NY' );
vi. The statement eval( xhr.responseText ); simply evaluates the response and gives a call to call_back_1234( 'New York, NY' ) which inserts the text  'New York, NY' inside the span (id='city').

On many cases, web services are received in JSON object wrapped in a function call like this :

call_back_123456789( {state : 'NY', city : 'New York', zip : '10001'} );

In these situations, our solution would work provided we define the call-back function prior to the webservice call.

Such Dynamic script insertion may create and append lots of dynamic <script> tags on the document body. We could have used another method ...

<script id='dyn_src'></script>

<script>
// Dynamically changes the src property of a script element
function search_result()
{
  document.getElementById('dyn_src').src = 'find_city.php?zip=10001&calback=call_back'
}

// Function body for call back function
function call_back(city_name)
{
  document.getElementById('city').innerHTML = city_name;
}
</script>



The response of find_city.php is received as :: call_back('New York, NY') which is executed when we change the src property of the script element inside the search_result() function. So, calling search_result() function would change the content of the span element. Unfortunately this method works well in IE only.


We can use jQuery getScript() method to load a piece of JS code and execute it dynamically. This has been discussed here.

No comments: