Just noticed there is a new blog post that addresses my last topic here at http://kingkoo-salesforce.blogspot.com/2013/09/winter-2014-give-me-back-my-code.html on the code coverage since Winter 14 was introduced.
I urge you to go take a good look.
By now I am used to going to Eclipse or Developer Console to view the code coverage. I still missed the fact I cannot see the information from my Apex Class page, but I think I am slowly understanding the reason behind it.
This blog was written by Josh Kaplan - he is the Product Manager for Apex at Salesforce.com
http://blogs.developerforce.com/engineering/2013/11/code-coverage-and-the-force-com-developer-console.html
Enjoy!
Showing posts with label Apex. Show all posts
Showing posts with label Apex. Show all posts
Friday, November 1, 2013
Thursday, September 19, 2013
Winter 2014 - give me back my Code Coverage!!!
Winter 14 is coming and for some it has already arrived.
While I was exploring some of the new exciting features of Winter 14, I was devastated to find that something has gone missing.
I have been writing a bit of Apex classes lately and as you may already know, Salesforce requires each Apex class to have accompanying test classes to test the code to ensure you have tested your code thoroughly before you deploy the code to production.
In the past, checking how well you have covered your code was a bliss. All you needed to do was to go to App Setup -> Develop -> Apex Classes and the information would be right there, under the "Code Coverage" column, such as this:
In Winter 14, however, the page becomes this instead:
What? The invaluable "Code Coverage" column has gone missing. What is going on??!?
I have submitted a Case, and the reply I received was that it is indeed a new "feature".
For now the only workaround I can think of is to make use of Developer Console, so all is not lost.
All you have to do is to open your Developer Console, click the "Tests" tab at the bottom - NOTE not the "Test" tab (singular) at the top, but the "Tests" tab (plural) at the bottom. A list of classes and the code coverage should be listed inside the right hand pane.
If you do not know where to find Developer Console, all you have to do is to click your name at the top left corner of the screen, and a pull down menu should pop up. Developer Console should be one of the options. Once you click it, a brand new window will appear.
Lastly, if you have any new development regarding this Code Coverage column, please drop me a note too!
While I was exploring some of the new exciting features of Winter 14, I was devastated to find that something has gone missing.
I have been writing a bit of Apex classes lately and as you may already know, Salesforce requires each Apex class to have accompanying test classes to test the code to ensure you have tested your code thoroughly before you deploy the code to production.
In the past, checking how well you have covered your code was a bliss. All you needed to do was to go to App Setup -> Develop -> Apex Classes and the information would be right there, under the "Code Coverage" column, such as this:
What? The invaluable "Code Coverage" column has gone missing. What is going on??!?
I have submitted a Case, and the reply I received was that it is indeed a new "feature".
For now the only workaround I can think of is to make use of Developer Console, so all is not lost.
All you have to do is to open your Developer Console, click the "Tests" tab at the bottom - NOTE not the "Test" tab (singular) at the top, but the "Tests" tab (plural) at the bottom. A list of classes and the code coverage should be listed inside the right hand pane.
If you do not know where to find Developer Console, all you have to do is to click your name at the top left corner of the screen, and a pull down menu should pop up. Developer Console should be one of the options. Once you click it, a brand new window will appear.
Lastly, if you have any new development regarding this Code Coverage column, please drop me a note too!
Thursday, January 24, 2013
BUG REPORT: Map's equals and hashCode methods.
Reading through Apex Developer's Guide (winter 13). There is a new section for Winter 13 that talks about how you can compare if two custom Apex classes are equal or not. The bug is not directly related to the equals and hashCode methods, but the bug is introduced here because from Winter 13, you are allowed to use non-primitive values as keys in maps.
You can find the documentation at this page:
http://www.salesforce.com/us/developer/docs/apexcode/Content/langCon_apex_collections_maps_keys_userdefined.htm
The following is the definition of PairNumbers class.
public class PairNumbers
{
Integer x,y;
public PairNumbers(Integer a, Integer b)
{
x=a;
y=b;
}
public Boolean equals(Object obj)
{
if (obj instanceof PairNumbers)
{
PairNumbers p = (PairNumbers)obj;
return ((x==p.x) && (y==p.y));
}
return false;
}
public Integer hashCode()
{
return (31 * x) ^ y;
}
}
The way it is implemented, if you create several instances of the class and add them to a Map collection, duplicate keys should be counted as one. For example, if you have lines of code like this:
Map<PairNumbers, String> m = new Map<PairNumbers, String>();
PairNumbers p1 = new PairNumbers(1,2);
PairNumbers p2 = new PairNumbers(3,4);
PairNumbers p3 = new PairNumbers(1,2);
m.put(p1, 'first');
m.put(p2, 'second');
m.put(p3, 'third');
You should now have two elements in the collection m, that is because p3 have the same key as p1. As a result, p3 replaces p1 in the collection m.
However, I noticed that, if I append the code with this line:
System.Debug(m.size());
and I run this code in Developer Console, the debug log will show 3 as the number of elements in the collection.
Strangely, when I run this on Eclipse, the debug log from there shows 2, which is the expected result.
This is a new feature introduced in Winter 13, and let's hope this gets fixed soon!
You can find the documentation at this page:
http://www.salesforce.com/us/developer/docs/apexcode/Content/langCon_apex_collections_maps_keys_userdefined.htm
The following is the definition of PairNumbers class.
public class PairNumbers
{
Integer x,y;
public PairNumbers(Integer a, Integer b)
{
x=a;
y=b;
}
public Boolean equals(Object obj)
{
if (obj instanceof PairNumbers)
{
PairNumbers p = (PairNumbers)obj;
return ((x==p.x) && (y==p.y));
}
return false;
}
public Integer hashCode()
{
return (31 * x) ^ y;
}
}
The way it is implemented, if you create several instances of the class and add them to a Map collection, duplicate keys should be counted as one. For example, if you have lines of code like this:
Map<PairNumbers, String> m = new Map<PairNumbers, String>();
PairNumbers p1 = new PairNumbers(1,2);
PairNumbers p2 = new PairNumbers(3,4);
PairNumbers p3 = new PairNumbers(1,2);
m.put(p1, 'first');
m.put(p2, 'second');
m.put(p3, 'third');
You should now have two elements in the collection m, that is because p3 have the same key as p1. As a result, p3 replaces p1 in the collection m.
However, I noticed that, if I append the code with this line:
System.Debug(m.size());
and I run this code in Developer Console, the debug log will show 3 as the number of elements in the collection.
Strangely, when I run this on Eclipse, the debug log from there shows 2, which is the expected result.
This is a new feature introduced in Winter 13, and let's hope this gets fixed soon!
Tuesday, January 15, 2013
How to show data from Map collection in sorted order inside apex:repeat
I was going through Salesforce's Visualforce Developer's Guide and Apex Developer's Guide the last few weeks over the Christmas holiday. In the Apex guide, for Map collection, the book warned that the "order of objects returned by maps may change without warning", and therefore we are asked to "not reply on the order in which map results are returned".
Then in the Visualforce guide, there is a section on "Referencing Apex Maps and Lists" section in the "Dynamic Visualforce Bindings" chapter. It talked about how we can use data from map in combination with the <apex:repeat> to display data in the map.
What good is it if it cannot display the data properly, especially if the data set is large?
I'm going to use the example in the Visualforce guide as a demonstration. It can be found on page 158 (if you are using Winter 13 of the Visualforce guide). I am also going to add just a little more records so it's easier to see how data is sorted.
The following is the code for the class:
public class p158_a_Blog_Example
{
public Map<String, String> directorMap {get; set;}
public p158_a_Blog_Example()
{
// set up the data in this constructor
directorMap = new Map<String, String>
{
'Kieslowski' => 'Poland',
'Gondry' => 'France',
'del Toro' => 'Mexico',
'Lee' => 'Taiwan',
'Cameron' => 'Canada'
};
}
}
This is the Visualforce page that makes use of this controller. I modified it a little so it displays the data in a table.
<apex:page controller="p158_a_Blog_Example">
<apex:pageBlock title="Directors List">
<apex:pageBlockTable value="{!directorMap}" var="dirKey" style='width:50%'>
<apex:column value="{!dirKey}" />
<apex:column value="{!directorMap[dirKey]}" />
</apex:pageBlockTable>
</apex:pageBlock>
</apex:page>
Using different DE orgs, I seem to get the results in different order, so the order of the data is indeed inconsistent. This is how the data came out this morning:
I would like the data to show Cameron, del Toro, Gondry, Kieslowski and Lee, in this order.
I need to modify the code, but the first lesson of all, if you need to preserve the sorting order, use LIST in your <apex:repeat> tag, do NOT use the map variable.
Now let's go back to the class and turn the Map into a List, so we can display the data sorted. It actually is very easy. All you need to do is to add a new List variable, set it and then sort it (via the sort() method of list collection).
The new class is modified as this:
public class p158_a_Blog_Example
{
public Map<String, String> directorMap {get; set;}
// new variable to hold the list of directors.
public List<String> directorList {get; set;}
public p158_a_Blog_Example()
{
// set up the data in this constructor
directorMap = new Map<String, String>
{
'Kieslowski' => 'Poland',
'Gondry' => 'France',
'del Toro' => 'Mexico',
'Lee' => 'Taiwan',
'Cameron' => 'Canada'
};
// Set the new List variable to be the set of directors using the addAll() method. After
// that simply all the sort function to sort the directors' names.
directorList = new List<String>();
directorList.addAll(directorMap.keySet());
directorList.sort();
}
}
The Visualforce also now needs to be modified so it is not using the map variable as the data set, but the list variable instead (which now contains a list of directors, sorted). NOTE: you still need to use the Map variable to access the country from which the director came from. The bolded, underlined text is the ONLY change you need to make to the Visualforce page:
<apex:page controller="p158_a_Blog_Example">
<apex:pageBlock title="Directors List">
<apex:pageBlockTable value="{!directorList}" var="dirKey" style='width:50%'>
<apex:column value="{!dirKey}" />
<apex:column value="{!directorMap[dirKey]}" />
</apex:pageBlockTable>
</apex:pageBlock>
</apex:page>
Well... still something is not quite right.
The list is now almost all sorted, except the Mexican director is displayed at the end. Turns out that when you call the sort() method on the List, it sorts the data based on the ascii code. The lower case 'd' (ascii code = 100) comes after 'L' (ascii code = 76). That is, the sort is case-sensitive.
In order to make the sort case-insensitive, what I found the easiest is to add two new variables.
A new Map variable (directorName) is created that maps between the director's name in upper case and the name intact. A new List variable (directorListIgnoreCase) is created to hold the director's name in upper case for sorting purposes. This List variable is used to determine the order of the directors. However the data stored in this variable is all in upper case. That's why you need the new Map variable directorName to find the director's name in its original form. Then you can use the original Map variable directorMap to get the country information.
This is the final class.
public class p158_a_Blog_Example
{
public Map<String, String> directorMap {get; set;}
// new variable to map the directors from all uppercase to the original name.
public Map<String, String> directorName {get; set;}
public List<String> directorListIgnoreCase {get; set;}
public p158_a_Blog_Example()
{
// set up the data in this constructor
directorMap = new Map<String, String>
{
'Kieslowski' => 'Poland',
'Gondry' => 'France',
'del Toro' => 'Mexico',
'Lee' => 'Taiwan',
'Cameron' => 'Canada'
};
directorName = new Map<String, String>();
directorListIgnoreCase = new List<String>();
for (String s : directorMap.keySet())
{
directorName.put(s.toUpperCase(), s);
directorListIgnoreCase.add(s.toUpperCase());
}
directorListIgnoreCase.sort();
}
}
The Visualforce now also is updated to reflect the changes:
<apex:page controller="p158_a_Blog_Example">
<apex:pageBlock title="Directors List">
<apex:pageBlockTable value="{!directorListIgnoreCase}" var="dirKey" style='width:50%'>
<apex:column value="{!directorName[dirKey]}" />
<apex:column value="{!directorMap[directorName[dirKey]]}" />
</apex:pageBlockTable>
</apex:pageBlock>
</apex:page>
Notice the repeating variable is the new List directorListIgnoreCase. Do remember this is the director's name in upper case, so you don't want to display that. To display the original director's name, you'll see to reference the new Map variable directorName, passing dirKey as the key. That's why the director's name is directorName[dirKey]. Finally, to get the country information, we will need to reference the original Map variable diretorMap, passing directorName[dirKey] as the key.
The resulting page now looks exactly what I'm hoping for:
I felt like I had to jump through a lot of hoops to get to the final desired result. If I have missed some steps, please let me know!
Friday, December 28, 2012
Using standard controller's "getRecord" method
There seems to be a common problem using the getRecord function, so I am going to write it down here. This problem has haunted me a few times now.
Whenever you use a controller extension, extending a standard object, you can use the getRecord() to get the reference to the record. For example, if you have an extension to the contact object, this can be what your Apex class looks like:
public class ContactExtension
{
private Contact contact;
public ContactExtension(ApexPages.StandardController sController)
{
this.contact = (Contact)sController.getRecord();
}
}
Besides the obvious constructor, you will most likely have some other methods inside your class to manipulate the contact. Let's say you want to provide a form so the user can change email address and phone number of the contact. Your VisualForce class looks very simple
<apex:page standardController="Contact" extensions="ContactExtension">
{!greeting}
<apex:form>
<apex:inputField value="{!contact.Email" />
<apex:inputField value="{!contact.MobilePhone}" />
<apex:commandButton action="{!save}" value="Save" />
</apex:form>
</apex:page>
The {!greeting} is going to display some greeting messages. That means we will need to write a getGreeting() method in the class.
If this is the definition of your getGreeting() inside the class,
public String getGreeting()
{
return 'Hi ' + contact.FirstName + ' how have you been? Please update the following.';
}
you will then get an error message that looks like this:
System.SObjectException: SObject row was retrieved via SOQL without querying the requested field: Contact.FirstName
Most of us think that as soon as we call the getRecord() method, then the entire record is loaded into the contact variable. However if you have an object with 5000 fields, and you only need to use 2 or 3 in your Visualforce pages, do you think it really makes sense to load all 5000 fields for that record into the contact variable? Probably not.
Then what can you do? The easiest way is to do add this into your Visualforce page:
<apex:outputText value="{!contact.FirstName}"/>
If you say, but I don't want to display the contact name twice! Then you can simply change this to:
<apex:outputText value="{!contact.FirstName}" rendered="false" />
By adding rendered="false", that will hide this variable. Or perhaps use <apex:variable> instead, such as:
<apex:variable value="{!contact.FirstName}" var="contactFName" />
When you load this Visualforce page, the standard controller will scan through your Visualforce page and knows that you need this field, and when making the getRecord() method call, it automatically and implicitly adds this FirstName field into the SOQL.
Remember, if you want to use any field in your extension via the getRecord() method, you need to let it be known by making sure that the field exists in your Visualforce page!
(Another alternative is to write your own SOQL in your Apex class, but why make an extra SOQL call when you can avoid it?)
Whenever you use a controller extension, extending a standard object, you can use the getRecord() to get the reference to the record. For example, if you have an extension to the contact object, this can be what your Apex class looks like:
public class ContactExtension
{
private Contact contact;
public ContactExtension(ApexPages.StandardController sController)
{
this.contact = (Contact)sController.getRecord();
}
}
Besides the obvious constructor, you will most likely have some other methods inside your class to manipulate the contact. Let's say you want to provide a form so the user can change email address and phone number of the contact. Your VisualForce class looks very simple
<apex:page standardController="Contact" extensions="ContactExtension">
{!greeting}
<apex:form>
<apex:inputField value="{!contact.Email" />
<apex:inputField value="{!contact.MobilePhone}" />
<apex:commandButton action="{!save}" value="Save" />
</apex:form>
</apex:page>
The {!greeting} is going to display some greeting messages. That means we will need to write a getGreeting() method in the class.
If this is the definition of your getGreeting() inside the class,
public String getGreeting()
{
return 'Hi ' + contact.FirstName + ' how have you been? Please update the following.';
}
you will then get an error message that looks like this:
System.SObjectException: SObject row was retrieved via SOQL without querying the requested field: Contact.FirstName
Most of us think that as soon as we call the getRecord() method, then the entire record is loaded into the contact variable. However if you have an object with 5000 fields, and you only need to use 2 or 3 in your Visualforce pages, do you think it really makes sense to load all 5000 fields for that record into the contact variable? Probably not.
Then what can you do? The easiest way is to do add this into your Visualforce page:
<apex:outputText value="{!contact.FirstName}"/>
If you say, but I don't want to display the contact name twice! Then you can simply change this to:
<apex:outputText value="{!contact.FirstName}" rendered="false" />
By adding rendered="false", that will hide this variable. Or perhaps use <apex:variable> instead, such as:
<apex:variable value="{!contact.FirstName}" var="contactFName" />
When you load this Visualforce page, the standard controller will scan through your Visualforce page and knows that you need this field, and when making the getRecord() method call, it automatically and implicitly adds this FirstName field into the SOQL.
Remember, if you want to use any field in your extension via the getRecord() method, you need to let it be known by making sure that the field exists in your Visualforce page!
(Another alternative is to write your own SOQL in your Apex class, but why make an extra SOQL call when you can avoid it?)
Wednesday, December 12, 2012
Caution with the ID field
The subject says it all. It is important to know that Salesforce's Id field is a field that needs to be handled with care. It is a read-only field.
Here is a use case that I have encountered. I'm going to keep using my Rig example from my last blog entry. Suffice to know is that I have an object that stores information about rigs, which are big machineries you deploy at well sites in order to drill oil. One of the things we capture in the Rig object is the Id of the rig owner (Rig_Owner__c), which is a lookup to the standard Account object.
Let's say all account information and rig information has been imported into Salesforce from an external system, and all data has been verified to be imported successfully. Now one of the rigs has been sold from company A to company B. We need to update the rig information. When the user clicks the lookup icon to the Account, too many records are returned because the system has over 50000 account records.
It's decided we need to add a custom checkbox (or boolean) field into the Account object, called Rig_Owner_Indicator__c, and is set to true only for accounts that are rig owners. This should help eliminate the number of records returned when we use the Lookup Filter associated with the lookup field.
After adding the column and defaulting the field values for all records to be false, now it's time we set those account records that are actually rig owners. All rig owners are already identified in the Rig_Owner__c of the Rig object. So one way we can run our code is:
List<Rig__c> rigs = [SELECT Rig_Owner__c FROM Rig__c];
List<Account> accounts = new List<Account>();
for (Rig__c rig : rigs)
{
account RigAccount = new Account();
RigAccount.Id = rig.Rig_Owner__c;
RigAccount.Rig_Owner_Indicator__c = true;
accounts.add(RigAccount);
}
update accounts;
However, you will get an error message that says the Id is not writeable, as this screen shot from Eclipse shows.
The offending line is
RigAccount.Id = rig.Rig_Owner__c;
In order to fix the problem, you will need to instantiate your Id in the line where you create a new instance of the Accout SObject. That is, if you rewrite the above as below (note the bolded line), the Apex code will execute successfully, and your Rig Owner Indicator will be populated as desired.
List<Rig__c> rigs = [SELECT Rig_Owner__c FROM Rig__c];
List<Account> accounts = new List<Account>();
for (Rig__c rig : rigs)
{
account RigAccount = new Account(Id = rig.Rig_Owner__c);
RigAccount.Rig_Owner_Indicator__c = true;
accounts.add(RigAccount);
}
update accounts;
The code above will now compile. That goes to show that when you are trying to manipulate the Id field, you need to set it when you instantiate the SObject. Make sure you do NOT attempt to set the Id field. Remember, it is a read-only field.
Lastly, the revised Apex code is still faulty because it will fail if more than one rig belongs to the same rig owner. This is related to adding SObjects to List collection objects. I will leave that to my next blog.
Friday, November 2, 2012
Converting a List (of sObjects) to a Map
This article assumes you already know Salesforce's powerful way to collect objects, List, Map and Set. If you are not familiar with them, you should go to the documentation for more information first.
What I found somewhat undocumented (it actually is documented but you don't find its usage this way in a lot of places) is a way you can initialize a Map collection. You probably already know Map is used to provide a mapping between a primitive type (such as Integer, String, etc. used as the key) and practically any other type (used as the value). A simple example is:
Map<Integer, String> myMap1 = new Map<Integer, String>{ 1 => 'a', 2 => 'b'};
The above declares a map variable, called myMap1 which contains 2 elements. The first element contains a mapping from the integer 1 to the string a, and the second element contains a mapping from the integer 2 to the string b.
The "value" does not have to a primitive type - it can be an sObject, for example.
Map<Id, Account> myMap2 = new Map<Id, Account>();
The above is a variable myMap2 that stores a mapping between Id and the account sObject.
If you have run a SOQL statement and has retrieved a list of contacts, such as:
List<Contact> myContactList1 = [SELECT Id FROM Contact];
you can then "convert" this list to a map, by doing this:
Map<Id, Contact> myMap3 = new Map<Id, Contact>(myContactList1);
The new operator will convert this list (myContactList1) to create the map variable myMap3. The key of myMap3 is obviously the Id field because that is what is selected. In fact, Id is always used as the key in this context. It is also always implicit. So even if your SOQL had been:
List<Contact> myContactList2 = [SELECT Title FROM Contact];
the key will still be the Id field of Contact. I think the Id will be based on what sObject you are selecting from. In this case, it is the Contact, and therefore the Id will be the id of contacts.
The value part, however, is dependent on what you "select". The list myContactList1 only selects the Id field, so the value only contains the Id field as well. The list myContactList2 however, selects the Title field, so you can actually access the Title field via this myContactList2 collection.
Let's say you have a contact named Tim Barr. If you run the following statements, you will see that in the debug log, the value for this contact contains the Id as well as the Title.
Contact c = [SELECT Id from Contact where FirstName = 'Tim' and LastName='Barr'];
List<Contact> myContactList2 = [SELECT Title FROM Contact];
Map<Id, Contact> myMap3 = new Map<Id, Contact>(myContactList2);
system.debug(myMap3.get(c.Id));
It seems to be the Id field is again implicit in the value too. The SELECT clause determines what other fields are available in the value.
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?
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;
}
}
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!
Subscribe to:
Posts (Atom)