[CRM 2011] Handle the OnRefresh event on a subgrid with 1:N relationship

7. November 2011 09:11 by Renaud in   //  Tags:   //   Comments (4)

If you use a Subgrid to display a list of related entities, you would like to display a value related to this list. For example, let's imagine a Billing entity, which have a relation 1:N with a Detail entity. On the Billing form, we can display a subgrid containing the Details. We may also want to display a new field Total Amount on the form, which would  be the sum of all the Details. This field would  be readonly and automatically updated when a related Detail is modified. To calculate the value of this field, we should use plugins or workflows. Here is what the plugin solution would look like (assuming that we cannot change the billing related to a detail):

- Message: Create / Primary entity: Detail / Execution : Post-operation
- Message: Update / Primary entity: Detail / Attributes filter: Amount / Execution : Post-operation
- Message: Delete / Primary entity: Detail / Execution: Pre-operation

In each case, the plugin will retrieve the Billing related to the Detail. Then it will retrieve all the Details related to this Billing, and and calculate the new total amount. (Notice that for the Delete message, it's a bit different: First, the "Target" object in the InputParameters is an EntityReference and not an Entity. And then, if you use a post-operation step, you won't be able to retrieve the billing related to this Detail because it won't exist in the database. ) Now, the total amount field is correctly updated for each creation/modification/deletion of a Detail. However, it's not good enough! If you try to open a Detail from the subgrid of a billing form, change its amount value and save it, the Total amount of the billing form will stay unchanged, until you refresh the page! Here is a solution to make your form more dynamic :) Let's call the subgrid "Details"! We are going to use two events from this subgrid: onreadystatechange and onrefresh. The purpose of the following code is to retrieve the new total amount value each time the onrefresh event of the subgrid is triggered. The main problem is that the subgrid is loaded asynchronously! We will call the following method once the form is loaded. We will first check the state of the subgrid. If the state is "completed", then we can add an eventhandler to the onrefresh event. If it's not completely loaded, we have to wait. And for this, we will attach a function to the onreadystatechange. This function will check the new state of the subgrid and attach the UpdateTotalAmount function to the onrefresh event once the subgrid state will be complete.

function Subpolicies_OnReadyRefresh(){
    var targetgrid = document.getElementById("Details");

    // If already loaded
    if (targetgrid.readyState == 'complete')
    {
        targetgrid.attachEvent("onrefresh", UpdateTotalAmount);
    }
    else
    {
        targetgrid.onreadystatechange = function applyRefreshEvent() {
            var targetgrid = document.getElementById("Details");
            if (targetgrid.readyState == 'complete') {
                targetgrid.attachEvent("onrefresh", UpdateTotalAmount);
            }
        }
    }
}

Now you just have to implement the UpdateTotalAmount function to retrieve the Total amount of the Billing :) Sources: http://blog.xrm-services.co.uk/?p=150

[CRM 2011] Passer des valeurs à une Form CRM via l'URL

13. October 2011 16:41 by Renaud in   //  Tags:   //   Comments (0)

Vous savez sûrement tous comment ouvrir une Form à partir d'un peu de javascript, mais ce qui serait intéressant maintenant c'est de pouvoir y passer des valeurs! On peut par exemple vouloir créer une nouvelle entité, tout en initialisant certains champs avec des valeurs données: une référence à une entité, un statut, etc... Pour cela, il suffit d'ajouter ces paramètres directement dans l'URL de la Form que l'on veut ouvrir. Imaginons la situation suivante:  des entités Person et Family avec une relation One to many entre elles, ainsi qu'une autre relation entre deux entités Person pour pouvoir associer à chaque record Person un record parent!  Ce qu'on aimerait ici, c'est pouvoir ajouter un enfant à une entité personne, sans devoir lui ajouter une référence vers une famille! Cela devrait être fait implicitement, puisque logiquement l'enfant sera de la même famille que le parent. Pour atteindre cet objectif, on va simplement utiliser des paramètres. Vous pouvez lire la doc MSDN à ce sujet: Set Field Values Using Parameters Passed to a Form.

Pour rappel, voici quelques paramètres utiles lors de l'ouverture d'une Form.  

etn Le LogicalName de l'entité liée
pagetype entityrecord pour afficher un record en détail, et entitylist pour afficher une view.
extraqs optionnel: permet de passer des paramètres
id optionnel: pour les forms, il permet d'ouvrir la fenêtre d'édition d'une entité existante en précisant l'id du record

Le paramètre qui va nous intéresser réellement ici, c'est extraqs. Ce paramètre va vous permettre de passer des valeurs pour les champs de la fenêtre que l'on va ouvrir. Vous pouvez passer des données pour tout type de champs, et même les champs lookups! On va donc construire une chaîne de caractères! Pour faire simple, ça fonctionne comme ça: attribute=value La seule particularité concerne les champs lookups. Pour ceux-là, il est nécessaire de spécifier trois valeurs! Dans notre cas, nous allons par exemple remplir le champs new_family qui est une référence vers un objet de type Family. Les trois valeurs que nous allons devoir passer sont:

  • new_family: l'id du record Family
  • new_familyname: la valeur qui sera affichée dans le champs (en principe, la valeur du primary field du record Family mais vous pouvez décider d'afficher autre chose)
  • new_familytype: le type du record, donc new_family dans ce cas-ci.
En javascript, cela donnera ceci:
    var extRaqs = "";
    var family = Xrm.Page.getAttribute("new_family").getValue();
    if (policyid != null) {
        extRaqs += "&new_family=" + family[0].id;
        extRaqs += "&new_familyname=" + family[0].name;
        extRaqs += "&new_familytype=new_family";
    }
Une fois qu'on a nos paramètres, il reste à encoder cette chaîne, pour la passer dans l'url comme valeur de l'attribut extraqs!
var newFamilyURL = serverUrl + "/main.aspx?etn=new_family&pagetype=entityrecord&extraqs=";

newFamilyURL += encodeURIComponent(extRaqs);

window.open(newFamilyURL , "_blank", "width=900px,height=600px,resizable=1");
Utilisez de préférence la méthode encoreURIComponent() plutôt que encodeURI() parce que la première encode également les caractères suivants: ":", "/", ";", et "?" !

[CRM 2011] Pass values to a CRM 2011 through URL parameters

13. October 2011 09:10 by Renaud in   //  Tags:   //   Comments (2)

You probably already know how to open a CRM Form in javascript, but what you may want to know is how to set fields values in the same time. For example you may want to create a new entity and automatically initialize some of the fields! To achieve this, you just have to use URL parameters. Let's take an example! We have two entity types: Person et Family. Each Person has a reference to a Family record, and each Person has a list of children of type Person. What we would like to do here, is to have a button that allow us to create a new child related to this entity. This child will have a reference to the Family entity which is referenced by the parent Person. And this must be done implicitly of course. The user doesn't want to lose time with this task. So like I said earlier, we are going to use URL parameters. You can read the MSDN documentation here about this topic: Set Field Values Using Parameters Passed to a Form.

Let me remind you the main parameters that are used to open a Form in CRM 2011:  

etn The LogicalName of the concerned entity.
pagetype entityrecord to display the details form of an entity, and entitylist to display a view.
extraqs optional: use this parameter to pass values to the form
id optional: used with the entityrecord value of pagetype, when the id parameter is present, the corresponding record is displayed

extraqs is the parameter that we should focus on! This parameter is used to pass values to the form that we are going to open. You can pass values for any type of fields, and even lookup fields! To make it simple, this is how it works: attribute=value There is only a little trick for lookup fields. For those ones, you'll have to give three different values! What does this mean for the above example if we want to fill the new_family field which is a reference to a Family record. The three values that we have to pass are:

  • new_family: the id of the family record that we want to reference
  • new_familyname: the value that will be displayed in the field (usually it is the primary field of the referenced record, but actually you can put whatever you want to displya )
  • new_familytype: the type of the record: new_family.
In javascript, we will have to make something like this:
    var extRaqs = "";
    var family = Xrm.Page.getAttribute("new_family").getValue();
    if (policyid != null) {
        extRaqs += "&new_family=" + family[0].id;
        extRaqs += "&new_familyname=" + family[0].name;
        extRaqs += "&new_familytype=new_family";
    }
Once we have the parameters in a string, we can use a javascript method to encode it, and then use the result as a value for the extraqs parameter!
var newFamilyURL = serverUrl + "/main.aspx?etn=new_family&pagetype=entityrecord&extraqs=";

newFamilyURL += encodeURIComponent(extRaqs);

window.open(newFamilyURL , "_blank", "width=900px,height=600px,resizable=1");
You should use the encoreURIComponent() method instead of encodeURI() because the second one doesn't encode the following characters: ":", "/", ";", et "?" and it may result into errors!

[CRM 2011] Ouvrir une fenêtre à l'aide d'un bouton custom et d'un peu de javascript

10. October 2011 16:44 by Renaud in   //  Tags:   //   Comments (0)

Pour ouvrir une nouvelle fenêtre via un bouton custom, vous pouvez utiliser l'action URL lors de la customization du menu Ribbon, ou bien faire vous servir d'un peu de javascript! L'intérêt du javascript c'est que vous pourrez facilement passer des données à une autre Form via le paramètre extraqs, mais ça fera l'objet d'un prochain article :) Pour le moment on va se contenter d'ouvrir une nouvelle Form! Dans cet article, vous allez voir rapidement comment:

  • Ajouter du javascript à une form
  • Ouvrir une nouvelle fenêtre en javascript
  • Ajouter un bouton au menu ribbon d'une form
  • Appeler une fonction javascript lors d'un clic sur le bouton

1/ Ajouter une ressource web: javascript

Pour insérer une feuille de script dans une page, vous devez simplement ajouter une nouvelle ressource web. Vous pouvez le faire directement via la customization de la form dans laquelle vous voulez ajouter votre script. Vous pouvez soit ajouter une nouvelle ressource, soit en utiliser une existante. Ouvrez l'éditeur et ajoutez une nouvelle fonction, disons openNewForm().

2/ Ouvrir une fenêtre

Pour ça, rien de compliqué (c'est jamais compliqué au final quand on sait :) ). Vous trouverez pas mal d'infos ici : [MSDN] Open Forms, Views, and Dialogs with a URL. L'idée c'est qu'avec certains paramètres, vous pourrez facilement ouvrir les fenêtres désirées. Vous pouvez ouvrir des Form pour éditer ou créer une nouvelle entité, afficher des listes d'entités en spécifiant la View désirée, ou ouvrir des Dialogs.

function openNewForm() {

    var serverUrl;

    var errorMessage = "Context to retrieve the Server URL is not available.";
    if (typeof GetGlobalContext != "undefined") {
        serverUrl = GetGlobalContext().getServerUrl();
    }
    else {
        if (typeof Xrm != "undefined") {
            serverUrl = Xrm.Page.context.getServerUrl();
        }
        else {
            alert(errorMessage);
            return;
        }
    }
    if (serverUrl.match(//$/))
    { 
        serverUrl = serverUrl.substring(0, serverUrl.length - 1);
    }

    var recordUrl = serverUrl + "/main.aspx?";

    var params = "etn=new_student";

    params += "&pagetype=entityrecord";

    var accountURL = recordUrl + params;

    window.open(accountURL, "_blank", "width=900px,height=600px,resizable=1");
}

Concrètement ici, on construit l'URL de la fenêtre que l'on veut ouvrir en récupérant d'abord l'URL du serveur, et en y ajoutant les paramètres nécessaires. Si vous êtes dans CRM Online, n'oubliez pas ces lignes, l'URL retournée se termine par un "/" supplémentaire. Si vous ne l'enlevez pas, le menu Ribbon ne s'affichera pas!

if (serverUrl.match(//$/))
{ 
    serverUrl = serverUrl.substring(0, serverUrl.length - 1);
}

Quelques paramètres utiles:

 

etn Le LogicalName de l'entité liée
pagetype entityrecord pour afficher un record en détail, et entitylist pour afficher une view.
extraqs optionnel: permet de passer des paramètres
id optionnel: pour les forms, il permet d'ouvrir la fenêtre d'édition d'une entité existante en précisant l'id du record

Dans le code ci-dessus, on utilise les paramètres etn=new_student et pagetype=entityrecord sans préciser l'id, ce qui aura pour effet d'ouvrir la Form de création d'un nouvel étudiant.

3/ Ajouter un bouton au menu Ribbon

Pour ajouter un nouveau bouton au menu, commencez par créer une nouvelle solution dans CRM, et ajoutez-y l'entité pour laquelle vous voulez modifier le ribbon. (Dans mon cas j'ai fait ça sur une entité new_student, qui se trouvait déjà là dans mon organisation de test :) ) Exportez la solution, et éditez le fichier customizations.xml. Localisez la balise RibbonDiffXml.

<ImportExportXml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Entities>
    <Entity>
      <Name LocalizedName="Student" OriginalName="Student">new_student</Name>
      <ObjectTypeCode>10001</ObjectTypeCode>
      <EntityInfo>...</EntityInfo>
      <FormXml>...</FormXml>
      <SavedQueries>...</SavedQueries>
      <RibbonDiffXml>
        <CustomActions>
          <CustomAction Id="form.CreateOne.CustomAction"
                        Location="Mscrm.Form.new_student.MainTab.Collaborate.Controls._children"
                        Sequence="1">
            <CommandUIDefinition>
              <Button Id="form.CreateOne.Button" Command="form.CreateOne.Command" 
                      LabelText="Star" ToolTipTitle="Tip" 
                      LabelText="$LocLabels:CreateOne.LabelText" 
                      ToolTipDescription="$LocLabels:CreateOne.ToolTip" 
                      TemplateAlias="o1" />
            </CommandUIDefinition>
          </CustomAction>
        </CustomActions>

C'est pas le but de ce billet d'expliquer en détails la customization du Ribbon, mais donc le principe est d'ajouter une balise CustomAction, en précisant l'attribut Location pour indiquer où sera placé le bouton. dans la balise Button, renseignez l'attribut Command. On va la définir tout de suite :)

4/ Appeler une fonction javascript

Un peu plus bas, vous devriez trouver la balise CommandDefinitions qui est une collection de CommandDefinition. Ajoutez-y votre propre commande. L'attribut intéressant ici est Actions, qui permet de dire ce que fera votre bouton. On va y ajouter un JavaScriptFunction avec deux attributs: Library, et FunctionName. Le premier indique la webresource contenant la librairie, et le second la fonction à appeler.

        <Templates>...</Templates>
        <CommandDefinitions>
          <CommandDefinition Id="form.CreateOne.Command">
            <EnableRules/>
            <DisplayRules/>
            <Actions>
              <JavaScriptFunction Library="$webresource:new_actionScript" FunctionName="openNewForm" />
            </Actions>
          </CommandDefinition>
        </CommandDefinitions>

On ne s'occupe pas des balises EnableRules et DisplayRules pour le moment, mais juste pour info, ils vous serviront à indiquer quand le bouton doit être affiché ou activé. Dans la partie LocLabels, on peut définir les labels et tooltips pour les différentes langues installées.

        <RuleDefinitions>...</RuleDefinitions>
        <LocLabels>
          <LocLabel Id="CreateOne.ToolTip">
            <Titles>
              <Title languagecode="1033" description="Create a new student" />
              <Title languagecode="1043" description="Crée un nouvel étudiant" />
            </Titles>
          </LocLabel>
          <LocLabel Id="CreateOne.LabelText">
            <Titles>
              <Title languagecode="1033" description="Create New" />
              <Title languagecode="1036" description="Créer nouveau" />
            </Titles>
          </LocLabel>
        </LocLabels>
      </RibbonDiffXml>
    </Entity>
  </Entities>
  <Roles></Roles>
  <Workflows></Workflows>
  <FieldSecurityProfiles></FieldSecurityProfiles>
  <Templates />
  <EntityMaps>
    <EntityMap>
      <EntitySource>new_student</EntitySource>
      <EntityTarget>new_student</EntityTarget>
      <AttributeMaps />
    </EntityMap>
  </EntityMaps>
  <EntityRelationships>...</EntityRelationships>
  <OrganizationSettings />
  <optionsets />
  <Languages>
    <Language>1033</Language>
    <Language>1036</Language>
  </Languages>
</ImportExportXml>

Vous pouvez maintenant sauver le fichiers customizations.xml, et réimporter la solution. Publiez les modifications, et ouvrer une Form de l'entité pour laquelle vous avez modifié le menu. Vous verrez un nouveau bouton apparaître, et si vous cliquez dessus, une nouvelle page s'ouvrira :)

[CRM 2011] Open a window with a custom button and some javascript code

10. October 2011 09:10 by Renaud in   //  Tags:   //   Comments (3)

If you want to open a CRM form with a custom button, you can either use some attributes in the URL when customizing the Ribbon menu, or you can use some javascript code! The interest of using javascript is that you can easily pass any informations to an other form via the extraqs parameter, but we will talk about it in the next post :) For the moment, we'll just open a new Form! In this post, you'll see the following topics:

  • Add a javascript webresource to a form
  • Build the URL and open a the new Form with javascript
  • Add a custom button to the ribbon
  • Make this button call a javascript function

1/ Add a new javascript webresource to a form

To insert a new script file into a form, you just have to create a new webresource. You can do it directly in the customization form. You can select an existing webresource or create a new one. Then open the text editor, and start writing a new function. In the following example we will call this function openNewForm().

2/ Open a window

Nothing complicated here! If you want more documentation about the possibilities, just browse this link: [MSDN] Open Forms, Views, and Dialogs with a URL. The point here is that you with some parameters you can easily open an existing window. For example you can open the Form of an existing entity to edit an existing record or open it with empty fields to create a new one. You can also open a list of record, specifying the view ID, or even open Dialogs. Here is the code to add in your webresource.

function openNewForm() {

    var serverUrl;

    var errorMessage = "Context to retrieve the Server URL is not available.";
    if (typeof GetGlobalContext != "undefined") {
        serverUrl = GetGlobalContext().getServerUrl();
    }
    else {
        if (typeof Xrm != "undefined") {
            serverUrl = Xrm.Page.context.getServerUrl();
        }
        else {
            alert(errorMessage);
            return;
        }
    }
    if (serverUrl.match(//$/))
    { 
        serverUrl = serverUrl.substring(0, serverUrl.length - 1);
    }

    var recordUrl = serverUrl + "/main.aspx?";

    var params = "etn=new_student";

    params += "&pagetype=entityrecord";

    var accountURL = recordUrl + params;

    window.open(accountURL, "_blank", "width=900px,height=600px,resizable=1");
}

What we are doing here is building the URL of the Form we want to open. First we retrieve the server URL, and then we append some parameters to it. If you are in CRM Online, you need the following lines to remove the extra slash append a the end of the URL. Otherwise the ribbon menu won't appear.

if (serverUrl.match(//$/))
{ 
    serverUrl = serverUrl.substring(0, serverUrl.length - 1);
}

Some useful parameters:

 

etn The LogicalName of the concerned entity.
pagetype entityrecord to display the details form of an entity, and entitylist to display a view.
extraqs optional: use this parameter 
id optional: used with the entityrecord value of pagetype, when the id parameter is present, the corresponding record is displayed

In the code above, we use the etn=new_student and pagetype=entityrecord parameters without giving any id. The consequence will be a Form to create a new new_student entity.

3/ Add a custom button to the ribbon

For customizing the ribbon menu, you have to create a new CRM solution, and add the entity for which you want to make changes. (In my case it was for a new_student entity that was in my test organization :) ). Then export the solution and edit the customizations.xml. Find the RibbonDiffXml tag.

<ImportExportXml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Entities>
    <Entity>
      <Name LocalizedName="Student" OriginalName="Student">new_student</Name>
      <ObjectTypeCode>10001</ObjectTypeCode>
      <EntityInfo>...</EntityInfo>
      <FormXml>...</FormXml>
      <SavedQueries>...</SavedQueries>
      <RibbonDiffXml>
        <CustomActions>
          <CustomAction Id="form.CreateOne.CustomAction"
                        Location="Mscrm.Form.new_student.MainTab.Collaborate.Controls._children"
                        Sequence="1">
            <CommandUIDefinition>
              <Button Id="form.CreateOne.Button" Command="form.CreateOne.Command" 
                      LabelText="Star" ToolTipTitle="Tip" 
                      LabelText="$LocLabels:CreateOne.LabelText" 
                      ToolTipDescription="$LocLabels:CreateOne.ToolTip" 
                      TemplateAlias="o1" />
            </CommandUIDefinition>
          </CustomAction>
        </CustomActions>

I won't explain the ribbon customization in details, because it's not the purpose of this article ! But to make it simple, you need to add a CustomAction tag with a Location attribute to set where the button should appear in the ribbon. Then in the Button tag, don't forget the Command attribute. We will define it right now.

4/ Call a javascript function

Then you should find the CommandDefinitions tag which is of course a collection of CommandDefinition. Add it you own command definition. In the Actions tag, you will add a new JavaScriptFunction tag with two important attributes: Library, and FunctionName. The first indicate where is located the javascript webresource that you have created in the first part of this post, and the second attribute is for the name of the function you want to call when the button has been clicked!

        <Templates>...</Templates>
        <CommandDefinitions>
          <CommandDefinition Id="form.CreateOne.Command">
            <EnableRules/>
            <DisplayRules/>
            <Actions>
              <JavaScriptFunction Library="$webresource:new_actionScript" FunctionName="openNewForm" />
            </Actions>
          </CommandDefinition>
        </CommandDefinitions>

Don't pay attention to the EnableRules and DisplayRules attributes for the moment, but you should know that with those ones you can decide to display/enable or not an action according to some rules. For example you could say that the button is only visible when a given option is selected in an optionset or when a text field has a particular value. In the LocLabels part, you can specify different labels/tooltip for each language pack.

        <RuleDefinitions>...</RuleDefinitions>
        <LocLabels>
          <LocLabel Id="CreateOne.ToolTip">
            <Titles>
              <Title languagecode="1033" description="Create a new student" />
              <Title languagecode="1043" description="Crée un nouvel étudiant" />
            </Titles>
          </LocLabel>
          <LocLabel Id="CreateOne.LabelText">
            <Titles>
              <Title languagecode="1033" description="Create New" />
              <Title languagecode="1036" description="Créer nouveau" />
            </Titles>
          </LocLabel>
        </LocLabels>
      </RibbonDiffXml>
    </Entity>
  </Entities>
  <Roles></Roles>
  <Workflows></Workflows>
  <FieldSecurityProfiles></FieldSecurityProfiles>
  <Templates />
  <EntityMaps>
    <EntityMap>
      <EntitySource>new_student</EntitySource>
      <EntityTarget>new_student</EntityTarget>
      <AttributeMaps />
    </EntityMap>
  </EntityMaps>
  <EntityRelationships>...</EntityRelationships>
  <OrganizationSettings />
  <optionsets />
  <Languages>
    <Language>1033</Language>
    <Language>1036</Language>
  </Languages>
</ImportExportXml>

Now you can save the  customizations.xml file, and import it to your organization. Publish all the modifications and open the form for which you just customized the ribbon to see the new button. Click on it to open the new Form! :)

[CRM 2011] Afficher/masquer des éléments d'une form en javascript

3. October 2011 16:48 by Renaud in   //  Tags:   //   Comments (0)

Il est assez simple d'améliorer l'expérience utilisateur en utilisant du javascript. Une des choses que vous pouvez faire est de masquer des champs qui ne sont pas nécessaires et les afficher uniquement au moment opportun. Par exemple, imaginez une entité Payment, qui représente une transaction pour une facture. Ce paiement peut être de plusieurs types: chèque, virement bancaire, cash. Dans le cas d'un virement bancaire, on aimerait en plus stocker la communication structurée. A priori, sans utiliser de javascript, le champs Communication structurée serait constamment visible. Cela pourrait être perturbant, et donner l'impression aux utilisateurs que ce champs a une importance dans tous les cas. Dans l'exemple qui va suivre, on va prendre une entité account, qui possède une picklist Account type avec pour valeurs "Type A", "Type B", ... Chacun de ces types possèdent des champs propres. Pour éviter de devoir afficher tous ces champs en même temps, on va simplement ajouter du javascript sur l'évènement OnChange du control accounttype.

1/ lier un nouveau script à la form

Ouvrez la fenêtre de customization d'une form, et cliquez sur Form Properties.

Dans la nouvelle fenêtre, le premier grid contient les fichiers javascript chargé avec la form. Cliqez sur Add, et ensuite sur New.

Entrez un nouveau nom, et comme type sélectionnez Script (JScript). Cliquez ensuite sur Text Editor. C'est là que vous allez entrer votre code. Pour bien comprendre le code qui va suivre, voici à quoi ressemble la structure de la fenêtre:

Globalement, la structure ressemble à ça:

  • Tabs: Details
    • Section typea
      • Field Type A
    • Section typeb
      • Field Type B
Pour commencer, on va simplement ajouter une fonction qui va tester la valeur sélectionnée dans new_type, et afficher ou masquer les éléments correspondants:
function onTypeChange(){
    if( Xrm.Page.getAttribute("new_type").getValue() ==1)
    {
        Xrm.Page.ui.tabs.get("details_tab").sections.get("details_typea_section").setVisible(true);
        Xrm.Page.ui.tabs.get("details_tab").sections.get("details_typeb_section").setVisible(false);
    }
    else if(Xrm.Page.getAttribute("new_type").getValue() ==2)
    {
        Xrm.Page.ui.tabs.get("details_tab").sections.get("details_typeb_section").setVisible(true);
        Xrm.Page.ui.tabs.get("details_tab").sections.get("details_typea_section").setVisible(false);
    }else
    {
        Xrm.Page.ui.tabs.get("details_tab").setVisible(false);
        Xrm.Page.ui.tabs.get("details_tab").sections.get("details_typea_section").setVisible(false);
        Xrm.Page.ui.tabs.get("details_tab").sections.get("details_typeb_section").setVisible(false);
        return;
    }

    Xrm.Page.ui.tabs.get("details_tab").setVisible(true);
}

Sauvez, et cliquez sur Ok. La nouvelle ressource javascript apparaîtra dans le grid de la Form Properties.

2/ appeler une fonction lorsqu'un évènement se déclenche

Dans cette même fenêtre, vous pouvez maintenant ajouter un nouvel évènement. Dans la première listbox, sélectionnez l'attribut Type. L'évènement onChange sera sélectionné. Cliquez sur Add, et entrez le nom de la fonction qui doit être appelée par cet évènement: onTypeChange. Vous pouvez faire la même chose pour l'évènement onLoad de la form.  [1] Pas de valeur sélectionnée: la tab Details est masquée. [2] Type A sélectionné: la section typea est affichée. [3] Type B sélectionné: la section typeb est affichée.

3/ quelques explications

En vérité, il y a plusieurs façon de masquer des champs dans CRM, mais voici quelques uns des choix que j'ai fait:

  • Préférer masquer une section plutôt qu'un champs. Lorsque vous masquez un champs, cela ne supprime pas l'espace lui étant réservé, et vous risquez d'avoir des blancs dans votre formulaire. Pas très esthétique.
  • Pour que ce soit vraiment idéal, pensez à mettre les champs qui peuvent être masqués en non-visibles par défaut. Cela évitera de les voir apparaître une fraction de seconde au chargement de la page. 
  • Et pour finir, utilisez de préférence la fonction setVisible(false) du sdk plutôt qu'une méthode pour modifier la visibilité d'un élément du DOM. La principale raison est que le nom que vous donnez à une section ou un tab n'apparaît pas dans le code html de la page. Vous ne trouverez que des guid pour chacun de ces éléments. Donc à moins de rechercher vous-même les guid de ces éléments pour ensuite y accéder directement, ça risque d'être un peu compliqué de retrouver le bon élément. Ne vous compliquez pas la vie :)

[CRM 2011] Dynamically show or hide fields using javascript

3. October 2011 10:10 by Renaud in   //  Tags:   //   Comments (3)

It's pretty easy to enhance the user experience with some javascript in CRM 2011. One of the things you should be able to do is to hide fields that are not necessary in some case and show them only when it's required. For example, imagine you have an account entity with an account type picklist (type A, type B, etc...) and for each type a couple of fields. if all of those fields are always visible,  it could fastly become confusing for the user. To fix this, you'll simply add some javascript on the onChange event of the paymenttype picklist.

1/ link a new javascript sheet to the form

Open the customization form of the entity, and click on Form Properties.

In the new window, there are two grids: the first one contains the javascript sheets loaded with the form, and the second one display the events handlers. Click on the Add button above the first grid and then click New. Add a name to your resource, select the Script (JScript) type, and click the Text Editor button. You can write your code here! To help you understand the following javascript code, here is what the form looks like: The structure is:

  • Tabs: Details
    • Section typea
      • Field Type A
    • Section typeb
      • Field Type B
First, we will simply add a new function to test the value of the new_type picklist (the one which contains account types). And according to the value we will display or hide the corresponding items.
function onTypeChange(){
    if( Xrm.Page.getAttribute("new_type").getValue() ==1)
    {
        Xrm.Page.ui.tabs.get("details_tab").sections.get("details_typea_section").setVisible(true);
        Xrm.Page.ui.tabs.get("details_tab").sections.get("details_typeb_section").setVisible(false);
    }
    else if(Xrm.Page.getAttribute("new_type").getValue() ==2)
    {
        Xrm.Page.ui.tabs.get("details_tab").sections.get("details_typeb_section").setVisible(true);
        Xrm.Page.ui.tabs.get("details_tab").sections.get("details_typea_section").setVisible(false);
    }else
    {
        Xrm.Page.ui.tabs.get("details_tab").setVisible(false);
        Xrm.Page.ui.tabs.get("details_tab").sections.get("details_typea_section").setVisible(false);
        Xrm.Page.ui.tabs.get("details_tab").sections.get("details_typeb_section").setVisible(false);
        return;
    }

    Xrm.Page.ui.tabs.get("details_tab").setVisible(true);
}

If the value of new_type equals 1 (Type A) then we show TypeA section and hide TypeB section, etc... Then you can click close this window, and it will appear on the first grid of the Properties Form window.

2/ call a function when an event is fired

In the same window, you can add a new event handler. First select the attribute (Type). The onChange attribute will then be selected. Click add, and type the name of the function you want to call: onTypeChange. To make this even better, you can also do the same with the onLoad event of the Form. [1] Nothing selected: Details tab is hidden. [2] Type A selected: Details tab and typea section are visible. [3] Type B selected: Details tab and typeb section are visible.

3/ explanations

The truth is that you have more than one possibility to show/hide fields. But in my opinion you should:

  • Prefer to hide section instead of fields. If you hide a field, it will not collapse the space, but a blank space will replace the field. So wherever you can, group your fields that need to be show/hide at the same time under one section, and play with that section!
  • Think to uncheck the Visible by default checkbox in the properties of the item you want to show/hide.  Otherwise, it will be visible during a fraction of a second while the page is loading, and it's not really what you want!
  • Finally, use the setVisible method of the crm sdk. It's easir than trying to retrieve element from the DOM and changing its display or visibility style because if you look at the HTML code of a CRM form, you'll see that the section name doesn't appear anywhere. You will only find a guid. So it will be a little tricky to find the right DOM element except if you look in your code to find the guid of each section you want to work with.

[CRM 2011] Débugguer un plugin dans CRM 2011 avec Visual Studio 2010

26. September 2011 09:09 by Renaud in   //  Tags:   //   Comments (0)

À ce stade, vous avez certainement déjà déployé votre premier plugin. J'espère que pour vous que tout s'est bien déroulé du premier coups. Mais dans ce comme dans l'autre, il peut être très intéressant de pouvoir débugguer votre plugin.

Pré-requis: Pour ce qui va suivre, il est nécessaire d'avoir Visual Studio installé sur le serveur sur lequel se trouve l'installation de CRM. Je n'ai pas encore utilisé le remote debugging, mais ça ne saurait tarder, et je vous ferai alors un autre article à ce sujet ;)

Vous pourrez ainsi faire des tests, explorer les données dont vous disposez, et apprendre à mieux utiliser les plugins.

Pour débugguer, rien de très compliqué: rappelez-vous l'enregistrement de votre assembly dans CRM.  Aviez-vous fait attention aux remarques concernant les fichiers .pdb? Ces grâce à eux que vous pourrez suivre l'exécution de vos plugins dans Visual Studio.

Commencez par placer le fichier .pdb correspondant à votre assembly dans le dossier server/bin/assembly. Attention de bien copier la bonne version du fichier, sinon le debug ne fonctionnera pas. C'est pour cela que la solution du stockage sur disque de votre assembly est pratique en développement. Vous pouvez, après chaque nouveau build de votre assembly, remplacer les deux fichiers (.dll et .pdb) en une seule opération, ce qui évitera toute erreur.

Ensuite, dans Visual Studio, allez dans le menu Debug et choisissez Attach to Process...

Dans la liste des processus actifs, cherchez w3wp.exe. S'il n'apparaît pas, c'est peut qu'il faut cocher la case "Show processes in all sessions". [caption id="attachment_554" align="aligncenter" width="738" caption="Dans mon cas, le process qui m'intéresse est celui lié à l'utilisateur "crm""][/caption]

Il se peut aussi que vous ayez plusieurs fois ce processus dans la liste. Si vous n'êtes pas sûr de quel processus vous intéresse réellement, ouvrez une page web pour vous connecter à votre organisation CRM. Ouvrez également le Task Manager pour observer les process w3wp.exe. Rafraîchissez la page affichée dans Internet Explorer, et vous verrez l'un des process consommer d'avantage de ressources pendant un bref instant. C'est lui le coupable :) Retenez le PID un cours instant, le temps de le retrouver dans Visual Studio!

Cliquez ensuite sur "Attach". Vous serez prévenu que cela peut endommage votre système, mais pas de panique, on ne fait rien de mal.  Confirmez!

Il ne reste plus maintenant qu'à mettre un point d'arrêt quelque part dans votre code, vous rendre sur la page concernée par votre plugin, et si tout à été fait correctement, l'exécution s'interrompra comme prévu! :)

[caption id="attachment_702" align="aligncenter" width="449" caption="Image thématique :)"][/caption]

Attention, lorsque vous voudrez remplacer l'assembly et le pdb par un nouveau build sur votre serveur, il se peut que les fichiers soient en cours d'utilisation et donc inaccessibles. Il suffit de tuer le process de crm utilisé précédemment via le Task manager, le temps de remplacer les fichiers. Ensuite, rafraîchissez la page dans CRM pour créer un nouveau process avec un nouveau PID auquel vous pourrez de nouveau attacher Visual Studio!

Bon débug ;)

[CRM 2011] Update the display of a lookup field [part 2/2]

19. September 2011 17:00 by Renaud in   //  Tags:   //   Comments (1)

Before we start

Here is the second part of this solution concerning lookup fields. Just to remind you, the goal of those articles is to display a different field than the primary field of a related entity in a lookup field.

In the following example, the interface is in french, but the "Discipline" field contains an english value. This is because the lookup field displays the new_name attribute of the Discipline entity. And CRM 2011 doesn't allow us to map this field to an other property of the related entity. That's why we have to build our own solution.

In the first article, we have build a plugin that replaces the actual value by its french traduction which is stored in the new_french attribute. This plugins works both for Create and Update message.

But there is another problem: if we change the current Discipline to which a student has subscribe, then the field will be updated, but it will show an english term again.

This window allows you to browse Discipline entities that you could associate with a student.

The point is that there isn't no request between the moment you click the Ok button and the moment the field is updated. The new value directly comes from the previous research dialog. This is a built-in behavior of CRM.

We will try to modify the data in that window, so that CRM will continue working the same way and will return the correct value without noticing that we have change something.

What are the possibilities...

We know that before opening the lookup dialog of Discipline entities, the RetrieveMultiple message is launched for the new_discipline entity. One possibility would be to display only one column in that window with the generic title "Name" instead of two columns "English name" and "French name". This column will be able to handle both languages, and we can create a plugin in post-operation that check for the user language and then, if it is french, retrieve the french values to replace the english ones.

In my case, the client wants to aways see both columns. So it's easier because we can just create a plugin in the post-operation pipeline of the RetrieveMultiple message to just switch the values of both columns! :)

Prerequisites

If you haven't read the first article yet, maybe you should have a look. So you could download the sources and do the lab with me in the best conditions! :)

Customize CRM view

Nothing complicated here: I'll simply do some modifications as explained above.

For this to work  fine, there is a little trick: The name of the colums in english will be "English" and "French" and the translations will be respectively "Français" and "Anglais" to fits the columns content!

To modify those titles, go into the customization form of the Discipline entity.

Then select a field, let's say English:

In the Ribbon menu, click on Change Properties. Go into Details, and then clik Edit. There, you'll be able to change the Display Name value. Do the same for the second field. The names you are writing here aren't those which are displayed directly on the form but those who will appear on grid views! If you want to change the display label of a field, you can do it directly in the properties of this field. Keep it in mind when you'll do the translations!

The source code

Here is the interesting part :) Take back the plugin library from the first article. You'll need the helper class to check the language! Create a new plugin:

RetrieveMultipleDiscipline.cs
using Microsoft.Xrm.Sdk;
using System;

namespace Plugins
{
    public class RetrieveMultipleDiscipline: IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            // get the context
            IPluginExecutionContext context = (IPluginExecutionContext)
                serviceProvider.GetService(typeof(IPluginExecutionContext));

            EntityCollection collection;

            // Check if the input parameters property bag contains a BusinessEntityCollection
            // key of type EntityCollection
            if (context.OutputParameters.Contains("BusinessEntityCollection") &&
              context.OutputParameters["BusinessEntityCollection"] is EntityCollection)
            {
                // Obtain the EntityCollection from the output parmameters.
                collection = (EntityCollection)context.OutputParameters["BusinessEntityCollection"];

                // Verify that the collection contains Discipline entities.
                if (collection.EntityName == "new_discipline")
                {
                    // TODO: checker la langue et switcher les valeurs.
                }
            }
        }
    }
}

This time, we will look for an EntityCollection! The EntityName attribute contains the LogicalName of the entity that this collection contains.

You will now use the method from the PluginHelper class to check wether the user has set the language to french or not. If he has, then you can start the process of switching the values.  Place the following code instead of the TODO.

RetrieveMultipleDiscipline.cs
                    // the factory will allow us to create a new IOrganizationService
                    IOrganizationServiceFactory factory = (IOrganizationServiceFactory)
                        serviceProvider.GetService(typeof(IOrganizationServiceFactory));
                    // Creates a new IOrganizationService to retrieve the user language.
                    IOrganizationService orgService = factory.CreateOrganizationService(context.UserId);

                    if (orgService.RetrieveLanguageForUser(context.UserId) == 1036)
                        SwitchFields(collection, "new_name", "new_french");

And last but not least, the SwitchFields method:

RetrieveMultipleDiscipline.cs
        /// <summary>
        /// Switches the specified fields of the given collection.
        /// </summary>
        /// <param name="collection">The entity collection.</param>
        /// <param name="firstField">The first field.</param>
        /// <param name="secondField">The second field.</param>
        private void SwitchFields(EntityCollection collection, string firstField, string secondField)
        {
            foreach (Entity targetEntity in collection.Entities)
            {
                string temp = targetEntity.Attributes[firstField] as string;

                targetEntity.Attributes[firstField] = targetEntity.Attributes[secondField];

                targetEntity.Attributes[secondField] = temp;
            }
        }

Register the plugin

For the registring, you just have to update the assembly because you had already registered it in the previous lab. Then, add a new step for the RetrieveMultiple message of the new_discipline entity. Use the post-operation event pipeline.

Now you can check the result by yourselves!

Whatever the language is, french or english, the first column of the Discipline lookup dialog will always display the value corresponding to the language. CRM will automatically return the value from the first column to the Student Form when you click ok, because it will suppose that it is the primary field! :)

You can download the sources here:

Visual Studio 2010 project

CRM 2011 Solution, with
entities + assembly + plugin

[CRM 2011] Modifier l’affichage d’un champs lookup [partie 2/2]

19. September 2011 16:59 by Renaud in   //  Tags:   //   Comments (0)

Prologue

Nous voici dans la deuxième partie de la solution consacrée aux lookup fields. Pour rappel, l'idée de cette série d'articles est d'arriver à afficher autre chose que le primary field d'une entité dans un champs lookup.

Dans l'exemple ci-dessous, l'interface est en français, mais le champs "Cours" est en anglais. En fait, c'est la propriété new_name du cours, qui est affichée. Ceci est dû à une limitation de CRM 2011, qui n'offre pas la possibilité de mapper un champs sur n'importe quelle propriété d'une entité relative. A moins donc de développer sa propre solution, vous verrez toujours le champs primaire de l'entité relative.

Dans le premier article, nous avons créé un plugin pour faire en sorte de remplacer cette valeur par sa traduction française à l'ouverture ou au rafraîchissement de la fenêtre ci-dessus.

Mais il reste un problème: en sélectionnant un cours parmi la liste ci-dessous, et en cliquant sur Ok, le champs de la Form étudiant est bien mis à jour, mais apparaît de nouveau en anglais!

Cette fenêtre permet de rechercher une entité à associer à un étudiant

En fait, il n'y a pas de requête effectuée vers le serveur entre le moment où l'on clique sur Ok et celui où le champs Cours est mis à jour. La nouvelle valeur provient directement de la fenêtre précédente. C'est donc là qu'il faut traiter nos entités.

On va modifier un petit peu les données de la fenêtre de sélection d'un cours. CRM pourra continuer à fonctionner de la même manière, et la fenêtre de sélection d'un cours renverra une valeur à afficher, mais la bonne cette fois-ci! C'est-à-dire celle correspondant à la langue de l'utilisateur :)

Solutions envisageables

Avant l'ouverture de la fenêtre ci-dessus, le message RetrieveMultiple est déclenché pour l'entité new_discipline. Une des possibilités serait de n'afficher qu'une seule colonne avec pour titre Name ou Intitulé, selon la langue de l'utilisateur. Avec un plugin, on check la langue de l'utilisateur. S'il s'agit du français, alors il suffit de remplacer les données de la propriété OutputParameters du IPluginExecutionContext par les valeurs françaises. Mais cette solution nécessite un deuxième accès à la base de données pour récupérer les infos manquantes!

Dans mon cas, l'utilisateur souhaite toujours voir les noms des cours dans les deux langues! Nous allons donc adapter les labels des attributs de l'entité cours. Et plutôt que de faire une deuxième requête après que le message RetrieveMultiple ait été traité, on va tout simplement switcher le contenu de chaque colonne pour s'adapter au scénario :)

Pré-requis

Si vous n'avez pas lu le premier article, vous pouvez allez y faire un tour, histoire de télécharger les sources et de pouvoir suivre cette solution dans les meilleures conditions! :)

Customization CRM

Rien de très compliqué ici, je vais simplement effectuer les modifs expliquées quelques lignes plus haut.

Bon, il y a quand même une petite astuce! Comme on va simplement switcher les données françaises et anglaises, il faudra que les titres des colonnes switchent également. Ainsi, la traduction de English sera Français, et la traduction de French sera Anglais ;) vous suivez?

Les titres de ces colonnes viennent des metadata des attributs. Pour les modifier, ouvrez la fenêtre de customization de la Form Discpline.

Sélectionnez l'un des deux champs à modifier:

Dans le menu ribbon, cliquez sur Change Properties. Allez dans Details, et cliquez sur Edit. Vous pourrez alors modifier le Display Name de l'attribut. Faites pareil pour le deuxième. Les noms que vous venez de modifier ne sont pas ceux affichés dans l'image ci-dessus, mais bien ceux qui seront affichés dans les colonnes des gridviews! Faites donc attention pour les traductions de ne pas confondre les titres des tableaux avec les labels de la Form.

Le code

Nous voilà dans la partie intéressante. On va la faire courte puisque vous êtes supposés avoir quelques notions maintenant.

Reprenez donc la librairie avec le premier plugin et la classe PluginHelper. Nous allons en avoir besoin :) Créez un nouveau plugin:

RetrieveMultipleDiscipline.cs
using Microsoft.Xrm.Sdk;
using System;

namespace Plugins
{
    public class RetrieveMultipleDiscipline: IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            // get the context
            IPluginExecutionContext context = (IPluginExecutionContext)
                serviceProvider.GetService(typeof(IPluginExecutionContext));

            EntityCollection collection;

            // Check if the input parameters property bag contains a BusinessEntityCollection
            // key of type EntityCollection
            if (context.OutputParameters.Contains("BusinessEntityCollection") &&
              context.OutputParameters["BusinessEntityCollection"] is EntityCollection)
            {
                // Obtain the EntityCollection from the output parmameters.
                collection = (EntityCollection)context.OutputParameters["BusinessEntityCollection"];

                // Verify that the collection contains Discipline entities.
                if (collection.EntityName == "new_discipline")
                {
                    // TODO: checker la langue et switcher les valeurs.
                }
            }
        }
    }
}

Cette fois-ci on va chercher une EntityCollection! On peut connaître le type d'entité qu'elle contient grâce à l'attribut EntityName.

On va utiliser l'unique méthode de la classe PluginHelper pour vérifier si l'utilisateur utilise ou non CRM en français. Si c'est le cas, on traite les données. Le bout de code suivant est à placer à l'endroit du TODO.

RetrieveMultipleDiscipline.cs
                    // the factory will allow us to create a new IOrganizationService
                    IOrganizationServiceFactory factory = (IOrganizationServiceFactory)
                        serviceProvider.GetService(typeof(IOrganizationServiceFactory));
                    // Creates a new IOrganizationService to retrieve the user language.
                    IOrganizationService orgService = factory.CreateOrganizationService(context.UserId);

                    if (orgService.RetrieveLanguageForUser(context.UserId) == 1036)
                        SwitchFields(collection, "new_name", "new_french");

Et pour finir, la méthode SwitchFields:

RetrieveMultipleDiscipline.cs
        /// <summary>
        /// Switches the specified fields of the given collection.
        /// </summary>
        /// <param name="collection">The entity collection.</param>
        /// <param name="firstField">The first field.</param>
        /// <param name="secondField">The second field.</param>
        private void SwitchFields(EntityCollection collection, string firstField, string secondField)
        {
            foreach (Entity targetEntity in collection.Entities)
            {
                string temp = targetEntity.Attributes[firstField] as string;

                targetEntity.Attributes[firstField] = targetEntity.Attributes[secondField];

                targetEntity.Attributes[secondField] = temp;
            }
        }

Enregistrement du plugin

Pour l'enregistrement, commencez par mettre à jour l'assembly. Ensuite choisissez le message RetrieveMultiple de l'entité new_discipline. Utilisez le mode post-operation.

Vous pouvez maintenant tester le résultat par vous-même! :) Les sources sont téléchargeables:

Le projet Visual Studio.

La solution CRM 2011, avec
entités + assembly + plugin

TextBox

About the author

I'm a developer, blog writer, and author, mainly focused on Microsoft technologies (but not only Smile). I'm Microsoft MVP Client Development since July 2013.

Microsoft Certified Professional

I'm currently working as an IT Evangelist with an awesome team at the Microsoft Innovation Center Belgique, where I spend time and energy helping people to develop their projects. I also give training to enthusiastic developers and organize afterworks with the help of the Belgian community.

MIC Belgique

Take a look at my first book (french only): Développez en HTML 5 pour Windows 8

Développez en HTML5 pour Windows 8

Membre de l'association Fier d'être développeur

TextBox

Month List