In the article, Including custom JavaScript files in MSCRM 3.0, I discussed using an external JavaScript code library to help reduce maintenace costs.  Seemed like a good idea, at the time.  Then I got to thinking about the CRM Laptop client and how it is disconnected from the main CRM server and realized that I had just created an unsupportable and unviable installation.

Shorely thereafter, Michaeljon Miller posted an article discussing the same situation: Using the CRM SDK offline

So I'm thinking I'm relatively screwed, uh, technically speaking, of course.

I was also starting work on a custom mortgage calculation entity that contained about 40 different formulas. And it got me to thinking about how, and more importantly, where, I was placing my JavaScript calculation code.

I was basically converting an Excel worksheet that had all of the calculations necessary to make the Mortgage Calculator function.  The problem is, Excel allows you to reference cells from other cells and automagically perform calculations when any referenced cell changes value.

What I needed was a method for replicating that exact functionality. The only problem was knowing where to put the calculations.  Ideally, I would put them behind the Field that contained the value I wanted to calculate. That would simplify the calculations by retaining them in a singular location that would automatically update itself.  The only issue with that idea was the fact that most of these Fields were not meant to be edited. They would be read-only.  Since the OnChange event doesn't fire unless you manually change and leave a Field, the calculation would never fire.

It was just then that I remembered seeing this really neat looking CRM 3.0 client-side function called FireOnChange(). 

What Does the FireOnChange() Do?

Well, it fires the Field's OnChange() event.  Pretty cool huh?

 

Starting from Scratch

So let's rewrite the solution we created in our original article using supported and therefore supportable technologies. Here is our user interface:

and here is our JavaScript calculator:

crmForm.all.new_total.DataValue=
crmForm.all.new_price.DataValue*
crmForm.all.new_quantity.DataValue 

Again, we can put the above code in the OnChange events of Price and Quantity, but that really doesn't help our goal of maintainability.  So I'll tell you what, let's put that code only in the Total field's OnChange event.  And before you start calling me eight kinds of idiot, let me finish.

In the OnChange event for both Price and Quantity, I want you to put the following code:

crmForm.all.new_total.FireOnChange();

Now, go Preview the form and enter amounts into Price and Quantity.  It should have updated Total with the correct number.

Caveats

I only ran into one issue using FireOnChange().  You can't execute a second FireOnChange() event while an existing FireOnChange() event is executing. For example:

  • I add a Grand Total Field to the form.
  • At the bottom of the Total Field's OnChange() event, I add:
    GrandTotal.FireOnChange().
  • This means when Price executes Total.FireOnChange(), Total in turn executes GrandTotal.FireOnChange().
  • This will result in something called a Stack Error.

My guess is that the Stack Error is caused by the FireOnChange() being called recursively and it wasn't built for that.

To make matters worse, the actual error specifies Line 0 as the location.  I had 40+ separate calculations so it was of no help at all.  It was only after a number of hours of manually tracing and debugging that I finally figured out what was happening.

Other Considerations

You may be wondering why I told you to put the FireOnChange() into both Price and Quantity fields and not just the Quantity field. That is because you can never count on a user doing the exact thing in the right order. 

So, if you are thinking that users will always enter Price, then Quantity, you're setting yourself up for failure, since they could change their mind and change the Price for some reason. Without the total.FireOnChange() in Price's OnChange event, we would never pick up that change. That would result in a bad user experience and a support call.

In Summary

I can't stress enough how much proper planning will help you here.  My biggest mistake was not knowing and understanding that I couldn't recursively call FireOnChange() and when I found out, I had already had all of my calculations in place which makes for a bunch of spaghetti-code.

So, figure out where you need your calculations, which ones can be stand-alone and which need to be executed remotely ( via FireOnChange() ).  After that, it is simply a matter of defining the execution rules for the calculation and making sure that no one calculation calls FileOnChange() recursively.

Next Steps 

I have it on my list to create an unsupported application that will read the Form from the database and extract the JavaScript code and stick it into a Word document for documentation purposes.  I'll let everyone know when I'm done with that.  I'll probably charge a gazillion dollars (US) for it too.

I can't get to Vegas on my looks, after all. :)