Wednesday, March 21, 2012

Beyond Hello, world

Many application frameworks come with a 'Hello, world' application, and almost every time I have built it and stared at it, wondering what do do next.
I have given up on Struts, JSF, XSLT and a few others because I could not for the life of me figure out how to manage application complexity using any of them.
If you ever experienced something like that, then this may just be for you.
Enter Vaadin
Finally, a web application framework has emerged that lets me do what I want!
Even though I am a total newbie when it comes to Vaadin, I hope to show you how to create a complex Model-View-Controller application with several layers of views, at least in principle.

I will start by showing step-by-step how to get Vaadin up and running, all the way to the 'Hello' app, and I will continue to show you how I proceeded to add sub-views to my application.

Preparation
In the following, you are expected to know some Java and to know how to extend your favorite Java IDE with plugins.
I use Netbeans, even though I know I should be using Eclipse.
If you are an Eclipse user, some parts may easier than they are with Netbeans, especially regarding plugins.

You need:

  1. A Java IDE

  2. Vaadin

  3. You may also like a plugin

After downloading Vaadin and installing the plugin, you should be good to go, creating the first Vaadin application.
Create a new project, select Web Application.


Call it VaadinFirst



Then, just accept the defaults in the next screen


The next screen lets you choose a framework, and if your plugin installation has gone OK, you should tick 'Vaadin' and accept the proposed defaults.



Now, the project should appear.


Open the file called MyApplication.java and verify that it looks something like this:

/*
* MyApplication.java
*
* Created on March 21, 2012, 12:32 PM
*/

package com.example.vaadin;

import com.vaadin.Application;
import com.vaadin.ui.Label;
import com.vaadin.ui.Window;
import java.util.logging.Logger;
/**
*
* @author atle
* @version
*/

public class MyApplication extends Application {
private static final long serialVersionUID = 1L;

@Override
public void init() {
Window mainWindow = new Window("MyApplication");
Label label = new Label("Hello Vaadin user");
mainWindow.addComponent(label);
setMainWindow(mainWindow);
}
private static final Logger LOG = Logger.getLogger(MyApplication.class.getName());

}


If you have set up an application server (I use Glassfish), you should be able to run the project.


If the project does not run, you may have to set up an application server.

If you do not see the application VaadinFirst, try removing the server and adding it back in.

Adding some code
First, add packages model and view, we will use MyApplication.java as controller.



Under view, create a class PanelPersonalInfo.



Edit PanelPersonalInfo.java to look something like this:

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.example.vaadin.view;

import com.vaadin.ui.Panel;
import java.util.logging.Logger;

/**
*
* @author atle
*/
public class PanelPersonalInfo extends Panel {
private static final Logger LOG = Logger.getLogger(PanelPersonalInfo.class.getName());
private static final long serialVersionUID = 1L;

}



Create a class PersonalInfo, something like shown below:

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.example.vaadin.model;

import java.util.logging.Logger;

/**
*
* @author atle
*/
public class PersonalInfo {
private static final Logger LOG = Logger.getLogger(PersonalInfo.class.getName());
private String name;
private String email;

/**
* @return the name
*/
public String getName() {
return name;
}

/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}

/**
* @return the email
*/
public String getEmail() {
return email;
}

/**
* @param email the email to set
*/
public void setEmail(String email) {
this.email = email;
}
}


Do not add any methods except getters and setters, possibly a toString() for debugging purposes.
If you need a class that supports complex methods, create a class PersonalInfoHelper like this:

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.example.vaadin.model;

import java.util.logging.Logger;

/**
*
* @author atle
*/
public class PersonalInfoHelper {
private static final Logger LOG = Logger.getLogger(PersonalInfoHelper.class.getName());
private PersonalInfo personalInfo;

public PersonalInfoHelper(PersonalInfo personalInfo) {
this.personalInfo = personalInfo;
}

}


Throw them both in the model layer.
The reason for using a helper class to access an entity it to avoid clogging up the class when using web services for storage/retrieval.

Now, extend PanelPersonalInfo with some event handling, define an event and a handler. Create a package called events and define the two classes below:

public class PersonalInfoEvent{
private PersonalInfoHelper personalInfoHelper;

public PersonalInfoEvent(PersonalInfoHelper personalInfoHelper) {
this.personalInfoHelper = personalInfoHelper;
}

/**
* @return the personalInfoHelper
*/
public PersonalInfoHelper getPersonalInfoHelper() {
return personalInfoHelper;
}

}

public abstract class PersonalInfoEventHandler{
public abstract void eventOccurred(PersonalInfoEvent evt);
}



Then add a handler list to PersonalInfoPanel.


/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.example.vaadin.view;

import com.example.vaadin.model.PersonalInfoHelper;
import com.vaadin.ui.Panel;
import java.util.ArrayList;
import java.util.logging.Logger;

/**
*
* @author atle
*/
public class PanelPersonalInfo extends Panel {
private static final Logger LOG = Logger.getLogger(PanelPersonalInfo.class.getName());
private static final long serialVersionUID = 1L;

private ArrayList handlerList = new ArrayList();


public void addHandler(PersonalInfoEventHandler h){
handlerList.add(h);
}

public void removeHandler(PersonalInfoEventHandler h){
handlerList.remove(h);
}

}


PanelPersonalInfo now has what it takes to report back to the controller, in our case, MyApplication.


Widgets!

For MyApplication to be able to display an instance of PersonalInfo, we let PanelPersonalInfo contain an instance of PersonalInfoHelper.
We then create a method setPersonalInfoHelper(PersonalInfoHelper pih) that updates the widgets.
We will also justify the use of PersonalInfoHelper by modifying PersonalInfo a little bit:

public class PersonalInfo {
private static final Logger LOG = Logger.getLogger(PersonalInfo.class.getName());
private String firstName;
private String middleName;
private String lastName;
private String email;

...


PersonalInfo no longer has a method called getName(), so PersonalInfoHelper stitches together the name from its components. It also tries to 'parse' a name string and spilt it into first,middle and last name, or rather, that is left as an exercise :)


public class PersonalInfoHelper {
private static final Logger LOG = Logger.getLogger(PersonalInfoHelper.class.getName());
private PersonalInfo personalInfo;

public PersonalInfoHelper(PersonalInfo personalInfo) {
this.personalInfo = personalInfo;
}

public String getName() {
String rv = personalInfo.getFirstName();
if (personalInfo.getMiddleName() != null && ! personalInfo.getMiddleName().isEmpty()){
rv += " " + personalInfo.getMiddleName();
}
rv += " " + personalInfo.getLastName();
return rv;
}


You should create a class that stores and retrieves data, and put that in the model package.

Seeing it in action!
I will now show the entire PanelPersonalInfo class with a few comments.

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.example.vaadin.view;

import com.example.vaadin.events.PersonalInfoEvent;
import com.example.vaadin.events.PersonalInfoEventHandler;
import com.example.vaadin.model.PersonalInfo;
import com.example.vaadin.model.PersonalInfoHelper;
import com.vaadin.event.FieldEvents;
import com.vaadin.event.FieldEvents.TextChangeEvent;
import com.vaadin.ui.GridLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Panel;
import com.vaadin.ui.TextField;
import java.util.ArrayList;
import java.util.logging.Logger;

/**
*
* @author atle
*/
public class PanelPersonalInfo extends Panel {

private static final Logger LOG = Logger.getLogger(PanelPersonalInfo.class.getName());
private static final long serialVersionUID = 1L;
private PersonalInfoHelper personalInfoHelper;
private ArrayList handlerList = new ArrayList();
private Label labelName;
private TextField textFieldName;
private Label labelEmail;
private TextField textFieldEmail;

// Whenever we set a value on a widget, we trigger the widget's event handlers.
// Since we want to process the data and then set them back into the panel, we need to disable
// callback on all the setXXX() functions in out panel (see the callBack() function.)
private Boolean disableCallback = false;

public PanelPersonalInfo(String caption) {
super(caption);
createWidgets();
}

private void createWidgets() {
labelName = new Label("Name");
textFieldName = new TextField();
textFieldName.setImmediate(true);
textFieldName.addListener(new FieldEvents.TextChangeListener() {

private static final long serialVersionUID = 1L;

@Override
public void textChange(TextChangeEvent event) {
personalInfoHelper.setName(event.getText());
}
});
labelEmail = new Label("Email");
textFieldEmail = new TextField();
textFieldEmail.setImmediate(true);
textFieldEmail.addListener(new FieldEvents.TextChangeListener() {

private static final long serialVersionUID = 1L;

@Override
public void textChange(TextChangeEvent event) {
personalInfoHelper.setEmail(event.getText());
callBack(); // If we had a PanelButtons as a sub-panel, we would only callBack when user pressed one.
}
});
GridLayout layout = new GridLayout(2, 2);
layout.addComponent(labelName);
layout.addComponent(textFieldName);
layout.addComponent(labelEmail);
layout.addComponent(textFieldEmail);
addComponent(layout);
}

public void addHandler(PersonalInfoEventHandler h) {
handlerList.add(h);
}

public void removeHandler(PersonalInfoEventHandler h) {
handlerList.remove(h);
}

/**
* Callback when the user makes a change
*/
private void callBack() {
if (!disableCallback) {
for (PersonalInfoEventHandler h : handlerList) {
h.eventOccurred(new PersonalInfoEvent(personalInfoHelper));
}
}
}

/**
* @return the personalInfoHelper
*/
public PersonalInfoHelper getPersonalInfoHelper() {
// Here, you could get the data from the widgets before returning
return personalInfoHelper;
}

/**
* @param personalInfoHelper the personalInfoHelper to set
*/
public void setPersonalInfoHelper(PersonalInfoHelper personalInfoHelper) {
this.personalInfoHelper = personalInfoHelper;
disableCallback = true; // Don't bubble events back up to parent. Chances are that the parent's
// event handling results in this method being called, resulting in
// a funny, blinking screen :)
textFieldName.setValue(personalInfoHelper.getName());
textFieldEmail.setValue(personalInfoHelper.getEmail());
disableCallback = false; // We manipulated the widgets, turn events back on.
}
}


All that remains, is to hook it up to MyApplication:

/*
* MyApplication.java
*
* Created on March 21, 2012, 12:32 PM
*/
package com.example.vaadin;

import com.example.vaadin.events.PersonalInfoEvent;
import com.example.vaadin.events.PersonalInfoEventHandler;
import com.example.vaadin.model.PersonalInfo;
import com.example.vaadin.model.PersonalInfoHelper;
import com.example.vaadin.view.PanelButtons;
import com.example.vaadin.view.PanelPersonalInfo;
import com.vaadin.Application;
import com.vaadin.ui.Window;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
*
* @author atle
* @version
*/
public class MyApplication extends Application {

private static final long serialVersionUID = 1L;
PersonalInfoHelper personalInfoHelper = new PersonalInfoHelper(new PersonalInfo()); // Create a Database class to store/retrieve
@Override
public void init() {
Window mainWindow = new Window("First Vaadin Application");
PanelPersonalInfo panelPersonalInfo = new PanelPersonalInfo("Personal Information");
panelPersonalInfo.addHandler(new PersonalInfoEventHandler() {

@Override
public void eventOccurred(PersonalInfoEvent evt) {
LOG.log(Level.INFO, "Got a PersonInfo event:{0} with data:{1}", new Object[]{evt.toString(), evt.getPersonalInfoHelper()});
}
});
personalInfoHelper.setName("Jan Atle Ramsli");
personalInfoHelper.setEmail("jaramsli@gmail.com");
panelPersonalInfo.setPersonalInfoHelper(personalInfoHelper);
mainWindow.addComponent(panelPersonalInfo);

PanelButtons panelButtons = new PanelButtons();

mainWindow.addComponent(panelButtons);
setMainWindow(mainWindow);
}
private static final Logger LOG = Logger.getLogger(MyApplication.class.getName());
}


Run the app, and you should see something like this:



What now?

Now you can create an address panel, and then you can let one person have multiple addresses, eg. home, work, billing, etc. For each Address, you could create a PanelAddress and call setAddress(AddressHelper ah) on it.
To contain the addresses you could use a TabSheet
The address panels would then bubble the address part of PersonalInfo up PanelPersonalInfo.
A Simplification
For a simple application, you could replace the handler list with abstract methods.

public abstract class LoginScreen extends Window {

private MyApplication myApplication;
private static final long serialVersionUID = 1L;
private static final Logger LOG = Logger.getLogger(LoginScreen.class.getName());
private String userName;
private String password;
private TextField textFieldUserName;
private TextField textFieldPassword;
private Button buttonLogin;
private Button buttonCancel;
private Label labelPopup;

public abstract void ok(String userName, String password);

public abstract void cancel();

public abstract void sendNewPassword(String email);
...
buttonLogin.addListener(new ClickListener() {

private static final long serialVersionUID = 1L;

@Override
public void buttonClick(ClickEvent event) {
setVisible(false);
ok(userName,password);
}
});

The next step would be to add some Input Validation to your app ...

Disclaimer
This example was thrown together in a hurry, so it will be full of errors - but I may find the time to get back to it.

Watch this space :)

Atle jaramsli (at) gmail.com

2 comments:

  1. Brilliant!! Works glitch free.

    ReplyDelete
    Replies
    1. Glad to hear it! Will refine it a little bit as I get time.

      Delete