The LoginForm component is a login form that allows a password manager in the web browser to remember and later automatically fill in the username and password. This commonly used functionality does not work with regular Vaadin components and is a common problem with Ajax applications.

// A wrapper with a caption for the login form
Panel loginPanel = new Panel("Login");
loginPanel.setWidth("250px");
        
LoginForm login = new LoginForm();
loginPanel.addComponent(login);

The resulting form is shown in Figure 5.69, “The LoginForm Component”.


The LoginForm uses static HTML inside an iframe element to enable the functionality. The component provides a default implementation of the static HTML; if you want to change the layout, you need to reimplement the getLoginHtml() method.

The login form has by default 100%x100% relative size, taking all the space given by the containing layout. You may set the size to fixed values, but not undefined in either direction, because the contained iframe element takes all of this size (it also has 100%x100% size). How the actual form uses this space depends on the static HTML. Giving too little space for the form results in scroll bars.

Customization of the login form is necessary, for example, if you need to change the layout or internationalize the form. Customization is done by overriding the getLoginHtml() method, which returns the static HTML of the form. The customization layer is very "unvaadin"-like, and at best hack-ish, but dictated by the form management in browsers.

Let us look at a custom login form that lets the user of the form to give the field captions:

class MyLoginForm extends LoginForm {
    String usernameCaption;
    String passwordCaption;
    String submitCaption;
    
    public MyLoginForm(String usernameCaption,
            String passwordCaption, String submitCaption) {
        this.usernameCaption = usernameCaption;
        this.passwordCaption = passwordCaption;
        this.submitCaption  = submitCaption;
    }

Then we override the method that generates the static HTML for the form:

@Override
protected byte[] getLoginHTML() {
    // Application URI needed for submitting form
    String appUri = getApplication().getURL().toString()
            + getWindow().getName() + "/";

    String x, h, b; // XML header, HTML head and body

The XML header is needed for the validity of the XHTML page:

    x = "<!DOCTYPE html PUBLIC \"-//W3C//DTD "
      + "XHTML 1.0 Transitional//EN\" "
      + "\"http://www.w3.org/TR/xhtml1/"
      + "DTD/xhtml1-transitional.dtd\">\n";

Notice that it is important to have a newline (\n) at the end of the XML header line.

The HTML header part contains JavaScript definitions that handle submitting the form data. It also copies the style sheet references from the parent window.

    h = "<head><script type='text/javascript'>"
      + "var setTarget = function() {"
      + "  var uri = '" + appUri + "loginHandler';"
      + "  var f = document.getElementById('loginf');"
      + "  document.forms[0].action = uri;"
      + "  document.forms[0].username.focus();"
      + "};"
      + ""
      + "var styles = window.parent.document.styleSheets;"
      + "for(var j = 0; j < styles.length; j++) {\n"
      + "  if(styles[j].href) {"
      + "    var stylesheet = document.createElement('link');\n"
      + "    stylesheet.setAttribute('rel', 'stylesheet');\n"
      + "    stylesheet.setAttribute('type', 'text/css');\n"
      + "    stylesheet.setAttribute('href', styles[j].href);\n"
      + "    document.getElementsByTagName('head')[0]"
      + "                .appendChild(stylesheet);\n"
      + "  }"
      + "}\n"
      + "function submitOnEnter(e) {"
      + "  var keycode = e.keyCode || e.which;"
      + "  if (keycode == 13) {document.forms[0].submit();}"
      + "}\n"
      + "</script>"
      + "</head>";

The HTML body element contains the actual form. Notice that it is contained within an inner iframe. The form and the button must have JavaScript calls to submit the form content.

    b = "<body onload='setTarget();'"
      + "  style='margin:0;padding:0; background:transparent;'"
      + "  class='"
      + ApplicationConnection.GENERATED_BODY_CLASSNAME + "'>"
      + "<div class='v-app v-app-loginpage'"
      + "     style='background:transparent;'>"
      + "<iframe name='logintarget' style='width:0;height:0;"
      + "border:0;margin:0;padding:0;'></iframe>"
      + "<form id='loginf' target='logintarget'"
      + "      onkeypress='submitOnEnter(event)'"
      + "      method='post'>"
      + "<table>"
      + "<tr><td>" + usernameCaption + "</td>"
      + "<td><input class='v-textfield' style='display:block;'"
      + "           type='text' name='username'></td></tr>"
      + "<tr><td>" + passwordCaption + "</td>"
      + "    <td><input class='v-textfield'"
      + "          style='display:block;' type='password'"
      + "          name='password'></td></tr>"
      + "</table>"
      + "<div>"
      + "<div onclick='document.forms[0].submit();'"
      + "     tabindex='0' class='v-button' role='button'>"
      + "<span class='v-button-wrap'>"
      + "<span class='v-button-caption'>"
      + submitCaption + "</span>"
      + "</span></div></div></form></div></body>";

Then combine and return the page as a byte array.

    return (x + "<html>" + h + b + "</html>").getBytes();
}

We can use the custom login form as follows:

MyLoginForm loginForm = new MyLoginForm("Name of the User",
        "A passing word", "Login Me Now");

The customized LoginForm is shown in Figure 5.70, “Customizing the LoginForm.