Wicket hacked use of ListView, which alternatives? - java

Wicket use of models can be cumbersome. For a stateful page to properly render an object, you need to use lots of boiler-plate code, overriding classes to properly get the visibility status, etc... A simple example:
private IModel<FooBar> fooBarModel;
public MyPage() {
Label lbl1 = new Label("field1",
new PropertyModel<>(fooBarModel, "field1")) {
#Override public boolean isVisible() {
return fooBarModel.getObject().someCondition();
} }
add(lbl1);
/// Etc... same for dozen of other fields
}
I'm often using a trick using a ListView to help. Same example:
public MyPage() {
add(new ListView<FooBar>("content",
new SingleListModel<FooBar>(fooBarModel)) {
#Override protected void populateItem(...) {
FooBar fooBar = item.getModelObject();
// Code here gets simpler:
Label lbl1 = new Label("field1", fooBar.getField1());
lbl1.setVisible(fooBar.someCondition());
item.add(lbl1);
// Etc...
}
});
}
With a simple utility class SingleListModel, that transform a IModel<T> to a ListModel<T>, having 1 or 0 elements, depending whether T is null or not:
public class SingleListModel<T>
extends LoadableDetachableModel<List<T>> {
private IModel<T> tModel;
public SingleListModel(IModel<T> tModel) {
this.tModel = tModel;
}
#Override
protected List<T> load() {
List<T> ret = new ArrayList<>(1);
T t = tModel.getObject();
if (t != null)
ret.add(tModel.getObject());
return ret;
}
}
The nice side-effect of this is that the whole "content" element in the markup is hidden if fooBarModel returns null; no special treatment needed.
But all this smells like a hack to me, as I use ListView in a somehow "unnatural" fashion.
Is there a cleaner way to get the same result? A standard wicket framework?

You should use Behavior instead to avoid such duplications.
public class MyBehavior extends Behavior {
private final MyModel model;
public MyBehavior(MyModel model) {this.model = model;}
#Override public void onConfigure(Component c) {
if (model.someCondition()) {
component.setVisible(false);
}
}
}
Usage:
MyBehavior b = new MyBehavior(modelInstance);
component1.add(b);
component2.add(b);
// dozen more

Label lbl1 = new Label("field1",
new PropertyModel<>(fooBarModel, "field1")) {
#Override public boolean isVisible() {
return fooBarModel.getObject().someCondition();
} }
add(lbl1);
with little refactoring it can be converted into
add(new FLabel("id","text")
.setVisibilityFunction(()->model.getObject().isVisible()))
);
the FLabel class:
public class FLabel extends Label implements IComponentWithVisibilityFunction<FLabel> {
private SerializableBooleanSupplier visibilityFunction;
public FLabel(String id) {
super(id);
}
public FLabel(String id, Serializable label) {
super(id, label);
}
public FLabel(String id, IModel<?> model) {
super(id, model);
}
#Override
public FLabel setVisibilityFunction(SerializableBooleanSupplier visibilityFunction) {
this.visibilityFunction = visibilityFunction;
return this;
}
#Override
protected void onConfigure() {
if (visibilityFunction != null) {
setVisible(visibilityFunction.getAsBoolean());
}
}
}
public interface IComponentWithVisibilityFunction<T> {
T setVisibilityFunction(SerializableBooleanSupplier visibilityFunction);
}
Moreover you can put supplier into constructor:
add(new FLabel("id","text", ()->model.getObject().isVisible()));

Related

Best way to serialize composite - (design pattern)

I have following java code that is implementation of Composite Design pattern:
//composite designed for type safety (all Leaf-only operations only in leaf)
interface Component extends Visitable {
void enable();
void disable();
}
class CompositeA implements Component {
private String compositeData;
private boolean enabled;
private Set<Component> components = new HashSet<>();
CompositeA(String compositeData) {
this.compositeData = compositeData;
}
void addChild(Component component){
this.components.add(component);
}
String getCompositeData() {
return compositeData;
}
Set<Component> getComponents() {
return components;
}
#Override
public void enable() {
this.enabled = true;
}
#Override
public void disable() {
this.enabled = false;
}
#Override
public Object accept(ComponentVisitor visitor) {
return visitor.visit(this);
}
}
class CompositeB implements Component{
private int compositeData;
private boolean enabled;
private Set<Component> components = new HashSet<>();
CompositeB(int compositeData) {
this.compositeData = compositeData;
}
void addChild(Component component){
this.components.add(component);
}
int getCompositeData() {
return compositeData;
}
Set<Component> getComponents() {
return components;
}
#Override
public void enable() {
this.enabled = true;
}
#Override
public void disable() {
this.enabled = false;
}
#Override
public Object accept(ComponentVisitor visitor) {
return visitor.visit(this);
}
}
class Leaf implements Component {
private boolean enabled;
private String[] leafData;
Leaf(String[] leafData) {
this.leafData = leafData;
}
String[] getLeafData() {
return leafData;
}
#Override
public void enable() {
this.enabled = true;
}
#Override
public void disable() {
this.enabled = false;
}
#Override
public Object accept(ComponentVisitor visitor) {
return visitor.visit(this);
}
}
There are 2 possible composite roots here (CompositeA and CompositeB) and one leaf component (Leaf).
Here I define DTOs that will hold serialized data:
class WholeCompositeASerialized {
String content;
List<Object> serializedChildren;
}
class WholeCompositeBSerialized{
String content;
List<Object> serializedChildren;
}
class WholeLeafSerialized{
String content;
}
Now if I use visitor pattern for serialization, I get something like this:
interface ComponentVisitor {
WholeCompositeASerialized visit(CompositeA compositeA);
WholeCompositeBSerialized visit(CompositeB compositeB);
WholeLeafSerialized visit(Leaf leaf);
}
class SerializableComponentVisitor implements ComponentVisitor{
#Override
public WholeCompositeASerialized visit(CompositeA compositeA) {
WholeCompositeASerialized wcas = new WholeCompositeASerialized();
wcas.serializedChildren = compositeA
.getComponents()
.stream()
.map(c -> c.accept(this))
.collect(Collectors.toList());
wcas.content = compositeA.getCompositeData();
return wcas;
}
#Override
public WholeCompositeBSerialized visit(CompositeB compositeB) {
WholeCompositeBSerialized wcbs = new WholeCompositeBSerialized();
wcbs.serializedChildren = compositeB
.getComponents()
.stream()
.map(c -> c.accept(this))
.collect(Collectors.toList());
wcbs.content = String.valueOf(compositeB.getCompositeData());
return wcbs;
}
#Override
public WholeLeafSerialized visit(Leaf leaf) {
WholeLeafSerialized wls = new WholeLeafSerialized();
wls.content = Arrays.toString(leaf.getLeafData());
return wls;
}
}
interface Visitable{
Object accept(ComponentVisitor visitor);
}
and if I use instanceof this is the code that does the same thing:
class SerializerUsingInstanceOf {
Object decide(Component component){
if(component instanceof CompositeA){
return serialize((CompositeA)component);
}
else if(component instanceof CompositeB){
return serialize((CompositeB)component);
}
else{
return serialize((Leaf)component);
}
}
WholeCompositeASerialized serialize(CompositeA compositeA) {
WholeCompositeASerialized wcas = new WholeCompositeASerialized();
wcas.serializedChildren = compositeA
.getComponents()
.stream()
.map(this::decide)
.collect(Collectors.toList());
wcas.content = compositeA.getCompositeData();
return wcas;
}
WholeCompositeBSerialized serialize(CompositeB compositeB) {
WholeCompositeBSerialized wcbs = new WholeCompositeBSerialized();
wcbs.serializedChildren = compositeB
.getComponents()
.stream()
.map(this::decide)
.collect(Collectors.toList());
wcbs.content = String.valueOf(compositeB.getCompositeData());
return wcbs;
}
WholeLeafSerialized serialize(Leaf leaf) {
WholeLeafSerialized wls = new WholeLeafSerialized();
wls.content = Arrays.toString(leaf.getLeafData());
return wls;
}
}
I guess also that visitor is preferred here because when we add new Component, we are required to implement Object accept(ComponentVisitor visitor) method also - so we cannot forget that we need a code for serialization of this new component. If we do the same when we use instanceof we would possibly forget to add it to that check.
Now - my question is - is there any way that we can get rid of that ugly Object return type in Object accept(ComponentVisitor visitor) method signature? The only other option that comes to my mind is to use some marker interface (eg. interface SerializedComponent {}) and then have all serializer classes implement that empty interface like this class WholeCompositeASerialized implements SerializedComponent but it still does not seem right.
I think the proper way could be to use generics here.
e.g. https://onlinegdb.com/r1m5Eg4DP
public class Main {
public static void main(String []args){
ComponentVisitor<SerializedComponent> serializer = new ComponentSerializer();
Component componentA = new ComponentA();
SerializedComponent serializedA = componentA.accept(serializer);
System.out.println(serializedA);
Component component = new ComponentB();
SerializedComponent serializedB = component.accept(serializer);
System.out.println(serializedB);
}
static interface Component {
public <V> V accept(ComponentVisitor<V> visitor);
}
static class ComponentA implements Component {
public <V> V accept(ComponentVisitor<V> visitor) {
return visitor.visit(this);
}
}
static class ComponentB implements Component {
public <V> V accept(ComponentVisitor<V> visitor) {
return visitor.visit(this);
}
}
static interface SerializedComponent {}
static class SerializedComponentA implements SerializedComponent {
}
static class SerializedComponentB implements SerializedComponent {
}
static interface ComponentVisitor<V> {
public V visit(ComponentA component);
public V visit(ComponentB component);
}
static class ComponentSerializer implements ComponentVisitor<SerializedComponent> {
public SerializedComponent visit(ComponentA component) {
return new SerializedComponentA();
}
public SerializedComponent visit(ComponentB component) {
return new SerializedComponentB();
}
}
}
You are attempting to return concrete type information from the Visitor. This is not the purpose of the pattern. The Visitor encapsulates (and handles) the concrete type internally.
The solution here is to move all logic specific to ComponentA (or any A-specific type you might convert it to) into the visit(ComponentA) method, and likewise for ComponentB.
If you don't want the type encapsulation of the Visitor, then a different design is more suitable, such as pattern matching.
Comments on the comment...
public static void main(String[] args) {
// Using a concrete type here defeats the purpose of these patterns.
// Instead, program to an interface:
// Component c1 = new CompositeA("root");
CompositeA c1 = new CompositeA("root");
c1.addChild(new Leaf(new String[]{"leaf11", "leaf12"}));
CompositeA c2 = new CompositeA("composite1");
c2.addChild(new Leaf(new String[]{"leaf21", "leaf22"}));
c1.addChild(c2);
SerializableComponentVisitor scv = new SerializableComponentVisitor();
// Clients never invoke visit methods directly,
// because they do not have the type information to make these calls.
// A client would execute, c1.accept(scv)
WholeCompositeASerialized wcas1 = scv.visit(c1);
}

How to make a complex object an Observable

I have multiple JavaFX panes and canvases that reference a complex object with data they need, and I want them to redraw when the object changes.
This would call for the object to be Observable, but which class do I use? JavaFX seems to mostly have ObservableValue subclasses, which wrap a value and allow swapping it out. I don't want to swap out the complex object, just notify the listeners when changes occur. I could do that by implementing addListener, but I'm sure there's a subclass that does it for me already.
class ComplexObject /* extends SomeObservableClass */ {
public int getValue1 { complex calculations... };
public int getValue2 { ... };
public void setNewValue1(int newValue) { ... }
}
class ComplexRenderer extends Canvas implements InvalidationListener {
private ComplexObject complexObject;
public void setComplexObject(ComplexObject complexObject) {
this.complexObject = complexObject;
complexObject.addListener(this);
}
public void draw() { ... }
}
Which class should ComplexObject extend? Is there something that maintains the list of listeners and has something like fireValueChangedEvent() so I can make it notify all listeners?
Everything I see in JavaFX seems to be geared towards properties, which don't seem the right choice here.
Not really sure what you meant by swapping, and not really sure if I understood you right.
class ComplexObject {
private IntegerProperty value1 = new SimpleIntegerProperty();
private IntegerProperty value2 = new SimpleIntegerProperty();
private BooleanProperty internalChanged = new SimpleBooleanProperty(false);
public ComplexObject() {
this.internalChanged.bind(Bindings.createBooleanBinding(() ->
this.internalChanged.set(!this.internalChanged.get()), this.value1, this.value2));
}
public IntegerProperty value1Property() { return this.value1; }
public int getValue1() { return this.value1.get(); }
public void setValue1(int value) { return this.value1.set(value); }
public IntegerProperty value2Property() { return this.value2; }
public int getValue2() { return this.value2.get(); }
public void setValue2(int value) { return this.value2.set(value); }
public void setNewValue1(int newValue) { /* What value is this??? */ }
public BooleanProperty internalChangedProperty() { return this.internalChanged; }
}
class ComplexRenderer extends Canvas implements InvalidationListener {
private ComplexObject complexObject;
public void setComplexObject(ComplexObject complexObject) {
this.complexObject = complexObject;
complexObject.internalChangedProperty().addListener(this);
}
#Override public void invalidated(Observable observable) {
// Something inside complex object changed
}
public void draw() { ... }
}
Maybe you can have a look at the Interface ObjectPropertyBase<T> and the classes ObjectPropertyBase<T> and SimpleObjectProperty<T> which implements Observable.
However you have to define when your object changes and listening logic.
I'm sorry it's just a trace of work, but I hope it may be useful.

GWT fire events

I'm using GWT 2.4 by using the MVP pattern
I have this situation:
In one presenter (let's call it a TreePresenter since it shows a tree) i have the following code:
display.getSelectedItem().addSelectionHandler(new SelectionHandler<TreeItem>() {
#Override
public void onSelection(SelectionEvent<TreeItem> event) {
....
evtBus.fireEvent(new SelectCategoryEvent(item.getText()));
.....
}
});
And in my AppController class I have this situation:
eventBus.addHandler(SelectCategoryEvent.TYPE, new SelectCategoryEventHandler() {
#Override
public void onSelectCategory(SelectCategoryEvent event) {
saveHistoryEvent(event);
}
});
When i select one itm in the three the event is correctly fired by the instruction
evtBus.fireEvent(new SelectCategoryEvent(item.getText()));
But in my AppController the event is not propagated and I can't handle it in the code
eventBus.addHandler(SelectCategoryEvent.TYPE, new SelectCategoryEventHandler() {
#Override
public void onSelectCategory(SelectCategoryEvent event) {
saveHistoryEvent(event);
}
});
Can anybody tell me the reason?
Thank
Angelo
I'll give you some detail; I built a class for my own history management; I built this class:
public class GwtHistoryEventsMgr {
private Map<String, List<GwtEvent>> tknEvts;
private HandlerManager eventBus;
public GwtHistoryEventsMgr(HandlerManager evtBus){
tknEvts = new HashMap<String, List<GwtEvent>>();
this.eventBus = evtBus;
}
private void saveHistoryEvent( GwtEvent event ){
List<GwtEvent> eventi = null;
if( tknEvts.containsKey(History.getToken()) ){
eventi = tknEvts.get(History.getToken());
}else{
eventi = new ArrayList<GwtEvent>();
}
eventi.add(event);
tknEvts.put(History.getToken(), eventi);
}
public void addEvtHandlers(){
//Aggiungo gli handler
eventBus.addHandler(CustomEvent.TYPE, new CustomEventHandler() {
#Override
public void onEvent(CustomEvent event) {
saveHistoryEvent(event);
}
});
}
public List<GwtEvent> getTokenTransWidgetEvents(String token){
if( tknEvts.containsKey(token) ){
return tknEvts.remove(token);
}else{
return null;
}
}
}
Then in my AppController constructor I wrote this code:
public AppController(HandlerManager eventBus, StandardDispatchAsync dispatcher){
super(null);
this.eventBus = eventBus;
this.dispatcher = dispatcher;
//Gestione history
histMgr = new GwtHistoryEventsMgr(eventBus);
histMgr.addEvtHandlers();
}
This means that I should be pretty sure that the AppController registers itself to the events I want (note: I strongly reduced the code...but all the code is in the way I wrote)
Then, since I use client-side reflection, I did, where I use the client side reflection, this code (after that all widgets have been initialized):
#SuppressWarnings("rawtypes")
private void generateHistoricalEvents(){
List<GwtEvent> eventi = hisMgr.getTokenTransWidgetEvents(History.getToken());
//Ci sono eventi....vuol dire back del browser cliccato
if( eventi != null ){
for (GwtEvent gwtEvent : eventi) {
this.eventBus.fireEvent(gwtEvent);
}
}
}
According to me it's all OK; may you tell me where the problem is?
Thank you
Angelo

Panel use same model as Parent page

How do i get a panel to use the some modelObject as the parent page?
I have a form that uses an OwnedAccount as its model, and in the form i have a custom panel which has a refreshingview containing a list of financeAccount. The problem is that changes to the financeaccounts are not being changed in the modelobject of the form.
Some code, i removed a lot of code where there is 3 dots "..."
#Entity
#Table(name = "ownedaccount")
public class OwnedAccount implements Serializable {
...
//used for multiple currencies
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "ownedAccount")
private List<FinanceAccount> financeAccounts = new ArrayList<FinanceAccount>();
...
}
public class AssetsPage extends LoggedInPage {
...
// bookmarkable constructor
public AssetsPage(PageParameters parameters) {
super(parameters);
init();
}
private void init() {
final OwnedAccount ownedAccount = getCurrentSelections().getSelectedOwnedAccount();
add(new FeedbackPanel("feedback"));
entityEdit = new OwnedAccountForm("entityEdit", ownedAccount);
add(entityEdit);
}
#Override
protected void selectionsChanged() {
OwnedAccount selectedOwnedAccount = getCurrentSelections().getSelectedOwnedAccount();
CompoundPropertyModel<OwnedAccount> model = new CompoundPropertyModel<OwnedAccount>(selectedOwnedAccount);
entityEdit.setModel(model);
}
...
class OwnedAccountForm extends BaseCreateEditForm<OwnedAccount, Void> {
...
public OwnedAccountForm(String s, OwnedAccount entity) {
super(s, entity, null);
assetType = entity.getAssetType();
}
#Override
protected void initComponents(Void initParams) {
...
multipleCurrenciesPanel = new MultipleCurrenciesPanel("multipleCurrenciesPanel", ownedAccountService, getCurrentSelections());
add(multipleCurrenciesPanel);
...
}
...
}
public class MultipleCurrenciesPanel extends Panel {
OwnedAccountService ownedAccountService;
CurrentSelections currentSelections;
public MultipleCurrenciesPanel(String id, OwnedAccountService ownedAccountService, CurrentSelections currentSelections) {
super(id);
this.ownedAccountService = ownedAccountService;
this.currentSelections = currentSelections;
init();
}
private void init() {
DepositoryLabel currencyLabel = new DepositoryLabel("currency", new ResourceModel("currency"));
add(currencyLabel);
DepositoryLabel accountForSecuritasLabel = new DepositoryLabel("account.for.securitas", new ResourceModel("account.for.securitas"));
add(accountForSecuritasLabel);
DepositoryLabel accountForCashLabel = new DepositoryLabel("account.for.cash", new ResourceModel("account.for.cash"));
add(accountForCashLabel);
DepositoryLabel buttonDeleteLabel = new DepositoryLabel("button.delete", new ResourceModel("button.delete"));
add(buttonDeleteLabel);
CurrenciesView currenciesView = new CurrenciesView("financeAccounts", ownedAccountService, currentSelections, this);
add(currenciesView);
setOutputMarkupId(true);
}
}
Updated 25/9 - 15:15
public class CurrenciesView extends RefreshingView<FinanceAccount> {
private OwnedAccountService ownedAccountService;
private CurrentSelections currentSelections;
private WebMarkupContainer multipleCurrenciesForDepot;
public CurrenciesView(String id, OwnedAccountService ownedAccountService, CurrentSelections currentSelections, WebMarkupContainer multipleCurrenciesForDepot) {
super(id);
this.ownedAccountService = ownedAccountService;
this.currentSelections = currentSelections;
this.multipleCurrenciesForDepot = multipleCurrenciesForDepot;
}
#Override
protected Iterator getItemModels() {
List<FinanceAccount> financeAccounts = ownedAccountService.getFinanceAccounts(currentSelections.getSelectedOwnedAccount());
return new ModelIteratorAdapter<FinanceAccount>(financeAccounts.iterator()) {
#Override
protected IModel<FinanceAccount> model(FinanceAccount object) {
return new CompoundPropertyModel<FinanceAccount>((object));
}
};
}
#Override
protected void populateItem(Item<FinanceAccount> item) {
final FinanceAccount financeAccount = item.getModelObject();
item.add(new EnumDropDownChoice<MoneyCurrency>("forCurrency"));
item.add(new TextField("accountNumber"));
item.add(new OwnedAccountDropDownChoice("accountForCash", currentSelections.getSelectedLegalEntity().getOwnedBankAccounts()));
item.add(new AjaxButton("deleteFinanceAccount") {
#Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
//TODO Delete finance account
ownedAccountService.deleteFinanceAccount(currentSelections.getSelectedOwnedAccount(), financeAccount);
target.add(multipleCurrenciesForDepot);
}
#Override
protected void onError(AjaxRequestTarget target, Form<?> form) {
//TODO create error message
}
});
}
}
CurrentSelections needs to model if its going to be used and mutiplated by two different wicket page/components.
For example theres a parent page, which has a constructor, a new String object and a Panel that uses the String object as a parameter,
public class ParentPage extends WebPage {
public ParentPage() {
String string = new String("Dave");
add(new Panel("childPanel", string));
string = new String("Brian");
}
}
If the string object is updated after the panel has been added, then that updated string isn't what the panel has. What the panel has is "Dave" when thought the ParentPage now has the string as "Brian".
But if we made a model and made it use the string object then when we change the string the childPanel will get the update.
public class ParentPage extends WebPage {
public ParentPage() {
String string = new String("Dave");
IModel model = new Model(string);
add(new Panel("childPanel", model));
model.setObject(new String("Brian"));
}
}
Its a very simple example but i hope it helps.
I saved the model reference passed in the constructor to the panel, so far its working ok.
BankAccountPanel bankAccountPanel = new BankAccountPanel("bankAccountPanel", getModel());
add(bankAccountPanel);
...
public class BankAccountPanel extends Panel {
IModel<OwnedAccount> iModel;
public BankAccountPanel(String id, IModel<OwnedAccount> model) {
super(id, model);
this.iModel = model;
init();
}

Multiple TextField in Wicket Form

I have a bean, which I attached to the form using Model and it works fine. Also I have a field in bean like Map<String, javax.mail.Address>. How can I bind this field with form by model considering that every map entry should be like Label: TextField?
Thanks in advance.
Maybe something like:
ListView<String> textAreasListView = new ListView<String>("someid", bean.map.keySet()) {
#Override
protected void populateItem(final ListItem<String> itemLang) {
itemLang.add(new Label("label", itemLang.getModelObject()));
Model<String> textModel = new Model<String>() {
#Override
public String getObject() {
return bean.map.get(itemLang.getModelObject()).toString;
}
#Override
public void setObject(String object) {
bean.map.put(itemLang.getModelObject(), new Address(object));
}
};
itemLang.add(new TextField<String>("email", textModel));
}
};
and add this to your form.
A custom converter for Address & String would be an additional improvement.
I think, you could bind it in such a way:
TextField<Address> textField = new TextField<Address>("address",
beanModel.<Address> bind("addressMap[addressKey]"), Address.class) {
#SuppressWarnings("unchecked")
#Override
public <C> IConverter<C> getConverter(Class<C> type) {
if (Address.class.isAssignableFrom(type)) {
return (IConverter<C>) new AddressConverter();
} else {
return super.getConverter(type);
}
}
};
form.add(textField);
Here "addressMap" is name of the map field, "addressKey" is the key of the address value in the map.
The listing for AddressConverter class:
public class AddressConverter implements IConverter<Address> {
public Address convertToObject(String string, Locale locale) {
try {
return new InternetAddress(string);
} catch (AddressException e) {
return null;
}
}
public String convertToString(Address address, Locale locale) {
return address.toString();
}
}
Of course, converter for Address class may be attached globally with the ConverterLocator. Just add the following method to your Application class:
#Override
protected IConverterLocator newConverterLocator() {
ConverterLocator locator = new ConverterLocator();
locator.set(Address.class, new AddressConverter());
return locator;
}
And then you don't need to override getConverter() method on the all text fields.

Categories