Build the User Interface
At this point, we've built the basic layout for myCRM. Now we're ready to start building the user interface using smartGWT widgets. In this post, we'll:
- Select the smartGWT widgets for each UI element
- Implement the smartGWT widgets
- Test the application in development mode
Like GWT, smartGWT shields you from worrying too much about cross-browser incompatibilities. If you construct your application's user interface with smartGWT widgets, the application will work on the most recent versions of Chrome, Firefox, Internet Explorer, Opera, and Safari. However, it's still a good idea to test your applications thoroughly on every browser.
1. Select the smartGWT widgets
Take a look at the smartGWT Showcase, the types of widgets and how they can be used. Some of the widgets that we might find useful are the menus, the section stack and the list grid.
2. Implement the smartGWT widgets
In the area to the left of the browser window we've decided to place a Navigation pane. The navigation pane is where you place information that enables you to move around within an application.
So let's start out by updating the Navigation pane:
package au.org.myCRM.client.ui.widgets;
import com.google.gwt.core.client.GWT;
import com.smartgwt.client.types.Overflow;
import com.smartgwt.client.types.VisibilityMode;
import com.smartgwt.client.widgets.HTMLFlow;
import com.smartgwt.client.widgets.layout.SectionStack;
import com.smartgwt.client.widgets.layout.SectionStackSection;
public class NavigationPane extends SectionStack {
private static final int WEST_WIDTH = 200;
public NavigationPane() {
super();
GWT.log("init NavigationPane()...", null);
// initialise the Section Stack
this.setWidth(WEST_WIDTH);
this.setVisibilityMode(VisibilityMode.MUTEX);
this.setShowExpandControls(false);
this.setAnimateSections(true);
// create sample content for the Section Stack sections
String sales = "Leads<br />Opportunities<br />Accounts<br />Contacts<br />" +
"Marketing Lists<br />Competitors<br />Products<br />" +
"Sales Literature<br />Quotes<br />Orders<br />Quick Campaigns";
String settings = "Administration<br />Business Management<br />Customisation<br />" +
"Templates<br />Product Catalogue<br />Workflows<br />" +
"Data Management<br />System Jobs<br />";
String resources = "Highlights<br />Sales<br />Marketing<br />Service<br />Settings<br />";
// initialise the Sales section
SectionStackSection section1 = new SectionStackSection("Sales");
section1.setExpanded(true);
HTMLFlow htmlFlow1 = new HTMLFlow();
htmlFlow1.setOverflow(Overflow.AUTO);
htmlFlow1.setPadding(10);
htmlFlow1.setContents(sales);
section1.addItem(htmlFlow1);
// initialise the Settings section
SectionStackSection section2 = new SectionStackSection("Settings");
section2.setExpanded(true);
HTMLFlow htmlFlow2 = new HTMLFlow();
htmlFlow2.setOverflow(Overflow.AUTO);
htmlFlow2.setPadding(10);
htmlFlow2.setContents(settings);
section2.addItem(htmlFlow2);
// initialise the Resource Centre section
SectionStackSection section3 = new SectionStackSection("Resource Centre");
section3.setExpanded(true);
HTMLFlow htmlFlow3 = new HTMLFlow();
htmlFlow3.setOverflow(Overflow.AUTO);
htmlFlow3.setPadding(10);
htmlFlow3.setContents(resources);
section3.addItem(htmlFlow3);
// add the sections to the Section Stack
this.addSection(section1);
this.addSection(section2);
this.addSection(section3);
}
}
We now have a UI element, the SectionStack that will help us to organise information in the Navigation pane.

Now, lets add a grid to the Context area by creating a new view class, the AccountView:
package au.org.myCRM.client.ui.view;
import au.org.myCRM.client.ui.widgets.ContextAreaListGrid;
import com.google.gwt.core.client.GWT;
import com.smartgwt.client.widgets.layout.VLayout;
public class AccountView extends VLayout {
public AccountView() {
super();
GWT.log("init AccountView()...", null);
// initialise the Account View layout container
this.addStyleName("crm-ContextArea");
this.setWidth("*");
// add the List Grid to the Account View layout container
this.addMember(new ContextAreaListGrid());
}
}
Then we need to create a wrapper for the ListGrid widget, the ContextAreaListGrid class:
package au.org.myCRM.client.ui.widgets;
import au.org.myCRM.client.ui.data.AccountData;
import com.google.gwt.core.client.GWT;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.widgets.grid.ListGrid;
import com.smartgwt.client.widgets.grid.ListGridField;
import com.smartgwt.client.types.ListGridFieldType;
public class ContextAreaListGrid extends ListGrid {
public ContextAreaListGrid() {
super();
GWT.log("init ContextAreaListGrid()...", null);
// initialise the List Grid
this.setShowAllRecords(true);
this.setSortField(1);
// initialise the List Grid fields
ListGridField iconField = new ListGridField("icon", "#", 27);
iconField.setImageSize(16);
iconField.setAlign(Alignment.CENTER);
iconField.setType(ListGridFieldType.IMAGE);
iconField.setImageURLPrefix("icons/16/");
iconField.setImageURLSuffix(".png");
ListGridField accountNameField = new ListGridField("accountName", "Account Name", 320);
ListGridField mainPhoneField = new ListGridField("mainPhone", "Main Phone", 100);
ListGridField locationField = new ListGridField("location", "Location", 100);
ListGridField primaryContactField = new ListGridField("primaryContact", "Primary Contact", 140);
primaryContactField.setType(ListGridFieldType.LINK);
ListGridField emailPrimaryContactField = new ListGridField("emailPrimaryContact",
"Email (Primary Contact)", 180);
ListGridField emptyField = new ListGridField("emptyField", " ");
// set the fields into the List Grid
this.setFields(new ListGridField[] {iconField, accountNameField, mainPhoneField, locationField,
primaryContactField, emailPrimaryContactField, emptyField });
// populate the List Grid
this.setData(AccountData.getRecords());
}
}
We also need to create a couple of supporting classes, the AccountData class:
package au.org.myCRM.client.ui.data;
public class AccountData {
private static AccountRecord[] records;
public static AccountRecord[] getRecords() {
if (records == null) {
records = getNewRecords();
}
return records;
}
public static AccountRecord[] getNewRecords() {
return new AccountRecord[]{
new AccountRecord("account", "ABC Pty Ltd", "(02) 251 6641", "Sydney",
"Sean Doyle", "sales@uptick.com.au"),
new AccountRecord("account", "DEF Pty Ltd", "(02) 251 6642", "Melbourne",
"Mario Bernatovic", "sales@uptick.com.au"),
new AccountRecord("account", "GHI Pty Ltd", "(02) 251 6643", "Newcastle",
"Rob Ferguson", "sales@uptick.com.au"),
new AccountRecord("account", "JKL Pty Ltd", "(02) 251 6644", "Perth",
"Alister Bennett", "sales@uptick.com.au"),
new AccountRecord("account", "MNO Pty Ltd", "(02) 251 6645", "Newcastle",
"Mark Kirkpatrick", "sales@uptick.com.au"),
new AccountRecord("account", "PQR Pty Ltd", "(02) 251 6647", "Bankstown",
"Grahame King", "sales@uptick.com.au"),
new AccountRecord("account", "STU Pty Ltd", "(02) 251 6648", "Sydney",
"Peter Wood", "sales@uptick.com.au"),
new AccountRecord("account", "VWX Pty Ltd", "(02) 251 6649", "Newcastle",
"Ross Hodge", "sales@uptick.com.au"),
new AccountRecord("account", "YZA Pty Ltd", "(02) 251 6610", "Sydney",
"Darren Poyner", "sales@uptick.com.au"),
new AccountRecord("account", "123 Pty Ltd", "(02) 251 6611", "Glebe",
"Carl Blick", "sales@uptick.com.au"),
new AccountRecord("account", "456 Pty Ltd", "(02) 251 6612", "Sydney",
"Mark Powrie", "sales@uptick.com.au"),
new AccountRecord("account", "789 Pty Ltd", "(02) 251 6613", "Perth",
"Jason Bance", "sales@uptick.com.au"),
new AccountRecord("account", "101 Pty Ltd", "(02) 251 6614", "Newcastle",
"Patrick Keegan", "sales@uptick.com.au")
};
}
}
and the AccountRecord class:
package au.org.myCRM.client.ui.data;
import com.smartgwt.client.widgets.grid.ListGridRecord;
public class AccountRecord extends ListGridRecord {
public AccountRecord() {}
public AccountRecord(String icon,
String accountName,
String mainPhone,
String location,
String primaryContact,
String emailPrimaryContact) {
setIcon(icon);
setAccountName(accountName);
setMainPhone(mainPhone);
setLocation(location);
setPrimaryContact(primaryContact);
setEmailPrimaryContact(emailPrimaryContact);
}
public void setIcon(String icon) {
setAttribute("icon", icon);
}
public void setAccountName(String accountName) {
setAttribute("accountName", accountName);
}
public void setMainPhone(String mainPhone) {
setAttribute("mainPhone", mainPhone);
}
public void setLocation(String location) {
setAttribute("location", location);
}
public void setPrimaryContact(String primaryContact) {
setAttribute("primaryContact", primaryContact);
}
public void setEmailPrimaryContact(String emailPrimaryContact) {
setAttribute("emailPrimaryContact", emailPrimaryContact);
}
public String getIcon() {
return getAttributeAsString("icon");
}
public String getAccountName() {
return getAttributeAsString("accountName");
}
public String getMainPhone() {
return getAttributeAsString("mainPhone");
}
public String getLocation() {
return getAttributeAsString("location");
}
public String getPrimaryContact() {
return getAttributeAsString("primaryContact");
}
public String getEmailPrimaryContact() {
return getAttributeAsString("emailPrimaryContact");
}
}
We also need to update the applications's style sheet, myCRM.css to include a background image and some padding around the ListGrid in the Context area.
/*
* Add css rules here for your application.
*
* Note: margin, border, padding, content (the box model)
*
*/
* {
font-family: Tahoma, Verdana, sans-serif;
font-size: 11px;
}
.crm-ContextArea {
background-image: url(images/context_area.png);
background-repeat: repeat-x;
padding-top: 4px;
padding-right: 4px;
padding-bottom: 4px;
padding-left: 4px;
}
Tip: Make sure you put the applications resources where smartGWT can find them at runtime (e.g. in an images subdirectory in the war directory).
We now have a UI element, the ListGrid that will help us to display information in the Context area.

Now, lets update the Application Menu class by adding support for a MenuBar, Menus and MenuItems.
package au.org.myCRM.client.ui.widgets;
import com.google.gwt.core.client.GWT;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.menu.Menu;
import com.smartgwt.client.widgets.menu.MenuBar;
import com.smartgwt.client.widgets.menu.MenuItem;
import com.smartgwt.client.widgets.menu.MenuItemSeparator;
public class ApplicationMenu extends HLayout {
private static final int APPLICATION_MENU_HEIGHT = 27;
private static final int DEFAULT_SHADOW_DEPTH = 10;
private static final String SEPARATOR = "separator";
private static final String ICON_PREFIX = "icons/16/";
private static final String ICON_SUFFIX = ".png";
private MenuBar menuBar ;
private int menuPosition = 0;
public ApplicationMenu() {
super();
GWT.log("init ApplicationMenu()...", null);
// initialise the Application Menu layout container
this.addStyleName("crm-ApplicationMenu");
this.setHeight(APPLICATION_MENU_HEIGHT);
// initialise the Menu Bar
menuBar = new MenuBar();
// add the Menu Bar to the Application Menu layout container
this.addMember(menuBar);
}
public Menu addMenu(String menuName, int width, String menuItemNames) {
// initialise the new menu
Menu menu = new Menu();
menu.setTitle(menuName);
menu.setShowShadow(true);
menu.setShadowDepth(DEFAULT_SHADOW_DEPTH);
menu.setWidth(width);
// create an array of menu item names
String[] menuItems = process(menuItemNames);
for (int i = 0; i < menuItems.length; i++) {
// remove any whitespace
String menuItemName = menuItems[i].replaceAll("\\W", "");
if (menuItemName.contentEquals(SEPARATOR)) {
MenuItemSeparator separator = new MenuItemSeparator();
menu.addItem(separator);
continue;
}
MenuItem menuItem = new MenuItem(menuItems[i], getIcon(menuItems[i]));
// menuItem.addClickHandler(clickHandler);
menu.addItem(menuItem);
}
Menu[] menus = new Menu[1];
menus[0] = menu;
menuBar.addMenus(menus, menuPosition);
menuPosition++ ;
return menus[0];
}
public Menu addMenu(String menuName, int width) {
// initialise the new menu
Menu menu = new Menu();
menu.setTitle(menuName);
menu.setShowShadow(true);
menu.setShadowDepth(DEFAULT_SHADOW_DEPTH);
menu.setWidth(width);
Menu[] menus = new Menu[1];
menus[0] = menu;
menuBar.addMenus(menus, menuPosition);
menuPosition++ ;
return menu;
}
public Menu addSubMenu(Menu parentMenu, String subMenuName, String menuItemNames) {
// initialise the new sub menu
Menu menu = new Menu();
menu.setShowShadow(true);
menu.setShadowDepth(DEFAULT_SHADOW_DEPTH);
MenuItem menuItem = new MenuItem(subMenuName);
// create an array of menu item names
String[] menuItems = process(menuItemNames);
for (int i = 0; i < menuItems.length; i++) {
// remove any whitespace
String menuItemName = menuItems[i].replaceAll("\\W", "");
if (menuItemName.contentEquals(SEPARATOR)) {
MenuItemSeparator separator = new MenuItemSeparator();
menu.addItem(separator);
continue;
}
menuItem = new MenuItem(menuItems[i], getIcon(menuItems[i]));
// menuItem.addClickHandler(clickHandler);
menu.addItem(menuItem);
}
// add the sub menu to the menu item
menuItem = new MenuItem(subMenuName);
parentMenu.addItem(menuItem);
menuItem.setSubmenu(menu);
return menu;
}
public final static String DELIMITER = ",";
public static String[] process(String line) {
return line.split(DELIMITER);
}
private String getIcon(String applicationName) {
// remove any whitespace
String name = applicationName.replaceAll("\\W", "");
// e.g. "icons/16/" + "activities" + ".png"
String icon = ICON_PREFIX + name.toLowerCase() + ICON_SUFFIX ;
return icon ;
}
}
We also need to add the following to the applications's style sheet, myCRM.css:
/*
* Add css rules here for your application.
*
* Note: margin, border, padding, content (the box model)
*
*/
* {
font-family: Tahoma, Verdana, sans-serif;
font-size: 11px;
}
body, table td, select {
font-family: Tahoma, Verdana, sans-serif;
font-size: 11px;
}
.crm-ContextArea {
border-top: 1px solid #7598c7;
background-image: url(images/context_area.png);
background-repeat: repeat-x;
padding-top: 4px;
padding-right: 4px;
padding-bottom: 4px;
padding-left: 4px;
}
.crm-ApplicationMenu {
background-image: url(images/application_menu.png);
background-repeat: repeat-x;
padding-top: 4px;
padding-left: 4px;
}
/*
* Application Menu buttons
*/
.menuButton,
.menuButtonOver,
.menuButtonDown,
.menuButtonDisabled,
.menuButtonSelected,
.menuButtonSelectedDown,
.menuButtonSelectedOver,
.menuButtonSelectedDisabled {
border: transparent 1px solid;
background: none;
color: #15428b;
font-family: Tahoma, Verdana, sans-serif;
font-size: 11px;
font-weight: normal;
padding-left: 4px;
padding-right: 4px;
}
.menuButtonOver {
border: #ffdb6c 1px solid;
background-image: url(images/application_menu_button_over.png);
background-repeat: repeat-x;
color: #15428b;
cursor: default;
font-family: Tahoma, Verdana, sans-serif;
font-size: 11px;
font-weight: normal;
padding-left: 2px;
padding-right: 2px;
}
.menuButtonDown,
.menuButtonSelected,
.menuButtonSelectedDown,
.menuButtonSelectedOver {
border: #ffdb6c 1px solid;
background-image: url(images/application_menu_button_down.png);
background-repeat: repeat-x;
color: #15428b;
cursor: default;
font-family: Tahoma, Verdana, sans-serif;
font-size: 11px;
font-weight: normal;
padding-left: 2px;
padding-right: 2px;
}
.menu,
.menuSelected,
.menuOver,
.menuSelectedOver,
.menuDisabled,
.menuTitleField,
.menuTitleFieldSelected,
.menuTitleFieldOver,
.menuTitleFieldSelectedOver,
.menuTitleFieldDisabled,
.menuIconField,
.menuIconFieldOver,
.menuIconFieldSelected,
.menuIconFieldSelectedOver,
.menuIconFieldDisabled,
.treeMenuSelected,
.treeMenuSelectedOver,
.treeMenuSelectedSelected,
.treeMenuSelectedSelectedOver {
color: #15428b;
font-family: Tahoma, Verdana, sans-serif;
font-size: 11px;
font-weight: normal;
padding-top: 2px;
padding-bottom: 2px;
}
and update the application's entry point class as follows:
public class myCRM implements EntryPoint {
private static final int NORTH_HEIGHT = 85; // MASTHEAD_HEIGHT + APPLICATION_MENU_HEIGHT
private static final int DEFAULT_MENU_WIDTH = 70;
private VLayout mainLayout;
private HLayout northLayout;
private HLayout southLayout;
private VLayout eastLayout;
private VLayout westLayout;
ApplicationMenu applicationMenu ;
interface GlobalResources extends ClientBundle {
@NotStrict
@Source("myCRM.css")
CssResource css();
}
public void onModuleLoad() {
GWT.log("init OnLoadModule()...", null);
// inject global styles
GWT.<GlobalResources>create(GlobalResources.class).css().ensureInjected();
// get rid of scroll bars, and clear out the window's built-in margin,
// because we want to take advantage of the entire client area
Window.enableScrolling(false);
Window.setMargin("0px");
// initialise the main layout container
mainLayout = new VLayout();
mainLayout.setWidth100();
mainLayout.setHeight100();
// initialise the North layout container
northLayout = new HLayout();
northLayout.setHeight(NORTH_HEIGHT);
VLayout vLayout = new VLayout();
// add the Masthead to the nested layout container
vLayout.addMember(new Masthead());
// intiialise the Application menu
applicationMenu = new ApplicationMenu();
applicationMenu.addMenu("<u>N</u>ew Activity", DEFAULT_MENU_WIDTH,
"Task, Fax, Phone Call, Email, Letter, " +
"Appointment");
applicationMenu.addMenu("New Re<u>c</u>ord", DEFAULT_MENU_WIDTH,
"Account, Contact, separator, Lead, Opportunity");
Menu goToMenu = applicationMenu.addMenu("<u>G</u>o To", DEFAULT_MENU_WIDTH - 30);
applicationMenu.addSubMenu(goToMenu, "Sales", "Leads, Opportunities, Accounts, Contacts");
applicationMenu.addSubMenu(goToMenu, "Settings", "Administration, Templates, Data Management");
applicationMenu.addSubMenu(goToMenu, "Resource Centre", "Highlights, Sales, Settings");
applicationMenu.addMenu("<u>T</u>ools", DEFAULT_MENU_WIDTH - 30,
"Import Data, Duplicate Detection, Advanced Find, Options");
applicationMenu.addMenu("<u>H</u>elp", DEFAULT_MENU_WIDTH - 30,
"Help on this Page, Contents, myCRM Online, About myCRM");
// add the Application Menu to the nested layout container
vLayout.addMember(applicationMenu);
// add the nested layout container to the North layout container
northLayout.addMember(vLayout);
// initialise the West layout container
westLayout = new NavigationPane();
// initialise the East layout container
eastLayout = new AccountView();
// initialise the South layout container
southLayout = new HLayout();
// set the Navigation Pane and Context Area as members of the South
// layout container
southLayout.setMembers(westLayout, eastLayout);
// add the North and South layout containers to the main layout container
mainLayout.addMember(northLayout);
mainLayout.addMember(southLayout);
// add the main layout container to GWT's root panel
RootLayoutPanel.get().add(mainLayout);
}
}
Tip: If you are looking for open source icons then make sure you check out the Silk Icons library by Mark James.
We now have a few more UI elements: a MenuBar, Menus and MenuItems.

Now, lets update the Masthead by adding support for a logo:
package au.org.myCRM.client.ui.widgets;
import com.google.gwt.core.client.GWT;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.widgets.Img;
import com.smartgwt.client.widgets.Label;
import com.smartgwt.client.widgets.layout.HLayout;
public class Masthead extends HLayout {
private static final int MASTHEAD_HEIGHT = 58;
public Masthead() {
super();
GWT.log("init Masthead()...", null);
// initialise the Masthead layout container
this.addStyleName("crm-Masthead");
this.setHeight(MASTHEAD_HEIGHT);
// initialise the Logo image
Img logo = new Img("logo.png", 48, 48);
logo.addStyleName("crm-Masthead-Logo");
// initialise the Name label
Label name = new Label();
name.addStyleName("crm-MastHead-Name");
name.setContents("myCRM");
// initialise the West layout container
HLayout westLayout = new HLayout();
westLayout.setHeight(MASTHEAD_HEIGHT);
westLayout.setWidth("50%");
westLayout.addMember(logo);
westLayout.addMember(name);
// initialise the Signed In User label
Label signedInUser = new Label();
signedInUser.addStyleName("crm-MastHead-SignedInUser");
signedInUser.setContents("<b>Rob Ferguson</b><br />upTick");
// initialise the East layout container
HLayout eastLayout = new HLayout();
eastLayout.setAlign(Alignment.RIGHT);
eastLayout.setHeight(MASTHEAD_HEIGHT);
eastLayout.setWidth("50%");
eastLayout.addMember(signedInUser);
// add the West and East layout containers to the Masthead layout container
this.addMember(westLayout);
this.addMember(eastLayout);
}
}
and then we need to update the applications's style sheet, myCRM.css to include a background image and some padding around the logo.
...
.crm-Masthead {
background-image: url(images/masthead.png);
background-repeat: repeat-x;
}
.crm-Masthead-Logo {
/* height: 48px;
width: 48px; */
padding-top: 4px;
padding-left: 8px;
}
.crm-MastHead-Name {
color: #FFFFFF;
font-family: Tahoma, Verdana, sans-serif;
font-size: 14px;
font-weight: bold;
overflow: hidden;
padding-top: 12px;
padding-left: 2px;
text-align: left;
}
.crm-MastHead-SignedInUser {
color: #FFFFFF;
font-family: Tahoma, Verdana, sans-serif;
font-size: 10px;
font-weight: normal;
overflow: hidden;
padding-right: 4px;
text-align: right;
}
3. Test the application in development mode
Launch myCRM in development mode and it should now look like the following screen shot.

What's Next
At this point, we've built the basic UI components of myCRM by implementing smartGWT widgets and layout containers. The widgets don't respond to any input yet so next we'll wire up the widgets to listen for events and write the code that responds to those events.
