Wednesday 11 September 2013

Dynamically removing/ replacing an external JavaScript or CSS file

Any external JavaScript or CSS file, whether added manually or dynamically, can be removed from the page. The end result may not be fully what you had in mind, however. I'll talk about this a little later.

Dynamically removing an external JavaScript or CSS file

To remove an external JavaScript or CSS file from a page, the key is to hunt them down first by traversing the DOM, then call DOM's removeChild() method to do the hit job. A generic approach is to identify an external file to remove based on its file name, though there are certainly other approaches, such as by CSS class name. With that in mind, the below function removes any external JavaScript or CSS file based on the file name entered:

function removejscssfile(filename, filetype){
 var targetelement=(filetype=="js")? "script" : (filetype=="css")? "link" : "none" 
//determine element type to create nodelist from
 var targetattr=(filetype=="js")? "src" : (filetype=="css")? "href" : "none" 
//determine corresponding attribute to test for
 var allsuspects=document.getElementsByTagName(targetelement)
 for (var i=allsuspects.length; i>=0; i--){ 
//search backwards within nodelist for matching elements to remove
  if (allsuspects[i] && allsuspects[i].getAttribute(targetattr)!=null 
&& allsuspects[i].getAttribute(targetattr).indexOf(filename)!=-1)
   allsuspects[i].parentNode.removeChild(allsuspects[i]) 
//remove element by calling parentNode.removeChild()
 }
}

removejscssfile("somescript.js", "js") 
//remove all occurences of "somescript.js" on page
removejscssfile("somestyle.css", "css") 
//remove all occurences "somestyle.css" on page

The function starts out by creating a collection out of either all "SCRIPT" or "LINK" elements on the page depending on the desired file type to remove. The corresponding attribute to look at also changes accordingly ("src" or "href" attribute). Then, the function sets out to loop through the gathered elements backwards to see if any of them match the name of the file that should be removed. There's a reason for the reversed direction- if/whenever an identified element is deleted, the collection collapses by one element each time, and to continue to cycle through the new collection correctly, reversing the direction does the trick (it may encounter undefined elements, hence the first check for allsuspects[i] in the if statement). Now, to delete the identified element, the DOM method parentNode.removeChild() is called on it.

So what actually happens when you remove an external JavaScript or CSS file? Perhaps not entirely what you would expect actually. In the case of JavaScript, while the element is removed from the document tree, any code loaded as part of the external JavaScript file remains in the browser's memory. That is to say, you can still access variables, functions etc that were added when the external file first loaded (at least in IE7 and Firefox 2.x). If you're looking to reclaim browser memory by removing an external JavaScript, don't rely on this operation to do all your work. With external CSS files, when you remove a file, the document does reflow to take into account the removed CSS rules, but unfortunately, not in IE7 (Firefox 2.x and Opera 9 do).

Demo: "myscript.js" source:
var petname="Spotty"
alert("Pet Name: " + petname)
"mystyle.css" source:
#demotable td{
background-color: lightyellow;
}

#demotable b{
color: blue;
}

Dynamically replacing an external JavaScript or CSS file

Replacing an external JavaScript or CSS file isn't much different than removing one as far as the process goes. Instead of calling parentNode.removeChild(), you'll be using parentNode.replaceChild() to do the bidding instead:

function createjscssfile(filename, filetype){
 if (filetype=="js"){ //if filename is a external JavaScript file
  var fileref=document.createElement('script')
  fileref.setAttribute("type","text/javascript")
  fileref.setAttribute("src", filename)
 }
 else if (filetype=="css"){ //if filename is an external CSS file
  var fileref=document.createElement("link")
  fileref.setAttribute("rel", "stylesheet")
  fileref.setAttribute("type", "text/css")
  fileref.setAttribute("href", filename)
 }
 return fileref
}

function replacejscssfile(oldfilename, newfilename, filetype){
 var targetelement=(filetype=="js")? "script" : (filetype=="css")? "link" : "none"
 //determine element type to create nodelist using
 var targetattr=(filetype=="js")? "src" : (filetype=="css")? "href" : "none" 
//determine corresponding attribute to test for
 var allsuspects=document.getElementsByTagName(targetelement)
 for (var i=allsuspects.length; i>=0; i--){
 //search backwards within nodelist for matching elements to remove
  if (allsuspects[i] && allsuspects[i].getAttribute(targetattr)!=null 
&& allsuspects[i].getAttribute(targetattr).indexOf(oldfilename)!=-1)
{
   var newelement=createjscssfile(newfilename, filetype)
   allsuspects[i].parentNode.replaceChild(newelement, allsuspects[i])
  }
 }
}

replacejscssfile("oldscript.js", "newscript.js", "js")
 //Replace all occurences of "oldscript.js" with "newscript.js"
replacejscssfile("oldstyle.css", "newstyle", "css")
 //Replace all occurences "oldstyle.css" with "newstyle.css"
 
Notice the helper function createjscssfile(), which is essentially just a duplicate of loadjscssfile() as seen on the previous page, but modified to return the newly created element instead of actually adding it to the page. It comes in handy when parentNode.replaceChild() is called in replacejscssfile() to replace the old element with the new. Some good news here- when you replace one external CSS file with another, all browsers, including IE7, will reflow the document automatically to take into account the new file's CSS rules.

Demo: "oldscript.js" source:
var petname="Spotty"
alert("Pet Name: " + petname)
"newscript.js" source:
var petname="Beauty"
alert("Pet Name: " + petname)
"oldstyle.css" source:
#demotable2 td{
background-color: lightyellow;
}

#demotable2 b{
color: blue;
}
"newstyle.css" source:
#demotable2 td{
background-color: lightblue;
}

#demotable2 b{
color: red;
}

Conclusion

So when is all this useful? Well, in today's world of Ajax and ever larger web applications, being able to load accompanying JavaScript/ CSS files asynchronously and on demand is not only handy, but in some cases, necessary. Have fun finding out what they are, or implementing the technique. :)

No comments :