Appirio's Tech Blog

Wednesday, January 20, 2010

Google Web Toolkit UiBinder Tutorial

Jeff Douglas

I’ve been working on a new project the past couple of weeks that (fortunately) requires Google Web Toolkit (GWT) and I wanted to use the new UiBinder that was released with GWT 2.0 in early December for a number of reasons (clean separation of UI and code, easier collaboration with designers, easier testing, etc ). However, I was having a hard time getting my head wrapped around it given that the GWT site has very little documentation and only a few examples. I’ve combed through the message boards, the docs and the sample Mail application that comes with the SDK and after finally groking the new functionality, I put together a little Hello World app, the kind that would have helped me out originally.

So I’m making some assumptions that you already have the GWT SDK and Eclipse Plugin installed and are familiar with both of them. If you are not, take a look at the GWT site for more info.

To get started, create a new Web Application Project called “HelloUiBinder” in the package of your choice but do not check “Use Google App Engine”.



Now create a new UiBinder template and owner class (File -> New -> UiBinder). Choose the client package for the project and then name it MyBinderWidget. Leave all of the other defaults. When you click Finish the plugin will create a new UiBinder template and owner class.



Open the MyBinderWidget.ui.xml template and add the following code. With GWT you can define your styles either in your template where you need them or externally. I’ve added a small style inline that adds some pizzaz to the label. Notice the field name myPanelContent in the template. You can programmatically read and write to this field from the template’s owner class. So when the owner class runs, it construct a new VerticalPanel, does something with it (probably add some type of content) and then fill this field with it.

Attributes for the elements (the text attribute in the Label element for example) correspond to a setter method for the widget. Unfortunately there is no code completion to get a list of these attributes in Eclipse when you hit the space bar so you either have to know the setters or refer to the JavaDocs each time. A painful process.





.bolder { font-weight:bold; }








For the owner class, MyBinderWidget.java, add the following code. In this class, a field with the same name, myPanelContent, is marked with the @UiField annotation. When uiBinder.createAndBindUi(this) is run, the content is created for the VerticalPanel and the template field is filled with the new instance.


package com.jeffdouglas.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

public class MyBinderWidget extends Composite {

private static MyBinderWidgetUiBinder uiBinder = GWT
.create(MyBinderWidgetUiBinder.class);

interface MyBinderWidgetUiBinder extends UiBinder { }

@UiField VerticalPanel myPanelContent;

public MyBinderWidget() {
initWidget(uiBinder.createAndBindUi(this));

HTML html1 = new HTML();
html1.setHTML("Click me!");
myPanelContent.add(html1);
HTML html2 = new HTML();
html2.setHTML("This is my sample content!");
myPanelContent.add(html2);

}

}


Now change the entry point class to look like the following.


package com.jeffdouglas.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.RootPanel;

public class HelloUiBinder implements EntryPoint {

public void onModuleLoad() {
MyBinderWidget w = new MyBinderWidget();
RootPanel.get().add(w);
}
}


Now open HelloUiBinder.html and remove all of the HTML content between the </noscript> and </body> and save it. Once you run the application, copy the development URL and run paste it into your favorite supported browser, you should see the following.



Now suppose you wanted to nest a widget inside your MyBinderWidget that did something when a button was clicked. We’ll create a small series of checkboxes that allows the user to select their favorite colors and display them when the button is clicked. Create a new UiBinder called FavoriteColorWidget in the client package. Add the following code to the FavoriteColorWidget.ui.xml template.





Choose your favorite color(s):
Red
White
Blue
Submit




Now add the click handler in the FavoriteColorWidget.java owner class.


package com.jeffdouglas.client;

import java.util.ArrayList;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;

public class FavoriteColorWidget extends Composite {

private static FavoriteColorWidgetUiBinder uiBinder = GWT
.create(FavoriteColorWidgetUiBinder.class);

interface FavoriteColorWidgetUiBinder extends
UiBinder {
}

@UiField Label greeting;
@UiField CheckBox red;
@UiField CheckBox white;
@UiField CheckBox blue;
@UiField Button button;

public FavoriteColorWidget() {
initWidget(uiBinder.createAndBindUi(this));

// add a greeting
greeting.setText("Hello Jeff!!");

final ArrayList checkboxes = new ArrayList();
checkboxes.add(red);
checkboxes.add(white);
checkboxes.add(blue);

// add a button handler to show the color when clicked
button.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
String t = "";
for(CheckBox box : checkboxes) {
// if the box was checked
if (box.getValue()) {
t += box.getFormValue() + ", ";
}
}
Window.alert("Your favorite color/colors are: "+ t);
}
});

}

}


The last thing we’ll need to do is add our new widget to the MyBinderWidget template. Open MyBinderWidget.ui.xml and add the custom namespace reference and the FavoriteColorWidget.


;


.bolder { font-weight:bold; }









Now when you run the application it should look like the following.

Tuesday, January 12, 2010

Upcoming Salesforce.com Spring ‘10 Features

Jeff Douglas

For those that don’t have time to weed through all 171 pages of the Spring ‘10 Release Notes, I’ve pulled out a few of my favorites for your viewing pleasure. I didn’t hit all of the items in the release notes so make sure you pull up the the PDF and check out the goodies in detail.

Entitlement Management – Service Cloud
By far the most detailed new feature at 45 pages! You can now set up entitlement management so that your support reps can verify if your customers are eligible for support, create and maintain service contracts, specify service levels on a per customer basis and enforcement of service levels with time-dependent, automated processes. There is a TON of stuff around Entitlement Management including service contracts and milestones so definitely take a closer look!

Quotes – Sales Cloud
A quote is a record showing proposed prices for products and services created from an opportunity and its products. You can create a set of quotes to show different combinations of products, discounts, and quantities so customers can compare prices. Each opportunity can have multiple associated quotes, and any one of them can be synced with the opportunity. You can also create and email PDF quotes.

Answers – Service Cloud
Answers is the newest feature of the Community application that lets community members ask questions, post replies, and vote whether they like or dislike a reply. It looks somewhat similar to Ideas in such that you assign categories to communities and then enable Answers for the customer and partner portals.

Knowledge Enhancements – Service Cloud
A ton on Knowledge enhancements including new API objects for articles, metadata components for data category groups, describe calls for data categories, SOQL and SOSL filter, field level security for articles, custom report types and access to articles in the partner portal.

SOQL Enhancements

New GROUP BY Clause
SELECT LeadSource, COUNT(Name) FROM Lead GROUP BY LeadSource

New HAVING Clause
SELECT Name, Count(Id) FROM Account GROUP BY Name HAVING Count(Id) > 1

Aggregate Functions (Finally!!!!)
SOQL now supports AVG(), COUNT(), COUNT_DISTINCT(), MIN(), MAX(), and SUM()

Date Functions
A slew of new date functions for SOQL to make your life easier.

Semi-Join and Anti-Join Support For Reference Fields
I ran into the issue all the time when writing queries. Now your subqueries can filter by ID (primary key) or reference (foreign key).

Force.com Apex Code Enhancements
  • The Apex scheduler is now generally available.
  • Callouts Enhancements including increased size limit, output messages with multiple elements (hurrah!!), new baked-in DOM class for parsing and generating XML content and enhanced two-way SSL authentication
  • Limits on the number of items a collection can hold have been removed. However, there’s still a general limit on heap size. What!!??
  • sObjects are now created as Objects and you can create generic sObject collections: Set foo = new Set();
  • I’m don’t this this is what Taggert was talking about during his DF09 presentation, but there are a lot of new logging features to check out on line 121. Check it out! Should make life easier until what Taggert revealed in on the roadmap for debugging is GA.
Visualforce Enhancements
  • enhancedList component now uses Ext 3.0
  • Salesforce.com Stylesheets No Longer Called for PDFs
Force.com Web Services API Enhancements
  • New SOSL Clause for Filtering By Data Categories
  • New Support for API Version in Outbound Messages
  • Numerous new Objects to support Entitlement Management, Answers and Quotes
  • There are also some new and changed API calls
  • Support for Sites objects (Site and SiteHistory)
Force.com IDE
The Force.com IDE Spring ‘10 release is scheduled for February 15th. Not sure of the new features but from what I have heard in the past you should (safe harbor) be able to copy from from SOQL search results.

Complex Data Modeling with Multilevel Master-Detail Relationships
You can now include multiple levels in master-detail relationships. For example, in addition to defining a two-object master-detail relationship, such as Account—Expense Report, you can extend the relationship to subdetail records, such as Account—Expense Report—Expense Line Item. You can then perform operations across the master—detail—subdetail relationship. There are some caveats so check out the details.

Sandbox to Production—Change Sets Beta
Use change sets to move configuration changes via a web interface. New enhancements include clone an existing change set, cross-version upload, delete change sets, and a six month expiration of change sets.

New UI Theme
The new UI theme that was unveiled at DF09 is an org-wide update. The new UI supports IE 7 & 8, Firefox 3.0.x and Safari 3.2.x. Sorry Chrome!

Dashboard Enhancements
  • With custom dashboard tables you can create tables with up to four columns, with column totals for number and currency summary fields.
  • With dashboard finder, you can quickly find a dashboard by typing its name in the search filter. All accessible dashboards matching that text are dynamically displayed in the drop-down list.
  • New visibility option for report and dashboard folders to deny access to a folder for portal users (“This folder is accessible by all users, except for portal users”).
Secure Communication with External Websites
Encrypt sensitive date with certificates and key pairs whenever Salesforce.com communicates with a supported external website. Salesforce.com offers either CA-signed or self-signed certificates.

CTI 2.0 Toolkit
A new toolkit for developers and partners to build more robust CTI adapters for call center users.

Globalization Enhancements
  • Translation Workbench Enhancements
  • Translatable Visualforce Email Templates
  • Increased Maximum Limit for Rules
  • Lookup Filter Beta Enhancements
  • Number of Remote Access Application Authorizations Increased
  • Enable Sidebar Search Auto-Complete Setting
  • Enable Single-Search-Result Shortcut Setting
  • A number of Auto-Complete Enhancements
  • Standard Action Overrides are Packageable – Standard action overrides on buttons and links are now packageable for custom objects.
  • Personalized Email Alerts – You can now set the From Email Address in email alerts to the address of the default workflow user.
Limited Release, Beta and Developer Previews

Adobe Flash Builder for Force.com – Developer Preview
Adobe® Flash® BuilderTM for Force.com is an Eclipse-based IDE for developing Force.com Stratus apps (Adobe Air apps that leverage Force.com logic and data). Stratus applications run seamlessly online or offline while taking full advantage of the security, scalability, and reliability of Force.com.

New Report Builder – Developer Preview
A new visual report builder for tabular reports (with limited functionality).

New Opportunity Page – Pilot
An improved opportunity detail page with highlight panels, recommendations, and drag and drop side tabs. Available after Feb. 5, 2010.

My Domain – Limited Release
You can brand your login and navigation URLs for Salesforce to create something like: https://appirio.my.salesforce.com. It also is more secure as it uses HTTPS.

Patch Updates for Packages – Limited Release
Automatically upgrade your managed packages with patches. A push upgrade is a method of automatically upgrading your customers to a newer version of your package. A package subscriber doesn’t need to do anything to receive the push upgrade. If you are developing managed packages this is a must read.

Rich Text Support – Beta
A new rich tech field type that is production quality but with some known limitation. The max size is 32,000 characters including HTML tags and only .gif, .jpeg and .png images are supported.

Wednesday, January 6, 2010

Calling a REST Web Service (JSON) with Apex

Jeff Douglas

Using JSON RESTful Web Services with Salesforce.com opens up your org to a number third-party integration opportunities (Google, Yahoo!, Flickr, bespoke, etc.). JSON support isn't baked into the Force.com platform but Ron Hess at Salesforce.com has created a JSON parser which will do the heavy lifting for you.

Last month I wrote a blog post and example of how to call a REST Web Service with Apex that returns and consumes XML. It was my intention to do the same demo using JSON, however, I ran into a small sang. I couldn’t get the Apex JSONObject parser to work. I tried on and off for a couple of days but couldn't beat it into submission. I checked around the twitter-verse and no one reported much success using the JSON parser with complex objects. I finally cried "uncle" and called Ron and asked for help. Ron was extremely responsive and over the course of a couple of days we worked worked through some of the parsing issues and finally updated the Google project with the changes.



I put together a small demo where you enter your address and the Apex code fetches the address and coordinates from the Google Maps . The service returns the data as a JSON object. You can run this example on my Developer Site.

To get started, you'll need to download the JSONObject class and install it into a Developer org or Sandbox. Unfortunately there is no documentation for the parser so you have to extrapolate from the json.org website.

You'll also need to sign up for a Google Maps API key in order to use their geocoding service. I would also recommend that you take a look at the docs for Google Maps geocoding service.

Here is the Controller for the demo. The interesting stuff is in the getAddress() and toGeoResult() methods. In getAddress(), the user-entered address is used to construct the URL for the GET call to the geocoding service. Make sure you properly encode the address or you may receive undesirable results returned from Google. One thing to point out is line #58. Google is returning a line feed in their JSON response which causes the JSON parser to choke. I simply replace all like feeds with spaces and that did the trick. Ron was going to look into making this change to the JSONObject class in the near future.

I was also having some problems with the geocoding service so I hard-coded the returned JSON object for testing. I checked around and it seems to be a common problem that the Google Maps API randomly returns 620 errors when overloaded. You might want to take a look at the JSON response returned for the hard-coded address. I will give you a little insight for the parsing process.

The toGeoResult() method parses the returned JSON response and populates the GeoResult object with the appropriate data. I chose this Google Maps example because it shows how to parse simple values, nested JSON objects and arrays. The coordinates for the address can either be returned as integers or doubles so I have to check each one.


public class RestDemoJsonController {

public String geoAddress {get;set;}
public String address {get;set;}
public String city {get;set;}
public String state {get;set;}
public Boolean useGoogle {get;set;}

// google api key
private String apiKey {get;set { apiKey = 'ABQIAAAAlI0DHB0p0WGX35GrKEAzQhTwZth5GdZI-P7ekoe_gyhfzl1yZhRAYdM-hb7aEWu30fGchcvGuwuUqg'; } }

// method called by the Visualforce page's submit button
public PageReference submit() {

if (address.length() == 0) {
ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR,'Address cannot be blank'));
}
if (city.length() == 0) {
ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR,'City cannot be blank'));
}
if (state.length() == 0) {
ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR,'State cannot be blank'));
}

if (!ApexPages.hasMessages())
geoAddress = getAddress(address,city,state);

return null;
}

// call the geocoding service
private String getAddress(String street, String city, String state) {

String json;

// hard-coded returned JSON response from Google
if (useGoogle) {
json = '{ "name": "1600 Amphitheatre Parkway, Mountain View, CA", "Status": { "code": 200, "request": "geocode" }, "Placemark": [ { "id": "p1", "address": "1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA", "AddressDetails": { "Accuracy" : 8, "Country" : { "AdministrativeArea" : { "AdministrativeAreaName" : "CA", "SubAdministrativeArea" : { "Locality" : { "LocalityName" : "Mountain View", "PostalCode" : { "PostalCodeNumber" : "94043" }, "Thoroughfare" : { "ThoroughfareName" : "1600 Amphitheatre Pkwy" } }, '+
' "SubAdministrativeAreaName" : "Santa Clara" } }, "CountryName" : "USA", "CountryNameCode" : "US" }}, "ExtendedData": { "LatLonBox": { "north": 37.4251466, "south": 37.4188514, "east": -122.0811574, "west": -122.0874526 } }, "Point": { "coordinates": [ -122.0843700, 37.4217590, 0 ] } } ]} ';

// call the geocoding service live
} else {

HttpRequest req = new HttpRequest();
Http http = new Http();
// set the method
req.setMethod('GET');
// generate the url for the request
String url = 'http://maps.google.com/maps/geo?q='+ EncodingUtil.urlEncode(street,'UTF-8')+',+'
+ EncodingUtil.urlEncode(city,'UTF-8')+',+'
+ EncodingUtil.urlEncode(state,'UTF-8')
+'&output=json&sensor=false&key='+apiKey;
// add the endpoint to the request
req.setEndpoint(url);
// create the response object
HTTPResponse resp = http.send(req);
// the geocoding service is returning a line feed so parse it out
json = resp.getBody().replace('\n', '');

}

try {
JSONObject j = new JSONObject( json );
return toGeoResult(j).toDisplayString();
} catch (JSONObject.JSONException e) {
return 'Error parsing JSON response from Google: '+e;
}

}

// utility method to convert the JSON object to the inner class
private GeoResult toGeoResult(JSONObject resp) {

GeoResult geo = new GeoResult();

try {

geo.address = resp.getValue('Placemark').values[0].obj.getValue('address').str;
geo.keys = resp.keys();
geo.name = resp.getString('name');
geo.statusCode = resp.getValue('Status').obj.getValue('code').num;

// set the coordinates - they may either be integers or doubles
geo.coordinate1 = resp.getValue('Placemark').values[0].obj.getValue('Point').obj.getValue('coordinates').values[0].num != NULL ? resp.getValue('Placemark').values[0].obj.getValue('Point').obj.getValue('coordinates').values[0].num.format() : resp.getValue('Placemark').values[0].obj.getValue('Point').obj.getValue('coordinates').values[0].dnum.format();
geo.coordinate2 = resp.getValue('Placemark').values[0].obj.getValue('Point').obj.getValue('coordinates').values[1].num != NULL ? resp.getValue('Placemark').values[0].obj.getValue('Point').obj.getValue('coordinates').values[1].num.format() : resp.getValue('Placemark').values[0].obj.getValue('Point').obj.getValue('coordinates').values[1].dnum.format();
geo.coordinate3 = resp.getValue('Placemark').values[0].obj.getValue('Point').obj.getValue('coordinates').values[2].num != NULL ? resp.getValue('Placemark').values[0].obj.getValue('Point').obj.getValue('coordinates').values[2].num.format() : resp.getValue('Placemark').values[0].obj.getValue('Point').obj.getValue('coordinates').values[2].dnum.format();

} catch (Exception e) {
// #fail
}

return geo;
}

// inner class
private class GeoResult {

public Set keys;
public Integer statusCode;
public String name;
public String coordinate1;
public String coordinate2;
public String coordinate3;
public String address;
public String toDisplayString() {
return address + ' ['
+ coordinate1 + ', '
+ coordinate2 + ', '
+ coordinate3 + '] - Status: '
+ statusCode;
}

}

}


The Visualforce page is fairly simple and presents the user with a form to enter their address. If the geocoding services is experiencing issues, the user can check "Use hard-coded Google JSON response?" and the Controller with use the hard-coded JSON response instead of making the GET call to the geocoding service. Once submitted, the address is processed and the outputPanel is rerendered with the resulting address and coordinates.













This example calls Google Map's geocoding REST service (JSON) with the address
you provide below.



Sometimes the geocoding services stops responding due to service availability. If you are receiving errors
with the returned JSON object, you can check the "Use hard-coded JSON response" to use a returned JSON
response hard-coded into the controller from Google's address.





Address






City






State







Use hard-coded Google JSON response?
















Unit Testing

Writing unit tests for callouts can present a challenge. Scott Hemmeter has a really good article entitled Testing HTTP Callouts which should provide you with some useful techniques. You should also check out An Introduction to Apex Code Test Methods on the developerforce wiki.
 
2006-2010 Appirio Inc. All rights reserved.
Appirio.com | Support | Resource Center | Contact | Careers | Privacy Policy