Sunday, July 27, 2008

Simplifying CRM UI development

This is a utility class which incorporates common tasks developers usually endure while developing mscrm solutions.

Spec:

CRMField.Disable() - Disable Field
CRMField.Enable() - Enable Field

CRMField.HideCell() - Hide Cell ( field + label )
CRMField.ShowCell() - Show Cell

CRMField.HideRow() - Hide Entire Row
CRMField.ShowRow() - Show Entire Row

CRMField.Required() - Make Field Required *
CRMField.Level() - Get Field Required level

CRMField.NotRequired() - Make Field Not Required

CRMField.Changed() - Returns whether the Field was changed since it was loaded
CRMField.IsEmpty() - Checks whether the Field is null or empty("")

CRMField.Alert() - Helper function - Alerts the Field DataValue
CRMField.OnChange( OnFieldChangeEvent ) – Attaches a function to the Field OnChange Event
CRMField.ReLabel( "New label" ) - Change Field label (HTML)

CRMField.InitValue - Holds the Field Initial Value
CRMField.LastValue - Holds the Field Last value before it was changed

CRMField.First() - Revert to the Field Initial Value
CRMField.Last() - Revert to the Field Last Value


Paste the CrmField class inside the entity onload event or global.js (unsupported).


function CrmField( fieldId )
{
var crmField = this;

crmField.Id = fieldId;
if( crmField.Id == null || crmField.Id == "" ) return;

crmField.Field = document.getElementById(fieldId);
if( this.Field == null ) return;

crmField.InitValue = crmField.Field.DataValue;
crmField.LastValue = "";
crmField.Cell = document.getElementById(this.Field.id + "_d");
crmField.Label = document.getElementById(this.Field.id + "_c");
crmField.Row = crmField.Cell.parentElement;
crmField.Disable = function(){ crmField.Field.Disabled = true; }
crmField.Enable = function(){ crmField.Field.Disabled = false; }
crmField.HideCell = function(){
crmField.Field.style.visibility = "hidden";
crmField.Cell.style.visibility = "hidden";
crmField.Label.style.visibility = "hidden";
}
crmField.HideRow = function(){ crmField.Row.style.display = "none"; }
crmField.ShowCell = function(){
crmField.Field.style.visibility = "visible";
crmField.Cell.style.visibility = "visible";
crmField.Label.style.visibility = "visible";
}
crmField.ShowRow = function() { crmField.Row.style.display = "inline"; }
crmField.Required = function() { crmForm.SetFieldReqLevel(crmField.Id,true); }
crmField.NotRequired = function(){ crmForm.SetFieldReqLevel(crmField.Id,false); }
crmField.Level = function() { return crmField.Field.RequiredLevel; }
crmField.Changed = function() { return crmField.Field.IsDirty(); }
crmField.IsEmpty = function() { return (crmField.Field.DataValue == null); }
crmField.Alert = function() { alert(crmField.Field.DataValue); }
crmField.Last = function() { crmField.Field.DataValue = crmField.LastValue; }
crmField.First = function() { crmField.Field.DataValue = crmField.InitValue; }
crmField.OnChange = function(f){ crmField.Field.attachEvent("onchange",f); }
crmField.ReLabel = function(txt){ crmField.Label.innerHTML = txt; }
}

//Test - contact form
var firstname;
function OnCrmPageLoad()
{
firstname = new CrmField("firstname");
firstname.ReLabel("Given Name");
firstname.OnChange( OnFirstnameChange )
}

function OnFirstnameChange()
{
firstname.Alert();
}

OnCrmPageLoad();


Feel free to comment.

isv.config Toggle Toolbar button


Isv.config toolbar buttons don’t support toggling, although you can accomplish this by creating a drop menu sometimes a button is the better choice.Here a simple example that demonstrates how to implement a toggle button.





Isv.config toggle button xml:

<Button Icon="/_imgs/ico_18_role_g.gif" JavaScript="OnDoSomethingClick();">
<Titles>
<Title Text="On" LCID="1033"/>
</Titles>
</Button>


In the entity onload event expose the OnDoSomethingClick function to the page level e.g. this.OnDoSomethingClick = OnDoSomethingClick;

inside the button , before your implementation add the following script:


function OnDoSomethingClick(){
if(OnToolbarButtonClick())
{
//Your Implementation Here...
}
else
{
//Your Implementation Here...
}
}

function OnToolbarButtonClick()
{
//Get the current active HTML Element
var button = document.activeElement;
//Search the LI ( menuitem container ) Element
while( button.tagName != 'LI' ) button = button.parentElement;
/*
Toolbar Image - you need to remove all references to this object
if the button does not contain an image.
*/
var btnImg = button.childNodes[0].childNodes[0].childNodes[0];
//Button Text
var btnTxt = button.childNodes[0].childNodes[0].childNodes[1];
//Saves the state ( direction ) on the button as a toggle attribute
button.toggle = !((button.toggle == 'undefined')? false:button.toggle);
if( button.toggle )
{
// Replace with relevant Off text
btnTxt.innerText = 'Off';
btnImg.src = '/_imgs/ico_18_role_x.gif';
}
else
{
// Replace with original text and image
btnTxt.innerText = 'On';
btnImg.src = '/_imgs/ico_18_role_g.gif';
}
return button.toggle;
}

this.OnDoSomethingClick = OnDoSomethingClick;


If you need to keep the button state between loads you can create a bit attribute and save its value each time a user clicks on the button. Then, in the onload event, assign the bit value to the button Toggle attribute. For example:



var button = getButtonByIdOrByName(); /*Not Implemented*/
//Search the LI ( menuitem container ) Element
while( button.tagName != 'LI' ) button = button.parentElement;

var toggleState = crmForm.all.<bit attribute id>.DataValue
button.Toggle = toggleState!=null?toggleState:false;

Saturday, July 26, 2008

Changing the Activity / History default view

The associated activity and history views within a given entity are not customizable and you cannot change their default view settings.
Although this type of DOM manipulation is unsupported, if this is a “must have” requirement you can implement the change with the following js script.

put the code inside the entity onload event.


//Activity scheduledend options
var ActivityOptions =
{
All : "All",
Overdue :"Overdue",
Today :"Today",
Tomorrow :"Tomorrow",
Next7Days :"NextXDays;7",
Next30Days :"NextXDays;30",
Next90Days :"NextXDays;90",
Next6Months :"NextXMonths;6"
}

//Activity History actualend options
var HistoryOptions =
{
All : "All",
Today : "Today",
Yesterday : "Yesterday",
Last7Days : "LastXDays;7",
Last30Days : "LastXDays;30",
Last90Days : "LastXDays;90",
Last6Months : "LastXMonths;6",
Last12Months: "LastXMonths;12"
}

var _loadarea = loadArea;
loadArea = function(sArea, sParams, sUrl, bIsvMode)
{
//load the iframe
_loadarea(sArea, sParams, sUrl, bIsvMode);

if( sArea != "areaActivityHistory" &&
sArea != "areaActivities" ) return;

//create the iframe object
var iframe = document.getElementById(sArea + "Frame");
//wait until the iframe is fully loaded ("complete")
iframe.onreadystatechange = function()
{
if( iframe.readyState == "complete")
{
var picklist,option;
//reference to the iframe document
var iframeDoc = iframe.contentWindow.document;
switch(sArea)
{
case "areaActivityHistory":
picklist = iframeDoc.all.actualend[0];
/* change to suit your needs */
option = HistoryOptions.Last90Days;
break;
case "areaActivities":
picklist = iframeDoc.all.scheduledend[0];
/* change to suit your needs */
option = ActivityOptions.Next7Days;
break;
default: return;
}
picklist.value = option;
picklist.FireOnChange();
}
}
}

Replacing CRM alert() with a Custom page

if you’re looking for a way to replace the JavaScript alert() function with something more appealing then you have come to the right place.

Integrate the following code inside each entity onload event (supported) or put it inside the global.js file (unsupported) The global.js resides in the “_static\common\scripts\ “ folder.


alert = function( message ) {
var features = “[Modal Dialog Features]”;
var url = “/” + ORG_UNIQUE_NAME + “/isv/appVD/alert.aspx?msg=” + escape(message);
window.openModalDialog( url , window , [FEATURES] );
}


Replace the [FEATURES] place holder with proper settings.
Replace the appVD with your application Virtual directory name.


Inside the alert.aspx page render the message as follows:


<html>
<head>
<title>Information Dialog</title>
</head>
<body>
<%=Request[“msg”]%>
</body>
</html>


You can add more QueryString Parameters like imagetype or implement your own report error button if you’re planning an ISV solution.




How To Remove padding when presenting CRMGrid in an IFRAME


The IFRAMEHandler is a utility class that handles the CRM GRID - IFRAME padding issue gracefully.
It also handles the preloading of a progress image and title e.g. “Account Opportunities”
If the URL is missing then an Error image is displayed followed by an inline message.

Here is a simple instruction on how to implement the solution:
1. Paste the IFRAMEHandler class inside the entity onload event.
2. Instanciate the IFRAMEHandler passing it the IFRAME id e.g. “IFRAME_Test”
3. The second parameter is an optional title/name that describes what the iframe is about to load.
4. Call the load function with the relevant url, this could be any url e.g. crm addressable url , your own aspx page or an external website.







Here is the complete code sample




var IFRAME_Test;
var IFRAME_More;

function OnCrmPageLoad()
{
//Create a Handler for the first IFRAME ( IframeId , IframeTitle )
IFRAME_Test = new IFRAMEHandler("IFRAME_Test","Account Homepage");

//Load the specified URL, this can be an inernal crm url , your own aspx or an external website
IFRAME_Test.LOAD("/[Organization Name]/_root/homepage.aspx?etc=1");

/* - */

//Create a Handler for the second IFRAME( IframeId )
IFRAME_More = new IFRAMEHandler("IFRAME_More");
//Set the iframe title, The title is displayed under the loading image
IFRAME_More.TITLE = "More Information";
IFRAME_More.LOAD("[URL]");
}

/* Paste the handler inside the entity onload event. */

function IFRAMEHandler(sIframeId,sTitle)
{
var Instance = this;

if(isNullOrEmpty(sIframeId)) return null;

/* Public Interface */

Instance.TITLE = sTitle;
//Get the Iframe object
Instance.IFRAME = document.getElementById(sIframeId);

//Bind to the change event
Instance.IFRAME.onreadystatechange = OnIframeReady;

//Pre-Defined messages
Instance.STRINGS = {
IFRAME_FAILEDLOAD : "", // + Title
IFRAME_PAGELOADING : "Loading" // + Title
}

Instance.IMAGES = {
IFRAME_LOADING : "/_imgs/AdvFind/progress.gif", //Progress
IFRAME_ERROR : "/_imgs/ico/16_L_remove.gif" //Error
}

Instance.LOAD = function(sUrl)
{
/* Reference the iframe document body and inject preloading html */
var IFRAMEBody = getIframeBody();
IFRAMEBody.style.margin = "0px";
IFRAMEBody.scroll = "no";

IFRAMEBody.innerHTML = "<table height='100%' width='100%' style='cursor:wait;border:1px solid #6b92ce'><tr><td valign='middle' align='center'><img id='Image' alt='' src='" + Instance.IMAGES.IFRAME_LOADING + "'/><br><span id='Message'>" + Instance.STRINGS.IFRAME_PAGELOADING + " " + Instance.TITLE + "</span></td></tr></table>";

/* Set the iframe url or preset an inline error message */
if(!isNullOrEmpty(sUrl)) Instance.IFRAME.src = sUrl;
else
{
var IFRAMElements = getIframeDoc().all;
IFRAMElements.Image.src = IMAGES.IFRAME_ERROR;
IFRAMElements.Message.innerHTML = STRINGS.IFRAME_FAILEDLOAD + " " + Instance.TITLE;
}
}

/* Private functions */
window.attachEvent("onunload",dispose);

function dispose()
{
Instance.STRINGS=null;
Instance.IMAGES=null;
Instance.IFRAME=null;
Instance=null;
}

function getIframeBody()
{
return getIframeDoc().body;
}

function getIframeDoc()
{
return Instance.IFRAME.contentWindow.document;
}

function OnIframeReady()
{
if( Instance.IFRAME.readyState!='complete') return;
if(typeof(getIframeDoc())=="unknown") return;

var IFRAMEBody = getIframeBody();
if(!IFRAMEBody.document.all.crmGrid) return;

IFRAMEBody.scroll="no";
IFRAMEBody.style.padding="0px";
IFRAMEBody.childNodes[0].rows[0].cells[0].style.padding="0px";
}

function isNullOrEmpty( obj )
{
return obj == null obj == "undefined" obj == "";
}
}

/* Entry point */
OnCrmPageLoad();