Monday, July 21, 2008

Client Side Scripting - More JavaScript Code - Part 4 (STUNNWARE)

STUNNWARE

I published the last article of the "More JavaScript" series more than half a year ago and thought that there wasn't too much more to say. Seems that I was wrong with that assumption, so here's the fourth part. I also updated the JavaScript Snippets Directory accordingly.

Maximizing a form

Put the following two lines of code into any OnLoad event to maximize the form:

window.moveTo(0,0);
window.resizeTo(screen.availWidth, screen.availHeight);

moveTo moves the window to the specified location and resizeTo resizes it. screen is a global object and give you the available screen width and height in the corresponding properties.

Using a toolbar button to open a referenced entity

Let's say you have added a lookup field to a form referencing one of your custom entities and you have added a toolbar button in isv.config.xml. When clicked it should open the entity shown in the lookup field, which basically is the same as clicking the link in the lookup itself.

Here's the code:

var lookup = crmForm.all.your_lookup_field;

if ((lookup != null) && (lookup.DataValue != null)) {
    var objectTypeCode = lookup[0].type;
    var objectId = lookup[0].id;
    var url = '/userdefined/edit.aspx?id=" + objectId + '&etc=' + objectTypeCode;

    window.open(url);
}

Note that when using a system entity, you have to replace /userdefined/edit.aspx with the appropriate edit URL of the system entity. The code first checks for the availability of the lookup field. If it's not included on the form (lookup will be null) or no data value has been set then no action is performed; otherwise the complete edit URL is stored in the url variable and passed to the window.open method.

Calculating the sum of two or more fields

Though it seems straightforward to sum up field values in a form, you can easily run into problems with null values. Here's a sample script that sums up three fields (your_field1, your_field2, your_field3) and stores the sum in your_sum:

//A field is accessed with crmForm.all.<the_field_name>
//A field value is accessed through it's DataValue property
var value1 = crmForm.all.your_field1.DataValue;
var value2 = crmForm.all.your_field2.DataValue;
var value3 = crmForm.all.your_field3.DataValue;

//The DataValue of an empty field is null, so in order to
//sum up the values, you have to check for null values
value1 = (value1 == null) ? 0 : value1;
value2 = (value2 == null) ? 0 : value2;
value3 = (value3 == null) ? 0 : value3;

//Setting a value follows the same rules used for retrieving
crmForm.all.your_sum.DataValue = value1 + value2 + value3;

Calculating the total charge based on actural duration, hourly rate, trip charge and tax rate

Instead of trying to make this one generic, I'm repeating the original question:

"I created 4 attributes under the Case entity as follows:

  • new_hourlyrate - picklist (95.00 and 125.00 for values)

  • new_taxrate - picklist (.06 and .07 for values)

  • new_tripcharge - picklist (0.00, 15.00, 30.00, 60.00 for values)

  • new_totalcharge - money

I need to populate the totalcharge field based on the actualdurationminutes attribute with the following math equation:
totalcharge = actualdurationminutes/60 (to get hours) multiplied by the hourly rate, add the tripcharge, multiplied by the tax rate. Take that value and add it to the hourly rate multiplied by the hours, and add the trip charge, to get total charge."

And here's the code:

var hourlyRateField = crmForm.all.new_hourlyrate;
var taxRateField = crmForm.all.new_taxrate;
var tripChargeField = crmForm.all.new_tripcharge;
var totalChargeField = crmForm.all.new_totalcharge;
var actualDurationMinutesField = crmForm.all.actualdurationminutes;

//Sanity check: if at least one of the fields is not available on the form,
the following condition is not met

if (hourlyRateField && taxRateField && tripChargeField && totalChargeField && actualDurationMinutesField) {

    var hourlyRate = (hourlyRateField.DataValue == null) ? 0 : parseFloat(hourlyRateField.SelectedText);
    var taxRate = (taxRateField.DataValue == null) ? 0 : parseFloat(taxRateField.SelectedText);
    var tripCharge = (tripChargeField.DataValue == null) ? 0 : parseFloat(tripChargeField.SelectedText);
    var actualDurationMinutes = (actualDurationMinutesField.DataValue == null) ? 0 : parseFloat(actualDurationMinutesField.DataValue);

    var totalCharge = (actualDurationMinutes/60 * hourlyRate) + tripCharge;
    var totalTax = totalCharge * taxRate;

    totalChargeField.DataValue = totalCharge + totalTax;
}

Note that in the above code parseFloat uses the SelectedText of the picklists instead of the DataValue.

Changing error messages in CRM forms

Sometimes the error messages displayed when entering an incorrect value may not be correct. An example from a Dutch system: when a user tries to input a date by hand, for example 14/05/1998 an error is displayed because the correct format is 14-05-1998. However the error message tells you to specify the date as D/M/YYYY, which isn't correct.

You can change these error messages on the fly by simply replacing the appropriate variable in OnLoad. Note the error message and search for it in in the page source. You will find something like this:

var LOCID_ALERT_ENTER_VALID_DATE = "De opgegeven datum is ongeldig. voer een datum in met de notatie: D/M/YYYY.";

To change it, put the following line in your OnLoad event:

LOCID_ALERT_ENTER_VALID_DATE = "De opgegeven datum is ongeldig. voer een datum in met de notatie: DD-MM-YYYY.";

Setting a custom date field to another date minus 60 days

Date calculation problems are still popping up in the newsgroups, so here's another quick example. Let's say you want to calculate a date based on the value of the effectiveto field in your crmForm minus 60 days. Here's the code:

var effectiveTo = crmForm.all.effectiveto.DataValue;

var remindOn = new Date(
    effectiveTo.getYear(),
    effectiveTo.getMonth(),
    effectiveTo.getDate() - 60);

or

var effectiveTo = crmForm.all.effectiveto.DataValue;

var remindOn = new Date(
    effectiveTo.getYear(),
    effectiveTo.getMonth() - 2,
    effectiveTo.getDate());

The difference between the codes is that the first subtracts exactly 60 days, whereas the second subtracts two months, which is between 58 and 62 days.

Changing the default height of the lookup window

This is an unsupported change, but if you want to change the initial size of a lookup window, open /_controls/lookup/lookup.js in the CRM web and search for the function BuildFeatures(lookupStyle). Inside of this function search for the following:

switch (lookupStyle)
{
case "multi":
oFeatures.height = "460px";
oFeatures.width = "600px";
break;
case "single":
oFeatures.height = "488px";
oFeatures.width = "600px";
break;

oFeatures.height and oFeatures.width are the initial lookup dimensions. After changing them, clear your browser cache to reload the include files the next time a lookup is accessed, otherwise IE will still use the cached values and you don't see a difference.

Accessing the previous field value in OnChange

Sometimes you need to know the previous field value in an OnChange event or you need to know the initial value after the form has loaded. This information of course is lost in OnChange, as the field value already has changed. Here's a simple workaround:

// OnLoad event
// no var statement here to declare a global variable

_oldDateValue = crmForm.all.the_fieldName.DataValue;

// OnChange event
if (_oldDateValue == null) {
    //no previous value
}

else {
    var currentValue = crmForm.all.the_fieldName.DataValue;
    DoStuff(currentValue, _oldValue);

    //update the old value with the new value, if appropriate
    _oldDateValue = currentValue;
}

Automatically calculate the tax value in an invoice line (invoicedetail)

CRM does not calculate the tax amount for you and if you want to automate it you have to add custom script. Sp here's an example for the invoicedetail. In the OnLoad event of the invoicedetail form add the following:

CalculateTax = function() {

    var pricePerUnit = 0;
    var quantity = 0;
    var manualDiscount = 0;

    if (crmForm.all.priceperunit.DataValue != null) {
        pricePerUnit = crmForm.all.priceperunit.DataValue;
    }

    if (crmForm.all.quantity.DataValue != null) {
        quantity = crmForm.all.quantity.DataValue;
    }

    if (crmForm.all.manualdiscountamount.DataValue != null) {
        manualDiscount = crmForm.all.manualdiscountamount.DataValue;
    }

    crmForm.all.tax.DataValue = (pricePerUnit * quantity - manualDiscount) * 0.175;
}

In the OnChange events of priceperunit, quantity and manualdiscountamount add:

CalculateTax();

Change 0.175 (17.5%) to the tax you need to charge. You can also add a new field instead of using a fixed value in the script code.

Performing an action when a CRM form closes

If you want to execute your script whenever the form closes, whether it is saved or not, you can subscribe to the onunload event:

window.onunload = function() {
    //add code here
}

Hooking into the "Lookup Address" feature in the order form

To get notified when a user presses the "Lookup Address" button in the order form, put this code into the order's OnLoad event:

if (document.all._MBLookupAddress != null) {
    document.all._MBLookupAddress.onclick = function() {
        LookupAddress();
        alert("Address lookup closed");
    }
}

The alert pops up after the address lookup dialog closes but there's no way to distinguish if the user selected an address or canceled the dialog. Of course this is an unsupported customization as it uses undocumented functions.

Changing the form title (not the browser title)

A CRM form displays the primary field of an entity in a large bold font just below the toolbar buttons. If you want to change the displayed text, put the following code into your OnLoad event:

var cells = document.getElementsByTagName("td");

for (var i = 0; i < cells.length; i++) {
    if (cells[i].className == "formTitle") {
        cells[i].innerText = "Ticket: 123456";
        break;
    }
}

Passing parameters from a toolbar button to a CRM form

Sometimes you add buttons to a form's toolbar that simply create a new entity. However in the created entity you need to know if it was created from your toolbar button or not. Here's an easy solution to pass an additional parameter that you can check in the OnLoad entity of the new entity form.

First of all let's start with the toolbar button. It usually has a Url attribute like "/userdefined/edit.aspx?etc=10018". To differentiate add an additional parameter like "/userdefined/edit.aspx?etc=10018&template=1".

Note: using the entity type code is dangerous, as it may break when deploying your solution to another server.

In the OnLoad event of the target entity (with object type code 10018) use the following code to extract the template parameter from the query string:

var QueryString = ParseQueryString();
var template = QueryString["template"];

alert(template);

if (template == "1") {
    // "New Template" clicked
}

else {
    // "New" clicked
}

function ParseQueryString() {

    var dict = new Object();

    if ((document.location.search != null) && (document.location.search != "?")) {
        var qsParts = document.location.search.substr(1).split("&");
        var index;

        for(index in qsParts) {
            var keyValue = qsParts[index].split("=");
            dict[keyValue[0]] = unescape(decodeURIComponent(keyValue[1]));
        }
    }

    return dict;
}

The basic idea is passing additional parameters to the form and reading them in OnLoad. As the default "New" button does not add the template parameter, you can use it as an indicator which button initiated the creation of the new entity.

Aborting an OnChange operation

Again I'm posting the original question to better understand the solution: "I populated  a picklist with some values and add code to the OnChange() event. When users change the picklist value by selection in the dropdown, a message box will pop up and ask the user to confirm the change. If the user chooses No, how could I restore the original value selected in the picklist?"

// OnLoad
// Note that the "var" keyword is missing intentionally to declare prevPicklistValue as a global variable
prevPicklistValue = crmForm.all.the_picklist.DataValue;

// OnChange of prevPicklistValue

var currentPicklistValue = crmForm.all.the_picklist.DataValue;

if (prevPicklistValue == currentPicklistValue) {
 //Reaching this line when restoring the previous value
 return;
}

var answer = window.confirm("Click Ok to proceed or Cancel to abort the operation.");

if (answer) {
 //User selected OK -> Save the current value as the last accepted value
 prevPicklistValue = currentPicklistValue;
}

else {
    //User selected Cancel -> Restore the previous value.
    crmForm.all.the_picklist.DataValue = prevPicklistValue
}

2 comments:

Jose M. said...

Hi.
I have one question about the code that calculates vat in invoice detail.
Let's assume this :
We change ispriceoverriden value to true and you introduce a value in the priceperunit attribute. We save the invoicedetail.
If we change the ispriceoverriden value to false the system doesn't calculate the vat properly.
¿How can we fix this issue?
Kind regards.

Bill Owens said...

Jose M,

I'm not sure. I have not worked with the VAT as of yet. However, I will be at Convergence next week and I will put the question to Microsoft.

Bill