GWT Editor Framework : custom LeafValueEditor implementing HasEditorErrors - gwt

I have implemented my own form field as IsEditor<LeafValueEditor<String>> that I'd like to use in the forms of my application.
public class FormField extends Composite implements IsEditor<LeafValueEditor<String>> {
interface FormFieldUiBinder extends UiBinder<Widget, FormField> {
}
private static FormFieldUiBinder uiBinder = GWT.create(FormFieldUiBinder.class);
interface FormFieldStyle extends CssResource {
String error();
}
#UiField
TextBox wrapped;
private String placeholder;
public FormField() {
initWidget(uiBinder.createAndBindUi(this));
wrapped.setTitle("");
}
#UiHandler("wrapped")
public void onFocus(FocusEvent event) {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
#Override
public void execute() {
wrapped.selectAll();
}
});
}
public String getText() {
return wrapped.getText();
}
public void setText(String text) {
wrapped.setText(text);
}
/**
* Gets the current placeholder text for the text box.
*
* #return the current placeholder text
*/
public String getPlaceholder() {
return placeholder;
}
/**
* Sets the placeholder text displayed in the text box.
*
* #param placeholder
* the placeholder text
*/
public void setPlaceholder(String text) {
placeholder = (text != null ? text : "");
wrapped.getElement().setPropertyString("placeholder", placeholder);
}
public String getTitle() {
return wrapped.getTitle();
}
public void setTitle(String title) {
wrapped.setTitle(title);
}
#Override
public LeafValueEditor<String> asEditor() {
return wrapped.asEditor();
}
public int getVisibleLength() {
return wrapped.getVisibleLength();
}
public void setVisibleLength(int length) {
wrapped.setVisibleLength(length);
}
public boolean isReadOnly() {
return wrapped.isReadOnly();
}
public void setReadOnly(boolean readOnly) {
wrapped.setReadOnly(readOnly);
}
public boolean isEnabled() {
return wrapped.isEnabled();
}
public void setEnabled(boolean enabled) {
wrapped.setEnabled(enabled);
}
public void setWidth(String width) {
wrapped.setWidth(width);
}
}
The corresponding UIBinder file is plain simple :
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'>
<g:HTMLPanel>
<g:TextBox ui:field="wrapped" />
</g:HTMLPanel>
</ui:UiBinder>
This works smoothly in the forms I create like this :
<g:AbsolutePanel width="350px" height="225px"
styleName="{res.css.inputArea}">
<g:at left='10' top='0'>
<g:HTMLPanel width="350px">
<h1>Personalia</h1>
</g:HTMLPanel>
</g:at>
<g:at left='10' top='65'>
<f:FormLabel text="voornaam" />
</g:at>
<g:at left='10' top='80'>
<f:FormField ui:field="firstName" placeholder="voornaam" />
</g:at>
<g:at left='10' top='115'>
<f:FormLabel text="achternaam" />
</g:at>
<g:at left='10' top='130'>
<f:FormField ui:field="lastName" placeholder="achternaam"/>
</g:at>
</g:AbsolutePanel>
In my views, I can then use the EditorDriver like this :
interface EditorDriver extends SimpleBeanEditorDriver<Account, AccountPersonaliaEditor> {
}
private final EditorDriver editorDriver = GWT.create(EditorDriver.class);
And populating the form works fine too
editorDriver.initialize(editor);
editorDriver.edit(presenter.getAccount());
As well as getting the values after editing :
Account account = editorDriver.flush();
Now I would like to implement feedback on the errors. I have the GWT Bean validation framework working fine too. I just need to show the errors.
So, what I am trying next, is to have the FormField implement HasEditorErrors. Here is my problem/question.
public class FormField extends Composite implements IsEditor<LeafValueEditor<String>>, HasEditorErrors<String>
As soon as I implement this interface (even with a empty implementation), I run into the following compile time error :
[DEBUG] [klawtapp] - Rebinding com.example.screen.ui.center.AccountPersonaliaImpl.EditorDriver
[DEBUG] [klawtapp] - Invoking generator com.google.gwt.editor.rebind.SimpleBeanEditorDriverGenerator
[DEBUG] [klawtapp] - Creating Editor model for com.example.screen.ui.center.AccountPersonaliaImpl.EditorDriver
[DEBUG] [klawtapp] - Descending into firstName
[ERROR] [klawtapp] - Could not find a getter for path wrapped in proxy type java.lang.String
[DEBUG] [klawtapp] - Descending into lastName
[ERROR] [klawtapp] - Could not find a getter for path wrapped in proxy type java.lang.String
[ERROR] [klawtapp] - Unable to create Editor model due to previous errors
[ERROR] [klawtapp] - Deferred binding failed for 'com.example.screen.ui.center.AccountPersonaliaImpl.EditorDriver'; expect subsequent failures
This seemed so trivial. I have tried adding getter/setter for wrapped but that does not really help.
EDIT: for a moment, I thought the solution would be to implement HasEditorErrors<LeafValueEditor<String>> instead of HasEditorErrors<String> to prevent descending the hierarchy to the wrapped TextBox, but the results are similar :
[ERROR] [klawtapp] - Could not find a getter for path wrapped in proxy type com.google.gwt.editor.client.LeafValueEditor

Simply annotate your wrapped text box with #Editor.Ignore.
Alternatively, you could remove the implements IsEditor<LeafValueEditor<String>> and instead annotate the wrapped field with #Path("") (you'll have to test with null values though if you might face them, as I'm not sure it'll work well as-is).
Or you could choose to implement your own LeafValueEditor<String> instead of relying on the one from the TextBox.

Related

Breaking cyclic dependency in Dagger

I am very new to dagger--I don't even know yet if it will work for my application
I have a search page that returns the latest news about a given celebrity.
I have written a test to verify that results appear on the page when we search for a popular celebrity.
The page has a searchField, which requires page in its constructor so the web driver I use for my tests can select it.
Celebrity Search Page Test
public class CelebritySearchPageTest {
#Test
public void testSearchResultsForKevinBaconVerifyHisPopularity() {
CelebritySearchPage searchPage = new CelebritySearchPage();
searchPage.searchFor("Kevin Bacon");
Assert.assertTrue(searchPage.getNumberOfResults() > 9999999, "Verify that Kevin Bacon is still relevant");
}
}
Celebrity Search Page
public class CelebritySearchPage extends Page {
#Inject
#Named("search field")
TextField searchField;
public void searchFor(String text) {
searchField.setText(text);
// ...
}
public int getNumberOfResults() {
// ...
}
}
Celebrity Search Page Module
#Module(injects = CelebritySearchPage.class)
public class CelebritySearchPageModule {
#Provides
#Named("search field")
public TextField provideSearchField() {
return new TextField(/* How do I get the page? */, "#searchField");
}
}
Page
public abstract class Page {
// ...
}
Text Field
public class TextField {
protected Page page;
protected String selector;
public TextField(Page page, String selector) {
this.page = page;
this.selector = selector;
}
public void setText(String text) {
// ...
}
}
The problem is that page needs searchField, but searchField needs page. How do I get over this cyclic dependency?
I can't initialize searchField inside of CelebritySearchPage
Consider this:
CelebritySearchPage
public class CelebritySearchPage extends Page {
private final Lazy<TextField> searchField;
// always prefer constructor injection
// avoid #Named if possible, since the compiler cannot check the string
#Inject
CelebritySearchPage(#Named("search field") Lazy<TextField> searchField) {
this.searchField = searchField;
}
}
Text Field
public class TextField {
protected final Lazy<Page> page;
protected final String selector;
#Inject TextField(Lazy<Page> page, String selector) {
this.page = page;
this.selector = selector;
}
/*
Lazy::get()
Return the underlying value, computing the value if necessary. All calls to the same Lazy instance will return the same result.
*/
}
I guess one Lazy should suffice as well.

GWT's Editor Framework and GWTP

building on this answer, i try to integrate the GWT editors into a popup presenter widget. What is the right way to do that?
My view looks like this:
public class DeviceEditorDialogView extends
PopupViewWithUiHandlers<DeviceEditorDialogUiHandlers> implements
DeviceEditorDialogPresenterWidget.MyView {
interface Binder extends UiBinder<PopupPanel, DeviceEditorDialogView> {
}
public interface Driver extends SimpleBeanEditorDriver<DeviceDto, DeviceEditorDialogView> {
}
#Inject
DeviceEditorDialogView(Binder uiBinder, EventBus eventBus) {
super(eventBus);
initWidget(uiBinder.createAndBindUi(this));
}
#Override
public SimpleBeanEditorDriver<DeviceDto, ?> createEditorDriver() {
Driver driver = GWT.create(Driver.class);
driver.initialize(this);
return driver;
}
}
and my presenter looks like this:
public class DeviceEditorDialogPresenterWidget extends PresenterWidget<DeviceEditorDialogPresenterWidget.MyView> implements
DeviceEditorDialogUiHandlers {
#Inject
DeviceEditorDialogPresenterWidget(EventBus eventBus,
MyView view) {
super(eventBus, view);
getView().setUiHandlers(this);
}
/**
* {#link LocalDialogPresenterWidget}'s PopupView.
*/
public interface MyView extends PopupView, DevicesEditView<DeviceDto>, HasUiHandlers<DeviceEditorDialogUiHandlers> {
}
private DeviceDto currentDeviceDTO = null;
private SimpleBeanEditorDriver<DeviceDto, ?> driver;
public DeviceDto getCurrentDeviceDTO() {
return currentDeviceDTO;
}
public void setCurrentDeviceDTO(DeviceDto currentDeviceDTO) {
this.currentDeviceDTO = currentDeviceDTO;
}
#Override
protected void onBind() {
super.onBind();
driver = getView().createEditorDriver();
}
//UiHandler Method: Person person = driver.flush();
}
Is this the right approach? What is missing? Currently nothing happens when i try to use it like this:
#Override
public void showDeviceDialog() {
deviceEditorDialog.setCurrentDeviceDTO(new DeviceDto());
addToPopupSlot(deviceEditorDialog);
}
showDeviceDialog is in the parent presenter and called when clicking a button in that parent Presenter, that instantiates the dialog with private final DeviceEditorDialogPresenterWidget deviceEditorDialog;
Thanks!
Here are a few key points that are missing from your code above:
Your DeviceEditorDialogView should implement Editor<DeviceDto>. This is required in order for the fields of DeviceEditorDialogView to be populated with data from you POJO.
Your DeviceEditorDialogView should have child editors that are mapped to fields in your POJO. For example, given the field deviceDto.modelName (type String), you could have a GWT Label named modelName in your DeviceEditorDialogView. This Label implements Editor<String> and will be populated with the modelName from your DeviceDto when you call driver.edit(deviceDto)
You should call driver.initialize(this) only once, in DeviceEditorDialogView's constructor
You should override onReveal() like this:
#Override
public void onReveal() {
super.onReveal();
driver.edit(currentDeviceDTO); // this will populate your view with the data from your POJO
}
This method will be called when the popup is displayed, just after your DeviceEditorDialogPresenterWidget has been addToPopupSlot

ValueBoxEditorDecorator<String> does not show errors

I was under the impression that ValueBoxEditorDecorator would display errors to the right of the text box. I am running this code. When errors are picked up, I get a alert box, but nothing is shown for ValueBoxEditorDecorator.
public class AddressEditor extends Composite implements Editor<Address>, HasEditorErrors<Address>
{
private static final Binder binder = GWT.create(Binder.class);
#UiField ValueBoxEditorDecorator<String> name;
interface Binder extends UiBinder<Widget, AddressEditor> {
}
public AddressEditor() {
initWidget(binder.createAndBindUi(this));
}
#Override
public void showErrors(List<EditorError> errors) {
name.showErrors(errors);
if (errors.size()>0)
{
StringBuilder sb = new StringBuilder();
for(EditorError e : errors)
{
sb.append(e.getMessage());
}
Window.alert(sb.toString());
}
}
This is the xml ui.
<e:ValueBoxEditorDecorator ui:field="name">
<e:valuebox>
<g:TextBox/>
</e:valuebox>
</e:ValueBoxEditorDecorator>
EDIT:
This is my validation detection code. Perhaps I am erasing some data about the path files of the errors.
a = editorDriver.flush();
ValidatorFactory factory = Validation.byDefaultProvider().configure().buildValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Address>> violations = validator.validate(a);
#SuppressWarnings({ "unchecked", "rawtypes" })
Set<ConstraintViolation<?>> violations2 = (Set<ConstraintViolation<?>>) (Set) violations;
editorDriver.setConstraintViolations(violations2);
Found it. The problem was that my JSR303 annotations where on the private fields, rather than on the getter.

Spring List Binding

Thanks in advance for any help.
I have the following object association in my model:
public class Contract {
private Integer id;
private String name;
//getters/setters...
}
public class User {
....
private List<Contract> contracts;
....
}
Controller:
#RequestMapping(....)
public String getUser(#PathVariable Integer userId, Model model) {
....
model.addAttribute(userDao.findUser(userId));
model.addAttribute("contractsList", contractDao.findAllContracts());
....
}
#RequestMapping(....)
public String processUser(#ModelAttribute User user, Model model) {
....
//Create a copy of the user to update...
User userToUpdate = userDao.findUser(user.getId);
....
userToUpdate.setContracts(user.getContracts());
//set other properties...
userDao.updateUser(userToUpdate);
return "someSuccessView";
}
#InitBinder
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
binder.registerCustomEditor(Contract.class, new UserContractsPropertyEditor());
}
My PropertyEditor:
public class UserContractsPropertyEditor extends PropertyEditorSupport {
#Inject ContractDao contractDao;
#Override
public void setAsText(String text) throws IllegalArgumentException {
System.out.println("matching value: " + text);
if (text != "") {
Integer contractId = new Integer(text);
super.setValue(contractDao.findContract(contractId));
}
}
}
My JSP form:
<form:form commandName="user">
<%-- Other fields... --%>
<form:checkboxes items="${contractsList}"
path="contracts"
itemValue="id"
itemLabel="name" />
</form:form>
The form renders correctly. That is, the checkbox list of Contracts is generated and the correct ones are "checked." The problem is when I submit I get:
java.lang.IllegalArgumentException: 'items' must not be null
at org.springframework.util.Assert.notNull(Assert.java:112)
at org.springframework.web.servlet.tags.form.AbstractMultiCheckedElementTag.setItems(AbstractMultiCheckedElementTag.java:83)
at org.apache.jsp.WEB_002dINF.jsp._005fn.forms.user_jsp._jspx_meth_form_005fcheckboxes_005f0(user_jsp.java:1192)
....
The custom property editor seems to be doing its job and there are no null/empty strings being passed.
If the form and controller makes the conversion when viewing the form, why is it having trouble when processing the form? What am I missing here?
You need to ensure that a call to getContract() returns a List instance:
public List<Contract> getContracts() {
if (contracts == null) contracts = new ArrayList<Contract>();
return contracts;
}
Thanks for your response. I guess a fresh set of eyes first thing in the morning does the trick again.
Apparently, my custom property editor had no clue what to do with the id value I was passing in since it couldn't access my DAO/service. So, I had to change the constructor:
public class UserContractsPropertyEditor extends PropertyEditorSupport {
private ContractDao contractDao;
public UserContractsPropertyEditor(ContractDao contractDao) {
this.contractDao = contractDao;
}
#Override
public void setAsText(String text) throws IllegalArgumentException {
Integer contractId = new Integer(text);
Contract contract = contractDao.findContract(contractId);
super.setValue(contract);
}
}
Then, modified the initBinder in my controller:
#Inject ContractDao contractDao;
....
#InitBinder
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
binder.registerCustomEditor(Contract.class, new UserContractsPropertyEditor(this.contractDao));
}
Maybe this will help someone else.

GWT ValueListBox Editor

I'm puzzled about how to use GWT's ValueListBox with an Editor. I'm getting this ERROR:
The method setValue(String) in the type TakesValueEditor<String>
is not applicable for the arguments (List<String>)
Here's the relevant code.
public class MyBean {
private List<String> dateFormats;
public List<String> getDateFormats() {
return dateFormats;
}
public void setDateFormats(List<String> dateFormats) {
this.dateFormats = dateFormats;
}
}
public interface MyBeanView extends IsWidget, Editor<MyBean> {
#Path("dateFormats")
IsEditor<TakesValueEditor<String>> getDateFormatEditor();
}
public class MyBeanViewImpl implements MyBeanView {
#UiField(provided=true) ValueListBox<String> dateFormats;
public MyBeanViewImpl() {
dateFormats = new ValueListBox<String>(PassthroughRenderer.instance(),
new ProvidesKey<String>() {
#Override
public Object getKey(String item) {
return item;
}
});
dateFormats.setAcceptableValues(Arrays.asList(new String[] {"YYYY"}));
// ... binder.createAndBindUi(this);
}
#Override
public IsEditor<TakesValueEditor<String>> getDateFormatEditor() {
return dateFormats;
}
}
Here's what's in ui.xml with xmlns:g='urn:import:com.google.gwt.user.client.ui'>
<g:HTMLPanel>
Data Formats: <g:ValueListBox ui:field="dateFormats"> </g:ValueListBox>
</g:HTMLPanel>
I'm surely missing something obvious here. Much thanks.
The problem that you're running into has to do with trying to map the List<String> dateFormats from MyBean onto the ValueListBox<String> dateFormats editor. The datatypes are incompatible, since a ValueListBox<T> doesn't edit a List<T>, but instead a single instance of T chosen from a list provided by setAcceptableValues(). Given the example above, it would make sense for MyBean to have a String getDateFormat() property and rename the editor field to dateFormat.