Handling JSON in Apex

Published on 25 April 2012 by

0

Apex provides 3 classes for processing JSON:

  1. System.JSON – main methods here are serialize() and deserialize()
  2. System.JSONParser – provides methods for JSON parsing
  3. System.JSONGenerator – provides methods for JSON generation

System.JSON

System.JSON class provides methods for serialization and deserialization.  For instance, the code snippet below serializes a list of Account objects into a JSON string:

1
2
List<Account> accounts = [SELECT id, name FROM Account LIMIT 2];
String accountsJSON = JSON.serializePretty(accounts); //serializePretty indents the content for better readability

Simply calling the serialize() method does the trick. The code above produces the following JSON output (depending on the data in your org the actual values may vary):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[ {
"attributes" : {
"type" : "Account",
"url" : "/services/data/v24.0/sobjects/Account/001d000000Ard7uAAB"
},
"Id" : "001d000000Ard7uAAB",
"Name" : "United Oil & Gas, Singapore"
}, {
"attributes" : {
"type" : "Account",
"url" : "/services/data/v24.0/sobjects/Account/001d000000Ard7vAAB"
},
"Id" : "001d000000Ard7vAAB",
"Name" : "Edge Communications"
} ]

Deserialization of the accountsJSON string can easily be done using JSON.deserialize method:

1
List<Account> accountsDeserialized = (List<Account>) JSON.deserialize(accountsJSON, List<Account>.class);

Notice, however, that deserialization wouldn’t fully work with nested queries:

1
2
3
4
5
6
List<Account> accounts = [SELECT id, name, (SELECT id, name FROM Contacts LIMIT 2) FROM Account LIMIT 2];
System.debug('Contacts' + accounts[0].contacts); //contacts are printed
String accountsJSON = JSON.serializePretty(accounts);
System.debug(accountsJSON);
List<Account> accountsDeserialized = (List<Account>) JSON.deserialize(accountsJSON, List<Account>.class);
System.debug('Contacts deserialized: '+ accountsDeserialized[0].contacts); //nothing gets printed

The last debug statement doesn’t output any contacts.

Apex objects are handled similarly to the sObjets by the System.JSON class. For instance, the GoogleCalendar class from the upcoming demo application:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class GoogleCalendar {
public String id;
public String kind;
public String etag;
public String summary;
public String description;
public String location;
public String timeZone;
public String summaryOverride;
public String colorId;
public Boolean hidden;
public Boolean selected;
public String accessRole;
public List defaultReminders;
...
}

is serialized and deserialized just like an sObject, except now you control the JSON structure through the Apex class:

1
2
3
4
 GoogleCalendar gCalendar = new GoogleCalendar();
//code to populate fields for the gCalendar is skipped
String gCalJSON = JSON.serialize(gCalendar);
GoogleCalendar gCalendarDeserialized = (GoogleCalendar) JSON.deserialize(gCalJSON, GoogleCalendar.class);

Using the System.JSON class methods significantly reduces the number of script statements required to process JSON content and eliminates the need to dynamically inspect the object schema to figure out which fields exist. It is an ideal choice for scenarios that involve working with structured data such as Apex objects.

System.JSONGenerator

System.JSONGenerator class contains methods used to serialize Apex objects, and sObjects for that matter, into JSON content. The key difference between System.JSONGenerator and System.JSON’s serialize() method is that the former provides a “manual” control over the serialization process.

Below is a simple example that creates a basic JSON content after querying for contacts:

1
2
3
4
5
6
List<Contact> contacts = [SELECT Id, Name FROM Contact LIMIT 10];
JSONGenerator generator = JSON.createGenerator(true); //instantiation of the generator
generator.writeStartObject(); // Writes the starting marker of a JSON object '{'
generator.writeNumberField('count', contacts.size()); //Writes the # of contacts
generator.writeEndObject(); //Writes the ending marker of a JSON object '}'
String jsonString = generator.getAsString();

The code above produces a simple JSON string analogous to the following:

1
2
3
{
"count" : 10
}

Now let’s take a look at a more sophisticated example: serialization of a GoogleCalendarEvent Apex object used in the demo application. The structure of the JSON Google Events Resource is the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{
"id": string,
"htmlLink": string,
"created": datetime,
"summary": string,
"description": string,
"location": string,
"start": {
"date": date,
"dateTime": datetime,
"timeZone": string
},
"end": {
"date": date,
"dateTime": datetime,
"timeZone": string
},
"sequence": integer,
"attendees": [
{
"email": string,
"displayName": string,
"organizer": boolean,
"self": boolean,
"resource": boolean,
"optional": boolean,
"responseStatus": string,
"comment": string,
"additionalGuests": integer
}
],
"reminders": {
"useDefault": boolean,
"overrides": [
{
"method": string,
"minutes": integer
}
]
}
}

JSON structure above is mapped to the GoogleCalendarEvent class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class GoogleCalendarEvent {
public String id;
public String htmlLink;
public DateTime created;
public String summary;
public String description;
public String location;
public Integer sequence;
public GoogleEventTime start;
public GoogleEventTime gEnd;
public List attendees;
public GoogleReminder reminders;
...
}

The calendar event JSON structure contains multiple data types, nested objects, and arrays – these are reflected in the GoogleCalendarEvent class and will require using various methods of the JSONGenerator class when creating the JSON content. In addition, some JSON structure’s properties, such as ‘end’, ‘date’, or ‘dateTime’ are reserved Apex keywords and hence had to be renamed, in this case into ‘gEnd’, ‘gDate’, and ‘gDateTime’ respectively for the GoogleCalendarEvent class.
For ease of understanding let’s walk through the serialization process in steps:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//instantiate the generator
JSONGenerator gen = JSON.createGenerator(true);
gen.writeStartObject();
//this corresponds to an instance of the GoogleCalendarEvent class
gen.writeStringField('summary', this.summary);
gen.writeStringField('location', this.location);
gen.writeFieldName('start');
gen.writeStartObject();
gen.writeObjectField('dateTime', this.start.gDatetime);
gen.writeEndObject();
gen.writeFieldName('end');
gen.writeStartObject();
//for demo pusposes writeDateTimeField() is used instead of writeObjectField()
gen.writeDateTimeField('dateTime', this.gEnd.gDatetime);
gen.writeEndObject();
//serialize reminders
gen.writeFieldName('reminders');
//writeObject() does the trick automatically since Apex object field names are the same as JSON field names
gen.writeObject(this.reminders);
...

The code above produces a JSON string analogous to the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
“summary”: “summary_value”,
“location”: “location_value”,
"start": {
"dateTime": "2012-02-15T18:03:32-08:00"
},
"end": {
"dateTime": "2012-02-15T19:03:32-08:00"
},
"reminders": {
"useDefault": false,
"overrides": [
{
"method": "email",
"minutes": 1
},
{
"method": "email",
"minutes": 2
}
]
}
...

writeStringField() writes a text value while writeDateTimeField writes a dateTime.
Notice how writeObject() method serializes the whole reminders object including the overrides array, which is pretty handy. Let’s continue with the serialization of the array of event attendees:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...
gen.writeFieldName('attendees');
gen.writeStartArray();
//for each attendee create a JSON object
for(GoogleEventAttendee gEventAttendee: this.attendees){
gen.writeStartObject();
gen.writeStringField('email', gEventAttendee.email);
gen.writeBooleanField('optional', gEventAttendee.optional);
gen.writeNumberField('additionalGuests', gEventAttendee.additionalGuests);
gen.writeEndObject();
}
gen.writeEndArray();
//end of the parent JSON object
gen.writeEndObject();
String jsonString = gen.getAsString();

For demo purposes, instead of using the writeObject() method we “manually” constructed the attendees array which resulted in the following JSON structure:

1
2
3
4
5
6
7
8
9
10
11
12
13
...
"attendees": [
{
"email": "testemail-1@test.com",
"optional": true,
"responseStatus": "needsAction"
},
{
"email": "testemail-0@test.com",
"optional": true,
"responseStatus": "needsAction"
}
]

System.JSONParser

In contrast to System.JSONGenerator, System.JSONParser does the opposite – it provides methods for parsing JSON content. Generally, JSONParser is useful for grabbing specific pieces of data without the need of a structure such as an Apex class.
For example, the code snippet below, taken from the authentication related code of the demo app, grabs access token and the expires_in parameter from the JSON response received from the authentication service:

1
2
3
4
5
6
7
8
9
10
11
12
13
//resp is a JSON string
JSONParser parser = JSON.createParser(resp);
while (parser.nextToken() != null) {
if ((parser.getCurrentToken() == JSONToken.FIELD_NAME)){
String fieldName = parser.getText();
parser.nextToken();
if(fieldName == 'access_token') {
accesstoken = parser.getText();
} else if(fieldName == 'expires_in'){
expiresIn = parser.getIntegerValue();
}
}
}

Depending on the JSON content you need to parse, it may make sense to break the while loop after all the necessary information has been obtained to prevent the unnecessary processing.
The System.JSONToken is an enumerator that provides values such as FIELD_NAME, START_OBJECT, END_OBJECT and others that inform you of the type of token currently being parsed.
Let’s now see how GoogleCalendarEvent object is constructed based on the parsed JSON string. For better readability let’s break down parsing code into several pieces:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//this – is an instance of GoogleCalendarEvent class
//instantiate the parser
JSONParser parser = JSON.createParser(jsonString);
while (parser.nextToken() != null) {
//if current token is a field name
if (parser.getCurrentToken() == JSONToken.FIELD_NAME){
String fieldName = parser.getText();
System.debug('fieldName: ' + fieldName);
//move to token after the field name
parser.nextToken();
if(fieldName == 'id'){
this.id = parser.getText();
}
else if(fieldName == 'htmlLink'){
this.htmlLink = parser.getText();
}
...

The code above executes a while loop that walks through the whole JSON string using the parser.nextToken() method and parses simple field text values such as ‘id’ or ‘htmlLink’ using the parser.getText() method.
Let’s see how the ‘start’ object is parsed (code below is inside the while loop from the previous snippet):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...
else if(fieldName == 'start'){ //start is a GoogleEventTime object
if(parser.getCurrentToken() == JSONToken.START_OBJECT){
while(parser.nextToken() != null){
if(parser.getCurrentToken() == JSONToken.FIELD_NAME){
if(parser.getText() == 'dateTime'){
parser.nextToken();
this.start.gDateTime = parser.getDateTimeValue();
break;
}
}
}
}
}
...

We use JSONToken.START_OBJECT enum to determine the beginning of the ‘start’ object, and then using the inner while loop iterate through its content to grab the this.start.gDateTime value using the parser.getDateTimeValue() method. Notice the break statement to stop the inner loop, without it the inner loop would continue processing the remaining JSON, which is the responsibility of the outer while loop.
Next, the ‘attendees’ array is parsed using the code below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
else if(fieldName == 'attendees'){
if(parser.getCurrentToken() == JSONToken.START_ARRAY){
while(parser.nextToken() != null){
if(parser.getCurrentToken() == JSONToken.START_OBJECT){
//read GoogleEventAttendee object
GoogleEventAttendee gEventAttendee = (GoogleEventAttendee) parser.readValueAs(GoogleEventAttendee.class);
this.attendees.add(gEventAttendee);
}
else if(parser.getCurrentToken() == JSONToken.END_ARRAY){
break;
}
}
}
}
...

After detecting the attendees array we use readValueAs() method to deserialize JSON structure into the GoogleEventAttendee object automatically, in other words without parsing the fields separately.

Demo App Walk-through

Prerequisites

  • Download the unmanaged package into your developer org
  • Go through the Google API configuration steps
  • Authenticate with the API from using Google Apps custom object record

Step 1: Log In to a Developer Edition Org

Log in to a Developer Edition org. If you don’t have one, create a new one.

Step 2: Download the package to your Developer org

Step 3: Create a Google APIs Project

In the browser, navigate to the Google APIs Console, log in using a Google/Gmail account that you use to manage your calendar, and then create a project.

On the Services tab, turn on the Calendar API and accept Google’s terms of service.

Step 4: Enable the OAuth 2.0 Authentication for Your Google Calendar

On the API Access tab in the Google APIs Console, click Create an OAuth 2.0 client ID ….

Enter a Product Name such as GCalendar, then click Next.

On the next screen, click Web Application and enter c.your_salesforce_instance.visual.force.com/apex/GoogleLogin into the Your site or hostname field. Make sure you enter the correct Salesforce.com instance name, which can be obtained from the address bar of your browser:

As soon as you move the cursor focus out of the site or hostname field, the page updates so that the Redirect URI at the bottom of the dialog turns into https://c.your_salesforce_instance.visual.force.com/apex/GoogleLogin.

Finally, click Create client ID to generate the Client ID and Client Secret.

Step 5: Configure Authentication

Log in or return to your Developer Edition org and launch the JSON App from the Force.com App Menu at the top right corner.

Click the Google Apps tab, click New to create a new record, then populate the following fields:

  1. GoogleApp Name: enter GoogleApp
  2. Client ID: copy/paste the Client ID from the Google APIs Console
  3. Client Secret: copy/paste the Client Secret from the Google APIs Console
  4. Scope: enter https://www.googleapis.com/auth/calendar, which provides read-write access to Calendars, Calendar Events, and Calendar ACLs

Click Save.

 

Now that you have all the necessary information on the Google App record, you can authenticate against the Google APIs. First, disable Development Mode for your account, if necessary.

  1. Click Setup -> My Personal Information -> Personal Information -> Edit.
  2. Disable Development Mode, if necessary.
  3. Click Save.

To authenticate:

  1. Load the JSON App.
  2. Click Google Apps -> Google App -> Authenticate.

Google displays a page asking if you are OK with providing the required permissions to the application. Click Allow access to grant the access.

If everything went through fine, your GoogleApp record now has populated Access TokenExpires In, and Code fields. The access token typically is good for one hour.

Creating Calendars and Events

Assuming that the authentication went fine, now try creating a new Google calendar. Click the Create Calendar tab, enter some values for the calendar, and click Create Calendar.

When you press the Create Calendar button, a few actions take place:

  1. The information you entered gets assigned to a GoogleCalendar object.
  2. The object gets serialized into a JSON string using System.JSON.
  3. The JSON string gets passed to the Google Calendar API via an HTTP callout.
    • The HTTP request header’s Content-Type property is set to application/json.
  4. As a result, Google creates a new calendar and sends back information such as id and etag as a JSON string.
  5. The app deserializes the JSON string into another GoogleCalendarObject and displays it in the Calendar Output section.

To verify the new calendar, navigate to your Gmail calendar. If everything went as expected, you should see a new calendar entry similar to the following:

Now, create a new event for the calendar you just created. To do so, click the Create Calendar Event tab, select the newly created calendar’s name from the drop-down, enter sample values and click Create Calendar Event:

A serialized (using JSONGeneratorGoogleCalendarEvent object gets passed to the API. Eventually, the Calendar Event Output section displays the deserialized (using JSONParserGoogleCalendarEvent object obtained from the API call response. To verify if the calendar event got created, navigate to the Gmail calendar page.

Summary

The Apex JSON classes provide easy-to-use means of serializing/deserializing Apex objects into/from JSON content. Native JSON implementation empowers you with ability to build robust applications that integrate with third-party systems, various JavaScript libraries, or use HTML5.

Current Force.com API version: 24

Continue Reading

Patch Org for Managed Package

Published on 19 January 2012 by

0
Patch Org for Managed Package

Patch Org Upgrade

In order to create a patch org, the user will have to click on the new button as shown below:

Patch1

Patch Org view

On Click of New, following screen is displayed:

Patch2

Create Patch Org View

Select the Version number, enter appropriate username for the patch org and make sure that the email address is correct for patch dev org.

Patch3

Selected Patch Org created

  1. Once the patch is created by retrieving all the latest code, the user will have to log into the patch org to make any code changes.
  2. Once the changes are made within the patch org, the user can upload the package as he/she would do in the managed dev org.
Patch4

Push Upgrade and Version view

The Version tab shows the version that was uploaded in the patch org, the admin can then click on the Push Upgrade button.

Patch5

Push Upgrade Screen

On push upgrade screen, click on the Schedule Push Upgrade button:

Patch6

Schedule Push Upgrade Screen

Within the Schedule Push Upgrade Screen, the admin can select the Patch Version, if there are any orgs that does not have the version of code uploaded it will show up in the Select Target Organizations grid.

The admin is able to schedule those selected organization and apply the patch version 1.2.2.

Patch7

Upload result screen

Upload results will be displayed on the Push Upgrade Screen, the targets have a hyper link that shows more details about the scheduled tasks.

 

 

 

 

 

 

 

Continue Reading

0

Recently I came across a client who wanted to provision the Salesforce user on their system, the criteria was to allow the target system to push data from their end without the use of Salesforce Session Id. This problem was unique such that the target system does not have any username password stored in their system so the problem requires some thought. I decided that I will create a POC that will allow the target system to be able to talk to Salesforce. One point to note here is that the target system has to be from a trusted organization and should require user’s approval.

The first thing that people would discuss is to make use of SSO, this my colleague Vilas have blogged earlier. I on another hand have taken another approach that to make use of Oauth 2.0 on Force.com it involves making use of Remote Access functionality. I will go through the exact steps to establish the initial handshake and thereby have target application push data to Salesforce. The target application will be written in C#.

The first step here is to set up the Remote Access, I have covered the Consumer Key but an entry here will provide a Consumer Key and Consumer Secret.

Step 1

Navigate to
Setup -> App Setup -> Remote Access

Remote Access Setup

Step 2
I then went ahead and created a Visual Force page called Admin Screen:

Here I have created two different buttons, one that will open a separate window using javascript and another that will open a tab and make use of the PageReference, both performing similar functionality.

Visual Force Page for the Admin Screen

Step 3

Admin Screen Controller

The code reference above provides two links, one that calls VisitConcur, on Click of that button the url will include isNewUser=true and sessionId is passed to the target website.

I have inserted a breakpoint where the session id is captured, and if it is a newUser, the GetRefreshToken(); function is called, this will inadvertently ask the user to insert the Salesforce username/password:

Step 4

Target Web site

Target Website (written in C#)

Step 5

Insertion of Salesforce Username/Password

On insertion of Salesforce username/password, the user will be prompted to the user to all the target application to be trusted:

Step 6

User prompted to trust the target website

Once the user clicks on Allow will send the control back to the Target website and this time the call back script that I have created will have the access and refresh token that will allow the target application to push data to Salesforce:

Step 8

Access / Refresh Token

Access / Refresh Token in Debug Mode

Target Website ASPX page

Call back script in code behind

Onload of the page, the javascript function CallServer is called passing in the url that contains the access and refresh token and its context (i.e. alert in this case).
Step 9

Now that we have the access and refresh token retrieved, we can make use of the access token to make API calls to Salesforce and retrieve or push data to Salesforce under the user’s credentials:

The URI here can be concatenated with token.instance_url + “/services/data/v20.0/query” and then we create a soql query to retrieve opportunities or any other object within Salesforce.

Post or Get information from Salesforce

Step 10

Refresh Token to retrieve access token

Finally there is one more point to note here, if the access token expires we can make use of the old refresh token to get a new access token without the user having to enter their username and password again. Instead of passing the access code we pass the refresh token, and pass the grant type as refreshToken instead of authorization_code. The function will return the new access token that we can make use of to communicate with Salesforce.

For more information visit:

http://wiki.developerforce.com/index.php/Digging_Deeper_into_OAuth_2.0_on_Force.com

Continue Reading

1
Integration of Salesforce.com lookup field functionality with ExtJS

Blog entry posted for: Ivan Melnikov

..

Overview

ExtJS javascript framework is becoming more and more popular among web site developers because of its simplicity and support of the most popular browsers in industry. Salesforce.com platform is not an exception. The newer 4th version of ExtJS can be utilized to enrich standard VisualForce functionality with flexible UI components and make data representation a lot more customizable.

However there are some custom tricks and hacks that have to be used to integrate standard Salesforce.com with ExtJS framework. In this article I’ll try to briefly explain how to integrate lookup fields with Ext buttons and use standard Salesforce popup window for searching records and adding them to the UI components on the page.

[...]

Continue Reading

Dreamforce 2011 : A spectacular event!!!

Published on 03 September 2011 by

0

Dreamforce 2011 was a phenomenal event for Comity Designs, partners and clients! The event is always jam packed with conversations, building relationships, networking, and not to forget – having a little bit of fun in the mix of it all (as can be seen in the picture above).

I also want to congratulate Comity Team members that once again this year have WON an award at the Dreamforce 2011 Hackathon!!!

**CHEERS**

And for all you visitors that are reading our blog, please check out the links below to read all about our announced victory.

Hackathon Entry: “Enterprise Mood Monitor”

Link 1 - Dreamforce ’11 Hackathon Winners Announced (Developer Force Blog)

Link 2 - Social Media “Mood” App Wins Big at Dreamforce ’11 Hackathon (Developer Force Blog)

Link 3 - Dreamforce Hackathon Winner: Enterprise Mood Monitor (GNIP Blog)

We will be sharing additional information about the hackathon and adding more pictures to our blog , so stay tuned and visit us again soon.

*****************************

Pictures:

*****************************

Comity winning at the Dreamforce 2011 Hackathon

Participants: Shamil Arsunukayev, Ivan Melnikov, Gaziz Tazhenov

Continue Reading

0

Overview

Salesforce.com provides Translation Workbench, a feature that can be turned on only by calling in Customer Support or logging a Case through your Org. When the translation workbench feature is available, you have to login to your org and enable the feature, add new languages that you want to support and then provide translations for the areas of your application that you control.

Steps to enable Translation Workbench

  1. Call Salesforce.com support or enter a case to activate a feature [this feature is non-reversible after it is turned on].
  2. When the customer support turns it on, login to your org and Enable the feature by going to Setup -> Translation Workbench
  3. Add languages that you want to support for you application
  4. Provide translation
  5. Update your code to use the translations if you haven’t already done designed for globalization

At what level the translations can be turned on?

  1. Org level by going into Setup -> Company Information -> Edit [Default Language]
  2. User level by going into Setup -> Manage Users -> Edit User [Language]

However, keep in mind changing the Default Language at org level doesn’t change every user’s default language. In order for user to start seeing the content in different language, either of two things need to happen:

  • Admin has to set the language at user level
  • User should use a browser with the locale for the language they want to see in Salesforce.com

What gets translated?

Salesforce.com documentation mentions three main areas that get translated or need to be addressed either by Salesforce.com or by yourself in your own customizations [code or admin changes]

  1. Main salesforce.com application content [this is your standard look and feel of salesforce.com, standard objects and their labels]
  2. Online help provided by Salesforce.com
  3. Your own customizations – things like labels for your custom objects/fields properties, Visualforce page content that you control, picklist values you control.

Very Simple Example to illustrate the implementation

Let’s make sure we have performed following steps before we start:

  1. The feature has been activated by Salesforce.com customer support
  2. You have logged in to the Salesforce.com org and Enabled the feature [even though customer service activates the feature you still have to turn it on by going to Setup -> Translation Workbench
  3. Add Spanish language as the new language we are going to support in our Salesforce.com Org and make sure you have checked the Active check box.
  4. Create a new Custom label named Field1Label to use in our Visualforce page. Set the value for it as “Value”. Also, add a spanish language translation for this custom label and set its value to to be “Valor”

At this point we are ready to get started on creating a very simple Visualforce page that will allow us to support two languages English and Spanish.

Here is our Sample page:

Save the following in a new Visualforce page named TestCustomLabelPage

<apex:page controller="TestTranslationController">

<apex:form >

<apex:pageBlock >

<apex:pageBlockSection >

<apex:pageBlockSectionItem >

<apex:outputLabel value="{!$Label.Field1Label}"></apex:outputLabel>

<apex:inputText />

</apex:pageBlockSectionItem>

</apex:pageBlockSection>

</apex:pageBlock>

</apex:form>

</apex:page>

Assuming that your Salesforce.com user has the default language set to be English, let’s see how the Visualforce page will look like this at present.

Let’s go to Edit the user and set the Language to be Espanol.

Go back and refresh the Visualforce page and we should see following page [see the label is changed to Valor [Spanish for Value as we have entered in the translation for the custom label].

What is good about the translation workbench?

  • Fairly straight forward implementation as long as you are supporting the language out of the languages that Salesforce.com supports
  • Ability to add new languages over time and leverage the same code infrastructure you have setup when you supported two languages
  • Earlier it was pretty tedious to enter all the values manually, but recently Salesforce.com has added support for translation import/export from both Apex Data loader as well as Force.com Eclipse IDE

What is not so good about it?

  • Salesforce.com supports fairly good number of languages out of the box but the number is still pretty small so if you are supporting a language that is not part of their list you are out of luck and the only option you have at that point is to build your own code infrastructure in apex to support it
  • Limit on maximum number of custom labels. This could be critical given that salesforce.com supports maximum 5000 for the whole org. I am interpreting this as all the code within the Salesforce.com org and the all the AppExchange packages installed in that org, all of them have to share that pool of 5000 custom labels. In my opinion, this is pretty sparse. In a fairly complex AppExchange application you will come pretty close to using at least a few thousand custom labels and if you have an Org where you have 4-5 of those AppExchange apps installed then you are going to run into this problem. Having said that, there is a possibility that Salesforce.com may not count the custom labels included in the AppExchange apps with Aloha status but not sure about this.

Further Reading

https://na9.salesforce.com/help/doc/en/customize_wbench.htm

https://na9.salesforce.com/help/doc/en/salesforce_ide_localization.pdf

https://na9.salesforce.com/help/doc/en/entering_translated_terms_in_packages.htm

https://na9.salesforce.com/help/doc/en/salesforce_workbench_cheatsheet.pdf

Continue Reading

0

Blog entry posted for: Rahul - Comity Intern (Summer’2011)

..

One of the limitations of the Salesforce.com platform is that it does not allow developers to inject their own logic into an Approval Process, especially when using standard VisualForce tags. With standard related lists (not custom components) columns cannot be customized to display custom information.

One common way of working around this limitation is to replicate the relatedList as a custom component where you duplicate the look and feel utilizing the pageBlockTable VF tag, thereby having the ability to add any custom logic. This approach is more rewarding but far more time consuming and not always necessary.

Another quicker mechanism to bypass this limitation is to use a combination of CSS and HTML tags and attributes on a page in addition to APEX code that replicates the needed standard functionality.

As an example, let us take a look at a use case that requires us to perform a check and display an error message on a VF page upon clicking the Approve/Reject Link from the ProcessSteps relatedList i.e. the Approval History.

One way to approach this, as highlighted in this post is to use CSS to hide the actionColumn on the standard relatedList, then provide commandButtons or commandLinks above or below the relatedList allowing the user to run custom pre-defined APEX logic.

In the event that the business does not require a heavily customized solution, modifying a standard related list can be the faster and more reasonable approach. However, its functionality is also limited, in the sense that using this method only allows the hiding of pre-existing columns that would otherwise be shown in the relatedList implementation. The addition of new columns is not possible and in this case one must revert to the complete custom solution.

For hiding the Action column:

Insert the following code on the Custom  VF Page in the correct place as specified by the tags:

<style>

/* === Hides Action Column for Approval History === */


[class$='actionColumn']

{display:none}


/* === END === */


</style>

NOTE: This will cause all columns of the name actionColumn to be hidden on the page, so only use this implementation if you are sure that nothing else has the same name or else it will disappear as well.

A better way would be to find the columnID because this is guaranteed to be unique at least within the page, but this is not always possible, as it was in this case.

So you could use this method to hide a column from a custom element in a similar manner.


To add commandButtons that do the same thing:

Insert the following code in the correct place on the Custom VF Page, either above or below the code that either looks like or contains the following: <apex:relatedList list=“ProcessSteps”></apex:relatedList>

<!– Buttons that mimic the functionality of the Action Column in the Approval History Related List –>


<apex:form>

<apex:pageBlock id=“appRej”>

<apex:pageBlockButtons location=“top” id=“appHistButtons”>

<apex:actionRegion >

<apex:commandLink value=“Approve/Reject” id=“appRejectBtn” styleClass=“btn” style=”padding:2px 5px 3px 5px; text-decoration:none;” action=“{!ApprRej}” />

</apex:actionRegion>

<apex:actionRegion >

<apex:commandLink value=“Reassign” id=“ReassignBtn” styleClass=“btn” style=”padding:2px 5px 3px 5px; text-decoration:none;” action=“{!Reassign}” />

</apex:actionRegion>

</apex:pageBlockButtons>

</apex:pageBlock>

</apex:form>


<!– END –>

In your controller extension, create the functions that these buttons are associated to as shown below (assuming that all variables needed, functions referenced etc. have been already predefined and setup elsewhere):

// Function called when Approve/Reject Button is clicked


public PageReference ApprRej()

{

// runs the check

approvePossible();


// if the check fails then it changes the flag to display error message and re-renders the current Page

if(canApprove == false)

{

canApprove2 = false;

PageReference pageRef = ApexPages.currentPage();

pageRef.setRedirect(true);

return null;

}


// if check is successful then it runs a SOQL query to get the Approval ID and uses that to redirect to the relevant Approve/Reject page

else

{

List<ProcessInstanceWorkItem> workItemList = [Select p.ProcessInstance.Status, p.ProcessInstance.TargetObjectId,p.ProcessInstanceId,p.OriginalActorId,p.Id,p.ActorId From ProcessInstanceWorkitem p where p.ProcessInstance.TargetObjectId = :opp.Id];


String strAppId = workItemList[0].Id;

String partialURL = ‘/p/process/ProcessInstanceWorkitemWizardStageManager?id=’ + strAppId;

canApprove2 = true;

PageReference pageRef = new PageReference(partialURL);

pageRef.setRedirect(true);

return pageRef;

}

}


// Function called when Reassign Button is clicked


public PageReference Reassign()

{

// runs a SOQL query to get the Approval ID and uses that to redirect to the relevant Reassign page

List<ProcessInstanceWorkItem> workItemList = [Select p.ProcessInstance.Status, p.ProcessInstance.TargetObjectId,p.ProcessInstanceId,p.OriginalActorId,p.Id,p.ActorId
From ProcessInstanceWorkitem p where p.ProcessInstance.TargetObjectId = :opp.Id];


String strAppId = workItemList[0].Id;

String partialURL = ‘/’ + strAppId + ‘/e?et=REASSIGN&retURL=apex/GMAView?id=’ + gma.Id + ‘&sfdc.override=1′;

PageReference pageRef = new PageReference(partialURL);

return pageRef;

}

In this case, it is presumed that the page has an element that is rendered based on some flag canApprove2

<apex: outputPanel style=”color:red;font-size:150%rendered=“{!NOT(canApprove2)}”>

<br/>Sample error message is displayed here<br/>

</apex:outputPanel>


NOTE: A better way of constructing the partial URLs is to avoid any hard-coding and instead use the setParameter and getParameter methods.

In case you do need to hard-code for any reason, as I have done in the above example, then you would need to click on the actionColumn commandLink that takes you to the standard page, and then copy and cleanup the URL by identifying and segregating the record ID, and also removing anywildcard or dummy Unicode/UTF characters.

For example, a URL that looks like this:

https://cs4.salesforce.com/04iP01234564ks0IAA/e?et=REASSIGN&retURL=apex%2FGMAView%3Fid%3D006P6543213MiuTIAS&sfdc.override=1

It has characters such as %2F which should be replaced by / or %3F which should be changed to the = sign etc accordingly, as is necessary. This applies to ANY and ALL salesforce.com URLs.

Also, partialURLs should always be used preferably over fullURLs to facilitate migration of code between orgs.


The end result is that something that originally looked like this:

Can now be seen showing up as this:

Observe that the left-hand most column missing, and also notice how the 2 new buttons have appeared at the bottom.

Continue Reading

0

I cannot stress enough the importance of using ‘Informational Headers’ on top of any code that you may write, regardless of what language it is or what platform that you maybe developing on.

Informational Headers should provide the reader (author or another developer) with at least the following :

  • Artifact name
  • Associated artifacts
  • Spawned artifacts
  • Purpose (summarized description) and context
  • Change log

This basic set of information allows yourself or anyone else a quick background of what you are going to view, review, edit, or deploy, including any information about dependancies if you choose to include this additional detail.

Well structured and commented code goes a long way, and speeds up development. Poorly structured and non-commented code makes it hard for the person that did not write it to understand and potentially delay development or even create further problems by modifying the wrong data, etc.

In addition a properly maintained change log allows one to track changes or updates that were made within the header, providing a quick facility to then reference the right individual for help in the event of code failure or malperformance.

I can list another 101 reasons in this entry, and trust me when I say that I’ve seen a lot of dirty code – it makes it very hard to move forward at a good and fast pace. SO, I urge all Technical Architects, developers (Sr. and Jr.), and to be developers to make it a practice to add ‘Informational Headers’ to your code artifacts, it will not only save you time but also for others that inherit your code.

Continue Reading

0

Sometimes while setting up reports you wish to quickly change the advanced filter criteria (boolean condition) to make sure you are pulling the right data without having to click on ‘Customize’ every single time.

Well, you can!

All you have to do is pass an additional URL parameter to your report with the new criteria for testing:

  • bool_filter=1+AND+2+AND+(+3+OR+4+)
  • Example: https://cs1.salesforce.com/00OM0000000FAUH?bool_filter=1+AND+2+AND+(+3+OR+4+)

So lets say that your original advanced filter criteria was: 1 AND 2 AND 3 AND 4, by passing the above URL parameter your report should now show 1 AND 2 AND ( 3 OR 4 ) as part of the advanced filter criteria. Additionally, your report data should reflect accordingly.

..

Disclaimer:

Any of these features could be changed by Salesforce.com in a future release.

Continue Reading

6

Console views or layouts are extremely helpful in Salesforce.com if your business model asks for them. As you know, while using consoles the SFDC header and side bar are both hidden for all the pages that you choose to display within the consoles, the font size is smaller, and the page help link (usually found on the top right of a standard page) are iconified.

This effect can be mimicked on standard pages by using the url parameter: isdtp=mn or lt .

  • isdtp=mn will retain the old SFDC styling and retain the page header (not the SFDC header with all the tabs)
  • isdtp=lt will retain the old SFDC styling and will remove the page header as well

You can use this trick on standard page layouts as such:

  • For example: https://cs1.salesforce.com/001C000000oaDWl?isdtp=mn

or on other native salesforce pages, such as reports. This trick allows you to you embed standard pages without the header and side bar within VF pages successfully.

Of course you do need to keep in mind that there are limitations to what all you can do, but nonetheless ‘isdtp’ is a very useful URL parameter to know of.

..

Disclaimer:

The new SFDC styling/UI Theme has not been made available to this URL parameter, and may not work on some of the native SFDC pages.

Continue Reading