-
Notifications
You must be signed in to change notification settings - Fork 119
How to use MVP for panels
Before you create your Model-View-Presenter based panel, we assume you have already read the How to create your first panel and How to create your second panel pages. If not we recommend you read those first to better familiarize yourself.
We also assume that, at this point, you already have followed the build instructions from Home page and you already have UberFire showcase up and running.
You will no doubt remember that the panel class created in How to create your first panel did not extend any class or implement any interface. The view was provided for with the @WorkbenchPartView
annotation.
Let's look at an adaptation of the original code to make it more interesting for the example we are discussing here (for brevity we show the minimum permissible code):-
@Dependent
@WorkbenchScreen(identifier = "MyFirstMVPPanel")
public class MyFirstMVPPanel {
private final TextBox textBox = new TextBox();
@PostConstruct
private void init() {
textBox.setText( "Hello World!" );
}
@WorkbenchPartTitle
public String myTitle() {
return "My First MVP Panel!";
}
@WorkbenchPartView
public IsWidget getView() {
return textBox;
}
}
The example class above, whilst not extending a UI component, still has the View embedded within it.
Let's change that by moving the View into a separate class.
Presenter
@Dependent
@WorkbenchScreen(identifier = "MyFirstMVPPanel")
public class MyFirstMVPPanelPresenter {
public interface View
extends
IsWidget {
void setText(String text);
}
@Inject
public View view;
@PostConstruct
private void init() {
view.setText( "Hello World!" );
}
@WorkbenchPartTitle
public String myTitle() {
return "My First MVP Panel!";
}
@WorkbenchPartView
public IsWidget getView() {
return view;
}
}
View
public class MyFirstMVPPanelView extends Composite
implements MyFirstMVPPanelPresenter.View {
private TextBox textBox = new TextBox();
public MyFirstMVPPanelView() {
initWidget( label );
}
@Override
public void setText(final String text) {
textBox.setText( text );
}
}
That's great, we've made a View that takes all of it's orders from the Presenter. Our view is a kindred spirit and wants to be able to tell the Presenter a thing or two; like when the user has interacted with it in such a way that the Presenter need know.
With all our CDI @Inject
'able loveliness there is a limitation. Client-side CDI only has two scopes: @Application
and @Dependent
.
If a single instance of our panel is itself scoped to the whole application we can @Inject
the Presenter into the view as both will be @ApplicationScoped
. No problem.
Beans scoped @Dependent
however have a new instance injected at every injection point. On face value this should not present a problem: You have a View and you have a Presenter. Every instance of a View needs an instance of the Presenter. Why not inject the Presenter into the View?
The problem is that one instance of your Presenter is injected into Uberfire (so it can delegate calls to your methods) and also another instance injected into your View. That is two instances. This can lead to unexpected interactions between Presenter and View and hard to find bugs.
To overcome this limitation we provide an Uberview<T>
interface that extends IsWidget
and allows UberFire to pass a Presenter to the View as illustrated with the following updated code fragments.
Presenter
@Dependent
@WorkbenchScreen(identifier = "MyFirstMVPPanel")
public class MyFirstMVPPanelPresenter {
public interface View
extends
UberView<MyFirstMVPPanelPresenter> {
void setText(String text);
}
@Inject
public View view;
@PostConstruct
private void init() {
view.setText( "Hello World!" );
}
@WorkbenchPartTitle
public String myTitle() {
return "My First MVP Panel!";
}
@WorkbenchPartView
public IsWidget getView() {
return view;
}
public void valueChanged( final String value ) {
//Do something useful
}
}
View
public class MyFirstMVPPanelView extends Composite
implements MyFirstMVPPanelPresenter.View {
private TextBox textBox = new TextBox();
private MyFirstMVPPanelPresenter presenter;
public MyFirstMVPPanelView() {
initWidget( label );
}
@Override
public void init(final MyFirstMVPPanelPresenter presenter) {
this.presenter = presenter;
}
@Override
public void setText(final String text) {
textBox.setText( text );
}
}
Finally we can invoke methods on our presenter as we may deem appropriate:-
View
public class MyFirstMVPPanelView extends Composite
implements MyFirstMVPPanelPresenter.View {
private TextBox textBox = new TextBox();
private MyFirstMVPPanelPresenter presenter;
public MyFirstMVPPanelView() {
initWidget( label );
}
@Override
public void init(final MyFirstMVPPanelPresenter presenter) {
this.presenter = presenter;
setupUiEventHandlers();
}
@Override
public void setText(final String text) {
textBox.setText( text );
}
private void setupUiEventHandlers() {
textBox.addValueChangeHandler(new ValueChangeHandler<String>() {
@Override
public void onValueChange( ValueChangeEvent<String> event ) {
presenter.valueChanged( event.getValue() );
}
});
}
}