Skip to content
This repository has been archived by the owner on Feb 9, 2021. It is now read-only.

How to use MVP for panels

manstis edited this page Nov 2, 2012 · 7 revisions

Before you begin...

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.

1. Annotate the Presenter

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;
    }

}

2. Separate the View

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 );
    }

}

3. It's a two-way street

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() );
            }

        });
    }

}