Blog-Archiv

Sonntag, 19. Februar 2017

MVC Vaadin Webapp Java Example

When you followed my recent Blog series about MVC and MVP you might think that MVP is for the web and MVC is for desktop apps. But that's not true, it depends on the web technology you use. MVP is more abstract, but both can be used in web applications.

In this Blog I'll show a web app that works with MVC. The technology I will use for this is Vaadin. Vaadin builds on top of GWT, and it has become a major player concerning desktop-like web apps. It is a server-oriented framework. Vaadin applications are Java and CSS, nothing else (no XML!).

Vaadin is open source, as is Maven. These two work together closely. Go to Maven's web-page and download and install the most recent version. You should put $MAVEN_HOME/bin into your operating system PATH environment variable. Then you can test Maven by entering mvn -version in a terminal window.

Open a terminal window and change to your Java workspace directory. In a browser window, go to the Vaadin page and copy the Maven archetype command line from there. Paste the archetype command in the terminal window and press ENTER. Maven will now create a directory "vaadin-app" (or whatever name you wrote in the command line) and build a demo project therein. After that, change into the "vaadin-app" directory and run mvn install to build the project.

In the following you will replace org.test.MyUI by vaadin.examples.ExamplesUi. You should remove then org.test.MyUI, to avoid URL pattern conflicts.

Demo Webapp

Here is the screenshot of the temperature application under Vaadin. Again the "Reset" button is not part of the MVC, it's just there to set a new model into the MVC.

Not much difference to the desktop application. The URL address line looks a little complicated. It denotes a Vaadin UI that can render any Vaadin Component constructed from the class given in URL parameter "class". That way you can quickly view different panels you are working on with just one Vaadin application. See below for its source code.

Package Structure

This is a standard Maven directory structure.

Demo Sources

Here is the ExamplesUi servlet that can render different Vaadin Components:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package vaadin.examples;

import javax.servlet.annotation.WebServlet;
import com.vaadin.annotations.*;
import com.vaadin.server.*;
import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.ui.*;

/**
 * Vaadin application main.
 * Delegates to various pages named in URL parameter, like
 * http://localhost:8080/ExamplesUi?class=fully.qualified.ClassName
 */
public class ExamplesUi extends UI
{
    @Override
    protected void init(VaadinRequest request) {
        final String urlParameter = request.getParameter("class");
        if (urlParameter != null) {
            try {
                final Class<?> clazz = Class.forName(urlParameter);
                final Component component = (Component) clazz.newInstance();
                setContent(component);
            }
            catch (Exception e) {
                Notification.show(e.toString(), Notification.Type.ERROR_MESSAGE);
            }
        }
    }

    @WebServlet(
            urlPatterns = { "/ExamplesUi/*", "/VAADIN/*" },
            name = "ExamplesUiServlet",
            asyncSupported = true)
    @VaadinServletConfiguration(
            ui = ExamplesUi.class,
            productionMode = false)
    public static class ExamplesUiServlet extends VaadinServlet
    {
    }
}

You see that the view is built generically by reflection. Any panel that you want to view here must be a Vaadin Component.

The newest Vaadin builds on the latest J2EE standards and requires no more web.xml editing. Now you can do it with the @WebServlet annotation. Vaadin will find it automatically. Mind that you need more than one servlet urlPatterns when not referring to "/*".

After you have put this class into package vaadin.examples, you can compile via

mvn install -DskipTests

and test if the web app is reachable after running

mvn jetty:run

Terminate by pressing Ctl-C. If you want to debug, install the Jetty-plugin into Eclipse, it will automatically find your Vaadin project and add a launcher for it.


And here is the source code for the Demo application that is rendered when you type

http://localhost:8080/ExamplesUi?class=vaadin.examples.mvc.model2views.viewimpl.Demo

in your browser's address line (after starting the Vaadin application with a web-server).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package vaadin.examples.mvc.model2views.viewimpl;

import vaadin.examples.mvc.model2views.*;
import com.vaadin.ui.*;

public class Demo extends VerticalLayout
{
    public Demo() {
        setSizeFull();
        setMargin(true);
        
        // build the MVP
        final TemperatureModel model = new TemperatureModel();
        final VaadinTemperatureView view = new VaadinTemperatureView();
        final TemperatureController<Component> controller = new TemperatureController<>(view);
        // set an initial model value
        model.setTemperatureInCelsius(21);
        controller.setModel(model);
        
        final Component component = view.getAddableComponent();
        addComponent(component);
        setComponentAlignment(component, Alignment.MIDDLE_CENTER);
        setExpandRatio(component, 1.0f);
        
        // add a button that sets a new model into the MVP
        final Button resetButton = new Button("Reset");
        resetButton.addClickListener(new Button.ClickListener() {
            @Override
            public void buttonClick(Button.ClickEvent event) {
                controller.setModel(new TemperatureModel());
            }
        });
        addComponent(resetButton);
        setComponentAlignment(resetButton, Alignment.MIDDLE_CENTER);
    }
}

Very similar to its Swing sister. To rewrite it from Swing to Vaadin was as easy as to rewrite from Swing to AWT.

Mind that this class resides in the viewimpl package because it is windowing-system dependent.

MVC Sources

Model, Controller and View Interface

Please fetch their source code from my Blog about MVC. All three can be reused as they are, they do not depend on any windowing-system. Import them, or copy their source into the Vaadin project. (Mind that I refactored them in my Review Blog.)

Vaadin View Implementations

These were ported from Swing to Vaadin very quickly. So I won't lose much words about it, here are the sources. Refer to my Blog where I explain the Swing classes.

Here is the view interface implementation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package vaadin.examples.mvc.model2views.viewimpl;

import vaadin.examples.mvc.model2views.*;
import com.vaadin.ui.*;

public class VaadinTemperatureView implements TemperatureView<Component>
{
    private final AbstractOrderedLayout view;
    private final VaadinTemperatureField celsius;
    private final VaadinTemperatureField fahrenheit;

    public VaadinTemperatureView() {
        celsius = new VaadinCelsiusField();
        fahrenheit = new VaadinFahrenheitField();

        view = new HorizontalLayout();
        view.addComponent(celsius.getAddableComponent());
        view.addComponent(fahrenheit.getAddableComponent());
    }

    @Override
    public void addListener(TemperatureView.Listener listener) {
        celsius.addListener(listener);
        fahrenheit.addListener(listener);
    }

    @Override
    public Component getAddableComponent() {
        return view;
    }

    @Override
    public void setModel(TemperatureModel model) {
        celsius.setModel(model);
        fahrenheit.setModel(model);
    }
}

The field abstraction:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package vaadin.examples.mvc.model2views.viewimpl;

import java.util.*;
import vaadin.examples.mvc.model2views.*;
import com.vaadin.data.Property;
import com.vaadin.ui.*;

abstract class VaadinTemperatureField implements TemperatureModel.Listener
{
    private final TextField temperature;
    private final AbstractLayout container;
    private final List<TemperatureView.Listener> listeners = new ArrayList<>();
    private TemperatureModel model;
    
    protected VaadinTemperatureField(String label)    {
        temperature = new TextField();
        
        temperature.addValueChangeListener(new Property.ValueChangeListener() {
            @Override
            public void valueChange(Property.ValueChangeEvent e) {
                final Integer newTemperature = toIntegerWithErrorReporting(temperature.getValue());
                for (TemperatureView.Listener listener : listeners)
                    input(newTemperature, listener);
            }
        });
        
        container = new GridLayout(2, 1);
        container.addComponent(new Label(label));
        container.addComponent(temperature);
    }

    void addListener(TemperatureView.Listener listener)    {
        listeners.remove(listener);
        listeners.add(listener);
    }
    
    void setModel(TemperatureModel model)    {
        if (this.model != null)
            this.model.removeListener(this);
        
        this.model = model;
        model.addListener(this);
        modelChanged(); // refresh view
    }

    Component getAddableComponent() {
        return container;
    }
    
    protected abstract void input(Integer temperature, TemperatureView.Listener listener);

    protected final void output(Integer newTemperature)  {
        temperature.setValue(newTemperature == null ? "" : Integer.toString(newTemperature));
        // setValue() does not trigger valueChange(), thus not recursive
    }
    
    protected final TemperatureModel model()  {
        return model;
    }
    
    private Integer toIntegerWithErrorReporting(String text) {
        if (text.trim().length() <= 0)
            return null;
        
        try {
            return Integer.valueOf(text);
        }
        catch (NumberFormatException e) {
            Notification.show(e.toString(), Notification.Type.ERROR_MESSAGE);
            return null;
        }
    }
}

Due to elegant abstraction, the two field implementations did not change at all:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package vaadin.examples.mvc.model2views.viewimpl;

import vaadin.examples.mvc.model2views.TemperatureView.Listener;

class VaadinCelsiusField extends VaadinTemperatureField
{
    VaadinCelsiusField()   {
        super("Celsius:");
    }

    @Override
    public void modelChanged() {
        output(model().getTemperatureInCelsius());
    }
    
    @Override
    protected void input(Integer celsius, Listener listener) {
        listener.inputCelsius(celsius);
    }
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package vaadin.examples.mvc.model2views.viewimpl;

import vaadin.examples.mvc.model2views.TemperatureView;

class VaadinFahrenheitField extends VaadinTemperatureField
{
    VaadinFahrenheitField()    {
        super("Fahrenheit:");
    }

    @Override
    public void modelChanged() {
        output(model().getTemperatureInFahrenheit());
    }
    
    @Override
    protected void input(Integer fahrenheit, TemperatureView.Listener listener) {
        listener.inputFahrenheit(fahrenheit);
    }
}

This is OO programming for the web. Paste all these codes into their packages, run the Maven compile again, and the restart the Jetty web server. Now you can view the temperature MVC as localhost web application on port 8080. Pressing ENTER will calculate the peer temperature.

Resume

MVC is as useful as MVP in context of a Vaadin web application. The MVC-problems with a web-app begin when you want to listen to each user keypress in the temperature field. This could create an enormous network traffic and thus is not recommendable for web apps.

Still on my MVC Blog schedule:

  1. unit testing with mock views
  2. hierarchical MVC
  3. data binding and other controller refinements



Keine Kommentare: