Client-side UI logic without server-side roundtrip

There exists some “edge” use cases that can benefit of pushing tiny part of application logic into the client-side. This means setting up part of your UI in such way that no server-side communication is required afterwards. Here’s an simple example how to do it with Vaadin using pure Java thanks to GWT.

This example consists of two GWT’s ListBoxes and one GWT’s Button widgets. The ListBoxes interact with each other on the client-side without communication to the server-side. The Button flushes client-side state back to the server-side.

Server-side UI component:


package com.example.component.test;

import java.util.Map;

import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
import com.vaadin.ui.AbstractComponent;

/**
 * Server side component for the VTestComponent widget.
 */
@com.vaadin.ui.ClientWidget(com.example.test.client.ui.VClientLogicComponent.class)
public class ClientLogicComponent extends AbstractComponent {

	private int value;

	public int getValue() {
		return value;
	}

	public void setValue(int value) {
		this.value = value;
		// If server-side state is changed then update client-side
		requestRepaint();
	}

	@Override
	public void paintContent(PaintTarget target) throws PaintException {
		super.paintContent(target);
		// Define items and selection for GWT's ListBox from the server-side
		target.addVariable(this, "items", new String[] { "A1", "A2", "A3" });
		target.addVariable(this, "selected", value);
	}

	/**
	 * Receive and handle events and other variable changes from the client.
	 * 
	 * {@inheritDoc}
	 */
	@Override
	public void changeVariables(Object source, Map<String, Object> variables) {
		super.changeVariables(source, variables);

		if (variables.containsKey("listboxA")) {
			// Process GWT's button click client-side event on the server-side
			value = (Integer) variables.get("listboxA");
			getApplication().getMainWindow().showNotification("Got " + value);
		}
	}

}

And the client-side counterpart for your UI component


package com.example.test.client.ui;

import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.ListBox;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.UIDL;
import com.vaadin.terminal.gwt.client.VConsole;

/**
 * Client side widget which communicates with the server. Messages from the
 * server are shown as HTML and mouse clicks are sent to the server.
 */
public class VClientLogicComponent extends HorizontalPanel implements Paintable {

	protected String paintableId;
	protected String uidlId;
	protected ApplicationConnection client;

	private ListBox listboxA;
	private ListBox listboxB;
	private Button button;

	public VClientLogicComponent() {
		wireClientSideLogic();
	}

	private void wireClientSideLogic() {
		// Populated from the server-side
		listboxA = new ListBox();

		listboxB = new ListBox();
		listboxB.addItem("B First");
		listboxB.addItem("B Second");
		listboxB.addItem("B Third");

		button = new Button("Flush state to server-side");

		add(listboxA);
		add(listboxB);
		add(button);

		// Simple UI logic that happens on the client-side only
		listboxA.addChangeHandler(new ChangeHandler() {
			public void onChange(ChangeEvent event) {
				int valueForB = listboxA.getSelectedIndex() + 1;
				if (valueForB > listboxB.getItemCount() - 1)
					valueForB = 0;
				listboxB.setSelectedIndex(valueForB);
			}
		});
		// Clicking button flushes client-side state back to server-side
		button.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				VConsole.log("Flushing");
				client.updateVariable(uidlId, "listboxA",
						listboxA.getSelectedIndex(), true);
			}
		});
	}

	/**
	 * Called whenever an update is received from the server
	 */
	public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
		uidlId = uidl.getId();
		this.client = client;
		// populate listboxA
		listboxA.clear();
		for (String value : uidl.getStringArrayVariable("items")) {
			listboxA.addItem(value);
		}
		listboxA.setSelectedIndex(uidl.getIntArrayVariable("selected")[0]
);
	}
}

The client-side code contains simple logic that changes ListBoxB value whenever ListBoxA value is changed without communication to the server-side. And when you click the Button, state is flushed back to the server-side explicitly.

Please note that

  1. any logic you push to the client-side is not secure

  2. you have to send all required data for the client-side in advance, you lose the benefit of lazy loading OR you have to code it yourself

  3. typically this involves “double” coding because any logic you code to the client-side need to be double checked on the server-side

  4. more lines of code because GWT is considerably more closer to the harder programming model (DOM / JavaScript / HTML / Browser differences)

  5. you most likely need to theme GWT components to match them to Vaadin theme

So this example is for rare edge cases, not a typical way to code your application with Vaadin :slight_smile: