Implementing 1:n-selection via DataSource's

Hello Community,

I am currently evaluating GWT and SmartGWT for a back-office application. While being very enthusiastic about the SmartGWT-capabilities I am suffering a major problem regarding 1:n-Selections from backend-model to frontend.

This is what I am trying to achive:

  • I have a customer-entity that is associated to a country.
  • A country is not a free text-field, but realized by an association to a separated entity (using Longs as primary keys for all entities).
  • I’d like to display (1) a list-grid containing all customers and (2) a dynamic-form for updating existing / adding new customers. Beside other attributes the country-name should (1) be shown in the list-grid as a read-only text-field and (2) be editable by a select-box containing all possible country-names. By pressing a save-button the customer-entity should be passed (for creation or updating) to the server.
  • While passed to the server a (changed) country should be identified by its primary key. The backend is supposed to process the customer-entity and country-relationship accordingly.
  • The possible countries should be retrieved from the server either (a) when the according view is oppened or (b) when a list-grid-entry is clicked.
  • The attached image illustrate my objective.

This is my basic architecture:

  • Server works with Hibernate-POJOs.
  • I am transferring defined DTOs via GWT-RPC. Business-logic might transfer slightly differing (from backend-model) DTOs.
  • I am working with DataSources passed by GWT-activities to the SmartGWT-view-components. DataSources are derived from GwtRpcDataSource (taken from smartgwt-extensions) for bridging SmartGWT and GWT-RPC.

This is my project-setting:

  • GWT 2.1, SmartGWT 2.4, Hibernate 3, Dozer

This is the model (Hibernate-POJO):

public class Country {
	private Long id;
	private String name;
	// ... additional fields + getters and setters
}

public class Customer {
	private Long id;
	private Country addressCountry;
	// ... additional fields + getters and setters
}

The DTOs (transferred via GWT-RPC): (pretty similar to the POJOs above)

public class CountryDTO {
	private Long id;
	private String name;
	// ... additional fields + getters and setters
}

public class CustomerDTO {
	private Long id;
	private CountryDTO addressCountry;
	// ... additional fields + getters and setters
}

I am working with two GwtRpcDataSource-derived data-sources:

public class CountryDataSource extends GwtRpcDataSource {
	public CountryDataSource() {
		setID("countryDS");
		
		Field field = new DataSourceIntField("country_id");
		field.setPrimaryKey(true);
        field.setRequired(true);
		addField(field);
		
		field = new DataSourceTextField("country_name");
		addField(field);
	}
	
	// ... fetching using GWT's AsynchCallback-class (countries are fetch-only)
	
	// method to convert a DTO to a record
	private static void mapToRecord(CountryDTO country, Record toRecord) {
		toRecord.setAttribute("country_id", country.getId());
		toRecord.setAttribute("country_name", country.getName());
	}
}

public class CustomerDataSource extends GwtRpcDataSource {
	public CustomerDataSource(CountryDataSource countryDataSource) {
		setID("customerDS");
		
		Field field = new DataSourceIntField("customer_id");
		field.setPrimaryKey(true);
		addField(field);
		
		field = new DataSourceIntField("customer_country_id");
		field.setForeignKey("countryDS.country_id");
		
		// HERE I AM WORKING WITH A CUSTOM EDITOR FOR THIS FIELD
		SelectItem countrySelectItem = new SelectItem();
		countrySelectItem.setOptionDataSource(countryDataSource);
		countrySelectItem.setOptionDataSource(countryDataSource);
		countrySelectItem.setAutoFetchData(true);
		countrySelectItem.setValueField("country_id");
		countrySelectItem.setDisplayField("country_name");
		field.setEditorType(countrySelectItem);
		
		addField(field);
	}
	
	// ... fetching, updting, adding, creaing using GWT's AsynchCallback-class
	
	// method to convert a DTO to a record
	private static void mapToRecord(CustomerDTO fromCustomer, Record toRecord) {
		toRecord.setAttribute("customer_id", fromCustomer.getId());
		toRecord.setAttribute("customer_country_id", fromCustomer.getCountry().getId());
	}
	
	// method to convert a DTO to a record
	private static void mapToRecord(Record fromRecord, CustomerDTO toCustomer) {
		toCustomer.setId(fromRecord.getAttribute("customer_id"));
		toCustomer.getCountry().setId(fromRecord.getAttribute("customer_country_id"));
	}
}

On the frontend I work with the following code:

// ...

VLayout vLayout = new VPanel();

// The data-sources
CountryDataSource countryDataSource = new CountryDataSource();
CustomerDataSource customerDataSource = new CustomerDataSource(countryDataSource);

// The detail-form
DynamicForm form = new DynamicForm();
form.setDataSource(customerDataSource);
form.setIsGroup(true);
vLayout.addMember(form);

// The list
ListGrid listGrid = new ListGrid();
listGrid.setDataSource(customerDataSource);
listGrid.setAutoFetchData(true);
listGrid.addRecordClickHandler(new RecordClickHandler() {
    public void onRecordClick(RecordClickEvent event) {
        form.reset();
        form.editSelectedData(listGrid);
    }
});
ListGridField listGridIdField = new ListGridField("customer_id");  
ListGridField listGridCountryField = new ListGridField("customer_country_id");  
listGrid.setFields(listGridIdField, listGridCountryField);  
vLayout.addMember(listGrid);

vPanel.draw();

// ...

These are the problems I encountered:

  1. When I first click on a customer-entity (without having touched the details before) the country-select-box shows the country-id instead of the according country-name. When I then open the select-box the number is adjusted to the according country-name (so far so good). When I then select another country-name the select-box changes its content accordingly, nevertheless it doesn’t really seem that the content of the select-box changed, because when I open the select-box again, the initial country-name is pre-selected, although when closing it shows the correct changed entry again (very confusing).
  2. When I try to save the customer-entry, I get the following exception and the executeUpdate-method in the CustomerDataSource is been omitted. Exception:
Caused by: com.google.gwt.core.client.JavaScriptException: (null): null
    at com.google.gwt.dev.shell.BrowserChannelServer.invokeJavascript(BrowserChannelServer.java:237)
    at com.google.gwt.dev.shell.ModuleSpaceOOPHM.doInvoke(ModuleSpaceOOPHM.java:126)
    at com.google.gwt.dev.shell.ModuleSpace.invokeNative(ModuleSpace.java:561)
    at com.google.gwt.dev.shell.ModuleSpace.invokeNativeVoid(ModuleSpace.java:289)
    at com.google.gwt.dev.shell.JavaScriptHost.invokeNativeVoid(JavaScriptHost.java:107)
    at com.smartgwt.client.widgets.form.DynamicForm.saveData(DynamicForm.java)

These are further questions I have:
3. When using my UI I see in the logs for every single customer selected a new fetch-access to the countries. It’s not the most important thing, but I’d like to know how do I just fetch the countries once a view is opened (not every time a customer-entry is clicked)?
4. Due to the asynchronious nature of GWT-applications there is no garanty that the response to the call to retrieve the country-list preceeds the result of a customer-list.

Any help would be much appreciated. Thanks a lot in advance!

Regards,
Paul

smartgwt-post_1toN-selection.JPG

optionDataSource is the right way to do this, and your UI-level settings look correct. Most likely, something is wrong with your GWT-RPC DataSource and the data it’s returning, since you can see the various optionDataSource samples working fine in the Showcase.

If you’d like to avoid writing a bunch of DTOs and other boilerplate code like implementation of basic CRUD operations, and have more logging and tools at your disposal, consider the Pro and EE versions - this sample shows how little you have to do to have correct CRUD operations working and this one shows easily pulling a display value from a related bean (your use case of showing a country name in the grid instead of country id).