Manage Events
At this point, we've created the basic components of myCRM's user interface. smartGWT, like GWT is event-based. This means that the code executes in response to some event occurring. Most often, that event is triggered by the user, who uses the mouse or keyboard to interact with the application.
In this post, we'll wire up some widgets to listen for and handle events.
- Listen for events
- Respond to events
- Test the application in development mode
1. Listening for events
Event Handler Interfaces
Events in smartGWT use an event handler model similar to other user interface frameworks. To subscribe to an event, you pass a particular event handler interface to the appropriate widget. An event handler interface defines one or more methods that the widget then calls to publish an event.
Handling Mouse Events
One way users can interact with myCRM is by using their mouse to click on the menu. You can handle the application menu's click event by passing it an object that implements the ClickHandler interface. We'll use an anonymous inner class to implement the ClickHandler. The ClickHandler interface has one method, onClick, which fires when the user clicks on the menu.
When the user clicks on a menu, myCRM should respond by performing the appropriate action. We haven't created the methods to respond to events just yet so for now we'll create a stub.
First, we need to update the addMenu method of the ApplicationMenu class:
...
public Menu addMenu(String menuName, int width, String menuItemNames,
ClickHandler clickHandler) {
// 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];
}
then we need to update the application's entry point class by implementing the ClickHandler:
...
private class ApplicationMenuClickHandler implements ClickHandler {
@Override
public void onClick(MenuItemClickEvent event) {
String applicationName = event.getItem().getTitle();
SC.say("You clicked: " + applicationName);
}
}
and then add the new event handlers.
...
VLayout vLayout = new VLayout();
// add the Masthead to the nested layout container
vLayout.addMember(new Masthead());
// initialise the Application menu
applicationMenu = new ApplicationMenu();
applicationMenu.addMenu("<u>N</u>ew Activity", DEFAULT_MENU_WIDTH,
"Task, Fax, Phone Call, Email, Letter, " +
"Appointment", new ApplicationMenuClickHandler());
applicationMenu.addMenu("New Re<u>c</u>ord", DEFAULT_MENU_WIDTH,
"Account, Contact, separator, Lead, Opportunity",
new ApplicationMenuClickHandler());
Menu goToMenu = applicationMenu.addMenu("<u>G</u>o To", DEFAULT_MENU_WIDTH - 30);
applicationMenu.addSubMenu(goToMenu, "Sales", "Leads, Opportunities, Accounts, Contacts",
new ApplicationMenuClickHandler());
applicationMenu.addSubMenu(goToMenu, "Settings", "Administration, Templates, Data Management",
new ApplicationMenuClickHandler());
applicationMenu.addSubMenu(goToMenu, "Resource Centre", "Highlights, Sales, Settings",
new ApplicationMenuClickHandler());
applicationMenu.addMenu("<u>T</u>ools", DEFAULT_MENU_WIDTH - 30,
"Import Data, Duplicate Detection, Advanced Find, Options",
new ApplicationMenuClickHandler());
applicationMenu.addMenu("<u>H</u>elp", DEFAULT_MENU_WIDTH - 30,
"Help on this Page, Contents, myCRM Online, About myCRM",
new ApplicationMenuClickHandler());
// add the Application Menu to the nested layout container
vLayout.addMember(applicationMenu);
Now, if you click on a menu a dialog box will be displayed.

2. Responding to events
At this point, we know that to subscribe to an event, you pass a particular event handler interface to the appropriate widget. So, now we'll update the Navigation pane so that it will respond to user events by changing the view displayed in the Context area.
First, we need to update the Navigation pane class:
package au.org.myCRM.client.ui.widgets;
import au.org.myCRM.client.ui.data.NavigationPaneRecord;
import com.google.gwt.core.client.GWT;
import com.smartgwt.client.widgets.layout.VLayout;
import com.smartgwt.client.types.VisibilityMode;
import com.smartgwt.client.widgets.grid.events.RecordClickHandler;
import com.smartgwt.client.widgets.layout.SectionStack;
public class NavigationPane extends VLayout {
private static final int WEST_WIDTH = 200;
private SectionStack sectionStack ;
public NavigationPane() {
super();
GWT.log("init NavigationPane()...", null);
// initialise the Navigation Pane layout container
this.addStyleName("crm-NavigationPane");
this.setWidth(WEST_WIDTH);
// this.setShowResizeBar(true);
// initialise the Section Stack
sectionStack = new SectionStack();
sectionStack.setWidth(WEST_WIDTH);
sectionStack.setVisibilityMode(VisibilityMode.MUTEX);
sectionStack.setShowExpandControls(true);
sectionStack.setAnimateSections(true);
// add the Section Stack to the Navigation Pane layout container
this.addMember(sectionStack);
}
public void add(String sectionName, NavigationPaneRecord[] sectionData,
RecordClickHandler clickHandler) {
sectionStack.addSection(new NavigationPaneSection(sectionName, sectionData,
clickHandler));
}
public void expandSection(int section) {
sectionStack.expandSection(section);
}
}
then we need to create a new class, the NavigationPaneSection.
package au.org.myCRM.client.ui.widgets;
import au.org.myCRM.client.ui.data.NavigationPaneRecord;
import com.google.gwt.core.client.GWT;
import com.smartgwt.client.widgets.layout.SectionStackSection;
import com.smartgwt.client.widgets.grid.ListGrid;
import com.smartgwt.client.widgets.grid.ListGridField;
import com.smartgwt.client.widgets.grid.events.RecordClickHandler;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.types.ListGridFieldType;
public class NavigationPaneSection extends SectionStackSection {
private ListGrid listGrid;
private NavigationPaneRecord[] sectionData;
public NavigationPaneSection(String sectionName, NavigationPaneRecord[] sectionData,
RecordClickHandler clickHandler) {
super(sectionName);
GWT.log("init NavigationPaneSection()...", null);
this.sectionData = sectionData;
// initialise the List Grid
listGrid = new ListGrid();
listGrid.setBaseStyle("crm-NavigationPaneGridCell");
listGrid.setWidth("100%");
listGrid.setHeight("100%");
listGrid.setShowAllRecords(true);
listGrid.setShowHeader(false);
// initialise the Icon field
ListGridField appIconField = new ListGridField("icon", "Icon", 27);
appIconField.setImageSize(16);
appIconField.setAlign(Alignment.RIGHT);
appIconField.setType(ListGridFieldType.IMAGE);
appIconField.setImageURLPrefix("icons/16/");
appIconField.setImageURLSuffix(".png");
appIconField.setCanEdit(false);
// initialise the Name field
ListGridField appNameField = new ListGridField("name", "Name");
// add the fields to the list Grid
listGrid.setFields(new ListGridField[] {appIconField, appNameField});
// set up the column data
listGrid.setData(sectionData);
listGrid.selectRecord(0);
// register the click handler
listGrid.addRecordClickHandler(clickHandler);
// section.setItems(appList);
this.addItem(listGrid);
this.setExpanded(true);
}
public ListGrid getListGrid() {
return listGrid;
}
public void selectRecord(String name) {
for (int i = 0; i < this.sectionData.length; i++) {
NavigationPaneRecord record = this.sectionData[i];
if (name.contentEquals(record.getName())) {
GWT.log("selectRecord->name.contentEquals(record.getName())", null);
getListGrid().deselectAllRecords();
getListGrid().selectRecord(i);
}
}
}
public int getRecord(String appName) {
int result = -1;
for (int i = 0; i < this.sectionData.length; i++) {
NavigationPaneRecord record = this.sectionData[i];
if (appName.contentEquals(record.getName())) {
GWT.log("selectRecord->name.contentEquals(record.getName())", null);
result = i;
}
}
return result;
}
public void setSectionData(NavigationPaneRecord[] sectionData) {
this.sectionData = sectionData;
}
public NavigationPaneRecord[] getSectionData() {
return sectionData;
}
}
We then need to create a new interface, the ContextAreaFactory:
package au.org.myCRM.client.ui.view;
import com.smartgwt.client.widgets.Canvas;
public interface ContextAreaFactory {
Canvas create();
String getID();
String getDescription();
}
and update the Account's View class:
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.Canvas;
import com.smartgwt.client.widgets.layout.VLayout;
public class AccountsView extends VLayout {
private static final String DESCRIPTION = "AccountsView";
public AccountsView() {
super();
GWT.log("init AccountsView()...", null);
// initialise the Accounts View layout container
this.addStyleName("crm-ContextArea");
this.setWidth("*");
// add the List Grid to the Accounts View layout container
this.addMember(new ContextAreaListGrid());
}
public static class Factory implements ContextAreaFactory {
private String id;
public Canvas create() {
AccountsView view = new AccountsView();
id = view.getID();
GWT.log("AccountsView.Factory.create()->view.getID() - " + id, null);
return view;
}
public String getID() {
return id;
}
public String getDescription() {
return DESCRIPTION;
}
}
}
and create a new view, the CalendarView:
public class CalendarView extends VLayout {
private static final String DESCRIPTION = "CalendarView";
public CalendarView() {
super();
GWT.log("init CalendarView()...", null);
// initialise the Calendar View layout container
this.addStyleName("crm-ContextArea");
this.setWidth("*");
DataSource eventDS = new DataSource();
DataSourceSequenceField eventIdField = new DataSourceSequenceField("eventId");
eventIdField.setPrimaryKey(true);
DataSourceTextField nameField = new DataSourceTextField("name");
DataSourceTextField descField = new DataSourceTextField("description");
DataSourceDateField startDateField = new DataSourceDateField("startDate");
DataSourceDateField endDateField = new DataSourceDateField("endDate");
eventDS.setFields(eventIdField, nameField, descField, startDateField, endDateField);
eventDS.setClientOnly(true);
eventDS.setTestData(CalendarData.getRecords());
Calendar calendar = new Calendar();
calendar.setShowWeekends(false);
calendar.setShowWorkday(true);
calendar.setScrollToWorkday(true);
calendar.setDataSource(eventDS);
calendar.setAutoFetchData(true);
LayoutSpacer paddingTop = new LayoutSpacer();
paddingTop.setHeight(8);
this.addMember(paddingTop);
this.addMember(calendar);
}
public static class Factory implements ContextAreaFactory {
private String id;
public Canvas create() {
CalendarView view = new CalendarView();
id = view.getID();
GWT.log("CalendarView.Factory.create()->view.getID() - " + id, null);
return view;
}
public String getID() {
return id;
}
public String getDescription() {
return DESCRIPTION;
}
}
We also need to create a couple of supporting classes, the NavigationPaneRecord:
package au.org.myCRM.client.ui.data;
import au.org.myCRM.client.ui.view.ContextAreaFactory;
import com.smartgwt.client.widgets.grid.ListGridRecord;
import com.smartgwt.client.widgets.grid.events.CellDoubleClickHandler;
public class NavigationPaneRecord extends ListGridRecord {
public NavigationPaneRecord() {}
public NavigationPaneRecord(String icon, String name, ContextAreaFactory factory,
CellDoubleClickHandler clickHandler) {
setIcon(icon);
setName(name);
setFactory(factory);
setDoubleClickHandler(clickHandler);
}
public void setIcon(String appIcon) {
setAttribute("icon", appIcon);
}
public void setName(String appName) {
setAttribute("name", appName);
}
public void setFactory(ContextAreaFactory factory) {
setAttribute("factory", factory);
}
public void setDoubleClickHandler(CellDoubleClickHandler clickHandler) {
setAttribute("clickHandler", clickHandler);
}
public String getIcon() {
return getAttributeAsString("icon");
}
public String getName() {
return getAttributeAsString("name");
}
public ContextAreaFactory getFactory() {
return (ContextAreaFactory) getAttributeAsObject("factory");
}
public CellDoubleClickHandler getDoubleClickHandler() {
return (CellDoubleClickHandler) getAttributeAsObject("clickHandler");
}
}
and the SalesNavigationPaneSectionData class,
package au.org.myCRM.client.ui.data;
import au.org.myCRM.client.ui.view.CalendarView;
import au.org.myCRM.client.ui.view.AccountsView;
public class SalesNavigationPaneSectionData {
private static NavigationPaneRecord[] records;
public static NavigationPaneRecord[] getRecords() {
if (records == null) {
records = getNewRecords();
}
return records;
}
public static NavigationPaneRecord[] getNewRecords() {
return new NavigationPaneRecord[]{
new NavigationPaneRecord("activities", "Activities", new AccountsView.Factory(), null),
new NavigationPaneRecord("calendar", "Calendar", new CalendarView.Factory(), null),
new NavigationPaneRecord("leads", "Leads", new AccountsView.Factory(), null),
new NavigationPaneRecord("opportunities", "Opportunities", new AccountsView.Factory(), null),
new NavigationPaneRecord("accounts", "Accounts", new AccountsView.Factory(), null),
new NavigationPaneRecord("contacts", "Contacts", new AccountsView.Factory(), null)
};
}
}
the SettingsNavigationPaneSectionData class,
...
public class SettingsNavigationPaneSectionData {
private static NavigationPaneRecord[] records;
public static NavigationPaneRecord[] getRecords() {
if (records == null) {
records = getNewRecords();
}
return records;
}
public static NavigationPaneRecord[] getNewRecords() {
return new NavigationPaneRecord[]{
new NavigationPaneRecord("administration", "Administration", new AccountsView.Factory(), null),
new NavigationPaneRecord("templates", "Templates", new AccountsView.Factory(), null),
new NavigationPaneRecord("datamanagement", "Data Management", new AccountsView.Factory(), null)
};
}
}
and the ResourceCentreNavigationPaneSectionData class.
...
public class ResourceCentreNavigationPaneSectionData {
private static NavigationPaneRecord[] records;
public static NavigationPaneRecord[] getRecords() {
if (records == null) {
records = getNewRecords();
}
return records;
}
public static NavigationPaneRecord[] getNewRecords() {
return new NavigationPaneRecord[]{
new NavigationPaneRecord("highlights", "Highlights", new AccountsView.Factory(), null),
new NavigationPaneRecord("sales", "Sales", new AccountsView.Factory(), null),
new NavigationPaneRecord("settings", "Settings", new AccountsView.Factory(), null)
};
}
}
We also need to add the following to the applications's style sheet, myCRM.css:
...
/*
* Navigation Pane SectionStack headers - image based
*/
.imgSectionHeaderclosed,
.imgSectionHeaderopened,
.imgSectionHeaderDisabledclosed,
.imgSectionHeaderDisabledopened {
color: #15428b;
cursor: pointer;
cursor: hand;
font-family: Tahoma, Verdana, sans-serif;
font-size: 11px;
font-weight: bold;
}
.imgSectionHeaderTitleclosed,
.imgSectionHeaderTitleopened {
color: #15428b;
cursor: pointer;
cursor: hand;
font-family: Tahoma, Verdana, sans-serif;
font-size: 11px;
font-weight: bold;
}
.imgSectionHeaderTitleOverclosed,
.imgSectionHeaderTitleOveropened {
color: #15428b;
cursor: pointer;
cursor: hand;
font-family: Tahoma, Verdana, sans-serif;
font-size: 11px;
font-weight: bold;
}
.imgSectionHeaderTitleDisabledopened,
.imgSectionHeaderTitleDisabledclosed {
color: #15428b;
cursor: pointer;
cursor: hand;
font-family: Tahoma, Verdana, sans-serif;
font-size: 11px;
font-weight: bold;
}
/*
* Navigation Pane ListGrid cells
*/
.crm-NavigationPaneGridCell {
color: black;
cursor: pointer;
cursor: hand;
font-family: Tahoma, Verdana, sans-serif;
font-size: 11px;
}
.crm-NavigationPaneGridCellDark {
color: black;
cursor: pointer;
cursor: hand;
font-family: Tahoma, Verdana, sans-serif;
font-size: 11px;
}
.crm-NavigationPaneGridCellOver,
.crm-NavigationPaneGridCellOverDark {
background: url(images/navigation_pane_gridcell_over.png) repeat-x bottom left scroll;
border-top: 1px solid #ffb74c;
border-bottom:1px solid #ffb74c;
color: black;
cursor: pointer;
cursor: hand;
font-family: Tahoma, Verdana, sans-serif;
font-size: 11px;
}
.crm-NavigationPaneGridCellSelected,
.crm-NavigationPaneGridCellSelectedDark {
border-top: 1px solid #ffb74c;
border-bottom: 1px solid #ffb74c;
background-color: #ffe6a0;
color: black;
cursor: pointer;
cursor: hand;
font-family: Tahoma, Verdana, sans-serif;
font-size: 11px;
}
.crm-NavigationPaneGridCellSelectedOver,
.crm-NavigationPaneGridCellSelectedOverDark {
border-top: 1px solid #ffb74c;
border-bottom: 1px solid #ffb74c;
background-color: #ffe6a0;
color: black;
cursor: pointer;
cursor: hand;
font-family: Tahoma, Verdana, sans-serif;
font-size: 11px;
}
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());
// initialise the Application menu
applicationMenu = new ApplicationMenu();
applicationMenu.addMenu("<u>N</u>ew Activity", DEFAULT_MENU_WIDTH,
"Task, Fax, Phone Call, Email, Letter, " +
"Appointment", new ApplicationMenuClickHandler());
applicationMenu.addMenu("New Re<u>c</u>ord", DEFAULT_MENU_WIDTH,
"Account, Contact, separator, Lead, Opportunity",
new ApplicationMenuClickHandler());
Menu goToMenu = applicationMenu.addMenu("<u>G</u>o To", DEFAULT_MENU_WIDTH - 30);
applicationMenu.addSubMenu(goToMenu, "Sales", "Leads, Opportunities, Accounts, Contacts",
new ApplicationMenuClickHandler());
applicationMenu.addSubMenu(goToMenu, "Settings", "Administration, Templates, Data Management",
new ApplicationMenuClickHandler());
applicationMenu.addSubMenu(goToMenu, "Resource Centre", "Highlights, Sales, Settings",
new ApplicationMenuClickHandler());
applicationMenu.addMenu("<u>T</u>ools", DEFAULT_MENU_WIDTH - 30,
"Import Data, Duplicate Detection, Advanced Find, Options",
new ApplicationMenuClickHandler());
applicationMenu.addMenu("<u>H</u>elp", DEFAULT_MENU_WIDTH - 30,
"Help on this Page, Contents, myCRM Online, About myCRM",
new ApplicationMenuClickHandler());
// 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 Navigation Pane
NavigationPane navigationPane = new NavigationPane();
navigationPane.add("Sales", SalesNavigationPaneSectionData.getRecords(),
new NavigationPaneClickHandler());
navigationPane.add("Settings", SettingsNavigationPaneSectionData.getRecords(),
new NavigationPaneClickHandler());
navigationPane.add("Resource Centre", ResourceCentreNavigationPaneSectionData.getRecords(),
new NavigationPaneClickHandler());
// select the first Navigation Pane section e.g Sales
navigationPane.expandSection(0);
// initialise the West layout container
westLayout = navigationPane;
// initialise the East layout container
eastLayout = new AccountsView();
// 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);
}
private class ApplicationMenuClickHandler implements ClickHandler {
@Override
public void onClick(MenuItemClickEvent event) {
String applicationName = event.getItem().getTitle();
SC.say("You clicked: " + applicationName);
}
}
private class NavigationPaneClickHandler implements RecordClickHandler {
@Override
public void onRecordClick(RecordClickEvent event) {
NavigationPaneRecord record = (NavigationPaneRecord) event.getRecord();
setContextAreaView(record);
}
}
private void setContextAreaView(NavigationPaneRecord record) {
ContextAreaFactory factory = record.getFactory();
Canvas view = factory.create();
southLayout.setMembers(westLayout, view);
}
}
3. Test the application in development mode
Launch myCRM in development mode and if you click in the Navigation pane the view displayed in the Context area will change.
The "Accounts" view:

The "Calendar" view:

What's Next
At this point, we've implemented event handlers to respond to mouse click events. Next, we'll add additional UI elements to myCRM.
