Microsoft CRM Tricks and Tips
February 06, 2012
XRM 2011 JavaScript: Another 101 Lesson in Microsoft Dynamics CRM 2011
When you’re learning JavaScript, you’ll often come across the need to do something to every field on the form. When you do, it’s important to have efficient code to make such widespread changes so the user doesn’t have to wait for the JavaScript to finish. This lesson consists of several parts, some of which you’ll probably already know but hopefully there are some things in here for everyone.
Setting up our Environment
First thing first, open your CRM 2011 development environment and browse to an account form. Once the account form is open, hit F12 on your keyboard. A window should popup that looks like this:
If instead, you see the window in the bottom of your browser page. Click the little “Unpin” button shown here:
This is IE Developer Tools and if you haven’t used this in the past, then I hope you enjoy not having to save/publish/refresh/test nearly as often. To start, we’re going to see how many fields we have on the form. When inside of the CRM form, we’d use something like:
Xrm.Page.data.entity.attributes.getLength()
But when we’re inside Developer Tools, we actually have a different context. So we need to click on the “Console” tab and execute:
frames[0].Xrm.Page.data.entity.attributes.getLength()
Anything we want to execute on the form, we can do by simply prepending “frames[0].” in Developer Tools to get the proper context. This allows us to write and test code instantaneously, which is a huge timesaver.
Iterating Fields
Now that we are playing in our Developer Tools sandbox, let’s iterate through some fields to see which fields are required. To use multiple lines, click the double up arrow in the bottom right corner.
Here’s some slightly modified code from the SDK that we can just copy and paste into our Console:
var message = "The following fields are required:\n";
frames[0].Xrm.Page.data.entity.attributes.forEach(function (attribute, index) {
if (attribute.getRequiredLevel() == "required") {
message += " \u2219 " + attribute.getName() + "\n";
}
});
alert(message);
Note: I can’t stress enough how awesome the SDK is. Everyone I’ve met who partakes in the SDK creation, maintenance, etc. have been top notch people. If you aren’t using the SDK, you are missing out in tons of awesome tips, features and customizations.
You’ll notice that the above code quickly goes through each field and checks to see if the field is required. We could also check if the field is dirty (meaning the user changed the value), if the field is enabled, or whatever. It’s just a simple way to make mass changes quickly.
About “forEach”
For the experienced developers, you may be thinking did IE get ForEach? No, but the CRM developers were gracious enough to add this into the Xrm variable. For those of you unfamiliar, this was an old Mozilla thing that made life a lot easier when dealing with arrays. Expanding further, the Xrm forEach approach is faster than most arrays people would generate using for loops. You can write some JScript that is faster and I’ll get into that one day, but at this point you are better off using the forEach approach.
I’ve said it several times, but I really like the Xrm object due to its attention to performance. In fact the method here is about a hundred times faster than iterating the crmForm.all object. I’ll back that up with some pretty graphs and such in a later post.
Checking for Changed (Dirty) Fields
var message = "The following fields are dirty:\n";
frames[0].Xrm.Page.data.entity.attributes.forEach(function (attribute, index) {
if (attribute.getIsDirty()) {
message += " \u2219 " + attribute.getName() + "\n";
}
});
alert(message);
Notice how easy it is to change what we are checking for. Whether we are looking for a specific value, dirty fields, required fields, or whatever, doing so inside the forEach function makes it fast and easy.
Back to the Web Resource
To go from the Developer Tools back to the Web Resources, we need to replace all of the “frames[0].” references with blanks. IE Developer Tools is a great way to create and test our code without affecting anyone else and there is a lot more to developer tools than the console. I hope you enjoy!
Posted by Paul Way on February 06, 2012 at 08:25 AM in CRM Best Practices, CRM Development, CRM Javascript, Dynamics CRM 2011, Microsoft CRM Customizations, Microsoft CRM Tricks and Tips, XRM | Permalink | Comments (1) | TrackBack (0)
February 01, 2012
Using a No-Operation Plugin to Examine the IPluginExecutionContext in Microsoft Dynamics CRM
If you have ever had the privilege of developing a plugin for CRM 2011 you know that one of the most important aspects of plugin development is understanding what is available in the context that is passed to the plugin by CRM. After creating a few plugins you generally have a good idea of what is available for the common operations of CRM such as Create, Retrieve, Update, and Delete, but some of the more uncommon request to the OrganizationService may still be cloudy.
One of the methods that I use to understand what is available in the IPluginExecutionContext of a plugin is to use a no-operation plugin registered for the message that I will be handling that will trace the context out to the event log. This is a great way to see a list of all of the data contained in the context’s InputParameters, OutputParameters, SharedVariables, and images. Once you have a trace of what is available in the context of the plugin, the plugin development seems like a much less arduous task.
Since tracing the plugin context is something that I commonly do when developing plugins for messages that I am not familiar with, I thought I would share the no-operation plugin that I use to trace the context for a plugin. The plugin that I use can be registered for any message, entity, or stage and it will trace the output to the event log on the server with a source of MSCRMServices. Simply register the plugin for the desired message, perform an action in CRM that will trigger the plugin, and voila, an event log entry will show up in the event log on the server.
Since the plugin requires access to the event log it cannot be used in a sandboxed environment and therefore it will not work online, but that shouldn’t be a big hurdle as I am sure if you are developing plugins you will have access to a test environment in which you can register the plugin for testing purposes.
You can download the plugin here. Good luck with your plugin and hopefully, the no-op plugin will have you on your way to a speedy plugin development.
Posted by Nick Doriot on February 01, 2012 at 09:48 AM in CRM Development, Dynamics CRM 2011, Microsoft CRM Customizations, Microsoft CRM Tricks and Tips | Permalink | Comments (0) | TrackBack (0)
January 30, 2012
Removing Query Data Cached by the OrganizationServiceContext in Microsoft Dynamics CRM
One of the great set of tools provided by the CRM 2011 SDK are the SDK Extensions which provide the OrganizationServiceContext for accessing data in CRM. The OrganizationServiceContext provides a way to access CRM and provides features such as change management, exposing an IQueryable interface, implementing a LINQ query provider, and providing caching services. It is the caching services that I would like to take a look at today.
Most of the time, the caching provided by the OrganizationServiceContext is exactly what you are looking for as a developer. It provides faster access to the data exposed by the OrganizationService and reduces the load on the network. However, there are times, when the retrieving data that you would like to have a current set of data rather than a set of cached data. There are a couple of methods that can be employed if you would like to retrieve a set of current data using the OrganizationServiceContext. One of these is to modify the configuration in the web.config or app.config file that is used to specify how the client context is initialized. Another is to instantiate the OrganizationServiceContext yourself and pass an instance of a non-caching OrganizationService when constructing the context.
While these methods will work and have their purposes, most of the time I would like to use a caching instance of the OrganizationService but still be able to control the caching so that I can retrieve a current set of data if necessary (I like to have my cake and eat it too). The great news is that it is possible to control the data caching of the OrganizationServiceContext to some extent if you understand the structure of the context class that is being used.
If you are using early binding and the CrmSvcUtil.exe to generate an OrganizationServiceContext to be used when accessing data in CRM, the class that gets generated derives from the CrmOrganizationServiceContext. If you are using late binding you will more than likely be using the CrmOrganizationServiceContext directly. The CrmOrganizationServiceContext implements the IOrganizationServiceContainer interface that exposes a Service property. If you have not specified a type for the service to be used by the context, this object will be an instance of the CachedOrganizationService. In addition, the CachedOrganizationService class exposes a Cache property that is an IOrganizationServiceCache instance. It is this instance of the IOrganizationServiceCache that exposes the methods that allow items to be removed from the cached used by the context.
Now that we have an understanding of the structure of the context it is simple to write an extension method that will remove cached data items. The following snippet of code can be used to remove entity data from the set of data cached by the context.
1: public void RemoveCachedData(OrganizationServiceContext context, string entityLogicalName, Guid? id) {
2:
3: var serviceContainer = context as IOrganizationServiceContainer;
4: if (serviceContainer == null) {
5: return;
6: }
7:
8: var cachedOrgService = serviceContainer.Service as CachedOrganizationService;
9: if (cachedOrgService == null) {
10: return;
11: }
12:
13: var orgServiceCache = cachedOrgService.Cache as IOrganizationServiceCache;
14: if (orgServiceCache == null) {
15: return;
16: }
17:
18: orgServiceCache.Remove(entityLogicalName, id);
19: }
As you can see, we simply need to cast the context to an IOrganizationServiceContainer and drill down through the object until we get at the actual OrganizationServiceCache. Once we have an instance of the OrganizationServiceCache we can use one of the Remove methods to remove the cached data. There are several other overloaded Remove methods that allow data to be removed and I have only shown one of them that will allow data to be removed for a specific entity. In the snippet I have shown, if the id argument is not supplied, any cached data for the specified entity will be removed.
Now all that needs to be done is call the method to remove the cached data before executing your query to retrieve data. If you would like to remove other pieces of cached data you can browse the CRM 2011 SDK and have a look at the Remove methods exposed by the OrganizationServiceCache class.
Posted by Nick Doriot on January 30, 2012 at 09:46 AM in CRM Development, Dynamics CRM 2011, Microsoft CRM Customizations, Microsoft CRM Tricks and Tips | Permalink | Comments (0) | TrackBack (0)
January 20, 2012
Retrieving Activity Feed Post with the OrganizationService
One of the new features introduced with Update Rollup 5 for CRM 2011 was the introduction of activity feeds to CRM. Activity Feeds give users the ability to monitor what is happening to various business entities. The out of the box functionality includes auto posts that include activities such as status changes in business entities, and manual posts that are created by users. Users also have the ability to post comments to posts that are created in an activity feed. Recently, I had the need to retrieve activity feeds for various business entities in order to display a summary of interactions that had recently taken place for a set of accounts and its related entities. While the out of the box functionality allows a user to see the record wall for an individual record, it does not give the user the ability to see activity posts for multiple records on a single wall, hence the need to retrieve the posts using the OrganizationService.
The OrganizationService provides two messages for retrieving activity posts. These are the RetrieveRecordWallRequest and RetrievePersonalWallRequest. These messages do exactly what you would think. The RetrieveRecordWallRequest retrieves all of the posts that involve the specified record in CRM, while the RetrievePersonalWallRequest retrieves all of the posts for a user that they are following or in which the user is mentioned. Let’s explore these two messages in a little more detail so that we can use the data that they expose.
In order to retrieve post to a record wall we will use the RetrieveRecordWallRequest. The following shows an example of the service call.
1: RetrieveRecordWallResponse recordWallResponse = null;
2: RetrieveRecordWallRequest recordWallRequest = new RetrieveRecordWallRequest();
3: recordWallRequest.CommentsPerPost = 50;
4: recordWallRequest.Entity = new EntityReference("account", accountGuid);
5: recordWallRequest.PageNumber = 1;
6: recordWallRequest.PageSize = 100;
7: recordWallResponse = _serviceProxy.Execute(recordWallRequest) as RetrieveRecordWallResponse;
As you can see, in this request to the service we created an instance of the RetrieveRecordWallRequest and passed it to the Execute method of the OrganizationService. Notice in line 3 that we specified 50 for the number of CommentsPerPost and in line 6 we also specified the PageSize as 100. Both of these are the maximum allowed values when making a request for wall posts. This will return up to 100 posts for the record and up to 50 comments for each post. If more posts are desired, another request can be made with the PageNumber property of the request changed to specify a different page. Also, notice that for the record wall request that we had to specify an entity reference to the record for which we wanted posts as done in line 4.
The other request for posts that can be made is for personal wall posts. This is done using the RetrievePersonalWallRequest message. The following shows an example of the service call.
1: RetrievePersonalWallResponse personalWallResponse = null;
2: RetrievePersonalWallRequest personalWallRequest = new RetrievePersonalWallRequest();
3: personalWallRequest.CommentsPerPost = 50;
4: personalWallRequest.PageNumber = 1;
5: personalWallRequest.PageSize = 100;
6: personalWallResponse = _serviceProxy.Execute(personalWallRequest) as RetrievePersonalWallResponse;
As you can see in this example, the request is a little simpler. In this case we still specified the CommentsPerPost, PageNumber, and PageSize, but we did not have to specify an entity reference. This is because the RetrievePersonalWallRequest will retrieve the post corresponding to the user in context that is making the service request. If we wanted to retrieve the wall for a different user we would have to impersonate the user for which we wanted posts when getting a reference to the OrganizationService used.
So now that we have the data, what does it look like? Well, both of the request return a result that contains an EntityCollection property that is a collection of Post entities. Each of the post entities will also contain data for the comments if they exist in the RelatedEntities collection of the individual entities. The relationship to reference is the “Post_Comments” relationship. The following diagram gives more detail as to what data is exposed.
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
Probably the most important piece of data in the entities is the [text] attribute. This is the attribute that actually contains the text for the post of comment. The text in this field is already localized for the current user, but it does contain placeholders for other records that are referenced by the post. The format of the placeholder is [ObjectTypeCode,ID,”Display String”] for example, [1,00000000-0000-0000-0000-000000000000,”An example post or comment.”]. This placeholder will have to be replaced with the desired text if the post is going to be displayed for the end user.
Hopefully, this example will help you get the activity post data for your business requirements. If you need more information concerning activity feeds you can check out the activity feed section of the CRM SDK here.
Posted by Nick Doriot on January 20, 2012 at 08:00 AM in Activity Feeds, CRM Development, Dynamics CRM 2011, Microsoft CRM Tricks and Tips | Permalink | Comments (0) | TrackBack (0)
January 18, 2012
Microsoft Dynamics CRM 2011 Standalone Sandbox Using VirtualBox Part 1
One of the best approaches to learn CRM is to have a sandbox. A lot of recommendations out there suggest having your IT administrator provide you with a CRM sandbox to play in. That is fine and is surely a good idea, but personally creating a sandbox will certainly give you the satisfaction of getting your hands dirty and will give you a little respect with your colleagues. So here we are going to look at getting a basic MS CRM 2011 up and running with Oracle’s VirtualBox.
So why VirtualBox?
- It is free
- Microsoft Dynamics CRM 2011 requires a 64-bit operating system to install
- Microsoft does not provide a desktop virtual platform that supports 64-bit guest OS. Microsoft's 64-bit virtual platform, Hyper-V, only runs on Windows server. Even the 64-bit version of Windows Virtual PC will not support 64-bit guest operating systems.
- VirtualBox can mount and run Hyper-V images, including the Microsoft provided demo VPC. (PartnerSource or Customersource login required).
So unless you have access to a server with Windows 2008 installed, VirtualBox is your best option for setting up a standalon virtual sandbox on your PC. Note that when Windows 8 is released, Microsoft will support Hyper-V on the desktop, so our recommendation may change at that time.
The steps included in this post should be applicable to setting up a standalone sandbox in "virtually" any other virtualization platform.
Performing the steps in order is very important to keep things easy. Here are the brief steps we are going to perform:
- Create a VirtualBox Instance
- Install Windows Server 2008 R2 Standard 64-bit
- Promote server to Active Directory domain controller
- Install SQL Server 2008 R2
- Install IIS and ASP.NET
- Install CRM 2011
This guide will enable us to create a brand new CRM environment within less than 3 hours, but there are a few prerequisites that you will want to have downloaded:
- Windows Server 2008 - Standard or Enterprise
- CRM 2011 Server Edition
- SQL Server 2008 R2 (x86, x64, ia64) - Standard or Enterprise
- Product Key for CRM 2011 Sever. *IMPORTANT: A valid key is a must to finish the install.
Other tools needed:
- ISO mounting tool, this guide is using MagicDisc. Use your own ISO mounting tool if you have one more familiar to you
- VirtualBox, which can be found at www.virtualbox.org
I am only going point out the installation steps necessary to get a very basic CRM 2011 environment up and running. This means that if a step is not mentioned I used the default. If you are interested in adding features please do so, as the scope here is just the bare bones.
Create a VirtualBox Instance
*IMPORTANT: Hardware virtualization must be enabled in the BIOS. This is an easy toggle and will not cause any issues to your system, but you will receive an error about VT-x/AMD-V and will not be able to boot Windows Server 2008 until this change is made. The setting lingo varies from Bios to Bios but a common word in all will be ‘virtualization’, so look for that.
Create a new VM host; in this case CRMDEMO2011 is the name. The defaults are fine for this example, but you may want to bump up the RAM if you are going to do some presenting or adding SharePoint, Outlook or Visual Studio. I also strongly recommend that you bump up the disk space to a minimum of 40gigs if you are going to use SharePoint, Outlook or VisualStudio. In any case, make sure you set the version of Windows to 8 (64 bit) or 2008 (64 bit). Both work, but the ‘8’ version automatically sets the RAM higher which is probably better.
1: Create VM Host
2: Mount Windows Server 2008 R2 ISO
We are going to use our mounting software from our host machine and mount the Windows Server 2008 R2 there. Make note of the drive that the mount is using, in this case ‘I:’.
3: Attach Drive with ISO to VM
The Windows Server 2008 R2 ISO is mapped on drive ‘I:’ from your host machine. Attach the ISO by going to Settings -> Storage -> IDE Controller -> select Empty or create a new controller and map it to Host Drive ‘I:’ or the drive you used for the Windows Server 2008 R2 ISO. When the VM starts the drive will be ‘D:’.
Summary: On host mount Windows Server 2008 R2 ISO on drive ‘I:’, on VM (CRMDEMO2011) the drive will be ‘D:’.
4: Start VM
Starting the VM with the Windows Server 2008 ISO attached to the ‘I:’ will start the install with ease because it is now a part of the boot sequence.
Install Windows Server 2008 R2 Standard 64-bit
Installing a new Windows Server 2008 R2 is pretty straight forward. Go through all the defaults. You will want to add your product key here if you have one. You may want to allow all the updates to happen if you have a nice network connection. Once that is all done, it is not a bad idea to back up your CRMDEMO2011.VDI file. That way you have a nice blank server ready to go for future use.
Two things for VirtualBox:
1: After you are done with the server install, go ahead and install the guest additions, so you can get a nice full screen:
2: A very important VirtualBox command: [RIGHT CTRL] +Delete = CTRL+ALT+DELETE for the VM…probably the only one you really need to know. This will drive you nuts if you don’t know it…you will hate VitualBox if you don’t remember!
Promote Windows Server 2008 to Active Directory Domain Controller
This step is in my opinion the most daunting. For many years I have be conditioned that installing a domain server is a one and done deal. That two domain server on the same network is really bad. Yes, having two or more domain controllers can be bad, but here we are going to set it up so we do not compromise our host network. Unless you really know what you are doing, DO NOT mess around with the AD settings or the VM network settings, especially while on your corporate network without the guidance from your local neighborhood IT guru. CRM requires a domain, and since we are going for a standalone installation, we have to promote the CRM server itself to the domain controller role. Additionally, promoting the server before we install SQL helps avoid additional configuration steps as a result of the server’s name and user accounts changing.
Promoting a domain controller is done from the commandline by running:
c:\>dcpromo
Seven tweaks are necessary:
1: Toggle: Create a new domain in a new forest
2: Name our network: CRM2011NET.local
3: Change forest function level to: Windows Server 2008 R2
You may get:
Selecting “Yes, the computer will use an IP…” is fine and keeps you from having to look at network settings.
You may also get:
Select ‘Yes’
Put in a password and you are done creating an Active Directory Domain Server…Congratulations! You will have to reboot after dcpromo has done its thing. The Domain we created is CRM2011NET, we will want to log back into the server using that domain.
4: Rename computer back to original name
Making the server a domain controller renames the computer to something weird, rename computer back to CRMDEMO2011 and reboot.
5: Create domain users
Create a few domain users is done from the commandline by running:
c:\>dsac
Create at least these users:
1: CRM2011NET\CRMDEMOADMIN
2: CRM2011NET\CRMSQLADMIN
3: CRM2011NET\Sal.Salesman
4: CRM2011NET\Mark.Marketing
Put the users in the ‘Users’ CN:
Make the password the same for all users and make it so it never expires to make things easier:
6: Make CRMDEMOADMIN and CRMSQLADMIN the administrators of CRMDEMO2011
7: Logout/Login
*IMPORTANT: Logout out as CRM2011NET/Administrator and log back in as CRM2011NET/CRMDEMOADMIN. Installing SQL and CRM as the domain administrator will cause strange installation errors.
Install SQL Server 2008 R2
1: Login to CRMDEMO2011 as CRM2011NET\CRMDEMOADMIN.
2: From the host machine unmount the Windows Server 2008 R2 and mount the SQL Server 2008 R2. When you mount SQL Server R2 from the host machine go to CRMDEMO2011 and run the SETUP.EXE from the ‘D:’ drive.
3: Install a new instance of SQL with a minimum of the following components selected:
4: Set the service accounts to the CRMSQLADMIN user:
5: Add CRMSQLADMIN as an administrator:
Install IIS and ASP.NET
Installing IIS is accomplished by adding the Web server role to the server using the server management console. In addition to the IIS role we’ll need ASP.NET, which must be selected separately. Once these two things are selected, the installer will install them both simultaneously. Starting the Server Manager can be started from the commandline:
C:\>ServerManager
1: Select Web Server (IIS) by selecting Roles -> Add Roles:
Continue... Microsoft Dynamics CRM 2011 Standalone on VirtualBox Part 2
Posted by Manny Ed on January 18, 2012 at 08:00 AM in CRM Development, Dynamics CRM 2011, Microsoft CRM Implementation, Microsoft CRM Tricks and Tips, Microsoft SQL Server, Web/Tech | Permalink | Comments (0) | TrackBack (0)
January 16, 2012
Manually Setting Up Activity Feeds on Forms in Microsoft Dynamics CRM 2011
Microsoft Dynamics 2011 Activity Feeds provide real time notifications and quick sharing of information via quick, short updates. The solution is available on the Dynamics Marketplace here. If you are an online user, and you do not use multiple forms on the entities you want to highlight, simply download, import and publish the solution. You would then go to configure your post entities (step 1 below) and then you are good to go.
If you are using Microsoft Dynamics CRM and you have multiple forms configured for an entity on which you wish to display the Activity Feed wall, you will need to download, import and publish the solution, and then you will need to follow the steps below to ensure the activity feeds are visible on the forms you want to display the wall.
1. Once you have imported and published the Activity Feed Solution, you need to enable your selected entities and enable wall posting. Settings > System > Activity Feeds Configuration. Note that this form uses a text field for entering the entity name (not the more common lookup) and you will need to be careful to use the logical name. Also check the “Enable walls” box. You will need to go then to the entity you enabled and publish it individually.
2. Open an entity form for the entity you want to enable for activity feeds. In this example, we are using accounts. Open the Form Editor.
-
- Insert > Tab > One Column
- Open the Tab to edit the Tab Properties > Display
- Name= tab_recordwall
- Label = Record Wall
- Uncheck “Expand this tab by default”
- Tab Properties > Events
- Form Libraries, add msdyn_/ActivityFeeds.Form.js
- In event handlers, Add
- Library: msdyn_/ActivityFeeds.Form.js
- Function: ActivityFeeds.Form.CustomizationUtils.updateRecordWallRenderingState
- Check “Enabled.
- Check “Pass execution context as first parameter.
3. Save your changes to the tab and return to the form editor. Select the section inside of the Record Wall Tab. Set to ‘One Column’.
4. Back in the Form Editor, click Insert > Web Resource
- General Tab
- Web resource = msdyn_/RecordWall.htm
- Name = RecordWall
- Visible by Defualt = checked
- Pass record object…= checked
- Formatting Tab
- One column
- 15 rows (this is flexible, but 15 is standard the standard look).
- Display border = yes
- Scrolling = as necessary
5. Save, publish and go!
Posted by Brad Koontz on January 16, 2012 at 08:18 AM in Dynamics CRM 2011, Microsoft CRM Tricks and Tips | Permalink | Comments (0) | TrackBack (0)
January 13, 2012
XRM 2011 - Microsoft Dynamics CRM 2011 Style Buttons
Adding a button to a Form is a great way to add additional functionality right where the user is already focused. Today we are looking at a few different approaches to adding a button. First, we’re looking at handling the upgrade for those of you who have already used the 4.0 button inside of 2011 or if you had a 4.0 environment and are upgrading. If you have a clean 2011 environment, then feel free to skip down to the “From Scratch” section.
There is already some code floating around to create a 4.0 style button inside of CRM 2011. The button we’re creating will instead create a CRM 2011 style button (i.e. “Example Button”).
The CRM 2011 button starts gray and then when the user hovers over it, the button will look like:
Creating our Web Resource
First,name your Web Resource whatever you’d like. In my case, I’m planning to re-use it in various ways, so I’m throwing it in our JS root directory.
1: // CRM 2011 Style Button
2: // Creates a button from a form field
3: // Paul Way - 1/3/2012
4: function ConvertToButton(fldName, btnLabel, btnWidth, evt){
5: var btn = '<button id="btn_' + fldName + '" ' +
6: ' style="width:' + btnWidth + '" ' +
7: ' class="ms-crm-Button" ' +
8: ' onmouseover="Mscrm.ButtonUtils.hoverOn(this);" ' +
9: ' onmouseout="Mscrm.ButtonUtils.hoverOff(this);" ' +
10: '>' + btnLabel + '</button>';
11:
12: var ctrl = Xrm.Page.ui.controls.get(fldName)._control;
13:
14: // Add the new button
15: ctrl.get_element().innerHTML += btn;
16:
17: // Hide the textbox
18: ctrl.get_element().firstChild.style.display = 'none';
19:
20: // Hide the label (optional)
21: Xrm.Page.ui.controls.get('pager').setLabel('');
22:
23: // Add Event to the newly created button
24: ctrl.get_element().childNodes[1].attachEvent('onclick', evt);
25:
26: }
Modifying our Form
To use this new function, you’ll first need to add the newly created web resource to the form. You’ll also want to have a separate JS web resource to call the ConvertToButton function. In my case, I already have a web resource for contact specific JavaScript. If I didn’t, I would need to create a new web resource and then place my contact specific code there. Here’s an example screenshot:
By having two web resources for this, you’ll have only one version of your ConvertToButton function throughout your CRM 2011 environment. Let’s say down the road you’ll need to update the function to change the label to “Button”, then you only need to modify one file. In CRM 4.0, you probably had a lot of duplicate code. With CRM 2011, your browser can cache the JavaScript for better performance and it is easier to maintain when organized appropriately.
In our other web resource, the one we setup with the OnLoad, we need the following code.
1: function contactsOnLoad(){
2: convertToButton('pager', 'Example Button', '150px', function(){alert("test")});
3: }
Why is this so different then the 4.0 code?
If you’ve used the CRM 4.0 code, you’ll notice the code here is a lot shorter. It’s actually pretty different as well.
- For one, instead of modifying the input element we are actually creating a button HTML element. This just means that we can now use about any kind of field instead of just pure textboxes. Not a huge deal, but opens up the email address 3 and other attributes.
- Both are really unsupported but the other uses the deprecated crmForm.all.
- Finally, the button style was meant for 4.0 whereas now we have the 2011 look-n-feel.
From Scratch
So far we’ve mainly focused on if you already were using the 4.0 code and were upgrading. But what if you don’t have the existing code structure? I’d argue against creating a new attribute just to have a button. When adding a new attribute, you are also adding the attribute to the underlying SQL tables and views. Using an existing field isn’t really a great option either because there is always the chance you will need the field or it will overlap with an application from the MS marketplace.
Instead, I’d recommend creating a button place holder web resource and then embedding the web resource on the page. The web resource is just a JPG image like this:
We then place the image wherever we want on the form. Make sure to set the formatting to one column and one row.
Finally, we need a little bit of code added to the /js/formButton.js web resource:
1: // CRM 2011 Style Button
2: // Creates a button from a form field
3: // Paul Way - 1/3/2012
4:
5: function convertWebResourceToButton(fldName, btnLabel, btnWidth, leftMargin, evt){
6: var btn = '<button id="btn_' + fldName + '" ' +
7: ' style="margin-left:' + leftMargin + ';width:' + btnWidth + ';" ' +
8: ' class="ms-crm-Button" ' +
9: ' onmouseover="Mscrm.ButtonUtils.hoverOn(this);" ' +
10: ' onmouseout="Mscrm.ButtonUtils.hoverOff(this);" ' +
11: '>' + btnLabel + '</button>';
12:
13: var ctrl = Xrm.Page.ui.controls.get(fldName)._control.get_element().childNodes[1];
14:
15: // Replace image with buttom
16: ctrl.innerHTML = btn;
17:
18: // Add Event to the newly created button
19: ctrl.firstChild.attachEvent('onclick', evt);
20:
21: }
And as for our OnLoad function, we’ll need to use this instead:
1: function contactsOnLoad(){
2: convertWebResourceToButton('WebResource_btnProfInfo', 'Another Approach', '150px', '119px', function(){alert("test2")});
3: }
Notice that this will require an additional parameter for the left margin.
Summary
As you can see, there are several different approaches to adding a button onto the CRM form for 2011. With 2011, I’d opt for using the button place holder image method. No matter which approach you take, the end goal is enhancing the user’s experience. I hope you enjoy!
Posted by Paul Way on January 13, 2012 at 08:53 AM in CRM Best Practices, CRM Development, Dynamics CRM 2011, Microsoft CRM Customizations, Microsoft CRM Tricks and Tips, XRM | Permalink | Comments (0) | TrackBack (0)
January 12, 2012
CRM 2011 - iFrames & Saving
After spending time on several of the CRM forums, I noticed a few people having trouble with iFrames and saving data inside of the iFrame. While a forum post is a little difficult to write a full response, I wanted to share some insight on capturing the form save event to then trigger a save event inside of your iFrame.
From a business case, this is a fantastic way to tie multiple systems together. If all you are doing is syncing data, then look first at a plugin or scribe. However, if you are looking for the user to interact with multiple systems simultaneously, then an iFrame is a wonderful way to integrated with an existing system. A good example might be where CRM is only storing the summary information and the iFrame contains the details. The user may update the details which should save both the CRM record and the details of the web site.
Prerequisites
First, let’s assume you have an iFrame inside an entity.
Secondly, we need to uncheck the “Restrict cross-frame scripting” check box on the iFrame properties.
Lastly, it’s a good idea to make sure our iFrame is using HTTPS to prevent mixed mode security warnings.
Talking Directly
If you try to talk directly to the iFrame via something like:
Xrm.Page.ui.controls.get('IFRAME_opp').getObject().contentWindow.document
Unless you are On-Premise, you may encounter the “Access is Denied” warning. This will show itself when dealing with URLs on a different domain (or subdomain). Cross domain scripting is a security feature within the browser. Now an easy fix is to disable the browser settings, but this isn’t a good solution.
Messaging
Instead of talking directly, we need send a messages to our iFrame. To send a message, we’d do something like:
Xrm.Page.ui.controls.get('IFRAME_opp').getObject().contentWindow.postMessage('test', "*")
To receive the message on the iFrame, you need to listen for the message event. Let’s take a look at the JavaScript on our example iFrame page:
1: function receiveMessage(e) {2:3: if (e.origin == 'https://way.crm.dynamics.com'){4:5: if (e.data == 'test'){6: document.getElementById('msgBox').innerHTML = 'testing...<br />' + e.origin + '<br />' + e.data;7: }8:9: if (e.data == 'save'){10: saveForm();11: }12: }13:14: }15:16: saveForm = function() {17: document.getElementById('msgBox').innerHTML = 'saving...';18: }19:20: window.attachEvent("onmessage", receiveMessage);
It is very important to check the origin and the message if you are dealing with publicly facing sites.
If the idea of messaging is new to you, here are some additional resources:
- http://msdn.microsoft.com/en-us/library/cc197015(VS.85).aspx
- http://msdn.microsoft.com/en-us/library/cc511311(v=vs.85).aspx
The OnSave
To initiate the message from the CRM 2011 form, you need to register an OnSave event from within the “Form Properties”.
How about .NET Pages?
If your iFrame is an ASPX page, all of this still applies. The only difference is that you want to have an ASP.Net button on your page to call the server side code. Instead of the saveForm event, simply call the click event of the button.
When dealing with server side saves though, you can either post a message back to say “I’m done” which will then complete the CRM save or you can put a set timeout in the onSave on the CRM side.
Conclusion
Hopefully you have seen how easy it is to integrate with another web site while inside of CRM 2011. If you have had problems with iFrames in the past, I really encourage you to use iFrames for your integration needs as they can really enhance your users experience and can allow for improved data across systems. I hope you enjoy!
Posted by Paul Way on January 12, 2012 at 01:22 PM in CRM Javascript, Dynamics CRM 2011, Microsoft CRM Customizations, Microsoft CRM Online, Microsoft CRM Tricks and Tips, XRM | Permalink | Comments (2) | TrackBack (0)
January 03, 2012
Microsoft Dynamics CRM 2011 – Pre-Populated Emails (Alternative Approach)
Recently, we came across the need for a single-click email feature on the ribbon of a custom entity. The idea was to be able to share CRM data as quickly and easily as possible. Another interesting caveat was the client wanted to have the email come directly from Outlook.
For those of you old enough to have used the internet before the iPad, you may recall that fancy-dancy “mailto” feature. (Granted I’m only 29 but my Omron body age says I’m 50 ).
Back to the point at hand though, CRM already has a few built in email features. Our situation calls for something a little different.
What’s Installed By Default
CRM already has the feature of sending a link to the CRM entity you’re working with.
CRM also has a really nice Email Template feature.
Which can automatically mail-merge information pertaining to the entity to eliminate any of that “automated” feel.
What We Need
Our situation is somewhat unique, but really we want both features combined. We want to open a new message like the link feature: inside outlook and with one click. However, we need more than just the link. We want to have a brief summary of the call report with a link to it.
Here’s an example email of what we’d like to send:
Adding the Button
The first thing we need to do is add the button to the ribbon like so:
If you are unfamiliar with the ribbon, Microsoft offers some really nice walkthroughs. I’d start with http://msdn.microsoft.com/en-us/library/gg334341.aspx.
For our situation, the XML that matters is our Action
<Actions> <JavaScriptFunction Library="$webresource:cei_example.js" FunctionName="emailContact" /></Actions>
Adding our JavaScript
At this point, the button should be on the form. From here, we need to add the JavaScript to open our populated email message.
To do this, add to the Form OnLoad events the following function:
emailContact = function() {
if (Xrm.Page.data.entity.getId() != null) {
var sRptID = Xrm.Page.data.entity.getId().replace('{', '').replace('}', '');
var sLink = "http://paul.customereffective.com/userdefined/edit.aspx?id=" + sRptID + "&etc=10011";var sSubject = 'Contact - ' + Xrm.Page.getAttribute("firstname").getValue() + ' ' + Xrm.Page.getAttribute("lastname").getValue();
var sCustomer = '';
if (Xrm.Page.getAttribute("parentcustomerid").getValue().length > 0){
sCustomer = Xrm.Page.getAttribute("parentcustomerid").getValue()[0].name;
}
var sBody = '---\n\nCustomer:\t' + sCustomer + '\n\n' + sLink + '\n\n---';
var sMailTo = 'mailto:?subject=' + escape(sSubject) +
'&body=' + escape(sBody);parent.location=sMailTo;
} else {
alert('You must save the contact before emailing.');
}
}
Note: If you change the name of the function, make sure it matches the name inside your Ribbon XML.
What about Window.Open
If you use Window.Open to trigger the email (or OpenStdWin), the user will get a blank IE page inside of Outlook. By using the parent.location for your mailto, you’ll have a consistent user experience across IE and Outlook.
Seeing the Final Results
Now whenever a user wants to share a call report, they can easily hit the “Email Call Report” button. Add any of the fields you want.
For our needs I ended up with the following; however, this technique can add whatever field you’d like.
Well this isn’t a tool in the toolbox I’d use daily, it can be valuable when the situation calls for it. I hope you enjoy!
Posted by Paul Way on January 03, 2012 at 01:58 PM in CRM Development, Dynamics CRM 2011, Microsoft CRM Customizations, Microsoft CRM for Outlook, Microsoft CRM Tricks and Tips, XRM | Permalink | Comments (0) | TrackBack (0)
December 29, 2011
Identifying Synchronized Microsoft Dynamics CRM items in Outlook
If you use Microsoft Dynamics CRM for Outlook, you may know that Microsoft uses the following icon to display records that are synced from a users outlook client to CRM.
Sometimes a user can do something which causes the icon to disappear. Some examples that would cause this situation include removing the CRM Outlook client, or reconfiguring the users client to point to an upgraded CRM 4 to 2011 Organizations, or editing fields on the record in Outlook that are not synched to CRM. When that icon disappears there is no real way to tell between a personal contact or other synced item to CRM. As a result a user might have hundreds of contacts and not know if they manually created them, or if it was synchronized with CRM.
Well I learned today there is a way. Using the following steps will quickly allow you to see items that are synchronized with CRM.
1.) Go to your views in Outlook. In our example we are going to make this change to the contact view.
2.) Click on Add Columns button and then Click on New Column button
3.) Enter the following new column information below.
Name: crmLinkState Type: select Number
4.) Then add the column to your view
5.) Then look at your outlook view. (2 – Means currently synced with your org, 0 – means previously synced and blank means never synced)
Hope it helps you too!
Posted by Sean Shilling on December 29, 2011 at 08:00 AM in CRM Best Practices, Microsoft CRM Tricks and Tips | Permalink | Comments (0) | TrackBack (0)
December 23, 2011
Dynamically Generating URLs for Cached Web Resources for Microsoft Dynamics CRM
CRM 2011 supports several methods for accessing web resources. These includes using the $webresource: directive when referencing web resources from the site map or ribbon, a relative URL, or an absolute URL. While this allows a web resource to be accessed in virtually any situation, if a scenario arises in which an absolute URL must be used, the benefit and speed of retrieving the cached web resource is lost. The reason for this has to do with the caching mechanism used by CRM for web resources. Take the following URL that could be generated by CRM when using the $webresource: directive or when a web resource is included on a CRM form as an example.
https://yourorgname.crm.dynamics.com/%7B634594655730000000%7D/WebResources/cei_/test.htm
Notice the magic number that is highlighted in the example URL. CRM automatically inserts this token into the URL and the CRM 2011 SDK indicates that this magic number is a GUID value that ensures the latest cached version of the requested web resource is used. If you use a tool like Fiddler to examine the response returned by the GET request for the web resource, you will notice that it has caching headers that set it to be cached for one year. If the magic GUID value is removed from the URL so that the URL would resemble an absolute URL to the web resource, the response returned by the GET request for the web resource, depending upon your version of CRM, will be a non cached version or a version that is cached for a short amount time such as 24 hours.
This caching functionality provided by CRM is great, but what if you need to dynamically generate the URL for a large web resource? It would be preferable to access the cached version, but in order to do that you need the magic GUID value. Fortunately, the magic GUID value is not magic at all, and not actually a GUID. The caching token inserted into the URL for the web resource by CRM is merely a timestamp that represents the modified time of the last web resource to be modified. The timestamp is represented as ticks in UTC time as generated by the .NET Framework.
Now in order to get the URL for a web resource, it is merely necessary to generate this timestamp. The example function below shows exactly how to generate the timestamp for the caching token using a quick call to the REST service in CRM and a simple conversion from the JavaScript Date object to the timestamp value.
1: function getWebResourceUrl(webResourceName) {
2:
3: var dotNetMillisecondsAt_1970_01_01 = 62135596800000,
4: ticksPerMillisecond = 10000,
5: lastWebResourceModifiedOn = null,
6: webResourceCachingToken = null,
7: webResourceUrl = "/WebResources/" + webResourceName;
8:
9: // Retrieve the modified date of the last web resource to be modified
10: $.ajax({
11: async: false,
12: beforeSend: function (xhr, settings) {
13: xhr.setRequestHeader("Accept", "application/json");
14: },
15: contentType: "application/json; charset=utf-8",
16: datatype: "json",
17: global: false,
18: success: function (data, textStatus, xhr) {
19:
20: // Extract the modified date from the results
21: lastWebResourceModifiedOn = +(/\/Date\((\d*)\)\//.exec(data.d.results[0].ModifiedOn)[1]);
22:
23: // Convert the JavaScript Date value to the caching token value
24: webResourceCachingToken = (lastWebResourceModifiedOn + dotNetMillisecondsAt_1970_01_01) * ticksPerMillisecond;
25: },
26: type: "GET",
27: url: Xrm.Page.context.prependOrgName("/XRMServices/2011/OrganizationData.svc/WebResourceSet()?$select=ModifiedOn&$orderby=ModifiedOn%20desc&$top=1")
28: });
29:
30: // If we have a caching token value then prepend it to the current URL.
31: if (webResourceCachingToken !== null) {
32: webResourceUrl = "/%7B" + webResourceCachingToken + "%7D" + webResourceUrl;
33: }
34:
35: // Return the URL to the web resource prepended with the organization name if necessary
36: return Xrm.Page.context.getServerUrl().replace(/\/$/,"") + Xrm.Page.context.prependOrgName(webResourceUrl);
37: }
Notice the function takes the name of a web resource, e.g. cei_/mypage.htm, and prepends it with the caching token, organization name if necessary, and the server URL. Now this function can be used to dynamically generate the URL for a web resource and still retrieve the cached web resource. Of course, there could be some improvements made, such as wrapping the call to retrieve the caching token in a closure so that it is only retrieved the first time a URL is generated, but I will leave that up to you.
**Note, that generating a URL in this manner is not supported by Microsoft and the caching mechanism used by CRM for web resources could be changed in the future.
Posted by Nick Doriot on December 23, 2011 at 09:28 AM in CRM Development, Dynamics CRM 2011, Microsoft CRM Customizations, Microsoft CRM Tricks and Tips, XRM | Permalink | Comments (0) | TrackBack (0)
December 16, 2011
Setting Lookup Field Values on Microsoft Dynamics CRM Forms with Silverlight
Recently, I have found myself developing a lot of Silverlight customizations for CRM 2011. If you happen to find yourself in the realm of Silverlight and CRM you will more than likely have the need to interact with the CRM form in context. The tool that Silverlight provides for interacting with the hosting web page is the HTML Bridge. The types and methods exposed by the HTML Bridge allow access to the hosting pages DOM and JavaScript objects. By utilizing the types and methods of the HTML Bridge it is possible to access the Xrm JavaScript object of the CRM SDK to set field values so let’s take a look at how the HTML Bridge can be leverages to do so.
There are a couple of different methods that can be used when accessing the Xrm object of CRM. One is to create an object model around the Xrm object that mirrors the Xrm object in JavaScript and the other is to use a dynamic object in Silverlight. I am going to use a dynamic object in this instance as it requires much less code, however, the downside is that there is not any compile-time type checking and errors may not be discovered until runtime.
1: // Get a reference to the Xrm object
2: dynamic Xrm = HtmlPage.Window.GetProperty("Xrm");
3:
4: // Set a string field value
5: Xrm.Page.getAttribute("cei_stringfield").setValue("Hello World!");
6:
7: // Set a floating point field value
8: Xrm.Page.getAttribute("cei_floatfield").setValue(3.14159);
Notice in the previous example I simply use the HtmlPage.Window object to get a reference to the Xrm property on the hosting page. Also, notice that the local Xrm object is declared as a dynamic variable. Declaring the local Xrm object as a dynamic object allows the object to bypass static type checking. At this point, it is simply a matter of using the Xrm object just as if it were being used from JavaScript. In line 5 of the previous code snippet I have done just that. A reference to the cei_stringfield attribute is obtained and the value is set to a “Hello World!”. In line 8, the same approach is used to set a numeric field.
After seeing how the previous snippet of code works, you may think, as I did, that setting a lookup field’s value could be done using the same technique with an anonymous array object that mimics a lookup fields value as the argument to the setValue function. However, calling the setValue function for a lookup field will not work from within Silverlight due to the way the HTML Bridge marshals managed objects to JavaScript. Fortunately, there is another method that can be used, although it is not quite as elegant. The following snippet shows how a lookup field can be set from within Silverlight.
1: private void SilverlightSetLookupValue(string fieldName, Guid? id, string entityLogicalName, string name)
2: {
3: // Define eval statements for setting lookup to a value and null
4: string setLookupJscript = @"Xrm.Page.getAttribute(""{0}"").setValue([ {{ id: ""{1:B}"", typename: ""{2}"", name: ""{3}"" }}])";
5: string setLookupToNullJscript = @"Xrm.Page.getAttribute(""{0}"").setValue(null)";
6: string evalStatement = null;
7:
8: // Set the statement to be evaluated based upon the value of the id argument
9: if (id.GetValueOrDefault().Equals(Guid.Empty))
10: {
11: // Setting the lookup to null
12: evalStatement = string.Format(setLookupToNullJscript, fieldName);
13: }
14: else
15: {
16: // Setting the lookup to a value
17: evalStatement = string.Format(setLookupJscript, fieldName, id, entityLogicalName, name);
18: }
19:
20: // Set the lookup
21: HtmlPage.Window.Eval(evalStatement);
22: }
That’s right, I broke out the big dog, the EVIL EVAL function. As I said previously, it is not the most elegant solution, however, sometimes the heavy hitters like the eval function need to be dusted off and it works beautifully. As you can see, the method in the snippet builds the JavaScript necessary to set a lookup field dynamically as a string and then evaluates the string on the hosting page using the HtmlPage.Window.Eval method within Silverlight.
Posted by Nick Doriot on December 16, 2011 at 09:55 AM in CRM Development, Dynamics CRM 2011, Microsoft CRM Customizations, Microsoft CRM Tricks and Tips, XRM | Permalink | Comments (0) | TrackBack (0)
December 08, 2011
Using the CRM 2011 REST Services and Deep Insert to Create Activities
One of the nice features of CRM 2011 is the REST based OrganizationData service. Lately, I have found myself using the REST service more often as it provides a simpler and cleaner method of accessing data from client side JScript. The REST service also provides the ability to do deep inserts of entity and related records. This capability can be leveraged to provide an elegant solution to creating activity records using client side JScript.
In my particular use case, I needed to create a new email activity from the currently logged in user with the regarding record set to a new custom entity record. It would have been possible to issue a couple of calls to the REST service that created the regarding record and then the email, but it is just as easy to create the email and related records in one call avoiding the need to clean up any records should one of the calls fail.
The first thing that needs to be done is to create the activity party record for the from field on the email. The activity party is a separate entity that stores all of the parties related to an activity, i.e. sender, recipient, etc. The activity party data needs to be specified as an array of activity party entities. The following snippet creates an activity party record for the from field on the email.
1: // Create the activity party records
2: var emailPartyEntities [{
3: PartyId: { LogicalName: "systemuser", Id: Xrm.Page.context.getUserId() },
4: ParticipationTypeMask: { Value: 1 }
5: }];
As I mentioned earlier, I also needed to create a custom entity record that will be set as the regarding on the created email. In my case, this custom entity is used to track an email thread. Since this is a N:1 relationship for the email entity, the record created does not need to be stored in an array and is merely an object with properties specifying the fields of my custom entity.
1: // Build the activity thread entity
2: var activityThreadEntity = {
3: cei_Department: { Value: 29669000 },
4: cei_Subject: "Activity Thread for Account",
5: };
Finally, I need to build the email entity that will be passed to the REST service during the create request. In order for the deep insert to work, the activity party array and the activity thread object created earlier will need to be set to properties on the created object that specify the relationships to the related entities as shown below.
1: // Build the email record
2: var emailEntity = {
3: Subject: "Test Email",
4: cei_activitythread_Emails: activityThreadEntity,
5: email_activity_parties: emailPartyEntities,
6: };
In the previous snippet, notice that the activityThreadEntity object and the emailPartyEntities array are set to their corresponding relationships, i.e. cei_activitythread_Emails is the name of the N:1 relationship for my custom entity to the regarding field on the email entity, and email_activity_parties is the name of the 1:N relationship from the email entity to the activity party entity.
Now that all of the required objects have been created we can issue the call to the REST service to create the email and related records. The following snippet utilizes a jQuery plugin from our custom JScript library. It is merely doing a POST request to the REST service with the data for the POST being the JSON serialized emailEntity object that was created.
1: // Create the email by POSTing the data to the REST service
2: var createResult = $.xrm.orgData.buildRequest().
3: async(false).
4: dataset("Email").
5: data(emailEntity).
6: create();
7: if (createResult.errorExists) {
8: alert("There was an error creating the email.");
9: } else {
10: alert("Success!");
11: }
That’s it! It really is that simple. The email, activity party, and regarding records were all created in a single call.
In this example, I utilized the deep insert capabilities of the REST services to create an email, but the deep insert feature can be used on any entity in order to create multiple records in a single call. The biggest hurdle that you will probably run into is determining whether to create the related records as JScript objects or arrays of objects. Just remember that 1:N relationships require and array of objects and that N:1 relationships require a single object.
Posted by Nick Doriot on December 08, 2011 at 08:05 AM in Dynamics CRM 2011, Microsoft CRM Customizations, Microsoft CRM Tricks and Tips, XRM | Permalink | Comments (1) | TrackBack (0)
December 01, 2011
CRM 2011–Excessive Sub-Gridding
Sub-grids enhance the user experience by conveniently providing associated information. Within a sub-grid the user can easily view, create, or modify a related entity. By default, Microsoft will auto-populate up to four sub-grids.
One thing to note however is that if the form contains more than four sub-grids, then sub-grids 5+ will contain a message inside the sub-grid “To load x records, click here”. To populate the sub-grid, the user has to click the refresh button or the link.
CRM 2011 does this intentionally to make sure the User is presented with the Form as quickly as possible. Each sub-grid is another Fetch call to the database, so reducing those inherently speeds up the form.
Now what if you only need a few more sub-grids and you are ok with the potential performance trade-off? Well, let’s add a little JavaScript to auto load all of our sub-grids that didn’t auto load already:
function getLinksWithClassName(classname) {
var bdy = document.getElementsByTagName("body")[0];
var els = [];
var re = new RegExp('\\b' + classname + '\\b');
var lnks = bdy.getElementsByTagName("a");for(var i=0,j=lnks.length; i<j; i++)
if(re.test(lnks[i].className))
els.push(lnks[i]);return els;
}
var lnksUnloaded = getLinksWithClassName('ms-crm-List-LoadOnDemand');for (var i = 0; i < lnksUnloaded.length; i++){
lnksUnloaded[i].click();
}
Now adding the bottom four lines to our OnLoad event will cause all of our sub-grids to auto-load. Hope you enjoy!
Posted by Paul Way on December 01, 2011 at 08:00 AM in CRM Development, Dynamics CRM 2011, Microsoft CRM Customizations, Microsoft CRM Tricks and Tips | Permalink | Comments (3) | TrackBack (0)
November 17, 2011
CRM 2011 Client Install Issue and .Net Framework 4
In my opinion, Microsoft has done a great job regarding the stability of the CRM 2011 client installation. We have successfully uninstalled CRM 4 client and installed the CRM 2011 client on 1000’s of client workstations with very few issues. On a recent new deployment of CRM 2011, we had one client out of seventy-five that would not install the client at all.
The client install wizard appeared to be installing the client as expected bit at the end of the installation it would just fail (See part of error log below)
| Info| Installing Microsoft Dynamics CRM for Outlook
16:35:37| Info| Launching external process:
16:35:37| Info| CmdLine: <msiexec /i "C:\Users\JVOORH~1\AppData\Local\Temp\MSCRM_{6EB7FE7E-8866-4581-8910-80AED84ECD94}\Client.msi" /q INSTALLLEVEL="3" INSTALLTYPE="INSTALL" SOURCEFOLDER="C:\Users\JVOORH~1\AppData\Local\Temp\MSCRM_{6EB7FE7E-8866-4581-8910-80AED84ECD94}" TARGETDIR="C:\Program Files (x86)\Microsoft Dynamics CRM" INSTALLDIR="C:\Program Files (x86)\Microsoft Dynamics CRM" /l+ "C:\Users\jvoorhees\AppData\Local\Microsoft\MSCRM\Logs\crm50clientmsi.log" LOGFILE="C:\Users\jvoorhees\AppData\Local\Microsoft\MSCRM\Logs\crm50clientmsi.log" ALLOWRUN="1" REBOOT=ReallySuppress NOSELECTION=1>
16:35:37| Info| WorkingDir: <C:\Users\JVOORH~1\AppData\Local\Temp\MSCRM_{6EB7FE7E-8866-4581-8910-80AED84ECD94}>
16:35:39| Info| Installation of Microsoft Dynamics CRM for Outlook failed. Exit code: 1603. Result: Fatal error during installation.
16:35:39| Info| Uninitializing COM.
16:35:39| Info| Uninitialized COM.
16:35:39| Info| Uninitializing COM.
16:35:39| Info| Uninitialized COM.
16:35:39| Info| === Setup bootstrap logging started 11/8/2011 4:35:39 PM ===
16:35:39| Info| Bootstrap version: 5.0.9688.1533.
16:35:39| Info| User: jvoorhees.
16:35:39| Error| Installation of Microsoft Dynamics CRM for Outlook failed.
There was not a lot of detail to work with from the log. After trial and error, we discovered that repairing/re-installing the .Net Framework 4 client fixed the issue. Open up control panel, go to add/remove programs, highlight .Net Framework 4 pieces and click on uninstall/change.
Posted by Sean Shilling on November 17, 2011 at 07:05 AM in Dynamics CRM 2011, Microsoft CRM for Outlook, Microsoft CRM Implementation, Microsoft CRM Tricks and Tips | Permalink | Comments (0) | TrackBack (0)
November 01, 2011
Controlling Tabs and Sections with JScript in Microsoft Dynamics CRM 2011
The options for managing Microsoft Dynamics CRM forms dynamically has increased tremendously in CRM 2011 – we can now, more than ever, tailor the look of a form to the specific type of record displayed.
Here are 2 functions I use to manage the form at runtime to show or hide sections or tabs based on data in the form.
The first function, “toggleTabDisplayState” hides or shows a tab and can show it collapsed or expanded. Call the function by passing in the ‘name’ of the tab, desired ‘display state’ [either “expanded” or “collapsed”] and whether the tab should be visible [ true or false ].
The second function, “toggleSectionDisplayState” hides or shows a section by setting its isVisible attribute to [ true or false ].
In this Example, I’ve named the “Prospect” tab: “tabProspect” in the form configuration - and based on whether a (previously set) variable “typeProspect” is true or not, I want to hide or show this tab – and when it’s displayed, I want it expanded. – I also want to hide/show the Customer tab based on whether the record is for a prospect or not.
// If the 'Prospect type' == true, // then Show and expand the Prospect tab and hide the Customer section // Otherwise, // Show the Customer Section and hide the Prospect Tab if (typeProspect == true) { toggleTabDisplayState("tabProspect", "expanded", true); toggleTabDisplayState("sectionCustomer", false); } else { toggleTabDisplayState("tabProspect", "collapsed", false); toggleTabDisplayState("sectionCustomer", true); } function toggleTabDisplayState(tabName, tabDisplayState, tabIsVisible) { //Hide/Show and/or Expand/Collapse tabs var tabs = Xrm.Page.ui.tabs.get(); for (var i in tabs) { var tab = tabs[i]; if (tab.getName() == tabName) { tab.setVisible(tabIsVisible); tab.setDisplayState(tabDisplayState); } } } function toggleSectionDisplayState(sectionName, sectionIsVisible) { //Hide or Show Sections var tabs = Xrm.Page.ui.tabs.get(); for (var i in tabs) { var tab = tabs[i]; tab.sections.forEach(function (section, index) { if (section.getName() == sectionName) { section.setVisible(sectionIsVisible); } }); } }
Posted by Scott Sewell on November 01, 2011 at 02:34 PM in CRM Development, Microsoft CRM Customizations, Microsoft CRM Implementation, Microsoft CRM Tricks and Tips | Permalink | Comments (0) | TrackBack (0)
October 25, 2011
Microsoft CRM 2011 -- When to Use an iFrame vs a Web Resource
Sit back and relax a bit because we aren’t going to get into any code today. Instead we are going to take a higher level look at some interesting caveats within CRM 2011.
Even if you aren’t a developer, if you customize the form with Web Resources or iFrames, this should be helpful to you.
Silverlight
First of all, I came across this due to a colleague using Silverlight. Like many of you out there, we do some crazy stuff with Silverlight. Silverlight is awesome and can be leveraged to do some serious heavy lifting. I’m actually more of a JavaScript guy, but until JavaScript supports multi-threading, Silverlight cannot be overlooked.
When using Silverlight, you have three options to put Silverlight on your form:
- Embed a Web Resource and select the XAP file (Figure A – 1)
- Embed a Web Resource and select an HTML wrapper (Figure A – 3)
- Embed an iFrame and enter an HTML wrapper (Figure A – 4)
Figure A (Before)
If you are embedding the XAP file (1), then all is good in the world and everything should behave like any other control. Problems only arise with the magnitude of what you are doing. Let’s say you have a couple of JavaScript resources you need to support your Silverlight control to talk to the form. If that’s the case, setup and maintenance is a little more cumbersome.
To make life easier for you and your customer, you could upload your own HTML file with everything self-contained. Now that you have everything nicely contained in an HTML web resource, we can either insert a web resource and select our HTML file (3) or we can use an iFrame and point to our HTML file (4). What’s the difference? Watch what happens when I select a Related entity (e.g. Activities) and then come back to my form. Notice how the Web Resource is blank? That’s because the form reloaded my web resource.
Figure B (After)
HTML
Ok, some of you out there may be saying, “but I don’t use Silverlight”. That’s fine, but this actually applies to HTML files as well. When embedding an HTML page, again you can either use a web resource or an iFrame. If you choose a web resource however, the page will reload when toggling to a new related entity. If you choose an iFrame, it will NOT reload (look back at Figure A&B paying attention to 2 and 5).
Well why?
This is all about your purpose. If you have a HTML page or Silverlight control that is read-only (e.g. graphs, charts, or accessing another system), then a Web Resource would be the better method most of the time. If you are expecting users to input data, then you’ll want to use an iFrame instead of a Web Resource so that the data is persistent. For some of you this could be a gotcha and for others it can be a feature. Hopefully none of your users are on the wrong end of this! Enjoy.
Posted by Paul Way on October 25, 2011 at 07:43 AM in CRM Best Practices, CRM Development, Dynamics CRM 2011, Microsoft CRM Customizations, Microsoft CRM Tricks and Tips, XRM | Permalink | Comments (0) | TrackBack (0)
October 13, 2011
Microsoft Dynamics CRM 2011: Using Marketing Lists to Control Contact Synchronization
One of the great features of CRM 2011 (and earlier versions too) is the ability to bring down contacts from CRM into your Outlook contacts folder. In CRM 2011 this is accomplished by managing Outlook Filters, where you can set up rules with which to bring down contacts using Advanced Find-esque tools. But as our own Jason Farmer wrote in this blog, “using local data groups to define which contacts sync to Outlook isn't necessarily for everyone”. (As an aside, Jason’s blog covers another way to tackle this as well). So, the trick is to create a local data group that users can then leverage without a lot of effort to manage CRM contacts in and out of Outlook. The answer comes in an unexpected place: Marketing Lists.
Marketing Lists are typically used in Campaigns or Quick Campaigns to control, for example, who gets an email or a phone call. But who says they can only be used for that? Its very easy to add or remove Contacts to or from a Marketing List, especially with the fancy ribbon in CRM 2011. See this screenshot, where I’ve highlighted a CRM contact I want to bring down into my Outlook Contacts folder. I highlight the record, and am about to click Add to Marketing List.
I happen to have a marketing list set up for the specific purpose of Outlook synchronization:
So we’ll add this contact to my Marketing List:
How easy was that? But what about removing someone from a marketing list? What if I change my mind about that Mr. Wingard?
I’ll open my Marketing List like so (filtered to just that record to protect the names of the innocent) and click Remove from Marketing List:
Presto! One less contact in the Marketing List.
But the question remains, how does this help to manage Contacts that come down to Outlook. Outlook Filters tie it all together. Access Filters from CRM like so:
I’ve added this Outlook Filter:
Which leverages my marketing list:
The key is to look for contacts that are part of the marketing list. So now the CRM synchronization process takes over and contacts jump in and out at your whim. If you have any questions about this process, please drop us a line at info@customereffective.com.
Posted by James Diamond on October 13, 2011 at 02:21 PM in Microsoft CRM for Outlook, Microsoft CRM Tricks and Tips | Permalink | Comments (1) | TrackBack (0)
October 05, 2011
Cases and Resolution Activities in Microsoft Dynamics CRM 2011
Many CRM users use the Case entity in CRM 2011 to track issues. These issue range from customer complaints to tracking actual software development bugs. The Case entity does a good job of allowing users to track a wide variety of issues and work on them them through resolution. What seems odd though is that when a user closes a case he/she cannot see those “resolution” activities anywhere.
The funny part of this problem is that a “resolution” is nothing more than another type of activity. Microsoft Dynamics CRM is just filtering out these activities from the activities views (see example 1) by setting the “Is Regular Activity” to “Yes.”
Example 1
You can probably see where we are going with this, but one of the easiest ways that we have found to show these on cases is to create a new view called “Resolution Activities” (See examples 2) and set the type of activity to resolution.
Example 2
As a result of those changes (Example 3), it is then very easy, especially under CRM 2011, to show the new view in a sub-grid on the case form. Users can now view a closed case and see what the resolution.
Example 3
Posted by Sean Shilling on October 05, 2011 at 01:36 AM in Microsoft CRM Customizations, Microsoft CRM Reporting, Microsoft CRM Tricks and Tips | Permalink | Comments (0) | TrackBack (0)
September 30, 2011
Hiding Ribbons in CRM 2011
I was recently involved in implementing Microsoft CRM for a large organization, and one of their requirements was a simplified, stripped down UI. One of the ways that we accomplished this goal was by hiding ribbon buttons that were not used in their deployment.
First, I found a great tool that made it very easy to add or remove ribbon buttons. I would recommend giving this tool a try. It was created by Pragma and did a pretty good job; and, it can be found on CodePlex.
Here is an example screenshot of hiding buttons for the case entity.
However, when you are editing the ribbon, either by using a tool or editing an entity xml manually, you can make mistakes that cause problems with your customization. In my case, I accidentally created two "hide" entries for the same item in the xml. As a result of this, the solution could not be imported.
Here is sample XML associated with hiding a ribbon entry. I placed big **** next to my duplicate records.
Once I deleted one of the duplicate rows from the xml file and reimported the solution, everything worked fine.
Lesson learned: It is fine to use tools and utilities to help customize your environment, but it is important that you still understand what is being changed "under the hood," as you may need to edit the xml file in case something doesn't work right.
Posted by Sean Shilling on September 30, 2011 at 08:35 AM in CRM Development, Microsoft CRM Customizations, Microsoft CRM Tricks and Tips | Permalink | Comments (0) | TrackBack (0)
September 29, 2011
CRM 2011 - Missing Ribbon Bar
An existing customer of ours, in the process of upgrading to CRM 2011, was testing an upgraded development organization against workstations that previously had the CRM 4 Outlook client installed on them. They encountered this strange behavior relating to the Outlook ribbon bar missing. This only happened on one of the desktop clients they were testing from. Initially they tried uninstall the CRM 2011 Outlook client and the ribbon bar would magically reappear.
The process that they were following:
- Uninstall CRM 4 Outlook client
- Install CRM 2011 Outlook Client
- Configure CRM 2011 Outlook Client
- Open Outlook
The workstation was Windows 7. I did not spend a lot of time troubleshooting because I found this MS KB article. In addition to that, we do know that there were some ribbon bar issues were introduced in earlier MS CRM 2011 releases. As a result of that we recommend doing the following process of for changing Outlook clients for CRM 2011.
We updated the client installation process as follows:
- Close Outlook
- Uninstall CRM 4 Outlook client
- Install CRM 2011 Outlook Client
- Install CRM 2011 Outlook Client rollup 3
- Configure CRM 2011 Outlook Client
- Open Outlook
Obviously, if that does not work the following KB article did fix their particular issues.
Posted by Sean Shilling on September 29, 2011 at 12:02 AM in CRM Best Practices, Microsoft CRM Implementation, Microsoft CRM Tricks and Tips | Permalink | Comments (0) | TrackBack (0)
August 29, 2011
CRM 2011 Form Customization (Part 2)
A while back we looked at customizing the CRM 2011 header and footer. I got some really positive feedback, but some people were tripped up. Troubleshooting via blog is difficult, so whenever you have problems feel free to tweet me (@paul_way). Today, I’m going to go into great detail of changing the form again, but this time we’re going to add a new twist. Take a look at the before and after:
Before
After
Now some of you may be wondering what I changed. First, we still have the light blue shading on the header. Secondly, we moved the footer to the left navigation. User’s may never request changing the color of the forms, but that was intended to show you that you could style the forms any which way you wanted. Moving the footer to the left hand navigation is quite practical. Some users may be faced with smaller screen resolutions and this will really free up space if you intend to put a lot of information in the footer (especially if you are adding to the header already).
Step-by-Step
Setting up our Web Resources
The first thing you’ll need to do is create a solution. I called mine “Form CSS Customization”. In it we are going to add two web resources.
For our initial web resource we’ll create is a CSS file:
.ms-crm-Form-HeaderContainer {
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffB3CAEC, endColorstr=#fff6f8faE)
}
.ms-crm-Form-Footer {
display: none;
}
.ftLabel {
color: #999;
font-style: italic;
padding-left: 4px;
}
.ftValue {
padding: 2px 2px 8px 12px;
text-align: left;
}
#myCustomNav { padding: 2px }
.myNavFoot {
margin-left: 4px;
border: solid 1px #999;
width: 99%;
}
Take note of the name of your CSS file, as we’ll use this in a minute. The CSS file contains the shading on the header as well as some custom table formatting that we’ll use with our JavaScript file.
Now, let’s add our JavaScript file:
function load_css_file(filename) {
var fileref = document.createElement("link")
fileref.setAttribute("rel", "stylesheet")
fileref.setAttribute("type", "text/css")
fileref.setAttribute("href", filename)
document.getElementsByTagName("head")[0].appendChild(fileref)
}function myFormOnLoad() {
load_css_file('/WebResources/cei_/css/case.css'); // don’t forget to change the file!
moveFooterToNav();
}function moveFooterToNav() {
var tbl = document.getElementById('crmNavBar');
var row = tbl.insertRow(tbl.rows.length);
var cFoot = row.insertCell(0);
var txt = document.createTextNode('testing...');
cFoot.appendChild(txt);
cFoot.id = 'myCustomNav';
document.getElementById('myCustomNav').innerHTML = "<table class='myNavFoot' cellspacing='0' cellPadding='0'>" +
"<tr><td class='ftLabel'>Created</td></tr>" +
"<tr><td class='ftValue'>" + document.getElementById('footer_createdby_d').childNodes[0].innerHTML +
"<br />" + document.getElementById('footer_createdon_d').childNodes[0].innerHTML + "</td></tr>" +
"<tr><td class='ftLabel'>Modified</td></tr>" +
"<tr><td class='ftValue'>" + document.getElementById('footer_modifiedby_d').childNodes[0].innerHTML +
"<br />" + document.getElementById('footer_modifiedon_d').childNodes[0].innerHTML + "</td></tr>" +
"</table>";}
The JavaScript file is doing two important things. First, it is injecting the CSS file into the header of the page. This basically tells the browser to pickup the CSS file as if it was always part of the page. After loading the CSS file, the JavaScript will append a table to the left area nav bar. This table is completely custom and you can style it any which way you want. Be careful using Xrm.Page.getAttribute() here, because even though the field is in the footer it may not be accessible to the Xrm object. In my case, the attributes I’m using were not on any of my tabs.
Changing the JavaScript
You must change the “load_css_file” function to contain your specific web resource. To find the web resource name, it’s easiest to look at the CSS web resource inside of CRM:
Configuring the Form
Now we need to go to the form we are customizing. The first step is to add everything we want to onto the footer.
Next, we’ll edit the form properties.
When editing the Form Properties, we’ll need to add the JavaScript library and add our onLoad function to the Form OnLoad. In our example, the function we’re using is called “myFormOnLoad”.
After this, we simply hit OK, Save, and Publish.
Reviewing the Finished Product
As you can see, we made subtle changes to improve the overall look and feel for the user. We also provided a real-life situation where you may need to squeak out some additional room for the user. Hope you enjoy!
Posted by Paul Way on August 29, 2011 at 03:58 PM in CRM Development, Dynamics CRM 2011, Microsoft CRM Customizations, Microsoft CRM Tricks and Tips, XRM | Permalink | Comments (0) | TrackBack (0)
August 23, 2011
CRM 2011–Changing the Form Header and Footer Colors
CRM 2011 has a beautiful default theme. Microsoft has already organized the CRM 2011 folder structure to support themes, but currently only supplies the Silver theme. Office 2010 on the other hand contains Silver, Blue, and Black.
Today we aren’t going to modify the entire theme, but modify the form header and footer. The code I’m going to include is a small gradient, but you could choose a solid background or any color you’d like. I was going to use a Clemson, I mean Customer Effective, Orange, but instead focused more on the blue theme look.
Before
After
Adding our CSS Web Resource
Granted this change was very minor, but you could do about anything you wanted to with it. To accomplish this basic change you must first upload a CSS web resource (I named mine new_header.css) with the following styling:
.ms-crm-Form-HeaderContainer{
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffB3CAEC, endColorstr=#fff6f8faE);
}.ms-crm-Form-Footer{
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffB3CAEC, endColorstr=#fff6f8faE);
background-image: none;
}
Note: The filter property is an IE specific CSS attribute. It isn’t supported in any other browsers (they will just ignore it), but Internet Explorer 7+ supports it just fine.
Modifying the OnLoad
Our next step is to modify the onLoad to inject the CSS file. We’ll do this by adding the following JavaScript to a web resource:
function load_css_file(filename){
var fileref = document.createElement("link")
fileref.setAttribute("rel", "stylesheet")
fileref.setAttribute("type", "text/css")
fileref.setAttribute("href", filename)
document.getElementsByTagName("head")[0].appendChild(fileref)
}function myFormOnLoad(){
load_css_file('/WebResources/new_header.css');
}
Changing the Form Properties
Finally we need to change the form properties to include this JavaScript web resource in the library and add our “myFormOnLoad” function to the OnLoad Event for the Form:
Conclusion
After going through this blog, hopefully you will see how easy it is to change the form header, footer, or anything else. By embedding a CSS file, our ideas are our only limits. Hope you enjoy!
Posted by Paul Way on August 23, 2011 at 08:00 AM in Dynamics CRM 2011, Microsoft CRM Customizations, Microsoft CRM Tricks and Tips, XRM | Permalink | Comments (4) | TrackBack (0)
Technorati Tags: CRM, header, Microsoft CRM, ribbon, ribbon color
August 16, 2011
Hiding the Form Assistant in CRM 2011
In CRM 2011, the Form Assistant is enabled by default for the following entity forms:
- Case (incident)
- Product
- Service Activity (serviceappointment)
For those of you unfamiliar with the Form Assistant, it is designed to help you fill out lookup fields. It is actually pretty nice, but with CRM 2011 it may be a little overkill considering how nice the lookup fields already are.
Hiding via Form XML
The SDK lays out how to hide the Form Assistant by setting the “enablerelatedinformation” attribute to false. http://msdn.microsoft.com/en-us/library/gg334527.aspx
To modify the Form XML, you will need to add the form to a solution and then export. Once you have unzipped the solution, you can modify the Form XML and re-import the solution.
Hiding via JavaScript
Unfortunately, there doesn’t appear to be a way to grab the Form Assistant via the Xrm object. Instead you will have to go old-school and use the document.getElementById. The code to disable the Form Assistant is:
document.getElementById('tdRelatedInformationPane').style.display = 'none';
Either way you go is fine. Personally, I prefer the JavaScript method since it is so easy to revert back. Anyhow, I hope this helps!
Posted by Paul Way on August 16, 2011 at 10:21 AM in Dynamics CRM 2011, Microsoft CRM Customizations, Microsoft CRM Tricks and Tips | Permalink | Comments (1) | TrackBack (0)
August 02, 2011
Printing in CRM 2011
Microsoft CRM Dynamics 2011 offers a nice print preview that will handle almost all of your needs; however, without the ability to execute JavaScript on the print preview, it’s can sometimes fall short. Generally this isn’t a problem because you can create an SSRS report and be on your merry way; however, today we’re going to look at a different option – using the print preview.
The Scenario
Let’s say you have someone that really wants the customer’s URL added to the header. They also want the text to always be blue, whether on the form and when printed. So you add it to the header and add some JavaScript to change the style, no problem.
You take a look at the print preview and find that the color isn’t blue like the customer strongly prefers. Talking to the customer they love this printout except the color has to be blue. You could go through and change this to an SSRS report, but why create another thing for the customer to maintain on something so simple as a blue URL? If only we could execute just a tiny piece of JavaScript on the report… but wait you can!
Executing JavaScript on the Print Preview
To execute JavaScript on the Print Preview, you’ll need to embed a web resource. You will want to make sure it is not visible by default since it really is only there for the JavaScript. I also pass in the record object-type, because chances are you’ll need this for something other than a blue URL.
Now, let’s take a look at this mysterious web resource:
<HTML>
<HEAD>
<SCRIPT type=text/javascript>
if (top.location.href.indexOf("/print/print.aspx") != -1){
top.frames[0].document.getElementById('header_websiteurl_d').firstChild.style.color = '#0000FF';
}
</SCRIPT>
<META charset=utf-8>
</HEAD>
<BODY contentEditable=true>
<FONT size=2 face="Tahoma, Verdana, Arial">Testing...</FONT>
</BODY>
</HTML>
Breaking Down the JavaScript
Looking back at the web resource, the only thing that really matters is the JavaScript. The web resource exists in a hidden iFrame, so we’ll just go ahead and execute the code once the iFrame gets called.
The first thing we’ll check for is if we are in the print preview. We only want to execute the JavaScript inside this web resource if we are on the print preview. So we check the URL of the window to see where we are at:
if (top.location.href.indexOf("/print/print.aspx") != -1){
Next we’ll grab the element we want and change the color of the text to blue:
top.frames[0].document.getElementById('header_websiteurl_d').firstChild.style.color = '#0000FF';
To get to the page being printed, we have to use top.frames[0]. We then will find the id we want changed (and in this case actually need to change the firstChild’s CSS). Any of your “document.getElementById” code on the main form should work so long as you prepend it with top.frames[0].
Seeing the Final Result
Now we see a pretty, blue link on our print preview form and printed document.
Understanding the Drawbacks
This scenario worked perfectly; however not all scenarios will. One of the biggest problems is the inability to access the XRM variable. You can get to an XRM variable on the “printMain” iFrame; however, that XRM variable is very limited. Instead, you can really only use document.getElementById.
Summary
Modifying the Print Preview is quite easy with JavaScript. You won’t get all of the advantages that you get with the normal form, but you can do everything on that print preview that you can do on the form. So whether you plan to resize an iFrame, modify a label, or whatever, you can easily do so while printing as well. Hope you enjoy!
Posted by Paul Way on August 02, 2011 at 02:54 PM in CRM Development, Dynamics CRM 2011, Microsoft CRM Customizations, Microsoft CRM Tricks and Tips | Permalink | Comments (1) | TrackBack (0)
July 25, 2011
Using FetchXML Aggregation to Create a Rollup Plugin in Microsoft Dynamics CRM 2011
One of the most common requirements for a CRM implementation is for a rollup of data on a set of child records to the parent record. CRM has several built in entities, e.g. Orders and Quotes, that implement rollups, but it does not natively support rolling up of fields for custom entities or for customizations of existing entities. Fortunately, it is quite easy to implement a rollup using a simple plugin. Generally, implementation of the rollup involves retrieving all of the child records related to the parent when one of the child records is created, updated, or deleted, calculating the rollup value and updating the parent. This can be done by iterating through all of the child records after retrieving them using the organization service, but why not make things a little easier and use the new aggregation features of FetchXML to calculate the rollup value.
Aggregation support was added to FetchXML with the release of CRM 2011 and it supports the basic aggregation operations of sum, average, minimum, maximum, and count. The query can be built dynamically in the plugin which allows us to create a generic configurable plugin that will work for most standard rollups.
The first piece of code that we will need is a method to build the aggregation query dynamically. The following method will allow us to build the needed query.
1: public string BuildRollupQuery(string rollupEntity, string rollupAttribute, string aggregateOperation, EntityReference parentLookup) {
2:
3: return string.Format(
4: "<fetch distinct='false' mapping='logical' aggregate='true'>" +
5: "<entity name='{0}'>" +
6: "<attribute name='{1}' aggregate='{2}' alias='{1}_rollup' />" +
7: "<filter type='and'>" +
8: "<filter type='and'>" +
9: "<condition attribute='{3}' operator='eq' value='{4:B}' />" +
10: "</filter>" +
11: "</filter>" +
12: "</entity>" +
13: "</fetch>",
14: rollupEntity,
15: rollupAttribute,
16: aggregateOperation,
17: parentLookup.Name,
18: parentLookup.Id);
19: }
This method will allow us to dynamically create the rollup query given the parameters that define the child entity and attribute that is being rolled up, the rollup operation to perform, and the lookup field to the parent of the child. The query will aggregate the rollup attribute for all of the child records related to the parent.
Using this query we can call the following method to execute the query using the organization service and return the rollup value.
1: public object GetRollupValue(IOrganizationService orgService, string rollupQuery, string rollupAttribute)
2: {
3: return orgService
4: .RetrieveMultiple(new FetchExpression(rollupQuery))
5: .Entities
6: .First()
7: .GetAttributeValue<AliasedValue>(rollupAttribute + "_rollup")
8: .Value;
9: }
As you can see, this method executes the query by passing it to the RetrieveMultiple method of the organization service and retrieves the rollup attribute returned from the first record returned by RetrieveMultiple. Notice that the attribute value is returned by RetrieveMultiple as an AliasedValue. This is how all aggregate attributes are returned by the RetrieveMultiple method. I have made a few assumptions with this method for simplicity. For example, if the attribute being rolled up, the Value property of the returned AliasedValue attribute would be returned as a Money object. If that were the case it would be necessary to return the Value property of the Money object. As I mentioned, for simplicity I have assumed that the attribute being rolled up is a simple type, i.e. decimal, int, or double.
We now have the value for our rollup and simply need to update the parent object. Finally, we can make a call to the organization service to perform the update using the following method.
1: public void UpdateParentRollupField(IOrganizationService orgService, EntityReference parentLookup, string parentRollupField, object rollupValue)
2: {
3: Entity rollupParentEntity = new Entity(parentLookup.LogicalName);
4: rollupParentEntity.Id = parentLookup.Id;
5: rollupParentEntity[parentRollupField] = rollupValue;
6:
7: orgService.Update(rollupParentEntity);
8: }
At this point we have all of the tools needed to create the rollup plugin. In order for the plugin to operate properly and efficiently it is necessary to add a few checks and images to the plugin. For example, it is only necessary to update the rollup if the parent lookup field was changed or the field being rolled up on the child record was changed. These values can be checked by examining the InputParameters object that is passed to the plugin. It will also be necessary to check to see if the parent lookup field for the rollup has changed. If it has changed it will be necessary to update the rollup field on the old parent and the new parent. The old parent lookup field value can be retrieved from a Pre Image entity that is registered for the update and delete messages. The following snippet shows an example of the required checks and assumes that the Pre Image registered for the plugin is called “Target”.
1: Entity targetInput = context.InputParameters["Target"] as Entity;
2: Entity targetPreImage = context.PreEntityImages["Target"] as Entity;
3:
4: bool parentLookupChanged = targetInput.Contains(parentLookupFieldName);
5: EntityReference parentLookupOldValue = targetPreImage.GetAttributeValue<EntityReference>(parentLookupFieldName);
6:
7: bool rollupFieldChanged = targetInput.Contains(rollupFieldName);
All that remains at this point is to wrap the code up in a plugin implementation and call the methods as needed. The plugin can be made configurable by setting a configuration when registering the plugin using the plugin registration tool supplied with the SDK and retrieving the configuration string from the configuration argument passed to the plugin constructor. Happy coding!
Posted by Nick Doriot on July 25, 2011 at 08:48 AM in CRM Development, Dynamics CRM 2011, Microsoft CRM Customizations, Microsoft CRM Tricks and Tips, XRM | Permalink | Comments (3) | TrackBack (0)
July 20, 2011
Creating Links to CRM Records from External Systems Using the CRM 2011 REST Services
A common scenario with a CRM implementation includes the need to link to CRM from an external system. The CRM SDK actually describes the URL format for accessing CRM Forms using a URL at the following link, Open Forms, Views, and Dialogs with a URL. Great, problem solved! However, if you take a close look at the URL for a CRM Form you will see that it takes the following format.
http://<mycrm>/<myOrg>/main.aspx?pagetype=entityrecord&etn=<entityLogicalName>&id=<entityId>
As you can see, the problem that presents itself is the need for the GUID of the record. However, quite often the external system/page linking into CRM may not have access to the GUIDs for CRM records. One solution to this problem is to use a tool to get the GUIDs for the CRM records into the external system. A better solution is to leverage CRM Web Resources and the REST services to create a HTML page that can redirect the user to the proper record given some identifying information from the external system, such as an external identifier from the system that is synchronized and stored in CRM.
The first thing needed is to create a HTML Web Resource within CRM. This is a very simple page that merely lets the user know that they are being redirected to a CRM record.
1: <html>
2: <head>
3: <title>Query Redirection</title>
4: <script src="../scripts/json2.min.js" type="text/javascript"></script>
5: <script src="../scripts/jquery_1.6.1.min.js" type="text/javascript"></script>
6: <script src="../scripts/jquery.xrm.orgData_1.0.1.min.js" type="text/javascript"></script>
7: <script src="../../../../ClientGlobalContext.js.aspx" type="text/javascript"></script>
8: <link href="../styles/RedirectQuery.css" rel="stylesheet" type="text/css" />
9: </head>
10: <body>
11: <table width="100%" height="100%" border="0" cellpadding="0" cellspacing="0">
12: <tr>
13: <td id="contentContainer" align="center" valign="middle">
14: <div class="content">
15: <img src="../images/spinner_circle.gif" />
16: <div>Searching for record...</div>
17: </div>
18: </td>
19: </tr>
20: </table>
21: <script src="../scripts/RedirectQuery.js" type="text/javascript"></script>
22: </body>
23: </html>
As you can see, I have included some JavaScript files to make things a little easier when calling the REST services. Also, notice the inclusion of the ClientGlobalContext.js.aspx script. This script file gives the Web Resources the needed XRM context, such as the Xrm.Page.context object. The last script included at the bottom of the page, i.e. RedirectQuery.js is where all of the magic is going to happen. Let’s take a look at what this script needs to do.
The RedirectQuery.js script is going to take a REST query that is passed as a query string parameter, execute the query, extract the logical name of the entity and the ID (GUID) of the record from the result, and redirect the user to the CRM Form for the record.
The first piece to this puzzle is to get the REST query from the query string. HTML Web Resources in CRM are only allowed to have a single query string parameter called “data”, so the REST query is going to be encoded in this query string parameter.
1: // Get the query parameters data
2: queryData = decodeURIComponent(getQueryParam("data"));
Now that we have our REST query we can execute it using a bit of jQuery and a homegrown jQuery plugin that wraps up the calls to the REST services.
1: // Get the data for the target record
2: targetData = $.xrm.orgData.retrieveMultiple({ async: false, query: targetQuery });
The next step is to extract the logical entity name from the original query and the ID (GUID) of the entity from the first record in the results of the REST query.
1: // Get the entity type name from the supplied target query
2: targetEtn = targetQuery.match(/^([a-zA-Z0-9_]+?)Set/)[1];
3:
4: // Extract the ID from the data
5: targetId = targetData[targetEtn + "Id"];
Finally, we can build the URL to the CRM Form with the data we have extracted from our original query and the query’s results and redirect to the CRM Form.
1: // Build the URL
2: targetUrl = Xrm.Page.context.getServerUrl().replace(/\/$/, "") + "/main.aspx" +
3: "?etn=" + targetEtn.toLowerCase() +
4: "&id=" + encodeURIComponent(targetId.toLowerCase()) +
5: "&pagetype=entityrecord";
6:
7: // Redirect to the CRM Form
8: window.location.replace(targetUrl);
As you can see, this CRM Web Resource can help solve the problem of linking to CRM Forms from an external system. All that is left to do is to create the REST query and link on the external web page. The REST query used can contain any criteria necessary to retrieve the desired CRM record. The criteria is commonly an external identifier stored in CRM, but the possibilities are only limited by the capabilities of the CRM REST services.
Posted by Nick Doriot on July 20, 2011 at 08:53 AM in CRM Development, Dynamics CRM 2011, Microsoft CRM Tricks and Tips, XRM | Permalink | Comments (0) | TrackBack (0)
July 18, 2011
CRM 2011 – JavaScript Execution Context - Xrm
A while back, I had two posts concerning the execution of Fetch against the 2011 service (1 & 2). A couple days ago, I received some great comments concerning these posts. One of the comments highlighted a frequent stumbling block I have seen with CRM 2011 – JavaScript Execution Context. Mainly, I’m talking about the JavaScript variable “Xrm”. I think we all got a little spoiled with “crmForm.all”; however, after you get acquainted to using “Xrm”, I think you’ll feel “crmForm.all” was a little bloated. (With it being summer, “Xrm” is ready for the beach!)
In these examples, I’m going to demonstrate executing the getServerUrl() method from an entity’s form.
Inside of a JavaScript Web Resource
To access the getServerUrl() method, simply use:
Xrm.Page.context.getServerUrl()
Inside of an embedded HTML Web Resource (aka an IFrame)
Now since we’re talking context, you can always reference the “ClientGlobalContext.js.aspx” and use:
But I’m wanting more than what’s inside Xrm.Page.context; I want everything inside the “Xrm” variable. For this we can use:
window.parent.Xrm.Page.context.getServerUrl()
To use the “window.parent.Xrm”, you will need to make sure Cross-Scripting is enabled.
Inside of Developer Tools (F12)
I’ve seen people use crmForm.all and then use the CRM 4 to CRM 2011 conversion utility to code. Instead, it’s much easier to use:
document.getElementById('contentIFrame').contentWindow.Xrm.Page.context.getServerUrl()
Summary
If you aren’t familiar with Execution Context, I would recommend delving a little deeper. My intentions for this was merely to demonstrate how to use the “Xrm” variable in the different scenarios. So whether you are using the Console inside Developer Tools or whatever, you can see using the “Xrm” variable is easy once you have the right execution context.
Posted by Paul Way on July 18, 2011 at 08:03 AM in Dynamics CRM 2011, Microsoft CRM Tricks and Tips, XRM | Permalink | Comments (1) | TrackBack (0)
July 08, 2011
Turbo-Charge Your Dynamics CRM Duplicate Detection with SSIS Fuzzy Grouping
One of the really nice features of Dynamics CRM is the built-in duplicate detection. You can look for things like contacts having the same email address or contacts having the same first N characters in their first and last name. But sometimes you want just a little more, and if you bring the power of the Microsoft Stack you have just that. In this post I’m going to talk about how to use the power of SQL Server Integration Services (SSIS) to uncover duplicates by providing a nice little score to determine the likelihood of a duplicate between records. If your on the “business” side of the house, you understand the importance of having a clean list of contacts. Nothing hurts user adoption more than questionable data. If users don’t trust it, they won’t use it. With that said, most of this blog is geared toward CRM system administrators or those on the IT side of the house. However, take a look at some of the finished product here, which we’ll come back to:
On a personal note, I’ve used this process with versions 3 and 4, and expect to use it shortly in 2011. On a more personal note, people have successfully or unsuccessfully tried to call me Jim, Jimmy, Jamie, or any other derivation of James. More than likely, this is how they try to enter me into their Dynamics CRM. The out-of-the-box duplicate detection will not ferret these out, but the example I will use does exactly this.
1. In the interest of time and space, I’m going to refer you to this posting from Microsoft, which walks one through how to set up the Fuzzy Grouping in the first place.
2. I will typically use another database that is on the same database server as my CRM database to be the destination of the Fuzzy Grouping process. This database will be used to create the many temp tables needed to handle the Fuzzy Grouping process as well. As a rule, I don’t modify the CRM database.
Now that you’ve read the aforementioned posting from Microsoft, you will understand these next sentences. I include the contactid and masterid as output columns. These columns are in the result set but do not factor into the probability score. They are still important. Other than that, I will include firstname, lastname, parentcustomeridname, and possibly address1_city as input columns. Include whatever other input columns depending on how you define a duplicate. Your Fuzzy Grouping step should look something like this:
Let’s take a look at the Advanced tab in the above screen as well:
The ability to throttle the similarity threshold is important, as you have another screen to reduce or increase the number of records which show as duplicates.
Here’s what the destination table should look like after you run it:
The _key_out column value is the same across all of these records, so now we need to do some fancy SQL to get to this:
Note the GUIDs were redacted, if only because I was looking for a chance to use that word.
With this all set, I now need to get it into CRM. I have a custom, cross reference entity with two N:1 relationships to the contact entity. That’s where the redacted GUIDs go. Load those into CRM using your ETL tool of choice and voila. See this screen shot as an example of how one of these records looks and how the entity is set up:
This leads us back to that first screen shot. I’ve included information from the contact record in my view to help me decide if they are in fact duplicates. I can use the Bulk Edit feature to update the Merge Status flag en masse as well.
Now that we’ve identified the duplicates and which record should be merge into which, what happens next? If you’re like me, you put in a call to the Customer Effective development team. They then provide you with a tool that uses the CRM SDK to cycle through these records and actually do the merge. (In case you were wondering, the Flip and Merge setting tells the application to flip the which contact is the Subordinate and which is the Master). One could still merge records based on this information manually, but I would not recommend it.
Additional Points:
1. If you want to automate this Fuzzy Grouping process, you will need to have SQL Server Enterprise edition. I have not yet experimented with using Windows Scheduler to do this, but I will give that the old college try.
2. Depending on your recordset size, you would be advised to run this off-hours. I’ve run this against approximately 250,000 records, and it took about an hour as I recall. Let this not be your benchmark (see point #3).
3. Test.
4. This process won’t replace a human being eyeballing the information. “Bright” and “Wright” will achieve a very high score with this process.
5. See point #3.
6. The sample screenshots here are for contacts, but this works swimmingly for accounts as well.
7. This should really only be used where your data is so complicated that duplicate detection is not up to the task. This is usually not the case.
If you’d like more details on this process or information on how we at Customer Effective can help you with cleaning your data, please contact us at info@customereffective.com.
Posted by James Diamond on July 08, 2011 at 03:36 PM in CRM Development, Microsoft CRM Tricks and Tips | Permalink | Comments (1) | TrackBack (0)
Using the FetchXML CRM 2011 Service within a JavaScript Web Resource
Last time we looked at using the FetchUtil.js file inside of an HTML file. This time, I’m going to show you how to execute fetch from JavaScript. I’m also going to talk a little about organizing your web resources and talk briefly about when to use the fetch. Before we begin, let’s lay some of the ground work here.
Keeping it Organized
CRM 2011 has some awesome features and web resources is certainly one of them. It’s important when you create your web resources to have some organization. The more you extend CRM, the easier it would be to start messing others up. I think the best route is to use your abbreviation, followed by the the folder, followed by the file. That sounds a little confusing, so lets look at an example.
Today we are going to create two files. The first is our JavaScript Fetch utility. The second is a JavaScript file specific to our incident entity. In fact, the incident entity is all part of our incidentX project. On the other hand, the Fetch Utility is going to be used across multiple projects. Now there isn’t any need to upload the Fetch Utility inside each project that needs it. So we will create two web resources like so:
- new_utils/FetchUtil.js
- new_incidentX/showMainPhone.js
Now, if I want to add some custom icons and other things to my incidentX project, I can simply do so in the new_incidentX folder.
Adding the Web Resources
Let’s look at both of the files and how they will appear on the form.
FetchUtil.js
var XMLHTTPSUCCESS = 200;
var XMLHTTPREADY = 4;function FetchUtil(sOrg, sServer) {
this.org = sOrg;
this.server = sServer;if (sOrg == null) {
if (typeof (ORG_UNIQUE_NAME) != "undefined") {
this.org = ORG_UNIQUE_NAME;
}
}if (sServer == null) {
this.server = window.location.protocol + "//" + window.location.host;
}
}FetchUtil.prototype._ExecuteRequest = function (sXml, sMessage, fInternalCallback, fUserCallback) {
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", this.server + "/XRMServices/2011/Organization.svc/web", (fUserCallback != null));
xmlhttp.setRequestHeader("Accept", "application/xml, text/xml, */*");
xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");if (fUserCallback != null) {
//asynchronous: register callback function, then send the request.
var crmServiceObject = this;
xmlhttp.onreadystatechange = function () {
fInternalCallback.call(crmServiceObject, xmlhttp, fUserCallback)
};
xmlhttp.send(sXml);
} else {
//synchronous: send request, then call the callback function directly
xmlhttp.send(sXml);
return fInternalCallback.call(this, xmlhttp, null);
}
}FetchUtil.prototype._HandleErrors = function (xmlhttp) {
/// <summary>(private) Handles xmlhttp errors</summary>
if (xmlhttp.status != XMLHTTPSUCCESS) {
var sError = "Error: " + xmlhttp.responseText + " " + xmlhttp.statusText;
alert(sError);
return true;
} else {
return false;
}
}FetchUtil.prototype.Fetch = function (sFetchXml, fCallback) {
/// <summary>Execute a FetchXml request. (result is the response XML)</summary>
/// <param name="sFetchXml">fetchxml string</param>
/// <param name="fCallback" optional="true" type="function">(Optional) Async callback function if specified. If left null, function is synchronous </param>var request = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
request += "<s:Body>";request += '<Execute xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services">' + '<request i:type="b:RetrieveMultipleRequest" ' + ' xmlns:b="http://schemas.microsoft.com/xrm/2011/Contracts" ' + ' xmlns:i="http://www.w3.org/2001/XMLSchema-instance">' + '<b:Parameters xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic">' + '<b:KeyValuePairOfstringanyType>' + '<c:key>Query</c:key>' + '<c:value i:type="b:FetchExpression">' + '<b:Query>';
request += CrmEncodeDecode.CrmXmlEncode(sFetchXml);
request += '</b:Query>' + '</c:value>' + '</b:KeyValuePairOfstringanyType>' + '</b:Parameters>' + '<b:RequestId i:nil="true"/>' + '<b:RequestName>RetrieveMultiple</b:RequestName>' + '</request>' + '</Execute>';
request += '</s:Body></s:Envelope>';
return this._ExecuteRequest(request, "Fetch", this._FetchCallback, fCallback);
}
FetchUtil.prototype._FetchCallback = function (xmlhttp, callback) {
///<summary>(private) Fetch message callback.</summary>
//xmlhttp must be completed
if (xmlhttp.readyState != XMLHTTPREADY) {
return;
}//check for server errors
if (this._HandleErrors(xmlhttp)) {
return;
}var sFetchResult = xmlhttp.responseXML.selectSingleNode("//a:Entities").xml;
var resultDoc = new ActiveXObject("Microsoft.XMLDOM");
resultDoc.async = false;
resultDoc.loadXML(sFetchResult);//parse result xml into array of jsDynamicEntity objects
var results = new Array(resultDoc.firstChild.childNodes.length);
for (var i = 0; i < resultDoc.firstChild.childNodes.length; i++) {
var oResultNode = resultDoc.firstChild.childNodes[i];
var jDE = new jsDynamicEntity();
var obj = new Object();for (var j = 0; j < oResultNode.childNodes.length; j++) {
switch (oResultNode.childNodes[j].baseName) {
case "Attributes":
var attr = oResultNode.childNodes[j];for (var k = 0; k < attr.childNodes.length; k++) {
// Establish the Key for the Attribute
var sKey = attr.childNodes[k].firstChild.text;
var sType = '';// Determine the Type of Attribute value we should expect
for (var l = 0; l < attr.childNodes[k].childNodes[1].attributes.length; l++) {
if (attr.childNodes[k].childNodes[1].attributes[l].baseName == 'type') {
sType = attr.childNodes[k].childNodes[1].attributes[l].text;
}
}switch (sType) {
case "a:OptionSetValue":
var entOSV = new jsOptionSetValue();
entOSV.type = sType;
entOSV.value = attr.childNodes[k].childNodes[1].text;
obj[sKey] = entOSV;
break;case "a:EntityReference":
var entRef = new jsEntityReference();
entRef.type = sType;
entRef.guid = attr.childNodes[k].childNodes[1].childNodes[0].text;
entRef.logicalName = attr.childNodes[k].childNodes[1].childNodes[1].text;
entRef.name = attr.childNodes[k].childNodes[1].childNodes[2].text;
obj[sKey] = entRef;
break;default:
var entCV = new jsCrmValue();
entCV.type = sType;
entCV.value = attr.childNodes[k].childNodes[1].text;
obj[sKey] = entCV;break;
}}
jDE.attributes = obj;
break;case "Id":
jDE.guid = oResultNode.childNodes[j].text;
break;case "LogicalName":
jDE.logicalName = oResultNode.childNodes[j].text;
break;case "FormattedValues":
var foVal = oResultNode.childNodes[j];for (var k = 0; k < foVal.childNodes.length; k++) {
// Establish the Key, we are going to fill in the formatted value of the already found attribute
var sKey = foVal.childNodes[k].firstChild.text;jDE.attributes[sKey].formattedValue = foVal.childNodes[k].childNodes[1].text;
}
break;
}}
results[i] = jDE;
}
//return entities
if (callback != null) callback(results);
else return results;}
function jsDynamicEntity(gID, sLogicalName) {
this.guid = gID;
this.logicalName = sLogicalName;
this.attributes = new Object();
}function jsCrmValue(sType, sValue) {
this.type = sType;
this.value = sValue;
}function jsEntityReference(gID, sLogicalName, sName) {
this.guid = gID;
this.logicalName = sLogicalName;
this.name = sName;
this.type = 'EntityReference';
}function jsOptionSetValue(iValue, sFormattedValue) {
this.value = iValue;
this.formattedValue = sFormattedValue;
this.type = 'OptionSetValue';
}
showMainPhone.js
var _oService;
var _sOrgName = "";
var _sServerUrl = Xrm.Page.context.getServerUrl();
function fetchOnLoad()
{
// Get the ID of the Customer
var sCustGUID = Xrm.Page.getAttribute(' customerid ').getValue()[0].id;
var sFetch = "<fetch mapping='logical' count='10'>" +
"<entity name='account'>" +
"<attribute name='telephone1' />" +
"<filter type='and'>" +
"<condition attribute = 'accountid' operator='eq' value='" + sCustGUID + "'/>" +
"</filter>" +
"</entity>" +
"</fetch>";_oService = new FetchUtil(_sOrgName, _sServerUrl);
_oService.Fetch(sFetch, myCallBack);
}function myCallBack(results){
alert(results[0].attributes["telephone1"].value);
}
Now I have added my code to the incident entity. I also called my files by a somewhat different name; in this case “showMainPhone.js” is the same as “usingFetch.js”. The great thing here is the only text that really has to match up is the function name in the onLoad. Everything else was made pretty much bullet proof by CRM 2011.
Seeing the Results
When opening an incident, I will now see an alert box with the phone number of the account associated.
Granted, this may not be the particular feature you users are begging for (in fact the “showMainPhone.js” file needs plenty of error handling), but hopefully you can see how easy it is to embed a fetch call into your JavaScript. Whether you are summing up values from related entities or whatever, it is quite easy to use the fetchUtil.js file to enhance your end user’s experience.
Switching to Synchronous Fetch
This example used an asynchronous callback because that is what I would recommend most of the time. An asynchronous callback will let the browser execute other code; whereas the synchronous approach will wait for the fetch to finish. To use the synchronous version, you will need to change the “fetchOnLoad()” function to like so:
function fetchOnLoad() {
// Get the ID of the Customer
var sCustGUID = Xrm.Page.getAttribute('customerid').getValue()[0].id;var sFetch = "<fetch mapping='logical' count='10'>" +
"<entity name='account'>" +
"<attribute name='telephone1' />" +
"<filter type='and'>" +
"<condition attribute = 'accountid' operator='eq' value='" + sCustGUID + "'/>" +
"</filter>" +
"</entity>" +
"</fetch>";_oService = new FetchUtil(_sOrgName, _sServerUrl);
var res =_oService.Fetch(sFetch);
alert(res[0].attributes["telephone1"].value);
}
Posted by Paul Way on July 08, 2011 at 03:34 PM in CRM Development, Dynamics CRM 2011, Microsoft CRM Tricks and Tips, XRM | Permalink | Comments (0) | TrackBack (0)




Recent Comments