Tribridge Connections

A Technology, Cloud Solutions & Industry Expertise Blog


Dynamically loaded Javascript in CRM 4.0

Published: February 23, 2011

Dynamically Loaded Javascript in CRM 4.0

The most common way to customize form UI in Microsoft Dynamics CRM 4.0 is to use Javascript.  Simple customizations can be afforded through CRM’s built in Javascript editor, but what if you want to reference outside libraries (say jQuery) or publish your Javascript customizations as part of an ISV package?  This solution enables this behavior by dynamically loading external Javascript files when a form loads. 

To deploy custom Javascript files in Dynamics CRM 4.0, you’ll need a few things.

  1. Setup a folder on your CRM front end server inside the “ISV” folder.  Call it whatever you want.  For the purpose of this example I’ve called mine “ScriptCustomizations”.  Modify the Javascript below to change the path to match your deployment path.
  2. You will need to deploy Core.js to this folder.  Core.js contains core functions for script debugging inside CRM.
  3. You will need to deploy one block of custom script to the form OnLoad event for the form you want to customize with external Javascript files.

Here is the content listing for Core.js:

Date.prototype.format = function(format) {
var returnStr = '';
var replace = Date.replaceChars;
for (var i = 0; i < format.length; i++) {
var curChar = format.charAt(i);
if (replace[curChar])
returnStr += replace[curChar].call(this);
else
returnStr += curChar;
}
return returnStr;
};

Date.replaceChars = {
shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
longMonths: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
longDays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],

// Day
d: function() { return (this.getDate() < 10 ? '0' : '') + this.getDate(); },
D: function() { return Date.replace.shortDays[this.getDay()]; },
j: function() { return this.getDate(); },
l: function() { return Date.replace.longDays[this.getDay()]; },
N: function() { return this.getDay() + 1; },
S: function() { return (this.getDate() % 10 == 1 && this.getDate() != 11 ? 'st' : (this.getDate() % 10 == 2 && this.getDate() != 12 ? 'nd' : (this.getDate() % 10 == 3 && this.getDate() != 13 ? 'rd' : 'th'))); },
w: function() { return this.getDay(); },
z: function() { return "Not Supported"; },
// Week
W: function() { return "Not Supported"; },
// Month
F: function() { return Date.replace.longMonths[this.getMonth()]; },
m: function() { return (this.getMonth() < 11 ? '0' : '') + (this.getMonth() + 1); },
M: function() { return Date.replace.shortMonths[this.getMonth()]; },
n: function() { return this.getMonth() + 1; },
t: function() { return "Not Supported"; },
// Year
L: function() { return "Not Supported"; },
o: function() { return "Not Supported"; },
Y: function() { return this.getFullYear(); },
y: function() { return ('' + this.getFullYear()).substr(2); },
// Time
a: function() { return this.getHours() < 12 ? 'am' : 'pm'; },
A: function() { return this.getHours() < 12 ? 'AM' : 'PM'; },
B: function() { return "Not Supported"; },
g: function() { return this.getHours() == 0 ? 12 : (this.getHours() > 12 ? this.getHours() - 12 : this.getHours()); },
G: function() { return this.getHours(); },
h: function() { return (this.getHours() < 10 || (12 < this.getHours() && this.getHours() < 22) ? '0' : '') + ((this.getHours() == 12 || this.getHours() == 0) ? 12 : this.getHours() < 13 ? this.getHours() : this.getHours() - 12); },
H: function() { return (this.getHours() < 10 ? '0' : '') + this.getHours(); },
i: function() { return (this.getMinutes() < 10 ? '0' : '') + this.getMinutes(); },
s: function() { return (this.getSeconds() < 10 ? '0' : '') + this.getSeconds(); },
// Timezone
e: function() { return "Not Supported"; },
I: function() { return "Not Supported"; },
O: function() { return (this.getTimezoneOffset() < 0 ? '-' : '+') + (this.getTimezoneOffset() / 60 < 10 ? '0' : '') + (this.getTimezoneOffset() / 60) + '00'; },
T: function() { return "Not Supported"; },
Z: function() { return this.getTimezoneOffset() * 60; },
// Full Date/Time
c: function() { return "Not Supported"; },
r: function() { return this.toString(); },
U: function() { return this.getTime() / 1000; }
}

DiagnosticsWindow = function() {
this.Enabled = false;
this.Verbose = true;

var TraceWindowHandle = null;
var LastTraceDate = new Date();
var suspendTrace = false;

var SEVERITY_NOTSPECIFIED = -1;
var SEVERITY_INFORMATION = 0;
var SEVERITY_WARNING = 1;
var SEVERITY_ERROR = 2;

DiagnosticsWindow.prototype.Open = function() {
if (TraceWindowHandle == null) {
TraceWindowHandle = open('', 'TraceWindow', 'width=750,height=400,location=no,menubar=no,resizable=no,left=200,top=20,directories=no,status=no,scrollbars=no');

this.Clear();
}

TraceWindowHandle.focus();
}

DiagnosticsWindow.prototype.Close = function() {
TraceWindowHandle.window.close();
TraceWindowHandle = null;
}

DiagnosticsWindow.prototype.SuspendTrace = function() {
suspendTrace = true;
}

DiagnosticsWindow.prototype.ResumeTrace = function() {
suspendTrace = false;
}

DiagnosticsWindow.prototype.Clear = function() {
if (TraceWindowHandle == null) return;

TraceWindowHandle.document.close();
TraceWindowHandle.document.open();

TraceWindowHandle.document.writeln('<html>');
TraceWindowHandle.document.writeln('<head>');
TraceWindowHandle.document.writeln(' <title>Diagnostics - Trace Window</title>');
TraceWindowHandle.document.writeln(' <style>');
TraceWindowHandle.document.writeln(' BODY { margin: 0px; font-family: "Arial, Helvetica, sans-serif"; font-size: 10pt; }');
TraceWindowHandle.document.writeln(' H1 { font-size: 1.2em; font-weight: bold; margin-top: 10px; margin-bottom: 3px; }');
TraceWindowHandle.document.writeln(' H2 { font-size: 1.1em; font-weight: bold; margin-top: 10px; margin-bottom: 3px; }');
TraceWindowHandle.document.writeln(' H3 { font-size: 1.05em; font-weight: bold; margin-top: 10px; margin-bottom: 3px; }');
TraceWindowHandle.document.writeln(' P { line-spacing: 1.1em; }');
TraceWindowHandle.document.writeln(' #toolbar { background-color: #E0E0E0; margin-bottom: 10px; padding-bottom: 5px; padding-top: 3px; height: 30px; }');
TraceWindowHandle.document.writeln(' #toolbar A { color: #555555; border: 1px solid #E0E0E0; margin-left: 2px; margin-right: 2px; margin-top: 1px; margin-bottom: 1px; line-height: 22px; padding-top: 3px; padding-bottom: 3px; padding-left: 4px; padding-right: 4px; }');
TraceWindowHandle.document.writeln(' #toolbar A:hover { background-color: #D0D0D0; border: 1px solid #C0C0C0; margin-left: 2px; margin-right: 2px; margin-top: 1px; margin-bottom: 1px; line-height: 22px; padding-top: 3px; padding-bottom: 3px; padding-left: 4px; padding-right: 4px; }');
TraceWindowHandle.document.writeln(' #toolbar TABLE { font-size: 10pt; }');
TraceWindowHandle.document.writeln(' #MessageTable { font-size: 10pt; }');
TraceWindowHandle.document.writeln(' #MessageTable THEAD TR TD { padding-top: 3px; padding-bottom: 3px; padding-left: 3px; padding-right: 3px; background-color: #E0E0FF; text-decoration: underline; font-weight: bold; }');
TraceWindowHandle.document.writeln(' #MessageTable TBODY TR TD { padding-top: 2px; padding-bottom: 2px; padding-left: 3px; padding-right: 3px; border-bottom: 1px solid #E0E0E0; }');
TraceWindowHandle.document.writeln(' .warning { background-color: #FFFF99; color: #D25400; }');
TraceWindowHandle.document.writeln(' .error { color: #FFFFFF; background-color: #CC0000; }');
TraceWindowHandle.document.writeln(' .information { color: #000099; background-color: #E1F0FF; }');
TraceWindowHandle.document.writeln(' .trace { color: #404040; }');
TraceWindowHandle.document.writeln(' </style>');
TraceWindowHandle.document.writeln('</head>');
TraceWindowHandle.document.writeln('<body>');
TraceWindowHandle.document.writeln('<div id="toolbar">');
TraceWindowHandle.document.writeln(' <table border="0" cellpadding="0" cellspacing="0" width="100%" height="100%">');
TraceWindowHandle.document.writeln(' <tr>');
TraceWindowHandle.document.writeln(' <td valign="middle" width="70%">');
TraceWindowHandle.document.writeln(' <a href="javascript:opener.Diagnostics.Clear();">Clear Window</a>');
TraceWindowHandle.document.writeln(' <a href="javascript:opener.Diagnostics.Close();">Close Window</a>');
TraceWindowHandle.document.writeln(' </td>');
TraceWindowHandle.document.writeln(' <td valign="middle" align="right" width="30%" style="padding-right: 10px;">');
TraceWindowHandle.document.writeln(' <input type="checkbox" id="VerbosityCheckbox" ' + (this.Verbose ? 'checked ' : '') + 'onclick="opener.Diagnostics.Verbose = this.checked;" />');
TraceWindowHandle.document.writeln(' <label for="VerbosityCheckbox">Verbose</label>');
TraceWindowHandle.document.writeln(' </td>');
TraceWindowHandle.document.writeln(' </tr>');
TraceWindowHandle.document.writeln(' </table>');
TraceWindowHandle.document.writeln('</div>');
TraceWindowHandle.document.writeln('<div id="scrollbox" style="width:100%; height:360px; overflow: auto;">');
TraceWindowHandle.document.writeln(' <h1>Trace</h1>');
TraceWindowHandle.document.writeln(' <table border="0" cellpadding="0" cellspacing="0" width="100%" id="MessageTable">');
TraceWindowHandle.document.writeln(' <thead>');
TraceWindowHandle.document.writeln(' <tr>');
TraceWindowHandle.document.writeln(' <td width="20%">Timestamp</td>');
TraceWindowHandle.document.writeln(' <td width="25%">Category</td>');
TraceWindowHandle.document.writeln(' <td width="55%">Message</td>');
TraceWindowHandle.document.writeln(' </tr>');
TraceWindowHandle.document.writeln(' </thead>');
TraceWindowHandle.document.writeln(' <tbody>');
TraceWindowHandle.document.writeln(' </tbody>');
TraceWindowHandle.document.writeln(' </table>');
TraceWindowHandle.document.writeln('</div>');
}

/// <summary>
/// The Trace() method writes a trace message to the diagnostics window. Trace messages are
/// not shown when verbosity is turned off.
/// </summary>
DiagnosticsWindow.prototype.Trace = function(cat, message) {
if (typeof this.Enabled == 'undefined') return;
if (this.Enabled == false) return;

this.Open();

try {
date_now = new Date();

if (this.Verbose)
AddTraceMessage(date_now, cat, message, SEVERITY_NOTSPECIFIED, true);
}
catch (err) {
if (TraceWindowHandle)
TraceWindowHandle.window.close();

TraceWindowHandle = null;
}
}

/// <summary>
/// The TraceRaw() method writes a trace message to the diagnostics window. Trace messages are
/// not shown when verbosity is turned off.
/// </summary>
DiagnosticsWindow.prototype.TraceRaw = function(cat, message, allowHTML) {
if (typeof this.Enabled == 'undefined') return;
if (this.Enabled == false) return;

this.Open();

try {
date_now = new Date();

if (this.Verbose)
AddTraceMessage(date_now, cat, message, SEVERITY_NOTSPECIFIED, allowHTML);
}
catch (err) {
if (TraceWindowHandle)
TraceWindowHandle.window.close();

TraceWindowHandle = null;
}
}
/// <summary>
/// The LogInformation() method logs an event to the diagnostics window. The information
/// will be displayed when verbosity is turned off.
/// </summary>
DiagnosticsWindow.prototype.LogInformation = function(cat, message) {
if (typeof this.Enabled == 'undefined') return;
if (this.Enabled == false) return;

this.Open();

try {
date_now = new Date();

AddTraceMessage(date_now, cat, message, SEVERITY_INFORMATION, true);
}
catch (err) {
if (TraceWindowHandle)
TraceWindowHandle.window.close();

TraceWindowHandle = null;
}
}
/// <summary>
/// The LogWarning() method logs a warning to the diagnostics window. The warning will
/// be displayed when verbosity is turned off.
/// </summary>
DiagnosticsWindow.prototype.LogWarning = function(cat, message) {
if (typeof this.Enabled == 'undefined') return;
if (this.Enabled == false) return;

this.Open();

try {
date_now = new Date();

AddTraceMessage(date_now, cat, message, SEVERITY_WARNING, true);
}
catch (err) {
if (TraceWindowHandle)
TraceWindowHandle.window.close();

TraceWindowHandle = null;
}
}

/// <summary>
/// The LogError() method logs an error message to the diagnostics window. The error message will
/// be displayed when verbosity is turned off.
/// </summary>
DiagnosticsWindow.prototype.LogError = function(cat, message) {
if (typeof this.Enabled == 'undefined') return;
if (this.Enabled == false) return;

this.Open();

try {
date_now = new Date();

AddTraceMessage(date_now, cat, message, SEVERITY_ERROR, true);
}
catch (err) {
if (TraceWindowHandle)
TraceWindowHandle.window.close();

TraceWindowHandle = null;
}
}

function AddTraceMessage(timestamp, category, message, severity, allowHTML) {
if (suspendTrace && severity < SEVERITY_ERROR)
return;

if (!allowHTML) {
message = message.split('<').join('&lt;');
message = message.split('>').join('&gt;');
}

var elapsedMS = timestamp.getTime() - LastTraceDate.getTime();
var messagesTable = TraceWindowHandle.document.getElementById("MessageTable");
var tableBody = messagesTable.tBodies[0];
var newRow = tableBody.insertRow(tableBody.rows.length);
var cell1 = newRow.insertCell(0);
var cell2 = newRow.insertCell(1);
var cell3 = newRow.insertCell(2);
var cssClass;

switch (severity) {
case SEVERITY_INFORMATION:
cssClass = "information";
break;
case SEVERITY_WARNING:
cssClass = "warning";
break;
case SEVERITY_ERROR:
cssClass = "error";
break;
default:
cssClass = "trace";
break;
}

if (cssClass != "") {
cell1.className = cssClass;
cell2.className = cssClass;
cell3.className = cssClass;
}

cell1.innerHTML = timestamp.format("[h:i:s A]");
cell2.innerHTML = category;
cell3.innerHTML = message;

LastTraceDate = timestamp;

var scrollbox = TraceWindowHandle.document.getElementById("scrollbox");
var scrollTop = scrollbox.scrollHeight > 360 ? scrollbox.scrollHeight - 360 : 0;

scrollbox.scrollTop = scrollTop;
}

DiagnosticsWindow.prototype.TraceWriteRaw = function(markup) {
if (typeof this.Enabled == 'undefined') return;
if (this.Enabled == false) return;

this.Open();

try {
var messagesTable = TraceWindowHandle.document.getElementById("MessageTable");
var tableBody = messagesTable.tBodies[0];
var newRow = tableBody.insertRow(tableBody.rows.length);
var cell1 = newRow.insertCell(0);

cell1.setAttribute("colspan", "3");
cell1.innerHTML = markup;

LastTraceDate = date_now;

var scrollbox = TraceWindowHandle.document.getElementById("scrollbox");
var scrollTop = scrollbox.scrollHeight > 360 ? scrollbox.scrollHeight - 360 : 0;

scrollbox.scrollTop = scrollTop;
}
catch (err) {
if (TraceWindowHandle)
TraceWindowHandle.window.close();

TraceWindowHandle = null;
}
}
}

window.Diagnostics = new DiagnosticsWindow();

After you have created and deployed Core.js, you will need to modify each individual form’s load code to fetch external Javascript files. 

To do so, customize an entity’s form (Settings > Customization > Customize Entities):

Customize Entities

Open the main form’s properties:


Dynamics CRM form properties

Click the “Form Properties” button:


Editing form properties in CRM 4.0

Click the Edit button on the OnLoad event, enable it and add the following script.

window.DynamicScripts = new Array();
window.DynamicScriptsLoaded = 0;

// *** BEGIN EDITS HERE *** Add any scripts you want to be loaded when the page begins, Core.js is required.
window.CustomScriptPath = "/ISV/ScriptCustomizations/";
var traceEnabled = false;

window.DynamicScripts[0] = window.CustomScriptPath + "jQuery.js";
window.DynamicScripts[1] = window.CustomScriptPath + "AnotherScript.js";
window.DynamicScripts[2] = window.CustomScriptPath + "YetAnotherScript.js";
// *** END EDITS HERE ***

var coreScript = document.createElement("SCRIPT");
coreScript.language = "javascript";
coreScript.src = window.CustomScriptPath + "Core.js";

window.__scriptReadyStateChanged = function() {
if (this.readyState == "loaded" || this.readyState == "complete") {
window.DynamicScriptsLoaded += 1;

if (window.DynamicScriptsLoaded == 1) {
window.Diagnostics.Enabled = traceEnabled;
window.Diagnostics.LogInformation("Dynamic Scripts", "Loading " + window.DynamicScripts.length.toString() + " scripts");
}
else {
var linkString = '<a href="' + this.src + '">' + this.src + '</a>' + ' ' + this.readyState;
window.Diagnostics.Trace("Dynamic Scripts", linkString);
}

if (window.DynamicScripts.length > window.DynamicScriptsLoaded - 1) {
var script = document.createElement("SCRIPT");

script.language = "javascript";
script.src = window.DynamicScripts[window.DynamicScriptsLoaded - 1] + "?nocache=" + Math.random();
script.onreadystatechange = window.__scriptReadyStateChanged;

document.getElementsByTagName("HEAD")[0].appendChild(script);
}
else {
window.Diagnostics.LogInformation("Dynamic Scripts", "Script loading complete");

if (document.FormHelper != null)
document.FormHelper.HandleFormOnLoad();
}

}
}

coreScript.onreadystatechange = window.__scriptReadyStateChanged;

document.getElementsByTagName("HEAD")[0].appendChild(coreScript);

As a bonus, you can enable tracing in your scripts and a trace window, by setting enableTrace = true in the above script.  In your own code, you can write messages to the window using the window.Diagnostics function in Dynamics CRM 4.0.  If you find this script helpful or to make any useful modifications to it, please share your comments and let us know!

Share Your Thoughts With Us

Load more comments
Thank you for the comment! Your comment must be approved first


Related Insights

Infographic: Bridge the Gap for Project Service Performance
Published: November 16, 2016

Learn the 7 ways to modernize project service management. Deepen customer engagement and build employee loyalty across the project lifecycle to grow your professional services business.

Infographics, E-Books, and Whitepapers