I was trying to fix a small bug but I was getting bogged down with the business logic (which I didn’t need/want to change) on the form.
The problem was there was lots of business logic which kept disabling the fields I wanted to change.
I tried briefly to understand the business logic but it was complex and detailed and I wasn’t really interested in understanding the form logic and/or briefly amending the JavaScript onload. All I needed to do was to enable an optionset on the form, which would enable a ribbon button.
So it’s time for a bit of off the cuff hacking
I loaded the form
Press F12 to bring up the debugger window
You can use the console to manipulate the fields and enable/disable certain fields.
You have access to the CRM JavasScript objects
So I used the Xrm.Page, selected the control (not the field attribute) and did a setDisabled(false); which enables the control. Xrm.Page.getControl(“customerid”).setDisabled(false);
This didn’t enable the ribbon button because although the JavaScript function which is used by the ribbon button (e.g. the enable rule for the Ribbon button) the ribbon hadn’t been refreshed.
So I used the console to do a Ribbon Refresh
Xrm.Page.ui.refreshRibbon();
Fantastic, now my field was enabled and the ribbon button was enabled and ready to be pressed.
Other uses for F12 JavaScript debugger is to get the guid of the record, which I did on this blog post
Browser bookmark
An alternative to bringing up the F12 debugger is to create a bookmark, this will enable you to pass javascript into a form before loading it.
The blog post below created a God Button, which enables all fields
I found myself writing this tried and tested code, which I have written many times before
if (entity.Contains("salutation"))
{
title = ( string)entity.Attributes["salutation"];
}
before you can get a value from the entity attributes you need to check the field is included in the list of parameters. I traditionally use to do this using entity.Contains(“fieldname”), if the field specified exists in the list of parameters it will return true. The reason this works is because CRM doesn’t pass null values in the parameter list, so if its null it won’t exist.
Often in code you will lines and lines of Entity.Contains code, checking the field exists before assigned the value.
The article was about using Entity.GetAttributeValue and in this blog he linked to an excellent and detailed explanation of how Entity.GetAttributeValue works from CRM MVP Dave Berry
I had a classic case of reading how to do something in a better way but automatically doing it the way I always did it, except I now had a chance to do it differently and that’s what I did.
What is the Entity Class
The Entity class is the base class in CRM development and this class holds some key details about what entity it is
LogicalName – logical name of the record e.g. contact,account, lead Id – ID of the record
The other important values are the Attributes. The attributes are a collection of attributes, e.g. all the fields in the entity/record.
What is the difference between an entity and a record
Entity
The entity e.g. account, contact, lead. This is the design of an entity but doesn’t hold any values. I would liken this to an object and an instance of an object.
Record
A record is an entity but with values, e.g. someone has created a New contact and this creates an instance of an entity (the design) and makes a record with individual values (e.g. name = Hosk, description = marvellous);
It uses object to enable it hold the various different variable types possible, it will mean if you are using late binding you will need to cast the values into the type you are expecting.
Trying Entity.GetAttributeValue
I thought I would try it out because it would save me wrapping if statements round things and seemed a better way to do things.
initially I removed the contains if statement and I got a variable doesn’t exist type error, hmm this isn’t meant to happen , I then realised it was a user error
I actually hadn’t typed it in but just removed the if title = (string)entity.Attributes[ “title”];
I then added the proper method title = entity.GetAttributeValue(“title”);
When developing I usually test my plugin code by making my plugin code take a iOrganisationService instance and an entity and put these in a separate class. This means the plugin can call my class but more importantly it means I can call this class by creating an iOrganisationService and an Entity object and not worry about any of the other plugin stuff. This enables me to call my new code in a console app which creates an IOrganisationService connected to the Dev environment and then do a service.Retrieve(“contact”, guid, columnset);
So I kicked of my console app and this retrieved a contact record and selected the title record and in this case the value was null and the code set the string title variable to null.
So I used this to retrieve a OptionSetValue and string and it worked fine.
it’s important to note this will bring null back if there is no value or the value is null.
Also remember for some types it will bring back the default value if something doesn’t exist and this might not be what your code is expecting. In Dave’s blog he has a handy table
Type
Return
Numerical (int, decimal, double)
0
Boolean
false
DateTime
DateTime.MinValue
Guid
Guid.Empty
Why isn’t Hosk using Early binding
A question you are asking is why is the Hosk using Entity instead of an early bound class like Contact or Lead. Great question, I’m glad you asked 🙂
The reason I am using the entity class is because the plugin was going to run on the contact and lead entity and maybe some other entities in the future. The fields had he same name on the different entities, this enabled me to write the code (using Entity) which would work on both Contact and Lead.
This article from Guido is great, it takes a question from the forum, an answer from CRM MVP Scott Durow, followed by some hard work by Guido to create the full solution, great work.
I will add another because I can, it’s from the snooze Berry, Dave Berry. It was written when Dave use to blog (pre snooze), although I have been informed from the great Berry himself he is busying doing great things with TypeScript and CRM
The investigation and problem I experienced where in CRM 2011 but I think it would be the same in CRM 2013, I had a problem with the ribbon javascript but I didn’t have a clue where it was because I had never seen a syntax error in the Javascript ribbon.
I put a debugger statement in the ribbon, so it would drop into the debugger. The reason you put the debugger statement into the Javascript code is because when the ribbon is loaded it is loaded in dynamic Javascript script block, this means when you press F5 it creates a brand new script file and you won’t have time to put a breakpoint in it if you bring up the debugger by pressing F12.
So I put my debugger line in and then the code hit my various display/enable javascript methods and I could step through them but the syntax error kept appearing.
I still wasn’t sure where/what the problem was.
I pressed F12 to bring up the debugger and this was bring up a syntax error
I turned on fiddler and found on one of the calls I was getting a nasty red triangle on one of the calls.
The thing which kept bugging me was it was a syntax error
I could see the code and it looked fine (although syntax errors are not obvious to the naked eye). The file compiled in Visual studio and pressing F12 to get the debugger didn’t throw an error, so this would suggest the javascript syntax was not the problem
frustrating
I fired up the Visual Ribbon Editor for CRM 2011/2013
In the Visual Ribbon Editor here you have display rules, enable rules and action.
There was no disable rule
there was an enable rule and an action rule which both called a javascript function, you can see below
Slowly something clicked, the error was trying to call the action, why was it calling the action whilst loading the form. Logically the ribbon has to make sure the Javascript method exists so when the user presses the button something will happen but why was it triggering a syntax error whislt loading the ribbon?
Syntax error, ribbon load…..hmmmmm
Look at the two rules, although I’m not showing you the actual file, look at the position of the library and the function boxes!
Finally after hours and dragging more eyes to the problem, my colleague found the problem by exporting the Ribbon xml, hazaaar the problem was obvious
The library and function values from the enable rule had been copied into the action rule, only this didn’t work because the function and library boxes are in different places.
He found the library – which should be the javascript library was pointing to a function.
Ribbon object not found
A new day a new ribbon problem. Today on one of the forms the ribbon wasn’t loading and I was getting an error
SCRIPT5007: Unable to get value of the property Ribbon ‘value’ of undefined or null reference
The first thing I checked was to see if the function and library values were correct, they were.
In the Javascript files we use we build up the names spaces so the function calls are like this
Hosk.Functions.Account.Ribbon.NewAccount()
To find the problem I had to put debugger statements in the enable/action javascript rules and walk through them.
Eventually I found one where Javascript file hadn’t been loaded, so when it it was trying to call a function the javascript file was loaded, the function didn’t exist and we got a null reference.
I have had this request from a few customers over the years and it’s converting and using the description of an email is trickier than you initially think it will be. This is a question which often appears on the CRM forum quite regularly as well.
The reason for this is although the email looks like plain text when you see it in CRM, when you get the value of the Email entity description (main body of the email) you will also get all the HTML tags.
The scenario I had when I was doing this was the customer wanted to convert an email into a case and get the values from description.
So to do this I had to strip out the HTML tags and a few other things, which I did using a plugin
I would also recommend anyone who wants to learn CRM development to go through Steve’s other CRM videos, many of them are in CRM 2011 but the code would still work in CRM 2013.
I had piece of functionality I did at work recently and it had a few interesting parts, so I thought I would blog it down. You can read the blog or watch the video
The required functionality required the ability to update multiple appointments, in this deployment we have created a new appointment entity called Client appointment but basically I needed to update multiple appointments.
The user wanted to specify a cancellation reason.
I was initial thinking of using a workflow and selecting lots of appointments but because they needed to specify a cancellation reason then this wasn’t an option
With input required a dialog seemed like a good option.
The problem I quickly found was Dialog’s can only be triggered on one record at a time and not multiple records like workflows.
Contact Dialog
So the plan was changed to be run on the contact. I would have a dialog which ran on the contact entity the user would be prompted for a start date, end date and a cancellation reason.
I would call a Custom Workflow from with the dialog and pass selected values to it and then the custom workflow would do a query, select all the appointments, cancel them and then create a reschedule task and assign it to a reschedule queue.
I usually trigger code changes from plugins and wondered what circumstances would I move that code to be triggered into a Custom Workflow but this a good example.
This code isn’t really suitable to be triggered from a plugin because there isn’t anything being changed/updated or created so there is no natural plugin trigger point.
Thinking about this I can see that within certain workflows and dialog’s is the perfect place to call a custom workflow, particularly when nothing has been updated or modified and you want to trigger those changes and the changes are going to be complex which needs a code to do. This is a good example because the code needs to retrieve a lot of appointments linked to a contact, this functionality couldn’t be achieved in workflow or definitely not easily.
After re-reading the requirements I noticed we needed to specify a start time and end time and this would also work in a slightly unusual manner. If the user specified 08:00 to 12:00 with a start date and end date which went over more than one day. The user wanted the morning appointments to be cancelled for each day specified but the afternoon appointments to be left alone. This would provide a tricky piece of querying which I will go into later.
In theory this seemed like it would work, so I initially created a skeleton Dialog and a Custom workflow which logged the fields.
The first little problem I had was, how to show the time. The choices I had were text single response, this would involve the user typing in the time, I didn’t like this option because it wasn’t easy for the user and had the potential for them to mistype.
The other option was to create an optionset with an hour value for each hour. e.g. 01:00, 02:00 etc. In then end I chose this option.
The user also wanted the ability to not select a time and this would mean they wanted to cancel the whole days appointments.
So I created the the optionSet with a default value of – Full Day and then 01:00 label would have the value of 1, 23:00 would have the value of 23.
SET THE CORRECT DATA TYPE
One mistake I made at this point was to incorrectly set the Data type to text. This is a real schoolboy error because once you set this you can’t change it. So I had set it to text gone through adding all 24 hours and then realizing and had to do it all again, very frustrating.
What is also very frustrating is the fact you can use an optionSet created in CRM, you have to copy the values again. This means there isn’t any schronisation between the OptionSet in your dialog and entity. It also means you may have to create the values numerous times in a dialog because you can’t reuse it in a dialog.
In this example I had to create the same OptionSet for start date and end date. Tedious indeed.
The cancellation reason was the same kind of thing, I had to copy an OptionSet inside CRM and recreate it and give the OptionSet values the same as the values in the CRM OptionSet field, this way I could pass the value into the Custom Workflow and then it could use the value to set the OptionSet.
Warning Popup Message
When I did a demo of the functionality to the client, they said they would like a warning to pop up, if the dates specified were within 3 days. The reason for this is if you cancel appointments with 3 days of today, it’s difficult to reorganize these so users should be certain they want to cancel these appointments.
Sometimes when someone asks you about how to achieve some functionality in CRM, you can be momentary stumped and can’t see how to do it and this was one of those moments.
I couldn’t see anyway to pop up a warning message.
It was only later that I was talking through the problem with the team that the obvious answer came to light.
Dialog’s are made up of pages and on each page you have a one or more prompt and responses.
So if I wanted a warning message all I needed to do was to create a new page, show the information the user had entered earlier in the dialog and ask them to confirm if they wanted to proceed with the changes using an OptionSet with Yes/No.
What I did was to check if the start date was greater than three days, if so put a confirmation page but if the start date was less than three days I would add the words warning.
Sensible variable names
This is the same for workflows but when you are adding conditions or adding steps/prompt and responses make sure you use easily understood variable names because later you will need to use these fields to update an entity/pass to a custom workflow so you need to be able to easily understand what each field is.
Here is the Custom Workflow being called from within the dialog
Below you can see the main code for the Custom Workflow, you can match it up with the screen shot above. The actual code is in this class commonClientAppointment class
public sealed class CwaBulkAppointmentStatusChange : CodeActivity
{
/// <summary>
/// Gets or sets Min Wait Time
/// </summary>
[Input("Start Date")]
[RequiredArgument]
public InArgument<DateTime> StartDate { get; set; }
/// <summary>
/// Gets or sets Max Wait Time
/// </summary>
[Input("End Date")]
[RequiredArgument]
public InArgument<DateTime> EndDate { get; set; }
/// <summary>
/// Gets or sets Max Wait Time
/// </summary>
[Input("Start Time")]
[RequiredArgument]
public InArgument<int> StartTime { get; set; }
/// <summary>
/// Gets or sets Max Wait Time
/// </summary>
[Input("EndTime")]
[RequiredArgument]
public InArgument<int> EndTime { get; set; }
/// <summary>
/// Gets or sets Max Wait Time
/// </summary>
[Input("Cancellation Reason")]
[RequiredArgument]
public InArgument<int> CancellationReason { get; set; }
/// <summary>
/// Gets or sets Task Reference
/// </summary>
[Input("Contact")]
[ReferenceTarget("contact")]
public InArgument<EntityReference> Contact { get; set; }
/// <summary>
/// Executes the workflow activity.
/// </summary>
/// <param name="executionContext">The execution context.</param>
protected override void Execute(CodeActivityContext executionContext)
{
// Create the tracing service
ITracingService tracingService = executionContext.GetExtension<ITracingService>();
if (tracingService == null)
{
throw new InvalidPluginExecutionException("Failed to retrieve tracing service.");
}
tracingService.Trace("Entered CwaBulkAppointmentStatusChange.Execute(), Activity Instance Id: {0}, Workflow Instance Id: {1}",
executionContext.ActivityInstanceId,
executionContext.WorkflowInstanceId);
// Create the context
IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
if (context == null)
{
throw new InvalidPluginExecutionException("Failed to retrieve workflow context.");
}
tracingService.Trace("CwaBulkAppointmentStatusChange.Execute(), Correlation Id: {0}, Initiating User: {1}",
context.CorrelationId,
context.InitiatingUserId);
IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
try
{
DateTime startDate = this.StartDate.Get(executionContext).ToLocalTime();
DateTime endDate = this.EndDate.Get(executionContext).ToLocalTime();
int startTime = this.StartTime.Get(executionContext);
int endTime = this.EndTime.Get(executionContext);
int cancellationReason = this.CancellationReason.Get(executionContext);
EntityReference contactRef = this.Contact.Get(executionContext);
CommonClientAppointment commonClientAppointment = new CommonClientAppointment();
commonClientAppointment.CancelHPAppointments(service, contactRef, startDate, endDate, startTime, endTime, cancellationReason);
}
catch (FaultException<OrganizationServiceFault> e)
{
tracingService.Trace("Exception: {0}", e.ToString());
}
tracingService.Trace("Exiting CwaBulkAppointmentStatusChange.Execute(), Correlation Id: {0}", context.CorrelationId);
}
}
Other interesting bits of code, below I set the start date to add the hours selected in the start time, unless it’s value is zero (which means whole day), which I then use the default time 00:00 and for enddate I give the value of 23:59 (e.g. the whole day)
The other tricky bit of code was time specified code. To recap if a time was specified then the appointments between the time should be cancelled on every day specified in the date criteria. To do this I calculated the difference in days and then looped around that many times and added a day each time I went round the loop.
e.g.
start date 01/01/2014
end date 07/01/2014
start time 07:00
end time 12:00
This would cancel all morning appointments for all the days (01, 02, 03, 04, 05 , 06, 07)
I had two options, I could either create one query with multiple date conditions or I could create one query and then repeatedly call this with the start and end date changed.
I couldn’t quite invisage doing one query so I called the query once for each day in the date range.
List<Entity> bookedAppointments = new List<Entity>();
if (startTime == 0 || endTime == 0)
{
//full days
bookedAppointments = CancelAppointmentsRetrieve(service, hpEntityRef, startDate, endDate, statusCode);
}
else
{
//time specified
int days = calculateDays(startDate, endDate);
int counter = 0;
DateTime loopEndDate = startDate.Date.AddHours(endDate.Hour);
while (counter <= days)
{
bookedAppointments.AddRange(CancelAppointmentsRetrieve(service, hpEntityRef, startDate, loopEndDate, statusCode));
startDate = startDate.AddDays(1);
loopEndDate = loopEndDate.AddDays(1);
counter++;
}
}
if (bookedAppointments != null && bookedAppointments.Count > 0)
{
this.UpdateBookedAppointments(service, bookedAppointments, cancelReasonInt);
}
<pre>