If you never listen to another piece of advice from me again, please listen to this one:
Never, ever. Ever. Buy a cheap water hose.
Ever.
Buying a cheap water hose is like renting a $35 hooker. It may look good all packaged up, but once you start using it, you'll find a lifetime of grief and irritation.
And no Mom, I've never rented a hooker for any amount. Love you Mom.
It would appear, that my neighbors neither enjoy nor approve of the freedom I get from my washing my Ford truck in my new man-thong.
It would also appear that the Coppell constabulary have an equal lack of understanding.
I mean, come on. How was I supposed to know those kids would be outside playing in the yard on a beautiful Spring day?
And for the record, Officer Jackson, I was not mooning the patrol vehicle. You just can't clean your tire rims without bending over.
Sheez.
So it's a beautiful data here in Coppell, Texas and I decide to wash the bird poop and accumulated grime off the old Ford pickup.
The last time I washed my truck I accidentally dropped on the ground, the spray-nozzle thingy that inherited from the previous homeowner. Since it was about 10 years old, it fractured and I spent the remainder of the truck wash getting about 50% of the water on truck and the remainder in my face. Joy, joy, joy.
Anyway, I trekked down to the local ACE Hardware to purchase a replacement this morning.
Do you know how many spray nozzles ACE carries? Somewhere around 23. And ranging in price from $1.99 to $11.49 and from single function ( water coming out the end ) to omni-directional-water-volume-something-something-something.
So my question to you is: Do we actually need to have 23 choices of spray nozzle?
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. :)
I think that one of the most underutilized CRM data types is the BIT field. Most people are thinking bit = on/off, 1/0, etc., and they would be correct. But what the most facinating thing about this data type is how you can display it.
Once you have created the Attribute and added it to the Form, double-click on the field to show its properties. Click on the Formatting Tab.
Take a look at the Control Formatting section. This is where you can change the display properties of the BIT field, of which there are three:

Now this may not seem like a big deal, but it really does open up user interface design possibilities.
In addition, you don't have to stick to the standard Yes/No choices. Remember, this is a binary selection, which means you have two choices.
What if you replaced Yes and No with Married and Single? That would give you fexibility of design while restricting the data entry to one of two choices.
It's really nothing more than a two-selection picklist, but with extra design options.
Let me state up front that I really don't know how practical this code will be, but I did it as an intellectual exercise and because I figured that the project that I am currently working on would need it at some point.
Note: If you copy the following code for use in CRM, copy it to notepad first and replace every instance of a single and double quote with the quotes that you can type in from the keyboard. My blog engine automatically converts quotes from standard to "fancy" and those fancy quotes will not be understood by JavaScript and give you a page error.
The idea for this code is to just cycle through each table cell it finds of a specific style type and remove any currency symbol it finds.
Insert the following into the Form OnLoad event.
/* Specify the currency symbol for your part of the world*/
var sCurrencySymbol = "($)";
/*
CRM tab IDs are numbered tabx – taby, from 0 to the number of tabs-1.
For each tab that you would like to alter, insert its ID into the array
myarray.
For example, if you only wanted to alter the field labels on the General Tab
your code would be:
var myarray = new Array('tab0');
*/
var myarray = new Array('tab0', 'tab1');
for (var loop in myarray)
{
/* get a hande to the tab containing our form fields */
var tabs = document.getElementById(myarray[loop]);
/* locate all of the HTML Table structures on the Tab */
var tables = tabs.getElementsByTagName("table");
/* loop through all of the Tables on the Tab */
for (var i = 0; i < tables.length ; i++)
{
/* Locate all of the table Cells within the specified Table */
var td1 = tables[i].getElementsByTagName("TD");
/* loop through all of the Cells in the Table */
for (var i2 = 0; i2 < td1.length ; i2++)
{
var sElem = td1[i2];
/* So we're looking for Cells that have a Class of n (for number)
If we find that class, we look to see if the text within the Cell
contains our currency symbol. If it does, we delete it by replacing
it with a blank string.
*/
if ((sElem.getAttribute('className') == "n") &&
(sElem.innerHTML.indexOf(sCurrencySymbol) > 0))
sElem.innerHTML = sElem.innerHTML.replace(sCurrencySymbol, "");
}
}
}
You may have noticed that when you create a Quote for an Account, much of the Account's data does not get populated into the Quote fields. Ship-To addresses are a perfect example.
This is caused by not having the Entity Mapping between Account and Quote fully populated.
To change this, you need to alter the Entity Mapping by performing the following these steps:
- Select Settings, Customization, Customize Entities
- Edit the Account Entity
- Click Relationships
- Locate the relationship between Account and Quote
- Edit that relationship
- Click Mappings
- Click New
- From the Account Attribute list on the left, select the Address Attribute you wish to map to Quote
- On the Quote Attribute list on the right, select the appropriate Ship To Attribute that matches the Account Attribute.
- Click OK
- Repeat steps 7 – 10 for each part of the address that you need to Map.
- Save your changes
- Publish All Customizations.
- Test it.




