Wednesday, September 29, 2010

Marketing List History

Greetings guys,

Last week I worked on a very strange requirement to implement some kind of a marketing list auditing mechanism. The customer wants to be able to track which user added particular contact in a given marketing list.
Since the list <–> contact relationship is many-to-many and therefore the relations are stored in a third table (called ListMemberBase in this case), there is no event available to trigger a workflow or do something out of the box. Technically nor the list is updated neither the contact. Since I didn’t have much time to do this I decided to use database trigger to achieve the goal. Yes it is unsupported but it works very fast and nice so I decided to share it.
I first created a custom field of type ntext called Marketing List History (new_marketinglisthistory) on the Contact Entity. Added this to the contact form.
Then created the following trigger:

USE [XXX_MSCRM]
GO
/****** Object: Trigger [dbo].[trg_ListHistory] Script Date: 09/28/2010 17:09:44 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[trg_ListHistory]
ON [dbo].[ListMemberBase]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
if (select EntityType from inserted) = 2
begin
if (select New_MarketingListHistory from Contact, inserted where Contact.contactid=inserted.entityid) is null
begin
UPDATE Contact
SET New_MarketingListHistory = (select l.createdbyname+' --> '+listname from listmember l inner join list m on l.listid=m.listid where l.ListMemberId=inserted.ListMemberId)
FROM inserted
WHERE Contact.contactid = inserted.entityid
end
else
begin
UPDATE Contact
SET New_MarketingListHistory = (select l.createdbyname+' --> '+listname + char(13) from listmember l inner join list m on l.listid=m.listid where l.ListMemberId=inserted.ListMemberId) + (select New_MarketingListHistory from Contact where Contact.contactid=inserted.entityid)
FROM inserted
WHERE Contact.contactid = inserted.entityid
end
end
END

Done! Here is the result:


The last addition in a given marketing list goes on top of the history.
You can also put this column in the marketing list view – all members to look like this:


Regards!
Rinshwind

Thursday, September 23, 2010

MS CRM 3.0: Set email default font

 Hello again,

 I've been working recently on a way to set the default font within the CRM 3.0 email message edit field. The default font is Tahoma 10, however, a different font was needed (in this code, Arial). From the 3 specified font families, Internet Explorer will use the first one available on the system. As those 3 fonts are rather mundane, it's unlikely they will not be available. Nonetheless, should you decide to use a special font, make sure it's available on each system the code will run on.

 My method uses the "subject" field in order to determine if the form is used to view an email or to edit a new email / a reply email / a forwarded email. It was enough to achieve my goal on this task. The thing about IFrames is that you're not able to determine exactly when they've finished loading. I have attempted to use the onLoad event of the IFrame, however that lead to nothing viable.

 Thus, it came down to the same old workaround of using a timer to make a check against the readyState of the IFrame object.

 The amount of time set on the timers is really up to you - it was enough for my objectives, you might need a quicker / slower time between checks. If the IFrame is not yet loaded, the method sets another timer to make another call attempt later. That, off course, only if the email is in edit mode (the "subject" field is editable).

 Please note that this is not tested on CRM 4.0. I am not aware if the IFrame structure is different than that used in 3.0, therefore I cannot claim it might work on 4.0 as well. ;-)



 Cheers!



Here is the code (in my case, it was placed within the onLoad code of the Email entity):

---------------------
function forceFontFormat(parentNode, fontFamily, fontSizeInPoints, fontSizeInIndex)
{
    if(parentNode)
    {
        try
        {
         var tagName = '';
         if(parentNode.tagName)
         tagName = parentNode.tagName.toLowerCase();
         switch (tagName)
         {
         case 'div':
         case 'p':
         case 'span':
            parentNode.style.fontFamily = fontFamily;
                 parentNode.style.fontSize = fontSizeInPoints + 'pt';
         break;
         case 'font':
         parentNode.setAttribute('face', fontFamily);
         parentNode.setAttribute('size', fontSizeInIndex);
         break;
         }
        }
        catch (err) { }
        if((parentNode.childNodes) && (parentNode.childNodes.length > 0))
        {
            for(var i = 0; i < parentNode.childNodes.length; i++)
            {
                forceFontFormat(parentNode.childNodes[i], fontFamily, fontSizeInPoints, fontSizeInIndex);
            }
        }
    }
}


function forceDefaultCrm30EmailFontFamily()
{
    try
    {
        var isEditable = false;
        try
        {
            var emailSubject = document.getElementById("subject");
            isEditable = emailSubject.isContentEditable;
        }
        catch (secondErr) {}
        if ( isEditable == true)
        {
            var iframe = document.getElementById("descriptionIFrame");
            var forceFont = false;
            try
            {
                if(iframe.contentWindow.document.readyState == "complete")
                    forceFont = true;
            }
            catch (thirdErr)
            {
                forceFont = false;
            }
            if(forceFont == true)
            {
                forceFontFormat(iframe.contentWindow.document.body, 'Arial', '10', '2');
            }
            else
            {
                setTimeout(forceDefaultCrm30EmailFontFamily, 1000);
            }
        }
    }
    catch (firstErr)
    {
        /*alert("CRM Development\n\nThis warning does not affect your work, please ignore it.\nThank you!\n\n\nError message (see below):\n\n" + err.description);*/
    }
}


// Method call
setTimeout(forceDefaultCrm30EmailFontFamily, 1000);

Wednesday, September 15, 2010

Another way of loading certain CRM Views into an IFRAME on the main form

Since every CRM View has its own GUID and can be launched using the Advanced Find out-of-the-box functionality, you can follow several easy steps to load what view you want (with whatever additional filters you want) in a certain IFRAME available on the crmForm.
Here's the scenario: you wanna display your active contacts (My Active Contacts view) in a separate TAB on your account entity form.
- find out the id and objecttypecode of your view;
- create a new TAB and IFRAME on the form of the account entity;
- dynamically set the IFRAME URL as following:
http://[crm_server_name]/[ORGANIZATION_NAME]/AdvancedFind/AdvFind.aspx?EntityCode=[CRM_VIEW_ENTITY_CODE]&QueryId=[CRM_VIEW_ID]&ViewType=1039&AutoRun=True (for our scenario, it would be something like http://CRMSERVER/MyOrganization/AdvancedFind/AdvFind.aspx?EntityCode=2&QueryId={FC3A5B78-1AA4-10C4-1234-12C45B78F01A}&AutoRun=True )
Warning: you must type "AutoRun=True" using this casing. lower/upper case won't trigger the Advanced Find to auto run (and that because the parsing of that parameter is as it is).

There you go - your custom filtered view available anywhere you want on any form :)

Sunday, April 25, 2010

CRM 3.0: Simple button next to a given field

Hello!

The code below generates a simple button next to a given input field (let's say, for instance, the Subject field on a task record). As simple as that.

This is how a standard button is displayed by CRM. The basic magnifying glass on a lookup type field is an image from the standard images CRM comes with (so no, it's not a real button, like those on a Windows Forms application). If you would like that sort of a "button", you'd have to replace the code from the "// Create a new button" line down to the "// Place the button inside the second table TD" line. So, instead of a button INPUT element, you'd create an image field, with onMouseOver and onMouseOut events defined, so it would behave like a "real" button. ;-)

The code and the call:


function attachCRMbutton(currentfieldName)

{
    try
   {
        // Get the parent DIV of the given field
        var parentObject = document.getElementById(currentfieldName).parentNode;
        // Create a holding table
        var myTable = document.createElement("Table");
        // The table should fill the entire surrounding DIV element
        myTable.setAttribute('width', '100%');
        // Add one row to our table
        var myRow = myTable.insertRow(0);
        // Create the TD elements
        var originalChildNodesArea = myRow.insertCell(0);
        var buttonArea = myRow.insertCell(1);
        // For this example, a width of 75 pixels is enough to enclose the "Open address"
        buttonArea.style.width = "75";
        // Place all the original child nodes inside the first table TD
        for(var i = 0; i < parentObject.childNodes.length; i++)
        {
            var myChild = parentObject.childNodes[i];
            originalChildNodesArea.appendChild(myChild);
        }
        // Attach the table to the DIV element
        parentObject.appendChild(myTable);
        // Create a new button
        var myButton = document.createElement('input');
        // Set the open action
        function launchURL()
        {
            window.open('http://crmstuff.blogspot.com');
            return true;
        }
        // Set the CRM-like attributes
        myButton.setAttribute('id', 'linkButton_' + currentfieldName);
        myButton.setAttribute('class', 'txt');
        myButton.setAttribute('type', 'button');
        myButton.setAttribute('maxLength', '50');
        myButton.setAttribute('value', 'Open address');
        myButton.setAttribute('req', '0');
        myButton.attachEvent('onclick', launchURL);
        // Place the button inside the second table TD
        buttonArea.appendChild(myButton);
        // Done
    }
    catch (e) {}
}

// Method call
attachCRMbutton('subject');
 
Good luck and good hunting!

Thursday, December 31, 2009

MS CRM 3.0 "Expected '}'" and "Object expected" errors

Hello, there :-)

Have you ever encountered this situation? You write some decent, valid JS code, throw it inside a CRM field event box, Save & Close x2, publish, test, BAM!-error?

Well, provided your code is actually correct (no logical, typing or access errors), you should check the next thing. In CRM 3.0 at least (not having a 4.0 box around atm., feel free to test if you're kind enough) if you load a piece of code having its last line using a line comment, you're in for trouble. I was lucky to find that out while debugging a whole page and noticed that the line comment I was using was extended to the rest of the CRM JS code (due to CRM placing the CATCH(e){} part of the TRY{} block right on the same line with the last line of my code). That was causing havoc to the entire page.

So, remember this hint: never leave the last line of code having a line comment ( "//" ) or you're in for trouble :D

Cheers!

Friday, December 18, 2009

CRM 4.0 Server - Hardcore Installing

There are moments when installing the Microsoft CRM 4.0 Server can be a real pain, if your Organization doesn't offer you full "trust" in its AD (Active Directory). After some digging, finally found a working solution (start to end :P):

step 1: Find a nice Active Directory Admin to manually create 5 groups for you in the Organization Node, with full privileges for the username that you will install the CRM 4 Server with:
PrivUserGroup
PrivReportingGroup
ReportingGroup
SQLAccessGroup
UserGroup


step 2: Create a custom precreateconfig.xml file that will look like this (ask the same friendly guy from first step to assist you with this):
<crmsetup><server><groups autogroupmanagementoff="true"><privusergroup>CN=PrivUserGroup,OU=Company Name,OU=Company Name,DC=<domain>,DC=<domain_extension></privusergroup> <sqlaccessgroup>CN=SQLAccessGroup,OU=Company Name,OU=Company Name, DC=<domain>,DC=<domain_extension></sqlaccessgroup> <usergroup>CN=UserGroup,OU=Company Name,OU=Company Name,DC=<domain>,DC=<domain_extension></usergroup> <reportinggroup>CN=ReportingGroup,OU=Company Name,OU=Company Name, DC=<domain>,DC=<domain_extension></reportinggroup> <privreportinggroup>CN=PrivReportingGroup,OU=Company Name,OU=Company Name, DC=<domain>,DC=<domain_extension></privreportinggroup> </groups></server></crmsetup>

step 3: Go Start > Run > cmd (Command Prompt) and type:
<Drive:>\CRM 4 Kit\Server\i386\SetupServer.exe /config <Drive:>\Path_to_your_creation\precreateconfig.xml

step 4:
Pray for no further random errors while enjoying your progress bars @ Setup Screen:

step 5: After setup is completed successfully, ask the same kind AD Admin to re-add the CRM Administrator (the one you installed with) in the PrivUserGroup (with Full Privileges). See step 1 for details...
step 6: Go http://<hostname>:<port>/ and enjoy your CRM (now you will most likely have the right to finally use the product you <paid> for :)
step 7: ???
step 8: Profit!

For a detailed article about 90% of this issue, you can study this MSDN KB article.

Also, if your setup failed and cannot be uninstalled/repaired, while experimenting OTHER ways of installing than the one described above, I recommend you study this MSDN KB article as well. You gotta try this one once if you wanna train your fingers in deleting keys in RegEdit @ light speed...

Cheers and may the patience be with you :)

Wednesday, December 16, 2009

WSDL Error: "Schema item 'element' named 'string' from namespace 'http://schemas.microsoft.com/crm/2006/WebServices'. [...]"

Hey,

Have you ever encountered the following error?
"Schema item 'element' named 'string' from namespace 'http://schemas.microsoft.com/crm/2006/WebServices'. The global element 'http://schemas.microsoft.com/crm/2006/WebServices:string' has already been declared."

That's what I got when I update my webservice references. Totally nagging and a real pain. That error prevented me from being able to access all the custom fields I created on the CRM entities.
Silly as it might sound, the sole answer to this problem was to take a deep breath (no, no Skype chat), backup the WSDL file and perform some orcish style surgery.

My troubling line was this:
<s:element name="SecurityPrincipal" nillable="true" type="s2:SecurityPrincipal">
<s:element name="string" nillable="true" type="s:string">
<s:element name="TargetFieldType" type="tns:TargetFieldType">

Make sure you have a backup to the original WSDL file, then nuke (a.k.a. "remove") the red-highlighted line and save the changes to the WSDL file (in my case, it was the WSDL file corresponding to the CRM SDK service). Unless there are other errors, you will be able to build your project and your customizations will be available within Visual Studio.

Good luck and happy coding afterwards!
Cheers!

Wednesday, December 02, 2009

How to extract Email attachments IDs (CRM 3.0)

Howdy,

At some point, I was requested by a manager to extend the functionality of the email entity (multiplying the number of messages sent) and I came across the attachment side. The following function is my solution to the occurring issue:

// Author: Octavian Cucuta ( octavian.cucuta [ AT ] gmail.com )

// Release: 1.0.0.1 ( 2nd of December 2009 ) for MS CRM 3.0
// * Gets the IDs of the uploaded attachments, as a string constant with IDs separated by pipes '|' characters
function GetEmailAttachmentsIDs()
{
// Returning results as a string, but locally storing data into an array, for a more practical approach
var result = Array();
// Encasing it all within a TRY{}CATCH{} block, to prevent unwanted errors
try
{
// Get table containers
var myTables = document.getElementsByTagName("table");
// Since MS did not specify a unique ID or name for the table,
// checking which one has the correct 'oname' tag.
// Tip: on email, the table we need is the 48th
for(var i = 0; i < myTables.length; i++)
{
if(myTables[i].oname == "1001")
{
// Get attachment IDs
var index = 0;
for(var j = 0; j < myTables[i].rows.length; j++)
{
result[index] = "";
result[index++] = myTables[i].rows[j].oid;
}
// Cancelling the processing loop here
break;
}
}
}
catch (err)
{
// In case something went wrong, instead of providing a partial set of attachments, return none
result = Array();
}

// Format output as a single string constant, separated by pipes
return result.join('|');
}



// Usage: place this function inside the needed event code body, then use the following call
var ids = GetEmailAttachmentsIDs();

Monday, October 20, 2008

JavaScript code reuse in CRM

In most of the cases, you don't want to copy-paste your custom methods in your CRM events source code each time you need them.

So, here's an easy approach on javascript code reuse in CRM.

First of all, make sure you have these 2 files: LargeNumber.js and StringsExtended.js located in a custom folder - named CRMStuff - that is also located in the root folder of the Microsoft Dynamics CRM server application (the relative paths to the files should be "/crmstuff/LargeNumbers.js" and "/crmstuff/StringsExtended.js"). As you can see, these 2 files contain custom methods.

Click here to download the source code that allows you to load your *.js files at runtime.

Monday, September 08, 2008

Welcome Tavi

Let's give a big welcome to our newest author - Mr. Octavian Cucuta on CRM Stuff blog. He promises a lot :P