Sunday, February 8, 2009

Reference External Javascript File in CRM Form Events Asynchronously

A few months ago I posted a method of importing external javascript files into CRM forms. See 'Reference External Javascript File in CRM Form Events'.

Some of you may have noticed an issue with this method when including multiple exeternal files. This is due to the fact that the process runs asynchronously while the rest of the code continues to execute.

A friend of mine Sam Manins managed to come up with a full work around for this. Thanks to Sam, the function below can be pasted into your OnLoad event for a more reliable method to import Javascript files into your CRM forms.

The idea is to invoke the command to add your external file, then wait for confirmation that the file was imported. Only then should we allow the code to continue. See comments within the code.

//Paste this function somewhere into your form "OnLoad" event
function includeExternalJSFiles(sec)
{
var errFound = 0;
// load the Ascentium js file into a Script element - do not do this on recursive calls to this function
if (sec == 1)
{
//Include the CRM js file
var Ascentium_Script = document.createElement("script");
Ascentium_Script.language = "javascript";
Ascentium_Script.src = "MyJSFunctions1.js"
document.getElementsByTagName("head")[0].appendChild(Ascentium_Script);
//Include another js file
var Ascentium_Script = document.createElement("script");
Ascentium_Script.language = "javascript";
Ascentium_Script.src = "MyJSFunctions2.js"
document.getElementsByTagName("head")[0].appendChild(Ascentium_Script);
//Add any more JS files you need
}
try
{
//Each file needs to have a simple function(with a unique name) that does nothing that can be called.
//th
en you can call this finction to see if the file has been imported yet.
MyJSFunction1_STUB();//this function is contained in the external JS file
MyJSFunction2_STUB();//this function is contained in the external JS file
}
catch(err)
{
//error found(probably could not find the function
errFound = 1;
}
if (sec < 5 && errFound == 1)
{
sec++;
setTimeout("includeExternalJSFiles(" + sec + ")", 100); //waits 0.1 second before trying the stubs again.
}
else
{
if (errFound == 1)
//Did not work after 5 attempts (0.5 seconds)
//Try a longer time period or output an error message for the user
//alert("Did not work: " + sec);
else
//All functions were found (all files imported)
//No need to do anything. Just exit functions and continue code
//alert("Success after " + sec + " attempts. ");
}
}

Remember to have a unique stub function to run in each external file. the function needs no do anything. Just needs to be there(See sample below).

//this function would be found in the file MyJSFunction1.js
function MyJSFunction1_STUB()
{
//stub function (does nothing)
var a;
}

Finally you need to call the function to import your Javascript files:

includeExternalJSFiles(0);

The above example makes the call to add the files to the page header, then continues to try to run a functions in each file every 0.1 seconds until successful or until 5 failed attempts. Different systems under different workloads may perform the job at different speeds. Feel free to modify the code to suit your needs.

And improvement on this function could be to also pass in the name of each Javascript file in an array, making it more generic.

A big thank you goes to Sam Manins for the solution.

2 comments:

Anonymous said...

I found this link also. It describes another way to do this which I think gets rid of the timing issue:

http://www.henrycordes.nl/post/2008/11/Using-jQuery-with-MS-CRM-40.aspx

Nathan said...

Thanks a lot for posting this code! I didn't want to use Henry Cordes method after reading all the comments on his blog about it and Adi Katz's blog posting in response (http://mscrm4ever.blogspot.com/2009/04/crm-form-script-loader.html), which basically says that the eval function is sometimes slow. But I couldn't figure out Adi's method for loading multiple files so I've used yours!

I actually use this code in my onLoad Form events:
//set variables
var script2Load = document.createElement("SCRIPT");
script2Load.language = "javascript";

//set the src (url) Property with a random number to avoid caching
script2Load.src = "/ISV/" + ORG_UNIQUE_NAME + "/" + crmForm.ObjectTypeName + "_onload.js?nocach=" + Math.random();

//append the element to the head tag (global scope)
document.getElementsByTagName("HEAD")[0].appendChild(script2Load);

Then at the beginning of every _onload file I use your code to load all the helper files I need.

I added a CrmFormLoad function at the end of your code and included all the rest of my onLoad code in it, so that my onload code doesn't run until the external files are loaded. Like this:

else {
//All functions were found (all files imported)
//No need to do anything. Just exit functions and continue code
//alert("Success after " + sec + " attempts. ");
CrmPageLoad();
}
}
}
IncludeExternalJSFiles(0);

CrmPageLoad = function() {

...all the rest of my code in the onLoad event...

Thanks a lot for all your help!