It's not very often that I see something new for Microsoft CRM that really impresses me to the point where I feel like I need to tell everyone about it. After all, CRM 4.0 has been out in the wild for more than 2 years now, and in that time many people in the CRM community have contributed the add-ons, tricks and code snippets to fill in short-comings in the product, or generally improve it's useability.
Today, however, is different. A colleague brought a blog post to my attention that really blew me away. The post is from Dave Berry, on his CRM Entropy blog, and it includes the code and instructions for implementing a feature that I've been missing in CRM since the v1.0 days: the ability to edit records in a list view, or grid.
We've built similar functionality using server-side technologies that have worked quite well. But they are complex and can be a challenge to implement. Dave's code is built completely in javascript and is embedded behind buttons via the ISV.Config XML. We've tested it out on some dev machines, and it's fast and very intuitive. Because it's all handled via the ISV.Config, I believe this will work on CRM Online as well.
I can easily foresee this client-side extension becoming a staple of every CRM project we do. Congratulations, Dave, on a job well done!
3.25.2010
CRM Grid Editor - All done with Javascript
1 comments Labels: crm 4.0, crm online, customization, javascript
8.26.2009
Change Left Nav Links to Point to Custom Associated Entities
In an earlier post, I talked about the shortcomings of the out-of-the-box Address entity. In a recent project, we replaced the system Address entity with a custom Address entity. When we added a 1:N relationship for Contacts:New_Addresses, the link to the new address entity showed up on the left hand nav of the Contact form, as expected. However, it was added at the bottom of the left hand nav links under the "Workflow" link.
We wanted to keep the Addresses link where it was, but load the associated view of our custom entity instead. So when a user clicks on the "More Addresses" link, they will see the list of custom address records, rather than the out-of-the-box addresses. Here's the OnLoad code we used:
// load the custom address entity instead of the system addresses
// in the associated view
// I used the IE Developer Toolbar to find the id of the custom entity's loadAreaif (document.getElementById('navAddresses') != null) { document.getElementById('navAddresses').onclick = function() { loadArea('new_contact_new_address') };
}// Hide the link to the custom address entity that was added under the workflow link
// when we created the 1:N relationshipif (document.getElementById('nav_new_contact_new_address') != null) { document.getElementById('nav_new_contact_new_address').style.display = 'none';
}
2 comments Labels: customization, javascript
6.04.2009
Including External JavaScript Files
There are a number of blogs out there with good information on how to include external javascript files in your CRM OnLoad events. I’m only adding this post to the mix to bring together some of the different information out there for easy reference.
CRM provides three primary places to add client-side code: OnLoad and the OnSave for the form, and OnChange for individual fields. In the form editor for an entity, OnLoad and OnSave scripts can be added to the form properties. Each field has its own properties area where OnChange scripts can be added.
For easier management of client-side code, we often use just the OnLoad area to store all the javascript we add for a particular form. We’ll define the functions we want to use throughout the form, and call them from field or form events. You can use attachEvent to hook into events on individual fields so you don’t need to add javascript behind every single field. An attachEvent works like this (notice my function is calling the FireOnChange method as well):
/* define your function */
function MyFunction(){
crmForm.all.myfieldname.FireOnChange();}/* attach your function to the onclick event
crmForm.all.myfieldname.attachEvent('onclick', MyFunction, false);
Check out Mitch Milam’s blog at http://blogs.infinite-x.net/2009/04/20/changing-a-checkbox-field/ for a look at how he used attachEvent.
Taking this approach a step further, it sometimes makes sense to store the javascript in an external .js file. In that case, the form’s OnLoad properties will just contain a reference to an external file, sort of like an ‘include’ statement that appends our external .js file as the form loads in the browser. Here’s an example from http://www.henrycordes.nl/post/2008/05/External-js-file-and-CRM.aspx:
/* Place in the OnLoad area of a form */function IncludeExternalScript(scriptFile)
{
var netRequest = new ActiveXObject("Msxml2.XMLHTTP");
netRequest.open("GET", scriptFile, false);
netRequest.send(null);
eval(netRequest.responseText);
}
/* There is an ISV folder in the CRM webroot */
/* that your filepath should point to */
IncludeExternalScript('/ISV/_customscript/customscript.js');
CallFunctionInCustomScriptjs();
One thing to be aware of with this approach is that if you set up any users with the full Outlook client that allows them to work with CRM offline, all your calls to external script files will fail when they are offline since the file is on the CRM server. You can include a check before you add the external script and then do something like disable all the form’s fields if they are offline, but this starts to defeat the purpose of using the external file to start with.
Also, there are various methods of attaching external scripts. This method seems to work well, but you should be aware that the file will be cached in the browser, so if they make a change, users may not get the changes right away. One way around this is to go into IIS and navigate to the ISV folder and add a custom header to the external file to control caching. More info on this is at http://ronaldlemmen.blogspot.com/2008/07/javascript-files-and-caching.html.
1 comments Labels: customization, javascript
5.18.2009
The CRM Address Entity: There’s more to it than you think!
I’ve been working with Microsoft CRM in production environments since 1.2 was released, but apart from some minor tweaks to the form I haven’t had to work with the Address entity in too much depth. Until now. I was surprised to find that there are a lot of, er, “undocumented features” that make this built-in entity behave quite differently than I expected. The SDK has a few remarks about the Address entity, but it took me a while to put all the pieces together, and I thought I’d share what I’ve found:
1. Two blank address records are created for each Account and Contact. Out of the box, CRM Accounts and Contacts have built-in fields to capture two addresses. These fields are address1_street1, address1_city, address1_stateorprovince, etc., as well as another whole set for address2 fields.
Whenever an Account or Contact is created, the CRM platform creates two blank address records related to that parent Account or Contact.The purpose of these two blank address records is to store synchronized data that is entered in the address fields on the Account or Contact itself. The platform handles keeping these two entities in synch. If you change the values stored in the Contact’s address fields, the associated Address record is updated automatically.
2. The first two Address records that the platform creates are hidden in the “More Addresses” associated view on the Account or Contact. I guess this is because these addresses should be maintained on the Account or Contact, not in the Address entity itself. The purpose seems to be so that addresses added on the Account or Contact form are available when you do an address lookup on a quote or order. (Note that you’ll need to enter data in the “Address Name” field on the Account or Contact if you want it to show up in the Address Lookup view as well.)3. You cannot update the Address record via workflow. You can create workflows based on the Address entity, but it’s not available to update itself. Not sure why, though…
4. You can’t modify the relationship mappings between the Account entity or the Contact entity and the Address entity. And you can’t add custom relationships to the Address entity.
5. Addresses are not available on Security Roles. You want to restrict users from editing or deleting Addresses? Can’t do it, at least not with Security Roles. Go figure…
6. There’s no built-in way to add the parent Account or Contact to the Address form. Adding the “Parent” field to the Address’s Advanced Find or Lookup views only returns a blank column as well. This seems to be a shortcoming or a bug of some sort. For all other entities with a N:1 relationship, you can add the ‘1’ part of that equation to the ‘N’ form. For example, if you create an entity called “Projects” with a N:1 relationship to Accounts, you can add the Account lookup field to the Project form. The lookup field actually consists of an array that contains a GUID, a string for the record’s name, and the object type code (OTC). In the case of Addresses, the only thing stored in the Address table is a field for the GUID. Without the name of the related object and the OTC, the interface can’t present a normal, functioning lookup field.
WORKAROUNDS:
With all of these limitations, organizations that need to manage multiple addresses for their Accounts or Contacts, and that need to perform logic against these addresses regularly, may need to do some workarounds. Among the options to consider: create a plug-in that “un-hides” the first two address records so they’re visible in the “More Addresses” associated views. Another option would be to create a custom entity with a N:1 relationship to the Account or Contact. This option has the benefit of allowing you to do the full range of mappings and workflow that you can do with just about any other entity. The downside is that if you want the address fields on the Account or Contact to be used and available for mail merges and Outlook synchs, the custom entity option will require some more create workflow solutions.
10 comments Labels: configuration, crm 4.0, customization, tips
4.30.2009
US States Picklists Values
Download it here.
tag to the ending
2 comments Labels: crm, customization
4.03.2009
Error Importing Customizations
On a development environment where I had imported a new organization, the developer was getting an error when trying to import customizations from another system (both on Update Rollup 3), and was unable to publish any customizations either. On importing, he got the following error:
“Failure: customeraddress: A SQL Server error occurred. Try this action again. If the problem continues, check the Microsoft Dynamics CRM Community for solutions or contact your organization's Microsoft Dynamics CRM Administrator. Finally, you can contact Microsoft Support.”
In the event log on the server was an error that said:
EventID: 19457
Source: MSCRMWebService
Customization Import failed. Error: Could not find stored procedure 'p_PublishMetadata'.
In digging into the database in SQL Server Management Studio, I saw that this sproc and another called 'p_PublishLabelsByObjectId' were listed not as dbo owned like the rest of the stored procedures, but user owned (DOMAIN\Username.p_PublishMetadata, for example).
I changed the ownership using the following query and publishing and importing started to work:
sp_changeobjectowner 'p_PublishLabelsByObjectId', 'dbo'
and then the same for the other stored procedure:
sp_changeobjectowner 'p_PublishMetadata', 'dbo'
(You'll get a caution about changing ownership that you can ignore.) Close SQL Management Studio and re-open it to verify that the ownership has changed (refreshing didn't work for me for some reason).
Somehow, when this organization was imported, the ownership didn't get set correctly on these two stored procedures. Tip of the hat to Luke Rogers for the tip on changing ownership of stored procedures one at a time.
Environment: SQL Server 2008 Standard, Microsoft CRM 4.0 UR3 Professional with the original organization disabled, and a new organization imported.
0 comments Labels: crm 4.0, customization, sql 2008
10.07.2008
SQL Error When Trying to Import Custom Entity
This error cropped up recently when moving customizations for a client from their dev server to their production server. The import looked like it was working, but then an error window popped up saying there was a SQL Error with cc_EntityName_DuplicateMatchingRecord. Turns out, this is a known issue, as stated in the CRM 4.0 Server Readme file - but who ever reads those things?!
Custom entity cannot be imported to a new system when duplicate detection has been enabled and then disabled.
The error message that you may receive is "Either the file could not be uploaded
or Import failed.”An additional message contains a reference that includes the
name of the custom entity and the text "A SQL Server error occurred.” This error
will occur when you try to import a custom entity into a new system if the
custom entity had had duplicate detection enabled but now has duplicate
detection disabled. To work around this issue, turn duplicate detection on for
the custom entity before you export it. After the custom entity is imported to a
new system, disable duplicate detection for the custom entity.
0 comments Labels: crm 4.0, customization
9.19.2008
Create a Link to a File or Directory from a CRM Record
If you are like many of our clients, you still retain a lot of Word and Excel documents about your customers. Wouldn't it be nice to be able to link directly to the appropriate folder from the CRM record for that customer? Here's how to use some OnLoad script to turn a regular text field into a link to a folder.
1) Add a text field attribute called 'folderlink' to your account form. Give it a maximum length of 300 characters to make sure you have enough space for long paths.
2) Paste the following code into the OnLoad event for the Account entity's form:
if (crmForm.all.new_folderlink != null)
{
crmForm.all.new_folderlink.style.color = '#0000FF';
crmForm.all.new_folderlink.style.textDecorationUnderline = true;
crmForm.all.new_folderlink.style.fontWeight = 'bold';
var folderunc = crmForm.all.new_folderlink.DataValue;
{
crmForm.all.new_folderlink.ondblclick = function()
{
window.open(folderunc);
}
}
}
3) That's it. Now save and publish your form and paste a path to a folder into that field and try it out. You'll notice that the script above also formats the text in the field to be blue, bold, and underlined so it looks like a standard link, helping your users intuitively understand that they can click on it to open the folder.
3 comments Labels: crm, customization, javascript
8.21.2008
CRM As A Rapid Development Platform
I'm late in getting this post out, but better late than never! Dave Yack released his book "CRM As a Rapid Development Platform" last month. It's an indispensable tool for every developer who is building on top of Microsoft Dynamics CRM 4.0. It's available in printed form or as an e-book, and also includes a ton of code samples and some helpful videos. It's 645 pages long, so you're sure to find plenty in the book that you weren't aware of, even if you've been working with CRM for a long time! I don't know how Dave found the time for this monster, but I'm glad he did!
1 comments Labels: crm 4.0, customization, sdk
7.16.2008
New Post on MS CRM Team Blog: LinkedIn with CRM
I've got another new post on the Microsoft CRM Team blog here. This one is about integrating LinkedIn information with leads in CRM. Check it out!
0 comments Labels: crm 4.0, customization
1.22.2008
Editing the Sitemap in CRM 4.0
I was banging my head on the wall trying to figure out why a simple edit I made to the sitemap in a new CRM 4.0 deployment was not working. The sitemap lets you add or re-arrange the left hand navigation in the main area of CRM. I was trying to move the Service Calendar to the top of this list in the left hand nav. In 4.0 you need to use a new "Titles" element to add a custom title, and, as in 3.0, you can define whether the navigational item shows up based on a user's priveleges in a certain entity. I made a simple change to the sitemap and when I tried to import it, I got the "either the file could not be uploaded or this is not a valid customization file" error. I checked and re-checked my XML and it looked fine.
It wasn't until a co-worker suggested I try moving the "Title" node above the "Privileges" node that CRM let me import the customizations file. I wouldn't have thought the order mattered in this case, but apparently it did.
Here's what DID work:
<SubArea id="ServCal" icon="/_imgs/ico_18_servicecal.gif" url="/sm/home_apptbook.aspx" availableoffline="false">
<Titles>
<Title lcid="1033" title="Scheduling Matrix">
</Titles>
<Privilege entity="activitypointer" privilege="Read">
<Privilege entity="service" privilege="Read">
</SubArea>
Here's what DID NOT work:
<SubArea id="ServCal" icon="/_imgs/ico_18_servicecal.gif" url="/sm/home_apptbook.aspx" availableoffline="false">
<Privilege entity="activitypointer" privilege="Read">
<Privilege entity="service" privilege="Read">
<Titles>
<Title lcid="1033" title="Scheduling Matrix">
</Titles>
</SubArea>
14 comments Labels: crm 4.0, customization
1.17.2008
Microsoft CRM 4.0 Software Development Kit (SDK)
Other blogs have made note of it, but for those of you haven't heard about it, or haven't downloaded it yet, the CRM 4.0 SDK is available now on Microsoft's download site.
0 comments Labels: crm 4.0, customization, downloads
4.18.2007
My Guest Post on the Microsoft CRM Team Blog On MSDN
Check it out! I was invited to post on the CRM development team's blog on MSDN.
I posted on how to show context-relevant files and folders within a CRM record. Hope you find it useful!
2 comments Labels: crm, customization, mvp, sharepoint
3.30.2007
Add Timestamps To Activity Descriptions
Under the heading of "I think this is unsupported, but it works and is easy to undo if necessary" comes the following entry on adding a timestamp button to an activity form.
Someone wanted a way to put a date and time stamp in the description field of a Task activity so they could track when they started working on the task and when they stopped. This way, if they worked on the task multiple times they would have an easy way to see how much time they spent on the various parts of the task.
So, in the isv.config.xml file on the CRM server, I added the following bit of code between the Entities tags:
<Entity name="task">
<ToolBar ValidForCreate="0"
ValidForUpdate="1">
<Button ToolTip="Start Time"
Icon="/_imgs/clock_add.gif" Title="Start Time"
JavaScript="crmForm.all.description.DataValue+='\nSTART TIME:
'+Date() +'\n'" />
<Button ToolTip="End Time"
Icon="/_imgs/clock_add.gif" Title="End Time"
JavaScript="crmForm.all.description.DataValue+='\nEND TIME:
'+Date() +'\n'" />
</ToolBar>
</Entity>
And here's what it looks like:
6 comments Labels: customization, javascript
4.11.2006
How To Extend CRM In Order To Clone A Record
Ben Vollmer over on his Mid-Atlantic Microsoft CRM blog has a great post on extending CRM with simple javascript in order to clone a record. Based on a sample for cloning a contact, Ben shows how to add a custom button to a case form that calls a simple .htm page. The .htm page contains some javascript that opens a new case and pre-populates the new case with data from the master case. This is a great example of how easy and flexible the customization of CRM is. I added some comments to his post to show some other ways to use this kind of script to get nice results. Check it out!
Thanks, Ben...
8 comments Labels: customization, javascript, tips
1.17.2005
OnChange Event Javascript
I frequently hear from people looking for samples of javascript code for onchange events in CRM. Here's a sample we use in our internal CRM system for customer service cases.
We use cases to track all of our time, since all of our time should be spent working on something for a customer or working on an internal project. On our case forms we have a casetype picklist with the following values:
Maintenance Contract
Billable Services
Training
Sales
Admin/Overhead
If the case type is Maintenance Contract, we want to make sure the technician enters the contract that the case is related to. I use the following code in the casetype event box on the case customization form to set the contract field to "required" if the case is a Maintenance Contract case:
switch (parseInt(event.srcElement.returnValue, 6))
if (crmForm.casetypecode.returnValue != "1"){crmForm.SetFieldReqLevel("contractid",0);}
if (crmForm.casetypecode.returnValue == "1"){crmForm.SetFieldReqLevel("contractid",1);}
The first line basically says there are six choices in the picklist (the five listed options plus the default null option). Then the code says that if the selected option is not equal to option 1, then set the contractid to Not Required. The remainder of the code says that if the selection is equal to option 1, then set the contractid field to Required.
When setting up the form, set the contractid field to be Not Required by default. Note that you need the line of javascript that says "If selection is not equal to option 1, set the contractid field to Not Required." Even though this field is not required by default, you want to take into account the times when the end user sets the field to Option 1 (making the contractid field required) and then changes his mind, or realizes he made a mistake. In that case, you want to make sure the contractid field gets set back to being not required.
5 comments Labels: customization, javascript
12.16.2004
Customizing Printed CRM Records
Got a post from someone on the newsgroup today about how to customize quotes and print them and/or email them. I posted a reply that got pretty long, so I thought I would finish it here. Here's the text of my reply:
I haven't done any work directly with quotes, but I have found that for most of the CRM records, the canned printed forms are somewhat lacking in content and visual appeal. (In addition to being unable to customize them with logos and company letterhead type stuff.)
I ran into this problem with leads. I have a client whose salespeople wanted to be able to print out a "Lead Card" to take on sales calls that would have all the most relevant info on one page. I created a report in Crystal that showed all the lead's pertinent information on the top (basically everything from the Lead form Information tab) and the 5 most recent activities on the bottom, plus the rep's name, etc.
This kind of report is easy to make in Crystal, the problem is that it creates a report for every lead in the database, and would crush the server if I ran it. So the next thing I did was to add a parameter to the report, then add an ISV button to the lead form that passed the lead's GUID to Crystal. You click on the button and Crystal report viewer launches and asks you to select a parameter (you have to have two choices, so in order to keep people from running the whole thing I made the second choice return a blank report). So the salesperson can leave the parameter at the default choice without selecting, and click OK, and voila---they have a customized, easily printed lead card.
[There's more about using parameters in CRM and Crystal Reports on Microsoft's website in a whitepaper about optimizing CRM performance.]
I think this would work well for quotes too. The next problem is how to email them, which I haven't had to try to figure out yet. That's a little harder, because from here you would basically be trying to email a dynamic web page. Maybe you could then use a PDF utility. Copying the report and pasting it into Outlook or word (or a CRM email) will cause it to lose formatting. I think I would look for a cheap or shareware PDF utility for salespeople to use so they wouldn't have to buy a copy of Acrobat.
Here are some screenshots of the lead card button and the screen that comes up in the Crystal Report Viewer window prompting you about the parameter:


After the user clicks OK, the one page lead report for the specific lead comes up. This same method could be used for quotes, or any other entity in CRM.
1 comments Labels: customization, tips

