CRM Development
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 2
Back to Part 1... Microsoft Dynamics CRM 2011 Standalone on VirtualBox Part 1
2: Add the ASP.NET role service:
Install CRM 2011
Great! We are ready to finish and fulfill our goal. From the host machine unmount the SQL Server 2008 R2 and mount the Microsoft Dynamics CRM Server 2011. When you mount Microsoft Dynamics CRM Server 2011 from the host machine go to CRMDEMO2011 and run the splash.exe from the ‘D:’ drive.
1: Select: Install Microsoft Dynamics CRM Server
2: Toggle: Get update for Microsoft Dynamics (recommended)
3: Specify the SQL Server, in this guide we are saying it is CRMDEMO2011, the local machine:
4: Specify the Organizational Unit, the Active Directory root we prompted the server to:
5: Specify what CRM services will run as, use the CRM2011NET\CRMDEMOADMIN account:
6: Specify the database name:
7: Get Excited! Here’s your first glimpse of the URL for your new CRM environment:
8: Blast off! Click install and wait with your http://crmdemo2011 url in the clipboard to paste that sucker into IE. But wait…make sure you allow the Microsoft Dynamices CRM Reporting Extensions Setup to run.
Login to http://crmdemo2011 with the CRMDEMOADMIN user and gloat with pride:
All-in-all, this installation should only take 3 hours or less once you have all the components downloaded and in place. The first time can take up to 6-8 hours because you have to download all the ISOs and get familiar with VirtualBox. But, once you make a copy of the CRMDEMO2011.VDI at a point that you consider a starting point then you can start from there to cut out more time. Which by the way…now is a good time to back up that CRMDEMO2011.VDI file. Looking forward, another idea from here is using the VM created to install the SharePoint and Outlook components.
Good Luck…
Back to Part 1... Microsoft Dynamics CRM 2011 Standalone on VirtualBox Part 1
Posted by Manny Ed on January 18, 2012 at 08:03 AM in CRM Development, Dynamics CRM 2011, Microsoft CRM Implementation, Web/Tech | Permalink | Comments (1) | TrackBack (0)
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 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 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 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 12, 2011
Jscript Pop-up in Microsoft Dynamics CRM - Internet Explorer vs. Outlook
Occasionally when developing custom solutions in CRM, there will be a need to open a new window from Jscript. Typically the window.open function is used to accomplish this, but this is not always ideal in CRM.
Consider developing a custom solution in CRM where you need to open another CRM record in a new window. In the web interface of CRM the window.open function works fine, but what about the Outlook integration functionality? Well, in On-Premise this doesn’t pose a big issue because Internet Explorer will typically use your logged in account to authenticate with CRM. But what if you are not using On-Premise, or what if you have another set of logins specifically for CRM in your Active Directory environment (not sure why you would, but it could happen).
Outlook allows a user to cache their credentials to allow communication with the CRM environment without having to log in every time. However, if you open a new window using the standard Jscript window.open function, it will open your window in Internet Explorer not Outlook. In situations where you have to log in to reach CRM (i.e. not using On-Premise with your standard Active Directory account), this will require the user to enter their credentials before opening the correct page. This really disrupts the flow of any process, and is annoying and confusing to end users.
Luckily, Microsoft provided a function to handle this situation (although it is undocumented). There is an openStdWin function that is a part of the CRM Jscript implementation that will check to determine if the user is using Outlook or Internet Explorer, and open the new window in the appropriate format. The following code displays the way this functions:
function openNewWindow(url) {
var name = "newWindow";
var width = 800;
var height = 600;
var newWindowFeatures = "status=1";
var oldWindowFeatures = "width=800,height=600,status=1";
// Regular Jscript function to open a new window
//window.open(url, name, oldWindowFeatures);
// CRM function to open a new window
openStdWin(url, name, width, height, newWindowFeatures);
// CRM function to open a new window
// with default CRM parameters
//openStdWin(url, name);
}
One point to note, if you simply pass the url and name to the openStdWin function and leave off the width, height, and windowFeatures, the window will be opened with the same parameters that regular CRM windows are opened with. (i.e. same width and height as a standard CRM pop-up with no address bar, but with the status bar and resizable)
Now you don’t have to worry if the user is in Outlook or Internet Explorer, your pop-up window will open correctly. Enjoy!
Notice: openStdWin is an internal function of CRM and may change at any given point without notice.
Posted by Tyler Compton on December 12, 2011 at 10:37 AM in CRM Development, Microsoft CRM for Outlook, Microsoft CRM Online | Permalink | Comments (0) | 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 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 26, 2011
CRM 2011 and Windows Identity Foundation
This is a quick reminder that if you are deploying custom applications on servers other than where you install CRM, you need to manually install WIF (Windows Identity Foundation). These pieces are not included in the .Net Framework 4. Here is a sample error message you may receive if you run a custom application without WIF installed.
You can download and install the appropriate WIF from here. I also noticed that Microsoft upgraded the download site for this piece. You no longer need to know what OS you are on to download the appropriate file.
Posted by Sean Shilling on October 26, 2011 at 03:30 PM in CRM Development, Microsoft CRM Customizations, XRM | 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)
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)
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 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 11, 2011
Adding a Loading Screen to an iFrame in CRM
iFrames aren’t Evil
iFrame’s caught a lot of flack years ago when websites used them all over the place. For those of you old enough to have enjoyed the Gummi Bears’ cartoons, you’ll remember navigating iFrame websites where you couldn’t bookmark the right page, never could print properly, and could easily have double navigation.
After iFrames, people created 30 minute flash introductions and I’m sure next we’ll see people butcher HTML5. None of those technologies are bad, just implemented poorly at times. My point being is that iFrames (and Gummi Bears) are still good. Let’s look at where an iFrame works really well.
The Before
Granted this is a CRM 4 project, but it can apply to any version of CRM.
Our customer needed a way to enter specific contracts and needed a decent amount of information on a single page. They also needed the maximum screen width for the “Contract Lines” tab. They were also wanting to read from another SQL database for Actuals.
The After
To maximize the viewable area I removed the left hand navigation, and swapped the header for a “CRM 2011 inspired” header. The new header is an iFrame and loads information from both CRM and a separate SQL database.
To inject an iFrame, you can do so by replacing an outerHTML (or inner) with:
<iframe id="ifrmHdr" src="/isv/cei/contracts/contracthdr.aspx?ID=' + crmForm.ObjectId + '" height="135" width="100%" frameborder="0" scrolling="no"></iframe>
Adding the Loading Screen
In my case, the other SQL server had a good amount of data and took around two seconds to return its results. To give the user a nice look and feel, I wanted to add a loading screen to the header while it was waiting on the results.
To do this, I injected both a DIV and an iFrame.
.outerHTML = ‘<div id="loadingGif" style="text-align:center;width:100%;height:135px;">’ +
‘<img src="/isv/cei/loading/loading.gif" /></div>’ +
‘<iframe id="ifrmHdr" src="/isv/cei/contracts/contracthdr.aspx?ID=' + crmForm.ObjectId + '" height="135" width="100%"’ +
‘ frameborder="0" scrolling="no" style="display:none;"></iframe>';
First, I made the iFrame hidden by default. Secondly, I added the loading in place of where the iFrame will be. You’ll want to make sure the div height and width matches the iFrame so the screen doesn’t jump around.
Next, on my custom ASPX page I had to add a little JavaScript.
<script type="text/javascript">
function hideLoading(){
try {
window.parent.document.getElementById('loadingGif').style.display = 'none';
window.parent.document.getElementById('ifrmHdr').style.display = 'block';
} catch (ex) {}
}
window.onload = hideLoading();
</script>
The JavaScript sits on the iFrame page and waits until everything has loaded on the page. After the loading is complete, it hides the Loading DIV container and shows the iFrame.
Summary
This was just one example of adding a loading screen. You could instead create web methods on your aspx page. You could also add an event listener to the iFrame if you aren’t able to add the JavaScript to the page (say a 3rd party page). Any which way you do it, adding a loading screen can be a nice touch. Hope you enjoy!
Posted by Paul Way on July 11, 2011 at 11:11 AM in CRM Development | Permalink | Comments (0) | 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)
June 28, 2011
MS CRM 2011 Developer JavaScript PDF
If you are like many of us trying to get up to speed on CRM 2011 new JavaScript objects, I hope this helps. I came across this the other day. The original post and credit goes to Daniel Cai. You can find his blog here.
However, I found that someone uploaded the same PDF to this free E-Book Browse site.
http://ebookbrowse.com/mscrm-2011-javascript-development-cheat-sheet-pdf-d126038641
Here is a sample screen shot.
Hope it helps!
Posted by Sean Shilling on June 28, 2011 at 03:00 PM in CRM Development, Microsoft CRM Customizations | Permalink | Comments (0) | TrackBack (0)
June 27, 2011
CRM 2011 for Private Equity: Fundraising, Deal Making, and Microsoft Outlook
Relationships count in every business, but for a private equity firm the ability to see the extent of relationships can be the difference between a good firm and a great firm. Its uncommon in other industries to see the web of connections that are seen in private equity. A company that a private equity firm works with could be the debt provider in one deal, a co-investor in another, and could be the company that brings the next big deal to its doorstep. And that same company could also be part of a bidding group that is that is the competition in another deal. Try keeping track of this in one CRM, even one system, and do it all through a tool that everyone uses everyday like, perhaps Microsoft Outlook. And to make matters even more complicated your system has to be able to turn on a dime in order to respond to one of the most transitory regulatory environments since the New Deal. These problems are right in the wheelhouse of CRM 2011.
First, CRM 2011 is inherently designed to work with Microsoft Outlook. Other CRM essentially try and force a synchronization. I will routinely get the question of “Am I in Outlook or CRM?” in working with new users. My answer is “Yes”: especially with 2011 , the difference is blurred. This is no small concern when thinking about how users are going to interact with the system. For one thing, users don’t have to go to sign in to X other systems each potentially with its own password and, even more importantly, its own learning curve. Instead, CRM 2011 is “just” another set of folders in Outlook, and that is just the tip of the iceberg when it comes to Outlook integration. But think about this feature in concrete terms: what if users had a Deals or Fundraising folder right in Outlook? What if you could tie Outlook appointments to a particular Deal, or even to a potential lender on that deal in 2 clicks? And of course, what if you wanted to see all this in a dashboard that shows your Deal pipeline for the year in Outlook? CRM 2011 has its own user-friendly charting tools to provide dashboards that are the three R’s: rich, relevant, and role-based. And of course, these dashboards are Outlook accessible. (See screen shot below as an example of the Outlook experience).
But even if you take Outlook off the table, CRM 2011 is powerful in its ability to surface relationships and do it rapidly. Being able to show how a given financial services company may interact with a private equity firm in the aforementioned ways is an easy feat for CRM 2011. In a world where a financial services company is always acting in many different capacities and their roles change depending on the deal, this is not by any stretch a hypothetical example. And of course, this is the kind of thing we see again and again in our work in private equity. But let’s talk about “rapidly”. Imagine being in a room with a compliance team, and they bring up a new requirement that the CRM must follow. And of course, this requirement must be followed presently. And then imagine that you prototype that change in the meeting. If only all meetings could be like that? This example is not hypothetical but very relevant. One question to ask in terms of a CRM is quite simply: can I change it if I need to?
This segues into what I think is the most compelling feature of CRM 2011 in that it allows for a private equity based CRM solution to be brought into a firm, without being locked into that solution. The regulatory environment brought about by Dodd-Frank is still being interpreted, and will most likely continue to be so for years. The takeaway here is that firms need to be able to react quickly, but have a solid foundation. That is CRM 2011.
This will be the first in a series of articles discussing the challenges faces by financial services firms and how CRM 2011 meets that challenge. If you’d like to hear more about our insights and experience in the private equity arena, and how we use CRM 2011 to solve problems like the ones discussed here, please contact us at info@customereffective.com.
Posted by James Diamond on June 27, 2011 at 09:12 PM in CRM Business Process, CRM Development, Dynamics CRM 2011, Microsoft CRM for Outlook | Permalink | Comments (0) | TrackBack (0)
June 08, 2011
Retrieving All Attributes in Microsoft Dynamics CRM 2011
Occasionally you may come across the need to return all of the attributes on an entity. Today I’m going to show you how to return all attributes inside of Fetch, within the IOrganizationService, and provide a little caution about returning all attributes.
Fetch XML
This is pretty well documented, but it’s worth mentioning. In FetchXML, you’ll simply use “<all-attributes />”:
<fetch mapping='logical' page='1' count='10'>
<entity name='account'>
<all-attributes />
<link-entity name='systemuser' to='createdby'>
<filter type='and'>
<condition attribute='firstname' operator='eq' value='Jeff' />
<condition attribute='lastname' operator='eq' value='Smith' />
</filter>
</link-entity>
<filter type='and'>
<condition attribute='createdon' operator='today' />
</filter>
</entity>
</fetch>
This query returns all of the accounts that Jeff Smith created today. In this example the “<all-attributes />” only applies to the account entity and does not return all of the attributes of the linked entity.
IOrganizationService
When it comes to the IOrganizationService, I couldn’t find any examples of this but was able to figure it out from the SDK. Here it is simply:
ColumnSet csAll = new ColumnSet(true);
Entity retEnt = (Entity)_service.Retrieve(sLogicalName, gEntity, csAll);
The trick here is that we need to build our ColumnSet with a Boolean value of true.
When to Use ALL
First off, it should be rare in production code. The reason you want to typically avoid returning all of the attributes is because it will be slower if you aren’t using but a few of the attributes. Typically you will specify what attributes you need or tie into a CRM View (which allows the user to easily change what records are displayed).
Sometimes though, you may need to provide flexibility in lieu of a slight performance hit. I came across the need for returning all of the attributes due to a customer portal where I am dynamically displaying an entity and allowing the user to update the entity. Using all attributes, I can allow the Portal form to be customizable as well as allowing for any entity.
Posted by Paul Way on June 08, 2011 at 08:47 AM in CRM Best Practices, CRM Development, Dynamics CRM 2011, Microsoft CRM Tricks and Tips | Permalink | Comments (0) | TrackBack (0)
May 18, 2011
FetchXML 2011–Total Record Count
Fetch in 2011 has added some really nice features. One of those nice features is the total record count of the applied filter. In 4.0 you had to execute two fetch statements, but now with 2011 you can add the ReturnTotalRecordCount attribute.
<fetch mapping='logical' count='25' returntotalrecordcount='true'>
<entity name='account'>
<attribute name='accountid' />
<attribute name='accountnumber' />
<attribute name='address1_city' />
<attribute name='address1_telephone1' />
<attribute name='name' />
<order attribute='name' />
<filter>
<condition attribute='address1_city' operator='like' value='Greenville%' />
</filter>
</entity>
</fetch>
Upon executing this fetch statement with the ReturnTotalRecordCount attribute set to true, the “TotalRecordCount” is returned.
Note: This is not the total number of entities, it is the total number of records that match the filter.
Posted by Paul Way on May 18, 2011 at 11:11 PM in CRM Development, Dynamics CRM 2011, Microsoft CRM Tricks and Tips, XRM | Permalink | Comments (0) | TrackBack (0)
May 12, 2011
Execute Fetch from JavaScript in CRM 2011
If you are like me, you love JavaScript and use it frequently. One thing I’ve been missing is the ability to execute Fetch statements against the 2011 Organization service through JavaScript. With this basic FetchUtil JavaScript library, now you can use the 2011 service. After using this, hopefully you will find some nice little enhancements baked into the 2011 endpoint.
There are basically two files involved here. The core of it all is the FetchUtil.js which will actually build the SOAP XML and execute against CRM 2011. The second part is a small little HTML (with JavaScript embedded) showing you an example use of the FetchUtil.js file.
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 xmlReturn = xmlhttp.responseXML.xml;
xmlReturn = xmlReturn.replace(/</g, '<');
xmlReturn = xmlReturn.replace(/>/g, '>');
results = xmlReturn;
//return entity id if sync, or call user callback func if async
if (callback != null)
{
callback(results);
}
else
{
return results;
}
}
fetchExample.htm
<html>
<head>
<title>Fetch 2011 JavaScript Example</title>
<link rel="stylesheet" type="text/css" href="/_common/styles/theme.css.aspx" />
<link rel="stylesheet" type="text/css" href="/_common/styles/global.css.aspx" />
<link rel="stylesheet" type="text/css" href="/_common/styles/fonts.css.aspx" />
<link rel="stylesheet" type="text/css" href="/_common/styles/Dialogs.css.aspx" /><script src="ClientGlobalContext.js.aspx"></script>
<script src="{pub}_FetchUtil.js"></script><script type="text/javascript">
var _oService;
var _sOrgName = "";
var _sServerUrl = GetGlobalContext().getServerUrl();function executeFetchCommand()
{
var sFetch = document.getElementById('txtFetch').value;
_oService = new FetchUtil(_sOrgName, _sServerUrl);
var oEntity = _oService.Fetch(sFetch, myCallBack);
}
function myCallBack(results){
var sOut = "";
sOut += "<b>XML Response</b><br />";
sOut += results;
document.getElementById('dvData').innerHTML = sOut;
}
</script></head>
<body>
<table style="width: 100%; height: 100%;" cellspacing="0" cellpadding="0" border="0">
<tr>
<td class="ms-crm-Dialog-Header" id="tdDialogHeader">
<div id="divTitle" class="ms-crm-Dialog-Header-Title">Executing Fetch from JavaScript in CRM 2011</div>
<div id="divInstructions" class="ms-crm-Dialog-Header-Desc">Enter some FetchXML to be executed against your CRM environment</div>
</td>
</tr>
<tr>
<td style="height: 100%;">
<div class="ms-crm-Dialog-Main" style="padding: 14px;">
<label>Fetch Command</label><br />
<textarea id="txtFetch" rows="8" width="100%" cols="100" ></textarea><br />
<input type="Submit" value="Fetch" onClick="javascript:executeFetchCommand();" style="width:100px;height:24px;">
<div id='dvData' style="width: 100%; height: 100%;"></div>
</div>
</td>
</tr>
<tr>
<td class="ms-crm-Dialog-Footer" id="tdDialogFooter">
<table cellspacing="0" cellpadding="0">
<tr>
<td width="100%"></td>
<td> <button onclick="window.close();" class="ms-crm-Button">Done</button></td>
</tr>
</table>
</td>
</tr>
</table></body>
</html>
Now that we have our JavaScript utility and our Fetch example, we need to do a few things.
- We need a solution to put these two files into (feel free to add it to an existing solution or create one named whatever you like).
- Let’s now import the FetchUtil.js file and publish. Take note of the Publisher Prefix (the letters in front of the name).
- We now need to modify the fetchExample.htm file to point to this FetchUtil.js by replacing the {pub} with your Publisher Prefix (ex. “new_FetchUtil.js”).
- We also need to modify the _sServerUrl and change the {org} to the appropriate organization abbreviation. If you are using the onsite, you will need to change the entire URL to match accordingly.
- Finally, let’s upload the fetchExample.htm file and publish.
This should now bring you to a super fancy screen to paste some Fetch into.
Well there you have it. The next step is to parse the XML with jQuery, RegEx, traversing the nodes, or however you prefer. Hope you enjoy!
Posted by Paul Way on May 12, 2011 at 08:36 AM in CRM Development, Dynamics CRM 2011 | Permalink | Comments (7) | TrackBack (0)
February 21, 2011
Obtaining a Reference to the Full Xrm.Page Object From A CRM 2011 Silverlight Application
When developing a Silverlight application for CRM 2011 one of the common needs is to access the Xrm.Page object and its child objects. The suggested solution provided by the CRM 2011 SDK is to utilize the HtmlPage static class to obtain a reference to the Xrm.Page.context object as a ScriptObject. Additionally, if the application is not hosted in an entity's form, a reference to the ClientGlobalContext.js.aspx page needs to be added to the page hosting the Silverlight application. See the related article in the SDK for more information http://msdn.microsoft.com/en-us/library/gg328358.aspx.
The solution suggested in the SDK will provide access to the Xrm.Page.context object, but what if there is a need to access the other objects contained in Xrm.Page such as the Xrm.Page.data.entity object? If the application is hosted in an entity form then accessing the Xrm.Page.data.entity object can be done using the same paradigm. However, if the application is hosted in a separate web page via an IFRAME, e.g. using a navigation link on the entity's form, then access to the Xrm.Page.data.entity object will not be possible using the solution suggested by the SDK as a null value will be returned.
In addition to having access to all of the available objects contained in Xrm.Page, it would be nice to encapsulate the logic in a library that can be reused regardless of how the Silverlight application is being hosted within CRM. Consider the following possibilities for hosting a Silverlight application within CRM:
- Directly in an entity form
In this scenario, the Silverlight application is hosted in the entity’s form using an OBJECT tag. Accessing the Xrm.Page object and its child objects can be done by using the window object that is hosting the application. - In a separate webpage accessed via a navigation link on an entity form
In this scenario, the Silverlight application is hosted in separate webpage in an IFRAME. Accessing the Xrm.Page object and its child objects other than the context object is only possible by using the parent window of the window object that is hosting the application. - In a separate webpage accessed via a link created in the Site Map
In this scenario, the Silverlight application is hosted in separate webpage in an IFRAME. Accessing the Xrm.Page object and its child objects other than the context object is not possible since there is not an entity form in context.
Considering the above scenarios, it is possible to obtain access to Xrm.Page and available child objects by walking up the chain of window objects until the Xrm.Page object is found. The first Xrm.Page object found should be in the context of the entity’s form if the application is hosted in either the first or second scenario presented previously. If the other scenario is being used, the global Xrm.Page object will be found which at least provides access to the context object.
The following snippet of code will obtain a reference to the Xrm.Page if it exists. This object can then be used to obtain references and invoke methods on the available child objects of Xrm.Page such as context, data.entity, ui, etc. if they exist. The last piece of the puzzle is to exclude the reference to the ClientGlobalContext.js.aspx as it is no longer needed, and will actually cause a Xrm.Page object that only contains a context object to be returned.
1: // Initialize to the current window/iframe hosting the silverlight app
2: string windowPath = "window.self";
3: bool windowIsTop = false;
4: ScriptObject window;
5: ScriptObject xrmObject = null;
6:
7: // Iterate until we find the Xrm object or we have reached the top window
8: do
9: {
10: // Attempt to obtain a reference to the Xrm object
11: window = HtmlPage.Window.Eval(windowPath) as ScriptObject;
12: xrmObject = window.GetProperty("Xrm") as ScriptObject;
13:
14: // Determine if the current window is the top window and update the current
15: // window path to its parent window
16: windowIsTop = (bool)HtmlPage.Window.Eval(string.Format("top === {0}", windowPath));
17: windowPath += ".parent";
18:
19: } while (!windowIsTop && (xrmObject == null));
20:
21: // Get a reference to the Xrm.Page object if found
22: ScriptObject XrmPageObject = null;
23: if (xrmObject != null)
24: {
25: XrmPageObject = xrmObject.GetProperty("Page") as ScriptObject;
26: }
27:
28: // TODO: Get references to the child objects of Xrm.Page using the GetProperty method
29: // of ScriptObject
In summary, with the above snippet of code, the logic to access the Xrm.Page object of CRM can be encapsulated into a library that can be utilized regardless of how the target Silverlight application is being deployed in the CRM environment.
Posted by Nick Doriot on February 21, 2011 at 03:43 PM in CRM Development, Dynamics CRM 2011 | Permalink | Comments (0) | TrackBack (0)
February 09, 2011
Validating a Dynamic iFrame Form in Microsoft Dynamics CRM 2011
Recently I tied in a 3rd party site within a CRM 2011 instance. Part of the process involved choosing a Template which then provided a list of additional fields. The additional fields are dynamic and can be seen below:
Part One: Validating the Fields
The first step is to add a validation function within your iFrame. This you will need to customize to your form. I mimicked CRM 2011 and added a “req=?” attribute (0 = not required, 2 = required).
function validParams(){
var inputArray = document.getElementsByTagName("input");
for (var index = 0; index < inputArray.length; index++){
if (inputArray[index].type = 'text'){
if (inputArray[index].attributes["req"].value == "2"){
// field is required so make sure it is populated
if (inputArray[index].value == "") {
// in this example, the id attribute is also the label
alert(inputArray[index].id + ' is a required field.');
return false;
}
}
}
}
return true;
}
Part Two: Handling the OnSave
The next step is to add an OnSave function, in this case I’m calling it onNewSave().
function onNewSave(){
// on create
if (Xrm.Page.ui.getFormType() == 1 ){
// grab our iFrame
var ifrm = document.getElementById('IFRAME_parameters');
try {
if (!ifrm.contentWindow.validParams()){
// a required field was not populated, so do not save
event.returnValue = false;
return false;
}
} catch (err) {}
}
}
Let me further elaborate two portions. First this line is important because the iFrame is an HTML web resource but you can only tie to the JavaScript web resources within the Form Properties.
if (!ifrm.contentWindow.validParams()){
Secondly, the most important portion of the code is canceling the OnSave event. Both of these lines are needed for the event to cancel:
event.returnValue = false;
return false;
Cancelling an OnSave event could also be quite useful when you simply need custom validation through JavaScript. Hopefully if you ever come across the need, this will make it somewhat easier to prevent the form from saving until everything is validated to your liking.
Posted by Paul Way on February 09, 2011 at 08:12 AM in CRM Development | Permalink | Comments (0) | TrackBack (0)
Technorati Tags: dynamic form, iframe, Microsoft Dynamics CRM 2011




Recent Comments