Monday, April 20, 2009

CRM Form Script Loader

I wrote this post as a response to a question on ms forums about using XMLHTTP as a mechanism for loading remote scripts.

It seems that using this technique causes a caching problem which can really slow things down while developing.

Here is the notorious script:


function load_script (url)
{
var x = new ActiveXObject("Msxml2.XMLHTTP");
x.open('GET', url, false); x.send('');
eval(x.responseText);

var s = x.responseText.split(/\n/);

var r = /^function\s*([a-z_]+)/i;
for (var i = 0; i < s.length; i++)
{
var m = r.exec(s[i]);
if (m != null)
{
window[m[1]] = eval(m[1]);
}
}
}

load_script("/_customscript/customscript1.js");
load_script("/_customscript/customscript2.js");
load_script("/_customscript/customscript3.js");


In my opinion loading scripts using XMLHTTP should be avoided! Why?
1. The eval() function is very slow and should only be used as a last resort i.e. if no other options are available.
2. RegExp is also known to be slow especially with large documents.
3. Using XMLHTTP synchronously hangs / blocks the entire page until each resource is fully loaded.


Here is a better choice. Why?
1. Caching can be avoided easily.
2. Works much faster.
3. Does not block the page natural loading order and does not wait for other resources to finish loading.


function ScriptLoader(func)
{
var loader = this;

/* if ScriptLoading > 0 then scripts are still loading */
loader.ScriptLoading = 0;
/* the script entity point e.g. OnCrmPageLoad */
loader.Init = func;
/*
url - script url
noc - include a nocache querystring parameter
*/
loader.Load = function(url,noc)
{
var script = document.createElement("SCRIPT");
script.src = url + (noc ? "?nocache=" + Math.random() : "");

script.onreadystatechange = function()
{
if (!(script.readyState == 'loaded' || script.readyState == 'complete'))
{
return;
}

/* if loader.ScriptLoading > 0 true else false */
if (--loader.ScriptLoading)
{
return;
}

/* finished loading (loader.ScriptLoading == 0) , call entry point */
loader.Init();
}

/* append the script to the head tag */
document.documentElement.childNodes[0].appendChild(script);
/* indicate that this script is loading */
loader.ScriptLoading++;
}
}

/*
create a Script loader object + function to be called when the script has
finished loading
*/
window.ScriptLibrary = new ScriptLoader(OnCrmPageLoad);

/* url , false - cache, true - no cache */
ScriptLibrary.Load('/ISV/SCLIB/JScript1.js',false);
ScriptLibrary.Load('/ISV/SCLIB/JScript2.js',true);
ScriptLibrary.Load('/ISV/SCLIB/JScript3.js',false);

/* Start Script Execution */
function OnCrmPageLoad()
{
var res = JS2Function();
res += "," + JS1Function();
res += "," + JS3Function();
alert(res)
}


The remote scripts functions can be written in such a way (e.g. window.FuncName = function(){ /* code */ }) which immediately exposes them to the window scope e.g.


// JScript3.js File
window.JS3Function = function()
{
return 'JS3Function';
}

// JScript2.js File
window.JS2Function = function()
{
return 'JS2Function';
}

// JScript1.js File
window.JS1Function = function()
{
return 'JS1Function';
}

Sunday, April 19, 2009

CRM 4.0 GI Partners Starter Pack


The GI Partners Starter Pack (PSP) is the best value added and cost effective solution to jump start your CRM projects. Save time and effort by integrating wizards that are able to solve common development tasks such as Field Level Security and Bulk Delete Operations. Gap missing CRM functionalities and focus your effort on your client’s CRM objectives. Take advantage of our discount offer, Royalty free distribution license and ensure your project success.

The Partner start pack contains the following wizards:

  • Field Level Security wizard (FLS)
  • Public View Manager (PVS)
  • State and Status Security wizard (SMW)
  • Bulk Delete Wizard (BDW)
  • Record and Page Counter (RCO & AVCR)

    All wizards are compliant with rollup3, Tested with IE8 and support IFD.
    All wizards supports both on-premise and partner hosted environments.
    All wizards support multi-tenancy and are available for all languages.

Click here to order our PSP package.

Saturday, April 18, 2009

CRM 4.0 Global Interface Security Package


GI Security Package is a complementary security solution for Dynamics. The package leverages the following wizards:

Field Level Security wizard (FLS)


Enables your organization to decide which fields and data are available and visible for each business unit, role and user.

Public View Manager (PVS)


Enables your organization to enhance and target business objectives by hiding, re-arranging and setting default views for each business unit, role and user.

State and Status Security wizard (SMW)


Enables your organization to decide which CRM functionality (e.g. wining an opportunity, fulfilling an invoice) is available for each business unit, role and user.

Together the security package enables your organization to enforce common security requirements and achieve common business goals out of the box. The benefits of using the security package are substantial as it eliminates development effort, ensures a low TCO and a faster ROI.

The Security Package is offered at a discounted price. Visit the Security Package product page for Pricing Information and Video Demonatrations.

Sunday, April 12, 2009

CRM 4.0 Many 2 Many IFrame Viewer

I’ve seen many posts about displaying a many 2 many grid inside an iframe so I didn’t post about it my self until now.

One of the disadvantages of using such customization is that it does not follow ms initial execution path which breaks some of the functionality.

In our case after you select an existing item the grid does not refresh / reflect your selection until you push the refresh button your self.

I wrote a small utility class that solves that problem. It also takes care of the iframe padding.

Basically a many to many iframe looks like this:

areas.aspx?oId={ObjectId}&oType={ObjectTypeCode}&security=852023&roleOrd=1&tabSet=area{relationship name}

The viewer accepts the RoleOrder and Relationship Name (TabsetId) and constructs the iframe for you.

Add the code in the entity onload event and Enjoy…


function OnCrmPageLoad()
{
/* Create a N2NViewer and give it the IFRAME (container) id */
var n2nViewer = new N2NViewer('IFRAME_account_association');
/* Set the role order - use iedevtoolber for exact parameters */
n2nViewer.RoleOrder = 1;
/* assing the relationship name (without the "area" word) */
n2nViewer.TabsetId = "gi_account_account";
/* Do the trick... */
n2nViewer.Load();
}

function N2NViewer(iframeId)
{
if (!document.all[iframeId])
{
alert(iframeId + " is missing!");
return;
}

var viewer = this;
var _locAssocObj = null;

viewer.IFRAME = document.all[iframeId];
viewer.RoleOrder;
viewer.TabsetId;

viewer.Load = function()
{
/* Construct a valid N2N IFRAME url */
viewer.IFRAME.src = "areas.aspx?oId=" + crmForm.ObjectId + "&oType=" + crmForm.ObjectTypeCode + "&security=" + crmFormSubmit.crmFormSubmitSecurity.value + "&roleOrd=" + viewer.RoleOrder + "&tabSet=area" + viewer.TabsetId;
viewer.IFRAME.onreadystatechange = viewer.StateChanged;
}

viewer.StateChanged = function()
{
if (viewer.IFRAME.readyState != 'complete')
{
return;
}

var iframeDoc = viewer.IFRAME.contentWindow.document;

/* Reomve scrolling space */
iframeDoc.body.scroll = "no";
/* Remove crmGrid Default padding */
iframeDoc.body.childNodes[0].rows[0].cells[0].style.padding = 0;

/* Save MS locAssocObj */
_locAssocObj = locAssocObj;
/* Override MS locAssocObj */
locAssocObj = viewer.locAssocObj;
}

viewer.locAssocObj = function(iType , sSubType, sAssociationName, iRoleOrdinal)
{
/* Open the Dialog */
_locAssocObj(iType , sSubType, sAssociationName, iRoleOrdinal);
/* Refresh only if our iframe contains the correnct tabset name */
if (sAssociationName == viewer.TabsetId)
{
viewer.IFRAME.contentWindow.document.all.crmGrid.Refresh();
}
}
}

//Entry Point
OnCrmPageLoad();