CrmExt.js is a script loading library I created to efficiently and deterministically load external JavaScript into CRM forms. This library solves the problem of external JavaScript getting cached by providing a set of options which give the developer control over how scripts are cached. Additionally, this library ensures that scripts are loaded in the correct order in cases of script dependency (where one external script relies on another to already be loaded) and allows the developer to easily call a function after the script is loaded.
Contents The CrmExt.js package includes the following:
Install CrmExt.js is configured to be installed into the /ISV/CrmExt folder under the root of your CRM site. To install, copy the CrmExt subfolder from the CrmExt.js package to the ISV folder in your CRM web root. You can install CrmExt.js and options.js to a different folder if you like by changing line 3 in CrmExt.js to reflect the new installation path.
It is recommended that you create a subfolder within the CrmExt folder named Entities. Within this folder, create a separate folder for each entity that will use script loading. For instance, you may have an account subfolder and a contact subfolder that would contain the form script for those entities.
Loading Script There are two ways to load script using CrmExt.js. You can use the Load function or the Enqueue function. Script added via the Load function will be loaded as soon as it is downloaded from the server or retrieved from cache. Script added with the Enqueue function is loaded in order. The script loader ensures that script added via Enqueue waits to be loaded until all previously added script (added by either method) is retrieved and loaded. All script is downloaded immediately. If a later script added via Enqueue downloads before a prior script, the loader will postpone loading that script until the earlier script is loaded.
CrmExt.js looks for your loading statements in one of two places. First it checks to see if you’ve created a global function named $CrmExt_LoadScripts. If this function is present, it will execute the function upon loading the CrmExt.js library. If it is not present, CrmExt.js will try to launch /ISV/CrmExt/Entities/EntityName/_LoadScripts.js where EntitiyName is the name of the entity and /ISV/CrmExt is the installation path of CrmExt.js. You will find examples of both methods in the code samples.
While both options are provided, it is recommended to use $CrmExt_LoadScripts because it performs slightly better.
Note that you don’t have to include all of your script loading calls in these locations. You could call the CrmExt.js script loading functions from anywhere once the CrmExt.js library is loaded. This means that you could load in one script that in turn loads a 2nd script. Generally, though, you will experience slightly better loading times by loading all necessary script from $CrmExt_LoadScripts.
Form Events CrmExt.js includes convenience functions for launching external OnLoad, OnChange, and OnSave event script. To use these convenience functions, you must explicitly assign your functions to them. The following is an example of how assigning the convenience functions forOnLoad and OnSave event script
OnLoad//assigns the code in the function to $CrmExt.FormEvents.OnLoad $CrmExt.FormEvents.OnLoad = function() { // OnLoad code goes here }
OnLoad
//assigns the code in the function to $CrmExt.FormEvents.OnLoad $CrmExt.FormEvents.OnLoad = function() { // OnLoad code goes here }
OnSave//assigns the code in the function to $CrmExt.FormEvents.OnSave $CrmExt.FormEvents.OnSave = function() { // OnSave code goes here }
OnSave
//assigns the code in the function to $CrmExt.FormEvents.OnSave $CrmExt.FormEvents.OnSave = function() { // OnSave code goes here }
OnChange functions are slightly different. You must assign your OnChange event scripts to $CrmExt.FormEvents.fieldname_OnChange where fieldname is the name of the field. There is a single convenience function: $CrmExt.FormEvents.OnChange(). It determines which event to fire based on the event.srcElement or by the value you provide. The following is an example of assigning an OnChange function for the name field.
// this script is assigned to the name attribute. OnChange events are always loaded as attribute_OnChange to ensure they can be called properly from $CrmExt.FormEvents.OnChange(attribute) $CrmExt.FormEvents.name_OnChange = function() { // name Onchange code goes here }
The code samples include several examples of loading, assigning, and using the Form Events convenience functions.
Note that you do not have to use the Form Events convenience functions. They are there to standardize and simplify external form event script, but they are not required. Instead you could do something on your own like the following:
$CrmExt_LoadScripts:window.$CrmExt_LoadScripts = function() { // Load OnLoad $CrmExt.ScriptLoader.Load("Entities/account/OnLoad.js"); // Call OnLoad $CrmExt.ScriptLoader.Enqueue(function(){ MY_OnLoad(); }); // we enqueue this to ensure it is not called before the script is loaded } OnLoad.js:function MY_OnLoad() { // OnLoad code goes here }
$CrmExt_LoadScripts:
window.$CrmExt_LoadScripts = function() { // Load OnLoad $CrmExt.ScriptLoader.Load("Entities/account/OnLoad.js"); // Call OnLoad $CrmExt.ScriptLoader.Enqueue(function(){ MY_OnLoad(); }); // we enqueue this to ensure it is not called before the script is loaded }
OnLoad.js:
function MY_OnLoad() { // OnLoad code goes here }
Calling Functions After Script Is Loaded Both Load and Enqueue allow you to pass in a function that will be executed once the script is loaded. Enqueue allows you to specify a function to call without also specifying a script to load. This allows you to add multiple function calls to the queue that will be called once the script loader has processed all prior queue items.
When passing in functions that are included in external script files, you must wrap them in an anonymous function. JavaScript will evaluate function pointers immediately. If you are trying to enqueue a call to DoSomething after DoSomething gets loaded, you cannot pass DoSomething in to the Enqueue request because the JavaScript engine will evaluate DoSomething to null since it hasn't been loaded yet. Instead, you should wrap your call to DoSomething in an anonymous function as follows: "function () { DoSomething(); }".
Setting Options CrmExt.js includes a base set of options. These should not be changed in that file. Instead, use options.js to override the defaults. Because CrmExt.js is usually cached by the browser, this external file is used for overrides. Any option set in the options.js file will be used as the default for each Load and Enqueue call. You can further override the default options in code by using the SetDefaultOptions method. Any subsequent call to Load and Enqueue will use the new defaults. Finally, you can pass in an option object to both Load and Enqueue to override the default options for that specific call only. See the Reference section below for a list of available options.
You should note that CrmExt.js utilizes IE8's session storage to cache options.js after first download. If you change options.js, you must start a new session in order to retrieve the changes. With previous versions of IE, options.js is downloaded each time.
Using Options to Configure Caching The use of options gives you control over the caching of external script. During development when script is in flux, using the CheckForLatest option is ideal. This option will ensure that any script retrieved from cache is the latest version. It does this by submitting an If-Modified-Since request header to the server. The server will respond with a 304 response indicating the file is unchanged or with a 200 response including the updated file. In rare cases CheckForLatest may not work. If you are experiencing such a case, you can use NoCache instead. With NoCache, the script will always be loaded from the server regardless of modification date.
In production when scripts are stable, you should use the versioning options: Version and VersionScripts. With versioning enabled by setting VersionScripts to true, a version number (Version) is added as a querystring parameter to the request. Each time a change is made to the scripts you will update the Version number in options.js. By versioning, you ensure that script is loaded from the cache unless a recent change has been made, in which case all script will get downloaded again and re-cached.
Debugging External Script CrmExt.js uses XMLHttpRequests to download script from the server. Downloaded script is wrapped in a script tag and dynamically added to the head of the web page. Many script debugging tools, including Visual Studio and IE8, have difficulty debugging script loaded in this manner. To get around this, use the Debugable option. When Debugable is turned on, The loader still uses and XMLHttpRequest to download the script, but when it adds the script to the page, it will use a src reference pointing to the external script. Be aware that using Debugable mode doesn't always guarantee script load order. In most cases, script will still be loaded in the proper order because it is not added to the form until after it has already been downloaded. When it is added with the src attribute, the browser should pull the script directly from cache since we downloaded the script already. In some cases, though, this may not happen.
In production, you should never enable Debugable mode globally. Instead, a method has been added to the script loader that will enable Debugable mode for the current session only. This method can only be called from an IE8 or newer browser because it relies on session storage. To use this, you must execute $CrmExt.ScriptLoader.EnableDebugableMode() from the browser followed by a refresh. From that point on, script will be loaded in Debugable mode. To end Debugable mode, you may close out of your session or execute $CrmExt.ScriptLoader.DisableDebugableMode() from the browser.
Beware of Scoping The functions you load onto the form are added to the header as script tags. Thus, they will have global scope. Be aware that the naming of your functions and variables could clash with what’s already in the global scope or with other script you add. The easiest way to prevent this from occurring is to use a namespace prefix for all of your names. So, instead of DoSomething, you would have NS_DoSomething (of course you would come up with your own namespace and not use NS). Alternatively, you can assign your functions to the $CrmExt.Shared object that has been provided for such purposes. Here are some examples:
// BAD function DoSomething() { } DoSomething(); // Using a namespace to prevent clashes function NS_DoSomething() { } NS_DoSomething(); // same result as above but more explicit (I like explicit because it makes it more likely that the next person who comes along understands what's going on) window.NS_DoSomething = function() { } NS_DoSomething(); // using $CrmExt.Shared $CrmExt.Shared.NS_DoSomething = function() { } $CrmExt.Shared.NS_DoSomething(); // using namespaces under $CrmExt.Shared $CrmExt.Shared.MyNamespace = {}; //{} is the shorthand way to declare an object $CrmExt.Shared.MyNamespace.DoSomething = function() { } $CrmExt.Shared.MyNamespace.DoSomething();
Options Object Several of the functions comprised within CrmExt.js accept an object which is populated with properties that configure the script loader.
Valid Option Properties
AlertOnError (boolean) - when true, errors will be displayed to the user in alerts. Default is false.
BasePath (string) - relative path URLs are prepended with this value. Default is the installation path of CrmExt.
CheckForLatest (boolean) - when true, the script loader will always check with the server using an If-Modified-Since header to ensure the latest file is in use. Default is false.
Debugable (boolean) - when true, the script loader will add script using the src attribute to ensure script is debugable. This setting should only be used when needed. Default is false.
NoCache (boolean) - when true, the script loader will ensure that the script is always downloaded from the server and never retrieved from the browser cache. Default is false.
Version (string) - the version number used when VersionScripts is true. When using script versioning, you should increment the number each time script is changed to ensure the changes are downloaded by the clients. Default is 1.0.0.0.
VersionScript (boolean) - when true, the script loader will version scripts). Versioning is accomplished by adding the version number (from the Version property) as a querystring parameter to the script URL. In doing this, the script loader ensures that the latest script is downloaded each time the version number changes. Default is false.
There is no predefined options class . Instead you simply create a new object and assign it one or more of the properties listed above. Any properties on the object other than those listed above are ignored. The options object you create will be merged with the default options object. Your properties will override those on the default object, replacing the matching default properties. The only properties, then, that you need to include are those that differ from the default options.
The easiest way to create an options object is using JavaScript Object Notation.
Examples:
{ CheckForLatest: true, Debugable: true }
{ VersionScripts: true, Version: "1.0.0.1" }
$CrmExt.CreateXmlHttpRequest(options) Creates and returns a new XMLHttpRequest object. This method works in all CRM supported browsers.
Parameters:
options - an options object (see Options Object above). The only option property used by this method is AlertOnError. If AlertOnError is true, the method will alert if an error occurs creating the XMLHttpRequest object; otherwise, the method will throw an exception. The default is to throw an exception.
var xhr = $CrmExt.CreateXmlHttpRequest();var xhr = $CrmExt.CreateXmlHttpRequest({ AlertOnError: true });
var xhr = $CrmExt.CreateXmlHttpRequest();
var xhr = $CrmExt.CreateXmlHttpRequest({ AlertOnError: true });
$CrmExt.FormEvents.OnLoad() A convenience function that executes the form's OnLoad event script.
See code samples and Form Events above for examples.
Note: initially $CrmExt.FormEvents.OnLoad is configured as an empty function. You must explicitly assign a function to $CrmExt.FormEvents.OnLoad to use this method. See Form Events above for additional information.
$CrmExt.FormEvents.OnChange(field) A convenience function that calls the specified field's OnChange event script. If field is not provided, the function will assume event.srcElement.
field - a reference to the field whose OnChange should be fired. field can be a string value referencing the name of the field or the actual field object.
$CrmExt.FormEvents.OnChange("firstname");
$CrmExt.FormEvents.OnChange(crmForm.all.firstname);
$CrmExt.FormEvents.OnChange(event.srcElement);
$CrmExt.FormEvents.OnChange(); // assumes event.srcElement
Note: You must assign OnChange script to $CrmExt.FormEvents.fieldname_OnChange where fieldname is replaced by the actual name of the field. If an OnChange script has not been assigned for the specified field, an empty function is executed. See Form Events above for additional information.
$CrmExt.FormEvents.OnSave() A convenience function that executes the form's OnSave event script.
Note: initially $CrmExt.FormEvents.OnSave is configured as an empty function. You must explicitly assign a function to $CrmExt.FormEvents.OnSave to use this method. See Form Events above for additional information.
$CrmExt.ScriptLoader.SetDefaultOptions(options) Override the default options.
options - an options object (see Options Object above) containing the options that should be defaulted.
$CrmExt.ScriptLoader.SetDefaultOptions({ CheckForLatest: true })$CrmExt.ScriptLoader.SetDefaultOptions({ VersionScripts: true, Version: "1.0.0.1" })
$CrmExt.ScriptLoader.SetDefaultOptions({ CheckForLatest: true })
$CrmExt.ScriptLoader.SetDefaultOptions({ VersionScripts: true, Version: "1.0.0.1" })
Note: the options.js file should be used to configure the standard default options. Use SetDefaultOptions when a particular entity needs something different.
$CrmExt.ScriptLoader.EnableDebugableMode() Will enable Debugable Mode for the current user's session (see Debugging External Script for additional information). This function only works in IE8.
$CrmExt.ScriptLoader.DisableDebugableMode() Will disable Debugable Mode for the current user's session (see Debugging External Script for additional information). This function only works in IE8.
$CrmExt.ScriptLoader.Enqueue(scriptUrl, fn, options) Adds a new script and/or function call to the script loading queue. Items added using Enqueue will wait for previously added items to load before they are loaded (waits for previously Enqueu-ed and Load-ed items to load).
scriptUrl - a string value set to the URL of the script to be loaded. Relative path URLs are prepended with the BasePath from the options
fn - a function to be executed (if both a script and function are provided, the function is executed after the script is loaded)
options - an options object that will override the default ScriptLoader options. This options object is merged with the default options and is only used for this call
$CrmExt.ScriptLoader.Enqueue("ScriptUrl.js");
$CrmExt.ScriptLoader.Enqueue(functionPointer);
$CrmExt.ScriptLoader.Enqueue("ScriptUrl.js", functionPointer);
$CrmExt.ScriptLoader.Enqueue("ScriptUrl.js", { BasePath: "/ISV/CustomPath/" });
$CrmExt.ScriptLoader.Enqueue("ScriptUrl.js", function() { alert(); }, { BasePath: "/ISV/CustomPath/" });
$CrmExt.FormEvents.Load(scriptUrl, fn, options) Adds a new script and optional function call to the script loading queue. Items added using Load will load as soon as they are retrieved from the server.
fn - a function to be executed after the script is loaded
$CrmExt.ScriptLoader.Load("ScriptUrl.js");
$CrmExt.ScriptLoader.Load("ScriptUrl.js", functionPointer);
$CrmExt.ScriptLoader.Load("ScriptUrl.js", { BasePath: "/ISV/CustomPath/" });
$CrmExt.ScriptLoader.Load("ScriptUrl.js", function() { alert(); }, { BasePath: "/ISV/CustomPath/" });
$CrmExt.Shared Shared is an empty object that is provided for developers to attach their custom functions to. See the sample for an example of using the Shared object with your custom functions.