Wednesday, October 31, 2012

New "Geolocation" data type

[Updated:  2012-11-14

I apologize I made a typo.  My original post had the fields to be
Location_Latitude__s and Location_Longitude__s.

They should instead be

Location__Latitude__s and Location__Longitude__s.

Note the two underline characters between your field name and the "Latitude__s" or "Longitude__s" portion instead of one as I originally posted.  I have already updated my code below.
]


Currently the new Geolocation data type is fairly limited.  I'll let you read the limitations here.

However, there are a few things to note.

1. a Geolocation field is actually a component field.  It contains 2 pieces of information, Latitude and Longitude (and also an internally used information which is not exposed to us users or developers.)

2. you can use either degrees or decimal places to describe your location.  However, it is important to note that Salesforce always uses the decimal point version.  The degree option is purely for representation purposes.  In fact, when you're entering a geolocation information, you are entering the values in decimal places, and then it'll be displayed as degrees after the data is saved.  The decimal point version is probably the easier way to go.

3. If you have a custom field of this type, say, Location__c, as you know, this is a component field that actually contains 2 pieces of information.  In your SOQL, if you need to access the latitude or longitude, all you need to do is to drop the "__c", and instead append it with "__Latitude__s" or "__Longitude__s".  For example,

SELECT Location__Latitude__s, Location__Longitude__s FROM
Person LIMIT 1;

4. Seems to me the only main thing you can now do is to calculate the distance between two points.  You now have 2 new SF functions to use, Distance() and Geolocation().  However, Geolocation() cannot be used alone.  It is always used in conjunction with Distance().  You may want to refer to the manual for more information.  The manual probably explains the usage better than I can.  NOTE:  I am able to use Distance() in the WHERE clause of a SOQL, but I have not been able to use it in the SELECT clause of the SOQL.  I will look further into it.

Monday, October 29, 2012

Trigger to disallow deleting parent if children exist

Whenever you have a master-detail relationship, and you try to delete a parent record using the standard Salesforce interface, Salesforce will delete that record and any associated children records.


Let's say you have a parent "Invoice" object and the child is "Line Item".  Each invoice record may have zero or more Line Item child records.


If you want to not let users delete that record, then you'll need to write a trigger.  There are two ways to do that.

If you have been writing stored procedures forever like myself, my normal approach would be to do an SQL like that:

SELECT i.Id, Count(li.Id) AS Counter FROM Invoice__c i INNER JOIN
    Line_Item__c li ON i.Id = li.InvoiceId

Basically what you want to do is to go through each invoice in the parameter list and do a count of how many line items there are.  Unfortunately in SOQL you cannot include aggregate functions in sub-queries (aggregate functions can only be at the root-query level).

So, the next best thing is to just do a list of each Invoice record and associated Line Item records.  The trigger code looks like:


trigger DeleteInvoice on Invoice__c (before delete) 
{
    List<Invoice__c> invoices = [select id, (select name from line_items__r) 
        from invoice__c where
        id in :Trigger.old];

    for (Invoice__c inv: invoices)
    {
        if (!inv.line_items__r.isempty())
        {
            Trigger.oldMap.get(inv.id).addError('error goes here');
        }
    }
}


So what the above does is to first get a list of invoices and associated line items.  Then go through each invoice and see if there are any related line items.  If there are, then provide an error to that invoice.

What I found out is that I might be able to simplify that  little.  Instead of going through the Invoice__c first, I can go through Line_Item__c first, as this following trigger shows:


trigger DeleteInvoice on Invoice__c (before delete) 
{
    List<Line_item__c> lis = [select invoice__c from line_item__c 
        where invoice__c in :Trigger.oldMap.keyset()];

    for (line_item__c li : lis)
    {
          trigger.oldmap.get(li.invoice__c).adderror('error goes here');
    }
}


What this second trigger does is to simplify look at the child table and find those line items where the invoice id matches the invoice ids in the parameter list.  The invoices associated with these line items retrieved are the ones that you do not want to allow deletion.

It looks to me that the second trigger is shorter because it does not have to have the "if" statement.  I don't know if there is any substantial performance improvement, but the code looks somewhat cleaner.

What do you think?


Friday, October 26, 2012

Calling an APEX method from interface (like a button)

An Apex class is just a regular class.  Besides calling it from trigger or as a Visualforce controller, you should be able to call it directly from the frontend, such as when the user clicks a button.  There is a way to do that.

Say this is your standard class.

public MyClass
{
    public static String MyReply(String psInputA, String psInputB)
    {
        return psInputA + ' ' + psInputB;
    }

}

If you want to call the method from your button, you need to make some changes to the above class.  It has to be called as a webservice.  Basically what you need to do is to change the scope of the class to global, and indicate the method as a web service.  So the above needs to be changed to:


global MyClass
{
    webservice static String MyReply(String psInputA, String psInputB)
    {
        return psInputA + ' ' + psInputB;
    }
}

Now you can define your button action.  You can go to your object, create a new custom button (Create -> Objects -> [object name] -> Custom Buttons and Links.  You want to run a javascript, so select "Execute Javascript" in the Behavior picklist.  The code for your javascript should look like this:

{!REQUIRESCRIPT("/soap/ajax/25.0/connection.js")}
{!REQUIRESCRIPT("/soap/ajax/25.0/apex.js")}

var result = sforce.apex.execute("MyClass", "MyReply", {psInputA:"{!Invoice__c.Name}", psInputB:"YES"});

alert(result);

As you can tell, you can easily use expression statement to incorporate data.

Please try it and let me know if it works for you!

Using the standard Name field


Be careful what you save in the Name field - it is a text field, so if you have Names that are 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, then if you sort them, 1 is followed by 10 instead of 2.  If you really need to treat them as numbers, use a custom field of the Number type instead.