Skip Ribbon Commands
Skip to main content
SharePoint

Dynamics Blog

apr 16
functie om een script dynamisch te laden

Als script afhankelijk is van een ander, gebruik:

​// dynamically loads script
function LoadFile(url) {
    var httpRequest = new ActiveXObject("Msxml2.XMLHTTP");
    httpRequest.open("GET", url, false);
    httpRequest.setRequestHeader("Content-Type", "Text/xml; charset=utf-8");
    httpRequest.send(null);
    return httpRequest.responseText;
}

apr 20
Linq to CRM
This way, querying to CRM is so easy and understandable.
 
Using this together with the crm toolkit (http://code.msdn.microsoft.com/E2DevTkt)
some casting must be done because linqtocrm does not work out of the box with the toolkit. It does however with the CRM SDK. Using anonymous classes, the SDK entities are casted to the toolkit entities like:
 
AddressValidator target = new AddressValidator(this.ServiceAdapter);
CrmQueryProvider p = new CrmQueryProvider(this.ServiceAdapter);
var accounts = from c in p.Linq<account>()
               select new Account()
               {
                   Id = c.accountid,
                   Address1Country = c.address1_country,
                   Address1Postalcode = c.address1_postalcode
               };
 
Now we have an array of Account (not account)!
jan 15
TODO: Create a HTTPmodule so that CRM will have a different look and feel
Just as a reminder for me... How about creating a httpmodule which changes the output of CRM without altering original CRM-code. this could be done to have the test-environment a different look-and-feel as to the production environment. This way it will be much clearer for users in which environment they are operating!
See the yellow background in this example image:
Background yellow
dec 29
CRM 4.0, ISV webapp showing from MS-outlook client

We have build a nice webapp which replaces the standard CRM calendar and have put this app in the ISV folder according to the SDK. This works fine in CRM from internet explorer so the default calendar is replaced as expected. However, when this webapp is shown in outlook client and when a “new record” is chosen from the menu bar, 

clip_image002[6]

there is an annoying javascript message saying:

clip_image002

I dig the DOM for a clue but this is somewhat harder from outlook client then IE. I found interestingly though that global.js is not loaded! It will get loaded after the messages are clicked away despite the answer and the expected window oddly shows up! But this is not acceptable, so i just added global.js and windowinformation.aspx to be loaded up front. I  also  added  some  variables  and  set  their  values  which  are -apparently- also necessary. So the final addings are:

<script type="text/javascript" src="/_static/_common/scripts/global.js"></script>
<script type="text/javascript" src="/_common/windowinformation/windowinformation.aspx"></script>

<script type="text/javascript">
    //These 5 settings are necessary for the outlookclient to work correctly
    var IS_PATHBASEDURLS = true;
    var ORG_UNIQUE_NAME = 'Berenschot';
    var LOCID_DIALOG_OFFSET_WIDTH = '0';
    var LOCID_DIALOG_OFFSET_HEIGHT = '0';
    var LOCID_UI_DIR = 'LTR';

</script>

Windowinformation.aspx is new in CRM 4.0. It adds dynamic javascript which holds information about window sizes.

Thanks for you’re time and attention to read this article!

dec 07
Add copy-item to context sensitive menu, JQuery to the resque.

Very odd, everywhere in CRM, users can select text and copy it to the clipboard. Why not in the notes screen? This post is about creating a new item to the (right click)-menu which copies text to clipboard.

I used jquery (http://jquery.com/) for the inserting of a new element. The result is:

Menu

The insertion takes place in the CRM file notesdata.aspx. Changing this CRM-file is not supported by Microsoft. Therefore, updates of CRM will override this change. Allways be aware of this issue whenever you change files like these.

The exact jquery line is this simple one:

$(document).ready(function() {
$("#divMenu").append($("<div class='item' onclick='NotesTable.CloseContextMenu();copyToClipboard(NotesTable.CurrentNote);'>Kopieren</div>"));
})

It says something like find the div with id ‘divMenu’ and append a div with class ‘item’ to it. The icon is default and suits for me just fine. The copyToClipboard is just a simple javascript line:

function copyToClipboard(trElement) {
var opmerkingText = trElement.nextSibling.nextSibling.firstChild.firstChild.innerText;
if( window.clipboardData && clipboardData.setData )    {
   clipboardData.setData("Text", opmerkingText);
}

Once again be aware of changes like these, which are being overridden when going to a new CRM version.

nov 20
Makkelijk meerdere attributen searchable aan/uit zetten en zichtbaarheid aan/uit zetten
  • Exporteer de entiteit in kwestie.
  • Zoek het veld in de customization xml
  • Verwijder ValidForAdvancedFind en/of ValidForGrid voor respectievelijk searchable en zichtbaarheid in results-view
  • Importeer daarna de customization.
sep 24
Deployment perikelen van CRM
Na de hele solution inclusief plugin te hebben overgezet naar produktie, ontstond bij testen van de plugin de HTTP fout 404, not found. Plugin code werd wel uitgevoerd want er waren eventlog meldingen. Maar waarom dan deze webexception en waarom wordt de webservice niet gevonden?
IIS1 start
 
Er is een extra webservice die op basis van postcode huisnummer, het adres opzoekt. Deze was op poort 80, localhost gezet. Uit IIS log blijkt echter dat CRM voor het aanroepen van de interne webserice gaat naar: http://localhost/MSCRMservices/2007/metadataservice.asmx en dat is deze postcode-webservice! Dat moet dus anders en kan op 2 manieren. Ofwel 1) portnummer wijzigen van de webservice ofwel 2) de hostheader invullen en ook op te geven in host-file in c:\windows\system32\driver\etc\hosts.
Gekozen is voor optie 2).
hostheader invullen
 
 
Resultaat van het wijzigen van de hostheader van de webservice:
result
 
Ook toevoegen van de regel: 127.0.0.1 postcodeservice aan de 'host'-file, en daarmee moet het gaan dan gaan werken...
 
Echter nu komt het systeem met melding HTTP 400, bad request!??! We zijn een stapje verder maar toch..
Blijkt een bug in CRM. CRM gaat intern altijd naar localhost. Dit is opgelost in de nieuwste rollup of volgende hotfix. Localhost is echter verwijderd in IIS door het veranderen van de hostheader van de webservice. Intypen van http://localhost heeft dan geen zin en de browser geeft HTTP400 terug als resultaat.
De oplossing is om localhost toe te voegen aan de CRM-site.
localhost toegevoegd aan CRM
 
JoeHoe het werkt!
 
 
aug 13
Help! Een exception en ben mijn backup vergeten
Heb ik weer. Vergeet ik eerst een backup te maken van een entiteit voordat ik dezelfde entiteit importeer vanaf een andere CRM 4.0 -instantie en nu verschijnt de algemene foutmelding dat er iets heel ernstigs is misgegaan en 'you're on you're own'!
 
Wat nu...
Het gaat om de volgende melding verschijnt:
Exception
 
Blijkt een (beginners) simpele fout. Bij aanpassen van de entititeit zie je in het form dat de lijn rondom attributen op het form niet doorgetrokken zijn. Dit is een aanleiding voor de oplossing. Zie volgende image:
Aanleiding
 
Nu moet het minder moeilijk zijn om dit op te lossen. Voeg het attribuut toe dat ontbreekt. Zie:
oplpossing
 
and you're done.
jun 15
Handig javascript om allerlei CRM calls te doen aan client-side.
Om attributen van entiteiten client-side van CRM 4.0 te lezen, gebruik ik veelal:
 
function GetAttributeValueFromID(sEntityName, sGUID, sAttributeName, sID)
{
    var xml = "" +
    "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
    "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">" +
    GenerateAuthenticationHeader() +
    "  <soap:Body>" +
    "    <RetrieveMultiple xmlns=\"http://schemas.microsoft.com/crm/2007/WebServices\">" +
    "      <query xmlns:q1=\"http://schemas.microsoft.com/crm/2006/Query\" xsi:type=\"q1:QueryExpression\">" +
    "        <q1:EntityName>"+sEntityName+"</q1:EntityName>" +
    "        <q1:ColumnSet xsi:type=\"q1:ColumnSet\">" +
    "          <q1:Attributes>" +
    "            <q1:Attribute>"+sAttributeName+"</q1:Attribute>" +
    "          </q1:Attributes>" +
    "        </q1:ColumnSet>" +
    "        <q1:Distinct>false</q1:Distinct>" +
    "        <q1:PageInfo>" +
    "          <q1:PageNumber>1</q1:PageNumber>" +
    "          <q1:Count>1</q1:Count>" +
    "        </q1:PageInfo>" +
    "        <q1:Criteria>" +
    "          <q1:FilterOperator>And</q1:FilterOperator>" +
    "          <q1:Conditions>" +
    "            <q1:Condition>" +
    "              <q1:AttributeName>"+sID+"</q1:AttributeName>" +
    "              <q1:Operator>Equal</q1:Operator>" +
    "              <q1:Values>" +
    "                <q1:Value xsi:type=\"xsd:string\">"+sGUID+"</q1:Value>" +
    "              </q1:Values>" +
    "            </q1:Condition>" +
    "          </q1:Conditions>" +
    "        </q1:Criteria>" +
    "      </query>" +
    "    </RetrieveMultiple>" +
    "  </soap:Body>" +
    "</soap:Envelope>" +
    "";
    var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
    xmlHttpRequest.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
    xmlHttpRequest.setRequestHeader("SOAPAction","http://schemas.microsoft.com/crm/2007/WebServices/RetrieveMultiple");
    xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
    xmlHttpRequest.send(xml);
    // retrieve response and find attribute value
    var result = xmlHttpRequest.responseXML.selectSingleNode("//q1:" + sAttributeName);
    if (result == null)
        return "";
    else
        return result.text;
}

 
Echter om in 1 call direct een aantal kolommen op te halen, werkt hierbij niet. Je kunt dan zelf uitviolieren wat dan te doen, maar ik kwam dit blogje tegen waarmee dit en direct ook updates en deletes uit te voeren waarbij plugin's ook gewoon werken.
 
Werkt goed. Op mijn CRM machine hoef ik nu niet 5 calls te doen. Ik kan nu met 1 call af wat goed te merken is kwa performance. Het scriptje is hier te downloaden.
sep 17
Creating a central Country table and type ahead box
One of the questions that was asked several times, is if it is possible to have a central Country table within CRM.
As it is not possible to have multiple relations between two entities (contact for example has two country fields in the address part and you might have a field like nationality), a custom entity by itself does not solve the problem.
Working with picklists requires to keep the values of the different picklists in sync and it's not the most user friendly way to select a country.
 
In search for a solution for this problem I came across Joris Kaltz's blog where he posted an example for a type ahead country field. In this great example he showed how to create the type ahead box, but it was filled with a static list. Then I remembered reading Arash Ghanaie's post on retrieving CRM data through CRM Webservices.
 
Combine the two and you can read the country values from your own country table!!!
 
Here's the result for the onload script of the form:

--------------------------------

function SuggestionTextBox(textfield, method, preload)

{   // max items in the suggestion box

   var maxItems = 6;

   this.suggestionList = new Array();

   this.suggestionListDisplayed = new Array();

   var actual_textfield = textfield;

   var actual_value = '';

   var selectedNumber = 0;

   var countMatches = 0;

   if (preload)

   {

       // load the data via external method

       this.suggestionList = method();

   }

   // attach this function to the textfield

   textfield.attachEvent("onfocus", initTextfield);

   // init textfield and attach necessary events

   function initTextfield()

   {

       // when leaving the field we have to clear our site   

       textfield.attachEvent("onblur", resetTextfield);

       document.attachEvent("onkeydown", keyDown);

   }

   function resetTextfield(e)

   {

       //when leaving the field, we have to remove all attached events

       document.detachEvent("onkeydown", keyDown);

       textfield.detachEvent("onblur",resetTextfield);

   }

   function keyDown(e)

   {

       keyCode = e.keyCode;

       switch (keyCode)

       {

           case 9: case 13:

               // enter & tab key

               if (countMatches > 0)

                 itemSelected();

               break;

           case 38:

               //pressing up key

               if(selectedNumber > 0 && countMatches > 0)

               {

                   selectedNumber--;

                   createSuggestionTable();

               }

               return false;

               break;

           case 40:

               // pressing down key

               if(selectedNumber < countMatches-1 && countMatches > 0 && selectedNumber < maxItems)

               {

                   selectedNumber++;

                   createSuggestionTable();

               }

               return false;

               break;               

           default:

               // do not call the function to often

               setTimeout(

                           function()

                           {

                               executeSuggestion(keyCode)

                           },  200 /* in ms */

                          );

               break;

       }

   }

   function itemSelected()

   {

       actual_textfield.value = suggestionListDisplayed[selectedNumber];

       if (document.getElementById('suggestion_table') != null)

       {

          document.body.removeChild(document.getElementById('suggestion_table'));

       }

   }

   function executeSuggestion(keyCode)

   {

       selectedNumber = 0;

       countMatches = 0;

       actual_value = textfield.value;

       //todo add keyCode

       // get all possible values from the suggestionList

       if (!preload)

       {

           // load the data via external method

           // todo add some caching function

           this.suggestionList = method();

       }

       // using regular expressions to match it against the suggestion list

       //var re = new RegExp(actual_value, "i");

       //if you want to search only from the beginning

       var re = new RegExp("^" + actual_value, "i");

       countMatches = 0;

       this.suggestionListDisplayed = new Array();

       // test each item against the RE pattern

       for (i = 0; i < this.suggestionList.length; i++)

       {

           // if it matche add it to suggestionListDisplayed array

           if (re.test(this.suggestionList[i]) && actual_value != '')

           {

               this.suggestionListDisplayed[countMatches] = this.suggestionList[i];

               countMatches++;

               // if there are more values than in maxItems, just break

               if (maxItems == countMatches)

                   break;

           }

       }

       if (countMatches > 0)

       {

           createSuggestionTable();

       }

       else

       {

           if (document.getElementById('suggestion_table'))

           {

               document.body.removeChild(document.getElementById('suggestion_table'));

           }

       }

   }

   function createSuggestionTable()

   {

       if (document.getElementById('suggestion_table'))

       {

           document.body.removeChild(document.getElementById('suggestion_table'));

       }

       // creating a table object which holds the suggesions

       table = document.createElement('table');

       table.id = 'suggestion_table';

       table.width = actual_textfield.style.width;

       table.style.position= 'absolute';

       table.style.zIndex = '100000';

       table.cellSpacing = '1px';

       table.cellPadding = '2px';

       topValue = 0;

       objTop = actual_textfield;

       while(objTop)

       {

           topValue += objTop.offsetTop;

           objTop = objTop.offsetParent;

       }

       table.style.top = eval(topValue + actual_textfield.offsetHeight) + "px";

       leftValue = 0;

       objLeft = actual_textfield

       while(objLeft)

       {

           leftValue += objLeft.offsetLeft;

           objLeft = objLeft.offsetParent;

       }

       table.style.left = leftValue + "px";

       table.style.backgroundColor = '#FFFFFF';

       table.style.border = "solid 1px #7F9DB9";

       table.style.borderTop = "none";

       document.body.appendChild(table);

       // iterate list to create the table rows       

       for ( i = 0; i < this.suggestionListDisplayed.length; i++)

       {

               row = table.insertRow(-1);

               row.id = 'suggestion_row' + (i);

               row.attachEvent("onclick", selectedwithmouse);

               column = row.insertCell(-1);

               column.id = 'suggestion_column' + (i);

               if (selectedNumber == i)

               {

                   column.style.color = '#ffffff';

                   column.style.backgroundColor = '#316AC5';

               }

               else

               {

                   column.style.color = '#000000';

                   column.style.backgroundColor = '#ffffff';

                   row.attachEvent("onmouseover", hoverwithmouse);

               }

               column.style.fontFamily = 'Tahoma';

               column.style.fontSize = '11px';

               column.innerHTML = this.suggestionListDisplayed[i];

       }

   }

   function hoverwithmouse()

   {

       var itemNumber = event.srcElement.id.replace('suggestion_column','');

       if (!isNaN(itemNumber))

       {

           selectedNumber = itemNumber;

           createSuggestionTable();

       }

   }

   function selectedwithmouse()

   {

       var itemNumber = event.srcElement.id.replace('suggestion_column','');

       if (!isNaN(itemNumber))

       {

           selectedNumber = itemNumber;

           itemSelected();

       }

   }

   // return object

   return this;

}

 

 

function AccessCRMWebServices()

{

    var serverUrl = "http://crmdemo:5555/mscrmservices/2006";

    var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");

    xmlhttp.open("POST", serverUrl + "/crmservice.asmx", false);

    xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8")

    xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2006/WebServices/RetrieveMultiple")

    xmlhttp.send("<?xml version='1.0' encoding='utf-8'?>"+"\n\n"+"<soap:Envelope"+

    ' xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"'+

    ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'+

    ' xmlns:xsd="http://www.w3.org/2001/XMLSchema">'+

    ' <soap:Body>' +

    ' <query xmlns:q1="http://schemas.microsoft.com/crm/2006/Query" xsi:type="q1:QueryByAttribute" xmlns="http://schemas.microsoft.com/crm/2006/WebServices">'+

    ' <q1:EntityName>new_country</q1:EntityName>'+

    ' <q1:ColumnSet xsi:type="q1:ColumnSet">'+

    ' <q1:Attributes>'+

    ' <q1:Attribute>new_name</q1:Attribute>'+

    ' </q1:Attributes>'+

    ' </q1:ColumnSet>'+

    ' <q1:Attributes>'+

    ' <q1:Attribute>statecode</q1:Attribute>'+

    ' </q1:Attributes>'+

    ' <q1:Values>'+

    ' <q1:Value xsi:type="xsd:string">Active</q1:Value>'+

    ' </q1:Values>'+

    ' </query>'+

    ' </soap:Body>'+

    ' </soap:Envelope>')

    var result = xmlhttp.responseXML.xml;

 

    //Separate the BusinessEntities XML tag

    var BEs= result.split("<BusinessEntities>");

    //Separate the BusinessEntity XML tag

    var BE = BEs[1].split("<BusinessEntity");

 

    //Walk through each Business Entity tag and extract account names

    var landenstring = ""

    for (i = 1; i < BE.length; i++)

    {

        first = BE[i].indexOf("<new_name>")+10;

        second = BE[i].indexOf("</new_name>");

        var s =  BE[i].substring(first,second);

        landenstring = landenstring + BE[i].substring(first,second) + ",";

    }

    landenstring = landenstring.substring(0, landenstring.length - 1);

    var landenArray = landenstring.split(",");

    return landenArray;

}

 

var obj = SuggestionTextBox(document.getElementById('address1_country'), AccessCRMWebServices, true);

--------------------------------
 
In the example above, we've created a new entity called new_country and put the name of the country in the new_name field. There's a check on the status of the records, so only active country's will be in the type ahead box.
 
* Thanks go out to my colleague Michel van Duijse who helped me out with splitting the result of the webservicecall.
1 - 10Next

 ‭(Hidden)‬ Admin Links