A common requirement people need to execute on the Force.com platform is to perform a JOIN in SOQL. This creates a unique challenge to many new developers as JOIN is not supported the same way it is in SOQL. In fact, JOIN is not even a valid keyword for SOQL syntax! Frustrating for new folks, but fear not. You can get some JOIN functionality in SOQL queries using what is called SOQL Relationship Queries.
First, a little explanation why you can't perform JOIN function in SOQL. Force.com platform is cloud based and uses a Multi-Tenant architecture. A multi-tenant architecture means that multiple clients are all using the same computing resources. You are sharing the computing resources with other clients, and to prevent developers from impacting the performance of other clients there are some important rules or governor limits put in place. These are the Governor Limits so many people hear about when they get introduced to the platform.
Since JOIN queries can get resource intensive, SOQL puts some limits around them. Instead of being able to do a JOIN you can use a Relationship query to pull in related records. If you have a lookup field on a object, you can perform a Relationship Query on that field to execute one SOQL query and retreive the related records.
For example, Accounts and Contacts have a relationship. Contacts have a one to many relationship to accounts. To perform a Relationship Query to retrieve all Contacts for an Account, you can perform the following SOQL method:
List<Account> accounts = [Select Id, Name, (Select Id, FirstName, LastName from Account.Contacts) from Account limit 10];
Notice the Bold section I have highlighted in the query. That is the relationship query. Notice that is uses the Account.Contacts relationship to perform the query. You can use the Force.com IDE to browse the relationships on your parent objects to retrieve the names to use in the queries.
After executing this, you can iterate over the retrieved children records by accessing a List<SObject> on the parent records. For example, this full Apex code will execute the query, print each accounts name in the system log, and print each contact under each account:
List<Account> accounts = [Select Id, Name, (Select Id, FirstName, LastName from Account.Contacts) from Account limit 10];
For(Account account : accounts)
{
System.debug('Account Name: ' + account.Name);
For(Contact contact : account.Contacts)
{
System.debug('Contact Name is ' + contact.FirstName + ' ' + contact.LastName);
}
}
And now the output:
14:01:16.041 (41930000)|USER_DEBUG|[4]|DEBUG|Account Name: Acme
14:01:16.042 (42154000)|USER_DEBUG|[7]|DEBUG|Contact Name is Edward Stamos
14:01:16.042 (42272000)|USER_DEBUG|[7]|DEBUG|Contact Name is Howard Jones
14:01:16.042 (42433000)|USER_DEBUG|[7]|DEBUG|Contact Name is Leanne Tomlin
14:01:16.042 (42674000)|USER_DEBUG|[4]|DEBUG|Account Name: salesforce.com
14:01:16.042 (42845000)|USER_DEBUG|[7]|DEBUG|Contact Name is Marc Benioff
14:01:16.043 (43007000)|USER_DEBUG|[4]|DEBUG|Account Name: Global Media
14:01:16.043 (43121000)|USER_DEBUG|[7]|DEBUG|Contact Name is Geoff Minor
Pretty simple, right? You can execute several relationship queries in one query.
If you want more information, checkout the full documentation on the Force.com support website: http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_soql_relationships.htm
Lessons learned and technical ramblings from 15 years in the exciting field of Information Technology, and specifically Software Development. Strong emphasis on Platform as a Service and Salesforce Platform for enterprise software development.
Stat Tracker
Sunday, December 11, 2011
Saturday, November 12, 2011
Passing the Salesforce Certified Administrator Exam
I just got home from the testing center here in Chicago and I'm happy to announce that I just passed the Salesforce Certified Administrator Exam (201). I decided to take this exam because its the prerequisite for all the implementation certifications including the Sales Cloud Consultant exam.
If you're already a certified Force.com developer, you'll find there is alot of overlap between the two exams. There were about 10 - 15 questions covering security and sharing rules which overlap between the 201 and 401 exams, which is no surprise since it's fundamental to working with the platform.
The Admin 201 exam had several questions about setting up Sales and Marketing processes, so you should be familiar with those pieces of Salesforce. You also need to know the Setup menu of Salesforce inside and out. How do you setup new users? Where and how can you restrict users login access?
You need to know how Role Hierarchies, Sales Teams, and Account Teams work. There were questions covering Assignment Rules, Escalation Rules for Cases, and how Campaign Members table works (You can assign Contact and Leads to Campaign Members). There are a few reporting questions also for Summary Fields and such.
Overall, not too bad of an exam. I'd recommend anyone who is thinking about it to dive in. If you follow the study guide that is on the certification site of Salesforce.com you should be golden.
If you're already a certified Force.com developer, you'll find there is alot of overlap between the two exams. There were about 10 - 15 questions covering security and sharing rules which overlap between the 201 and 401 exams, which is no surprise since it's fundamental to working with the platform.
The Admin 201 exam had several questions about setting up Sales and Marketing processes, so you should be familiar with those pieces of Salesforce. You also need to know the Setup menu of Salesforce inside and out. How do you setup new users? Where and how can you restrict users login access?
You need to know how Role Hierarchies, Sales Teams, and Account Teams work. There were questions covering Assignment Rules, Escalation Rules for Cases, and how Campaign Members table works (You can assign Contact and Leads to Campaign Members). There are a few reporting questions also for Summary Fields and such.
Overall, not too bad of an exam. I'd recommend anyone who is thinking about it to dive in. If you follow the study guide that is on the certification site of Salesforce.com you should be golden.
Thursday, October 20, 2011
Salesforce Mobile - Formula Fields with Line Breaks Work Around
If you are an admin for Salesforce Mobile you are probably familiar with how easy it is to get setup and running for simple out of the box implementations. However, you might see issues with line breaks in formula fields. Let me explain:
Formula Fields are rendered at the Salesforce Server for Salesforce Mobile and pushed down. So when you have Formula Field which is a text field with line breaks (BR() Function) the field won't look correct in Salesforce Mobile. Instead of seeing a line break like this:
In Salesforce Mobile you will see
This was giving me quite a headache. Here is the simplest solution I could find.
1. Create a new TextArea field in Salesforce to replace your existing Formula Field.
2. Create a Workflow Rule on the Object. Have the Workflow rule execute every time a record is updated or created. Make sure the formula always evaluates to true.
3. Create a new Workflow Field update for the new TextArea field from step 1. Copy your existing Formula Field's Formula into the new value for the Field.
4. Activate the Workflow.
That's it. Now the formula will be evaluated and stored in Salesforce as a String. It will now be displayed properly in both Salesforce and Salesforce Mobile.
It's too bad it doesn't render properly in Mobile out of the box, but this is a liveable work around.
If anyone has a better solution please share!
Formula Fields are rendered at the Salesforce Server for Salesforce Mobile and pushed down. So when you have Formula Field which is a text field with line breaks (BR() Function) the field won't look correct in Salesforce Mobile. Instead of seeing a line break like this:
In Salesforce Mobile you will see
This was giving me quite a headache. Here is the simplest solution I could find.
1. Create a new TextArea field in Salesforce to replace your existing Formula Field.
2. Create a Workflow Rule on the Object. Have the Workflow rule execute every time a record is updated or created. Make sure the formula always evaluates to true.
3. Create a new Workflow Field update for the new TextArea field from step 1. Copy your existing Formula Field's Formula into the new value for the Field.
4. Activate the Workflow.
That's it. Now the formula will be evaluated and stored in Salesforce as a String. It will now be displayed properly in both Salesforce and Salesforce Mobile.
It's too bad it doesn't render properly in Mobile out of the box, but this is a liveable work around.
If anyone has a better solution please share!
Thursday, September 1, 2011
Blogging Live From Dreamforce 2011!
So this is my third day here in San Francisco at Dreamforce 2011 and wow. If you take part technology conference and part rock concert, and mix it in a blender you'll get Dreamforce. This is my first time at Dreamforce, and my head is literally spinning with all the different ideas of applications you can build with Force.com. Its incredible.
I've spent a good amount of time in the Developer Zone networking, attending sessions, and participating in the Dreamforce Hackathon. I built a new Force.com application specifically for Dreamforce which integrated with the San Francisco MUNI Transit authority in a matter of hours. Specifically for this event, I took the Chicago Bus Radar Force.com Application, and rebuilt it to make it work with SF MUNI API, and cleaned up the code for others to use more easily. My hope is that attendees have found it useful to find Buses around SF during the conference.
If you want the code for the SF MUNI HTML5 Force.com Application (Wow, thats a Mouthful!), here is a link to the Force.com unmanaged package which I have released under MIT license: https://test.salesforce.com/packaging/installPackage.apexp?p0=04tZ00000008qDo
Look for a GitHub release in the near future, probably later today!
Networking here has been incredible. Its funny how many people you know from Twitter, the Force.com Developer Boards, and various blogs but you've never met face to face. To meet them in person in real life is a rare opportunity given our geographic differences, and the fact we are all up in the clouds! Connecting with folks here has been phenomenal.
I didn't even mention how awesome the Metallica concert was last night? It was hilarious to see folks in the audience using iPads to take photos and videos during the concert. Only at Dreamforce.
I present my session tomorrow morning at 8:30 AM, so hopefully folks don't party too hard tonight and wake up! It'll be worth it!
I've spent a good amount of time in the Developer Zone networking, attending sessions, and participating in the Dreamforce Hackathon. I built a new Force.com application specifically for Dreamforce which integrated with the San Francisco MUNI Transit authority in a matter of hours. Specifically for this event, I took the Chicago Bus Radar Force.com Application, and rebuilt it to make it work with SF MUNI API, and cleaned up the code for others to use more easily. My hope is that attendees have found it useful to find Buses around SF during the conference.
If you want the code for the SF MUNI HTML5 Force.com Application (Wow, thats a Mouthful!), here is a link to the Force.com unmanaged package which I have released under MIT license: https://test.salesforce.com/packaging/installPackage.apexp?p0=04tZ00000008qDo
Look for a GitHub release in the near future, probably later today!
Networking here has been incredible. Its funny how many people you know from Twitter, the Force.com Developer Boards, and various blogs but you've never met face to face. To meet them in person in real life is a rare opportunity given our geographic differences, and the fact we are all up in the clouds! Connecting with folks here has been phenomenal.
I didn't even mention how awesome the Metallica concert was last night? It was hilarious to see folks in the audience using iPads to take photos and videos during the concert. Only at Dreamforce.
I present my session tomorrow morning at 8:30 AM, so hopefully folks don't party too hard tonight and wake up! It'll be worth it!
Monday, June 13, 2011
Google Charts API and Force.com
Recently I created an open-source project for integrating Google Charts API and Force.com to create custom dashboards. The project is a Visualforce component that leverages a custom Apex Controller, a custom dashboard record object, and Google Charts API to dynamically generate charts. Its pretty simple and can be used out of the box via "clicks not code". The codebase could be extended upon for lots of different use cases if developers so desire.
Here is a demonstration video:
And here is a code deep dive video:
You can access the repository here:
http://github.com/corycowgill/Google-Charts-Dashboards-for-Force.com
Hope you find some uses for this!
Here is a demonstration video:
And here is a code deep dive video:
You can access the repository here:
http://github.com/corycowgill/Google-Charts-Dashboards-for-Force.com
Hope you find some uses for this!
Tuesday, June 7, 2011
I'll be presenting at Dreamforce 2011!
Update: The session went great! Thanks to all who attended, it was a packed audience! Here is the YouTube Video if you couldn't attend Dreamforce: http://www.youtube.com/user/sa lesforce#p/search/1/wwTdGqOygB Q
I am very honored to announce that I have been chosen to present a developer session at Dreamforce 2011 in San Fransisco!
You can find me session on the Dreamforce website here: http://www.salesforce.com/dreamforce/DF11/schedule/sessions/
If you have trouble finding it, here is the description on the site.
I am very honored to announce that I have been chosen to present a developer session at Dreamforce 2011 in San Fransisco!
You can find me session on the Dreamforce website here: http://www.salesforce.com/dreamforce/DF11/schedule/sessions/
If you have trouble finding it, here is the description on the site.
HTML 5 and Geo-Location Using Visualforce and Apex
As the popularity of mobile computing rises, so does the importance of location-based data. Join us to see how you can easily combine Visualforce, Apex, and a 3rd party Web services API to create a geo-location aware application. As an example, we'll study a custom application that locates the nearby bus times and bus stops for the Chicago Transportation Authority. We'll provide code walkthroughs and a demo, and you'll leave with all the tools necessary to build your own location-aware applications.
Speakers: Cory Cowgill, West Monroe Partners
I will be going over my Force.com / HTML5 application which you can access at http://www.chicagobusradar.com. There will be code snippets, code walk-through, and lots of good material if your a developer.
If your going to Dreamforce I'd love for you to register for my session! You can also follow my session in the Dreamforce Chatter application. Here is a direct URL for the session (note: you'll need to be a registered Dreamforce attendee to view): https://dreamevent.my.salesforce.com/a093000000BtFdVAAV
I'll see you in San Francisco in August!
Thursday, May 26, 2011
Used Location Services Icon in iPhone Settings
So I had to go to the Apple store last night to replace my broken iPhone. The microphone for calls wasn't working, so they replaced it with a new one. As any good techie would do, I go straight home and restore from a backup. After restoring the backup, some settings still need to be tweaked so I go into the settings app and start tweaking, and I noticed something new. Or at least something I hadn't noticed before.
When you go into Settings > Location Services, it will show you the list of applications which are enabled for Location Services. I have seen that before. But for the first time I noticed that if you used one of those apps in the last 24 hours, it will show a little cursor icon next to the service to indicate the app has used it recently.
When you go into Settings > Location Services, it will show you the list of applications which are enabled for Location Services. I have seen that before. But for the first time I noticed that if you used one of those apps in the last 24 hours, it will show a little cursor icon next to the service to indicate the app has used it recently.
Kinda neat eh? I don't know if this is a new feature since the whole privacy debacle a few back and the 4.3.3 update released it or if its always been there, but kinda neat.
Tuesday, May 24, 2011
Passing the Force.com Advanced Developer Exam
Update 12/8/2011: I recently completed the Assignment and Essay portions of the process and am happy to report that I passed! I am now a Force.com Certified Advanced Developer.
Yesterday I passed the Force.com Advanced Developer Exam! There are 2 additional stages to this certification which I have not completed yet. There is a programming assignment and a essay assignment, both of which I am waiting for Salesforce to notify me when the next session is. That being said, the exam portion is pretty tough. If you don't have at least 6 months experience hands on with Apex and Visualforce I wouldn't recommend taking it.
The format is similar to the Developer exam which is a prerequisite before you can take the advanced exam. There are multiple choice questions which also have the tricky "Select 2" or "Select 3" type questions. So the format is nothing new. What is new is the questions. The exam took me 48 minutes, but your allotted 2 hours.
There are lots of questions on Visualforce, Apex, and SOQL. So you need to be very familiar with them. For example, there are questions which will present you a SOQL query and ask you what will result from the code. Another question provided a Visualforce Page and an Apex Class, then asked if it could be used as a Standard Controller, Extension, ETC. So you need to have built lots of different Visualforce Pages and Apex Classes to pass the exam.
Another important piece that is covered in multiple questions is Visualforce Templates and Visualforce Components. You need to know how to use Visualforce Components, especially how to pass variables from a Visualforce Page to a Visualforce Component. There were 3 questions just on Visualforce Components, how to build them, how to pass variables to them, and what type of variables they can accept.
Unit Testing questions are also on the exam. Make sure you know how to write GOOD unit tests. Don't presume test data will be available in your instance for example. Also remember that a Unit Test NEVER commits data to the database. They try to catch you a few times with that asking if at the end of unit test the data is committed.
There are 3 or 4 questions specific to Salesforce environments. You need to know what environments to use for different use cases. For example, Developer Edition should be used for building stand alone managed packages. They will ask you to match up multiple environemnts to the proper use case so be familiar with all the different types and their proper use cases.
There was a few questions around Email Services also. How do you build one and how do you test one will be covered so make sure you've built and deployed a few Email Services to different environments.
There are quite a few tools and deployment questions on the exam. Make sure you know how to deploy changes using the Force.com IDE, Force.com Migration Tool, and Change Sets. You need to know how to make destructive changes and how the xml files are structured for the Force.com Migration Tool. You also need to know all the concepts around how packages and what environments they can be built in.
My best advice is to go through the Force.com Developer Guide which can be found here: http://wiki.developerforce.com/index.php/Force_Platform_Developer_Guide. The guide is a good primer on both the Developer Exam (First 7 chapters only) and the Advanced Developer Exam (Chapters 8 through 15). For the advanced developer exam, if you have experience with Force.com, and you review chapters 8 through 15 you will have a good chance at passing the exam.
I'd also reference Jeff Douglas's blog post about the exam. He has some other items that I may have missed above. http://blog.jeffdouglas.com/2009/07/13/i-passed-the-salesforce-com-certified-advanced-developer-exam-so-can-you/
Good Luck!
Monday, May 23, 2011
Chicago Bus Radar Demo Video and Updates
I uploaded a demonstration video of a recent release of Chicago Bus Radar.
The demonstration video shows how the application works on both a Desktop and iPad/iPhone.
You can view the demonstration here:
http://youtu.be/VqOm7XEOamI
On another note last night I updated the application to show a search radius box and auto-zoom based on the search radius selected. This update allows you to see visibly what the search radius was. Because this application is using a bounding box search pattern, it is a square and not a circle. You can see the latest screenshot below.
The next update I'm mulling is adding click events to the bus's in the data table. This would allow you to click on one of the bus times in the data grid, and on the integrated Google Map the marker would popup information or become highlighted so you can see exactly which stop it is.
Anyway, thanks for reading and please feel free to provide me some feedback on the application via this blog's comments or my twitter feed (@corycowgill).
Note: I introduced a bug if your in a location with 0 buses. The Google Map won't initialize properly and won't display. I'm targeting a fix for tonight 5/23/2011. Thanks @shobyabdi for finding the bug!
The demonstration video shows how the application works on both a Desktop and iPad/iPhone.
You can view the demonstration here:
http://youtu.be/VqOm7XEOamI
On another note last night I updated the application to show a search radius box and auto-zoom based on the search radius selected. This update allows you to see visibly what the search radius was. Because this application is using a bounding box search pattern, it is a square and not a circle. You can see the latest screenshot below.
The next update I'm mulling is adding click events to the bus's in the data table. This would allow you to click on one of the bus times in the data grid, and on the integrated Google Map the marker would popup information or become highlighted so you can see exactly which stop it is.
Anyway, thanks for reading and please feel free to provide me some feedback on the application via this blog's comments or my twitter feed (@corycowgill).
Note: I introduced a bug if your in a location with 0 buses. The Google Map won't initialize properly and won't display. I'm targeting a fix for tonight 5/23/2011. Thanks @shobyabdi for finding the bug!
Monday, May 16, 2011
Setting the Email Bounced Alert in Salesforce Via Apex / API
Salesforce has a pretty nice feature called "Bounced Email Alerts" which can be configured using the declaritive setup menu in Salesforce. This allows emails which were bounced when being sent via Salesforce to popup alerts on the Email address field in the standard pages. It's pretty useful, especially if your sending emails via Salesforce.
Take a look at the email alert and prompt below for what it looks like.
And if a user clicks on the alert it gives them some details:
But many users have other 3rd party systems which sends the email. So the question is how do I get the "Bounced Email Alerts" in Salesforce if I'm using another system to send the emails?
The answer is you can use Apex code. It actually very simple but not documented anywhere that I saw. There are two fields on the Contact object which trigger the Email Bounce Alert functionality. They are EmailBouncedDate and EmailBounceReason. If you populate these two fields on a Contact record, the Email Bounce Alert functionality will fire. And vice versa, if you null out those two fields the alerts go away.
These fields are only visible in Apex code. You can't create a workflow rule / field update to do this unfortunately. Luckily, the Apex code is pretty simple. For example, below I added a boolean flag to Contact call "Invalid Email". Whenever I update this field, be it from the UI or a Web Service call from a 3rd party email system, the Bounce Alert functionality fires.
Sample Code to set the Email Bounced Alerts in Apex:
trigger ContactTrigger on Contact (after delete, after insert, after undelete,
after update, before delete, before insert, before update)
{
if(Trigger.isBefore)
{
for(Contact contact : trigger.new)
{
if(contact.EmailBouncedDate == null && contact.EmailBouncedReason == null && contact.Invalid_Email__c == true)
{
contact.EmailBouncedDate = DateTime.now();
contact.EmailBouncedReason = 'Invalid Email Address Set By User';
}
if(contact.Invalid_Email__c == false)
{
contact.EmailBouncedDate = null;
contact.EmailBouncedReason = null;
}
}
}
}
In conclusion, if you have a 3rd party email system with Salesforce but still want to use the Bounced Email Alert functionality, you can do it if you have Enterprise or Unlimited Editions' of Salesforce.
Take a look at the email alert and prompt below for what it looks like.
And if a user clicks on the alert it gives them some details:
But many users have other 3rd party systems which sends the email. So the question is how do I get the "Bounced Email Alerts" in Salesforce if I'm using another system to send the emails?
The answer is you can use Apex code. It actually very simple but not documented anywhere that I saw. There are two fields on the Contact object which trigger the Email Bounce Alert functionality. They are EmailBouncedDate and EmailBounceReason. If you populate these two fields on a Contact record, the Email Bounce Alert functionality will fire. And vice versa, if you null out those two fields the alerts go away.
These fields are only visible in Apex code. You can't create a workflow rule / field update to do this unfortunately. Luckily, the Apex code is pretty simple. For example, below I added a boolean flag to Contact call "Invalid Email". Whenever I update this field, be it from the UI or a Web Service call from a 3rd party email system, the Bounce Alert functionality fires.
Sample Code to set the Email Bounced Alerts in Apex:
trigger ContactTrigger on Contact (after delete, after insert, after undelete,
after update, before delete, before insert, before update)
{
if(Trigger.isBefore)
{
for(Contact contact : trigger.new)
{
if(contact.EmailBouncedDate == null && contact.EmailBouncedReason == null && contact.Invalid_Email__c == true)
{
contact.EmailBouncedDate = DateTime.now();
contact.EmailBouncedReason = 'Invalid Email Address Set By User';
}
if(contact.Invalid_Email__c == false)
{
contact.EmailBouncedDate = null;
contact.EmailBouncedReason = null;
}
}
}
}
In conclusion, if you have a 3rd party email system with Salesforce but still want to use the Bounced Email Alert functionality, you can do it if you have Enterprise or Unlimited Editions' of Salesforce.
Monday, May 2, 2011
Chicago Bus Radar - Builing a Force.com HTML 5 Application with Geo Location and Third Party API's
Chicago Bus Radar
For the past few weeks during my spare time I've been doing some work with the CTA (Chicago Transit Authority) API. For those who don't live in Chicago, the CTA manages all of Chicago's bus's and trains. About two years ago they upgraded all their bus's with a pretty sweet GPS system, as well as a Website for getting estimated arrival times (www.ctabustracker.com). Literally this was life changing for me when it was released. I rely exclusively on CTA for my transportation since I live in the city, and the ability to see bus times in real-time has saved me so much time.
My only problem with the default CTA Website is that its dumb when it comes to being location-aware. If I want to get the bus time for a stop I'm at, I have to either bookmark the exact bus stop into my browser on my iPhone, or I have to text message the CTA with the Stop ID.
The other problem I have is that its not very good for doing comparative analysis of bus stops. For example, I live in between two different sets of bus stops. Both of these have routes which go downtown. Based on which bus arrives at a stop first, I can take either one to get downtown where I need to go. But I don't have a way to see one view of both the bus stops in the default CTA application.
Fortunately for me, the CTA released an API so that anyone who want to can access all the bus arrival time information. I just had to sign up and the CTA granted me API access. With the API Access I was able to quickly build an application which could do this. It is located at http://www.chicagobusradar.com. If you don't live in Chicago you won't get any buses naturally, so you can take a look at the screenshot below.
A few of the highlights:
1. HTML 5 GeoLocation functionality for cross platform compliance (iPhone, Android, etc)
2. Force.com is the platform which runs the application database and web pages, controller code, etc.
3. CTA API HTTP calls to retrieve the bus estimate arrival times from CTA System.
I'm still working on tweaking it some more, but right now its got 3,000 stops in the system which is about 1/4 of all the routes in Chicago. I still need to cache the rest of the Route's, as well as build an HTML 5 Manifest file for some nice icons on iPhone / Android devices.
I also need to add some filtering capabilities, differentiate colors on the Google Map, and a few more UI tweaks. But the major pieces are all in place at least.
Eventually I'll be posting the code sample somewhere, so if your interested in using Force.com to build HTML5 / Geo-Location based application you'll be able to pull the code as a reference.
Stay tuned, and peace out!
For the past few weeks during my spare time I've been doing some work with the CTA (Chicago Transit Authority) API. For those who don't live in Chicago, the CTA manages all of Chicago's bus's and trains. About two years ago they upgraded all their bus's with a pretty sweet GPS system, as well as a Website for getting estimated arrival times (www.ctabustracker.com). Literally this was life changing for me when it was released. I rely exclusively on CTA for my transportation since I live in the city, and the ability to see bus times in real-time has saved me so much time.
My only problem with the default CTA Website is that its dumb when it comes to being location-aware. If I want to get the bus time for a stop I'm at, I have to either bookmark the exact bus stop into my browser on my iPhone, or I have to text message the CTA with the Stop ID.
The other problem I have is that its not very good for doing comparative analysis of bus stops. For example, I live in between two different sets of bus stops. Both of these have routes which go downtown. Based on which bus arrives at a stop first, I can take either one to get downtown where I need to go. But I don't have a way to see one view of both the bus stops in the default CTA application.
Fortunately for me, the CTA released an API so that anyone who want to can access all the bus arrival time information. I just had to sign up and the CTA granted me API access. With the API Access I was able to quickly build an application which could do this. It is located at http://www.chicagobusradar.com. If you don't live in Chicago you won't get any buses naturally, so you can take a look at the screenshot below.
A few of the highlights:
1. HTML 5 GeoLocation functionality for cross platform compliance (iPhone, Android, etc)
2. Force.com is the platform which runs the application database and web pages, controller code, etc.
3. CTA API HTTP calls to retrieve the bus estimate arrival times from CTA System.
I'm still working on tweaking it some more, but right now its got 3,000 stops in the system which is about 1/4 of all the routes in Chicago. I still need to cache the rest of the Route's, as well as build an HTML 5 Manifest file for some nice icons on iPhone / Android devices.
I also need to add some filtering capabilities, differentiate colors on the Google Map, and a few more UI tweaks. But the major pieces are all in place at least.
Eventually I'll be posting the code sample somewhere, so if your interested in using Force.com to build HTML5 / Geo-Location based application you'll be able to pull the code as a reference.
Stay tuned, and peace out!
Thursday, April 7, 2011
Visualforce Inline Editing - IE Error - Fix
Salesforce released inline editing in Spring 11 for Visualforce pages, which is pretty sweet. Especially considering when you need to replace a standard detail page with a VF page to do some secret sauce awesomeness, this was one feature which roadblocked it many times. It couldn't be simpler to implement, for example:
<apex:page standardController="CustomObjectABC__c" extensions="SecretSauceController" >
<apex:form>
<apex:detail inlineedit="true">
</apex:detail>
<apex:pageBlock title="Secret Sauce Functionality!!!!" >
<apex:pageBlockButtons >
<apex:commandButton value="Execute The Awesome" action="{!awesomeMethod}" rendered="{!isUserAwesome}"/>
</apex:pageBlockButtons>
<apex:outputText value={!howAwesomeIsUser}/>
</apex:pageBlock>
<apex:form>
</apex:page>
So whats the problem? Nothing if you use a decent browser like Firefox or Chrome.
But what about IE?
Well, if you have some <apex:commandButton>'s on the page, the buttons won't work and you will get a javascript error like this:
IE Mesage: 'document.forms.echoScontrolForm_066700000004hqK' is null or not an object
And now your custom code won't execute! Yeah, this frustrated me big time, but I found a work around by playing around with the page for about an hour. The issue is how Force.com platform is handling the forms. If you wrap each piece in its own <apex:form> with their own ids your good. So using the above example, if you do this it will work:
<apex:page standardController="CustomObjectABC__c" extensions="SecretSauceController" >
<apex:form id="detailForm1">
<apex:detail inlineedit="true">
</apex:detail>
</apex:form>
<apex:form id="detailForm2">
<apex:pageBlock title="Secret Sauce Functionality!!!!" >
<apex:pageBlockButtons >
<apex:commandButton value="Execute The Awesome" action="{!awesomeMethod}" rendered="{!isUserAwesome}"/>
</apex:pageBlockButtons>
<apex:outputText value={!howAwesomeIsUser}/>
</apex:pageBlock>
<apex:form>
</apex:page>
It should be noted that this is a known issue and that Salesforce is working on a fix that is scheduled to be released in the upcoming Summer 11 release. Until then, if you can't wait for inline editing (and why would you?) you can use the above fix.
I mean basically I hope the goal is to give VF all the feature of standard detail pages. Its still not quite there yet, some things you just can't get. But they are getting there.
Man, all this secret sauce code has me craving a Big Mac...... MMMMMM.......Heart Attackalicious.....
http://boards.developerforce.com/t5/Visualforce-Development/lt-apex-form-gt-tag-and-inline-editing-break-googlemaps-packaged/m-p/265569#M34264
<apex:page standardController="CustomObjectABC__c" extensions="SecretSauceController" >
<apex:form>
<apex:detail inlineedit="true">
</apex:detail>
<apex:pageBlock title="Secret Sauce Functionality!!!!" >
<apex:pageBlockButtons >
<apex:commandButton value="Execute The Awesome" action="{!awesomeMethod}" rendered="{!isUserAwesome}"/>
</apex:pageBlockButtons>
<apex:outputText value={!howAwesomeIsUser}/>
</apex:pageBlock>
<apex:form>
</apex:page>
So whats the problem? Nothing if you use a decent browser like Firefox or Chrome.
But what about IE?
Well, if you have some <apex:commandButton>'s on the page, the buttons won't work and you will get a javascript error like this:
IE Mesage: 'document.forms.echoScontrolForm_066700000004hqK' is null or not an object
And now your custom code won't execute! Yeah, this frustrated me big time, but I found a work around by playing around with the page for about an hour. The issue is how Force.com platform is handling the forms. If you wrap each piece in its own <apex:form> with their own ids your good. So using the above example, if you do this it will work:
<apex:page standardController="CustomObjectABC__c" extensions="SecretSauceController" >
<apex:form id="detailForm1">
<apex:detail inlineedit="true">
</apex:detail>
</apex:form>
<apex:form id="detailForm2">
<apex:pageBlock title="Secret Sauce Functionality!!!!" >
<apex:pageBlockButtons >
<apex:commandButton value="Execute The Awesome" action="{!awesomeMethod}" rendered="{!isUserAwesome}"/>
</apex:pageBlockButtons>
<apex:outputText value={!howAwesomeIsUser}/>
</apex:pageBlock>
<apex:form>
</apex:page>
It should be noted that this is a known issue and that Salesforce is working on a fix that is scheduled to be released in the upcoming Summer 11 release. Until then, if you can't wait for inline editing (and why would you?) you can use the above fix.
I mean basically I hope the goal is to give VF all the feature of standard detail pages. Its still not quite there yet, some things you just can't get. But they are getting there.
Man, all this secret sauce code has me craving a Big Mac...... MMMMMM.......Heart Attackalicious.....
http://boards.developerforce.com/t5/Visualforce-Development/lt-apex-form-gt-tag-and-inline-editing-break-googlemaps-packaged/m-p/265569#M34264
Tuesday, April 5, 2011
Salesforce ERD Tool Review - ERD Tool by Xactium
I just saw a twitter feed about a new ERD tool which is available in preview called "ERD Tool" which is provided for free by Peter Gascoyne and Xactium.
You can get to the AppExchange listing here: https://sites.secure.force.com/appexchange/listingDetail?listingId=a0N30000003KNErEAO .
Since I love entity relationship diagrams, I had to check it out. I find a good ERD can help keep you sane when dealing with large data models. And I'm frequently annoyed that I can't find ERD Tools that meet my needs. I feel like I've tried every ERD Plugin in eclipse and been disappointed everytime.
This application is very simple. Once you install it, all you have to do is select which objects you want to have available on the ER Diagram. It gives you a simple multiselect picklist where you pick and choose the objects:
After you click Save, it will take you to the Diagram page. Here you click the Sidebar button in the upper left to select which Objects you want to display in your ERD.
Bottom line: If your a SF Consultant or Developer who needs to generate ER Diagrams, check this tool out. I'm excited to see what other enhancements they add to this tool. Some cool enhancements would be the ability to save your diagrams to a file, and or email diagrams from straight from the tool to clients.
You can get to the AppExchange listing here: https://sites.secure.force.com/appexchange/listingDetail?listingId=a0N30000003KNErEAO .
Since I love entity relationship diagrams, I had to check it out. I find a good ERD can help keep you sane when dealing with large data models. And I'm frequently annoyed that I can't find ERD Tools that meet my needs. I feel like I've tried every ERD Plugin in eclipse and been disappointed everytime.
This application is very simple. Once you install it, all you have to do is select which objects you want to have available on the ER Diagram. It gives you a simple multiselect picklist where you pick and choose the objects:
After you click Save, it will take you to the Diagram page. Here you click the Sidebar button in the upper left to select which Objects you want to display in your ERD.
Now all you have to do is click on those objects, and the applicaiton will automatically start plopping the objects on the ERD. By default, it seems to give you the small (non-expanded) entity, but if you click the plus sign in the upper right it expands it with the field level information. Very cool! You can drag and move the entities around straight in your browser via HTML5 Canvas tag.
Bottom line: If your a SF Consultant or Developer who needs to generate ER Diagrams, check this tool out. I'm excited to see what other enhancements they add to this tool. Some cool enhancements would be the ability to save your diagrams to a file, and or email diagrams from straight from the tool to clients.
Monday, March 21, 2011
Retrieve Salesforce / Force.com Server URL in Apex Code
Update - As of Summer 11 release this past weekend, you can now use the System.URL class to retrieve this information from a Trigger, Batch Apex, ETC. So this blog post is no longer valid if you use version 22 or later (which you should!).
Checkout this link for the information:
http://blog.sforce.com/sforce/2011/05/one-of-the-trends-i-have-noticed-in-the-past-few-releases-is-an-even-greater-emphasis-on-openness-on-the-forcecom-platform.html
If you want to retrieve the Server URL in Apex code on the Force.com platform, you can use the following line of code in an Apex class:
ApexPages.currentPage().getHeaders().get('Host')
The only problem with this approach is that you can't get to the URL if you are in a Trigger or Apex Batch job.
If you need the Server URL in a non-request context peice of Apex code (Trigger, Batch Job, ETC) than I suggest using Custom Settings. You create a custom setting, and you can call it "Server Config" or something similar. Then, one time only, when you first create the environment, you manaully populate a string field on the Custom Setting and call it something like "Server URL" where you paste the URL from the address bar in your browser.
Then in all your custom Apex code, you can just retrieve the Server URL from your Custom Setting.
Its the most pain-free method I know of doing it.
Checkout this link for the information:
http://blog.sforce.com/sforce/2011/05/one-of-the-trends-i-have-noticed-in-the-past-few-releases-is-an-even-greater-emphasis-on-openness-on-the-forcecom-platform.html
If you want to retrieve the Server URL in Apex code on the Force.com platform, you can use the following line of code in an Apex class:
ApexPages.currentPage().getHeaders().get('Host')
The only problem with this approach is that you can't get to the URL if you are in a Trigger or Apex Batch job.
If you need the Server URL in a non-request context peice of Apex code (Trigger, Batch Job, ETC) than I suggest using Custom Settings. You create a custom setting, and you can call it "Server Config" or something similar. Then, one time only, when you first create the environment, you manaully populate a string field on the Custom Setting and call it something like "Server URL" where you paste the URL from the address bar in your browser.
Then in all your custom Apex code, you can just retrieve the Server URL from your Custom Setting.
Its the most pain-free method I know of doing it.
Friday, March 18, 2011
Similarities between JSF 2.0 and Force.com
I don't know if many people know this, but under the hood Force.com runs on JSF. Salesforce doesn't publish this information anywhere that I know of (if you know please share!). As a developer, if you are familiar with JSF than when you start working with Force.com you should be able to make the transition fairly smoothly. This hopefully should remove the sticker shock from anyone who is looking at implement Salesforce.com and thinks "Visualforce and Apex? I don't have people who can support that?". Well, if you have Java developers, there's a good chance they can use their existing skill sets to help support your Force.com implementation.
The two major components of the Force.com platform are Visualforce and Apex. The first part is Visualforce. You can think of Visualforce as a tag library which wraps around JSF tags. Instead of having a tag like <h:datatable> in JSF the Visualforce equivalent is <apex:datatable>. You'll find many of the JSF components (Templates, Compositions, PanelGrid, OutputText, InputText, ETC) have almost identical wrappers in Visualforce with the major difference being the taglib using the <apex:> name space.
The second major component is Apex. Apex is essentially a wrapper around Java. In JSF you have backing beans for your JSF pages. In Force.com, your backing bean is an Apex Controller. The Java Bean naming convention applies just like it does in JSF. So if you have a property called "accountName" with a getter of "getAccountName()" in your backing bean, you would acces it in JSF by #{beanName.accountName}. Its the same way in Force.com except when you bind to your Apex Controller Class attribute to the Visualforce page you use {!apexClass.accountName}.
There are many more similarities to JSF. For example, in your Apex Classes the ApexPages classes are similar to the FacesContext classes. There are lots of other examples but for the scope of this blog just wanted to point out that basic similarity between JSF and Force.com for folks who didn't know they had a relationship.
The two major components of the Force.com platform are Visualforce and Apex. The first part is Visualforce. You can think of Visualforce as a tag library which wraps around JSF tags. Instead of having a tag like <h:datatable> in JSF the Visualforce equivalent is <apex:datatable>. You'll find many of the JSF components (Templates, Compositions, PanelGrid, OutputText, InputText, ETC) have almost identical wrappers in Visualforce with the major difference being the taglib using the <apex:> name space.
The second major component is Apex. Apex is essentially a wrapper around Java. In JSF you have backing beans for your JSF pages. In Force.com, your backing bean is an Apex Controller. The Java Bean naming convention applies just like it does in JSF. So if you have a property called "accountName" with a getter of "getAccountName()" in your backing bean, you would acces it in JSF by #{beanName.accountName}. Its the same way in Force.com except when you bind to your Apex Controller Class attribute to the Visualforce page you use {!apexClass.accountName}.
There are many more similarities to JSF. For example, in your Apex Classes the ApexPages classes are similar to the FacesContext classes. There are lots of other examples but for the scope of this blog just wanted to point out that basic similarity between JSF and Force.com for folks who didn't know they had a relationship.
Monday, February 14, 2011
Infamous Rookie Error : System.QueryException: List has no rows for assignment to SObject
System.QueryException: List has no rows for assignment to SObject
I see this error time and time again in the developer boards, when I'm working with junior developers, or working with developers who are new to the platform. So I figured I'd write up a quick post about it and how to solve it. Lets say I have a trigger or controller or any Apex code which needs to execute a SOQL query.
For the purpose of this example, lets just say I'm going to query on Account Name. In this example, I expect all my Accounts to have unique names. Below is how a junior developer may write this query:
Example Problematic Apex Code:
The problem? If this SOQL executes and there are no matches for inputName, than this statement will throw the System.QueryException. Why? Behind the scenes I think Apex is trying to take a result from List in memory (The list of results from SOQL) and convert the first row into a SOBject for assignment to your variable. Since there are 0 results, it throws the exception.
There are 2 ways to solve this:
1. You can wrap this in a Try/Catch block like so:
2. You can store the results in a List<SObject> instead of just an SObject and grab the first result like so:
Its as simple as that. What method do I prefer? Personally, I like storing my results in Lists. Why? Well I usually already have try/catch logic to handle exceptions, and nesting additional try/catches can get a bit bulky. Its just a preference is all. Both approaches work perfectly fine. And depending on how your write your code, it may be preferable to do the Try/Catch approach. Its really up to the developers own taste.
Bottom Line - Its an easy problem to solve if you know the details, and now you know!
And knowing is half the battle!
I see this error time and time again in the developer boards, when I'm working with junior developers, or working with developers who are new to the platform. So I figured I'd write up a quick post about it and how to solve it. Lets say I have a trigger or controller or any Apex code which needs to execute a SOQL query.
For the purpose of this example, lets just say I'm going to query on Account Name. In this example, I expect all my Accounts to have unique names. Below is how a junior developer may write this query:
Example Problematic Apex Code:
Account lateAccount = [Select Id, Name from Account where Name =: inputName limit 1];
The problem? If this SOQL executes and there are no matches for inputName, than this statement will throw the System.QueryException. Why? Behind the scenes I think Apex is trying to take a result from List in memory (The list of results from SOQL) and convert the first row into a SOBject for assignment to your variable. Since there are 0 results, it throws the exception.
There are 2 ways to solve this:
1. You can wrap this in a Try/Catch block like so:
Account lateAccount = null;
try{
lateAccount = Account lateAccount = [Select Id, Name from Account where Name =: inputName limit 1];}
Catch(Exception e)
{ system.debug('There was an exception, but we can just set the lateAccount to null and continue on....);}
2. You can store the results in a List<SObject> instead of just an SObject and grab the first result like so:
Account lateAccount = null;
List<Account> resultAccounts = [Select Id, Name from Account where Name =: inputName limit 1];
if(resultAccounts.size() > 0){lateAccount = resultAccounts.get(0);}
Its as simple as that. What method do I prefer? Personally, I like storing my results in Lists. Why? Well I usually already have try/catch logic to handle exceptions, and nesting additional try/catches can get a bit bulky. Its just a preference is all. Both approaches work perfectly fine. And depending on how your write your code, it may be preferable to do the Try/Catch approach. Its really up to the developers own taste.
Bottom Line - Its an easy problem to solve if you know the details, and now you know!
And knowing is half the battle!
Saturday, February 5, 2011
Force.com Sites - Creating a Personal Website in 15 minutes and 15 dollars.
Do you have 15 minutes and 15 dollars? Do you want to create your own personal website and have the full power of an easy to use database on the back end? Well, if so then Force.com has you covered! I used Force.com, some super simple HTML, a 15 dollar domain name registration from www.godaddy.com, and 15 minutes of my time and created a personal website (http://www.corycowgill.com). Its not real pretty at the moment, but the point of this is you can setup a site in 15 minutes. :)
Salesforce gives away free Force.com Licenses to anyone. These are production instances, NOT the developer edition instances you can also get for free. The Free Force.com edition gives you 1 GB of data, 100 Force.com user licenses, 250,000 page views, and some other goodies. You can sign up here to get your own free instance: https://www.salesforce.com/form/signup/freeforce-platform.jsp .
When you sign up for the Free Force.com edition, go ahead and setup your Force.com Site. You can follow this guide here: http://wiki.developerforce.com/index.php/An_Introduction_to_Force.com_Sites.
When you create a Force.com Site, it will provide you a URL like http://free-12415f14c3a-124e539428a-12dbf2f8eb2.force.com . Great, so we have a public website, but we don't have a very friendly name. No problem. Go to any DNS registrar, like GoDaddy, and you can register a domain name. Then use a CNAME to point to your Force.com Site. There is a guide here that lets you do that: http://developer.force.com/cookbook/recipe/registering-a-custom-domain-for-your-force-com-site .
This is another reason why I love working with the Force.com Platform. It makes your life super simple. You don't have to deal with Web Servers, Database setup, Email Services, Security setup, ETC. When you deploy a Force.com Site, you get the features of Salesforce.com like the Database, Secutity, Email Services, ETC.
Salesforce gives away free Force.com Licenses to anyone. These are production instances, NOT the developer edition instances you can also get for free. The Free Force.com edition gives you 1 GB of data, 100 Force.com user licenses, 250,000 page views, and some other goodies. You can sign up here to get your own free instance: https://www.salesforce.com/form/signup/freeforce-platform.jsp .
When you sign up for the Free Force.com edition, go ahead and setup your Force.com Site. You can follow this guide here: http://wiki.developerforce.com/index.php/An_Introduction_to_Force.com_Sites.
When you create a Force.com Site, it will provide you a URL like http://free-12415f14c3a-124e539428a-12dbf2f8eb2.force.com . Great, so we have a public website, but we don't have a very friendly name. No problem. Go to any DNS registrar, like GoDaddy, and you can register a domain name. Then use a CNAME to point to your Force.com Site. There is a guide here that lets you do that: http://developer.force.com/cookbook/recipe/registering-a-custom-domain-for-your-force-com-site .
This is another reason why I love working with the Force.com Platform. It makes your life super simple. You don't have to deal with Web Servers, Database setup, Email Services, Security setup, ETC. When you deploy a Force.com Site, you get the features of Salesforce.com like the Database, Secutity, Email Services, ETC.
Monday, January 24, 2011
SQL / SOQL Injection
This past weekend I was hanging out with some friends and we came up on the topic of SQL injection. Yeah, we are nerds. Anyway, I was giving my buddy a hard time because he wasn't parametizing his SQL statement in PHP. Really I didn't even dig in his code that much, just glanced at it and wanted to give him a hard time since I was stressed about the Bears game. And for good reason, since we lost! Ah, but its all in good fun!
Anyhoot, I've played around in a lot of languages, but not PHP. There was some minor back and forth about this as another buddy shared his thoughts. I stated that I've never seen any language or database that takes a query and automatically escapes it for you once you call it. The standard defense against SQL injection that I've always used, and most often seen implemented in production has been SQL parametrization.
My friend stated that in PHP you can call built in escape string function on the SQL string and it will take care of it for you. (Note: You can't escape the whole query string, you have to escape each individual input field and then use those escaped string in the query string).
I stated that generally as a developer you want to prevent SQL injection by using parametrization or persistence frameworks if possible. This set off some discussions, and then the Bears game got started so the discussion kind of died as my heart rate ramped up for the (disappointing) game. However, during lunch today I decided to write up on the topic.
You can checkout the SQL Injection wikipedia entry here, parametrization is the first method recommended. Escaping strings is also mentioned as a solution for PHP, although the ability to forget to escape a field makes this method error prone and cumbersome. Another SQL Injection site here recommends using escape string methods only when necessary as it creates frail code.
In Java using straight JDBC drivers you usually use parametrization of the query AKA prepared statements. (Java noobs click here). Some persistence frameworks like Hibernate will take care of parametization for you by default, but are still vulnerable to additional injection attacks you need to guard against. If your a glutten for pain your probably an Adobe Flex developer, and you also parametrize the SQL query. Shiver. I'm not a fan of developing in Adobe Flex. I spent about 6 months working with it and decided it wasn't for me!
In Force.com, we use parametization by using the bind ( :varName) ability of SOQL in Apex. Per the Force.com Documenation, "To prevent a SOQL injection attack, avoid using dynamic SOQL queries. Instead, use static queries and binding variables. "
For example:
List<String> idList = new List<String>{'XXXXXXXXX','YYYYYYYYY','ZZZZZZZZZZZ'};
List<Account> accounts = [Select Id, Name from Account where Id in :idList];
You can escape the user input, per Force.com documentation "If you must use dynamic SOQL, use the
Furthermore, Salesforce and Apex developers should be using the Force.com Security Scanner to check their code base for security holes like SOQL injection. Using the scanner is very easy, all you need to do is provide your login address to the security scanner at http://security.force.com/sourcescanner. It will automatically generate a pretty sweet code analysis report in PDF format and email it to your email address associated with the login address. It also gives you the solution on how to re-factor your code to solve the hole, which saves you time researching the solution. Pretty slick!
Anyhoot, I've played around in a lot of languages, but not PHP. There was some minor back and forth about this as another buddy shared his thoughts. I stated that I've never seen any language or database that takes a query and automatically escapes it for you once you call it. The standard defense against SQL injection that I've always used, and most often seen implemented in production has been SQL parametrization.
My friend stated that in PHP you can call built in escape string function on the SQL string and it will take care of it for you. (Note: You can't escape the whole query string, you have to escape each individual input field and then use those escaped string in the query string).
I stated that generally as a developer you want to prevent SQL injection by using parametrization or persistence frameworks if possible. This set off some discussions, and then the Bears game got started so the discussion kind of died as my heart rate ramped up for the (disappointing) game. However, during lunch today I decided to write up on the topic.
You can checkout the SQL Injection wikipedia entry here, parametrization is the first method recommended. Escaping strings is also mentioned as a solution for PHP, although the ability to forget to escape a field makes this method error prone and cumbersome. Another SQL Injection site here recommends using escape string methods only when necessary as it creates frail code.
In Java using straight JDBC drivers you usually use parametrization of the query AKA prepared statements. (Java noobs click here). Some persistence frameworks like Hibernate will take care of parametization for you by default, but are still vulnerable to additional injection attacks you need to guard against. If your a glutten for pain your probably an Adobe Flex developer, and you also parametrize the SQL query. Shiver. I'm not a fan of developing in Adobe Flex. I spent about 6 months working with it and decided it wasn't for me!
In Force.com, we use parametization by using the bind ( :varName) ability of SOQL in Apex. Per the Force.com Documenation, "To prevent a SOQL injection attack, avoid using dynamic SOQL queries. Instead, use static queries and binding variables. "
For example:
List<String> idList = new List<String>{'XXXXXXXXX','YYYYYYYYY','ZZZZZZZZZZZ'};
List<Account> accounts = [Select Id, Name from Account where Id in :idList];
You can escape the user input, per Force.com documentation "If you must use dynamic SOQL, use the
escapeSingleQuotes
method to sanitize user-supplied input.". But they strongly recommend parametrization of queries. Furthermore, Salesforce and Apex developers should be using the Force.com Security Scanner to check their code base for security holes like SOQL injection. Using the scanner is very easy, all you need to do is provide your login address to the security scanner at http://security.force.com/sourcescanner. It will automatically generate a pretty sweet code analysis report in PDF format and email it to your email address associated with the login address. It also gives you the solution on how to re-factor your code to solve the hole, which saves you time researching the solution. Pretty slick!
Tuesday, January 18, 2011
Using Visual Flow in Salesforce Spring 11
NOTE: As of Spring 12 all Orgs will have Visual Flow for FREE! SWEET! Also, this post is a little old, there is already an update with the new Cloud Flow Designer.
You know what can be tedious to work with? Wizards. And I’m not talking about wizards like Gandolf who can shoot lighting and carry swords with a +10 mana regeneration rate. I’m talking about wizards that navigate a user through a flow or decision making process. Many programming languages and platforms provide setup wizards in their IDE’s that allow you to create a wizard framework quickly. This frees the developer from manually creating all the framework code to build one.
You know what can be tedious to work with? Wizards. And I’m not talking about wizards like Gandolf who can shoot lighting and carry swords with a +10 mana regeneration rate. I’m talking about wizards that navigate a user through a flow or decision making process. Many programming languages and platforms provide setup wizards in their IDE’s that allow you to create a wizard framework quickly. This frees the developer from manually creating all the framework code to build one.
As of Salesforce Winter 11, this was something that was lacking in Salesforce and Force.com platform.
You’d have to manually create a bunch of Visualforce Pages and Apex Controller and flow the user through them manually, which was a pain!
In Spring 11 however, we are getting Visual Flows GA (Generally Available). This was previously available in Pilot for Winter 11 release under the name “Visual Process Manager”. This allows you to build business processes quickly by drag, dropping, and connecting items in the Flow Designer tool. In my Spring 11 Release org I’ve been playing around with it and it’s pretty sweet. You can even invoke Apex methods from the flows for reusable / complex logic.
Use Case: New Account Wizard
This use case will create a new account using the Visual Flow tool. To create a Visual Flow, we need to do a few things first.
- Gain access to a Salesforce Pre-Release Spring 11 Org. https://www.salesforce.com/form/signup/prerelease-spring11.jsp
- Enable the user as a “Force.com Flow user”. You can access this in Setup -> Manage Users -> Edit A user. Check the “Force.com Flow User” box.
- Download the Flow Designer tool from Developer Tools and install. You can access this in Setup -> Create -> Workflows & Approvals - > Flows. The download link is on the screen.
Once we have our environments setup and our user configured as a Flow user, we can create a flow. I recommend following the User Manual PDF provided in the Flow Designer tool to learn how to create a beginning flow (Help -> User Manual). It contains a pretty decent quick tutorial, but it’s a little lightweight. For example, the flow tutorial only provides a flow which does a calculation. It does not utilize the Lookup or Update elements which are critical to a lot of workflows. But it does introduce you to the concepts.
After following the tutorial, I updated my flow to use the Data Update element and the Send Email element to perform an Account insert as well as email me a confirmation email that the account was created. I am including my test flow file so you can use it in your org.
You can grab the complete test flow file here: Google Code - TestFlow File.
When I finished, my flow looked like this in the Flow Editor:
To test this flow, I created a homepage component that linked to the flow allowing the user to execute it from their homepage. As you can see, the flow will execute in a new window and the user can go through the generated screens and create a new Account.
1. User clicks the Homepage Component to execute the wizard:
2. Wizard starts up and user goes through the prompts to create the record.You can also embed these flows inside Visualforce Mashups so maybe I’ll play around with that next.
Overall, this is pretty exciting for developers and even administrators of Salesforce. I can see a lot of efficiency by building all sorts of custom wizards quickly using this tool. This will probably be heavily utilized in all those call center applications.
Friday, January 14, 2011
Generating a Barcode in a Visualforce Page using Javascript
You can generate a barcode in a Visualforce Page using Javascript pretty easily. Some folks forget that you can execute Javascript in a Visualforce page. This opens you up to a lot of the goodies you can do with the client browser.
The trick is to find the right Javascript library which provides the type of barcode you want to generate, or you could always write your own.
At a highlevel, the steps you need to do are:
1. Upload a JS Library (someJSLib.js) to your Static Resources in Salesforce.
2. Reference the JS Library in your Visualforce page by using the <apex:includeScript> tag.
3. Execute the Javascript functions to generate the barcode content and display in your page.
For this example, I am using a Barcode 39 Javascript library provided by http://www.codeproject.com/KB/HTML/Code-39-Barcode.aspx.
1. First I upload the JS Library to my Static Resources:
2. After I have uploaded the library, I embed the JS file in my VF Page using the <apex:includeScript> tag:
<apex:includeScript value="{!$Resource.BarcodeScript}"/>
3. I execute javascript in the Visualforce page to generate the barcode (Full VF Page Code):
<apex:page standardController="Position__c">
<apex:includeScript value="{!$Resource.BarcodeScript}"/>
<apex:detail relatedList="false"></apex:detail>
<br/>
<br/>
<div id="inputdata">{!Position__c.Name}</div>
<div id="externalbox" style="width:5in"></div>
<script type="text/javascript">
/* <![CDATA[ */
function get_object(id) {
alert('Executed');
var object = null;
if (document.layers) {
object = document.layers[id];
} else if (document.all) {
object = document.all[id];
} else if (document.getElementById) {
object = document.getElementById(id);
}
return object;
}
get_object("inputdata").innerHTML=DrawCode39Barcode(get_object("inputdata").innerHTML,1);
/* ]]> */
</script>
</apex:page>
4. When I view this page it will take the Name field from my Position__c object and encode it in the Barcode:
That's all it takes!
The only challenging part really is finding JS Libraries that are opensource and support your barcode format.
Here are some websites that have JS Libraries available:
EAN-13: http://www.parkscomputing.com/barcode.html
Barcode 39: http://www.codeproject.com/KB/HTML/Code-39-Barcode.aspx
Barcode 39: http://www.atalasoft.com/cs/blogs/loufranco/archive/2008/04/25/writing-code-39-barcodes-with-javascript.aspx
The trick is to find the right Javascript library which provides the type of barcode you want to generate, or you could always write your own.
At a highlevel, the steps you need to do are:
1. Upload a JS Library (someJSLib.js) to your Static Resources in Salesforce.
2. Reference the JS Library in your Visualforce page by using the <apex:includeScript> tag.
3. Execute the Javascript functions to generate the barcode content and display in your page.
For this example, I am using a Barcode 39 Javascript library provided by http://www.codeproject.com/KB/HTML/Code-39-Barcode.aspx.
1. First I upload the JS Library to my Static Resources:
2. After I have uploaded the library, I embed the JS file in my VF Page using the <apex:includeScript> tag:
<apex:includeScript value="{!$Resource.BarcodeScript}"/>
3. I execute javascript in the Visualforce page to generate the barcode (Full VF Page Code):
<apex:page standardController="Position__c">
<apex:includeScript value="{!$Resource.BarcodeScript}"/>
<apex:detail relatedList="false"></apex:detail>
<br/>
<br/>
<div id="inputdata">{!Position__c.Name}</div>
<div id="externalbox" style="width:5in"></div>
<script type="text/javascript">
/* <![CDATA[ */
function get_object(id) {
alert('Executed');
var object = null;
if (document.layers) {
object = document.layers[id];
} else if (document.all) {
object = document.all[id];
} else if (document.getElementById) {
object = document.getElementById(id);
}
return object;
}
get_object("inputdata").innerHTML=DrawCode39Barcode(get_object("inputdata").innerHTML,1);
/* ]]> */
</script>
</apex:page>
4. When I view this page it will take the Name field from my Position__c object and encode it in the Barcode:
That's all it takes!
The only challenging part really is finding JS Libraries that are opensource and support your barcode format.
Here are some websites that have JS Libraries available:
EAN-13: http://www.parkscomputing.com/barcode.html
Barcode 39: http://www.codeproject.com/KB/HTML/Code-39-Barcode.aspx
Barcode 39: http://www.atalasoft.com/cs/blogs/loufranco/archive/2008/04/25/writing-code-39-barcodes-with-javascript.aspx
Monday, January 10, 2011
Salesforce Spring 11 Release Update - Governor Limits
I just finished reading the Salesforce Spring 11 Release Notes and I am floored by the new Force.com updates. Some of my most frustrating headaches are getting alleviated. Not completely eliminated, but alleviated by a huge amount.
When I first started working on the Force.com Platform, coming from a J2EE background, I was instantly frustrated with the governor limits enforced on Apex code. Only 1000 records in all my SOQL queries in a Trigger context? Only 20 DML statements? It has been the most frustrating part of an otherwise simple development platform.
Well, in Spring 11, we are getting some huge updates!
First, all Apex code will now execute in a single context. Previously we had two context (Trigger, and Anonymous/Controller/Everything Else). These two context had their own governor limits, with the Trigger context being the most restrictive. Well, not anymore! All Apex code will now execute in one context! WOW.
And if thats not all, there's more!
Secondly, the total number of records in a context's SOQL queries has increased to 50,000! Yeah, 50,000! Previously in a Trigger you could only get 1,000. Thats 50 times the number of records!
Oh, and almost forgot. Full REST API is generally available!
Man, what an awesome release for developers.
Check out the full release notes here: Salesforce Spring 11 Release Notes
When I first started working on the Force.com Platform, coming from a J2EE background, I was instantly frustrated with the governor limits enforced on Apex code. Only 1000 records in all my SOQL queries in a Trigger context? Only 20 DML statements? It has been the most frustrating part of an otherwise simple development platform.
Well, in Spring 11, we are getting some huge updates!
First, all Apex code will now execute in a single context. Previously we had two context (Trigger, and Anonymous/Controller/Everything Else). These two context had their own governor limits, with the Trigger context being the most restrictive. Well, not anymore! All Apex code will now execute in one context! WOW.
And if thats not all, there's more!
Secondly, the total number of records in a context's SOQL queries has increased to 50,000! Yeah, 50,000! Previously in a Trigger you could only get 1,000. Thats 50 times the number of records!
Oh, and almost forgot. Full REST API is generally available!
Man, what an awesome release for developers.
Check out the full release notes here: Salesforce Spring 11 Release Notes
Sunday, January 9, 2011
The Death Star: Poor Project Management in Practice
This week I pulled out my Star Wars DVD's and watched them from start to finish for the first time in a few years. Those movies were the baseline for my childhood. I remember playing Star Wars with my friends on Sunday afternoons as a child. There would always be the argument over who would play Luke and who would play Han Solo, with the unluckiest kid getting stuck as Chewbacca or Lando. We'd run around the neighborhood fighing Tie Fighters and AT-AT's. But watching these holy films as an adult is a different experience entirely.
The opening scene of 'Return of the Jedi' is a prime example. Lord Vader arrives at the second Death Star which is under construction. Apparently the Death Star construction is behind schedule, and the primary stakehold (Emperor Palpatine) is not happy. So Lord Vader flies out there to speak with the Commander in charge of the construction to get them back on schedule. The commander tells Lord Vader that the timelines are ridiculous, and that he needs more men to meet the deadlines.
I watch this scene and now, I see the whole Death Star construction as a project. Complete with milestones, tasks, assigned resources, stakeholders and status meetings. And I can't help but see a few of the problems facing the Death Star project.
1. Bad Assumptions
I can just see someone saying "Well, the second one should be easier than the first because we have learned so many lessons". Normally, yes this would be true. But you've got quite a few differences this time around. First, you've lost Grand Moff Tarkin, arguably the father of the Death Star, when he died in the explosion of the first Death Star. Who knows how much knowledge was lost there that could impact timelines. If you lose your subject matter expert, don't expert to gain efficiencies on the next project automatically!
Secondly, even when repeating similar projects, they each have their own differences which can manifest unique problems. And I'm just talking about software development for enterprises. Imagine building giant space stations the size of a small moon!
2. Poor Escalation of Issues
The first thing the commander says to Darth Vader is "I need more men!". Really buddy? You have had resource bandwidth issues for long enough that the primary stakeholder has noticed and has had to take action? Your lucky Lord Vader didn't force choke you on the spot. You have to escalate the issues when they first crop up, not halfway through the development process.
Waiting until your already behind on the milestones and tasks is horrible project management. And just throwing more people at a problem is not going to solve it. You need bring in the right resources, not just more people. I've seen this dozen of times. A project is behind schedule, so the PM tries to just throw more hands at the problem by utilizing a bunch of offshore resources or low cost junior developers at the problem. The result? Poor quality deliverables that need to be rewritten by a more senior resource at greater expense and more time.
3. The Production Deadline Drives the Process
The Emperor engages the rebel fleet with this half constructed Death Star on purpose. Its not even fully built yet. Its fully operational in regards to the fact that its Planetary Laser is working, and he blows up a few capital ships. But a full frontal attack by capital ships was never the weakness of the Death Star, it was small fighters attacking it that made it vulnerable.
The second Death Star had the defect fixed so that the exhaust ports would not allow a direct hit to blow it up. But its outer hull and its full complement of defenses are not finished when the Emperor traps the rebels into fighting. If the Emperor had waited until the Death Star was fully completed like the first one, then there would have been no holes in the hull to allow the ships to fly to the center and blow it up!
Deploying a half completed application into production is going to bite you in the ass everytime!
Oh Palpatine, if you had just managed the construction of the second Death Star properly, you could have won! You could have crushed the rebel scum! Oh well.
The opening scene of 'Return of the Jedi' is a prime example. Lord Vader arrives at the second Death Star which is under construction. Apparently the Death Star construction is behind schedule, and the primary stakehold (Emperor Palpatine) is not happy. So Lord Vader flies out there to speak with the Commander in charge of the construction to get them back on schedule. The commander tells Lord Vader that the timelines are ridiculous, and that he needs more men to meet the deadlines.
I watch this scene and now, I see the whole Death Star construction as a project. Complete with milestones, tasks, assigned resources, stakeholders and status meetings. And I can't help but see a few of the problems facing the Death Star project.
1. Bad Assumptions
I can just see someone saying "Well, the second one should be easier than the first because we have learned so many lessons". Normally, yes this would be true. But you've got quite a few differences this time around. First, you've lost Grand Moff Tarkin, arguably the father of the Death Star, when he died in the explosion of the first Death Star. Who knows how much knowledge was lost there that could impact timelines. If you lose your subject matter expert, don't expert to gain efficiencies on the next project automatically!
Secondly, even when repeating similar projects, they each have their own differences which can manifest unique problems. And I'm just talking about software development for enterprises. Imagine building giant space stations the size of a small moon!
2. Poor Escalation of Issues
The first thing the commander says to Darth Vader is "I need more men!". Really buddy? You have had resource bandwidth issues for long enough that the primary stakeholder has noticed and has had to take action? Your lucky Lord Vader didn't force choke you on the spot. You have to escalate the issues when they first crop up, not halfway through the development process.
Waiting until your already behind on the milestones and tasks is horrible project management. And just throwing more people at a problem is not going to solve it. You need bring in the right resources, not just more people. I've seen this dozen of times. A project is behind schedule, so the PM tries to just throw more hands at the problem by utilizing a bunch of offshore resources or low cost junior developers at the problem. The result? Poor quality deliverables that need to be rewritten by a more senior resource at greater expense and more time.
3. The Production Deadline Drives the Process
The Emperor engages the rebel fleet with this half constructed Death Star on purpose. Its not even fully built yet. Its fully operational in regards to the fact that its Planetary Laser is working, and he blows up a few capital ships. But a full frontal attack by capital ships was never the weakness of the Death Star, it was small fighters attacking it that made it vulnerable.
The second Death Star had the defect fixed so that the exhaust ports would not allow a direct hit to blow it up. But its outer hull and its full complement of defenses are not finished when the Emperor traps the rebels into fighting. If the Emperor had waited until the Death Star was fully completed like the first one, then there would have been no holes in the hull to allow the ships to fly to the center and blow it up!
Deploying a half completed application into production is going to bite you in the ass everytime!
Oh Palpatine, if you had just managed the construction of the second Death Star properly, you could have won! You could have crushed the rebel scum! Oh well.
Tuesday, January 4, 2011
Visualforce Page - Attachment Upload Example
I see a lot of folks posting questions in the developer forums about uploading an attachment into Salesforce through a Visualforce Page. It is actually very easy to accomplish this.
You will want to use the apex tag <apex:inputFile> to put the FileChooser Popup on the VF Page.
Inside your controller, you simply create an empty Document object, and you bind the <apex:inputFile> tag to the Document in the controller with this code here:
<apex:inputFile value="{!attach.body}" filename="{!attach.name}"/>
With your bindings all set, you just need to create an action on the controller to retrieve the values from the insert the Attachment Object. This can be done in the upload() method in the code below.
This small custom controller and Visualforce page will allow a user to upload a file and create an attachment on the record for an ID give. Here are the screenshots to show this in action.
Once the upload() method finishes it sends the user to the new Attachment.
Here is the full code below:
AttachmentUploadController - Apex Controller
//Simple Custom Controller to Insert an Attachment into Salesforce
public with sharing class AttachmentUploadController
{
public String parentId {get;set;}
public Attachment attach {get;set;}
public AttachmentUploadController()
{
attach = new Attachment();
}
//When user clicks upload button on Visualforce Page, perform upload/insert
//Redirect user to newly inserted document
public ApexPages.Pagereference upload()
{
//This shows how to insert an Attachment
attach.ParentId = parentId;
insert attach;
return new ApexPages.Standardcontroller(attach).view();
}
}
AttachmentUpload - Visualforce Page
<apex:page controller="AttachmentUploadController">
<apex:form >
<apex:outputText value="Parent Object ID: "/><apex:inputText value="{!parentId}"/><br/>
<apex:outputText value="Input File: "/><apex:inputFile value="{!attach.body}" filename="{!attach.name}"/><br/>
<apex:commandButton value="Upload" action="{!upload}"/>
</apex:form>
</apex:page>
Its really that easy. Of course you'll need to add some error handling to check for null files and stuff like that, but the basics are super easy. This is a good example of a development activity that Apex makes quick and easy.
Of course when you hit Apex limits or things it doesn't do well, thats when you want to pull your hair out.
You will want to use the apex tag <apex:inputFile> to put the FileChooser Popup on the VF Page.
Inside your controller, you simply create an empty Document object, and you bind the <apex:inputFile> tag to the Document in the controller with this code here:
<apex:inputFile value="{!attach.body}" filename="{!attach.name}"/>
With your bindings all set, you just need to create an action on the controller to retrieve the values from the insert the Attachment Object. This can be done in the upload() method in the code below.
This small custom controller and Visualforce page will allow a user to upload a file and create an attachment on the record for an ID give. Here are the screenshots to show this in action.
Once the upload() method finishes it sends the user to the new Attachment.
Here is the full code below:
AttachmentUploadController - Apex Controller
//Simple Custom Controller to Insert an Attachment into Salesforce
public with sharing class AttachmentUploadController
{
public String parentId {get;set;}
public Attachment attach {get;set;}
public AttachmentUploadController()
{
attach = new Attachment();
}
//When user clicks upload button on Visualforce Page, perform upload/insert
//Redirect user to newly inserted document
public ApexPages.Pagereference upload()
{
//This shows how to insert an Attachment
attach.ParentId = parentId;
insert attach;
return new ApexPages.Standardcontroller(attach).view();
}
}
AttachmentUpload - Visualforce Page
<apex:page controller="AttachmentUploadController">
<apex:form >
<apex:outputText value="Parent Object ID: "/><apex:inputText value="{!parentId}"/><br/>
<apex:outputText value="Input File: "/><apex:inputFile value="{!attach.body}" filename="{!attach.name}"/><br/>
<apex:commandButton value="Upload" action="{!upload}"/>
</apex:form>
</apex:page>
Its really that easy. Of course you'll need to add some error handling to check for null files and stuff like that, but the basics are super easy. This is a good example of a development activity that Apex makes quick and easy.
Of course when you hit Apex limits or things it doesn't do well, thats when you want to pull your hair out.
Monday, January 3, 2011
Building Dynamic SOQL - Select All Query
Salesforce SOQL does not allow select * queries. For example, you cannot do "Select * from Account where Id = 'XXXXXXXX'". This can be confusing to new Apex developers who are familiar with SQL, since SOQL syntax is very close to SQL. This presents developers with the question: How can I dynamically query for all the fields on an object? The answer is you can use Salesforce Schema Describe objects to dynamically build SOQL queries at run time to query for all fields on a record.
In this post, we are going to use the Schema Describe methods to do a few interesting things.
Map<String,Schema.SObjectType> schemaMap = Schema.getGlobalDescribe();
Now that we have the Schema SObjects in memory, we can iterate over the SObject to determine which type of SObject this ID referers to by calling this code here:
List<Schema.SObjectType> sobjects = schemaMap.values();
List<Sobject> theObjectResults;
Schema.DescribeSObjectResult objDescribe;
List<Schema.SObjectField> tempFields;
for(Schema.SObjectType objType : sobjects)
{
objDescribe = objType.getDescribe();
String sobjectPrefix = objDescribe.getKeyPrefix();
if(id != null && sobjectPrefix != null && id.startsWith(sobjectPrefix))
{
objectType = objDescribe.getLocalName();
Map<String, Schema.SObjectField> fieldMap = objDescribe.fields.getMap();
tempFields = fieldMap.values();
for(Schema.SObjectField sof : tempFields)
{
fields.add(sof.getDescribe());
}
getAllQuery = buildQueryAllString(fields,objDescribe,id);
}
}
resultObject = Database.query(getAllQuery);
for(Schema.DescribeFieldResult dfr : fields)
{
fieldVals.add(new GenericFieldVO(dfr,resultObject));
}
We use the objDescribe.getKeyPrefix() method to retrieve the prefix for the Object. This is a great API call that I seldom see used. Many times I see developers hard-code the prefix in the code, which I personally can't stand. After we have the prefix, we check that against the ID to see if the ID starts with the prefix. If it does, then we know that this is the Object Describe to use.
Once we have the correct SObject definition, then we can get all the field definitions by calling objDescribe.fields.getMap(). This returns a Map of the SObjectField type which contains the field definitions (labels, data type, etc). With this information we are now ready to build a dynamic query which will query for all the data for this particular ID.
//Build the Query String
private String buildQueryAllString(List<Schema.DescribeFieldResult> queryFields,DescribeSObjectResult obj, String theId)
{
String query = QUERY_SELECT;
for(Schema.DescribeFieldResult dfr : queryFields)
{
query = query + dfr.getName() + ',';
}
query = query.subString(0,query.length() - 1);
query = query + QUERY_FROM;
query = query + obj.getName();
query = query + QUERY_WHERE;
query = query + theId + '\'';
system.debug('Build Query == ' + query);
return query;
}
Bam. We now have the ability to build dynamic queries which will retrieve all the information for a object. For this example I have built a Visualforce Page which displays the dynamic values. I will included the full source for this at the bottom of this post. Here is the output of our dynamic SOQL calls for when I give it an Contact ID:
And then I just provide a second ID of a Account:
You can see that the values are dynamically queried and populated on the screen. Of course this particular example doesn't have much business value, you can always just put http://www.salesforce.com/XXXXXXXXX where XXXXXXX is your ID and go straight to the record. But this example shows that you can build dynamic SOQL queries to do 'Select *' type functionality with relative ease.
Now here is the full Apex code dump of this simple page.
To test, just simply pass a URL like https://c.na3.visual.force.com/apex/GenericSelectAll?id=XXXXXXXXX into your browser where ?id=XXXXXXXXXXX is your ID.
SchemaManager
//This class will do all the methods to retrieve schema infomraiton on SObject for apex
//This will ensure that multiple calls to descibes aren't called so we don't hit gov limits
public with sharing class SchemaManager
{
private static Map<String, Schema.SObjectType> sobjectSchemaMap;
public static Map<String,Schema.SObjectType> getSchemaMap()
{
if(sobjectSchemaMap == null)
{
sobjectSchemaMap = Schema.getGlobalDescribe();
}
return sobjectSchemaMap;
}
//Retrieve the specific Schema.SobjectType for a object so we can inspect it
public static Schema.SObjectType getObjectSchema(String objectAPIName)
{
getSchemaMap();
Schema.SObjectType aSObjectType = sobjectSchemaMap.get(objectAPIName);
return aSobjectType;
}
}
GenericSelectAllController
//A simple custom controller that will take any ID as input
//and query for all fields (up to 90) on the SObject dynamically
public with sharing class GenericSelectAllController
{
public List<Schema.DescribeFieldResult> fields {get;set;}
public List<GenericFieldVO> fieldVals {get;set;}
public String getAllQuery {get;set;}
public SObject resultObject {get;set;}
public String objectType {get;set;}
public String searchId {get;set;}
public List<SObject> vals {get;set;}
public static final String ERROR_ID_MISSING = 'There was no id passed in the parameters. Id is required.';
public static final String QUERY_SELECT = 'select ';
public static final String QUERY_FROM = ' from ';
public static final String QUERY_WHERE = ' where Id = \'';
//Instantiate the controller. If there is no ID then send the error message to the page.
public GenericSelectAllController()
{
init();
String id = ApexPages.currentPage().getParameters().get('id');
if(id == null || id == '')
{
ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL,ERROR_ID_MISSING));
}
else
{
searchId = id;
processSchemaInfo(id);
}
}
//Process the Schema information
//1. Retrieve the global Schema Information
//2. Iterate over the SObject Schema Information
//2.A Retrieve the SObject Key Prefix and match to the ID passed into page
//2.B Describe all the Fields for the SObject
//2.C Build a Query String from all the Fields
private void processSchemaInfo(String id)
{
system.debug(id);
Map<String,Schema.SObjectType> schemaMap = SchemaManager.getSchemaMap();
List<Schema.SObjectType> sobjects = schemaMap.values();
List<Sobject> theObjectResults;
Schema.DescribeSObjectResult objDescribe;
List<Schema.SObjectField> tempFields;
for(Schema.SObjectType objType : sobjects)
{
objDescribe = objType.getDescribe();
String sobjectPrefix = objDescribe.getKeyPrefix();
if(id != null && sobjectPrefix != null && id.startsWith(sobjectPrefix))
{
objectType = objDescribe.getLocalName();
Map<String, Schema.SObjectField> fieldMap = objDescribe.fields.getMap();
tempFields = fieldMap.values();
for(Schema.SObjectField sof : tempFields)
{
fields.add(sof.getDescribe());
}
getAllQuery = buildQueryAllString(fields,objDescribe,id);
}
}
resultObject = Database.query(getAllQuery);
for(Schema.DescribeFieldResult dfr : fields)
{
fieldVals.add(new GenericFieldVO(dfr,resultObject));
}
}
private void init()
{
getAllQuery = '';
fields = new List<Schema.DescribeFieldResult>();
fieldVals = new List<GenericFieldVO>();
}
//Build the Query String
private String buildQueryAllString(List<Schema.DescribeFieldResult> queryFields,DescribeSObjectResult obj, String theId)
{
String query = QUERY_SELECT;
for(Schema.DescribeFieldResult dfr : queryFields)
{
query = query + dfr.getName() + ',';
}
query = query.subString(0,query.length() - 1);
query = query + QUERY_FROM;
query = query + obj.getName();
query = query + QUERY_WHERE;
query = query + theId + '\'';
system.debug('Build Query == ' + query);
return query;
}
}
GenericFieldVO
GenericFieldVO
/* ============================================================
* Part of this code has been modified from source that is part of the "apex-lang" open source project avaiable at:
*
* http://code.google.com/p/apex-lang/
*
* This code is licensed under the Apache License, Version 2.0. You may obtain a
* copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
* ============================================================
*/
// A Generic View Object which is used to wrap generic SObject Field Data for Visualforce Page display.
public with sharing class GenericFieldVO
{
public String fieldLabel {get;set;}
public String stringVal {get;set;}
public Boolean boolVal {get;set;}
public Date dateVal {get;set;}
public Integer intVal {get;set;}
public Double doubleVal {get;set;}
public DateTime dateTimeVal {get;set;}
public ID idVal {get;set;}
public Boolean isBool {get;set;}
private static final List<Schema.DisplayType> STRING_TYPES = new List<Schema.DisplayType>{
Schema.DisplayType.base64
,Schema.DisplayType.Email
,Schema.DisplayType.MultiPicklist
,Schema.DisplayType.Phone
,Schema.DisplayType.Picklist
,Schema.DisplayType.String
,Schema.DisplayType.TextArea
,Schema.DisplayType.URL
};
private static final List<Schema.DisplayType> INTEGER_TYPES = new List<Schema.DisplayType>{
Schema.DisplayType.Integer
};
private static final List<Schema.DisplayType> ID_TYPES = new List<Schema.DisplayType>{
Schema.DisplayType.ID
,Schema.DisplayType.Reference
};
private static final List<Schema.DisplayType> DOUBLE_TYPES = new List<Schema.DisplayType>{
Schema.DisplayType.Currency
,Schema.DisplayType.Double
,Schema.DisplayType.Percent
};
private static final List<Schema.DisplayType> DATETIME_TYPES = new List<Schema.DisplayType>{
Schema.DisplayType.DateTime
};
private static final List<Schema.DisplayType> DATE_TYPES = new List<Schema.DisplayType>{
Schema.DisplayType.Date
};
private static final List<Schema.DisplayType> BOOLEAN_TYPES = new List<Schema.DisplayType>{
Schema.DisplayType.Boolean
,Schema.DisplayType.Combobox
};
public GenericFieldVO(Schema.DescribeFieldResult sourceField, SObject source)
{
fieldLabel = sourceField.getLabel();
if(contains(STRING_TYPES,sourceField.getType())){
stringVal = (String)source.get(sourceField.getName());
} else if(contains(INTEGER_TYPES,sourceField.getType())){
intVal = (Integer)source.get(sourceField.getName());
} else if(contains(ID_TYPES,sourceField.getType())){
idVal = (ID)source.get(sourceField.getName());
} else if(contains(DOUBLE_TYPES,sourceField.getType())){
doubleVal = (Double)source.get(sourceField.getName());
} else if(contains(DATETIME_TYPES,sourceField.getType())){
dateTimeVal = (DateTime)source.get(sourceField.getName());
} else if(contains(DATE_TYPES,sourceField.getType())){
dateVal = (Date)source.get(sourceField.getName());
} else if(contains(BOOLEAN_TYPES,sourceField.getType())){
boolVal = (Boolean)source.get(sourceField.getName());
isBool = true;
}
}
private static Boolean contains(List<Schema.DisplayType> aListActingAsSet, Schema.DisplayType typeToCheck){
if(aListActingAsSet != null && aListActingAsSet.size() > 0){
for(Schema.DisplayType aType : aListActingAsSet){
if(aType == typeToCheck){
return true;
}
}
}
return false;
}
}
GenericSelectAllPage
<apex:page controller="GenericSelectAllController">
<style>
table {width:100%;}
td {width:100%;}
</style>
<apex:messages />
<apex:pageBlock title="Generic Search All: {!searchId}">
<apex:outputPanel layout="block" style="width:100%;" rendered="{!resultObject != null}" id="SobjectDetailPanel">
<apex:outputText style="font-weight:bold;" value="Type: {!objectType}"/>
<br/>
<br/>
<apex:repeat value="{!fieldVals}" var="fieldVal">
<apex:outputText style="font-weight:bold;" value="{!fieldVal.fieldLabel}: "/>
<apex:outputPanel rendered="{!fieldVal.stringVal != null}">
<apex:outputText value="{!fieldVal.stringVal}"/>
</apex:outputPanel>
<apex:outputPanel rendered="{!fieldVal.isBool == true}">
<apex:outputText value="{!fieldVal.boolVal}"/>
</apex:outputPanel>
<apex:outputPanel rendered="{!fieldVal.idVal != null}">
<a href="/{!fieldVal.idVal}"><apex:outputText value="{!fieldVal.idVal}"/></a>
</apex:outputPanel>
<apex:outputPanel rendered="{!fieldVal.intVal != null}">
<apex:outputText value="{0, number, 0}">
<apex:param value="{!fieldVal.intVal}" />
</apex:outputText>
</apex:outputPanel>
<apex:outputPanel rendered="{!fieldVal.dateVal != null}">
<apex:outputText value="{0,date,yyyy.MM.dd}">
<apex:param value="{!fieldVal.dateVal}" />
</apex:outputText>
</apex:outputPanel>
<apex:outputPanel rendered="{!fieldVal.dateTimeVal != null}">
<apex:outputText value="{0,date,yyyy.MM.dd G 'at' HH:mm:ss z}">
<apex:param value="{!fieldVal.dateTimeVal}" />
</apex:outputText>
</apex:outputPanel>
<apex:outputPanel rendered="{!fieldVal.doubleVal != null}">
<apex:outputText value="{0, number,0.00}">
<apex:param value="{!fieldVal.doubleVal}" />
</apex:outputText>
</apex:outputPanel>
<br/>
<br/>
</apex:repeat>
</apex:outputPanel>
</apex:pageBlock>
</apex:page>
In this post, we are going to use the Schema Describe methods to do a few interesting things.
- We are going to dynamically determine the Object Type based on the ID at run-time.
- We are going to retrieve all the field definitions for the Object.
- We are going to build a SOQL query dynamically to query for all the fields.
- Just for fun we are going to populate the results dynamically in a Visualforce Page.
Map<String,Schema.SObjectType> schemaMap = Schema.getGlobalDescribe();
Now that we have the Schema SObjects in memory, we can iterate over the SObject to determine which type of SObject this ID referers to by calling this code here:
List<Schema.SObjectType> sobjects = schemaMap.values();
List<Sobject> theObjectResults;
Schema.DescribeSObjectResult objDescribe;
List<Schema.SObjectField> tempFields;
for(Schema.SObjectType objType : sobjects)
{
objDescribe = objType.getDescribe();
String sobjectPrefix = objDescribe.getKeyPrefix();
if(id != null && sobjectPrefix != null && id.startsWith(sobjectPrefix))
{
objectType = objDescribe.getLocalName();
Map<String, Schema.SObjectField> fieldMap = objDescribe.fields.getMap();
tempFields = fieldMap.values();
for(Schema.SObjectField sof : tempFields)
{
fields.add(sof.getDescribe());
}
getAllQuery = buildQueryAllString(fields,objDescribe,id);
}
}
resultObject = Database.query(getAllQuery);
for(Schema.DescribeFieldResult dfr : fields)
{
fieldVals.add(new GenericFieldVO(dfr,resultObject));
}
We use the objDescribe.getKeyPrefix() method to retrieve the prefix for the Object. This is a great API call that I seldom see used. Many times I see developers hard-code the prefix in the code, which I personally can't stand. After we have the prefix, we check that against the ID to see if the ID starts with the prefix. If it does, then we know that this is the Object Describe to use.
Once we have the correct SObject definition, then we can get all the field definitions by calling objDescribe.fields.getMap(). This returns a Map of the SObjectField type which contains the field definitions (labels, data type, etc). With this information we are now ready to build a dynamic query which will query for all the data for this particular ID.
//Build the Query String
private String buildQueryAllString(List<Schema.DescribeFieldResult> queryFields,DescribeSObjectResult obj, String theId)
{
String query = QUERY_SELECT;
for(Schema.DescribeFieldResult dfr : queryFields)
{
query = query + dfr.getName() + ',';
}
query = query.subString(0,query.length() - 1);
query = query + QUERY_FROM;
query = query + obj.getName();
query = query + QUERY_WHERE;
query = query + theId + '\'';
system.debug('Build Query == ' + query);
return query;
}
Bam. We now have the ability to build dynamic queries which will retrieve all the information for a object. For this example I have built a Visualforce Page which displays the dynamic values. I will included the full source for this at the bottom of this post. Here is the output of our dynamic SOQL calls for when I give it an Contact ID:
And then I just provide a second ID of a Account:
You can see that the values are dynamically queried and populated on the screen. Of course this particular example doesn't have much business value, you can always just put http://www.salesforce.com/XXXXXXXXX where XXXXXXX is your ID and go straight to the record. But this example shows that you can build dynamic SOQL queries to do 'Select *' type functionality with relative ease.
Now here is the full Apex code dump of this simple page.
To test, just simply pass a URL like https://c.na3.visual.force.com/apex/GenericSelectAll?id=XXXXXXXXX into your browser where ?id=XXXXXXXXXXX is your ID.
SchemaManager
//This class will do all the methods to retrieve schema infomraiton on SObject for apex
//This will ensure that multiple calls to descibes aren't called so we don't hit gov limits
public with sharing class SchemaManager
{
private static Map<String, Schema.SObjectType> sobjectSchemaMap;
public static Map<String,Schema.SObjectType> getSchemaMap()
{
if(sobjectSchemaMap == null)
{
sobjectSchemaMap = Schema.getGlobalDescribe();
}
return sobjectSchemaMap;
}
//Retrieve the specific Schema.SobjectType for a object so we can inspect it
public static Schema.SObjectType getObjectSchema(String objectAPIName)
{
getSchemaMap();
Schema.SObjectType aSObjectType = sobjectSchemaMap.get(objectAPIName);
return aSobjectType;
}
}
GenericSelectAllController
//A simple custom controller that will take any ID as input
//and query for all fields (up to 90) on the SObject dynamically
public with sharing class GenericSelectAllController
{
public List<Schema.DescribeFieldResult> fields {get;set;}
public List<GenericFieldVO> fieldVals {get;set;}
public String getAllQuery {get;set;}
public SObject resultObject {get;set;}
public String objectType {get;set;}
public String searchId {get;set;}
public List<SObject> vals {get;set;}
public static final String ERROR_ID_MISSING = 'There was no id passed in the parameters. Id is required.';
public static final String QUERY_SELECT = 'select ';
public static final String QUERY_FROM = ' from ';
public static final String QUERY_WHERE = ' where Id = \'';
//Instantiate the controller. If there is no ID then send the error message to the page.
public GenericSelectAllController()
{
init();
String id = ApexPages.currentPage().getParameters().get('id');
if(id == null || id == '')
{
ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL,ERROR_ID_MISSING));
}
else
{
searchId = id;
processSchemaInfo(id);
}
}
//Process the Schema information
//1. Retrieve the global Schema Information
//2. Iterate over the SObject Schema Information
//2.A Retrieve the SObject Key Prefix and match to the ID passed into page
//2.B Describe all the Fields for the SObject
//2.C Build a Query String from all the Fields
private void processSchemaInfo(String id)
{
system.debug(id);
Map<String,Schema.SObjectType> schemaMap = SchemaManager.getSchemaMap();
List<Schema.SObjectType> sobjects = schemaMap.values();
List<Sobject> theObjectResults;
Schema.DescribeSObjectResult objDescribe;
List<Schema.SObjectField> tempFields;
for(Schema.SObjectType objType : sobjects)
{
objDescribe = objType.getDescribe();
String sobjectPrefix = objDescribe.getKeyPrefix();
if(id != null && sobjectPrefix != null && id.startsWith(sobjectPrefix))
{
objectType = objDescribe.getLocalName();
Map<String, Schema.SObjectField> fieldMap = objDescribe.fields.getMap();
tempFields = fieldMap.values();
for(Schema.SObjectField sof : tempFields)
{
fields.add(sof.getDescribe());
}
getAllQuery = buildQueryAllString(fields,objDescribe,id);
}
}
resultObject = Database.query(getAllQuery);
for(Schema.DescribeFieldResult dfr : fields)
{
fieldVals.add(new GenericFieldVO(dfr,resultObject));
}
}
private void init()
{
getAllQuery = '';
fields = new List<Schema.DescribeFieldResult>();
fieldVals = new List<GenericFieldVO>();
}
//Build the Query String
private String buildQueryAllString(List<Schema.DescribeFieldResult> queryFields,DescribeSObjectResult obj, String theId)
{
String query = QUERY_SELECT;
for(Schema.DescribeFieldResult dfr : queryFields)
{
query = query + dfr.getName() + ',';
}
query = query.subString(0,query.length() - 1);
query = query + QUERY_FROM;
query = query + obj.getName();
query = query + QUERY_WHERE;
query = query + theId + '\'';
system.debug('Build Query == ' + query);
return query;
}
}
GenericFieldVO
GenericFieldVO
/* ============================================================
* Part of this code has been modified from source that is part of the "apex-lang" open source project avaiable at:
*
* http://code.google.com/p/apex-lang/
*
* This code is licensed under the Apache License, Version 2.0. You may obtain a
* copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
* ============================================================
*/
// A Generic View Object which is used to wrap generic SObject Field Data for Visualforce Page display.
public with sharing class GenericFieldVO
{
public String fieldLabel {get;set;}
public String stringVal {get;set;}
public Boolean boolVal {get;set;}
public Date dateVal {get;set;}
public Integer intVal {get;set;}
public Double doubleVal {get;set;}
public DateTime dateTimeVal {get;set;}
public ID idVal {get;set;}
public Boolean isBool {get;set;}
private static final List<Schema.DisplayType> STRING_TYPES = new List<Schema.DisplayType>{
Schema.DisplayType.base64
,Schema.DisplayType.Email
,Schema.DisplayType.MultiPicklist
,Schema.DisplayType.Phone
,Schema.DisplayType.Picklist
,Schema.DisplayType.String
,Schema.DisplayType.TextArea
,Schema.DisplayType.URL
};
private static final List<Schema.DisplayType> INTEGER_TYPES = new List<Schema.DisplayType>{
Schema.DisplayType.Integer
};
private static final List<Schema.DisplayType> ID_TYPES = new List<Schema.DisplayType>{
Schema.DisplayType.ID
,Schema.DisplayType.Reference
};
private static final List<Schema.DisplayType> DOUBLE_TYPES = new List<Schema.DisplayType>{
Schema.DisplayType.Currency
,Schema.DisplayType.Double
,Schema.DisplayType.Percent
};
private static final List<Schema.DisplayType> DATETIME_TYPES = new List<Schema.DisplayType>{
Schema.DisplayType.DateTime
};
private static final List<Schema.DisplayType> DATE_TYPES = new List<Schema.DisplayType>{
Schema.DisplayType.Date
};
private static final List<Schema.DisplayType> BOOLEAN_TYPES = new List<Schema.DisplayType>{
Schema.DisplayType.Boolean
,Schema.DisplayType.Combobox
};
public GenericFieldVO(Schema.DescribeFieldResult sourceField, SObject source)
{
fieldLabel = sourceField.getLabel();
if(contains(STRING_TYPES,sourceField.getType())){
stringVal = (String)source.get(sourceField.getName());
} else if(contains(INTEGER_TYPES,sourceField.getType())){
intVal = (Integer)source.get(sourceField.getName());
} else if(contains(ID_TYPES,sourceField.getType())){
idVal = (ID)source.get(sourceField.getName());
} else if(contains(DOUBLE_TYPES,sourceField.getType())){
doubleVal = (Double)source.get(sourceField.getName());
} else if(contains(DATETIME_TYPES,sourceField.getType())){
dateTimeVal = (DateTime)source.get(sourceField.getName());
} else if(contains(DATE_TYPES,sourceField.getType())){
dateVal = (Date)source.get(sourceField.getName());
} else if(contains(BOOLEAN_TYPES,sourceField.getType())){
boolVal = (Boolean)source.get(sourceField.getName());
isBool = true;
}
}
private static Boolean contains(List<Schema.DisplayType> aListActingAsSet, Schema.DisplayType typeToCheck){
if(aListActingAsSet != null && aListActingAsSet.size() > 0){
for(Schema.DisplayType aType : aListActingAsSet){
if(aType == typeToCheck){
return true;
}
}
}
return false;
}
}
GenericSelectAllPage
<apex:page controller="GenericSelectAllController">
<style>
table {width:100%;}
td {width:100%;}
</style>
<apex:messages />
<apex:pageBlock title="Generic Search All: {!searchId}">
<apex:outputPanel layout="block" style="width:100%;" rendered="{!resultObject != null}" id="SobjectDetailPanel">
<apex:outputText style="font-weight:bold;" value="Type: {!objectType}"/>
<br/>
<br/>
<apex:repeat value="{!fieldVals}" var="fieldVal">
<apex:outputText style="font-weight:bold;" value="{!fieldVal.fieldLabel}: "/>
<apex:outputPanel rendered="{!fieldVal.stringVal != null}">
<apex:outputText value="{!fieldVal.stringVal}"/>
</apex:outputPanel>
<apex:outputPanel rendered="{!fieldVal.isBool == true}">
<apex:outputText value="{!fieldVal.boolVal}"/>
</apex:outputPanel>
<apex:outputPanel rendered="{!fieldVal.idVal != null}">
<a href="/{!fieldVal.idVal}"><apex:outputText value="{!fieldVal.idVal}"/></a>
</apex:outputPanel>
<apex:outputPanel rendered="{!fieldVal.intVal != null}">
<apex:outputText value="{0, number, 0}">
<apex:param value="{!fieldVal.intVal}" />
</apex:outputText>
</apex:outputPanel>
<apex:outputPanel rendered="{!fieldVal.dateVal != null}">
<apex:outputText value="{0,date,yyyy.MM.dd}">
<apex:param value="{!fieldVal.dateVal}" />
</apex:outputText>
</apex:outputPanel>
<apex:outputPanel rendered="{!fieldVal.dateTimeVal != null}">
<apex:outputText value="{0,date,yyyy.MM.dd G 'at' HH:mm:ss z}">
<apex:param value="{!fieldVal.dateTimeVal}" />
</apex:outputText>
</apex:outputPanel>
<apex:outputPanel rendered="{!fieldVal.doubleVal != null}">
<apex:outputText value="{0, number,0.00}">
<apex:param value="{!fieldVal.doubleVal}" />
</apex:outputText>
</apex:outputPanel>
<br/>
<br/>
</apex:repeat>
</apex:outputPanel>
</apex:pageBlock>
</apex:page>
Subscribe to:
Posts (Atom)