I've been taking a look at some Spring 11 features this week, via a
pre-release
org, particularly the Apex Test Framework, which sadly is a pilot program. While
most of the focus of the release notes/help etc is around the ability to select
groups of classes to test and to execute those test in the background, there's
an additional feature that grabbed my attention.
Once I'd run my unit tests, I browsed to the Apex Classes setup page and found a
new column in the class list which is going to save me time on every
project I work on - code coverage:
During the development phase of a project, the first thing I do every morning is
run all unit tests and check the output to see if I'm missing any coverage.
If there's been a lot of development taking place, I'll repeat this many
times during the course of a day. Sometimes I'll scribble the names of
classes that need attention or print out the results page, but it all feels a
bit clunky. Now all I need to do is to execute the tests once (which can
be automated - there's a post explaining this in the pipeline) and browse to the
Apex Classes page each time I want to see the coverage percentages.
Even better - this looks to be available outside of the pilot program
functionality - I've just checked my free force sandbox that has been upgraded
to the Spring 11 release and the code coverage figures are there!
Pages
▼
Sunday, 30 January 2011
Saturday, 22 January 2011
Uploading Multiple Attachments via Visualforce
I've lost count of the number of times I've implemented an attachments page in
Visualforce, sometimes for a single file, sometimes for a finite number and once
or twice for an indeterminate number. Following the principles of
DRY, I decided
to create a custom component that would allow me to attach files to any
specified parent sobject with Add More functionality that allowed me to upload
several files at once. As its easy to forget what attachments you have
already uploaded, I also wanted the existing attachments to be listed. Below is
a screenshot of the completed component:
The component has a mandatory attribute named "sobjId" - this is the id of the sobject that the uploaded files will be attached to. Note that you can't use the name "id" for the attribute, as this is a standard attribute that allows the component to be identified by other components in the page.
The Attachment sobjects backing the input file elements are stored in a list in the controller that is initialised in the constructor to contain a single element. Clicking the Add More button causes another five Attachment sobjects to be appended to the list. As the user can choose to fill in the attachments in any order (e.g. just populating the last two elements), we can't simply insert the attachments when the save button is clicked, as that will attempt to insert the empty attachments that the user hasn't supplied details for. Thus we iterate the list and only insert those Attachments that have the Body field populated.
Note that there is no field for the user to input the name of the Attachment - I'm using the filename for the this, as the filename extension gives a browser a good chance of automatically opening the attachment when it is downloaded.
The Done button simply takes the user back to the view page for the object matching the supplied id attribute.
There's a couple of wrinkles when dealing with file uploads:
Note that when using the example page, you'll need to supply the id of an existing sobject (Account, Opportunity etc).
The component has a mandatory attribute named "sobjId" - this is the id of the sobject that the uploaded files will be attached to. Note that you can't use the name "id" for the attribute, as this is a standard attribute that allows the component to be identified by other components in the page.
The Attachment sobjects backing the input file elements are stored in a list in the controller that is initialised in the constructor to contain a single element. Clicking the Add More button causes another five Attachment sobjects to be appended to the list. As the user can choose to fill in the attachments in any order (e.g. just populating the last two elements), we can't simply insert the attachments when the save button is clicked, as that will attempt to insert the empty attachments that the user hasn't supplied details for. Thus we iterate the list and only insert those Attachments that have the Body field populated.
Note that there is no field for the user to input the name of the Attachment - I'm using the filename for the this, as the filename extension gives a browser a good chance of automatically opening the attachment when it is downloaded.
The Done button simply takes the user back to the view page for the object matching the supplied id attribute.
There's a couple of wrinkles when dealing with file uploads:
- When the page initially renders with one file chooser, if you choose a file and then click the Add More button, the chooser will not remember the file that you chose. This is browser behaviour, as it is not possible to prefill a file chooser under any circumstances - otherwise malicious pages could upload files from your hard disk without your consent.
-
The body of the uploaded file is stored as a Blob. To unit test code
that handles file uploading, you can create a Blob from a String using the
valueof method:
Blob bodyBlob=Blob.valueOf('Unit Test Attachment Body');
Note that when using the example page, you'll need to supply the id of an existing sobject (Account, Opportunity etc).
Friday, 14 January 2011
Post Event Detail to Parent Chatter Feed
This week I needed to update an account's chatter feed when a new event was created related to it. Turns out to be quite a short trigger:
The basic premise is check if the parent object is feed enabled and then post a summary of the event to the parent's chatter feed. In the case where this event isn't associated with an Account or Opportunity (the whatId is null) it will be posted to the feed of the Contact etc (the whoId) for the event. This concept can also be applied to tasks and attachments.
Some obvious improvements:
(1) Allow users to mark events as Not for Chatter or similar, as otherwise everything gets posted!
(2) When an event is deleted, find the previous posts in the feed and remove them, otherwise the links in the feed take you to an error page.
Unit tests are left as an exercise for the avid student!
trigger PostEventToParentChatterFeed on Event (after delete, after insert, after update) { List<Event> toProcess=trigger.new; if (trigger.isDelete) { toProcess=trigger.old; } // build a lookup of describe results by key prefix Map<String, Schema.SObjectType> schemaMap = Schema.getGlobalDescribe(); Map<String, Schema.DescribeSObjectResult> descMap=new Map<String, Schema.DescribeSObjectResult>(); for (Schema.SObjectType sot : schemaMap.values()) { Schema.DescribeSObjectResult descRes=sot.getDescribe(); String kp=descRes.getKeyPrefix(); descMap.put(kp, descRes); } List<FeedPost> feedPosts=new List<FeedPost>(); for (Event ev : toProcess) { FeedPost fpost = new FeedPost(); // if whatId (Opportunity etc) is defined, post to that, otherwise to the event owner Id parentId=(null!=ev.whatId?ev.whatId:ev.whoId); // ensure feed enabled String prefix=((String) parentId).substring(0, 3); Schema.DescribeSObjectResult descObj=descMap.get(prefix); if ( (null!=descObj) && (descObj.isFeedEnabled()) ) { fpost.ParentId=parentId; String user=Userinfo.getUserName(); String action='Created'; // add a link to allow the user to click into the event from the feed String linkUrl= '/' + ev.id; String title='View Event'; if (trigger.isDelete) { action='Deleted'; linkUrl=''; title=''; } else if (trigger.isUpdate) { action='Updated'; } // set the body to a brief summary fpost.Body = action + ' event : ' + ev.Subject; fpost.LinkUrl=linkUrl; fpost.title=title; feedPosts.add(fpost); } } if (feedPosts.size()>0) { insert feedPosts; } }
The basic premise is check if the parent object is feed enabled and then post a summary of the event to the parent's chatter feed. In the case where this event isn't associated with an Account or Opportunity (the whatId is null) it will be posted to the feed of the Contact etc (the whoId) for the event. This concept can also be applied to tasks and attachments.
Some obvious improvements:
(1) Allow users to mark events as Not for Chatter or similar, as otherwise everything gets posted!
(2) When an event is deleted, find the previous posts in the feed and remove them, otherwise the links in the feed take you to an error page.
Unit tests are left as an exercise for the avid student!