Thursday, June 24, 2010

How to modify header of CRM entity form

CRM single-record form always displays the same word 'Information' in the header area below entity name. This area can be reused to display something more meaningful about the record using simple script below:

var headerElement = document.getElementById("leftNavBreadcrumbText");

if (headerElement != null) {
//Can change style in any way
// headerElement.style.color = [Select color];
headerElement.innerHTML = 'More info about this record...';
}

Wednesday, June 23, 2010

MSCRM4 Web Service Toolkit for JavaScript

Daniel Cai (http://danielcai.blogspot.com) published a great little bit of JavaScript that cleverly packages CRM's APIs for a very easy consumption through JavaScript. After including his code, distributed through Microsoft Public License, you can write update/create or other CRM Web Service calls using a very simple syntax. For example, code below updates an entity:

var memberId =;
var updateMember = new CrmServiceToolkit.BusinessEntity("new_member");
var updateDict = {
"nr_dob": dateOfBirth,
"nr_workphone": workPhone,
"nr_cellphone":cellPhone,
"nr_email": email
};

updateMember.attributes["new_memberid"] = memberId;

for (var key in updateDict) {
if (updateDict[key] != null) updateMember.attributes[key] = updateDict[key];
}

var updateResponse = CrmServiceToolkit.Update(updateMember);
if (!updateResponse == "") { alert("Member update failed!"); }

Tuesday, February 23, 2010

New features of Microsoft Dynamics CRM 5.0 - extended list

Main new features

• Single page for all views, including single record
• subgrids
• collapsible sections
• customizable lookups
• office ribbon (fully customizable)
• aggregate charts for any grid
• groupby in FetchXML
• MOSS integration and provisioning
• teams can have roles, own records and queues, be default
• supports partial code trust on server
• Solution management
• Auditing
• global picklists

Forms

• global search box
• header, footer for single record view
• multiple forms per entity (based on user role and record type)
• custom tabs
• new form editor supports drag/drop, inline properties
• new controls (subgrid, choice)
• any control is overridable
• ribbon controls can be created for any form/grid/entity-based, context sensitive
• ISV isolation

Custom code


• Plugins in online environment
• CRM and metadata services consolidated
• support for CRM transactions and tracing from code
• CAS (execution permissions)
• separate processes per tenant
• plugin profiler and monitoring
• plugin publishing per tenant
• Transactions: commit and rollback (no distributed transactions)
• WCF and AJAX support

CRM Solutions

- entities
- workflows
- ui
- roles
- templates
- transactions
• support for import/export, packaging for external distribution
• solutions can be based on other solutions
• default solution ('customize entities' view in CRM 4)
• solution is an entity

Grids and visualization


• Runtime filters
• personal default grid view
• sticky grid views
• visualizations tightly coupled with grids using ASP.NET Chart
• support for aggregate data charting, time-based data
• drilldown support for visualizations
• works on whole data set (not based on page view)
• import/export for visualizations
• visualizations for subgrids

Connections - ad-hoc relationships between records
Queues: owned by users or teams. can contain any entity


SharePoint provisioning

• creation of sites; publishing of CRM roles to SharePoint sites
• Documents as CRM entity
- no records stored in CRM
- documents are fetched from SharePoint
• Create documents associated with CRM object
• Allow SharePoint actions (check in/out, version history etc)
• Team-owned
• Full-text document search
• Workflow support (custom actions to create documents)
• Main site for Org, subsites for each BU, subsite for each team in each BU
- doc library for entity type
 folder in doc library for each object instance


Recurrent appointments

Monday, February 22, 2010

Main new features in CRM 5.0

• Single page for all views, including single record
• subgrids
• collapsible sections
• customizable lookups
• office ribbon (fully customizable)
• aggregate charts for any grid
• groupby in FetchXML
• MOSS integration and provisioning
• teams can have roles, own records and queues, be default
• supports partial code trust on server
• Solution management
• Auditing
• global picklists

Friday, January 29, 2010

Creating inline toolbar

This code creates inline buttons on the form which greatly enhance usually bland CRM UI.
Code is taken from Adi Katz' blog http://mscrm4ever.blogspot.com/


////Example
//function OnCrmPageLoad()
//{
// window.GeneralToolbar = new InlineToolbar("gi_toolbar");
// GeneralToolbar.AddButton("btnReset","Reset","15%",Reset_Click);
// GeneralToolbar.AddButton("btnLookup","Lookup","10%",Lookup_Click);
// //GeneralToolbar.RemoveButton("btnLookup");
// GeneralToolbar.AddButton("btnAddNote","Create Note","16px",AddNote_Click,"/_imgs/ico_16_5_d.gif");
//}
//
//function Reset_Click()
//{
// alert('Reseting Fields...');
//}
//
//function Lookup_Click()
//{
// alert('lookup records...');
//}
//
//function AddNote_Click()
//{
// alert('Add new note');


//}

function InlineToolbar(containerId)
{
var toolbar = this;
var container = document.all[containerId];

if (!container)
{
return alert("Toolbar Field: " + containerId + " is missing");
}

crmForm.all[containerId + "_c"].style.display = 'none';

container.style.display = "none";
container = container.parentElement;

toolbar.AddButton = function(id,text,width,callback,imgSrc)
{
var btn = document.createElement("button");
var btStyle = new StyleBuilder();
btStyle.Add( "font-family" , "Arial" );
btStyle.Add( "font-size" , "12px" );
btStyle.Add( "line-height" , "16px" );
btStyle.Add( "text-align" , "center" );
btStyle.Add( "cursor" , "hand" );
btStyle.Add( "border" , "1px solid #3366CC" );
btStyle.Add( "background-color" , "#CEE7FF" );
btStyle.Add( "background-image" , "url( '/_imgs/btn_rest.gif' )" );
btStyle.Add( "background-repeat" , "repeat-x" );
btStyle.Add( "padding-left" , "5px" );
btStyle.Add( "padding-right" , "5px" );
btStyle.Add( "overflow" , "visible" );
btStyle.Add( "width" , width );

btn.style.cssText = btStyle.ToString();
btn.attachEvent("onclick",callback);
btn.id = id;

if (imgSrc)
{
var img = document.createElement("img");
img.src = imgSrc;
img.style.verticalAlign = "middle";
btn.appendChild(img);
btn.appendChild(document.createTextNode(" "));
var spn = document.createElement("span");
spn.innerText = text;
btn.appendChild(spn);
}
else
{
btn.innerText = text;
}

container.appendChild(btn);
container.appendChild(document.createTextNode(" "));

return btn;
}

toolbar.RemoveButton = function(id)
{
var btn = toolbar.GetButton(id)
if (btn)
{
btn.parentNode.removeChild(btn);
}
}

toolbar.GetButton = function(id)
{
return document.getElementById(id);
}

function StyleBuilder()
{
var cssText = new StringBuilder();
this.Add = function( key , value ){cssText.Append( key ).Append( ":" ).Append( value ).Append( ";" );}
this.ToString = function(){return cssText.ToString();}
}

function StringBuilder()
{
var parts = [];
this.Append = function( text ){parts[ parts.length ] = text;return this;}
this.Reset = function(){parts = [];}
this.ToString = function(){return parts.join( "" );}
}
}

Monday, May 5, 2008

Creating note with a file attachment

There is often a need to attach a file to an entity (contact, task, etc.). Easiest way to do it is by creating a note belonging to the entity and attaching a file to the note. Code below shows how to create a note, encode a file, attach file to the note and attach note to a parent entity.



public string GetMimeType(byte[] fileBytes)
{
try
{
System.Security.Cryptography.Pkcs.ContentInfo info = new System.Security.Cryptography.Pkcs.ContentInfo(fileBytes);
return info.ContentType.Value;
}
catch
{
return "application/octet-stream";
}
} // end GetMimeType

private void AddAttachment(CrmService.CrmService crmService, Guid parentAnntotationId, string attachmentBase64Data, string mimeType, string attachmentFileName)
{
CrmService.UploadFromBase64DataAnnotationRequest upload = new CrmService.UploadFromBase64DataAnnotationRequest();

upload.AnnotationId = parentAnntotationId;
upload.FileName = attachmentFileName;
upload.MimeType = mimeType;
upload.Base64Data = attachmentBase64Data;

crmService.Execute(upload);
} // end AddAttachment


private String EncodeFile(string attachmentFileName, ref String mimeType)
{
string data;
// Create an instance of StreamReader to write text to a file.
// The using statement also closes the StreamReader.
using (System.IO.StreamReader sr = new System.IO.StreamReader(attachmentFileName))
{
System.IO.TextReader reader = sr;
data = reader.ReadToEnd();
}

// Encode the data using base64.
byte[] byteData = new byte[data.Length];
byteData = System.Text.Encoding.UTF8.GetBytes(data);
string encodedData = System.Convert.ToBase64String(byteData);

////mimeType = GetMimeType(byteData);

return encodedData;
} // end EncodeFile

// Create a new note, attach file to it, attach note to an entity
//
// Create or find an entity to which new note should be appended
// example below attaches note to a task referenced by its GUID newTaskID

....... ...................

CrmService.annotation newNote = new CrmService.annotation();
newNote.subject = "Subject for my new note...";

newNote.objectid = new CrmService.Lookup();
newNote.objectid.Value = newTaskID; // GUID of entity
newNote.objectid.type = "task"; // type of entity
newNote.objecttypecode = new CrmService.EntityNameReference();
newNote.objecttypecode.Value = CrmService.EntityName.task.ToString();

newNote.ownerid = new CrmService.Owner();
newNote.ownerid.Value = ownerID; // ID of the owner (could be current user or could be owner of parent entity)
newNote.ownerid.type = CrmService.EntityName.systemuser.ToString();

Guid noteID = service.Create(newNote);

String mimeType = String.Empty, encodedFile = String.Empty;

encodedFile = EncodeFile(fileNameToAttach, ref mimeType);

AddAttachment(service, noteID, encodedFile, mimeType, System.IO.Path.GetFileName(fileNameToAttach));


Thursday, May 1, 2008

Architecture astronauts take over

Great article by Joel Spolsky, about Live Mesh from MS and its roots in Groove:

Architecture astronauts take over

Tuesday, April 29, 2008

Making any text field on CRM form look and behave like a hyperlink control

Some CRM form fields behave by default like hyperlinks. If you need to add a custom attribute to a CRM entity and display it on the form making it look like a hyperlink, this code is one way to do it:

var urlField = crmForm.all.myfield;

//Set the text color to blue
urlField.style.color = 0x0000ff;

//Display the text underlined
urlField.style.textDecorationUnderline = true;

//Use the hand cursor
urlField.style.cursor = "hand";

//Add the ondblclick (or onclick) event handler to open target site in a new window
urlField.ondblclick = function() {
//Get the url from value in the field
var url = urlField.DataValue; // Or get it from somewhere else

//If the url is not empty
if (url != null && url.length > 0) {
window.open(url, "_blank");
}
}

Friday, April 25, 2008

Enable more than 8 tabs in CRM form

Some of CRM settings look to be spontaneous rather than designed; one of them is restriction of maximum number of tabs on CRM form to 8 tabs only. It crops up in every other project when someone just has to have a form with lots of tabs.

Turns out you can easily change it by modifying form editor code file: open to Tools\FormEditor\formeditor.aspx (path in CRM 4.0) and change
var _iMaxTabs = 8;
to some other number.

That's it; it'll work both in CRM 3.0 and 4.0. Should've been a configurable parameter really.

Thursday, April 24, 2008

Class browser and disassembler

I had to look inside a bunch of MS-minted assemblies recently and stumbled upon this great tool:

Lutz Roeder's Reflector for .NET
"Reflector is the class browser, explorer, analyzer and documentation viewer for .NET. Reflector allows to easily view, navigate, search, decompile and analyze .NET assemblies in C#, Visual Basic and IL."
http://www.aisto.com/roeder/dotnet/

Determine user role membership in JavaScript

CRM unfortunately lacks field-level security. The poor man's implementation of field-level security is to enabling changing attribute properties (such as enabled, visible) on CRM form based on current user's CRM role. Here's the code to get user role membership (or roles current user participates in); this code can be referenced by external js file and hooked to various form or field events.

function getUserId()
{
try
{
var command = new RemoteCommand("SystemUser", "WhoAmI", "/MSCRMServices/");

var oResult = command.Execute();
if (oResult.Success){

return oResult.ReturnValue.UserId;}
}
catch(e){alert("Error while retrieving userid.");}
return null;

}

function getUserRoles(userId)
{
try {
var command = new RemoteCommand("UserManager", "GetUserRoles");

command.SetParameter("userIds", "" + userId + "");
var oResult = command.Execute();
if (oResult.Success){

//alert("Roles: " + oResult.ReturnValue);
return oResult.ReturnValue;
}
}
catch(e){alert("Error while retrieving roles.");}
return null;

}

function userHasRole(userId, roleName)
{
result = getUserRoles(userId);
if (result != null)
{
var oXml = new ActiveXObject("Microsoft.XMLDOM");
oXml.resolveExternals = false;
oXml.async = false;
oXml.loadXML(result);

roleNode = oXml.selectSingleNode("/roles/role[name='" + roleName + "']");
if (roleNode != null)
{
if (roleNode.selectSingleNode("roleid[@checked='true']") != null)
return true;
}
}

return false;
}


function currentUserHasRole(roleName)
{
userId = getUserId();
return userHasRole(userId, roleName);
}


// final function returns true/false, used it to toggle field or tab visibility;
// could easily work off an array of role names
function currentUserCanSeeSecuriField()

{
var requiredRole = "CEO-Business Manager";
var isInrole = currentUserHasRole(requiredRole);
if (isInrole) return true;
return false;
}

Hiding toolbar button on CRM form

To hide the button, you'd need to find the control name, one way to do it is by simply looking through rendered HTML. Hit CTRL-N when the form is open to open it again, with IE menus and toolbars and view source. Find your button. Hide it using similar code:

var buttonID = "ISV_New_1_2_ConverttoEvent";
var theBtn = window.document.getElementById( buttonID );
if (theBtn != null) {
// ... do whatever to determine hide/show state
theBtn.style.visibility = "hidden";
}

Controlling Form Assistant pane on CRM forms

Sometimes, especially whith many attributes on a form, it can be too large and you want to hide Form Assistant when form loads (it's mostly expanded in CRM 3.0 and collapsed in CRM 4.0). In other cases, you'd want to expand it by default (I had some users complain that they can't find it...).

Here's how you control it:

// to make sure it loads lookup data, if you want it expanded
if (document.all.RelatedInformationPane != null)
document.all.RelatedInformationPane.LoadContextData();

if (document.all.RelatedInformationPane != null)
document.all.RelatedInformationPane.ToggleInformationPane();


Btw, on large form you can move and set window size of the form during Form_Load event:

window.moveTo(3,3);
window.resizeTo(1000,760) ;

Hook up external javascript file in CRM Form Load event

I didn't write this, but it works nevertheless:

try {
var script = document.createElement("script");
script.language = "javascript";
script.src = "http://MyServer/myscripts/myfunctions.js";

document.getElementsByTagName("head")[0].appendChild(script);

}
catch (err)
{ alert("Error while loading global script module myfunctions.js: " + err.description);}

Wednesday, April 16, 2008

Microsoft CRM 4.0 missing features

No filtered lookups (check out Stunnware for this)
No editable grids (like a datagrid for example)
Views doesn't show the total number of records, no plugins for other summaries
Advanced find; mail merge; report wizard - all limit to one up-level of parent data
Custom controls on forms; built-in field validation mechanism
Attribute-level security
Both data migration mechanisms are still very weak; I end up using direct table access thru sql...
Multi-select pick lists?
Service calendar needs more work

airplane security idea of the month

From Schneier's list: Wacky airplane security idea of the month: Force everyone to wear abracelet that, when remotely activated, gives the person a debilitatingshock. No, really. A company is trying to commercialize this idea.The mind boggles.

http://www.lamperdlesslethal.com/