Tuesday 30 June 2015

The difference: Xrm.Page.data.refresh() vs Xrm.Utility.openEntityForm(entityName, id) in Practice

Brief post here, Assuming, in the ribbon, I have successCallback function that will refresh the form.
Then, in the form onLoad I have script to show Form Notification.

Now, I want to research the difference of those function usages.

image

Now, In the Submit Button SuccessCallback() I have specified one is using .refresh() and another one is using the .openEntityForm()

Xrm.Page.data.refresh()

What happened after I put it in the code?

image

It is just refreshing the Form like a modal popup async and it is fast!

image

But..It does not trigger my notification onLoad(), it did not reload the whole form.

Now, the second research:

Xrm.Utility.openEntityForm()

image

Well, it is really refreshing and reloading your form, like you were opening a new window of entity record.

image

And here is the result after it comes back.

image

*My Notification which i put in the formLoad is appearing, different from the first function result I used before.

Hope this can make you clear which function you’d like to use!

As I know, both are also refreshing the ribbon without additional ribbon refresh function based on my research.

*additional one on 19 October 2015
the Xrm.Page.data.refresh() function will not refresh the whole thing, so you might find that the footer is not updated like this:



Thanks!

Sunday 28 June 2015

Tab State Change Event CRM Javascript

Introduction

In CRM Form, Tab has important role to make your CRM Form more structured and easy to read.
But, not many people try to play with JavaScript for this Tab Event.
Basically, CRM Tab same as field, also has event, but Tab only has event triggered once you click it and then it will be expanded or collapsed and its state will be changed accordingly.

‘Hidden Required Fields’ in Collapsed Tabs

I give you one scenario, for example:
You save the form and then you found that you need to fill some fields based on its requirement option or your custom validation, then what you can find is you cannot save the form, but you don’t know where to find the field since this field is inside a Tab that is in collapsed mode! If you realize that, it makes users difficult to finish the form.
image
Yeah, this is very clear prompting me to fill in the ‘Course Title’ but how to find it if this field inside the Collapsed tab? That’s why sometimes you might need to expand it programmatically.

Here is the article about Tab:

https://msdn.microsoft.com/en-us/library/gg328067.aspx

Which I want to put more spices on it.

The Code

Here is the Code to add the event handler of the Tab state changes programmatically

Add Event Handler Programmatically

Xrm.Page.ui.tabs.get("tab_general").add_tabStateChange(tabGeneralStateChange);

*Change to your tab name and place this on the form OnLoad()

To Get/Set the display state programmatically


*To Get

Xrm.Page.ui.tabs.get("tab_general").getDisplayState();

*To Set

Xrm.Page.ui.tabs.get("tab_general").setDisplayState(string);
//change the string to either 'expanded' or 'collapsed

*To Set Focus

Xrm.Page.ui.tabs.get("tab_general").setFocus()

*As per mentioned before, those methods can be useful for users to find the field location

Sample Code – Event Handler Code

Then here is sample code for the event

Xrm.Page.ui.tabs.get("tab_general").add_tabStateChange(tabGeneralStateChange);

function tabGeneralStateChange() {
    var currTabState = Xrm.Page.ui.tabs.get("tab_general").getDisplayState();
    if (currTabState == "expanded") {
        alert("Expanded, horreey");
    }
    else {
        alert("Collapsed, ooh noo.");
    }
}

Result:

*Expanded

image

*Collapsed

image

*In one of my project, I can use this event to call my custom page once user click it, so that I do not use ribbon for this as well.

image

Now, I want to change the label accordingly also:

Sample Code – Event Handler with Label Handler

function tabGeneralStateChange() {
    var currTabState = Xrm.Page.ui.tabs.get("tab_general").getDisplayState();
    if (currTabState == "expanded") {
        alert("Expanded, horreey");
        Xrm.Page.ui.tabs.get("tab_general").setLabel("- Hide Me -");
    }
    else {
        alert("Collapsed, ooh noo.");
        Xrm.Page.ui.tabs.get("tab_general").setLabel("+ Show Me +");
    }
}

Result:

image

SNAGHTML11e6de4

Hope this helps!

Thanks.

Convert CRM Plugin Message to CRM Alert or Notification When Save Programmatically JavaScript

Introduction

In my previous post, I have mentioned about how to get the Plugin Context Message and pass it to CRM Client Script.

Now, I just want to continue that post and make some advance message on it (while I shown before by parsing and send it to form notification before)

Assuming, in your Plugin, you list down and combine all of your message together and you put it in the List<string> or even a StringBuilder as usual then you use AppendLine Method

For example:


sbValidationMessage.AppendLine("Hey you!");
sbValidationMessage.AppendLine("Hey you2!");

With sbValidationMessage is initialized instance name of StringBuilder()

Okay, now I want to show you in the real message, you are expecting that you will see two lines as well.

But, your expectations is not going to be real..

Why? Because Plugin in CRM is not supporting the HTML Message, so it does not recognize newline or break, it will make as whitespace only, well you can change it unsupported by converting the dialog message to innerHTML method but again, it is unsupported!

So, we will have this:

image

In fact, you are expecting to have two lines instead of messy message mixed in one statement.

So, what you can do is you can get and parse the message from the ErrorCallback!

Solution

Here we go..

I see this article and I found that we can have two next function, either success or error

https://msdn.microsoft.com/en-us/library/dn481607(v=crm.6).aspx
Xrm.Page.data.save().then(successCallback, errorCallback)
So, i check and start my investigation I get this clue:

image

Then, now I go and fix my code:

The Code

//this the function that my ribbon or other event that need to save programmatically
function publish() {
    if (confirm("Confirm this Class, so that it can be appearing in the Class creation for course reference?")) {
        Xrm.Page.getAttribute("new_ispublished").setValue(true);
        Xrm.Page.data.save().then(successCallback, errorCallback);
        //Xrm.Page.data.refresh();
    }
}

function successCallback() {
    //Needed to set form dirty to false explicitly as it is not done by platform
    Xrm.Page.data.setFormDirty(false);
    var Id = Xrm.Page.data.entity.getId();
    Xrm.Utility.openEntityForm("my_entity", Id);
}

function errorCallback(saveErrorResponse) {
    if (saveErrorResponse != null) {
        if (saveErrorResponse.debugMessage != null) {
            alert(saveErrorResponse.debugMessage);
        }
    }
}

Result

image

As you can see, now we have lines with break line.

or you might break it into Form Notifications

image

Code for Form Notification

//this the function that my ribbon or other event that need to save programmatically
function publish() {
    if (confirm("Confirm this Class, so that it can be appearing in the Class creation for course reference?")) {
        Xrm.Page.getAttribute("new_ispublished").setValue(true);
        Xrm.Page.data.save().then(successCallback, errorCallback);
        //Xrm.Page.data.refresh();
    }
}

function successCallback() {
    //Needed to set form dirty to false explicitly as it is not done by platform
    Xrm.Page.data.setFormDirty(false);
    var Id = Xrm.Page.data.entity.getId();
    Xrm.Utility.openEntityForm("my_entity", Id);
}

function errorCallback(saveErrorResponse) {
    if (saveErrorResponse != null) {
        if (saveErrorResponse.debugMessage != null) {
            alert(saveErrorResponse.debugMessage);
            var strSplitted = splitStringToArray(saveErrorResponse.debugMessage, "\r\n");
            for (var i = 0; i < strSplitted.length; i++) {
                var notifId = "Notif_" + i + 1;
                Xrm.Page.ui.setFormNotification(strSplitted[i], 'ERROR', notifId);
                //remember you might need to clear the notification also when first hit the ribbon, otherwise it will keep adding.
            }
        }
    }
}

function splitStringToArray(str, separator) {
    return str.split(separator);
}

*But you need to note that the plugin message will be still appearing since this is the OOB Save Method, if you need to avoid it, you might need to use OData Merge to Update programmatically and pass the callback function async.

https://msdn.microsoft.com/en-us/library/gg328090.aspx

https://msdn.microsoft.com/en-us/library/gg334427.aspx

Well, it can be useful for next client script action but you cannot avoid the plugin message still appearing as debug message in supported way.
I use odata Merge rather than Save if want to manipulate without caring about this Plugin Message and it works.

Hope this helps!

Thanks.

Saturday 27 June 2015

CRM Status Reason to Control Process/Stage Flow Codeless

Introduction

In real world, we always have a status or stage for each instance, same well as if we implement in CRM record, each record can have multiple stages and you might need to control it.

Imagine, we have this stage sequence:

image

How about Business Process Flow?

Now, in CRM 2013 we also have the Business Process Flow.

image

This feature apparently is used to guide you manage a record by process one by one.

But, this does not stop you to go back and forth (yeah, only required Fields that can stop), we know that in CRM 2015 it has new special events, but, well, you still need to control it programmatically using client script event or server side behind code.

And also if you notice in the final Stage, Closing, if you want to deactivate the record you need to manipulate it using workflow or programmatically.

Status Reason Transition?

Now, let’s go to the topic itself, in CRM 2013 SP1, we have been given a feature so-called Status Reason Transition, this feature is considered cool feature and to be honest now not many people using this, since they are commonly confusing about its real usage and always thinking to link process to BPF.

So, in this post, my point is to accentuate status reason can be useful to control stages.

I try to translate the Requirement to Status Reason now.

I have these status now, which I divided into two state:

*Active

image

And

*Inactive

image

Status Reason WITHOUT Transition in the Form

Now, put it to the Form and then see the result:
image

Hm..Where is the control?

Well, by default, users can select and change the status immediately without control as they like.
Now, we need to control!

How?

Status Reason Transition Rules

Go back to the customization and go to the Entity –> Fields –> Status Reason, which you will see this button!

image

Then, let’s setup our transition eligibility rules

image

Save and Publish, then see the result

Status Reason WITH Transition in the Form

*When you are in the ‘Tender Briefing’ State

image

You can see here, you can only choose to keep remain in the ‘Tender Briefing’ or change to the ‘Requirement Gathering’

*When you are in the ‘Requirement Gathering’ stage:

image

You can only move to ‘Presentation’ and you can’t back to the first state.

And so on until you reach the final step. Here you can validate that the users so that they cannot go back to the initial stage or move forward without pass the stages, they cannot manipulate the stages and also it is good for them to control then they won’t be confused using CRM for replacing them reporting their progress.

Let’s say you have another scenario, for example from Tender Briefing you already know the Requirement and then the prospect already knew your Company Profile because your company is famous! So, to enhance and accelerate sales process you allow salesperson to skip the stages and straight forward go to Proposal or Demo.

Here we translate:
image

While we still focus to the sequence that still enable, such as from Presentation to Proposal and so on.
Now, we start again comparing this with new CRM 2015 Feature for BPF that is branching for process.

Well, we can do this branching also with Status Reason.

Back to your Status Reason Transition Setting and then do this setup:

image

Now, when you are in the Tender Briefing State, you will be facing 4 stages that you can choose.

image

State and Status Transition

One more not to know is this Status Reason Transition also allow you to validate across the State, for those who have known CRM, we knew that each CRM Record has two state, that is State (that is always Action or Inactive) and then Status Reason (that for most entities you can add or modify the available option set).

So, assuming you are in the ‘Active’ state specifically in the ‘Proposal’ then you want to close it as Won, the system won’t allow it since you want to control every single step.

image

So, using this feature you can also control the deactivate button.

However, if you want to allow them ‘Close as Lost’ you can also just add in the transition so that they can straight forward close this as Lost without processing to  many stages, save time for impossible Winning Tender.

image

Result:

image

And again..All is codeless!

Hope this helps!
Thanks.

Wednesday 24 June 2015

Quick Tip Generic SQL Error CRM Plugin C#

A quick tip here, just for sharing what happened.

Once you get this Generic SQL Error, beside you can check the event viewer, DB Log, or SQL Tracing or even your Plugin Tracing, I want to share that there are two conditions triggering this error that is actually simple but not anticipated before.

There are:

1. I tried to set a field text that is actually max length = 100, but I give more than 100.
This is commonly happening once you try to set a name as primary field that it is good to not leave it blank, so you use combination of the important fields since CRM cannot use any other than text field to be primary key.

2. Try to create child record or associated record before parent record is created.
This was happening that time I forgot to set the Plugin to Post-event creation instead of Pre-event one.

Hope this can help you, I waste time to see what happened, while I was trying to debug, in fact those two simple cases causing the trouble.

You have any other simple one causing the trouble? Let us know!

Thank you.

Saturday 20 June 2015

Pass Plugin Context Message C# Exception to Form Script (Javascript) during CRM Ribbon Calling Action

Introduction & Example

In my previous post, I was talking about how to validate the mandatory fields when you click a ribbon utilizing the “Xrm.Page.data.save().then(successCallback, errorCallback);” method.

Now, what if we need one more advance validation such as from related entities or external service, we need server side code in plugin, not only because it is needed but also because it is easier for some .NET Programmer rather than writing in javascript and also we might need to use to validate it from anywhere.

Following my previous post, now, there is requirement to check whether the campaign has Campaign Activity defined or not before you make this campaign as launched.

Solution & Result

So, now I create a plugin to validate

Here is my Plugin Code:

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Xrm.CRM2015.Plugin.Campaign
{
    public class Campaign : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            #region must to have

            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

            // Create service with context of current user
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            //create tracing service
            ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

            #endregion

            if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
            {
                //create entity context
                Entity entity = (Entity)context.InputParameters["Target"];

                if (entity.LogicalName != "campaign")
                    return;
                else
                {
                    //if status reason is changed
                    if (entity.Contains("statuscode"))
                    {
                        if (((OptionSetValue)entity["statuscode"]).Value == 2) //Launched
                        {
                            //put MY Logic here
                            if (CheckIsHavingCampaignActivity(service, entity) == false)
                            {
                                throw new InvalidPluginExecutionException("Please add at least one Campaign Activity");
                            }
                        }
                    }
                }
            }

        }

        private bool CheckIsHavingCampaignActivity(IOrganizationService service, Entity entity)
        {
            //get the Campaign Activity from this Campaign
            QueryByAttribute querybyattribute = new QueryByAttribute("campaignactivity");
            querybyattribute.ColumnSet = new ColumnSet(true);

            //  Attribute to query.
            querybyattribute.Attributes.AddRange("regardingobjectid");

            //  Value of queried attribute to return.
            querybyattribute.Values.AddRange(entity.Id);

            //  Query passed to service proxy.
            EntityCollection retrieved = service.RetrieveMultiple(querybyattribute);

            if (retrieved.Entities.Count == 0)
            {
                return false;
            }
            else
            {
                return true;
            }
        }
    }
}

image

As we know that plugin message is very flat, simple, and straight-forward, the user can be confused with only one message in the centre of the screen, different from Javascript validation that you can add alert or you can add notification for CRM 2013 and above. This is one of the disadvantage over its plugin capability, you might need some additional art to reach user expectation in validation.

Now, I want to make sure that this message also passed to the Form Scripting and then I want to add more notification to the users telling them that to launch this Campaign, they need at least one Campaign Activity.

So, I would like to catch the exception from plugin to determine my next action.

From my previous post, we can add the errorCallback method and attach some function on it and I put debugger:

image

Yay,, horreey, we can grab the SaveErrorResponse message…

Now, I add some scripts:

function launch() {
    Xrm.Page.data.save().then(successCallback, errorCallback);
}

function successCallback() {
    //set status to Launched
    alert("Your campaign has been published");
    //then refresh if needed
    var Id = Xrm.Page.data.entity.getId();
    Xrm.Utility.openEntityForm("campaign", Id);

}

function errorCallback(saveErrorResponse) {
    if (saveErrorResponse.message.indexOf("Activity") != -1)
    {
        Xrm.Page.ui.setFormNotification("Hi, don't forget to add the Campaign Activity!!", 'ERROR');
    }
    //remember you can also clean up the notification

}
And this is the result:
image

Not only that, using this message you can do more than that.

Now, I want to put more spices on it..

That is to make the user more aware by set notification in field level

image

image

Then, after I add Campaign Activity, I can launch it successfully

image

The Code

function launch() {
    //do not forget to always clear it
    Xrm.Page.getControl("totalcampaignactivityactualcost").clearNotification();
    Xrm.Page.data.save().then(successCallback, errorCallback);
}

function successCallback() {
    //set status to Launched
    alert("Your campaign has been published");
    //then refresh if needed
    var Id = Xrm.Page.data.entity.getId();
    Xrm.Utility.openEntityForm("campaign", Id);

}

function errorCallback(saveErrorResponse) {
    if (saveErrorResponse != null) {
        if (saveErrorResponse.message != 'undefined' && saveErrorResponse.message != null) {
            if (saveErrorResponse.message.indexOf("Activity") != -1) {
                Xrm.Page.ui.setFormNotification("Hi, don't forget to add the Campaign Activity!!", 'ERROR');
                //remember you can also clean up the notification
                Xrm.Page.getControl("totalcampaignactivityactualcost").setNotification("Please put more activity");
                Xrm.Page.getControl("totalcampaignactivityactualcost").setFocus();
            }
        }
    }
}

Conclusion


*In CRM 2013 and above, I meet new favourite idol feature to love, that is Custom Action.

But, for CRM 2011 this feature is not available and since it is a new feature, I realize many developers still do not want to use it and plugin is more friendly for us since it has been accompanying us for years and we already knew its concept pretty well.

This method can be useful for some circumstances:

1. When you want validate something from Ribbon action or any other javascript that can trigger plugin through onsave event (in this example)

*You can also use oData action that can call plugin and return it.

2. When you want to check duplicate not using duplicate detection, then you can show the user which field is really causing the duplicate trouble by giving it special setFocus treatment

3. Do other action that need server side code

Now, you are be able to pass and obtain the plugin context message that you can use to manipulate the CRM Form UI.

But, remember, plugin will stop the saving if got exception, if you need process to keep going, there is no other choice than using Custom Action, Javascript, or you can put some field yes/no to pass the plugin.

Hope this helps!

Thanks.

CRM 2011/2013/2015 Mastering Plugin Registration Filtering Attributes

In plugin, we also can filter our logic to do or not to do something. Moreover, for Plugin onUpdate, for example : to prevent user to update AccountCode or Quote Type, or whatever, sometimes our logic only work, for example if Quote type has been changed, then you have to prompt user to say, you cannot change the Quote Type. But, if you only change the Address in Quote, you by default will also hit the plugin, so that you have to filter, like this :

if(entity.Attributes.Contains("tfp_quotetype"))
{
     throw new InvalidPluginExecutionException("Hi you cannot change Quote Type");
}

But, again your plugin also will be executed.

Let’s say in your plugin, you update this entity using this plugin, this also can cause infinite looping for updating.

In this post, I would like to know to filter Attribute that you don’t want to fire the Plugin.

By default, your plugin, after registered will not filter any attributes, meaning that, once your entity updated, no matter which attribute it is updated, it will trigger your plugin.

image

In my code, I did not do any filter :

image

Meaning that, any changes on every field, the plugin will be triggered

We try this


image

Let’s we do filter, I don’t want to trigger this if Ship Method field changed,

image

then I try to uncheck this attribute :

image

Then, I try to update the Shipping Method :

Will it work? Meaning that if I update the Shipping Method, this plugin will not be triggered?

Let me try!

I update the value from null to Airbone,

But, why I still get this plugin executed?

image

Then, you debug, yes it hits your plugin :

image

If you thought like that, it is wrong, and you’re still confused about Plugin Filtering

Actually plugin filtering attributes is used to trigger plugin or execute plugin once the field is changed, then which fields you want to trigger the plugin, you have make it as checked.

When you uncheck, it doesn’t mean that if you change the shippingmethodcode value (only this field you change in UI) so that your plugin will not be executed, since there are many other attributes that your checked before that has changed, it can be CRM system field that is hidden.

Those fields, for example :

ModifiedOn

image

And you realize that ModifiedOn field is in the checked list.

image

So, even though you are only update the Shipping Method, it also affect to other field, even though your target entity as input parameter does not contain shippingmethodcode, your plugin will still be executed.

Then, you try to not include the ModifiedOn and its friends

image

Then you try again, your plugin is still being hit

image

Then, what you do?

Okey, let’s understand this filtering, let’s change your mind to not focus to what you don’t want, but let’s focus to what you want.

You will fill difficult to uncheck every attribute that you don’t want.

For example, you don’t want to execute plugin after you change your shippingmethod only.

Now, let’s think that actually you want to execute your Plugin, IF QUOTE TYPE is changed.

Okay, what will do is :

1. Deselect all your attributes

2. Then check the ‘Quote Type’

image

Until your plugin step will be look like this.

image

Then stay tune in your plugin and trigger your quote, keep your breakpoint, then let’s you see if your plugin is triggered or not.

Wow, your plugin is not executed after you change the shipping method :

image

So, let’s you have very long code for calculation, it is better to filter attribute do you want to trigger and execute your plugin rather than you let any minor change in your field then run your long lines code of plugin.

Remember that QuoteId (uniqueidentifier) will be always be the InputParameter

image

Hope it helps and change your mind!
*copied from my other blog: http://aileengusni.blogspot.sg/2014/05/crm-2011-2013-mastering-plugin.html